123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373 |
- #!@PYTHON@
- # Copyright (C) 2010, 2011 Internet Systems Consortium.
- #
- # Permission to use, copy, modify, and distribute this software for any
- # purpose with or without fee is hereby granted, provided that the above
- # copyright notice and this permission notice appear in all copies.
- #
- # THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
- # DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
- # INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
- # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
- # FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
- # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
- # WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- """
- Statistics daemon in BIND 10
- """
- import sys; sys.path.append ('@@PYTHONPATH@@')
- import os
- from time import time, strftime, gmtime
- from optparse import OptionParser, OptionValueError
- import isc
- import isc.util.process
- import isc.log
- from stats_messages import *
- isc.log.init("b10-stats")
- logger = isc.log.Logger("stats")
- # Some constants for debug levels, these should be removed when we
- # have #1074
- DBG_STATS_MESSAGING = 30
- # This is for boot_time of Stats
- _BASETIME = gmtime()
- # for setproctitle
- isc.util.process.rename()
- # If B10_FROM_SOURCE is set in the environment, we use data files
- # from a directory relative to that, otherwise we use the ones
- # installed on the system
- if "B10_FROM_SOURCE" in os.environ:
- SPECFILE_LOCATION = os.environ["B10_FROM_SOURCE"] + os.sep + \
- "src" + os.sep + "bin" + os.sep + "stats" + os.sep + "stats.spec"
- else:
- PREFIX = "@prefix@"
- DATAROOTDIR = "@datarootdir@"
- SPECFILE_LOCATION = "@datadir@" + os.sep + "@PACKAGE@" + os.sep + "stats.spec"
- SPECFILE_LOCATION = SPECFILE_LOCATION.replace("${datarootdir}", DATAROOTDIR)\
- .replace("${prefix}", PREFIX)
- def get_timestamp():
- """
- get current timestamp
- """
- return time()
- def get_datetime(gmt=None):
- """
- get current datetime
- """
- if not gmt: gmt = gmtime()
- return strftime("%Y-%m-%dT%H:%M:%SZ", gmt)
- def parse_spec(spec):
- """
- parse spec type data
- """
- if type(spec) is not list: return {}
- def _parse_spec(spec):
- item_type = spec['item_type']
- if item_type == "integer":
- return int(spec.get('item_default', 0))
- elif item_type == "real":
- return float(spec.get('item_default', 0.0))
- elif item_type == "boolean":
- return bool(spec.get('item_default', False))
- elif item_type == "string":
- return str(spec.get('item_default', ""))
- elif item_type == "list":
- return spec.get(
- "item_default",
- [ _parse_spec(s) for s in spec["list_item_spec"] ])
- elif item_type == "map":
- return spec.get(
- "item_default",
- dict([ (s["item_name"], _parse_spec(s)) for s in spec["map_item_spec"] ]) )
- else:
- return spec.get("item_default", None)
- return dict([ (s['item_name'], _parse_spec(s)) for s in spec ])
- class Callback():
- """
- A Callback handler class
- """
- def __init__(self, command=None, args=(), kwargs={}):
- self.command = command
- self.args = args
- self.kwargs = kwargs
- def __call__(self, *args, **kwargs):
- if not args: args = self.args
- if not kwargs: kwargs = self.kwargs
- if self.command: return self.command(*args, **kwargs)
- class StatsError(Exception):
- """Exception class for Stats class"""
- pass
- class Stats:
- """
- Main class of stats module
- """
- def __init__(self):
- self.running = False
- # create ModuleCCSession object
- self.mccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
- self.config_handler,
- self.command_handler)
- self.cc_session = self.mccs._session
- # get module spec
- self.module_name = self.mccs.get_module_spec().get_module_name()
- self.modules = {}
- self.statistics_data = {}
- # get commands spec
- self.commands_spec = self.mccs.get_module_spec().get_commands_spec()
- # add event handler related command_handler of ModuleCCSession
- self.callbacks = {}
- for cmd in self.commands_spec:
- # add prefix "command_"
- name = "command_" + cmd["command_name"]
- try:
- callback = getattr(self, name)
- kwargs = parse_spec(cmd["command_args"])
- self.callbacks[name] = Callback(command=callback, kwargs=kwargs)
- except AttributeError:
- raise StatsError(STATS_UNKNOWN_COMMAND_IN_SPEC, cmd["command_name"])
- self.mccs.start()
- def start(self):
- """
- Start stats module
- """
- self.running = True
- # TODO: should be added into new logging interface
- # if self.verbose:
- # sys.stdout.write("[b10-stats] starting\n")
- # request Bob to send statistics data
- logger.debug(DBG_STATS_MESSAGING, STATS_SEND_REQUEST_BOSS)
- cmd = isc.config.ccsession.create_command("sendstats", None)
- seq = self.cc_session.group_sendmsg(cmd, 'Boss')
- self.cc_session.group_recvmsg(True, seq)
- # initialized Statistics data
- errors = self.update_statistics_data(
- self.module_name,
- lname=self.cc_session.lname,
- boot_time=get_datetime(_BASETIME)
- )
- if errors:
- raise StatsError("stats spec file is incorrect")
- while self.running:
- self.mccs.check_command(False)
- def config_handler(self, new_config):
- """
- handle a configure from the cc channel
- """
- logger.debug(DBG_STATS_MESSAGING, STATS_RECEIVED_NEW_CONFIG,
- new_config)
- # do nothing currently
- return isc.config.create_answer(0)
- def command_handler(self, command, kwargs):
- """
- handle commands from the cc channel
- """
- name = 'command_' + command
- if name in self.callbacks:
- callback = self.callbacks[name]
- if kwargs:
- return callback(**kwargs)
- else:
- return callback()
- else:
- logger.error(STATS_RECEIVED_UNKNOWN_COMMAND, command)
- return isc.config.create_answer(1, "Unknown command: '"+str(command)+"'")
- def update_modules(self):
- """
- update information of each module
- """
- modules = {}
- seq = self.cc_session.group_sendmsg(
- isc.config.ccsession.create_command(
- isc.config.ccsession.COMMAND_GET_STATISTICS_SPEC),
- 'ConfigManager')
- (answer, env) = self.cc_session.group_recvmsg(False, seq)
- if answer:
- (rcode, value) = isc.config.ccsession.parse_answer(answer)
- if rcode == 0:
- for mod in value:
- spec = { "module_name" : mod }
- if value[mod] and type(value[mod]) is list:
- spec["statistics"] = value[mod]
- modules[mod] = isc.config.module_spec.ModuleSpec(spec)
- modules[self.module_name] = self.mccs.get_module_spec()
- self.modules = modules
- def get_statistics_data(self, owner=None, name=None):
- """
- return statistics data which stats module has of each module
- """
- self.update_statistics_data()
- if owner and name:
- try:
- return self.statistics_data[owner][name]
- except KeyError:
- pass
- elif owner:
- try:
- return self.statistics_data[owner]
- except KeyError:
- pass
- elif name:
- pass
- else:
- return self.statistics_data
- def update_statistics_data(self, owner=None, **data):
- """
- change statistics date of specified module into specified data
- """
- self.update_modules()
- statistics_data = {}
- for (name, module) in self.modules.items():
- value = parse_spec(module.get_statistics_spec())
- if module.validate_statistics(True, value):
- statistics_data[name] = value
- for (name, value) in self.statistics_data.items():
- if name in statistics_data:
- statistics_data[name].update(value)
- else:
- statistics_data[name] = value
- self.statistics_data = statistics_data
- if owner and data:
- errors = []
- try:
- if self.modules[owner].validate_statistics(False, data, errors):
- self.statistics_data[owner].update(data)
- return
- except KeyError:
- errors.append("unknown module name: " + str(owner))
- return errors
- def command_status(self):
- """
- handle status command
- """
- logger.debug(DBG_STATS_MESSAGING, STATS_RECEIVED_STATUS_COMMAND)
- return isc.config.create_answer(
- 0, "Stats is up. (PID " + str(os.getpid()) + ")")
- def command_shutdown(self):
- """
- handle shutdown command
- """
- logger.info(STATS_RECEIVED_SHUTDOWN_COMMAND)
- self.running = False
- return isc.config.create_answer(0)
- def command_show(self, owner=None, name=None):
- """
- handle show command
- """
- if (owner or name):
- logger.debug(DBG_STATS_MESSAGING,
- STATS_RECEIVED_SHOW_NAME_COMMAND,
- str(owner)+", "+str(name))
- else:
- logger.debug(DBG_STATS_MESSAGING,
- STATS_RECEIVED_SHOW_ALL_COMMAND)
- errors = self.update_statistics_data(
- self.module_name,
- timestamp=get_timestamp(),
- report_time=get_datetime()
- )
- if errors: raise StatsError("stats spec file is incorrect")
- ret = self.get_statistics_data(owner, name)
- if ret is not None:
- return isc.config.create_answer(0, ret)
- else:
- return isc.config.create_answer(
- 1, "specified arguments are incorrect: " \
- + "owner: " + str(owner) + ", name: " + str(name))
- def command_showschema(self, owner=None, name=None):
- """
- handle show command
- """
- # TODO: should be added into new logging interface
- # if self.verbose:
- # sys.stdout.write("[b10-stats] 'showschema' command received\n")
- self.update_modules()
- schema = {}
- schema_byname = {}
- for mod in self.modules:
- spec = self.modules[mod].get_statistics_spec()
- schema_byname[mod] = {}
- if spec:
- schema[mod] = spec
- for item in spec:
- schema_byname[mod][item['item_name']] = item
- if owner:
- try:
- if name:
- return isc.config.create_answer(0, schema_byname[owner][name])
- else:
- return isc.config.create_answer(0, schema[owner])
- except KeyError:
- pass
- else:
- if name:
- return isc.config.create_answer(1, "module name is not specified")
- else:
- return isc.config.create_answer(0, schema)
- return isc.config.create_answer(
- 1, "specified arguments are incorrect: " \
- + "owner: " + str(owner) + ", name: " + str(name))
- def command_set(self, owner, data):
- """
- handle set command
- """
- errors = self.update_statistics_data(owner, **data)
- if errors:
- return isc.config.create_answer(
- 1, "errors while setting statistics data: " \
- + ", ".join(errors))
- errors = self.update_statistics_data(
- self.module_name, last_update_time=get_datetime() )
- if errors:
- raise StatsError("stats spec file is incorrect")
- return isc.config.create_answer(0)
- if __name__ == "__main__":
- try:
- parser = OptionParser()
- parser.add_option(
- "-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
- (options, args) = parser.parse_args()
- if options.verbose:
- isc.log.init("b10-stats", "DEBUG", 99)
- stats = Stats()
- stats.start()
- except OptionValueError as ove:
- logger.fatal(STATS_BAD_OPTION_VALUE, ove)
- except SessionError as se:
- logger.fatal(STATS_CC_SESSION_ERROR, se)
- # TODO: should be added into new logging interface
- except StatsError as se:
- sys.exit("[b10-stats] %s" % se)
- except KeyboardInterrupt as kie:
- logger.info(STATS_STOPPED_BY_KEYBOARD)
|