diff --git a/daemon/core/corehandlers.py b/daemon/core/corehandlers.py index 74534af5..9a220fe0 100644 --- a/daemon/core/corehandlers.py +++ b/daemon/core/corehandlers.py @@ -42,8 +42,8 @@ from core.enumerations import SessionTlvs from core.misc import nodeutils from core.misc import structutils from core.misc import utils -from core.service import CoreService from core.service import ServiceManager +from core.service import ServiceShim class CoreHandler(SocketServer.BaseRequestHandler): @@ -1072,10 +1072,10 @@ class CoreHandler(SocketServer.BaseRequestHandler): # sort groups by name and map services to groups groups = set() group_map = {} - for service in ServiceManager.services.itervalues(): - group = service._group + for service_name in ServiceManager.services.itervalues(): + group = service_name.group groups.add(group) - group_map.setdefault(group, []).append(service) + group_map.setdefault(group, []).append(service_name) groups = sorted(groups, key=lambda x: x.lower()) # define tlv values in proper order @@ -1086,15 +1086,15 @@ class CoreHandler(SocketServer.BaseRequestHandler): start_index = 1 logger.info("sorted groups: %s", groups) for group in groups: - services = sorted(group_map[group], key=lambda x: x._name.lower()) + services = sorted(group_map[group], key=lambda x: x.name.lower()) logger.info("sorted services for group(%s): %s", group, services) end_index = start_index + len(services) - 1 group_strings.append("%s:%s-%s" % (group, start_index, end_index)) - start_index = start_index + len(services) - for service in services: - captions.append(service._name) + start_index += len(services) + for service_name in services: + captions.append(service_name.name) values.append("0") - if service._custom_needed: + if service_name.custom_needed: possible_values.append("1") else: possible_values.append("") @@ -1111,30 +1111,31 @@ class CoreHandler(SocketServer.BaseRequestHandler): node = self.session.get_object(node_id) if node is None: - logger.warn("Request to configure service for unknown node %s", node_id) + logger.warn("request to configure service for unknown node %s", node_id) return replies - servicesstring = opaque.split(':') - services, unknown = self.session.services.servicesfromopaque(opaque, node.objid) - for u in unknown: - logger.warn("Request for unknown service '%s'" % u) + services = ServiceShim.servicesfromopaque(opaque) if not services: return replies + servicesstring = opaque.split(":") if len(servicesstring) == 3: # a file request: e.g. "service:zebra:quagga.conf" - file_data = self.session.services.getservicefile(services, node, servicesstring[2]) + file_name = servicesstring[2] + service_name = services[0] + file_data = self.session.services.getservicefile(service_name, node, file_name, services) self.session.broadcast_file(file_data) # short circuit this request early to avoid returning response below return replies # the first service in the list is the one being configured - svc = services[0] + service_name = services[0] # send back: # dirs, configs, startindex, startup, shutdown, metadata, config type_flag = ConfigFlags.UPDATE.value - data_types = tuple(repeat(ConfigDataTypes.STRING.value, len(svc.keys))) - values = svc.tovaluelist(node, services) + data_types = tuple(repeat(ConfigDataTypes.STRING.value, len(ServiceShim.keys))) + service = self.session.services.getcustomservice(node_id, service_name, default_service=True) + values = ServiceShim.tovaluelist(service, node, services) captions = None possible_values = None groups = None @@ -1173,15 +1174,21 @@ class CoreHandler(SocketServer.BaseRequestHandler): self.session.services.defaultservices[key] = values logger.debug("default services for type %s set to %s", key, values) elif node_id: - # store service customized config in self.customservices[] - services, unknown = self.session.services.servicesfromopaque(opaque, node_id) - for u in unknown: - logger.warn("request for unknown service: %s", u) - + services = ServiceShim.servicesfromopaque(opaque) if services: - svc = services[0] + service_name = services[0] + + # set custom service for node + self.session.services.setcustomservice(node_id, service_name) + + # set custom values for custom service + service = self.session.services.getcustomservice(node_id, service_name) + if not service: + raise ValueError("custom service(%s) for node(%s) does not exist", service_name, node_id) + values = ConfigShim.str_to_dict(values) - self.session.services.setcustomservice(node_id, svc, values) + for name, value in values.iteritems(): + ServiceShim.setvalue(service, name, value) return replies @@ -1324,7 +1331,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): if file_type is not None: if file_type.startswith("service:"): _, service_name = file_type.split(':')[:2] - self.session.add_node_service_file(node_num, service_name, file_name, source_name, data) + self.session.services.setservicefile(node_num, service_name, file_name, data) return () elif file_type.startswith("hook:"): _, state = file_type.split(':')[:2] @@ -1430,7 +1437,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): # TODO: register system for event message handlers, # like confobjs if name.startswith("service:"): - self.session.services_event(event_data) + self.handle_service_event(event_data) handled = True elif name.startswith("mobility:"): self.session.mobility_event(event_data) @@ -1463,6 +1470,72 @@ class CoreHandler(SocketServer.BaseRequestHandler): return () + def handle_service_event(self, event_data): + """ + Handle an Event Message used to start, stop, restart, or validate + a service on a given node. + + :param EventData event_data: event data to handle + :return: nothing + """ + event_type = event_data.event_type + node_id = event_data.node + name = event_data.name + + try: + node = self.session.get_object(node_id) + except KeyError: + logger.warn("ignoring event for service '%s', unknown node '%s'", name, node_id) + return + + fail = "" + unknown = [] + services = ServiceShim.servicesfromopaque(name) + for service_name in services: + service = self.session.services.getcustomservice(node_id, service_name, default_service=True) + if not service: + unknown.append(service_name) + continue + + if event_type == EventTypes.STOP.value or event_type == EventTypes.RESTART.value: + status = self.session.services.stopnodeservice(node, service) + if status != "0": + fail += "Stop %s," % service.name + if event_type == EventTypes.START.value or event_type == EventTypes.RESTART.value: + status = self.session.services.node_service_startup(node, service, services) + if status != "0": + fail += "Start %s(%s)," % service.name + if event_type == EventTypes.PAUSE.value: + status = self.session.services.validatenodeservice(node, service, services) + if status != 0: + fail += "%s," % service.name + if event_type == EventTypes.RECONFIGURE.value: + self.session.services.node_service_reconfigure(node, service, services) + + fail_data = "" + if len(fail) > 0: + fail_data += "Fail:" + fail + unknown_data = "" + num = len(unknown) + if num > 0: + for u in unknown: + unknown_data += u + if num > 1: + unknown_data += ", " + num -= 1 + logger.warn("Event requested for unknown service(s): %s", unknown_data) + unknown_data = "Unknown:" + unknown_data + + event_data = EventData( + node=node_id, + event_type=event_type, + name=name, + data=fail_data + ";" + unknown_data, + time="%s" % time.time() + ) + + self.session.broadcast_event(event_data) + def handle_session_message(self, message): """ Session Message handler @@ -1620,10 +1693,10 @@ class CoreHandler(SocketServer.BaseRequestHandler): # service customizations service_configs = self.session.services.getallconfigs() for node_id, service in service_configs: - opaque = "service:%s" % service._name - data_types = tuple(repeat(ConfigDataTypes.STRING.value, len(CoreService.keys))) + opaque = "service:%s" % service.name + data_types = tuple(repeat(ConfigDataTypes.STRING.value, len(ServiceShim.keys))) node = self.session.get_object(node_id) - values = CoreService.tovaluelist(node, node.services) + values = ServiceShim.tovaluelist(service, node, node.services) config_data = ConfigData( message_type=0, node=node_id, diff --git a/daemon/core/coreobj.py b/daemon/core/coreobj.py index badd22ce..c5031260 100644 --- a/daemon/core/coreobj.py +++ b/daemon/core/coreobj.py @@ -218,7 +218,7 @@ class PyCoreObj(object): if hasattr(self, "services") and len(self.services) != 0: nodeservices = [] for s in self.services: - nodeservices.append(s._name) + nodeservices.append(s.name) services = "|".join(nodeservices) node_data = NodeData( diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 182527ad..7d948552 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -507,8 +507,7 @@ class EmuSession(Session): if _type in [NodeTypes.DEFAULT, NodeTypes.PHYSICAL]: node.type = node_options.model logger.debug("set node type: %s", node.type) - services = "|".join(node_options.services) or None - self.services.addservicestonode(node, node.type, services) + self.services.addservicestonode(node, node.type, node_options.services) # boot nodes if created after runtime, LcxNodes, Physical, and RJ45 are all PyCoreNodes is_boot_node = isinstance(node, PyCoreNode) and not nodeutils.is_node(node, NodeTypes.RJ45) @@ -697,21 +696,6 @@ class EmuSession(Session): state = ":%s" % state self.set_hook(state, file_name, source_name, data) - def add_node_service_file(self, node_id, service_name, file_name, source_name, data): - """ - Add a service file for a node. - - :param int node_id: node to add service file to - :param str service_name: service file to add - :param str file_name: file name to use - :param str source_name: source file - :param str data: file data to save - :return: nothing - """ - # hack to conform with old logic until updated - service_name = ":%s" % service_name - self.services.setservicefile(node_id, service_name, file_name, source_name, data) - def add_node_file(self, node_id, source_name, file_name, data): """ Add a file to a node. @@ -749,15 +733,6 @@ class EmuSession(Session): """ self.event_loop.run() - def services_event(self, event_data): - """ - Handle a service event. - - :param core.data.EventData event_data: event data to handle - :return: - """ - self.services.handleevent(event_data) - def mobility_event(self, event_data): """ Handle a mobility event. diff --git a/daemon/core/service.py b/daemon/core/service.py index ffdaf7cc..21f94a24 100644 --- a/daemon/core/service.py +++ b/daemon/core/service.py @@ -6,18 +6,110 @@ The CoreServices class handles configuration messages for sending a list of available services to the GUI and for configuring individual services. """ -import time from core import CoreCommandError from core import logger -from core.data import EventData from core.data import FileData -from core.enumerations import EventTypes from core.enumerations import MessageFlags from core.enumerations import RegisterTlvs from core.misc import utils +class ServiceShim(object): + keys = ["dirs", "files", "startidx", "cmdup", "cmddown", "cmdval", "meta", "starttime"] + + @classmethod + def tovaluelist(cls, service, node, services): + """ + Convert service properties into a string list of key=value pairs, + separated by "|". + + :param CoreService service: service to get value list for + :param core.netns.nodes.CoreNode node: node to get value list for + :param list[CoreService] services: services for node + :return: value list string + :rtype: str + """ + valmap = [service.dirs, service.configs, service.startindex, service.startup, + service.shutdown, service.validate, service.meta, service.starttime] + if not service.custom: + # this is always reached due to classmethod + valmap[valmap.index(service.configs)] = service.getconfigfilenames(node.objid, services) + valmap[valmap.index(service.startup)] = service.getstartup(node, services) + vals = map(lambda a, b: "%s=%s" % (a, str(b)), cls.keys, valmap) + return "|".join(vals) + + @classmethod + def fromvaluelist(cls, service, values): + """ + Convert list of values into properties for this instantiated + (customized) service. + + :param CoreService service: service to get value list for + :param dict values: value list to set properties from + :return: nothing + """ + # TODO: support empty value? e.g. override default meta with '' + for key in cls.keys: + try: + cls.setvalue(service, key, values[cls.keys.index(key)]) + except IndexError: + # old config does not need to have new keys + logger.exception("error indexing into key") + + @classmethod + def setvalue(cls, service, key, value): + """ + Set values for this service. + + :param CoreService service: service to get value list for + :param str key: key to set value for + :param value: value of key to set + :return: nothing + """ + if key not in cls.keys: + raise ValueError('key `%s` not in `%s`' % (key, cls.keys)) + # this handles data conversion to int, string, and tuples + if value: + if key == "startidx": + value = int(value) + elif key == "meta": + value = str(value) + else: + value = utils.make_tuple_fromstr(value, str) + + if key == "dirs": + service.dirs = value + elif key == "files": + service.configs = value + elif key == "startidx": + service.startindex = value + elif key == "cmdup": + service.startup = value + elif key == "cmddown": + service.shutdown = value + elif key == "cmdval": + service.validate = value + elif key == "meta": + service.meta = value + elif key == "starttime": + service.starttime = value + + @classmethod + def servicesfromopaque(self, opaque): + """ + Build a list of services from an opaque data string. + + :param str opaque: opaque data string + :return: services + :rtype: list + """ + servicesstring = opaque.split(':') + if servicesstring[0] != "service": + return [] + return servicesstring[1].split(',') + + class ServiceManager(object): """ Manages services available for CORE nodes to use. @@ -33,7 +125,7 @@ class ServiceManager(object): :return: nothing """ logger.info("loading service: %s", service.__name__) - name = service._name + name = service.name if name in cls.services: raise ValueError("duplicate service being added: %s" % name) cls.services[name] = service @@ -45,7 +137,7 @@ class ServiceManager(object): :param str name: name of the service to retrieve :return: service if it exists, None otherwise - :rtype: CoreService + :rtype: CoreService.class """ return cls.services.get(name) @@ -60,7 +152,7 @@ class ServiceManager(object): """ services = utils.load_classes(path, CoreService) for service in services: - if not service._name: + if not service.name: continue service.on_load() cls.add(service) @@ -76,7 +168,6 @@ class CoreServices(object): """ name = "services" config_type = RegisterTlvs.UTILITY.value - _invalid_custom_names = ("core", "api", "emane", "misc", "netns", "phys", "services") def __init__(self, session): """ @@ -84,7 +175,6 @@ class CoreServices(object): :param core.session.Session session: session this manager is tied to """ - # ConfigurableManager.__init__(self) self.session = session # dict of default services tuples, key is node type self.defaultservices = {} @@ -109,99 +199,79 @@ class CoreServices(object): :param service_type: service type to get default services for :return: default services - :rtype: list + :rtype: list[CoreService] """ logger.debug("getting default services for type: %s", service_type) results = [] - if service_type in self.defaultservices: - defaults = self.defaultservices[service_type] - for name in defaults: - logger.debug("checking for service with service manager: %s", name) - service = ServiceManager.get(name) - if not service: - logger.warn("default service %s is unknown", name) - else: - results.append(service) + defaults = self.defaultservices.get(service_type, []) + for name in defaults: + logger.debug("checking for service with service manager: %s", name) + service = ServiceManager.get(name) + if not service: + logger.warn("default service %s is unknown", name) + else: + results.append(service) return results - def getcustomservice(self, object_id, service): + def getcustomservice(self, node_id, service_name, default_service=False): """ Get any custom service configured for the given node that matches the specified service name. If no custom service is found, return the specified service. - :param int object_id: object id to get service from - :param CoreService service: custom service to retrieve + :param int node_id: object id to get service from + :param str service_name: name of service to retrieve + :param bool default_service: True to return default service when custom does not exist, False returns None :return: custom service from the node :rtype: CoreService """ - if object_id in self.customservices: - for s in self.customservices[object_id]: - if s._name == service._name: - return s - return service + node_services = self.customservices.setdefault(node_id, {}) + default = None + if default_service: + default = ServiceManager.get(service_name) + return node_services.get(service_name, default) - def setcustomservice(self, object_id, service, config): + def setcustomservice(self, node_id, service_name): """ Store service customizations in an instantiated service object using a list of values that came from a config message. - :param int object_id: object id to set custom service for - :param class service: service to set - :param dict config: values to - :return: - """ - logger.debug("setting custom service(%s) for node(%s): %s", object_id, service, config) - if service._custom: - s = service - else: - # instantiate the class, for storing config customization - s = service() - - # set custom service configuration - for name, value in config.iteritems(): - s.setvalue(name, value) - - # assume custom service already in dict - if service._custom: - return - - # add the custom service to dict - if object_id in self.customservices: - self.customservices[object_id] += (s,) - else: - self.customservices[object_id] = (s,) - - def addservicestonode(self, node, nodetype, services_str): - """ - Populate the node.service list using (1) the list of services - requested from the services TLV, (2) using any custom service - configuration, or (3) using the default services for this node type. - - :param core.coreobj.PyCoreNode node: node to add services to - :param str nodetype: node type to add services to - :param str services_str: string formatted service list + :param int node_id: object id to set custom service for + :param str service_name: name of service to set :return: nothing """ - if services_str is not None: - logger.info("setting custom services for node(%s)", node.name) - services = services_str.split("|") - for name in services: - s = ServiceManager.get(name) - if s is None: - logger.warn("configured service %s for node %s is unknown", name, node.name) - continue - logger.info("adding service to node(%s): %s", node.name, s._name) - s = self.getcustomservice(node.objid, s) - node.addservice(s) - else: - logger.info("setting default services for node(%s) type (%s)", node.name, nodetype) - services = self.getdefaultservices(nodetype) - for s in services: - logger.info("adding service to node(%s): %s", node.name, s._name) - s = self.getcustomservice(node.objid, s) - node.addservice(s) + logger.debug("setting custom service(%s) for node: %s", node_id, service_name) + service = self.getcustomservice(node_id, service_name) + if not service: + service_class = ServiceManager.get(service_name) + service = service_class() - def getallconfigs(self, use_clsmap=True): + # add the custom service to dict + node_services = self.customservices.setdefault(node_id, {}) + node_services[service.name] = service + + def addservicestonode(self, node, node_type, services=None): + """ + Add services to a node. + + :param core.coreobj.PyCoreNode node: node to add services to + :param str node_type: node type to add services to + :param list[str] services: services to add to node + :return: nothing + """ + if not services: + logger.info("using default services for node(%s) type(%s)", node.name, node_type) + services = self.defaultservices.get(node_type, []) + + logger.info("setting services for node(%s): %s", node.name, services) + for service_name in services: + service = self.getcustomservice(node.objid, service_name, default_service=True) + if not service: + logger.warn("unknown service(%s) for node(%s)", service_name, node.name) + continue + logger.info("adding service to node(%s): %s", node.name, service_name) + node.addservice(service) + + def getallconfigs(self): """ Return (nodenum, service) tuples for all stored configs. Used when reconnecting to a session or opening XML. @@ -211,9 +281,9 @@ class CoreServices(object): :rtype: list """ configs = [] - for nodenum in self.customservices: - for service in self.customservices[nodenum]: - configs.append((nodenum, service)) + for node_id in self.customservices.iterkeys(): + for service in self.customservices[node_id].itervalues(): + configs.append((node_id, service)) return configs def getallfiles(self, service): @@ -226,11 +296,11 @@ class CoreServices(object): """ files = [] - if not service._custom: + if not service.custom: return files - for filename in service._configs: - data = self.getservicefiledata(service, filename) + for filename in service.configs: + data = service.configtxt.get(filename) if data is None: continue files.append((filename, data)) @@ -244,19 +314,19 @@ class CoreServices(object): :param core.netns.vnode.LxcNode node: node to start services on :return: nothing """ - services = sorted(node.services, key=lambda service: service._startindex) + services = sorted(node.services, key=lambda x: x.startindex) use_startup_service = any(map(self.is_startup_service, services)) - for s in services: - if len(str(s._starttime)) > 0: + for service in services: + if len(str(service.starttime)) > 0: try: - t = float(s._starttime) - if t > 0.0: + starttime = float(service.starttime) + if starttime > 0.0: fn = self.bootnodeservice - self.session.event_loop.add_event(t, fn, node, s, services, False) + self.session.event_loop.add_event(starttime, fn, node, service, services, False) continue except ValueError: logger.exception("error converting start time to float") - self.bootnodeservice(node, s, services, use_startup_service) + self.bootnodeservice(node, service, services, use_startup_service) def bootnodeservice(self, node, service, services, use_startup_service): """ @@ -269,12 +339,12 @@ class CoreServices(object): :param bool use_startup_service: flag to use startup services or not :return: nothing """ - if service._custom: + if service.custom: self.bootnodecustomservice(node, service, services, use_startup_service) return - logger.info("starting node(%s) service: %s (%s)", node.name, service._name, service._startindex) - for directory in service._dirs: + logger.info("starting node(%s) service: %s (%s)", node.name, service.name, service.startindex) + for directory in service.dirs: node.privatedir(directory) for filename in service.getconfigfilenames(node.objid, services): @@ -299,18 +369,17 @@ class CoreServices(object): :param bool use_startup_service: flag to use startup services or not :return: nothing """ - logger.info("starting service(%s) %s (%s)(custom)", service, service._name, service._startindex) - for directory in service._dirs: + logger.info("starting node(%s) service(custom): %s (%s)", node.name, service.name, service.startindex) + for directory in service.dirs: node.privatedir(directory) - logger.info("service configurations: %s", service._configs) - for i, filename in enumerate(service._configs): + logger.info("service configurations: %s", service.configs) + for filename in service.configs: logger.info("generating service config: %s", filename) - if len(filename) == 0: - continue - cfg = self.getservicefiledata(service, filename) + cfg = service.configtxt.get(filename) if cfg is None: cfg = service.generateconfig(node, filename, services) + # cfg may have a file:/// url for copying from a file try: if self.copyservicefile(node, filename, cfg): @@ -323,7 +392,7 @@ class CoreServices(object): if use_startup_service and not self.is_startup_service(service): return - for args in service._startup: + for args in service.startup: # TODO: this wait=False can be problematic! node.cmd(args, wait=False) @@ -355,9 +424,9 @@ class CoreServices(object): :param core.netns.vnode.LxcNode node: node to validate services for :return: nothing """ - services = sorted(node.services, key=lambda service: service._startindex) - for s in services: - self.validatenodeservice(node, s, services) + services = sorted(node.services, key=lambda service: service.startindex) + for service in services: + self.validatenodeservice(node, service, services) def validatenodeservice(self, node, service, services): """ @@ -369,22 +438,20 @@ class CoreServices(object): :return: service validation status :rtype: int """ - logger.info("validating service for node (%s): %s (%s)", node.name, service._name, service._startindex) - if service._custom: - validate_cmds = service._validate + logger.info("validating service for node (%s): %s (%s)", node.name, service.name, service.startindex) + if service.custom: + validate_cmds = service.validate else: validate_cmds = service.getvalidate(node, services) status = 0 - # has validate commands - if validate_cmds: - for args in validate_cmds: - logger.info("validating service %s using: %s", service._name, args) - try: - node.check_cmd(args) - except CoreCommandError: - logger.exception("validate command failed") - status = -1 + for args in validate_cmds: + logger.info("validating service %s using: %s", service.name, args) + try: + node.check_cmd(args) + except CoreCommandError: + logger.exception("validate command failed") + status = -1 return status @@ -395,9 +462,9 @@ class CoreServices(object): :param core.netns.nodes.CoreNode node: node to stop services on :return: nothing """ - services = sorted(node.services, key=lambda service: service._startindex) - for s in services: - self.stopnodeservice(node, s) + services = sorted(node.services, key=lambda x: x.startindex) + for service in services: + self.stopnodeservice(node, service) def stopnodeservice(self, node, service): """ @@ -409,68 +476,57 @@ class CoreServices(object): :rtype: str """ status = "0" - if service._shutdown: - for args in service._shutdown: - try: - node.check_cmd(args) - except CoreCommandError: - logger.exception("error running stop command %s", args) - # TODO: determine if its ok to just return the bad exit status - status = "-1" + for args in service.shutdown: + try: + node.check_cmd(args) + except CoreCommandError: + logger.exception("error running stop command %s", args) + # TODO: determine if its ok to just return the bad exit status + status = "-1" return status - def servicesfromopaque(self, opaque, object_id): - """ - Build a list of services from an opaque data string. - - :param str opaque: opaque data string - :param int object_id: object id - :return: services and unknown services lists tuple - :rtype: tuple - """ - services = [] - unknown = [] - servicesstring = opaque.split(':') - if servicesstring[0] != "service": - return [] - servicenames = servicesstring[1].split(',') - for name in servicenames: - s = ServiceManager.get(name) - s = self.getcustomservice(object_id, s) - if s is None: - unknown.append(name) - else: - services.append(s) - return services, unknown - - def getservicefile(self, services, node, filename): + def getservicefile(self, service_name, node, filename, services): """ Send a File Message when the GUI has requested a service file. The file data is either auto-generated or comes from an existing config. - :param list services: service list + :param str service_name: service to get file from :param core.netns.vnode.LxcNode node: node to get service file from :param str filename: file name to retrieve + :param list[str] services: list of services associated with node :return: file message for node """ - svc = services[0] - # get the filename and determine the config file index - if svc._custom: - cfgfiles = svc._configs + # get service to get file from + service = self.getcustomservice(node.objid, service_name, default_service=True) + if not service: + raise ValueError("invalid service: %s", service_name) + + # get service for node + node_services = [] + for service_name in services: + node_service = self.getcustomservice(node.objid, service_name, default_service=True) + if not node_service: + logger.warn("unknown service: %s", service) + continue + node_services.append(node_service) + + # retrieve config files for default/custom service + if service.custom: + config_files = service.configs else: - cfgfiles = svc.getconfigfilenames(node.objid, services) - if filename not in cfgfiles: - logger.warn("Request for unknown file '%s' for service '%s'" % (filename, services[0])) - return None + config_files = service.getconfigfilenames(node.objid, node_services) + + if filename not in config_files: + raise ValueError("unknown service(%s) config file: %s", service_name, filename) # get the file data - data = self.getservicefiledata(svc, filename) + data = service.configtxt.get(filename) if data is None: - data = "%s" % svc.generateconfig(node, filename, services) + data = "%s" % service.generateconfig(node, filename, services) else: data = "%s" % data - filetypestr = "service:%s" % svc._name + filetypestr = "service:%s" % service.name return FileData( message_type=MessageFlags.ADD.value, node=node.objid, @@ -479,141 +535,85 @@ class CoreServices(object): data=data ) - def getservicefiledata(self, service, filename): - """ - Get the customized file data associated with a service. Return None - for invalid filenames or missing file data. - - :param CoreService service: service to get file data from - :param str filename: file name to get data from - :return: file data - """ - try: - i = service._configs.index(filename) - except ValueError: - return None - if i >= len(service._configtxt) or service._configtxt[i] is None: - return None - return service._configtxt[i] - - def setservicefile(self, nodenum, type, filename, srcname, data): + def setservicefile(self, node_id, service_name, filename, data): """ Receive a File Message from the GUI and store the customized file in the service config. The filename must match one from the list of config files in the service. - :param int nodenum: node id to set service file - :param str type: file type to set + :param int node_id: node id to set service file + :param str service_name: service name to set file for :param str filename: file name to set - :param str srcname: source name of file to set :param data: data for file to set :return: nothing """ - if len(type.split(':')) < 2: - logger.warn("Received file type did not contain service info.") - return - if srcname is not None: - raise NotImplementedError - svcid, svcname = type.split(':')[:2] - svc = ServiceManager.get(svcname) - svc = self.getcustomservice(nodenum, svc) + # attempt to set custom service, if needed + self.setcustomservice(node_id, service_name) + + # retrieve custom service + svc = self.getcustomservice(node_id, service_name) if svc is None: - logger.warn("Received filename for unknown service '%s'" % svcname) + logger.warn("received filename for unknown service: %s", service_name) return - cfgfiles = svc._configs + + # validate file being set is valid + cfgfiles = svc.configs if filename not in cfgfiles: - logger.warn("Received unknown file '%s' for service '%s'" % (filename, svcname)) + logger.warn("received unknown file '%s' for service '%s'", filename, service_name) return - i = cfgfiles.index(filename) - configtxtlist = list(svc._configtxt) - numitems = len(configtxtlist) - if numitems < i + 1: - # add empty elements to list to support index assignment - for j in range(1, (i + 2) - numitems): - configtxtlist += None, - configtxtlist[i] = data - svc._configtxt = configtxtlist - def handleevent(self, event_data): + # set custom service file data + svc.configtxt[filename] = data + + def node_service_startup(self, node, service, services): """ - Handle an Event Message used to start, stop, restart, or validate - a service on a given node. + Startup a node service. - :param EventData event_data: event data to handle + :param PyCoreNode node: node to reconfigure service for + :param CoreService service: service to reconfigure + :param list[CoreService] services: node services + :return: status of startup + :rtype: str + """ + + if service.custom: + cmds = service.startup + else: + cmds = service.getstartup(node, services) + + status = "0" + for args in cmds: + try: + node.check_cmd(args) + except CoreCommandError: + logger.exception("error starting command") + status = "-1" + return status + + def node_service_reconfigure(self, node, service, services): + """ + Reconfigure a node service. + + :param PyCoreNode node: node to reconfigure service for + :param CoreService service: service to reconfigure + :param list[CoreService] services: node services :return: nothing """ - event_type = event_data.event_type - node_id = event_data.node - name = event_data.name + if service.custom: + cfgfiles = service.configs + else: + cfgfiles = service.getconfigfilenames(node.objid, services) - try: - node = self.session.get_object(node_id) - except KeyError: - logger.warn("Ignoring event for service '%s', unknown node '%s'", name, node_id) - return + for filename in cfgfiles: + if filename[:7] == "file:///": + # TODO: implement this + raise NotImplementedError - fail = "" - services, unknown = self.servicesfromopaque(name, node_id) - for s in services: - if event_type == EventTypes.STOP.value or event_type == EventTypes.RESTART.value: - status = self.stopnodeservice(node, s) - if status != "0": - fail += "Stop %s," % s._name - if event_type == EventTypes.START.value or event_type == EventTypes.RESTART.value: - if s._custom: - cmds = s._startup - else: - cmds = s.getstartup(node, services) - if len(cmds) > 0: - for args in cmds: - try: - node.check_cmd(args) - except CoreCommandError: - logger.exception("error starting command") - fail += "Start %s(%s)," % (s._name, args) - if event_type == EventTypes.PAUSE.value: - status = self.validatenodeservice(node, s, services) - if status != 0: - fail += "%s," % s._name - if event_type == EventTypes.RECONFIGURE.value: - if s._custom: - cfgfiles = s._configs - else: - cfgfiles = s.getconfigfilenames(node.objid, services) - if len(cfgfiles) > 0: - for filename in cfgfiles: - if filename[:7] == "file:///": - # TODO: implement this - raise NotImplementedError - cfg = self.getservicefiledata(s, filename) - if cfg is None: - cfg = s.generateconfig(node, filename, services) + cfg = service.configtxt.get(filename) + if cfg is None: + cfg = service.generateconfig(node, filename, services) - node.nodefile(filename, cfg) - - fail_data = "" - if len(fail) > 0: - fail_data += "Fail:" + fail - unknown_data = "" - num = len(unknown) - if num > 0: - for u in unknown: - unknown_data += u - if num > 1: - unknown_data += ", " - num -= 1 - logger.warn("Event requested for unknown service(s): %s", unknown_data) - unknown_data = "Unknown:" + unknown_data - - event_data = EventData( - node=node_id, - event_type=event_type, - name=name, - data=fail_data + ";" + unknown_data, - time="%s" % time.time() - ) - - self.session.broadcast_event(event_data) + node.nodefile(filename, cfg) class CoreService(object): @@ -621,45 +621,42 @@ class CoreService(object): Parent class used for defining services. """ # service name should not include spaces - _name = "" + name = None # group string allows grouping services together - _group = "" + group = None # list name(s) of services that this service depends upon - _depends = () - keys = ["dirs", "files", "startidx", "cmdup", "cmddown", "cmdval", "meta", "starttime"] + depends = () # private, per-node directories required by this service - _dirs = () + dirs = () # config files written by this service - _configs = () - # configs = [] + configs = () # index used to determine start order with other services - _startindex = 0 + startindex = 0 # time in seconds after runtime to run startup commands - _starttime = "" + starttime = 0 # list of startup commands - _startup = () - # startup = [] + startup = () # list of shutdown commands - _shutdown = () + shutdown = () # list of validate commands - _validate = () + validate = () # metadata associated with this service - _meta = "" + meta = None # custom configuration text - _configtxt = () - _custom = False - _custom_needed = False + configtxt = {} + custom = False + custom_needed = False def __init__(self): """ @@ -667,7 +664,16 @@ class CoreService(object): against their config. Services are instantiated when a custom configuration is used to override their default parameters. """ - self._custom = True + self.custom = True + self.dirs = self.__class__.dirs + self.configs = self.__class__.configs + self.startindex = self.__class__.startindex + self.startup = self.__class__.startup + self.shutdown = self.__class__.shutdown + self.validate = self.__class__.validate + self.meta = self.__class__.meta + self.starttime = self.__class__.starttime + self.configtxt = self.__class__.configtxt @classmethod def on_load(cls): @@ -685,7 +691,7 @@ class CoreService(object): :return: class configuration files :rtype: tuple """ - return cls._configs + return cls.configs @classmethod def generateconfig(cls, node, filename, services): @@ -716,7 +722,7 @@ class CoreService(object): :return: startup commands :rtype: tuple """ - return cls._startup + return cls.startup @classmethod def getvalidate(cls, node, services): @@ -731,76 +737,4 @@ class CoreService(object): :return: validation commands :rtype: tuple """ - return cls._validate - - @classmethod - def tovaluelist(cls, node, services): - """ - Convert service properties into a string list of key=value pairs, - separated by "|". - - :param core.netns.nodes.CoreNode node: node to get value list for - :param list services: services for node - :return: value list string - :rtype: str - """ - valmap = [cls._dirs, cls._configs, cls._startindex, cls._startup, - cls._shutdown, cls._validate, cls._meta, cls._starttime] - if not cls._custom: - # this is always reached due to classmethod - valmap[valmap.index(cls._configs)] = cls.getconfigfilenames(node.objid, services) - valmap[valmap.index(cls._startup)] = cls.getstartup(node, services) - vals = map(lambda a, b: "%s=%s" % (a, str(b)), cls.keys, valmap) - return "|".join(vals) - - def fromvaluelist(self, values): - """ - Convert list of values into properties for this instantiated - (customized) service. - - :param list values: value list to set properties from - :return: nothing - """ - # TODO: support empty value? e.g. override default meta with '' - for key in self.keys: - try: - self.setvalue(key, values[self.keys.index(key)]) - except IndexError: - # old config does not need to have new keys - logger.exception("error indexing into key") - - def setvalue(self, key, value): - """ - Set values for this service. - - :param str key: key to set value for - :param value: value of key to set - :return: nothing - """ - if key not in self.keys: - raise ValueError('key `%s` not in `%s`' % (key, self.keys)) - # this handles data conversion to int, string, and tuples - if value: - if key == "startidx": - value = int(value) - elif key == "meta": - value = str(value) - else: - value = utils.make_tuple_fromstr(value, str) - - if key == "dirs": - self._dirs = value - elif key == "files": - self._configs = value - elif key == "startidx": - self._startindex = value - elif key == "cmdup": - self._startup = value - elif key == "cmddown": - self._shutdown = value - elif key == "cmdval": - self._validate = value - elif key == "meta": - self._meta = value - elif key == "starttime": - self._starttime = value + return cls.validate diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index 097053c4..d56bc14c 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -9,22 +9,22 @@ class Bird(CoreService): """ Bird router support """ - _name = "bird" - _group = "BIRD" - _depends = () - _dirs = ("/etc/bird",) - _configs = ("/etc/bird/bird.conf",) - _startindex = 35 - _startup = ("bird -c %s" % (_configs[0]),) - _shutdown = ("killall bird",) - _validate = ("pidof bird",) + name = "bird" + group = "BIRD" + depends = () + dirs = ("/etc/bird",) + configs = ("/etc/bird/bird.conf",) + startindex = 35 + startup = ("bird -c %s" % (configs[0]),) + shutdown = ("killall bird",) + validate = ("pidof bird",) @classmethod def generateconfig(cls, node, filename, services): """ Return the bird.conf file contents. """ - if filename == cls._configs[0]: + if filename == cls.configs[0]: return cls.generateBirdConf(node, services) else: raise ValueError @@ -35,7 +35,7 @@ class Bird(CoreService): Helper to return the first IPv4 address of a node as its router ID. """ for ifc in node.netifs(): - if hasattr(ifc, 'control') and ifc.control == True: + if hasattr(ifc, 'control') and ifc.control is True: continue for a in ifc.addrlist: if a.find(".") >= 0: @@ -73,11 +73,11 @@ protocol device { scan time 10; # Scan interfaces every 10 seconds } -""" % (cls._name, cls.routerid(node)) +""" % (cls.name, cls.routerid(node)) # Generate protocol specific configurations for s in services: - if cls._name not in s._depends: + if cls.name not in s.depends: continue cfg += s.generatebirdconfig(node) @@ -90,15 +90,15 @@ class BirdService(CoreService): common to Bird's routing daemons. """ - _name = None - _group = "BIRD" - _depends = ("bird",) - _dirs = () - _configs = () - _startindex = 40 - _startup = () - _shutdown = () - _meta = "The config file for this service can be found in the bird service." + name = None + group = "BIRD" + depends = ("bird",) + dirs = () + configs = () + startindex = 40 + startup = () + shutdown = () + meta = "The config file for this service can be found in the bird service." @classmethod def generatebirdconfig(cls, node): @@ -106,14 +106,15 @@ class BirdService(CoreService): @classmethod def generatebirdifcconfig(cls, node): - ''' Use only bare interfaces descriptions in generated protocol + """ + Use only bare interfaces descriptions in generated protocol configurations. This has the slight advantage of being the same everywhere. - ''' + """ cfg = "" for ifc in node.netifs(): - if hasattr(ifc, 'control') and ifc.control == True: + if hasattr(ifc, 'control') and ifc.control is True: continue cfg += ' interface "%s";\n' % ifc.name @@ -125,8 +126,8 @@ class BirdBgp(BirdService): BGP BIRD Service (configuration generation) """ - _name = "BIRD_BGP" - _custom_needed = True + name = "BIRD_BGP" + custom_needed = True @classmethod def generatebirdconfig(cls, node): @@ -156,7 +157,7 @@ class BirdOspf(BirdService): OSPF BIRD Service (configuration generation) """ - _name = "BIRD_OSPFv2" + name = "BIRD_OSPFv2" @classmethod def generatebirdconfig(cls, node): @@ -181,7 +182,7 @@ class BirdRadv(BirdService): RADV BIRD Service (configuration generation) """ - _name = "BIRD_RADV" + name = "BIRD_RADV" @classmethod def generatebirdconfig(cls, node): @@ -209,7 +210,7 @@ class BirdRip(BirdService): RIP BIRD Service (configuration generation) """ - _name = "BIRD_RIP" + name = "BIRD_RIP" @classmethod def generatebirdconfig(cls, node): @@ -231,8 +232,8 @@ class BirdStatic(BirdService): Static Bird Service (configuration generation) """ - _name = "BIRD_static" - _custom_needed = True + name = "BIRD_static" + custom_needed = True @classmethod def generatebirdconfig(cls, node): diff --git a/daemon/core/services/dockersvc.py b/daemon/core/services/dockersvc.py index 1733f04b..9699465c 100644 --- a/daemon/core/services/dockersvc.py +++ b/daemon/core/services/dockersvc.py @@ -112,16 +112,16 @@ class DockerService(CoreService): This is a service which will allow running docker containers in a CORE node. """ - _name = "Docker" - _group = "Docker" - _depends = () - _dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',) - _configs = ('docker.sh',) - _startindex = 50 - _startup = ('sh docker.sh',) - _shutdown = ('service docker stop',) + name = "Docker" + group = "Docker" + depends = () + dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',) + configs = ('docker.sh',) + startindex = 50 + startup = ('sh docker.sh',) + shutdown = ('service docker stop',) # Container image to start - _image = "" + image = "" @classmethod def generateconfig(cls, node, filename, services): @@ -139,7 +139,7 @@ class DockerService(CoreService): # distros may just be docker cfg += 'service docker start\n' cfg += "# you could add a command to start a image here eg:\n" - if not cls._image: + if not cls.image: cfg += "# docker run -d --net host --name coreDock \n" else: cfg += """\ @@ -150,7 +150,7 @@ until [ $result -eq 0 ]; do # this is to alleviate contention to docker's SQLite database sleep 0.3 done -""" % (cls._image,) +""" % (cls.image,) return cfg @classmethod diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 5fe74ecd..07152404 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -13,14 +13,14 @@ class NrlService(CoreService): Parent class for NRL services. Defines properties and methods common to NRL's routing daemons. """"" - _name = None - _group = "ProtoSvc" - _depends = () - _dirs = () - _configs = () - _startindex = 45 - _startup = () - _shutdown = () + name = None + group = "ProtoSvc" + depends = () + dirs = () + configs = () + startindex = 45 + startup = () + shutdown = () @classmethod def generateconfig(cls, node, filename, services): @@ -34,7 +34,7 @@ class NrlService(CoreService): interface's prefix length, so e.g. '/32' can turn into '/24'. """ for ifc in node.netifs(): - if hasattr(ifc, 'control') and ifc.control == True: + if hasattr(ifc, 'control') and ifc.control is True: continue for a in ifc.addrlist: if a.find(".") >= 0: @@ -46,12 +46,12 @@ class NrlService(CoreService): class MgenSinkService(NrlService): - _name = "MGEN_Sink" - _configs = ("sink.mgen",) - _startindex = 5 - _startup = ("mgen input sink.mgen",) - _validate = ("pidof mgen",) - _shutdown = ("killall mgen",) + name = "MGEN_Sink" + configs = ("sink.mgen",) + startindex = 5 + startup = ("mgen input sink.mgen",) + validate = ("pidof mgen",) + shutdown = ("killall mgen",) @classmethod def generateconfig(cls, node, filename, services): @@ -63,7 +63,7 @@ class MgenSinkService(NrlService): @classmethod def getstartup(cls, node, services): - cmd = cls._startup[0] + cmd = cls.startup[0] cmd += " output /tmp/mgen_%s.log" % node.name return cmd, @@ -72,27 +72,26 @@ class NrlNhdp(NrlService): """ NeighborHood Discovery Protocol for MANET networks. """ - _name = "NHDP" - _startup = ("nrlnhdp",) - _shutdown = ("killall nrlnhdp",) - _validate = ("pidof nrlnhdp",) + name = "NHDP" + startup = ("nrlnhdp",) + shutdown = ("killall nrlnhdp",) + validate = ("pidof nrlnhdp",) @classmethod def getstartup(cls, node, services): """ Generate the appropriate command-line based on node interfaces. """ - cmd = cls._startup[0] + cmd = cls.startup[0] cmd += " -l /var/log/nrlnhdp.log" cmd += " -rpipe %s_nhdp" % node.name - servicenames = map(lambda x: x._name, services) + servicenames = map(lambda x: x.name, services) if "SMF" in servicenames: cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name - netifs = filter(lambda x: not getattr(x, 'control', False), \ - node.netifs()) + netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs()) if len(netifs) > 0: interfacenames = map(lambda x: x.name, netifs) cmd += " -i " @@ -105,11 +104,11 @@ class NrlSmf(NrlService): """ Simplified Multicast Forwarding for MANET networks. """ - _name = "SMF" - _startup = ("sh startsmf.sh",) - _shutdown = ("killall nrlsmf",) - _validate = ("pidof nrlsmf",) - _configs = ("startsmf.sh",) + name = "SMF" + startup = ("sh startsmf.sh",) + shutdown = ("killall nrlsmf",) + validate = ("pidof nrlsmf",) + configs = ("startsmf.sh",) @classmethod def generateconfig(cls, node, filename, services): @@ -123,7 +122,7 @@ class NrlSmf(NrlService): comments = "" cmd = "nrlsmf instance %s_smf" % node.name - servicenames = map(lambda x: x._name, services) + servicenames = map(lambda x: x.name, services) netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs()) if len(netifs) == 0: return "" @@ -156,17 +155,17 @@ class NrlOlsr(NrlService): """ Optimized Link State Routing protocol for MANET networks. """ - _name = "OLSR" - _startup = ("nrlolsrd",) - _shutdown = ("killall nrlolsrd",) - _validate = ("pidof nrlolsrd",) + name = "OLSR" + startup = ("nrlolsrd",) + shutdown = ("killall nrlolsrd",) + validate = ("pidof nrlolsrd",) @classmethod def getstartup(cls, node, services): """ Generate the appropriate command-line based on node interfaces. """ - cmd = cls._startup[0] + cmd = cls.startup[0] # are multiple interfaces supported? No. netifs = list(node.netifs()) if len(netifs) > 0: @@ -175,7 +174,7 @@ class NrlOlsr(NrlService): cmd += " -l /var/log/nrlolsrd.log" cmd += " -rpipe %s_olsr" % node.name - servicenames = map(lambda x: x._name, services) + servicenames = map(lambda x: x.name, services) if "SMF" in servicenames and not "NHDP" in servicenames: cmd += " -flooding s-mpr" cmd += " -smfClient %s_smf" % node.name @@ -189,21 +188,21 @@ class NrlOlsrv2(NrlService): """ Optimized Link State Routing protocol version 2 for MANET networks. """ - _name = "OLSRv2" - _startup = ("nrlolsrv2",) - _shutdown = ("killall nrlolsrv2",) - _validate = ("pidof nrlolsrv2",) + name = "OLSRv2" + startup = ("nrlolsrv2",) + shutdown = ("killall nrlolsrv2",) + validate = ("pidof nrlolsrv2",) @classmethod def getstartup(cls, node, services): """ Generate the appropriate command-line based on node interfaces. """ - cmd = cls._startup[0] + cmd = cls.startup[0] cmd += " -l /var/log/nrlolsrv2.log" cmd += " -rpipe %s_olsrv2" % node.name - servicenames = map(lambda x: x._name, services) + servicenames = map(lambda x: x.name, services) if "SMF" in servicenames: cmd += " -flooding ecds" cmd += " -smfClient %s_smf" % node.name @@ -223,19 +222,19 @@ class OlsrOrg(NrlService): """ Optimized Link State Routing protocol from olsr.org for MANET networks. """ - _name = "OLSRORG" - _configs = ("/etc/olsrd/olsrd.conf",) - _dirs = ("/etc/olsrd",) - _startup = ("olsrd",) - _shutdown = ("killall olsrd",) - _validate = ("pidof olsrd",) + name = "OLSRORG" + configs = ("/etc/olsrd/olsrd.conf",) + dirs = ("/etc/olsrd",) + startup = ("olsrd",) + shutdown = ("killall olsrd",) + validate = ("pidof olsrd",) @classmethod def getstartup(cls, node, services): """ Generate the appropriate command-line based on node interfaces. """ - cmd = cls._startup[0] + cmd = cls.startup[0] netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs()) if len(netifs) > 0: interfacenames = map(lambda x: x.name, netifs) @@ -572,24 +571,24 @@ class MgenActor(NrlService): """ # a unique name is required, without spaces - _name = "MgenActor" + name = "MgenActor" # you can create your own group here - _group = "ProtoSvc" + group = "ProtoSvc" # list of other services this service depends on - _depends = () + depends = () # per-node directories - _dirs = () + dirs = () # generated files (without a full path this file goes in the node's dir, # e.g. /tmp/pycore.12345/n1.conf/) - _configs = ('start_mgen_actor.sh',) + configs = ('start_mgen_actor.sh',) # this controls the starting order vs other enabled services - _startindex = 50 + startindex = 50 # list of startup commands, also may be generated during startup - _startup = ("sh start_mgen_actor.sh",) + startup = ("sh start_mgen_actor.sh",) # list of validation commands - _validate = ("pidof mgen",) + validate = ("pidof mgen",) # list of shutdown commands - _shutdown = ("killall mgen",) + shutdown = ("killall mgen",) @classmethod def generateconfig(cls, node, filename, services): @@ -603,7 +602,7 @@ class MgenActor(NrlService): comments = "" cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name - servicenames = map(lambda x: x._name, services) + servicenames = map(lambda x: x.name, services) netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs()) if len(netifs) == 0: return "" @@ -616,12 +615,12 @@ class Arouted(NrlService): """ Adaptive Routing """ - _name = "arouted" - _configs = ("startarouted.sh",) - _startindex = NrlService._startindex + 10 - _startup = ("sh startarouted.sh",) - _shutdown = ("pkill arouted",) - _validate = ("pidof arouted",) + name = "arouted" + configs = ("startarouted.sh",) + startindex = NrlService.startindex + 10 + startup = ("sh startarouted.sh",) + shutdown = ("pkill arouted",) + validate = ("pidof arouted",) @classmethod def generateconfig(cls, node, filename, services): diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index 73f797a5..791d892d 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -10,33 +10,33 @@ from core.service import CoreService class Zebra(CoreService): - _name = "zebra" - _group = "Quagga" - _dirs = ("/usr/local/etc/quagga", "/var/run/quagga") - _configs = ( + name = "zebra" + group = "Quagga" + dirs = ("/usr/local/etc/quagga", "/var/run/quagga") + configs = ( "/usr/local/etc/quagga/Quagga.conf", "quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf" ) - _startindex = 35 - _startup = ("sh quaggaboot.sh zebra",) - _shutdown = ("killall zebra",) - _validate = ("pidof zebra",) + startindex = 35 + startup = ("sh quaggaboot.sh zebra",) + shutdown = ("killall zebra",) + validate = ("pidof zebra",) @classmethod def generateconfig(cls, node, filename, services): """ Return the Quagga.conf or quaggaboot.sh file contents. """ - if filename == cls._configs[0]: + if filename == cls.configs[0]: return cls.generateQuaggaConf(node, services) - elif filename == cls._configs[1]: + elif filename == cls.configs[1]: return cls.generateQuaggaBoot(node, services) - elif filename == cls._configs[2]: + elif filename == cls.configs[2]: return cls.generateVtyshConf(node, services) else: raise ValueError("file name (%s) is not a known configuration: %s", - filename, cls._configs) + filename, cls.configs) @classmethod def generateVtyshConf(cls, node, services): @@ -67,12 +67,12 @@ class Zebra(CoreService): want_ipv4 = False want_ipv6 = False for s in services: - if cls._name not in s._depends: + if cls.name not in s.depends: continue ifccfg = s.generatequaggaifcconfig(node, ifc) - if s._ipv4_routing: + if s.ipv4_routing: want_ipv4 = True - if s._ipv6_routing: + if s.ipv6_routing: want_ipv6 = True cfgv6 += ifccfg else: @@ -93,7 +93,7 @@ class Zebra(CoreService): cfg += "!\n" for s in services: - if cls._name not in s._depends: + if cls.name not in s.depends: continue cfg += s.generatequaggaconfig(node) return cfg @@ -212,7 +212,7 @@ if [ "$1" != "zebra" ]; then fi confcheck bootquagga -""" % (cls._configs[0], quagga_sbin_search, quagga_bin_search, constants.QUAGGA_STATE_DIR) +""" % (cls.configs[0], quagga_sbin_search, quagga_bin_search, constants.QUAGGA_STATE_DIR) class QuaggaService(CoreService): @@ -220,18 +220,18 @@ class QuaggaService(CoreService): Parent class for Quagga services. Defines properties and methods common to Quagga's routing daemons. """ - _name = None - _group = "Quagga" - _depends = ("zebra",) - _dirs = () - _configs = () - _startindex = 40 - _startup = () - _shutdown = () - _meta = "The config file for this service can be found in the Zebra service." + name = None + group = "Quagga" + depends = ("zebra",) + dirs = () + configs = () + startindex = 40 + startup = () + shutdown = () + meta = "The config file for this service can be found in the Zebra service." - _ipv4_routing = False - _ipv6_routing = False + ipv4_routing = False + ipv6_routing = False @staticmethod def routerid(node): @@ -239,7 +239,7 @@ class QuaggaService(CoreService): Helper to return the first IPv4 address of a node as its router ID. """ for ifc in node.netifs(): - if hasattr(ifc, 'control') and ifc.control == True: + if hasattr(ifc, 'control') and ifc.control is True: continue for a in ifc.addrlist: if a.find(".") >= 0: @@ -280,11 +280,11 @@ class Ospfv2(QuaggaService): not build its own configuration file but has hooks for adding to the unified Quagga.conf file. """ - _name = "OSPFv2" - _startup = () - _shutdown = ("killall ospfd",) - _validate = ("pidof ospfd",) - _ipv4_routing = True + name = "OSPFv2" + startup = () + shutdown = ("killall ospfd",) + validate = ("pidof ospfd",) + ipv4_routing = True @staticmethod def mtucheck(ifc): @@ -355,12 +355,12 @@ class Ospfv3(QuaggaService): not build its own configuration file but has hooks for adding to the unified Quagga.conf file. """ - _name = "OSPFv3" - _startup = () - _shutdown = ("killall ospf6d",) - _validate = ("pidof ospf6d",) - _ipv4_routing = True - _ipv6_routing = True + name = "OSPFv3" + startup = () + shutdown = ("killall ospf6d",) + validate = ("pidof ospf6d",) + ipv4_routing = True + ipv6_routing = True @staticmethod def minmtu(ifc): @@ -436,8 +436,8 @@ class Ospfv3mdr(Ospfv3): configuration file but has hooks for adding to the unified Quagga.conf file. """ - _name = "OSPFv3MDR" - _ipv4_routing = True + name = "OSPFv3MDR" + ipv4_routing = True @classmethod def generatequaggaifcconfig(cls, node, ifc): @@ -464,13 +464,13 @@ class Bgp(QuaggaService): Peers must be manually configured, with a full mesh for those having the same AS number. """ - _name = "BGP" - _startup = () - _shutdown = ("killall bgpd",) - _validate = ("pidof bgpd",) - _custom_needed = True - _ipv4_routing = True - _ipv6_routing = True + name = "BGP" + startup = () + shutdown = ("killall bgpd",) + validate = ("pidof bgpd",) + custom_needed = True + ipv4_routing = True + ipv6_routing = True @classmethod def generatequaggaconfig(cls, node): @@ -489,11 +489,11 @@ class Rip(QuaggaService): """ The RIP service provides IPv4 routing for wired networks. """ - _name = "RIP" - _startup = () - _shutdown = ("killall ripd",) - _validate = ("pidof ripd",) - _ipv4_routing = True + name = "RIP" + startup = () + shutdown = ("killall ripd",) + validate = ("pidof ripd",) + ipv4_routing = True @classmethod def generatequaggaconfig(cls, node): @@ -512,11 +512,11 @@ class Ripng(QuaggaService): """ The RIP NG service provides IPv6 routing for wired networks. """ - _name = "RIPNG" - _startup = () - _shutdown = ("killall ripngd",) - _validate = ("pidof ripngd",) - _ipv6_routing = True + name = "RIPNG" + startup = () + shutdown = ("killall ripngd",) + validate = ("pidof ripngd",) + ipv6_routing = True @classmethod def generatequaggaconfig(cls, node): @@ -536,11 +536,11 @@ class Babel(QuaggaService): The Babel service provides a loop-avoiding distance-vector routing protocol for IPv6 and IPv4 with fast convergence properties. """ - _name = "Babel" - _startup = () - _shutdown = ("killall babeld",) - _validate = ("pidof babeld",) - _ipv6_routing = True + name = "Babel" + startup = () + shutdown = ("killall babeld",) + validate = ("pidof babeld",) + ipv6_routing = True @classmethod def generatequaggaconfig(cls, node): @@ -565,11 +565,11 @@ class Xpimd(QuaggaService): """ PIM multicast routing based on XORP. """ - _name = 'Xpimd' - _startup = () - _shutdown = ('killall xpimd',) - _validate = ('pidof xpimd',) - _ipv4_routing = True + name = 'Xpimd' + startup = () + shutdown = ('killall xpimd',) + validate = ('pidof xpimd',) + ipv4_routing = True @classmethod def generatequaggaconfig(cls, node): diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index 9d8a26ba..33f50109 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -11,14 +11,14 @@ class SdnService(CoreService): """ Parent class for SDN services. """ - _name = None - _group = "SDN" - _depends = () - _dirs = () - _configs = () - _startindex = 50 - _startup = () - _shutdown = () + name = None + group = "SDN" + depends = () + dirs = () + configs = () + startindex = 50 + startup = () + shutdown = () @classmethod def generateconfig(cls, node, filename, services): @@ -26,27 +26,27 @@ class SdnService(CoreService): class OvsService(SdnService): - _name = "OvsService" - _group = "SDN" - _depends = () - _dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch") - _configs = ('OvsService.sh',) - _startindex = 50 - _startup = ('sh OvsService.sh',) - _shutdown = ('killall ovs-vswitchd', 'killall ovsdb-server') + name = "OvsService" + group = "SDN" + depends = () + dirs = ("/etc/openvswitch", "/var/run/openvswitch", "/var/log/openvswitch") + configs = ('OvsService.sh',) + startindex = 50 + startup = ('sh OvsService.sh',) + shutdown = ('killall ovs-vswitchd', 'killall ovsdb-server') @classmethod def generateconfig(cls, node, filename, services): # Check whether the node is running zebra has_zebra = 0 for s in services: - if s._name == "zebra": + if s.name == "zebra": has_zebra = 1 # Check whether the node is running an SDN controller has_sdn_ctrlr = 0 for s in services: - if s._name == "ryuService": + if s.name == "ryuService": has_sdn_ctrlr = 1 cfg = "#!/bin/sh\n" @@ -100,14 +100,14 @@ class OvsService(SdnService): class RyuService(SdnService): - _name = "ryuService" - _group = "SDN" - _depends = () - _dirs = () - _configs = ('ryuService.sh',) - _startindex = 50 - _startup = ('sh ryuService.sh',) - _shutdown = ('killall ryu-manager',) + name = "ryuService" + group = "SDN" + depends = () + dirs = () + configs = ('ryuService.sh',) + startindex = 50 + startup = ('sh ryuService.sh',) + shutdown = ('killall ryu-manager',) @classmethod def generateconfig(cls, node, filename, services): diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index 34a37624..a8318e42 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -9,14 +9,14 @@ from core.service import CoreService class VPNClient(CoreService): - _name = "VPNClient" - _group = "Security" - _configs = ('vpnclient.sh',) - _startindex = 60 - _startup = ('sh vpnclient.sh',) - _shutdown = ("killall openvpn",) - _validate = ("pidof openvpn",) - _custom_needed = True + name = "VPNClient" + group = "Security" + configs = ('vpnclient.sh',) + startindex = 60 + startup = ('sh vpnclient.sh',) + shutdown = ("killall openvpn",) + validate = ("pidof openvpn",) + custom_needed = True @classmethod def generateconfig(cls, node, filename, services): @@ -36,14 +36,14 @@ class VPNClient(CoreService): class VPNServer(CoreService): - _name = "VPNServer" - _group = "Security" - _configs = ('vpnserver.sh',) - _startindex = 50 - _startup = ('sh vpnserver.sh',) - _shutdown = ("killall openvpn",) - _validate = ("pidof openvpn",) - _custom_needed = True + name = "VPNServer" + group = "Security" + configs = ('vpnserver.sh',) + startindex = 50 + startup = ('sh vpnserver.sh',) + shutdown = ("killall openvpn",) + validate = ("pidof openvpn",) + custom_needed = True @classmethod def generateconfig(cls, node, filename, services): @@ -64,13 +64,13 @@ class VPNServer(CoreService): class IPsec(CoreService): - _name = "IPsec" - _group = "Security" - _configs = ('ipsec.sh',) - _startindex = 60 - _startup = ('sh ipsec.sh',) - _shutdown = ("killall racoon",) - _custom_needed = True + name = "IPsec" + group = "Security" + configs = ('ipsec.sh',) + startindex = 60 + startup = ('sh ipsec.sh',) + shutdown = ("killall racoon",) + custom_needed = True @classmethod def generateconfig(cls, node, filename, services): @@ -92,12 +92,12 @@ class IPsec(CoreService): class Firewall(CoreService): - _name = "Firewall" - _group = "Security" - _configs = ('firewall.sh',) - _startindex = 20 - _startup = ('sh firewall.sh',) - _custom_needed = True + name = "Firewall" + group = "Security" + configs = ('firewall.sh',) + startindex = 20 + startup = ('sh firewall.sh',) + custom_needed = True @classmethod def generateconfig(cls, node, filename, services): diff --git a/daemon/core/services/startup.py b/daemon/core/services/startup.py index a26cd6f4..6346c541 100644 --- a/daemon/core/services/startup.py +++ b/daemon/core/services/startup.py @@ -8,15 +8,15 @@ class Startup(CoreService): """ A CORE service to start other services in order, serially """ - _name = 'startup' - _group = 'Utility' - _depends = () - _dirs = () - _configs = ('startup.sh',) - _startindex = maxint - _startup = ('sh startup.sh',) - _shutdown = () - _validate = () + name = 'startup' + group = 'Utility' + depends = () + dirs = () + configs = ('startup.sh',) + startindex = maxint + startup = ('sh startup.sh',) + shutdown = () + validate = () @staticmethod def is_startup_service(s): @@ -24,13 +24,13 @@ class Startup(CoreService): @classmethod def generateconfig(cls, node, filename, services): - if filename != cls._configs[0]: + if filename != cls.configs[0]: return '' script = '#!/bin/sh\n' \ '# auto-generated by Startup (startup.py)\n\n' \ 'exec > startup.log 2>&1\n\n' - for s in sorted(services, key=lambda x: x._startindex): - if cls.is_startup_service(s) or len(str(s._starttime)) > 0: + for s in sorted(services, key=lambda x: x.startindex): + if cls.is_startup_service(s) or len(str(s.starttime)) > 0: continue start = '\n'.join(s.getstartup(node, services)) if start: diff --git a/daemon/core/services/ucarp.py b/daemon/core/services/ucarp.py index ee73e7ca..5009ea6d 100644 --- a/daemon/core/services/ucarp.py +++ b/daemon/core/services/ucarp.py @@ -8,29 +8,29 @@ UCARP_ETC = "/usr/local/etc/ucarp" class Ucarp(CoreService): - _name = "ucarp" - _group = "Utility" - _depends = ( ) - _dirs = (UCARP_ETC,) - _configs = ( + name = "ucarp" + group = "Utility" + depends = ( ) + dirs = (UCARP_ETC,) + configs = ( UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh",) - _startindex = 65 - _startup = ("sh ucarpboot.sh",) - _shutdown = ("killall ucarp",) - _validate = ("pidof ucarp",) + startindex = 65 + startup = ("sh ucarpboot.sh",) + shutdown = ("killall ucarp",) + validate = ("pidof ucarp",) @classmethod def generateconfig(cls, node, filename, services): """ Return the default file contents """ - if filename == cls._configs[0]: + if filename == cls.configs[0]: return cls.generateUcarpConf(node, services) - elif filename == cls._configs[1]: + elif filename == cls.configs[1]: return cls.generateVipUp(node, services) - elif filename == cls._configs[2]: + elif filename == cls.configs[2]: return cls.generateVipDown(node, services) - elif filename == cls._configs[3]: + elif filename == cls.configs[3]: return cls.generateUcarpBoot(node, services) else: raise ValueError diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index e1495ca9..2bc11893 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -16,14 +16,14 @@ class UtilService(CoreService): """ Parent class for utility services. """ - _name = None - _group = "Utility" - _depends = () - _dirs = () - _configs = () - _startindex = 80 - _startup = () - _shutdown = () + name = None + group = "Utility" + depends = () + dirs = () + configs = () + startindex = 80 + startup = () + shutdown = () @classmethod def generateconfig(cls, node, filename, services): @@ -31,10 +31,10 @@ class UtilService(CoreService): class IPForwardService(UtilService): - _name = "IPForward" - _configs = ("ipforward.sh",) - _startindex = 5 - _startup = ("sh ipforward.sh",) + name = "IPForward" + configs = ("ipforward.sh",) + startindex = 5 + startup = ("sh ipforward.sh",) @classmethod def generateconfig(cls, node, filename, services): @@ -67,9 +67,9 @@ class IPForwardService(UtilService): class DefaultRouteService(UtilService): - _name = "DefaultRoute" - _configs = ("defaultroute.sh",) - _startup = ("sh defaultroute.sh",) + name = "DefaultRoute" + configs = ("defaultroute.sh",) + startup = ("sh defaultroute.sh",) @classmethod def generateconfig(cls, node, filename, services): @@ -101,9 +101,9 @@ class DefaultRouteService(UtilService): class DefaultMulticastRouteService(UtilService): - _name = "DefaultMulticastRoute" - _configs = ("defaultmroute.sh",) - _startup = ("sh defaultmroute.sh",) + name = "DefaultMulticastRoute" + configs = ("defaultmroute.sh",) + startup = ("sh defaultmroute.sh",) @classmethod def generateconfig(cls, node, filename, services): @@ -113,7 +113,7 @@ class DefaultMulticastRouteService(UtilService): cfg += "as needed\n" for ifc in node.netifs(): - if hasattr(ifc, 'control') and ifc.control == True: + if hasattr(ifc, 'control') and ifc.control is True: continue if os.uname()[0] == "Linux": rtcmd = "ip route add 224.0.0.0/4 dev" @@ -126,10 +126,10 @@ class DefaultMulticastRouteService(UtilService): class StaticRouteService(UtilService): - _name = "StaticRoute" - _configs = ("staticroute.sh",) - _startup = ("sh staticroute.sh",) - _custom_needed = True + name = "StaticRoute" + configs = ("staticroute.sh",) + startup = ("sh staticroute.sh",) + custom_needed = True @classmethod def generateconfig(cls, node, filename, services): @@ -165,12 +165,12 @@ class StaticRouteService(UtilService): class SshService(UtilService): - _name = "SSH" - _configs = ("startsshd.sh", "/etc/ssh/sshd_config",) - _dirs = ("/etc/ssh", "/var/run/sshd",) - _startup = ("sh startsshd.sh",) - _shutdown = ("killall sshd",) - _validate = () + name = "SSH" + configs = ("startsshd.sh", "/etc/ssh/sshd_config",) + dirs = ("/etc/ssh", "/var/run/sshd",) + startup = ("sh startsshd.sh",) + shutdown = ("killall sshd",) + validate = () @classmethod def generateconfig(cls, node, filename, services): @@ -178,8 +178,8 @@ class SshService(UtilService): Use a startup script for launching sshd in order to wait for host key generation. """ - sshcfgdir = cls._dirs[0] - sshstatedir = cls._dirs[1] + sshcfgdir = cls.dirs[0] + sshstatedir = cls.dirs[1] sshlibdir = "/usr/lib/openssh" if filename == "startsshd.sh": return """\ @@ -233,12 +233,12 @@ UseDNS no class DhcpService(UtilService): - _name = "DHCP" - _configs = ("/etc/dhcp/dhcpd.conf",) - _dirs = ("/etc/dhcp",) - _startup = ("dhcpd",) - _shutdown = ("killall dhcpd",) - _validate = ("pidof dhcpd",) + name = "DHCP" + configs = ("/etc/dhcp/dhcpd.conf",) + dirs = ("/etc/dhcp",) + startup = ("dhcpd",) + shutdown = ("killall dhcpd",) + validate = ("pidof dhcpd",) @classmethod def generateconfig(cls, node, filename, services): @@ -296,11 +296,11 @@ class DhcpClientService(UtilService): """ Use a DHCP client for all interfaces for addressing. """ - _name = "DHCPClient" - _configs = ("startdhcpclient.sh",) - _startup = ("sh startdhcpclient.sh",) - _shutdown = ("killall dhclient",) - _validate = ("pidof dhclient",) + name = "DHCPClient" + configs = ("startdhcpclient.sh",) + startup = ("sh startdhcpclient.sh",) + shutdown = ("killall dhclient",) + validate = ("pidof dhclient",) @classmethod def generateconfig(cls, node, filename, services): @@ -327,12 +327,12 @@ class FtpService(UtilService): """ Start a vsftpd server. """ - _name = "FTP" - _configs = ("vsftpd.conf",) - _dirs = ("/var/run/vsftpd/empty", "/var/ftp",) - _startup = ("vsftpd ./vsftpd.conf",) - _shutdown = ("killall vsftpd",) - _validate = ("pidof vsftpd",) + name = "FTP" + configs = ("vsftpd.conf",) + dirs = ("/var/run/vsftpd/empty", "/var/ftp",) + startup = ("vsftpd ./vsftpd.conf",) + shutdown = ("killall vsftpd",) + validate = ("pidof vsftpd",) @classmethod def generateconfig(cls, node, filename, services): @@ -359,14 +359,14 @@ class HttpService(UtilService): """ Start an apache server. """ - _name = "HTTP" - _configs = ("/etc/apache2/apache2.conf", "/etc/apache2/envvars", - "/var/www/index.html",) - _dirs = ("/etc/apache2", "/var/run/apache2", "/var/log/apache2", - "/run/lock", "/var/lock/apache2", "/var/www",) - _startup = ("chown www-data /var/lock/apache2", "apache2ctl start",) - _shutdown = ("apache2ctl stop",) - _validate = ("pidof apache2",) + name = "HTTP" + configs = ("/etc/apache2/apache2.conf", "/etc/apache2/envvars", + "/var/www/index.html",) + dirs = ("/etc/apache2", "/var/run/apache2", "/var/log/apache2", + "/run/lock", "/var/lock/apache2", "/var/www",) + startup = ("chown www-data /var/lock/apache2", "apache2ctl start",) + shutdown = ("apache2ctl stop",) + validate = ("pidof apache2",) APACHEVER22, APACHEVER24 = (22, 24) @@ -375,11 +375,11 @@ class HttpService(UtilService): """ Generate an apache2.conf configuration file. """ - if filename == cls._configs[0]: + if filename == cls.configs[0]: return cls.generateapache2conf(node, filename, services) - elif filename == cls._configs[1]: + elif filename == cls.configs[1]: return cls.generateenvvars(node, filename, services) - elif filename == cls._configs[2]: + elif filename == cls.configs[2]: return cls.generatehtml(node, filename, services) else: return "" @@ -561,7 +561,7 @@ export LANG

The web server software is running but no content has been added, yet.

""" % node.name for ifc in node.netifs(): - if hasattr(ifc, 'control') and ifc.control == True: + if hasattr(ifc, 'control') and ifc.control is True: continue body += "
  • %s - %s
  • \n" % (ifc.name, ifc.addrlist) return "%s" % body @@ -571,14 +571,14 @@ class PcapService(UtilService): """ Pcap service for logging packets. """ - _name = "pcap" - _configs = ("pcap.sh",) - _dirs = () - _startindex = 1 - _startup = ("sh pcap.sh start",) - _shutdown = ("sh pcap.sh stop",) - _validate = ("pidof tcpdump",) - _meta = "logs network traffic to pcap packet capture files" + name = "pcap" + configs = ("pcap.sh",) + dirs = () + startindex = 1 + startup = ("sh pcap.sh start",) + shutdown = ("sh pcap.sh stop",) + validate = ("pidof tcpdump",) + meta = "logs network traffic to pcap packet capture files" @classmethod def generateconfig(cls, node, filename, services): @@ -595,7 +595,7 @@ if [ "x$1" = "xstart" ]; then """ for ifc in node.netifs(): - if hasattr(ifc, 'control') and ifc.control == True: + if hasattr(ifc, 'control') and ifc.control is True: cfg += '# ' redir = "< /dev/null" cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % \ @@ -611,12 +611,12 @@ fi; class RadvdService(UtilService): - _name = "radvd" - _configs = ("/etc/radvd/radvd.conf",) - _dirs = ("/etc/radvd",) - _startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",) - _shutdown = ("pkill radvd",) - _validate = ("pidof radvd",) + name = "radvd" + configs = ("/etc/radvd/radvd.conf",) + dirs = ("/etc/radvd",) + startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",) + shutdown = ("pkill radvd",) + validate = ("pidof radvd",) @classmethod def generateconfig(cls, node, filename, services): @@ -626,7 +626,7 @@ class RadvdService(UtilService): """ cfg = "# auto-generated by RADVD service (utility.py)\n" for ifc in node.netifs(): - if hasattr(ifc, 'control') and ifc.control == True: + if hasattr(ifc, 'control') and ifc.control is True: continue prefixes = map(cls.subnetentry, ifc.addrlist) if len(prefixes) < 1: @@ -671,11 +671,11 @@ class AtdService(UtilService): """ Atd service for scheduling at jobs """ - _name = "atd" - _configs = ("startatd.sh",) - _dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") - _startup = ("sh startatd.sh",) - _shutdown = ("pkill atd",) + name = "atd" + configs = ("startatd.sh",) + dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") + startup = ("sh startatd.sh",) + shutdown = ("pkill atd",) @classmethod def generateconfig(cls, node, filename, services): @@ -692,6 +692,6 @@ class UserDefinedService(UtilService): """ Dummy service allowing customization of anything. """ - _name = "UserDefined" - _startindex = 50 - _meta = "Customize this service to do anything upon startup." + name = "UserDefined" + startindex = 50 + meta = "Customize this service to do anything upon startup." diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index bbe58769..a6b85609 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -11,15 +11,15 @@ class XorpRtrmgr(CoreService): XORP router manager service builds a config.boot file based on other enabled XORP services, and launches necessary daemons upon startup. """ - _name = "xorp_rtrmgr" - _group = "XORP" - _depends = () - _dirs = ("/etc/xorp",) - _configs = ("/etc/xorp/config.boot",) - _startindex = 35 - _startup = ("xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (_configs[0], _name, _name),) - _shutdown = ("killall xorp_rtrmgr",) - _validate = ("pidof xorp_rtrmgr",) + name = "xorp_rtrmgr" + group = "XORP" + depends = () + dirs = ("/etc/xorp",) + configs = ("/etc/xorp/config.boot",) + startindex = 35 + startup = ("xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (configs[0], name, name),) + shutdown = ("killall xorp_rtrmgr",) + validate = ("pidof xorp_rtrmgr",) @classmethod def generateconfig(cls, node, filename, services): @@ -40,10 +40,10 @@ class XorpRtrmgr(CoreService): for s in services: try: - s._depends.index(cls._name) + s.depends.index(cls.name) cfg += s.generatexorpconfig(node) except ValueError: - logger.exception("error getting value from service: %s", cls._name) + logger.exception("error getting value from service: %s", cls.name) return cfg @@ -74,15 +74,15 @@ class XorpService(CoreService): Parent class for XORP services. Defines properties and methods common to XORP's routing daemons. """ - _name = None - _group = "XORP" - _depends = ("xorp_rtrmgr",) - _dirs = () - _configs = () - _startindex = 40 - _startup = () - _shutdown = () - _meta = "The config file for this service can be found in the xorp_rtrmgr service." + name = None + group = "XORP" + depends = ("xorp_rtrmgr",) + dirs = () + configs = () + startindex = 40 + startup = () + shutdown = () + meta = "The config file for this service can be found in the xorp_rtrmgr service." @staticmethod def fea(forwarding): @@ -165,7 +165,7 @@ class XorpOspfv2(XorpService): not build its own configuration file but has hooks for adding to the unified XORP configuration file. """ - _name = "XORP_OSPFv2" + name = "XORP_OSPFv2" @classmethod def generatexorpconfig(cls, node): @@ -200,7 +200,7 @@ class XorpOspfv3(XorpService): not build its own configuration file but has hooks for adding to the unified XORP configuration file. """ - _name = "XORP_OSPFv3" + name = "XORP_OSPFv3" @classmethod def generatexorpconfig(cls, node): @@ -227,8 +227,8 @@ class XorpBgp(XorpService): """ IPv4 inter-domain routing. AS numbers and peers must be customized. """ - _name = "XORP_BGP" - _custom_needed = True + name = "XORP_BGP" + custom_needed = True @classmethod def generatexorpconfig(cls, node): @@ -257,7 +257,7 @@ class XorpRip(XorpService): RIP IPv4 unicast routing. """ - _name = "XORP_RIP" + name = "XORP_RIP" @classmethod def generatexorpconfig(cls, node): @@ -289,7 +289,7 @@ class XorpRipng(XorpService): """ RIP NG IPv6 unicast routing. """ - _name = "XORP_RIPNG" + name = "XORP_RIPNG" @classmethod def generatexorpconfig(cls, node): @@ -324,7 +324,7 @@ class XorpPimSm4(XorpService): """ PIM Sparse Mode IPv4 multicast routing. """ - _name = "XORP_PIMSM4" + name = "XORP_PIMSM4" @classmethod def generatexorpconfig(cls, node): @@ -383,7 +383,7 @@ class XorpPimSm6(XorpService): """ PIM Sparse Mode IPv6 multicast routing. """ - _name = "XORP_PIMSM6" + name = "XORP_PIMSM6" @classmethod def generatexorpconfig(cls, node): @@ -442,7 +442,7 @@ class XorpOlsr(XorpService): """ OLSR IPv4 unicast MANET routing. """ - _name = "XORP_OLSR" + name = "XORP_OLSR" @classmethod def generatexorpconfig(cls, node): diff --git a/daemon/core/xml/xmlparser0.py b/daemon/core/xml/xmlparser0.py index 6ab25912..be9a1f19 100644 --- a/daemon/core/xml/xmlparser0.py +++ b/daemon/core/xml/xmlparser0.py @@ -4,7 +4,7 @@ from core import logger from core.conf import ConfigShim from core.enumerations import NodeTypes from core.misc import nodeutils -from core.service import ServiceManager +from core.service import ServiceManager, ServiceShim from core.xml import xmlutils @@ -316,7 +316,10 @@ class CoreDocumentParser0(object): # associate nodes with services for objid in sorted(svclists.keys()): n = self.session.get_object(objid) - self.session.services.addservicestonode(node=n, nodetype=n.type, services_str=svclists[objid]) + services = svclists[objid] + if services: + services = services.split("|") + self.session.services.addservicestonode(node=n, node_type=n.type, services=services) def parseservice(self, service, n): """ @@ -367,16 +370,20 @@ class CoreDocumentParser0(object): filename = file.getAttribute("name") files.append(filename) data = xmlutils.get_text_child(file) - typestr = "service:%s:%s" % (name, filename) - self.session.services.setservicefile(nodenum=n.objid, type=typestr, - filename=filename, - srcname=None, data=data) + self.session.services.setservicefile(node_id=n.objid, service_name=name, filename=filename, data=data) + if len(files): values.append("files=%s" % files) if not bool(service.getAttribute("custom")): return True - values = ConfigShim.str_to_dict(values) - self.session.services.setcustomservice(n.objid, svc, values) + self.session.services.setcustomservice(n.objid, svc) + # set custom values for custom service + svc = self.session.services.getcustomservice(n.objid, None) + if not svc: + raise ValueError("custom service(%s) for node(%s) does not exist", svc.name, n.objid) + values = ConfigShim.str_to_dict("|".join(values)) + for name, value in values.iteritems(): + ServiceShim.setvalue(svc, name, value) return True def parsehooks(self, hooks): diff --git a/daemon/core/xml/xmlparser1.py b/daemon/core/xml/xmlparser1.py index 46f6bcf9..cef79590 100644 --- a/daemon/core/xml/xmlparser1.py +++ b/daemon/core/xml/xmlparser1.py @@ -8,7 +8,7 @@ from core.conf import ConfigShim from core.enumerations import NodeTypes from core.misc import nodeutils from core.misc.ipaddress import MacAddress -from core.service import ServiceManager +from core.service import ServiceManager, ServiceShim from core.xml import xmlutils @@ -639,17 +639,19 @@ class CoreDocumentParser1(object): custom = service.getAttribute('custom') if custom and custom.lower() == 'true': - values = ConfigShim.str_to_dict(values) - self.session.services.setcustomservice(node.objid, session_service, values) + self.session.services.setcustomservice(node.objid, session_service.name) + values = ConfigShim.str_to_dict("|".join(values)) + for key, value in values.iteritems(): + ServiceShim.setvalue(session_service, key, value) # NOTE: if a custom service is used, setservicefile() must be # called after the custom service exists for typestr, filename, data in files: + svcname = typestr.split(":")[1] self.session.services.setservicefile( - nodenum=node.objid, - type=typestr, + node_id=node.objid, + service_name=svcname, filename=filename, - srcname=None, data=data ) return str(name) @@ -678,10 +680,13 @@ class CoreDocumentParser1(object): services_str = None # default services will be added else: return + if services_str: + services_str = services_str.split("|") + self.session.services.addservicestonode( node=node, - nodetype=node_type, - services_str=services_str + node_type=node_type, + services=services_str ) def set_object_presentation(self, obj, element, node_type): diff --git a/daemon/core/xml/xmlwriter0.py b/daemon/core/xml/xmlwriter0.py index a11e099a..41f097fc 100644 --- a/daemon/core/xml/xmlwriter0.py +++ b/daemon/core/xml/xmlwriter0.py @@ -284,7 +284,7 @@ class CoreDocumentWriter0(Document): for svc in defaults: s = self.createElement("Service") spn.appendChild(s) - s.setAttribute("name", str(svc._name)) + s.setAttribute("name", str(svc.name)) def addservices(self, node): """ @@ -302,17 +302,17 @@ class CoreDocumentWriter0(Document): for svc in node.services: s = self.createElement("Service") spn.appendChild(s) - s.setAttribute("name", str(svc._name)) - s.setAttribute("startup_idx", str(svc._startindex)) - if svc._starttime != "": - s.setAttribute("start_time", str(svc._starttime)) + s.setAttribute("name", str(svc.name)) + s.setAttribute("startup_idx", str(svc.startindex)) + if svc.starttime != "": + s.setAttribute("start_time", str(svc.starttime)) # only record service names if not a customized service - if not svc._custom: + if not svc.custom: continue - s.setAttribute("custom", str(svc._custom)) - xmlutils.add_elements_from_list(self, s, svc._dirs, "Directory", "name") + s.setAttribute("custom", str(svc.custom)) + xmlutils.add_elements_from_list(self, s, svc.dirs, "Directory", "name") - for fn in svc._configs: + for fn in svc.configs: if len(fn) == 0: continue f = self.createElement("File") @@ -327,9 +327,9 @@ class CoreDocumentWriter0(Document): txt = self.createTextNode(data) f.appendChild(txt) - xmlutils.add_text_elements_from_list(self, s, svc._startup, "Command", (("type", "start"),)) - xmlutils.add_text_elements_from_list(self, s, svc._shutdown, "Command", (("type", "stop"),)) - xmlutils.add_text_elements_from_list(self, s, svc._validate, "Command", (("type", "validate"),)) + xmlutils.add_text_elements_from_list(self, s, svc.startup, "Command", (("type", "start"),)) + xmlutils.add_text_elements_from_list(self, s, svc.shutdown, "Command", (("type", "stop"),)) + xmlutils.add_text_elements_from_list(self, s, svc.validate, "Command", (("type", "validate"),)) def addaddresses(self, i, netif): """ diff --git a/daemon/core/xml/xmlwriter1.py b/daemon/core/xml/xmlwriter1.py index 6f5deb50..22c03a23 100644 --- a/daemon/core/xml/xmlwriter1.py +++ b/daemon/core/xml/xmlwriter1.py @@ -277,7 +277,7 @@ class ScenarioPlan(XmlElement): for svc in defaults: s = self.createElement("service") spn.appendChild(s) - s.setAttribute("name", str(svc._name)) + s.setAttribute("name", str(svc.name)) if defaultservices.hasChildNodes(): self.appendChild(defaultservices) @@ -680,24 +680,24 @@ class DeviceElement(NamedXmlElement): for svc in device_object.services: s = self.createElement("service") spn.appendChild(s) - s.setAttribute("name", str(svc._name)) - s.setAttribute("startup_idx", str(svc._startindex)) - if svc._starttime != "": - s.setAttribute("start_time", str(svc._starttime)) + s.setAttribute("name", str(svc.name)) + s.setAttribute("startup_idx", str(svc.startindex)) + if svc.starttime != "": + s.setAttribute("start_time", str(svc.starttime)) # only record service names if not a customized service - if not svc._custom: + if not svc.custom: continue - s.setAttribute("custom", str(svc._custom)) - xmlutils.add_elements_from_list(self, s, svc._dirs, "directory", "name") + s.setAttribute("custom", str(svc.custom)) + xmlutils.add_elements_from_list(self, s, svc.dirs, "directory", "name") - for fn in svc._configs: + for fn in svc.configs: if len(fn) == 0: continue f = self.createElement("file") f.setAttribute("name", fn) # all file names are added to determine when a file has been deleted s.appendChild(f) - data = self.coreSession.services.getservicefiledata(svc, fn) + data = svc.configtxt.get(fn) if data is None: # this includes only customized file contents and skips # the auto-generated files @@ -705,12 +705,9 @@ class DeviceElement(NamedXmlElement): txt = self.createTextNode("\n" + data) f.appendChild(txt) - xmlutils.add_text_elements_from_list(self, s, svc._startup, "command", - (("type", "start"),)) - xmlutils.add_text_elements_from_list(self, s, svc._shutdown, "command", - (("type", "stop"),)) - xmlutils.add_text_elements_from_list(self, s, svc._validate, "command", - (("type", "validate"),)) + xmlutils.add_text_elements_from_list(self, s, svc.startup, "command", (("type", "start"),)) + xmlutils.add_text_elements_from_list(self, s, svc.shutdown, "command", (("type", "stop"),)) + xmlutils.add_text_elements_from_list(self, s, svc.validate, "command", (("type", "validate"),)) class ChannelElement(NamedXmlElement): diff --git a/daemon/examples/netns/howmanynodes.py b/daemon/examples/netns/howmanynodes.py index 1a20e013..5ca989f6 100755 --- a/daemon/examples/netns/howmanynodes.py +++ b/daemon/examples/netns/howmanynodes.py @@ -159,7 +159,7 @@ def main(): n.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) n.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) if options.services is not None: - session.services.addservicestonode(n, "", options.services) + session.services.addservicestonode(n, "", options.services.split("|")) n.boot() nodelist.append(n) if i % 25 == 0: diff --git a/daemon/examples/netns/wlanemanetests.py b/daemon/examples/netns/wlanemanetests.py index ba6f2aa2..d3323f13 100755 --- a/daemon/examples/netns/wlanemanetests.py +++ b/daemon/examples/netns/wlanemanetests.py @@ -429,8 +429,7 @@ class Experiment(object): self.net.link(prev.netif(0), tmp.netif(0)) prev = tmp - def createemanesession(self, numnodes, verbose=False, cls=None, - values=None): + def createemanesession(self, numnodes, verbose=False, cls=None, values=None): """ Build a topology consisting of the given number of LxcNodes connected to an EMANE WLAN. """ diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 6923807a..d087e2e6 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -7,7 +7,6 @@ import os import pytest from mock.mock import MagicMock -from core import services from core.api.coreapi import CoreConfMessage from core.api.coreapi import CoreEventMessage from core.api.coreapi import CoreExecMessage @@ -29,6 +28,7 @@ from core.enumerations import NodeTlvs from core.enumerations import NodeTypes from core.misc import ipaddress from core.misc.ipaddress import MacAddress +from core.service import ServiceManager EMANE_SERVICES = "zebra|OSPFv3MDR|IPForward" @@ -199,6 +199,7 @@ class CoreServerTest(object): self.request_handler.handle_message(message) def shutdown(self): + self.server.coreemu.shutdown() self.server.shutdown() self.server.server_close() @@ -223,6 +224,9 @@ def session(): # shutdown coreemu coreemu.shutdown() + # clear services, since they will be reloaded + ServiceManager.services.clear() + @pytest.fixture(scope="module") def ip_prefixes(): @@ -231,9 +235,6 @@ def ip_prefixes(): @pytest.fixture() def cored(): - # load default services - services.load() - # create and return server server = CoreServerTest() yield server @@ -241,6 +242,11 @@ def cored(): # cleanup server.shutdown() + # + + # cleanup services + ServiceManager.services.clear() + def ping(from_node, to_node, ip_prefixes, count=3): address = ip_prefixes.ip4_address(to_node) diff --git a/daemon/tests/myservices/sample.py b/daemon/tests/myservices/sample.py index c986cc4a..80545d29 100644 --- a/daemon/tests/myservices/sample.py +++ b/daemon/tests/myservices/sample.py @@ -6,22 +6,22 @@ from core.service import CoreService class MyService(CoreService): - _name = "MyService" - _group = "Utility" - _depends = () - _dirs = () - _configs = ('myservice.sh',) - _startindex = 50 - _startup = ('sh myservice.sh',) - _shutdown = () + name = "MyService" + group = "Utility" + depends = () + dirs = () + configs = ('myservice.sh',) + startindex = 50 + startup = ('sh myservice.sh',) + shutdown = () class MyService2(CoreService): - _name = "MyService2" - _group = "Utility" - _depends = () - _dirs = () - _configs = ('myservice.sh',) - _startindex = 50 - _startup = ('sh myservice.sh',) - _shutdown = () + name = "MyService2" + group = "Utility" + depends = () + dirs = () + configs = ('myservice.sh',) + startindex = 50 + startup = ('sh myservice.sh',) + shutdown = () diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 6b657304..453c7717 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -15,10 +15,8 @@ from core.enumerations import NodeTypes from core.mobility import BasicRangeModel from core.mobility import Ns2ScriptedMobility from core.netns.vnodeclient import VnodeClient -from core.service import ServiceManager _PATH = os.path.abspath(os.path.dirname(__file__)) -_SERVICES_PATH = os.path.join(_PATH, "myservices") _MOBILITY_FILE = os.path.join(_PATH, "mobility.scen") _WIRED = [ NodeTypes.PEER_TO_PEER, @@ -51,16 +49,6 @@ def ping(from_node, to_node, ip_prefixes): class TestCore: - def test_import_service(self): - """ - Test importing a custom service. - - :param conftest.Core core: core fixture to test with - """ - ServiceManager.add_services(_SERVICES_PATH) - assert ServiceManager.get("MyService") - assert ServiceManager.get("MyService2") - @pytest.mark.parametrize("net_type", _WIRED) def test_wired_ping(self, session, net_type, ip_prefixes): """ diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index bf6d3b01..d0fd91fc 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -101,7 +101,7 @@ def run_cmd(node, exec_cmd): class TestGui: - def test_broker(self, session, cored): + def test_broker(self, cored): """ Test session broker creation. @@ -119,6 +119,7 @@ class TestGui: daemon = "localhost" # add server + session = cored.server.coreemu.create_session() session.broker.addserver(daemon, "127.0.0.1", CORE_API_PORT) # setup server diff --git a/daemon/tests/test_services.py b/daemon/tests/test_services.py new file mode 100644 index 00000000..7b60ff1b --- /dev/null +++ b/daemon/tests/test_services.py @@ -0,0 +1,18 @@ +import os + +from core.service import ServiceManager + +_PATH = os.path.abspath(os.path.dirname(__file__)) +_SERVICES_PATH = os.path.join(_PATH, "myservices") + + +class TestServices: + def test_import_service(self): + """ + Test importing a custom service. + + :param conftest.Core core: core fixture to test with + """ + ServiceManager.add_services(_SERVICES_PATH) + assert ServiceManager.get("MyService") + assert ServiceManager.get("MyService2") diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 4d0cfa7f..f3fe8660 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -6,6 +6,7 @@ from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emulator.emudata import NodeOptions from core.enumerations import NodeTypes from core.mobility import BasicRangeModel +from core.services.utility import SshService _XML_VERSIONS = [ "0.0", @@ -68,6 +69,75 @@ class TestXml: assert session.get_object(n1_id) assert session.get_object(n2_id) + @pytest.mark.parametrize("version", _XML_VERSIONS) + def test_xml_ptp_services(self, session, tmpdir, version, ip_prefixes): + """ + Test xml client methods for a ptp neetwork. + + :param session: session for test + :param tmpdir: tmpdir to create data in + :param str version: xml version to write and parse + :param ip_prefixes: generates ip addresses for nodes + """ + # create ptp + ptp_node = session.add_node(_type=NodeTypes.PEER_TO_PEER) + + # create nodes + node_options = NodeOptions(model="host") + node_one = session.add_node(node_options=node_options) + node_two = session.add_node() + + # link nodes to ptp net + for node in [node_one, node_two]: + interface = ip_prefixes.create_interface(node) + session.add_link(node.objid, ptp_node.objid, interface_one=interface) + + # set custom values for node service\ + custom_start = 50 + session.services.setcustomservice(node_one.objid, SshService.name) + service = session.services.getcustomservice(node_one.objid, SshService.name) + service.startindex = custom_start + service_file = SshService.configs[0] + file_data = "# test" + session.services.setservicefile(node_one.objid, SshService.name, service_file, file_data) + + # instantiate session + session.instantiate() + + # get ids for nodes + n1_id = node_one.objid + n2_id = node_two.objid + + # save xml + xml_file = tmpdir.join("session.xml") + file_path = xml_file.strpath + session.save_xml(file_path, version) + + # verify xml file was created and can be parsed + assert xml_file.isfile() + assert ElementTree.parse(file_path) + + # stop current session, clearing data + session.shutdown() + + # verify nodes have been removed from session + with pytest.raises(KeyError): + assert not session.get_object(n1_id) + with pytest.raises(KeyError): + assert not session.get_object(n2_id) + + # load saved xml + session.open_xml(file_path, start=True) + + # retrieve custom service + service = session.services.getcustomservice(node_one.objid, SshService.name) + + # verify nodes have been recreated + assert session.get_object(n1_id) + assert session.get_object(n2_id) + assert service.startindex == custom_start + assert service.configtxt.get(service_file) == file_data + @pytest.mark.parametrize("version", _XML_VERSIONS) def test_xml_mobility(self, session, tmpdir, version, ip_prefixes): """ diff --git a/ns3/examples/ns3wifirandomwalk.py b/ns3/examples/ns3wifirandomwalk.py index 18f473ad..cf5acee1 100644 --- a/ns3/examples/ns3wifirandomwalk.py +++ b/ns3/examples/ns3wifirandomwalk.py @@ -16,13 +16,13 @@ import sys import ns.core import ns.network +from corens3.obj import Ns3Session +from corens3.obj import Ns3WifiNet from core import logger from core.misc import ipaddress from core.misc import nodemaps from core.misc import nodeutils -from corens3.obj import Ns3Session -from corens3.obj import Ns3WifiNet def add_to_server(session): @@ -60,7 +60,7 @@ def wifisession(opt): node = session.addnode(name="n%d" % i) node.newnetif(wifi, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) nodes.append(node) - session.services.addservicestonode(node, "router", services_str) + session.services.addservicestonode(node, "router", services_str.split("|")) session.services.bootnodeservices(node) session.setuprandomwalkmobility(bounds=(1000.0, 750.0, 0))