From 7dedaa0344a7c29523d3e5add541f7a6275b4043 Mon Sep 17 00:00:00 2001 From: Andreas Martens Date: Mon, 30 Jul 2018 14:42:02 +0100 Subject: [PATCH 0001/1992] add comments to the OVS service --- daemon/core/services/sdn.py | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index 9d8a26ba..44a40f48 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -51,10 +51,15 @@ class OvsService(SdnService): cfg = "#!/bin/sh\n" cfg += "# auto-generated by OvsService (OvsService.py)\n" - cfg += "/etc/init.d/openvswitch-switch start < /dev/null\n" - cfg += "ovs-vsctl add-br ovsbr0\n" - cfg += "ifconfig ovsbr0 up\n" + cfg += "## First make sure that the ovs services are up and running\n" + cfg += "/etc/init.d/openvswitch-switch start < /dev/null\n\n" + cfg += "## create the switch itself, set the fail mode to secure, \n" + cfg += "## this stops it from routing traffic without defined flows.\n" + cfg += "## remove the -- and everything after if you want it to act as a regular switch\n" + cfg += "ovs-vsctl add-br ovsbr0 -- set Bridge ovsbr0 fail-mode=secure\n" + cfg += "\n## Now add all our interfaces as ports to the switch\n" + portnum = 1 for ifc in node.netifs(): if hasattr(ifc, 'control') and ifc.control is True: continue @@ -62,9 +67,10 @@ class OvsService(SdnService): ifnum = ifnumstr[0] # create virtual interfaces + cfg += "## Create a veth pair to send the data to\n" cfg += "ip link add rtr%s type veth peer name sw%s\n" % (ifnum, ifnum) - cfg += "ifconfig rtr%s up\n" % ifnum - cfg += "ifconfig sw%s up\n" % ifnum +# cfg += "ifconfig rtr%s up\n" % ifnum +# cfg += "ifconfig sw%s up\n" % ifnum # remove ip address of eths because quagga/zebra will assign same IPs to rtr interfaces # or assign them manually to rtr interfaces if zebra is not running @@ -81,17 +87,31 @@ class OvsService(SdnService): raise ValueError("invalid address: %s" % ifcaddr) # add interfaces to bridge - cfg += "ovs-vsctl add-port ovsbr0 eth%s\n" % ifnum - cfg += "ovs-vsctl add-port ovsbr0 sw%s\n" % ifnum + # Make port numbers explicit so they're easier to follow in reading the script + cfg += "## Add the CORE interface to the switch\n" + cfg += "ovs-vsctl add-port ovsbr0 eth%s -- set Interface eth%s ofport_request=%d\n" % (ifnum, ifnum, portnum) + cfg += "## And then add its sibling veth interface\n" + cfg += "ovs-vsctl add-port ovsbr0 sw%s -- set Interface sw%s ofport_request=%d\n" % (ifnum, ifnum, portnum+1) + cfg += "## start them up so we can send/receive data\n" + cfg += "ovs-ofctl mod-port ovsbr0 eth%s up\n" % ifnum + cfg += "ovs-ofctl mod-port ovsbr0 sw%s up\n" % ifnum + cfg += "## Bring up the lower part of the veth pair\n" + cfg += "ip link set dev rtr%s up\n" % ifnum + portnum += 2 # Add rule for default controller if there is one local (even if the controller is not local, it finds it) + cfg += "\n## We assume there will be an SDN controller on the other end of this, \n" + cfg += "## but it will still function if there's not\n" cfg += "ovs-vsctl set-controller ovsbr0 tcp:127.0.0.1:6633\n" + cfg += "\n## Now to create some default flows, \n" + cfg += "## if the above controller will be present then you probably want to delete them\n" # Setup default flows portnum = 1 for ifc in node.netifs(): if hasattr(ifc, 'control') and ifc.control is True: continue + cfg += "## Take the data from the CORE interface and put it on the veth and vice versa\n" cfg += "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" % (portnum, portnum + 1) cfg += "ovs-ofctl add-flow ovsbr0 priority=1000,in_port=%d,action=output:%d\n" % (portnum + 1, portnum) portnum += 2 From 939921812358ab4502b17df847494977ce30c6ae Mon Sep 17 00:00:00 2001 From: Jeff Ahrenholz Date: Wed, 10 Oct 2018 09:58:18 -0700 Subject: [PATCH 0002/1992] enable OSPFv2 fast convergence, and fix router-id for IPv6-only nodes --- daemon/core/services/quagga.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index de25ecda..a7b85ed8 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -242,7 +242,7 @@ class QuaggaService(CoreService): if a.find(".") >= 0: return a.split('/')[0] # raise ValueError, "no IPv4 address found for router ID" - return "0.0.0.0" + return "0.0.0.%d" % node.objid @staticmethod def rj45check(ifc): @@ -329,22 +329,18 @@ class Ospfv2(QuaggaService): return cfg @classmethod - def generatequaggaifcconfig(cls, node, ifc): - return cls.mtucheck(ifc) - # cfg = cls.mtucheck(ifc) + def generatequaggaifcconfig(cls, node, ifc): + cfg = cls.mtucheck(ifc) # external RJ45 connections will use default OSPF timers - # if cls.rj45check(ifc): - # return cfg - # cfg += cls.ptpcheck(ifc) - - # return cfg + """\ - - -# ip ospf hello-interval 2 -# ip ospf dead-interval 6 -# ip ospf retransmit-interval 5 -# """ + if cls.rj45check(ifc): + return cfg + cfg += cls.ptpcheck(ifc) + return cfg + """\ + ip ospf hello-interval 2 + ip ospf dead-interval 6 + ip ospf retransmit-interval 5 +""" class Ospfv3(QuaggaService): """ From 95177407046ab3b372540f886581d240bf9434a9 Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 26 Apr 2019 22:07:51 -0700 Subject: [PATCH 0003/1992] initial refactor for all variables named objid --- daemon/core/broker.py | 7 +- daemon/core/conf.py | 10 +-- daemon/core/corehandlers.py | 10 +-- daemon/core/coreobj.py | 46 ++++++------ daemon/core/emane/emanemanager.py | 22 +++--- daemon/core/emane/nodes.py | 18 ++--- daemon/core/emulator/coreemu.py | 6 +- daemon/core/emulator/emudata.py | 8 +-- daemon/core/grpc/server.py | 10 +-- daemon/core/misc/utils.py | 2 +- daemon/core/mobility.py | 36 +++++----- daemon/core/netns/nodes.py | 48 ++++++------- daemon/core/netns/openvswitch.py | 58 +++++++-------- daemon/core/netns/vif.py | 12 ++-- daemon/core/netns/vnet.py | 30 ++++---- daemon/core/netns/vnode.py | 20 +++--- daemon/core/phys/pnodes.py | 6 +- daemon/core/sdt.py | 10 +-- daemon/core/service.py | 6 +- daemon/core/services/emaneservices.py | 6 +- daemon/core/services/frr.py | 2 +- daemon/core/services/quagga.py | 2 +- daemon/core/session.py | 32 ++++----- daemon/core/xml/corexml.py | 4 +- daemon/core/xml/emanexml.py | 10 +-- daemon/examples/api/emane80211.py | 2 +- daemon/examples/api/switch.py | 2 +- daemon/examples/api/switch_inject.py | 2 +- daemon/examples/api/wlan.py | 2 +- daemon/examples/netns/daemonnodes.py | 6 +- daemon/examples/netns/distributed.py | 6 +- daemon/examples/netns/ospfmanetmdrtest.py | 6 +- daemon/examples/netns/wlanemanetests.py | 9 ++- daemon/tests/conftest.py | 8 +-- daemon/tests/distributed/test_distributed.py | 16 ++--- daemon/tests/test_conf.py | 2 +- daemon/tests/test_core.py | 10 +-- daemon/tests/test_emane.py | 4 +- daemon/tests/test_grpc.py | 74 ++++++++++---------- daemon/tests/test_gui.py | 8 +-- daemon/tests/test_links.py | 26 +++---- daemon/tests/test_nodes.py | 6 +- daemon/tests/test_services.py | 28 ++++---- daemon/tests/test_xml.py | 34 ++++----- docs/scripting.md | 10 +-- ns3/corens3/obj.py | 17 +++-- 46 files changed, 346 insertions(+), 353 deletions(-) diff --git a/daemon/core/broker.py b/daemon/core/broker.py index 19d9713b..c20a4a41 100644 --- a/daemon/core/broker.py +++ b/daemon/core/broker.py @@ -413,15 +413,14 @@ class CoreBroker(object): if key in self.tunnels.keys(): logging.warn("tunnel with key %s (%s-%s) already exists!", key, n1num, n2num) else: - objid = key & ((1 << 16) - 1) + _id = key & ((1 << 16) - 1) logging.info("adding tunnel for %s-%s to %s with key %s", n1num, n2num, remoteip, key) if localnum in self.physical_nodes: # no bridge is needed on physical nodes; use the GreTap directly gt = GreTap(node=None, name=None, session=self.session, remoteip=remoteip, key=key) else: - gt = self.session.add_object(cls=GreTapBridge, objid=objid, - policy="ACCEPT", remoteip=remoteip, key=key) + gt = self.session.add_object(cls=GreTapBridge, _id=_id, policy="ACCEPT", remoteip=remoteip, key=key) gt.localnum = localnum gt.remotenum = remotenum self.tunnels[key] = gt @@ -518,7 +517,7 @@ class CoreBroker(object): except KeyError: gt = None if gt: - self.session.delete_object(gt.objid) + self.session.delete_object(gt.id) del gt def gettunnel(self, n1num, n2num): diff --git a/daemon/core/conf.py b/daemon/core/conf.py index b2696b7a..b4e9c65f 100644 --- a/daemon/core/conf.py +++ b/daemon/core/conf.py @@ -369,9 +369,9 @@ class ModelManager(ConfigurableManager): :param dict config: model configuration, None for default configuration :return: nothing """ - logging.info("setting mobility model(%s) for node(%s): %s", model_class.name, node.objid, config) - self.set_model_config(node.objid, model_class.name, config) - config = self.get_model_config(node.objid, model_class.name) + logging.info("setting mobility model(%s) for node(%s): %s", model_class.name, node.id, config) + self.set_model_config(node.id, model_class.name, config) + config = self.get_model_config(node.id, model_class.name) node.setmodel(model_class, config) def get_models(self, node): @@ -383,7 +383,7 @@ class ModelManager(ConfigurableManager): :return: list of model and values tuples for the network node :rtype: list """ - all_configs = self.get_all_configs(node.objid) + all_configs = self.get_all_configs(node.id) if not all_configs: all_configs = {} @@ -394,5 +394,5 @@ class ModelManager(ConfigurableManager): model_class = self.models[model_name] models.append((model_class, config)) - logging.debug("models for node(%s): %s", node.objid, models) + logging.debug("models for node(%s): %s", node.id, models) return models diff --git a/daemon/core/corehandlers.py b/daemon/core/corehandlers.py index 7c4a377c..5eaaa4cd 100644 --- a/daemon/core/corehandlers.py +++ b/daemon/core/corehandlers.py @@ -670,10 +670,10 @@ class CoreHandler(SocketServer.BaseRequestHandler): node = self.session.add_node(node_type, node_id, node_options) if node: if message.flags & MessageFlags.STRING.value: - self.node_status_request[node.objid] = True + self.node_status_request[node.id] = True if self.session.state == EventTypes.RUNTIME_STATE.value: - self.send_node_emulation_id(node.objid) + self.send_node_emulation_id(node.id) elif message.flags & MessageFlags.DELETE.value: with self._shutdown_lock: result = self.session.delete_node(node_id) @@ -1424,7 +1424,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): # configure mobility models for WLAN added during runtime if event_type == EventTypes.INSTANTIATION_STATE and nodeutils.is_node(node, NodeTypes.WIRELESS_LAN): - self.session.start_mobility(node_ids=(node.objid,)) + self.session.start_mobility(node_ids=(node.id,)) return () logging.warn("dropping unhandled Event message with node number") @@ -1442,8 +1442,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): self.session.instantiate() # after booting nodes attempt to send emulation id for nodes waiting on status - for obj in self.session.objects.itervalues(): - self.send_node_emulation_id(obj.objid) + for _id in self.session.objects: + self.send_node_emulation_id(_id) elif event_type == EventTypes.RUNTIME_STATE: if self.session.master: logging.warn("Unexpected event message: RUNTIME state received at session master") diff --git a/daemon/core/coreobj.py b/daemon/core/coreobj.py index daf5d9da..a4ab9467 100644 --- a/daemon/core/coreobj.py +++ b/daemon/core/coreobj.py @@ -67,23 +67,23 @@ class PyCoreObj(object): apitype = None # TODO: appears start has no usage, verify and remove - def __init__(self, session, objid=None, name=None, start=True): + def __init__(self, session, _id=None, name=None, start=True): """ Creates a PyCoreObj instance. :param core.session.Session session: CORE session object - :param int objid: object id + :param int _id: id :param str name: object name :param bool start: start value :return: """ self.session = session - if objid is None: - objid = session.get_object_id() - self.objid = objid + if _id is None: + _id = session.get_node_id() + self.id = _id if name is None: - name = "o%s" % self.objid + name = "o%s" % self.id self.name = name self.type = None self.server = None @@ -217,10 +217,10 @@ class PyCoreObj(object): node_data = NodeData( message_type=message_type, - id=self.objid, + id=self.id, node_type=self.apitype, name=self.name, - emulation_id=self.objid, + emulation_id=self.id, canvas=self.canvas, icon=self.icon, opaque=self.opaque, @@ -254,16 +254,16 @@ class PyCoreNode(PyCoreObj): Base class for CORE nodes. """ - def __init__(self, session, objid=None, name=None, start=True): + def __init__(self, session, _id=None, name=None, start=True): """ Create a PyCoreNode instance. :param core.session.Session session: CORE session object - :param int objid: object id + :param int _id: object id :param str name: object name :param bool start: boolean for starting """ - super(PyCoreNode, self).__init__(session, objid, name, start=start) + super(PyCoreNode, self).__init__(session, _id, name, start=start) self.services = [] self.nodedir = None self.tmpnodedir = False @@ -452,16 +452,16 @@ class PyCoreNet(PyCoreObj): """ linktype = LinkTypes.WIRED.value - def __init__(self, session, objid, name, start=True): + def __init__(self, session, _id, name, start=True): """ Create a PyCoreNet instance. :param core.session.Session session: CORE session object - :param int objid: object id + :param int _id: object id :param str name: object name :param bool start: should object start """ - super(PyCoreNet, self).__init__(session, objid, name, start=start) + super(PyCoreNet, self).__init__(session, _id, name, start=start) self._linked = {} self._linked_lock = threading.Lock() @@ -518,14 +518,14 @@ class PyCoreNet(PyCoreObj): for netif in self.netifs(sort=True): if not hasattr(netif, "node"): continue - otherobj = netif.node + linked_node = netif.node uni = False - if otherobj is None: + if linked_node is None: # two layer-2 switches/hubs linked together via linknet() if not hasattr(netif, "othernet"): continue - otherobj = netif.othernet - if otherobj.objid == self.objid: + linked_node = netif.othernet + if linked_node.id == self.id: continue netif.swapparams('_params_up') upstream_params = netif.getparams() @@ -557,11 +557,11 @@ class PyCoreNet(PyCoreObj): link_data = LinkData( message_type=flags, - node1_id=self.objid, - node2_id=otherobj.objid, + node1_id=self.id, + node2_id=linked_node.id, link_type=self.linktype, unidirectional=unidirectional, - interface2_id=otherobj.getifindex(netif), + interface2_id=linked_node.getifindex(netif), interface2_mac=netif.hwaddr, interface2_ip4=interface2_ip4, interface2_ip4_mask=interface2_ip4_mask, @@ -582,8 +582,8 @@ class PyCoreNet(PyCoreObj): netif.swapparams('_params_up') link_data = LinkData( message_type=0, - node1_id=otherobj.objid, - node2_id=self.objid, + node1_id=linked_node.id, + node2_id=self.id, unidirectional=1, delay=netif.getparam("delay"), bandwidth=netif.getparam("bw"), diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index a7c9b121..44e23831 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -109,14 +109,14 @@ class EmaneManager(ModelManager): return self.get_configs(node_id=node_id, config_type=model_name) else: # don"t use default values when interface config is the same as net - # note here that using ifc.node.objid as key allows for only one type + # note here that using ifc.node.id as key allows for only one type # of each model per node; # TODO: use both node and interface as key # Adamson change: first check for iface config keyed by "node:ifc.name" # (so that nodes w/ multiple interfaces of same conftype can have # different configs for each separate interface) - key = 1000 * interface.node.objid + key = 1000 * interface.node.id if interface.netindex is not None: key += interface.netindex @@ -125,7 +125,7 @@ class EmaneManager(ModelManager): # otherwise retrieve the interfaces node configuration, avoid using defaults if not config: - config = self.get_configs(node_id=interface.node.objid, config_type=model_name) + config = self.get_configs(node_id=interface.node.id, config_type=model_name) # get non interface config, when none found if not config: @@ -225,9 +225,9 @@ class EmaneManager(ModelManager): :return: nothing """ with self._emane_node_lock: - if emane_node.objid in self._emane_nodes: - raise KeyError("non-unique EMANE object id %s for %s" % (emane_node.objid, emane_node)) - self._emane_nodes[emane_node.objid] = emane_node + if emane_node.id in self._emane_nodes: + raise KeyError("non-unique EMANE object id %s for %s" % (emane_node.id, emane_node)) + self._emane_nodes[emane_node.id] = emane_node def getnodes(self): """ @@ -254,7 +254,7 @@ class EmaneManager(ModelManager): with self.session._objects_lock: for node in self.session.objects.itervalues(): if nodeutils.is_node(node, NodeTypes.EMANE): - logging.debug("adding emane node: id(%s) name(%s)", node.objid, node.name) + logging.debug("adding emane node: id(%s) name(%s)", node.id, node.name) self.add_node(node) if not self._emane_nodes: @@ -345,7 +345,7 @@ class EmaneManager(ModelManager): with self._emane_node_lock: for key in sorted(self._emane_nodes.keys()): emane_node = self._emane_nodes[key] - logging.debug("post startup for emane node: %s - %s", emane_node.objid, emane_node.name) + logging.debug("post startup for emane node: %s - %s", emane_node.id, emane_node.name) emane_node.model.post_startup() for netif in emane_node.netifs(): x, y, z = netif.node.position.get() @@ -517,7 +517,7 @@ class EmaneManager(ModelManager): # skip nodes that already have a model set if emane_node.model: - logging.debug("node(%s) already has model(%s)", emane_node.objid, emane_node.model.name) + logging.debug("node(%s) already has model(%s)", emane_node.id, emane_node.model.name) continue # set model configured for node, due to legacy messaging configuration before nodes exist @@ -644,7 +644,7 @@ class EmaneManager(ModelManager): run_emane_on_host = True continue path = self.session.session_dir - n = node.objid + n = node.id # control network not yet started here self.session.add_remove_control_interface(node, 0, remove=False, conf_required=False) @@ -828,7 +828,7 @@ class EmaneManager(ModelManager): logging.info("location event for unknown NEM %s", nemid) return False - n = netif.node.objid + n = netif.node.id # convert from lat/long/alt to x,y,z coordinates x, y, z = self.session.location.getxyz(lat, lon, alt) x = int(x) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index b599d177..016dc031 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -37,8 +37,8 @@ class EmaneNode(EmaneNet): Emane controller object that exists in a session. """ - def __init__(self, session, objid=None, name=None, start=True): - super(EmaneNode, self).__init__(session, objid, name, start) + def __init__(self, session, _id=None, name=None, start=True): + super(EmaneNode, self).__init__(session, _id, name, start) self.conf = "" self.up = False self.nemidmap = {} @@ -68,9 +68,9 @@ class EmaneNode(EmaneNet): def updatemodel(self, config): if not self.model: - raise ValueError("no model set to update for node(%s)", self.objid) - logging.info("node(%s) updating model(%s): %s", self.objid, self.model.name, config) - self.model.set_configs(config, node_id=self.objid) + raise ValueError("no model set to update for node(%s)", self.id) + logging.info("node(%s) updating model(%s): %s", self.id, self.model.name, config) + self.model.set_configs(config, node_id=self.id) def setmodel(self, model, config): """ @@ -80,10 +80,10 @@ class EmaneNode(EmaneNet): if model.config_type == RegisterTlvs.WIRELESS.value: # EmaneModel really uses values from ConfigurableManager # when buildnemxml() is called, not during init() - self.model = model(session=self.session, object_id=self.objid) + self.model = model(session=self.session, object_id=self.id) self.model.update_config(config) elif model.config_type == RegisterTlvs.MOBILITY.value: - self.mobility = model(session=self.session, object_id=self.objid) + self.mobility = model(session=self.session, object_id=self.id) self.mobility.update_config(config) def setnemid(self, netif, nemid): @@ -116,7 +116,7 @@ class EmaneNode(EmaneNet): """ Retrieve list of linked interfaces sorted by node number. """ - return sorted(self._netif.values(), key=lambda ifc: ifc.node.objid) + return sorted(self._netif.values(), key=lambda ifc: ifc.node.id) def installnetifs(self): """ @@ -130,7 +130,7 @@ class EmaneNode(EmaneNet): logging.error(warntxt) for netif in self.netifs(): - external = self.session.emane.get_config("external", self.objid, self.model.name) + external = self.session.emane.get_config("external", self.id, self.model.name) if external == "0": netif.setaddrs() diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 9f7e128a..a95a347d 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -366,7 +366,7 @@ class EmuSession(Session): interface_one.detachnet() interface_two.detachnet() if net_one.numnetif() == 0: - self.delete_object(net_one.objid) + self.delete_object(net_one.id) node_one.delnetif(interface_one.netindex) node_two.delnetif(interface_two.netindex) finally: @@ -493,7 +493,7 @@ class EmuSession(Session): # create node logging.info("creating node(%s) id(%s) name(%s) start(%s)", node_class.__name__, _id, name, start) - node = self.add_object(cls=node_class, objid=_id, name=name, start=start) + node = self.add_object(cls=node_class, _id=_id, name=name, start=start) # set node attributes node.icon = node_options.icon @@ -599,7 +599,7 @@ class EmuSession(Session): """ node_data = NodeData( message_type=0, - id=node.objid, + id=node.id, x_position=node.position.x, y_position=node.position.y ) diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 2d70d367..437220da 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -117,7 +117,7 @@ class IpPrefixes(object): """ if not self.ip4: raise ValueError("ip4 prefixes have not been set") - return str(self.ip4.addr(node.objid)) + return str(self.ip4.addr(node.id)) def ip6_address(self, node): """ @@ -129,7 +129,7 @@ class IpPrefixes(object): """ if not self.ip6: raise ValueError("ip6 prefixes have not been set") - return str(self.ip6.addr(node.objid)) + return str(self.ip6.addr(node.id)) def create_interface(self, node, name=None, mac=None): """ @@ -149,14 +149,14 @@ class IpPrefixes(object): ip4 = None ip4_mask = None if self.ip4: - ip4 = str(self.ip4.addr(node.objid)) + ip4 = str(self.ip4.addr(node.id)) ip4_mask = self.ip4.prefixlen # generate ip6 data ip6 = None ip6_mask = None if self.ip6: - ip6 = str(self.ip6.addr(node.objid)) + ip6 = str(self.ip6.addr(node.id)) ip6_mask = self.ip6.prefixlen # random mac diff --git a/daemon/core/grpc/server.py b/daemon/core/grpc/server.py index 45c35fc8..bc18594a 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/grpc/server.py @@ -235,7 +235,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): nodes = [] for node_id in session.objects: node = session.objects[node_id] - if not isinstance(node.objid, int): + if not isinstance(node.id, int): continue node_type = nodeutils.get_node_type(node.__class__).value @@ -252,7 +252,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): emane_model = node.model.name node_proto = core_pb2.Node( - id=node.objid, name=node.name, emane=emane_model, model=model, + id=node.id, name=node.name, emane=emane_model, model=model, type=node_type, position=position, services=services) nodes.append(node_proto) @@ -457,7 +457,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): if emane_model: session.emane.set_model_config(node_id, emane_model) - return core_pb2.AddNodeResponse(id=node.objid) + return core_pb2.AddNodeResponse(id=node.id) def GetNode(self, request, context): logging.debug("get node: %s", request) @@ -468,7 +468,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): for interface_id, interface in node._netif.iteritems(): net_id = None if interface.net: - net_id = interface.net.objid + net_id = interface.net.id interface_proto = core_pb2.Interface( id=interface_id, netid=net_id, name=interface.name, mac=str(interface.hwaddr), mtu=interface.mtu, flowid=interface.flow_id) @@ -482,7 +482,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): position = core_pb2.Position(x=node.position.x, y=node.position.y, z=node.position.z) node_type = nodeutils.get_node_type(node.__class__).value node = core_pb2.Node( - id=node.objid, name=node.name, type=node_type, emane=emane_model, model=node.type, position=position, + id=node.id, name=node.name, type=node_type, emane=emane_model, model=node.type, position=position, services=services) return core_pb2.GetNodeResponse(node=node, interfaces=interfaces) diff --git a/daemon/core/misc/utils.py b/daemon/core/misc/utils.py index f2e578bf..1306badc 100644 --- a/daemon/core/misc/utils.py +++ b/daemon/core/misc/utils.py @@ -324,7 +324,7 @@ def expand_corepath(pathname, session=None, node=None): pathname = pathname.replace("%SESSION_USER%", session.user) if node is not None: - pathname = pathname.replace("%NODE%", str(node.objid)) + pathname = pathname.replace("%NODE%", str(node.id)) pathname = pathname.replace("%NODENAME%", node.name) return pathname diff --git a/daemon/core/mobility.py b/daemon/core/mobility.py index 4a9eb746..3f2cc4e5 100644 --- a/daemon/core/mobility.py +++ b/daemon/core/mobility.py @@ -187,7 +187,7 @@ class MobilityManager(ModelManager): :param core.coreobj.PyCoreNode node: node to add physical network to :return: nothing """ - node_id = node.objid + node_id = node.id self.phys[node_id] = node if netnum not in self.physnets: self.physnets[netnum] = [node_id, ] @@ -213,7 +213,7 @@ class MobilityManager(ModelManager): return if nn[1] in self.session.broker.physical_nodes: # record the fact that this PhysicalNode is linked to a net - dummy = PyCoreNode(session=self.session, objid=nn[1], name="n%d" % nn[1], start=False) + dummy = PyCoreNode(session=self.session, _id=nn[1], name="n%d" % nn[1], start=False) self.addphys(nn[0], dummy) # TODO: remove need to handling old style messages @@ -243,13 +243,13 @@ class MobilityManager(ModelManager): :param net: network to install :return: nothing """ - nodenums = self.physnets.get(net.objid, []) - for nodenum in nodenums: - node = self.phys[nodenum] + node_ids = self.physnets.get(net.id, []) + for node_id in node_ids: + node = self.phys[node_id] # TODO: fix this bad logic, relating to depending on a break to get a valid server - for server in self.session.broker.getserversbynode(nodenum): + for server in self.session.broker.getserversbynode(node_id): break - netif = self.session.broker.gettunnel(net.objid, IpAddress.to_int(server.host)) + netif = self.session.broker.gettunnel(net.id, IpAddress.to_int(server.host)) node.addnetif(netif, 0) netif.node = node x, y, z = netif.node.position.get() @@ -357,7 +357,7 @@ class BasicRangeModel(WirelessModel): :return: nothing """ self.range = float(config["range"]) - logging.info("basic range model configured for WLAN %d using range %d", self.wlan.objid, self.range) + logging.info("basic range model configured for WLAN %d using range %d", self.wlan.id, self.range) self.bw = int(config["bandwidth"]) if self.bw == 0.0: self.bw = None @@ -521,9 +521,9 @@ class BasicRangeModel(WirelessModel): """ return LinkData( message_type=message_type, - node1_id=interface1.node.objid, - node2_id=interface2.node.objid, - network_id=self.wlan.objid, + node1_id=interface1.node.id, + node2_id=interface2.node.id, + network_id=self.wlan.id, link_type=LinkTypes.WIRELESS.value ) @@ -705,15 +705,15 @@ class WayPointMobility(WirelessModel): :return: True if node was moved, False otherwise :rtype: bool """ - if node.objid not in self.points: + if node.id not in self.points: return False x1, y1, z1 = node.getposition() - x2, y2, z2 = self.points[node.objid].coords - speed = self.points[node.objid].speed + x2, y2, z2 = self.points[node.id].coords + speed = self.points[node.id].speed # instantaneous move (prevents dx/dy == 0.0 below) if speed == 0: self.setnodeposition(node, x2, y2, z2) - del self.points[node.objid] + del self.points[node.id] return True # speed can be a velocity vector (ns3 mobility) or speed value if isinstance(speed, (float, int)): @@ -739,7 +739,7 @@ class WayPointMobility(WirelessModel): # the last node to reach the last waypoint determines this # script's endtime self.endtime = self.lasttime - self.timezero - del self.points[node.objid] + del self.points[node.id] return False if (x1 + dx) < 0.0: dx = 0.0 - x1 @@ -758,9 +758,9 @@ class WayPointMobility(WirelessModel): moved_netifs = [] for netif in self.wlan.netifs(): node = netif.node - if node.objid not in self.initial: + if node.id not in self.initial: continue - x, y, z = self.initial[node.objid].coords + x, y, z = self.initial[node.id].coords self.setnodeposition(node, x, y, z) moved.append(node) moved_netifs.append(netif) diff --git a/daemon/core/netns/nodes.py b/daemon/core/netns/nodes.py index 4f9b4a51..df7b18b4 100644 --- a/daemon/core/netns/nodes.py +++ b/daemon/core/netns/nodes.py @@ -39,14 +39,14 @@ class CtrlNet(LxBrNet): "172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24" ] - def __init__(self, session, objid="ctrlnet", name=None, prefix=None, + def __init__(self, session, _id="ctrlnet", name=None, prefix=None, hostid=None, start=True, assign_address=True, updown_script=None, serverintf=None): """ Creates a CtrlNet instance. :param core.session.Session session: core session instance - :param int objid: node id + :param int _id: node id :param str name: node namee :param prefix: control network ipv4 prefix :param hostid: host id @@ -61,7 +61,7 @@ class CtrlNet(LxBrNet): self.assign_address = assign_address self.updown_script = updown_script self.serverintf = serverintf - LxBrNet.__init__(self, session, objid=objid, name=name, start=start) + LxBrNet.__init__(self, session, _id=_id, name=name, start=start) def startup(self): """ @@ -116,7 +116,7 @@ class CtrlNet(LxBrNet): oldbr = cols[0] flds = cols[0].split(".") if len(flds) == 3: - if flds[0] == "b" and flds[1] == self.objid: + if flds[0] == "b" and flds[1] == self.id: logging.error( "error: An active control net bridge (%s) found. " "An older session might still be running. " @@ -255,8 +255,8 @@ class PtpNet(LxBrNet): link_data = LinkData( message_type=flags, - node1_id=if1.node.objid, - node2_id=if2.node.objid, + node1_id=if1.node.id, + node2_id=if2.node.id, link_type=self.linktype, unidirectional=unidirectional, delay=if1.getparam("delay"), @@ -284,8 +284,8 @@ class PtpNet(LxBrNet): if unidirectional: link_data = LinkData( message_type=0, - node1_id=if2.node.objid, - node2_id=if1.node.objid, + node1_id=if2.node.id, + node2_id=if1.node.id, delay=if1.getparam("delay"), bandwidth=if1.getparam("bw"), dup=if1.getparam("duplicate"), @@ -317,17 +317,17 @@ class HubNode(LxBrNet): policy = "ACCEPT" type = "hub" - def __init__(self, session, objid=None, name=None, start=True): + def __init__(self, session, _id=None, name=None, start=True): """ Creates a HubNode instance. :param core.session.Session session: core session instance - :param int objid: node id + :param int _id: node id :param str name: node namee :param bool start: start flag :raises CoreCommandError: when there is a command exception """ - LxBrNet.__init__(self, session, objid, name, start) + LxBrNet.__init__(self, session, _id, name, start) # TODO: move to startup method if start: @@ -343,17 +343,17 @@ class WlanNode(LxBrNet): policy = "DROP" type = "wlan" - def __init__(self, session, objid=None, name=None, start=True, policy=None): + def __init__(self, session, _id=None, name=None, start=True, policy=None): """ Create a WlanNode instance. :param core.session.Session session: core session instance - :param int objid: node id + :param int _id: node id :param str name: node name :param bool start: start flag :param policy: wlan policy """ - LxBrNet.__init__(self, session, objid, name, start, policy) + LxBrNet.__init__(self, session, _id, name, start, policy) # wireless model such as basic range self.model = None # mobility model such as scripted @@ -385,7 +385,7 @@ class WlanNode(LxBrNet): """ logging.info("adding model: %s", model.name) if model.config_type == RegisterTlvs.WIRELESS.value: - self.model = model(session=self.session, object_id=self.objid) + self.model = model(session=self.session, object_id=self.id) self.model.update_config(config) if self.model.position_callback: for netif in self.netifs(): @@ -395,19 +395,19 @@ class WlanNode(LxBrNet): netif.poshook(netif, x, y, z) self.model.setlinkparams() elif model.config_type == RegisterTlvs.MOBILITY.value: - self.mobility = model(session=self.session, object_id=self.objid) + self.mobility = model(session=self.session, object_id=self.id) self.mobility.update_config(config) def update_mobility(self, config): if not self.mobility: - raise ValueError("no mobility set to update for node(%s)", self.objid) - self.mobility.set_configs(config, node_id=self.objid) + raise ValueError("no mobility set to update for node(%s)", self.id) + self.mobility.set_configs(config, node_id=self.id) def updatemodel(self, config): if not self.model: - raise ValueError("no model set to update for node(%s)", self.objid) - logging.info("node(%s) updating model(%s): %s", self.objid, self.model.name, config) - self.model.set_configs(config, node_id=self.objid) + raise ValueError("no model set to update for node(%s)", self.id) + logging.info("node(%s) updating model(%s): %s", self.id, self.model.name, config) + self.model.set_configs(config, node_id=self.id) if self.model.position_callback: for netif in self.netifs(): netif.poshook = self.model.position_callback @@ -440,18 +440,18 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): apitype = NodeTypes.RJ45.value type = "rj45" - def __init__(self, session, objid=None, name=None, mtu=1500, start=True): + def __init__(self, session, _id=None, name=None, mtu=1500, start=True): """ Create an RJ45Node instance. :param core.session.Session session: core session instance - :param int objid: node id + :param int _id: node id :param str name: node name :param mtu: rj45 mtu :param bool start: start flag :return: """ - PyCoreNode.__init__(self, session, objid, name, start=start) + PyCoreNode.__init__(self, session, _id, name, start=start) PyCoreNetIf.__init__(self, node=self, name=name, mtu=mtu) self.up = False self.lock = threading.RLock() diff --git a/daemon/core/netns/openvswitch.py b/daemon/core/netns/openvswitch.py index d18471ea..c79c2835 100644 --- a/daemon/core/netns/openvswitch.py +++ b/daemon/core/netns/openvswitch.py @@ -50,19 +50,19 @@ class OvsNet(PyCoreNet): policy = "DROP" - def __init__(self, session, objid=None, name=None, start=True, policy=None): + def __init__(self, session, _id=None, name=None, start=True, policy=None): """ Creates an OvsNet instance. :param core.session.Session session: session this object is a part of - :param objid: + :param _id: :param name: :param start: :param policy: :return: """ - PyCoreNet.__init__(self, session, objid, name, start) + PyCoreNet.__init__(self, session, _id, name, start) if policy: self.policy = policy @@ -70,7 +70,7 @@ class OvsNet(PyCoreNet): self.policy = self.__class__.policy session_id = self.session.short_session_id() - self.bridge_name = "b.%s.%s" % (str(self.objid), session_id) + self.bridge_name = "b.%s.%s" % (str(self.id), session_id) self.up = False if start: @@ -279,21 +279,21 @@ class OvsNet(PyCoreNet): session_id = self.session.short_session_id() try: - self_objid = "%x" % self.objid + _id = "%x" % self.id except TypeError: - self_objid = "%s" % self.objid + _id = "%s" % self.id try: - net_objid = "%x" % network.objid + network_id = "%x" % network.id except TypeError: - net_objid = "%s" % network.objid + network_id = "%s" % network.id - localname = "veth%s.%s.%s" % (self_objid, net_objid, session_id) + localname = "veth%s.%s.%s" % (_id, network_id, session_id) if len(localname) >= 16: raise ValueError("interface local name %s too long" % localname) - name = "veth%s.%s.%s" % (net_objid, self_objid, session_id) + name = "veth%s.%s.%s" % (network_id, _id, session_id) if len(name) >= 16: raise ValueError("interface name %s too long" % name) @@ -349,14 +349,14 @@ class OvsCtrlNet(OvsNet): "172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24" ] - def __init__(self, session, objid="ctrlnet", name=None, prefix=None, hostid=None, + def __init__(self, session, _id="ctrlnet", name=None, prefix=None, hostid=None, start=True, assign_address=True, updown_script=None, serverintf=None): self.prefix = ipaddress.Ipv4Prefix(prefix) self.hostid = hostid self.assign_address = assign_address self.updown_script = updown_script self.serverintf = serverintf - OvsNet.__init__(self, session, objid=objid, name=name, start=start) + OvsNet.__init__(self, session, _id=_id, name=name, start=start) def startup(self): if self.detectoldbridge(): @@ -394,7 +394,7 @@ class OvsCtrlNet(OvsNet): if output: for line in output.split("\n"): bride_name = line.split(".") - if bride_name[0] == "b" and bride_name[1] == self.objid: + if bride_name[0] == "b" and bride_name[1] == self.id: logging.error("older session may still be running with conflicting id for bridge: %s", line) return True @@ -495,8 +495,8 @@ class OvsPtpNet(OvsNet): # loss=netif.getparam("loss") link_data = LinkData( message_type=flags, - node1_id=if1.node.objid, - node2_id=if2.node.objid, + node1_id=if1.node.id, + node2_id=if2.node.id, link_type=self.linktype, unidirectional=unidirectional, delay=if1.getparam("delay"), @@ -524,8 +524,8 @@ class OvsPtpNet(OvsNet): if unidirectional: link_data = LinkData( message_type=0, - node1_id=if2.node.objid, - node2_id=if1.node.objid, + node1_id=if2.node.id, + node2_id=if1.node.id, delay=if1.getparam("delay"), bandwidth=if1.getparam("bw"), dup=if1.getparam("duplicate"), @@ -550,12 +550,12 @@ class OvsHubNode(OvsNet): policy = "ACCEPT" type = "hub" - def __init__(self, session, objid=None, name=None, start=True): + def __init__(self, session, _id=None, name=None, start=True): """ the Hub node forwards packets to all bridge ports by turning off the MAC address learning """ - OvsNet.__init__(self, session, objid, name, start) + OvsNet.__init__(self, session, _id, name, start) if start: # TODO: verify that the below flow accomplishes what is desired for a "HUB" @@ -569,8 +569,8 @@ class OvsWlanNode(OvsNet): policy = "DROP" type = "wlan" - def __init__(self, session, objid=None, name=None, start=True, policy=None): - OvsNet.__init__(self, session, objid, name, start, policy) + def __init__(self, session, _id=None, name=None, start=True, policy=None): + OvsNet.__init__(self, session, _id, name, start, policy) # wireless model such as basic range self.model = None @@ -598,7 +598,7 @@ class OvsWlanNode(OvsNet): logging.info("adding model %s", model.name) if model.type == RegisterTlvs.WIRELESS.value: - self.model = model(session=self.session, object_id=self.objid, config=config) + self.model = model(session=self.session, object_id=self.id, config=config) if self.model.position_callback: for interface in self.netifs(): interface.poshook = self.model.position_callback @@ -607,13 +607,13 @@ class OvsWlanNode(OvsNet): interface.poshook(interface, x, y, z) self.model.setlinkparams() elif model.type == RegisterTlvs.MOBILITY.value: - self.mobility = model(session=self.session, object_id=self.objid, config=config) + self.mobility = model(session=self.session, object_id=self.id, config=config) def updatemodel(self, config): if not self.model: - raise ValueError("no model set to update for node(%s)", self.objid) - logging.info("node(%s) updating model(%s): %s", self.objid, self.model.name, config) - self.model.set_configs(config, node_id=self.objid) + raise ValueError("no model set to update for node(%s)", self.id) + logging.info("node(%s) updating model(%s): %s", self.id, self.model.name, config) + self.model.set_configs(config, node_id=self.id) if self.model.position_callback: for netif in self.netifs(): netif.poshook = self.model.position_callback @@ -643,12 +643,12 @@ class OvsGreTapBridge(OvsNet): another system. """ - def __init__(self, session, remoteip=None, objid=None, name=None, policy="ACCEPT", + def __init__(self, session, remoteip=None, _id=None, name=None, policy="ACCEPT", localip=None, ttl=255, key=None, start=True): - OvsNet.__init__(self, session=session, objid=objid, name=name, policy=policy, start=False) + OvsNet.__init__(self, session=session, _id=_id, name=name, policy=policy, start=False) self.grekey = key if self.grekey is None: - self.grekey = self.session.id ^ self.objid + self.grekey = self.session.id ^ self.id self.localnum = None self.remotenum = None diff --git a/daemon/core/netns/vif.py b/daemon/core/netns/vif.py index 8b224c1e..b7e8f23d 100644 --- a/daemon/core/netns/vif.py +++ b/daemon/core/netns/vif.py @@ -241,7 +241,7 @@ class GreTap(PyCoreNetIf): """ def __init__(self, node=None, name=None, session=None, mtu=1458, - remoteip=None, objid=None, localip=None, ttl=255, + remoteip=None, _id=None, localip=None, ttl=255, key=None, start=True): """ Creates a GreTap instance. @@ -251,7 +251,7 @@ class GreTap(PyCoreNetIf): :param core.session.Session session: core session instance :param mtu: interface mtu :param str remoteip: remote address - :param int objid: object id + :param int _id: object id :param str localip: local address :param ttl: ttl value :param key: gre tap key @@ -260,13 +260,13 @@ class GreTap(PyCoreNetIf): """ PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu) self.session = session - if objid is None: + if _id is None: # from PyCoreObj - objid = ((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff - self.objid = objid + _id = ((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff + self.id = _id sessionid = self.session.short_session_id() # interface name on the local host machine - self.localname = "gt.%s.%s" % (self.objid, sessionid) + self.localname = "gt.%s.%s" % (self.id, sessionid) self.transport_type = "raw" if not start: self.up = False diff --git a/daemon/core/netns/vnet.py b/daemon/core/netns/vnet.py index 17bcada0..a7be41b5 100644 --- a/daemon/core/netns/vnet.py +++ b/daemon/core/netns/vnet.py @@ -242,24 +242,24 @@ class LxBrNet(PyCoreNet): """ policy = "DROP" - def __init__(self, session, objid=None, name=None, start=True, policy=None): + def __init__(self, session, _id=None, name=None, start=True, policy=None): """ Creates a LxBrNet instance. :param core.session.Session session: core session instance - :param int objid: object id + :param int _id: object id :param str name: object name :param bool start: start flag :param policy: network policy """ - PyCoreNet.__init__(self, session, objid, name, start) + PyCoreNet.__init__(self, session, _id, name, start) if name is None: - name = str(self.objid) + name = str(self.id) if policy is not None: self.policy = policy self.name = name sessionid = self.session.short_session_id() - self.brname = "b.%s.%s" % (str(self.objid), sessionid) + self.brname = "b.%s.%s" % (str(self.id), sessionid) self.up = False if start: self.startup() @@ -503,20 +503,20 @@ class LxBrNet(PyCoreNet): """ sessionid = self.session.short_session_id() try: - self_objid = "%x" % self.objid + _id = "%x" % self.id except TypeError: - self_objid = "%s" % self.objid + _id = "%s" % self.id try: - net_objid = "%x" % net.objid + net_id = "%x" % net.id except TypeError: - net_objid = "%s" % net.objid + net_id = "%s" % net.id - localname = "veth%s.%s.%s" % (self_objid, net_objid, sessionid) + localname = "veth%s.%s.%s" % (_id, net_id, sessionid) if len(localname) >= 16: raise ValueError("interface local name %s too long" % localname) - name = "veth%s.%s.%s" % (net_objid, self_objid, sessionid) + name = "veth%s.%s.%s" % (net_id, _id, sessionid) if len(name) >= 16: raise ValueError("interface name %s too long" % name) @@ -570,14 +570,14 @@ class GreTapBridge(LxBrNet): another system. """ - def __init__(self, session, remoteip=None, objid=None, name=None, + def __init__(self, session, remoteip=None, _id=None, name=None, policy="ACCEPT", localip=None, ttl=255, key=None, start=True): """ Create a GreTapBridge instance. :param core.session.Session session: core session instance :param str remoteip: remote address - :param int objid: object id + :param int _id: object id :param str name: object name :param policy: network policy :param str localip: local address @@ -586,10 +586,10 @@ class GreTapBridge(LxBrNet): :param bool start: start flag :return: """ - LxBrNet.__init__(self, session=session, objid=objid, name=name, policy=policy, start=False) + LxBrNet.__init__(self, session=session, _id=_id, name=name, policy=policy, start=False) self.grekey = key if self.grekey is None: - self.grekey = self.session.id ^ self.objid + self.grekey = self.session.id ^ self.id self.localnum = None self.remotenum = None self.remoteip = remoteip diff --git a/daemon/core/netns/vnode.py b/daemon/core/netns/vnode.py index 3c41e33a..d56418d0 100644 --- a/daemon/core/netns/vnode.py +++ b/daemon/core/netns/vnode.py @@ -42,17 +42,17 @@ class SimpleLxcNode(PyCoreNode): """ valid_address_types = {"inet", "inet6", "inet6link"} - def __init__(self, session, objid=None, name=None, nodedir=None, start=True): + def __init__(self, session, _id=None, name=None, nodedir=None, start=True): """ Create a SimpleLxcNode instance. :param core.session.Session session: core session instance - :param int objid: object id + :param int _id: object id :param str name: object name :param str nodedir: node directory :param bool start: start flag """ - PyCoreNode.__init__(self, session, objid, name, start=start) + PyCoreNode.__init__(self, session, _id, name, start=start) self.nodedir = nodedir self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name)) self.client = None @@ -97,7 +97,7 @@ class SimpleLxcNode(PyCoreNode): if self.nodedir: vnoded += ["-C", self.nodedir] env = self.session.get_environment(state=False) - env["NODE_NUMBER"] = str(self.objid) + env["NODE_NUMBER"] = str(self.id) env["NODE_NAME"] = str(self.name) output = utils.check_cmd(vnoded, env=env) @@ -243,9 +243,9 @@ class SimpleLxcNode(PyCoreNode): sessionid = self.session.short_session_id() try: - suffix = "%x.%s.%s" % (self.objid, ifindex, sessionid) + suffix = "%x.%s.%s" % (self.id, ifindex, sessionid) except TypeError: - suffix = "%s.%s.%s" % (self.objid, ifindex, sessionid) + suffix = "%s.%s.%s" % (self.id, ifindex, sessionid) localname = "veth" + suffix if len(localname) >= 16: @@ -301,7 +301,7 @@ class SimpleLxcNode(PyCoreNode): ifname = "eth%d" % ifindex sessionid = self.session.short_session_id() - localname = "tap%s.%s.%s" % (self.objid, ifindex, sessionid) + localname = "tap%s.%s.%s" % (self.id, ifindex, sessionid) name = ifname tuntap = TunTap(node=self, name=name, localname=localname, net=net, start=self.up) @@ -489,18 +489,18 @@ class LxcNode(SimpleLxcNode): Provides lcx node functionality for core nodes. """ - def __init__(self, session, objid=None, name=None, nodedir=None, bootsh="boot.sh", start=True): + def __init__(self, session, _id=None, name=None, nodedir=None, bootsh="boot.sh", start=True): """ Create a LxcNode instance. :param core.session.Session session: core session instance - :param int objid: object id + :param int _id: object id :param str name: object name :param str nodedir: node directory :param bootsh: boot shell :param bool start: start flag """ - super(LxcNode, self).__init__(session=session, objid=objid, name=name, nodedir=nodedir, start=start) + super(LxcNode, self).__init__(session=session, _id=_id, name=name, nodedir=nodedir, start=start) self.bootsh = bootsh if start: self.startup() diff --git a/daemon/core/phys/pnodes.py b/daemon/core/phys/pnodes.py index 08b892e7..a2313e97 100644 --- a/daemon/core/phys/pnodes.py +++ b/daemon/core/phys/pnodes.py @@ -16,8 +16,8 @@ from core.netns.vnet import LxBrNet class PhysicalNode(PyCoreNode): - def __init__(self, session, objid=None, name=None, nodedir=None, start=True): - PyCoreNode.__init__(self, session, objid, name, start=start) + def __init__(self, session, _id=None, name=None, nodedir=None, start=True): + PyCoreNode.__init__(self, session, _id, name, start=start) self.nodedir = nodedir self.up = start self.lock = threading.RLock() @@ -185,7 +185,7 @@ class PhysicalNode(PyCoreNode): if self.up: # this is reached when this node is linked to a network node # tunnel to net not built yet, so build it now and adopt it - gt = self.session.broker.addnettunnel(net.objid) + gt = self.session.broker.addnettunnel(net.id) if gt is None or len(gt) != 1: raise ValueError("error building tunnel from adding a new network interface: %s" % gt) gt = gt[0] diff --git a/daemon/core/sdt.py b/daemon/core/sdt.py index 21843d98..fd9c9b14 100644 --- a/daemon/core/sdt.py +++ b/daemon/core/sdt.py @@ -330,7 +330,7 @@ class Sdt(object): (x, y, z) = obj.getposition() if x is None or y is None: continue - self.updatenode(obj.objid, MessageFlags.ADD.value, x, y, z, + self.updatenode(obj.id, MessageFlags.ADD.value, x, y, z, obj.name, obj.type, obj.icon) for nodenum in sorted(self.remotes.keys()): r = self.remotes[nodenum] @@ -343,7 +343,7 @@ class Sdt(object): for link_data in all_links: is_wireless = nodeutils.is_node(net, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)) wireless_link = link_data.message_type == LinkTypes.WIRELESS.value - if is_wireless and link_data.node1_id == net.objid: + if is_wireless and link_data.node1_id == net.id: continue self.updatelink( @@ -385,7 +385,7 @@ class Sdt(object): # enabled prior to starting the session if not self.is_enabled(): return False - # node.(objid, type, icon, name) are used. + # node.(_id, type, icon, name) are used. nodenum = msg.get_tlv(NodeTlvs.NUMBER.value) if not nodenum: return @@ -415,7 +415,7 @@ class Sdt(object): except KeyError: node = None if node: - self.updatenode(node.objid, msg.flags, x, y, z, node.name, node.type, node.icon) + self.updatenode(node.id, msg.flags, x, y, z, node.name, node.type, node.icon) else: if nodenum in self.remotes: remote = self.remotes[nodenum] @@ -426,7 +426,7 @@ class Sdt(object): if icon is None: icon = remote.icon else: - remote = Bunch(objid=nodenum, type=nodetype, icon=icon, name=name, net=net, links=set()) + remote = Bunch(_id=nodenum, type=nodetype, icon=icon, name=name, net=net, links=set()) self.remotes[nodenum] = remote remote.pos = (x, y, z) self.updatenode(nodenum, msg.flags, x, y, z, name, nodetype, icon) diff --git a/daemon/core/service.py b/daemon/core/service.py index ba8a98ed..86a8e3a4 100644 --- a/daemon/core/service.py +++ b/daemon/core/service.py @@ -374,7 +374,7 @@ class CoreServices(object): logging.info("setting services for node(%s): %s", node.name, services) for service_name in services: - service = self.get_service(node.objid, service_name, default_service=True) + service = self.get_service(node.id, service_name, default_service=True) if not service: logging.warn("unknown service(%s) for node(%s)", service_name, node.name) continue @@ -591,7 +591,7 @@ class CoreServices(object): :return: file message for node """ # get service to get file from - service = self.get_service(node.objid, service_name, default_service=True) + service = self.get_service(node.id, service_name, default_service=True) if not service: raise ValueError("invalid service: %s", service_name) @@ -614,7 +614,7 @@ class CoreServices(object): filetypestr = "service:%s" % service.name return FileData( message_type=MessageFlags.ADD.value, - node=node.objid, + node=node.id, name=filename, type=filetypestr, data=data diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index 6c9ea0a7..62010e8b 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -21,9 +21,9 @@ class EmaneTransportService(CoreService): if filename == cls.configs[0]: transport_commands = [] for interface in node.netifs(sort=True): - network_node = node.session.get_object(interface.net.objid) + network_node = node.session.get_object(interface.net.id) if nodeutils.is_node(network_node, NodeTypes.EMANE): - config = node.session.emane.get_configs(network_node.objid, network_node.model.name) + config = node.session.emane.get_configs(network_node.id, network_node.model.name) if config and emanexml.is_external(config): nem_id = network_node.getnemid(interface) command = "emanetransportd -r -l 0 -d ../transportdaemon%s.xml" % nem_id @@ -32,6 +32,6 @@ class EmaneTransportService(CoreService): return """ emanegentransportxml -o ../ ../platform%s.xml %s -""" % (node.objid, transport_commands) +""" % (node.id, transport_commands) else: raise ValueError diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index c79542ac..4b51706c 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -525,7 +525,7 @@ class FRRBgp(FrrService): cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" - cfg += "router bgp %s\n" % node.objid + cfg += "router bgp %s\n" % node.id rtrid = cls.routerid(node) cfg += " bgp router-id %s\n" % rtrid cfg += " redistribute connected\n" diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index ffd48bf6..e036a06d 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -474,7 +474,7 @@ class Bgp(QuaggaService): cfg = "!\n! BGP configuration\n!\n" cfg += "! You should configure the AS number below,\n" cfg += "! along with this router's peers.\n!\n" - cfg += "router bgp %s\n" % node.objid + cfg += "router bgp %s\n" % node.id rtrid = cls.routerid(node) cfg += " bgp router-id %s\n" % rtrid cfg += " redistribute connected\n" diff --git a/daemon/core/session.py b/daemon/core/session.py index 84f1d822..43500b2c 100644 --- a/daemon/core/session.py +++ b/daemon/core/session.py @@ -460,12 +460,10 @@ class Session(object): self.user = user - def get_object_id(self): + def get_node_id(self): """ - Return a unique, new random object id. + Return a unique, new node id. """ - object_id = None - with self._objects_lock: while True: object_id = random.randint(1, 0xFFFF) @@ -476,24 +474,22 @@ class Session(object): def add_object(self, cls, *clsargs, **clskwds): """ - Add an emulation object. + Create an emulation node. :param class cls: object class to add :param list clsargs: list of arguments for the class to create :param dict clskwds: dictionary of arguments for the class to create :return: the created class instance """ - obj = cls(self, *clsargs, **clskwds) + node = cls(self, *clsargs, **clskwds) - self._objects_lock.acquire() - if obj.objid in self.objects: - self._objects_lock.release() - obj.shutdown() - raise KeyError("duplicate object id %s for %s" % (obj.objid, obj)) - self.objects[obj.objid] = obj - self._objects_lock.release() + with self._objects_lock: + if node.id in self.objects: + node.shutdown() + raise KeyError("duplicate node id %s for %s" % (node.id, node.name)) + self.objects[node.id] = node - return obj + return node def get_object(self, object_id): """ @@ -827,7 +823,7 @@ class Session(object): control_net = self.get_control_net_object(net_index) if remove: - self.delete_object(control_net.objid) + self.delete_object(control_net.id) return None return control_net @@ -891,7 +887,7 @@ class Session(object): prefix = prefixes[0] control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET) - control_net = self.add_object(cls=control_net_class, objid=object_id, prefix=prefix, + control_net = self.add_object(cls=control_net_class, _id=object_id, prefix=prefix, assign_address=assign_address, updown_script=updown_script, serverintf=server_interface) @@ -929,12 +925,12 @@ class Session(object): if node.netif(control_net.CTRLIF_IDX_BASE + net_index): return - control_ip = node.objid + control_ip = node.id try: addrlist = ["%s/%s" % (control_net.prefix.addr(control_ip), control_net.prefix.prefixlen)] except ValueError: - msg = "Control interface not added to node %s. " % node.objid + msg = "Control interface not added to node %s. " % node.id msg += "Invalid control network prefix (%s). " % control_net.prefix msg += "A longer prefix length may be required for this many nodes." logging.exception(msg) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index bc206165..ff063d35 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -103,7 +103,7 @@ class NodeElement(object): self.session = session self.node = node self.element = etree.Element(element_name) - add_attribute(self.element, "id", node.objid) + add_attribute(self.element, "id", node.id) add_attribute(self.element, "name", node.name) add_attribute(self.element, "icon", node.icon) add_attribute(self.element, "canvas", node.canvas) @@ -399,7 +399,7 @@ class CoreXmlWriter(object): if nodeutils.is_node(node, (NodeTypes.SWITCH, NodeTypes.HUB)): for netif in node.netifs(sort=True): othernet = getattr(netif, "othernet", None) - if othernet and othernet.objid != node.objid: + if othernet and othernet.id != node.id: logging.info("writer ignoring node(%s) othernet(%s)", node.name, othernet.name) return diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index c1c9fbb6..3fb3945c 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -135,7 +135,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x if not transport_type: logging.info("warning: %s interface type unsupported!", netif.name) transport_type = "raw" - transport_file = transport_file_name(node.objid, transport_type) + transport_file = transport_file_name(node.id, transport_type) transport_element = etree.SubElement(nem_element, "transport", definition=transport_file) # add transport parameter @@ -145,7 +145,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x nem_entries[netif] = nem_element # merging code - key = netif.node.objid + key = netif.node.id if netif.transport_type == "raw": key = "host" otadev = control_net.brname @@ -267,7 +267,7 @@ def build_transport_xml(emane_manager, node, transport_type): add_param(transport_element, "bitrate", "0") # get emane model cnfiguration - config = emane_manager.get_configs(node.objid, node.model.name) + config = emane_manager.get_configs(node.id, node.model.name) flowcontrol = config.get("flowcontrolenable", "0") == "1" if "virtual" in transport_type.lower(): @@ -280,7 +280,7 @@ def build_transport_xml(emane_manager, node, transport_type): add_param(transport_element, "flowcontrolenable", "on") doc_name = "transport" - file_name = transport_file_name(node.objid, transport_type) + file_name = transport_file_name(node.id, transport_type) file_path = os.path.join(emane_manager.session.session_dir, file_name) create_file(transport_element, doc_name, file_path) @@ -382,7 +382,7 @@ def _basename(emane_model, interface=None): name = "n%s" % emane_model.object_id if interface: - node_id = interface.node.objid + node_id = interface.node.id if emane_model.session.emane.getifcconfig(node_id, interface, emane_model.name): name = interface.localname.replace(".", "_") diff --git a/daemon/examples/api/emane80211.py b/daemon/examples/api/emane80211.py index 0e2d2377..c0ecf11e 100644 --- a/daemon/examples/api/emane80211.py +++ b/daemon/examples/api/emane80211.py @@ -37,7 +37,7 @@ def example(options): node = session.create_wireless_node() node.setposition(x=150 * (i + 1), y=150) interface = prefixes.create_interface(node) - session.add_link(node.objid, emane_network.objid, interface_one=interface) + session.add_link(node.id, emane_network.id, interface_one=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/api/switch.py b/daemon/examples/api/switch.py index 87f031cd..1bab2fff 100644 --- a/daemon/examples/api/switch.py +++ b/daemon/examples/api/switch.py @@ -34,7 +34,7 @@ def example(options): for _ in xrange(options.nodes): node = session.add_node() interface = prefixes.create_interface(node) - session.add_link(node.objid, switch.objid, interface_one=interface) + session.add_link(node.id, switch.id, interface_one=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/api/switch_inject.py b/daemon/examples/api/switch_inject.py index f727f0ca..7e9300a6 100644 --- a/daemon/examples/api/switch_inject.py +++ b/daemon/examples/api/switch_inject.py @@ -29,7 +29,7 @@ def example(nodes): for _ in xrange(nodes): node = session.add_node() interface = prefixes.create_interface(node) - session.add_link(node.objid, switch.objid, interface_one=interface) + session.add_link(node.id, switch.id, interface_one=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/api/wlan.py b/daemon/examples/api/wlan.py index f152de37..1cbd9dd9 100644 --- a/daemon/examples/api/wlan.py +++ b/daemon/examples/api/wlan.py @@ -38,7 +38,7 @@ def example(options): for _ in xrange(options.nodes): node = session.add_node(node_options=node_options) interface = prefixes.create_interface(node) - session.add_link(node.objid, wlan.objid, interface_one=interface) + session.add_link(node.id, wlan.id, interface_one=interface) # instantiate session session.instantiate() diff --git a/daemon/examples/netns/daemonnodes.py b/daemon/examples/netns/daemonnodes.py index 3fe1d599..5d1e2d3c 100755 --- a/daemon/examples/netns/daemonnodes.py +++ b/daemon/examples/netns/daemonnodes.py @@ -46,7 +46,7 @@ def cmd(node, exec_cmd): global exec_num # Set up the command api message - tlvdata = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.objid) + tlvdata = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.id) tlvdata += CoreExecuteTlv.pack(ExecuteTlvs.NUMBER.value, exec_num) tlvdata += CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, exec_cmd) msg = coreapi.CoreExecMessage.pack(MessageFlags.STRING.value | MessageFlags.TEXT.value, tlvdata) @@ -149,7 +149,7 @@ def main(): # create remote nodes via API for i in xrange(1, number_of_nodes + 1): - node = nodes.CoreNode(session=session, objid=i, name="n%d" % i, start=False) + node = nodes.CoreNode(session=session, _id=i, name="n%d" % i, start=False) node.setposition(x=150 * i, y=150) node.server = daemon node_data = node.data(flags) @@ -159,7 +159,7 @@ def main(): # create remote links via API for i in xrange(1, number_of_nodes + 1): - tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.objid) + tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.id) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, 0) diff --git a/daemon/examples/netns/distributed.py b/daemon/examples/netns/distributed.py index b84450af..3c092b66 100755 --- a/daemon/examples/netns/distributed.py +++ b/daemon/examples/netns/distributed.py @@ -80,7 +80,7 @@ def main(): print "creating %d (%d local / %d remote) nodes with addresses from %s" % \ (options.numnodes, num_local, num_remote, prefix) for i in xrange(1, num_local + 1): - node = session.add_object(cls=nodes.CoreNode, name="n%d" % i, objid=i) + node = session.add_object(cls=nodes.CoreNode, name="n%d" % i, _id=i) node.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) node.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) node.setposition(x=150 * i, y=150) @@ -91,7 +91,7 @@ def main(): # create remote nodes via API for i in xrange(num_local + 1, options.numnodes + 1): - node = nodes.CoreNode(session=session, objid=i, name="n%d" % i, start=False) + node = nodes.CoreNode(session=session, _id=i, name="n%d" % i, start=False) node.setposition(x=150 * i, y=150) node.server = slave n.append(node) @@ -101,7 +101,7 @@ def main(): # create remote links via API for i in xrange(num_local + 1, options.numnodes + 1): - tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.objid) + tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.id) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, 0) diff --git a/daemon/examples/netns/ospfmanetmdrtest.py b/daemon/examples/netns/ospfmanetmdrtest.py index 7d0800a7..ed6b3ba8 100755 --- a/daemon/examples/netns/ospfmanetmdrtest.py +++ b/daemon/examples/netns/ospfmanetmdrtest.py @@ -64,12 +64,12 @@ ip forwarding confdir = "/usr/local/etc/quagga" def __init__(self, core, ipaddr, routerid=None, - objid=None, name=None, nodedir=None): + _id=None, name=None, nodedir=None): if routerid is None: routerid = ipaddr.split("/")[0] self.ipaddr = ipaddr self.routerid = routerid - nodes.LxcNode.__init__(self, core, objid, name, nodedir) + nodes.LxcNode.__init__(self, core, _id, name, nodedir) self.privatedir(self.confdir) self.privatedir(QUAGGA_STATE_DIR) @@ -246,7 +246,7 @@ class ManetExperiment(object): self.net = self.session.add_object(cls=nodes.WlanNode) for i in xrange(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) - tmp = self.session.add_object(cls=ManetNode, ipaddr=addr, objid="%d" % i, name="n%d" % i) + tmp = self.session.add_object(cls=ManetNode, ipaddr=addr, _id="%d" % i, name="n%d" % i) tmp.newnetif(self.net, [addr]) self.nodes.append(tmp) # connect nodes with probability linkprob diff --git a/daemon/examples/netns/wlanemanetests.py b/daemon/examples/netns/wlanemanetests.py index 9befcc23..0d78d792 100755 --- a/daemon/examples/netns/wlanemanetests.py +++ b/daemon/examples/netns/wlanemanetests.py @@ -417,7 +417,7 @@ class Experiment(object): prev = None for i in xrange(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) - tmp = self.session.add_object(cls=nodes.CoreNode, objid=i, name="n%d" % i) + tmp = self.session.add_object(cls=nodes.CoreNode, _id=i, name="n%d" % i) tmp.newnetif(self.net, [addr]) self.nodes.append(tmp) self.session.services.add_services(tmp, "router", "IPForward") @@ -440,13 +440,12 @@ class Experiment(object): self.session.location.setrefgeo(47.57917, -122.13232, 2.00000) self.session.location.refscale = 150.0 self.session.emane.loadmodels() - self.net = self.session.add_object(cls=EmaneNode, objid=numnodes + 1, name="wlan1") + self.net = self.session.add_object(cls=EmaneNode, _id=numnodes + 1, name="wlan1") self.net.verbose = verbose # self.session.emane.addobj(self.net) for i in xrange(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) - tmp = self.session.add_object(cls=nodes.CoreNode, objid=i, - name="n%d" % i) + tmp = self.session.add_object(cls=nodes.CoreNode, _id=i, name="n%d" % i) # tmp.setposition(i * 20, 50, None) tmp.setposition(50, 50, None) tmp.newnetif(self.net, [addr]) @@ -455,7 +454,7 @@ class Experiment(object): if values is None: values = cls.getdefaultvalues() - self.session.emane.setconfig(self.net.objid, cls.name, values) + self.session.emane.setconfig(self.net.id, cls.name, values) self.session.instantiate() self.info("waiting %s sec (TAP bring-up)" % 2) diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 3bd32121..4e8a2871 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -37,11 +37,11 @@ from core.service import ServiceManager EMANE_SERVICES = "zebra|OSPFv3MDR|IPForward" -def node_message(objid, name, emulation_server=None, node_type=NodeTypes.DEFAULT, model=None): +def node_message(_id, name, emulation_server=None, node_type=NodeTypes.DEFAULT, model=None): """ Convenience method for creating a node TLV messages. - :param int objid: node id + :param int _id: node id :param str name: node name :param str emulation_server: distributed server name, if desired :param core.enumerations.NodeTypes node_type: node type @@ -50,7 +50,7 @@ def node_message(objid, name, emulation_server=None, node_type=NodeTypes.DEFAULT :rtype: core.api.coreapi.CoreNodeMessage """ values = [ - (NodeTlvs.NUMBER, objid), + (NodeTlvs.NUMBER, _id), (NodeTlvs.TYPE, node_type.value), (NodeTlvs.NAME, name), (NodeTlvs.EMULATION_SERVER, emulation_server), @@ -118,7 +118,7 @@ def command_message(node, command): """ flags = MessageFlags.STRING.value | MessageFlags.TEXT.value return CoreExecMessage.create(flags, [ - (ExecuteTlvs.NODE, node.objid), + (ExecuteTlvs.NODE, node.id), (ExecuteTlvs.NUMBER, 1), (ExecuteTlvs.COMMAND, command) ]) diff --git a/daemon/tests/distributed/test_distributed.py b/daemon/tests/distributed/test_distributed.py index 1a8b5d9f..0a808f0f 100644 --- a/daemon/tests/distributed/test_distributed.py +++ b/daemon/tests/distributed/test_distributed.py @@ -40,7 +40,7 @@ class TestDistributed: # create local node message = conftest.node_message( - objid=1, + _id=1, name="n1", model="host" ) @@ -48,7 +48,7 @@ class TestDistributed: # create distributed node and assign to distributed server message = conftest.node_message( - objid=2, + _id=2, name="n2", emulation_server=cored.distributed_server, model="host" @@ -57,7 +57,7 @@ class TestDistributed: # create distributed switch and assign to distributed server message = conftest.node_message( - objid=3, + _id=3, name="n3", emulation_server=cored.distributed_server, node_type=NodeTypes.SWITCH @@ -106,7 +106,7 @@ class TestDistributed: # create local node message = conftest.node_message( - objid=1, + _id=1, name="n1", model="host" ) @@ -114,7 +114,7 @@ class TestDistributed: # create distributed node and assign to distributed server message = conftest.node_message( - objid=2, + _id=2, name="n2", emulation_server=cored.distributed_server, node_type=NodeTypes.PHYSICAL, @@ -124,7 +124,7 @@ class TestDistributed: # create distributed switch and assign to distributed server message = conftest.node_message( - objid=3, + _id=3, name="n3", node_type=NodeTypes.SWITCH ) @@ -173,7 +173,7 @@ class TestDistributed: # create local node message = conftest.node_message( - objid=1, + _id=1, name="n1", model="host" ) @@ -181,7 +181,7 @@ class TestDistributed: # create distributed node and assign to distributed server message = conftest.node_message( - objid=2, + _id=2, name=distributed_address, emulation_server=cored.distributed_server, node_type=NodeTypes.TUNNEL diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index 6516de04..15b17300 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -160,7 +160,7 @@ class TestConf: session.mobility.set_model(wlan_node, BasicRangeModel) # then - assert session.mobility.get_model_config(wlan_node.objid, BasicRangeModel.name) + assert session.mobility.get_model_config(wlan_node.id, BasicRangeModel.name) def test_model_set_error(self, session): # given diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 7b29ba75..04f05897 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -69,7 +69,7 @@ class TestCore: # link nodes to net node for node in [node_one, node_two]: interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, net_node.objid, interface_one=interface) + session.add_link(node.id, net_node.id, interface_one=interface) # instantiate session session.instantiate() @@ -96,7 +96,7 @@ class TestCore: # 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) + session.add_link(node.id, ptp_node.id, interface_one=interface) # get node client for testing client = node_one.client @@ -152,7 +152,7 @@ class TestCore: # 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) + session.add_link(node.id, ptp_node.id, interface_one=interface) # instantiate session session.instantiate() @@ -199,7 +199,7 @@ class TestCore: # link nodes for node in [node_one, node_two]: interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, wlan_node.objid, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface_one=interface) # instantiate session session.instantiate() @@ -229,7 +229,7 @@ class TestCore: # link nodes for node in [node_one, node_two]: interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, wlan_node.objid, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface_one=interface) # configure mobility script for session config = { diff --git a/daemon/tests/test_emane.py b/daemon/tests/test_emane.py index 1c77d8ea..838631b3 100644 --- a/daemon/tests/test_emane.py +++ b/daemon/tests/test_emane.py @@ -43,7 +43,7 @@ class TestEmane: # configure tdma if model == EmaneTdmaModel: - session.emane.set_model_config(emane_network.objid, EmaneTdmaModel.name, { + session.emane.set_model_config(emane_network.id, EmaneTdmaModel.name, { "schedule": os.path.join(_DIR, "../examples/tdma/schedule.xml") }) @@ -57,7 +57,7 @@ class TestEmane: for i, node in enumerate([node_one, node_two]): node.setposition(x=150 * (i + 1), y=150) interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, emane_network.objid, interface_one=interface) + session.add_link(node.id, emane_network.id, interface_one=interface) # instantiate session session.instantiate() diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 85a7910d..9b4cc0fa 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -192,10 +192,10 @@ class TestGrpc: # then with client.context_connect(): - response = client.get_node(session.id, node.objid) + response = client.get_node(session.id, node.id) # then - assert response.node.id == node.objid + assert response.node.id == node.id @pytest.mark.parametrize("node_id, expected", [ (1, True), @@ -237,7 +237,7 @@ class TestGrpc: assert response.result is expected if expected is True: with pytest.raises(KeyError): - assert session.get_object(node.objid) + assert session.get_object(node.id) def test_get_hooks(self, grpc_server): # given @@ -307,11 +307,11 @@ class TestGrpc: switch = session.add_node(_type=NodeTypes.SWITCH) node = session.add_node() interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, switch.objid, interface) + session.add_link(node.id, switch.id, interface) # then with client.context_connect(): - response = client.get_node_links(session.id, switch.objid) + response = client.get_node_links(session.id, switch.id) # then assert len(response.links) == 1 @@ -323,7 +323,7 @@ class TestGrpc: switch = session.add_node(_type=NodeTypes.SWITCH) node = session.add_node() interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, switch.objid, interface) + session.add_link(node.id, switch.id, interface) # then with pytest.raises(grpc.RpcError): @@ -339,9 +339,9 @@ class TestGrpc: assert len(switch.all_link_data(0)) == 0 # then - interface = interface_helper.create_interface(node.objid, 0) + interface = interface_helper.create_interface(node.id, 0) with client.context_connect(): - response = client.add_link(session.id, node.objid, switch.objid, interface) + response = client.add_link(session.id, node.id, switch.id, interface) # then assert response.result is True @@ -354,7 +354,7 @@ class TestGrpc: node = session.add_node() # then - interface = interface_helper.create_interface(node.objid, 0) + interface = interface_helper.create_interface(node.id, 0) with pytest.raises(grpc.RpcError): with client.context_connect(): client.add_link(session.id, 1, 3, interface) @@ -366,14 +366,14 @@ class TestGrpc: switch = session.add_node(_type=NodeTypes.SWITCH) node = session.add_node() interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, switch.objid, interface) + session.add_link(node.id, switch.id, interface) options = core_pb2.LinkOptions(bandwidth=30000) link = switch.all_link_data(0)[0] assert options.bandwidth != link.bandwidth # then with client.context_connect(): - response = client.edit_link(session.id, node.objid, switch.objid, options) + response = client.edit_link(session.id, node.id, switch.id, options) # then assert response.result is True @@ -388,11 +388,11 @@ class TestGrpc: interface_one = ip_prefixes.create_interface(node_one) node_two = session.add_node() interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.objid, node_two.objid, interface_one, interface_two) + session.add_link(node_one.id, node_two.id, interface_one, interface_two) link_node = None for node_id in session.objects: node = session.objects[node_id] - if node.objid not in {node_one.objid, node_two.objid}: + if node.id not in {node_one.id, node_two.id}: link_node = node break assert len(link_node.all_link_data(0)) == 1 @@ -400,7 +400,7 @@ class TestGrpc: # then with client.context_connect(): response = client.delete_link( - session.id, node_one.objid, node_two.objid, interface_one.id, interface_two.id) + session.id, node_one.id, node_two.id, interface_one.id, interface_two.id) # then assert response.result is True @@ -414,7 +414,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.get_wlan_config(session.id, wlan.objid) + response = client.get_wlan_config(session.id, wlan.id) # then assert len(response.groups) > 0 @@ -429,11 +429,11 @@ class TestGrpc: # then with client.context_connect(): - response = client.set_wlan_config(session.id, wlan.objid, {range_key: range_value}) + response = client.set_wlan_config(session.id, wlan.id, {range_key: range_value}) # then assert response.result is True - config = session.mobility.get_model_config(wlan.objid, BasicRangeModel.name) + config = session.mobility.get_model_config(wlan.id, BasicRangeModel.name) assert config[range_key] == range_value def test_get_emane_config(self, grpc_server): @@ -475,7 +475,7 @@ class TestGrpc: ) config_key = "platform_id_start" config_value = "2" - session.emane.set_model_config(emane_network.objid, EmaneIeee80211abgModel.name, {config_key: config_value}) + session.emane.set_model_config(emane_network.id, EmaneIeee80211abgModel.name, {config_key: config_value}) # then with client.context_connect(): @@ -483,7 +483,7 @@ class TestGrpc: # then assert len(response.configs) == 1 - assert emane_network.objid in response.configs + assert emane_network.id in response.configs def test_set_emane_model_config(self, grpc_server): # given @@ -499,11 +499,11 @@ class TestGrpc: # then with client.context_connect(): response = client.set_emane_model_config( - session.id, emane_network.objid, EmaneIeee80211abgModel.name, {config_key: config_value}) + session.id, emane_network.id, EmaneIeee80211abgModel.name, {config_key: config_value}) # then assert response.result is True - config = session.emane.get_model_config(emane_network.objid, EmaneIeee80211abgModel.name) + config = session.emane.get_model_config(emane_network.id, EmaneIeee80211abgModel.name) assert config[config_key] == config_value def test_get_emane_model_config(self, grpc_server): @@ -518,7 +518,7 @@ class TestGrpc: # then with client.context_connect(): response = client.get_emane_model_config( - session.id, emane_network.objid, EmaneIeee80211abgModel.name) + session.id, emane_network.id, EmaneIeee80211abgModel.name) # then assert len(response.groups) > 0 @@ -540,7 +540,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN) - session.mobility.set_model_config(wlan.objid, Ns2ScriptedMobility.name, {}) + session.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {}) # then with client.context_connect(): @@ -548,18 +548,18 @@ class TestGrpc: # then assert len(response.configs) > 0 - assert wlan.objid in response.configs + assert wlan.id in response.configs def test_get_mobility_config(self, grpc_server): # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN) - session.mobility.set_model_config(wlan.objid, Ns2ScriptedMobility.name, {}) + session.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {}) # then with client.context_connect(): - response = client.get_mobility_config(session.id, wlan.objid) + response = client.get_mobility_config(session.id, wlan.id) # then assert len(response.groups) > 0 @@ -574,11 +574,11 @@ class TestGrpc: # then with client.context_connect(): - response = client.set_mobility_config(session.id, wlan.objid, {config_key: config_value}) + response = client.set_mobility_config(session.id, wlan.id, {config_key: config_value}) # then assert response.result is True - config = session.mobility.get_model_config(wlan.objid, Ns2ScriptedMobility.name) + config = session.mobility.get_model_config(wlan.id, Ns2ScriptedMobility.name) assert config[config_key] == config_value def test_mobility_action(self, grpc_server): @@ -586,12 +586,12 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN) - session.mobility.set_model_config(wlan.objid, Ns2ScriptedMobility.name, {}) + session.mobility.set_model_config(wlan.id, Ns2ScriptedMobility.name, {}) session.instantiate() # then with client.context_connect(): - response = client.mobility_action(session.id, wlan.objid, core_pb2.MOBILITY_STOP) + response = client.mobility_action(session.id, wlan.id, core_pb2.MOBILITY_STOP) # then assert response.result is True @@ -642,7 +642,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.get_node_service(session.id, node.objid, "IPForward") + response = client.get_node_service(session.id, node.id, "IPForward") # then assert len(response.service.configs) > 0 @@ -655,7 +655,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.get_node_service_file(session.id, node.objid, "IPForward", "ipforward.sh") + response = client.get_node_service_file(session.id, node.id, "IPForward", "ipforward.sh") # then assert response.data is not None @@ -670,11 +670,11 @@ class TestGrpc: # then with client.context_connect(): - response = client.set_node_service(session.id, node.objid, service_name, (), validate, ()) + response = client.set_node_service(session.id, node.id, service_name, (), validate, ()) # then assert response.result is True - service = session.services.get_service(node.objid, service_name, default_service=True) + service = session.services.get_service(node.id, service_name, default_service=True) assert service.validate == validate def test_set_node_service_file(self, grpc_server): @@ -688,7 +688,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.set_node_service_file(session.id, node.objid, service_name, file_name, file_data) + response = client.set_node_service_file(session.id, node.id, service_name, file_name, file_data) # then assert response.result is True @@ -704,7 +704,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.service_action(session.id, node.objid, service_name, core_pb2.SERVICE_STOP) + response = client.service_action(session.id, node.id, service_name, core_pb2.SERVICE_STOP) # then assert response.result is True @@ -736,7 +736,7 @@ class TestGrpc: wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN) node = session.add_node() interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, wlan.objid, interface) + session.add_link(node.id, wlan.id, interface) link_data = wlan.all_link_data(0)[0] queue = Queue() diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index d0fd91fc..95962739 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -25,7 +25,7 @@ def command_message(node, command): :param command: command to execute :return: packed execute message """ - tlv_data = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.objid) + tlv_data = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.id) tlv_data += CoreExecuteTlv.pack(ExecuteTlvs.NUMBER.value, 1) tlv_data += CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, command) return coreapi.CoreExecMessage.pack(MessageFlags.STRING.value | MessageFlags.TEXT.value, tlv_data) @@ -52,8 +52,8 @@ def switch_link_message(switch, node, address, prefix_len): :param prefix_len: prefix length of address :return: packed link message """ - tlv_data = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.objid) - tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, node.objid) + tlv_data = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.id) + tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, node.id) tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value) tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, 0) tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4.value, address) @@ -70,7 +70,7 @@ def run_cmd(node, exec_cmd): :return: Returns the result of the command """ # Set up the command api message - # tlv_data = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.objid) + # tlv_data = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.id) # tlv_data += CoreExecuteTlv.pack(ExecuteTlvs.NUMBER.value, 1) # tlv_data += CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, exec_cmd) # message = coreapi.CoreExecMessage.pack(MessageFlags.STRING.value | MessageFlags.TEXT.value, tlv_data) diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index 27289014..c032c4a6 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -11,7 +11,7 @@ def create_ptp_network(session, ip_prefixes): # link nodes to net node interface_one = ip_prefixes.create_interface(node_one) interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.objid, node_two.objid, interface_one, interface_two) + session.add_link(node_one.id, node_two.id, interface_one, interface_two) # instantiate session session.instantiate() @@ -43,7 +43,7 @@ class TestLinks: inteface_two = ip_prefixes.create_interface(node_two) # when - session.add_link(node_one.objid, node_two.objid, interface_one, inteface_two) + session.add_link(node_one.id, node_two.id, interface_one, inteface_two) # then assert node_one.netif(interface_one.id) @@ -56,7 +56,7 @@ class TestLinks: interface_one = ip_prefixes.create_interface(node_one) # when - session.add_link(node_one.objid, node_two.objid, interface_one) + session.add_link(node_one.id, node_two.id, interface_one) # then assert node_two.all_link_data(0) @@ -69,7 +69,7 @@ class TestLinks: interface_two = ip_prefixes.create_interface(node_two) # when - session.add_link(node_one.objid, node_two.objid, interface_two=interface_two) + session.add_link(node_one.id, node_two.id, interface_two=interface_two) # then assert node_one.all_link_data(0) @@ -81,7 +81,7 @@ class TestLinks: node_two = session.add_node(_type=NodeTypes.SWITCH) # when - session.add_link(node_one.objid, node_two.objid) + session.add_link(node_one.id, node_two.id) # then assert node_one.all_link_data(0) @@ -91,7 +91,7 @@ class TestLinks: node_one = session.add_node() node_two = session.add_node(_type=NodeTypes.SWITCH) interface_one = ip_prefixes.create_interface(node_one) - session.add_link(node_one.objid, node_two.objid, interface_one) + session.add_link(node_one.id, node_two.id, interface_one) interface = node_one.netif(interface_one.id) output = utils.check_cmd(["tc", "qdisc", "show", "dev", interface.localname]) assert "delay" not in output @@ -105,7 +105,7 @@ class TestLinks: link_options.bandwidth = 5000000 link_options.per = 25 link_options.dup = 25 - session.update_link(node_one.objid, node_two.objid, + session.update_link(node_one.id, node_two.id, interface_one_id=interface_one.id, link_options=link_options) # then @@ -121,12 +121,12 @@ class TestLinks: node_two = session.add_node() interface_one = ip_prefixes.create_interface(node_one) interface_two = ip_prefixes.create_interface(node_two) - session.add_link(node_one.objid, node_two.objid, interface_one, interface_two) + session.add_link(node_one.id, node_two.id, interface_one, interface_two) assert node_one.netif(interface_one.id) assert node_two.netif(interface_two.id) # when - session.delete_link(node_one.objid, node_two.objid, interface_one.id, interface_two.id) + session.delete_link(node_one.id, node_two.id, interface_one.id, interface_two.id) # then assert not node_one.netif(interface_one.id) @@ -155,7 +155,7 @@ class TestLinks: # change bandwidth in bits per second link_options = LinkOptions() link_options.bandwidth = 500000 - session.update_link(node_one.objid, node_two.objid, link_options=link_options) + session.update_link(node_one.id, node_two.id, link_options=link_options) # run iperf again stdout = iperf(node_one, node_two, ip_prefixes) @@ -186,7 +186,7 @@ class TestLinks: # change bandwidth in bits per second link_options = LinkOptions() link_options.per = 50 - session.update_link(node_one.objid, node_two.objid, link_options=link_options) + session.update_link(node_one.id, node_two.id, link_options=link_options) # run iperf again stdout = iperf(node_one, node_two, ip_prefixes) @@ -216,7 +216,7 @@ class TestLinks: # change delay in microseconds link_options = LinkOptions() link_options.delay = 1000000 - session.update_link(node_one.objid, node_two.objid, link_options=link_options) + session.update_link(node_one.id, node_two.id, link_options=link_options) # run ping for delay information again stdout = ping_output(node_one, node_two, ip_prefixes) @@ -249,7 +249,7 @@ class TestLinks: # change jitter in microseconds link_options = LinkOptions() link_options.jitter = 1000000 - session.update_link(node_one.objid, node_two.objid, link_options=link_options) + session.update_link(node_one.id, node_two.id, link_options=link_options) # run iperf again stdout = iperf(node_one, node_two, ip_prefixes) diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 513856ec..a361ff6c 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -48,7 +48,7 @@ class TestNodes: update_options.set_position(x=position_value, y=position_value) # when - session.update_node(node.objid, update_options) + session.update_node(node.id, update_options) # then assert node.position.x == position_value @@ -59,11 +59,11 @@ class TestNodes: node = session.add_node() # when - session.delete_node(node.objid) + session.delete_node(node.id) # then with pytest.raises(KeyError): - session.get_object(node.objid) + session.get_object(node.id) @pytest.mark.parametrize("net_type", NET_TYPES) def test_net(self, session, net_type): diff --git a/daemon/tests/test_services.py b/daemon/tests/test_services.py index 789d05f5..42c10634 100644 --- a/daemon/tests/test_services.py +++ b/daemon/tests/test_services.py @@ -55,10 +55,10 @@ class TestServices: node = session.add_node() # when - session.services.set_service_file(node.objid, SERVICE_ONE, file_name, "# test") + session.services.set_service_file(node.id, SERVICE_ONE, file_name, "# test") # then - service = session.services.get_service(node.objid, SERVICE_ONE) + service = session.services.get_service(node.id, SERVICE_ONE) all_files = session.services.all_files(service) assert service assert all_files and len(all_files) == 1 @@ -69,8 +69,8 @@ class TestServices: node = session.add_node() # when - session.services.set_service(node.objid, SERVICE_ONE) - session.services.set_service(node.objid, SERVICE_TWO) + session.services.set_service(node.id, SERVICE_ONE) + session.services.set_service(node.id, SERVICE_TWO) # then all_configs = session.services.all_configs() @@ -189,8 +189,8 @@ class TestServices: node = session.add_node() # when - session.services.set_service(node.objid, my_service.name) - custom_my_service = session.services.get_service(node.objid, my_service.name) + session.services.set_service(node.id, my_service.name) + custom_my_service = session.services.get_service(node.id, my_service.name) custom_my_service.startup = ("sh custom.sh",) # then @@ -205,13 +205,13 @@ class TestServices: file_name = my_service.configs[0] file_data_one = "# custom file one" file_data_two = "# custom file two" - session.services.set_service_file(node_one.objid, my_service.name, file_name, file_data_one) - session.services.set_service_file(node_two.objid, my_service.name, file_name, file_data_two) + session.services.set_service_file(node_one.id, my_service.name, file_name, file_data_one) + session.services.set_service_file(node_two.id, my_service.name, file_name, file_data_two) # when - custom_service_one = session.services.get_service(node_one.objid, my_service.name) + custom_service_one = session.services.get_service(node_one.id, my_service.name) session.services.create_service_files(node_one, custom_service_one) - custom_service_two = session.services.get_service(node_two.objid, my_service.name) + custom_service_two = session.services.get_service(node_two.id, my_service.name) session.services.create_service_files(node_two, custom_service_two) # then @@ -240,10 +240,10 @@ class TestServices: node = session.add_node() # when - no_service = session.services.get_service(node.objid, SERVICE_ONE) - default_service = session.services.get_service(node.objid, SERVICE_ONE, default_service=True) - session.services.set_service(node.objid, SERVICE_ONE) - custom_service = session.services.get_service(node.objid, SERVICE_ONE, default_service=True) + no_service = session.services.get_service(node.id, SERVICE_ONE) + default_service = session.services.get_service(node.id, SERVICE_ONE, default_service=True) + session.services.set_service(node.id, SERVICE_ONE) + custom_service = session.services.get_service(node.id, SERVICE_ONE, default_service=True) # then assert no_service is None diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 058a5e7b..04db4c0e 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -64,14 +64,14 @@ class TestXml: # 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) + session.add_link(node.id, ptp_node.id, interface_one=interface) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.objid - n2_id = node_two.objid + n1_id = node_one.id + n2_id = node_two.id # save xml xml_file = tmpdir.join("session.xml") @@ -118,20 +118,20 @@ class TestXml: # 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) + session.add_link(node.id, ptp_node.id, interface_one=interface) # set custom values for node service - session.services.set_service(node_one.objid, SshService.name) + session.services.set_service(node_one.id, SshService.name) service_file = SshService.configs[0] file_data = "# test" - session.services.set_service_file(node_one.objid, SshService.name, service_file, file_data) + session.services.set_service_file(node_one.id, SshService.name, service_file, file_data) # instantiate session session.instantiate() # get ids for nodes - n1_id = node_one.objid - n2_id = node_two.objid + n1_id = node_one.id + n2_id = node_two.id # save xml xml_file = tmpdir.join("session.xml") @@ -155,7 +155,7 @@ class TestXml: session.open_xml(file_path, start=True) # retrieve custom service - service = session.services.get_service(node_one.objid, SshService.name) + service = session.services.get_service(node_one.id, SshService.name) # verify nodes have been recreated assert session.get_object(n1_id) @@ -184,15 +184,15 @@ class TestXml: # link nodes for node in [node_one, node_two]: interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, wlan_node.objid, interface_one=interface) + session.add_link(node.id, wlan_node.id, interface_one=interface) # instantiate session session.instantiate() # get ids for nodes - wlan_id = wlan_node.objid - n1_id = node_one.objid - n2_id = node_two.objid + wlan_id = wlan_node.id + n1_id = node_one.id + n2_id = node_two.id # save xml xml_file = tmpdir.join("session.xml") @@ -251,15 +251,15 @@ class TestXml: for i, node in enumerate([node_one, node_two]): node.setposition(x=150 * (i + 1), y=150) interface = ip_prefixes.create_interface(node) - session.add_link(node.objid, emane_network.objid, interface_one=interface) + session.add_link(node.id, emane_network.id, interface_one=interface) # instantiate session session.instantiate() # get ids for nodes - emane_id = emane_network.objid - n1_id = node_one.objid - n2_id = node_two.objid + emane_id = emane_network.id + n1_id = node_one.id + n2_id = node_two.id # save xml xml_file = tmpdir.join("session.xml") diff --git a/docs/scripting.md b/docs/scripting.md index 0b0ca47f..efb77ff7 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -37,7 +37,7 @@ switch = session.add_node(_type=NodeTypes.SWITCH) for _ in xrange(options.nodes): node = session.add_node() interface = prefixes.create_interface(node) - session.add_link(node.objid, switch.objid, interface_one=interface) + session.add_link(node.id, switch.id, interface_one=interface) # instantiate session session.instantiate() @@ -90,11 +90,11 @@ session = coreemu.create_session() node = session.add_node() # create and retrieve custom service -session.services.set_service(node.objid, "ServiceName") -custom_service = session.services.get_service(node.objid, "ServiceName") +session.services.set_service(node.id, "ServiceName") +custom_service = session.services.get_service(node.id, "ServiceName") # set custom file data -session.services.set_service_file(node.objid, "ServiceName", "FileName", "custom file data") +session.services.set_service_file(node.id, "ServiceName", "FileName", "custom file data") # set services to a node, using custom services when defined session.services.add_services(node, node.type, ["Service1", "Service2"]) @@ -116,5 +116,5 @@ emane_network.setposition(x=80, y=50) # set custom emane model config config = {} -session.emane.set_model_config(emane_network.objid, EmaneIeee80211abgModel.name, config) +session.emane.set_model_config(emane_network.id, EmaneIeee80211abgModel.name, config) ``` diff --git a/ns3/corens3/obj.py b/ns3/corens3/obj.py index 10e7e614..1920fd62 100644 --- a/ns3/corens3/obj.py +++ b/ns3/corens3/obj.py @@ -46,9 +46,9 @@ class CoreNs3Node(CoreNode, ns.network.Node): def __init__(self, *args, **kwds): ns.network.Node.__init__(self) # ns-3 ID starts at 0, CORE uses 1 - objid = self.GetId() + 1 - if 'objid' not in kwds: - kwds['objid'] = objid + _id = self.GetId() + 1 + if '_id' not in kwds: + kwds['_id'] = _id CoreNode.__init__(self, *args, **kwds) def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): @@ -118,8 +118,8 @@ class CoreNs3Net(PyCoreNet): # icon used type = "wlan" - def __init__(self, session, objid=None, name=None, start=True, policy=None): - PyCoreNet.__init__(self, session, objid, name) + def __init__(self, session, _id=None, name=None, start=True, policy=None): + PyCoreNet.__init__(self, session, _id, name) self.tapbridge = ns.tap_bridge.TapBridgeHelper() self._ns3devs = {} self._tapdevs = {} @@ -480,7 +480,7 @@ class Ns3Session(Session): node.position.set(x, y, z) node_data = node.data(0) self.broadcast_node(node_data) - self.sdt.updatenode(node.objid, flags=0, x=x, y=y, z=z) + self.sdt.updatenode(node.id, flags=0, x=x, y=y, z=z) time.sleep(0.001 * refresh_ms) def setupmobilitytracing(self, net, filename, nodes): @@ -488,7 +488,7 @@ class Ns3Session(Session): Start a tracing thread using the ASCII output from the ns3 mobility helper. """ - net.mobility = WayPointMobility(session=self, object_id=net.objid) + net.mobility = WayPointMobility(session=self, object_id=net.id) net.mobility.setendtime() net.mobility.refresh_ms = 300 net.mobility.empty_queue_stop = False @@ -533,8 +533,7 @@ class Ns3Session(Session): x, y, z = map(float, items['pos'].split(':')) vel = map(float, items['vel'].split(':')) node = nodemap[int(items['node'])] - net.mobility.addwaypoint(time=0, nodenum=node.objid, - x=x, y=y, z=z, speed=vel) + net.mobility.addwaypoint(time=0, nodenum=node.id, x=x, y=y, z=z, speed=vel) if kickstart: kickstart = False self.event_loop.add_event(0, net.mobility.start) From 66e603906a7c47f574b6ad82211515ce8c93da2d Mon Sep 17 00:00:00 2001 From: bharnden Date: Mon, 29 Apr 2019 23:31:47 -0700 Subject: [PATCH 0004/1992] updates to layout core module packages in a more logical way, including renaming methods from objects to nodes and nodes.objid to nodes.id --- daemon/core/api/__init__.py | 3 - daemon/core/{ => api}/grpc/__init__.py | 0 daemon/core/{ => api}/grpc/client.py | 6 +- daemon/core/api/grpc/core_pb2.py | 6527 +++++++++++++++++ daemon/core/api/grpc/core_pb2_grpc.py | 828 +++ daemon/core/{ => api}/grpc/server.py | 24 +- daemon/core/{misc => api/tlv}/__init__.py | 0 daemon/core/{ => api/tlv}/broker.py | 51 +- daemon/core/api/{ => tlv}/coreapi.py | 32 +- daemon/core/{ => api/tlv}/corehandlers.py | 72 +- daemon/core/{ => api/tlv}/coreserver.py | 0 daemon/core/api/{ => tlv}/dataconversion.py | 7 +- daemon/core/{misc => api/tlv}/structutils.py | 0 daemon/core/{conf.py => config.py} | 2 +- daemon/core/coreobj.py | 758 -- daemon/core/emane/bypass.py | 6 +- daemon/core/emane/commeffect.py | 6 +- daemon/core/emane/emanemanager.py | 40 +- daemon/core/emane/emanemanifest.py | 4 +- daemon/core/emane/emanemodel.py | 12 +- daemon/core/emane/nodes.py | 14 +- daemon/core/emane/tdma.py | 9 +- daemon/core/emulator/coreemu.py | 73 +- daemon/core/{ => emulator}/data.py | 0 daemon/core/emulator/emudata.py | 8 +- daemon/core/{ => emulator}/enumerations.py | 0 daemon/core/{ => emulator}/session.py | 229 +- daemon/core/{netns => location}/__init__.py | 0 .../{location.py => location/corelocation.py} | 4 +- daemon/core/{misc => location}/event.py | 0 daemon/core/{ => location}/mobility.py | 76 +- daemon/core/{misc => location}/utm.py | 0 daemon/core/misc/LatLongUTMconversion.py | 246 - daemon/core/misc/nodemaps.py | 28 - daemon/core/misc/quagga.py | 172 - daemon/core/netns/nodes.py | 739 -- daemon/core/netns/vnode.py | 609 -- daemon/core/{phys => nodes}/__init__.py | 0 daemon/core/nodes/base.py | 1158 +++ .../{netns/vnodeclient.py => nodes/client.py} | 5 +- .../core/{netns/vif.py => nodes/interface.py} | 180 +- daemon/core/{misc => nodes}/ipaddress.py | 0 .../core/{netns/vnet.py => nodes/network.py} | 443 +- daemon/core/nodes/nodemaps.py | 29 + daemon/core/{misc => nodes}/nodeutils.py | 0 daemon/core/{netns => nodes}/openvswitch.py | 37 +- daemon/core/nodes/physical.py | 544 ++ daemon/core/phys/pnodes.py | 245 - daemon/core/plugins/__init__.py | 0 daemon/core/{ => plugins}/sdt.py | 41 +- daemon/core/services/__init__.py | 2 +- daemon/core/services/bird.py | 2 +- .../{service.py => services/coreservices.py} | 15 +- daemon/core/services/dockersvc.py | 4 +- daemon/core/services/emaneservices.py | 8 +- daemon/core/services/frr.py | 7 +- daemon/core/services/nrl.py | 6 +- daemon/core/services/quagga.py | 7 +- daemon/core/services/sdn.py | 2 +- daemon/core/services/security.py | 2 +- daemon/core/services/ucarp.py | 2 +- daemon/core/services/utility.py | 9 +- daemon/core/services/xorp.py | 2 +- daemon/core/{misc => }/utils.py | 0 daemon/core/xml/corexml.py | 21 +- daemon/core/xml/corexmldeployment.py | 14 +- daemon/core/xml/emanexml.py | 14 +- daemon/examples/api/emane80211.py | 4 +- daemon/examples/api/switch.py | 6 +- daemon/examples/api/switch_inject.py | 2 +- daemon/examples/api/wlan.py | 8 +- daemon/examples/eventloop.py | 2 +- daemon/examples/grpc/switch.py | 3 +- daemon/examples/myservices/sample.py | 4 +- daemon/examples/netns/daemonnodes.py | 32 +- daemon/examples/netns/distributed.py | 17 +- daemon/examples/netns/howmanynodes.py | 13 +- daemon/examples/netns/ospfmanetmdrtest.py | 19 +- daemon/examples/netns/wlanemanetests.py | 15 +- daemon/examples/stopsession.py | 4 +- daemon/proto/Makefile.am | 4 +- daemon/scripts/core-daemon | 12 +- daemon/scripts/coresendmsg | 10 +- daemon/tests/conftest.py | 44 +- daemon/tests/distributed/test_distributed.py | 16 +- daemon/tests/myservices/sample.py | 2 +- daemon/tests/test_conf.py | 14 +- daemon/tests/test_core.py | 10 +- daemon/tests/test_grpc.py | 20 +- daemon/tests/test_gui.py | 22 +- daemon/tests/test_links.py | 4 +- daemon/tests/test_nodes.py | 6 +- daemon/tests/test_services.py | 6 +- daemon/tests/test_utils.py | 2 +- daemon/tests/test_xml.py | 40 +- ns3/corens3/obj.py | 23 +- ns3/examples/ns3lte.py | 6 +- ns3/examples/ns3wifi.py | 6 +- ns3/examples/ns3wifirandomwalk.py | 6 +- ns3/examples/ns3wimax.py | 6 +- 100 files changed, 10283 insertions(+), 3489 deletions(-) rename daemon/core/{ => api}/grpc/__init__.py (100%) rename daemon/core/{ => api}/grpc/client.py (99%) create mode 100644 daemon/core/api/grpc/core_pb2.py create mode 100644 daemon/core/api/grpc/core_pb2_grpc.py rename daemon/core/{ => api}/grpc/server.py (98%) rename daemon/core/{misc => api/tlv}/__init__.py (100%) rename daemon/core/{ => api/tlv}/broker.py (96%) rename daemon/core/api/{ => tlv}/coreapi.py (97%) rename daemon/core/{ => api/tlv}/corehandlers.py (97%) rename daemon/core/{ => api/tlv}/coreserver.py (100%) rename daemon/core/api/{ => tlv}/dataconversion.py (94%) rename daemon/core/{misc => api/tlv}/structutils.py (100%) rename daemon/core/{conf.py => config.py} (99%) delete mode 100644 daemon/core/coreobj.py rename daemon/core/{ => emulator}/data.py (100%) rename daemon/core/{ => emulator}/enumerations.py (100%) rename daemon/core/{ => emulator}/session.py (86%) rename daemon/core/{netns => location}/__init__.py (100%) rename daemon/core/{location.py => location/corelocation.py} (99%) rename daemon/core/{misc => location}/event.py (100%) rename daemon/core/{ => location}/mobility.py (95%) rename daemon/core/{misc => location}/utm.py (100%) delete mode 100755 daemon/core/misc/LatLongUTMconversion.py delete mode 100644 daemon/core/misc/nodemaps.py delete mode 100644 daemon/core/misc/quagga.py delete mode 100644 daemon/core/netns/nodes.py delete mode 100644 daemon/core/netns/vnode.py rename daemon/core/{phys => nodes}/__init__.py (100%) create mode 100644 daemon/core/nodes/base.py rename daemon/core/{netns/vnodeclient.py => nodes/client.py} (98%) rename daemon/core/{netns/vif.py => nodes/interface.py} (69%) rename daemon/core/{misc => nodes}/ipaddress.py (100%) rename daemon/core/{netns/vnet.py => nodes/network.py} (58%) create mode 100644 daemon/core/nodes/nodemaps.py rename daemon/core/{misc => nodes}/nodeutils.py (100%) rename daemon/core/{netns => nodes}/openvswitch.py (96%) create mode 100644 daemon/core/nodes/physical.py delete mode 100644 daemon/core/phys/pnodes.py create mode 100644 daemon/core/plugins/__init__.py rename daemon/core/{ => plugins}/sdt.py (94%) rename daemon/core/{service.py => services/coreservices.py} (98%) rename daemon/core/{misc => }/utils.py (100%) diff --git a/daemon/core/api/__init__.py b/daemon/core/api/__init__.py index c2e3c613..e69de29b 100644 --- a/daemon/core/api/__init__.py +++ b/daemon/core/api/__init__.py @@ -1,3 +0,0 @@ -""" -Contains code specific to the legacy TCP API for interacting with the TCL based GUI. -""" diff --git a/daemon/core/grpc/__init__.py b/daemon/core/api/grpc/__init__.py similarity index 100% rename from daemon/core/grpc/__init__.py rename to daemon/core/api/grpc/__init__.py diff --git a/daemon/core/grpc/client.py b/daemon/core/api/grpc/client.py similarity index 99% rename from daemon/core/grpc/client.py rename to daemon/core/api/grpc/client.py index a6863e37..d3ec9ae8 100644 --- a/daemon/core/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -10,9 +10,9 @@ from contextlib import contextmanager import grpc -from core.grpc import core_pb2 -from core.grpc import core_pb2_grpc -from core.misc.ipaddress import Ipv4Prefix, Ipv6Prefix, MacAddress +from core.api.grpc import core_pb2 +from core.api.grpc import core_pb2_grpc +from core.nodes.ipaddress import Ipv4Prefix, Ipv6Prefix, MacAddress class InterfaceHelper(object): diff --git a/daemon/core/api/grpc/core_pb2.py b/daemon/core/api/grpc/core_pb2.py new file mode 100644 index 00000000..754f76c7 --- /dev/null +++ b/daemon/core/api/grpc/core_pb2.py @@ -0,0 +1,6527 @@ +# Generated by the protocol buffer compiler. DO NOT EDIT! +# source: core.proto + +import sys +_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) +from google.protobuf.internal import enum_type_wrapper +from google.protobuf import descriptor as _descriptor +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection +from google.protobuf import symbol_database as _symbol_database +# @@protoc_insertion_point(imports) + +_sym_db = _symbol_database.Default() + + + + +DESCRIPTOR = _descriptor.FileDescriptor( + name='core.proto', + package='core', + syntax='proto3', + serialized_options=None, + serialized_pb=_b('\n\ncore.proto\x12\x04\x63ore\"\"\n\x14\x43reateSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"F\n\x15\x43reateSessionResponse\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\"\"\n\x14\x44\x65leteSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\'\n\x15\x44\x65leteSessionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x14\n\x12GetSessionsRequest\"=\n\x13GetSessionsResponse\x12&\n\x08sessions\x18\x01 \x03(\x0b\x32\x14.core.SessionSummary\"\x1f\n\x11GetSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"4\n\x12GetSessionResponse\x12\x1e\n\x07session\x18\x01 \x01(\x0b\x32\r.core.Session\"&\n\x18GetSessionOptionsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\">\n\x19GetSessionOptionsResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x91\x01\n\x18SetSessionOptionsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12:\n\x06\x63onfig\x18\x02 \x03(\x0b\x32*.core.SetSessionOptionsRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x19SetSessionOptionsResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\'\n\x19GetSessionLocationRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"M\n\x1aGetSessionLocationResponse\x12 \n\x08position\x18\x01 \x01(\x0b\x32\x0e.core.Position\x12\r\n\x05scale\x18\x02 \x01(\x02\"X\n\x19SetSessionLocationRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12 \n\x08position\x18\x02 \x01(\x0b\x32\x0e.core.Position\x12\r\n\x05scale\x18\x03 \x01(\x02\",\n\x1aSetSessionLocationResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"G\n\x16SetSessionStateRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\")\n\x17SetSessionStateResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x1f\n\x11NodeEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"%\n\tNodeEvent\x12\x18\n\x04node\x18\x01 \x01(\x0b\x32\n.core.Node\"\x1f\n\x11LinkEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"N\n\tLinkEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x18\n\x04link\x18\x02 \x01(\x0b\x32\n.core.Link\"\"\n\x14SessionEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"f\n\x0cSessionEvent\x12\x0c\n\x04node\x18\x01 \x01(\x05\x12\r\n\x05\x65vent\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0c\n\x04time\x18\x05 \x01(\x02\x12\x0f\n\x07session\x18\x06 \x01(\x05\"!\n\x13\x43onfigEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\x9e\x02\n\x0b\x43onfigEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x0c\n\x04node\x18\x02 \x01(\x05\x12\x0e\n\x06object\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x12\n\ndata_types\x18\x05 \x03(\x05\x12\x13\n\x0b\x64\x61ta_values\x18\x06 \x01(\t\x12\x10\n\x08\x63\x61ptions\x18\x07 \x01(\t\x12\x0e\n\x06\x62itmap\x18\x08 \x01(\t\x12\x17\n\x0fpossible_values\x18\t \x01(\t\x12\x0e\n\x06groups\x18\n \x01(\t\x12\x0f\n\x07session\x18\x0b \x01(\t\x12\x11\n\tinterface\x18\x0c \x01(\x05\x12\x12\n\nnetwork_id\x18\r \x01(\x05\x12\x0e\n\x06opaque\x18\x0e \x01(\t\"$\n\x16\x45xceptionEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\x90\x01\n\x0e\x45xceptionEvent\x12\x0c\n\x04node\x18\x01 \x01(\x05\x12\x0f\n\x07session\x18\x02 \x01(\x05\x12#\n\x05level\x18\x03 \x01(\x0e\x32\x14.core.ExceptionLevel\x12\x0e\n\x06source\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61te\x18\x05 \x01(\t\x12\x0c\n\x04text\x18\x06 \x01(\t\x12\x0e\n\x06opaque\x18\x07 \x01(\t\"\x1f\n\x11\x46ileEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\xc4\x01\n\tFileEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x0c\n\x04node\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04mode\x18\x04 \x01(\t\x12\x0e\n\x06number\x18\x05 \x01(\x05\x12\x0c\n\x04type\x18\x06 \x01(\t\x12\x0e\n\x06source\x18\x07 \x01(\t\x12\x0f\n\x07session\x18\x08 \x01(\x05\x12\x0c\n\x04\x64\x61ta\x18\t \x01(\x0c\x12\x17\n\x0f\x63ompressed_data\x18\n \x01(\x0c\";\n\x0e\x41\x64\x64NodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04node\x18\x02 \x01(\x0b\x32\n.core.Node\"\x1d\n\x0f\x41\x64\x64NodeResponse\x12\n\n\x02id\x18\x01 \x01(\x05\"-\n\x0eGetNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"P\n\x0fGetNodeResponse\x12\x18\n\x04node\x18\x01 \x01(\x0b\x32\n.core.Node\x12#\n\ninterfaces\x18\x02 \x03(\x0b\x32\x0f.core.Interface\"P\n\x0f\x45\x64itNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12 \n\x08position\x18\x03 \x01(\x0b\x32\x0e.core.Position\"\"\n\x10\x45\x64itNodeResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"0\n\x11\x44\x65leteNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"$\n\x12\x44\x65leteNodeResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"2\n\x13GetNodeLinksRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"1\n\x14GetNodeLinksResponse\x12\x19\n\x05links\x18\x01 \x03(\x0b\x32\n.core.Link\";\n\x0e\x41\x64\x64LinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04link\x18\x02 \x01(\x0b\x32\n.core.Link\"!\n\x0f\x41\x64\x64LinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x98\x01\n\x0f\x45\x64itLinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x10\n\x08node_one\x18\x02 \x01(\x05\x12\x10\n\x08node_two\x18\x03 \x01(\x05\x12\x15\n\rinterface_one\x18\x04 \x01(\x05\x12\x15\n\rinterface_two\x18\x05 \x01(\x05\x12\"\n\x07options\x18\x06 \x01(\x0b\x32\x11.core.LinkOptions\"\"\n\x10\x45\x64itLinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"v\n\x11\x44\x65leteLinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x10\n\x08node_one\x18\x02 \x01(\x05\x12\x10\n\x08node_two\x18\x03 \x01(\x05\x12\x15\n\rinterface_one\x18\x04 \x01(\x05\x12\x15\n\rinterface_two\x18\x05 \x01(\x05\"$\n\x12\x44\x65leteLinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\"\n\x0fGetHooksRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"-\n\x10GetHooksResponse\x12\x19\n\x05hooks\x18\x01 \x03(\x0b\x32\n.core.Hook\";\n\x0e\x41\x64\x64HookRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04hook\x18\x02 \x01(\x0b\x32\n.core.Hook\"!\n\x0f\x41\x64\x64HookResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\",\n\x19GetMobilityConfigsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\xf2\x01\n\x1aGetMobilityConfigsResponse\x12>\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32-.core.GetMobilityConfigsResponse.ConfigsEntry\x1a\x33\n\x0eMobilityConfig\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\x1a_\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12>\n\x05value\x18\x02 \x01(\x0b\x32/.core.GetMobilityConfigsResponse.MobilityConfig:\x02\x38\x01\"7\n\x18GetMobilityConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\">\n\x19GetMobilityConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\xa2\x01\n\x18SetMobilityConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12:\n\x06\x63onfig\x18\x03 \x03(\x0b\x32*.core.SetMobilityConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x19SetMobilityConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"Z\n\x15MobilityActionRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12$\n\x06\x61\x63tion\x18\x03 \x01(\x0e\x32\x14.core.MobilityAction\"(\n\x16MobilityActionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x14\n\x12GetServicesRequest\"6\n\x13GetServicesResponse\x12\x1f\n\x08services\x18\x01 \x03(\x0b\x32\r.core.Service\",\n\x19GetServiceDefaultsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"E\n\x1aGetServiceDefaultsResponse\x12\'\n\x08\x64\x65\x66\x61ults\x18\x01 \x03(\x0b\x32\x15.core.ServiceDefaults\"U\n\x19SetServiceDefaultsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\'\n\x08\x64\x65\x66\x61ults\x18\x02 \x03(\x0b\x32\x15.core.ServiceDefaults\",\n\x1aSetServiceDefaultsResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"E\n\x15GetNodeServiceRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\"@\n\x16GetNodeServiceResponse\x12&\n\x07service\x18\x01 \x01(\x0b\x32\x15.core.NodeServiceData\"W\n\x19GetNodeServiceFileRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0c\n\x04\x66ile\x18\x04 \x01(\t\"*\n\x1aGetNodeServiceFileResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"z\n\x15SetNodeServiceRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0f\n\x07startup\x18\x04 \x03(\t\x12\x10\n\x08validate\x18\x05 \x03(\t\x12\x10\n\x08shutdown\x18\x06 \x03(\t\"(\n\x16SetNodeServiceResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"e\n\x19SetNodeServiceFileRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0c\n\x04\x66ile\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\",\n\x1aSetNodeServiceFileResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"i\n\x14ServiceActionRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12#\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32\x13.core.ServiceAction\"\'\n\x15ServiceActionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"3\n\x14GetWlanConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\":\n\x15GetWlanConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x9a\x01\n\x14SetWlanConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x36\n\x06\x63onfig\x18\x03 \x03(\x0b\x32&.core.SetWlanConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\'\n\x15SetWlanConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"(\n\x15GetEmaneConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\";\n\x16GetEmaneConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x90\x01\n\x15SetEmaneConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x37\n\x06\x63onfig\x18\x02 \x03(\x0b\x32\'.core.SetEmaneConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"(\n\x16SetEmaneConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"(\n\x15GetEmaneModelsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"(\n\x16GetEmaneModelsResponse\x12\x0e\n\x06models\x18\x01 \x03(\t\"[\n\x1aGetEmaneModelConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x11\n\tinterface\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\"@\n\x1bGetEmaneModelConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\xc8\x01\n\x1aSetEmaneModelConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x11\n\tinterface\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\x12<\n\x06\x63onfig\x18\x05 \x03(\x0b\x32,.core.SetEmaneModelConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"-\n\x1bSetEmaneModelConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\".\n\x1bGetEmaneModelConfigsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\x81\x02\n\x1cGetEmaneModelConfigsResponse\x12@\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32/.core.GetEmaneModelConfigsResponse.ConfigsEntry\x1a?\n\x0bModelConfig\x12\r\n\x05model\x18\x01 \x01(\t\x12!\n\x06groups\x18\x02 \x03(\x0b\x32\x11.core.ConfigGroup\x1a^\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12=\n\x05value\x18\x02 \x01(\x0b\x32..core.GetEmaneModelConfigsResponse.ModelConfig:\x02\x38\x01\"!\n\x0eSaveXmlRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\x1f\n\x0fSaveXmlResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x1e\n\x0eOpenXmlRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"2\n\x0fOpenXmlResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\x12\x0f\n\x07session\x18\x02 \x01(\x05\"E\n\x04Hook\x12!\n\x05state\x18\x01 \x01(\x0e\x32\x12.core.SessionState\x12\x0c\n\x04\x66ile\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"6\n\x0fServiceDefaults\x12\x11\n\tnode_type\x18\x01 \x01(\t\x12\x10\n\x08services\x18\x02 \x03(\t\"&\n\x07Service\x12\r\n\x05group\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xee\x01\n\x0fNodeServiceData\x12\x13\n\x0b\x65xecutables\x18\x01 \x03(\t\x12\x14\n\x0c\x64\x65pendencies\x18\x02 \x03(\t\x12\x0c\n\x04\x64irs\x18\x03 \x03(\t\x12\x0f\n\x07\x63onfigs\x18\x04 \x03(\t\x12\x0f\n\x07startup\x18\x05 \x03(\t\x12\x10\n\x08validate\x18\x06 \x03(\t\x12\x34\n\x0fvalidation_mode\x18\x07 \x01(\x0e\x32\x1b.core.ServiceValidationMode\x12\x18\n\x10validation_timer\x18\x08 \x01(\x05\x12\x10\n\x08shutdown\x18\t \x03(\t\x12\x0c\n\x04meta\x18\n \x01(\t\"@\n\x0b\x43onfigGroup\x12\x0c\n\x04name\x18\x01 \x01(\t\x12#\n\x07options\x18\x02 \x03(\x0b\x32\x12.core.ConfigOption\"X\n\x0c\x43onfigOption\x12\r\n\x05label\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x0e\n\x06select\x18\x05 \x03(\t\"n\n\x07Session\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\x12\x19\n\x05nodes\x18\x03 \x03(\x0b\x32\n.core.Node\x12\x19\n\x05links\x18\x04 \x03(\x0b\x32\n.core.Link\"N\n\x0eSessionSummary\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\x12\r\n\x05nodes\x18\x03 \x01(\x05\"\xae\x01\n\x04Node\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x1c\n\x04type\x18\x03 \x01(\x0e\x32\x0e.core.NodeType\x12\r\n\x05model\x18\x04 \x01(\t\x12 \n\x08position\x18\x05 \x01(\x0b\x32\x0e.core.Position\x12\x10\n\x08services\x18\x06 \x03(\t\x12\r\n\x05\x65mane\x18\x07 \x01(\t\x12\x0c\n\x04icon\x18\x08 \x01(\t\x12\x0e\n\x06opaque\x18\t \x01(\t\"\xbc\x01\n\x04Link\x12\x10\n\x08node_one\x18\x01 \x01(\x05\x12\x10\n\x08node_two\x18\x02 \x01(\x05\x12\x1c\n\x04type\x18\x03 \x01(\x0e\x32\x0e.core.LinkType\x12&\n\rinterface_one\x18\x04 \x01(\x0b\x32\x0f.core.Interface\x12&\n\rinterface_two\x18\x05 \x01(\x0b\x32\x0f.core.Interface\x12\"\n\x07options\x18\x06 \x01(\x0b\x32\x11.core.LinkOptions\"\xba\x01\n\x0bLinkOptions\x12\x0e\n\x06opaque\x18\x01 \x01(\t\x12\x0e\n\x06jitter\x18\x02 \x01(\x02\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0e\n\x06mburst\x18\x04 \x01(\x02\x12\x0b\n\x03mer\x18\x05 \x01(\x02\x12\x0b\n\x03per\x18\x06 \x01(\x02\x12\x11\n\tbandwidth\x18\x07 \x01(\x02\x12\r\n\x05\x62urst\x18\x08 \x01(\x02\x12\r\n\x05\x64\x65lay\x18\t \x01(\x02\x12\x0b\n\x03\x64up\x18\n \x01(\x02\x12\x16\n\x0eunidirectional\x18\x0b \x01(\x08\"\x9a\x01\n\tInterface\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0b\n\x03mac\x18\x03 \x01(\t\x12\x0b\n\x03ip4\x18\x04 \x01(\t\x12\x0f\n\x07ip4mask\x18\x05 \x01(\x05\x12\x0b\n\x03ip6\x18\x06 \x01(\t\x12\x0f\n\x07ip6mask\x18\x07 \x01(\x05\x12\r\n\x05netid\x18\x08 \x01(\x05\x12\x0e\n\x06\x66lowid\x18\t \x01(\x05\x12\x0b\n\x03mtu\x18\n \x01(\x05\"R\n\x08Position\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\x12\x0b\n\x03lat\x18\x04 \x01(\x02\x12\x0b\n\x03lon\x18\x05 \x01(\x02\x12\x0b\n\x03\x61lt\x18\x06 \x01(\x02*\x9f\x01\n\x0bMessageType\x12\x10\n\x0cMESSAGE_NONE\x10\x00\x12\x0f\n\x0bMESSAGE_ADD\x10\x01\x12\x12\n\x0eMESSAGE_DELETE\x10\x02\x12\x0f\n\x0bMESSAGE_CRI\x10\x04\x12\x11\n\rMESSAGE_LOCAL\x10\x08\x12\x12\n\x0eMESSAGE_STRING\x10\x10\x12\x10\n\x0cMESSAGE_TEXT\x10 \x12\x0f\n\x0bMESSAGE_TTY\x10@*-\n\x08LinkType\x12\x11\n\rLINK_WIRELESS\x10\x00\x12\x0e\n\nLINK_WIRED\x10\x01*\xa4\x01\n\x0cSessionState\x12\x0e\n\nSTATE_NONE\x10\x00\x12\x14\n\x10STATE_DEFINITION\x10\x01\x12\x17\n\x13STATE_CONFIGURATION\x10\x02\x12\x17\n\x13STATE_INSTANTIATION\x10\x03\x12\x11\n\rSTATE_RUNTIME\x10\x04\x12\x15\n\x11STATE_DATACOLLECT\x10\x05\x12\x12\n\x0eSTATE_SHUTDOWN\x10\x06*\x8b\x02\n\x08NodeType\x12\x10\n\x0cNODE_DEFAULT\x10\x00\x12\x11\n\rNODE_PHYSICAL\x10\x01\x12\x0c\n\x08NODE_TBD\x10\x03\x12\x0f\n\x0bNODE_SWITCH\x10\x04\x12\x0c\n\x08NODE_HUB\x10\x05\x12\x15\n\x11NODE_WIRELESS_LAN\x10\x06\x12\r\n\tNODE_RJ45\x10\x07\x12\x0f\n\x0bNODE_TUNNEL\x10\x08\x12\x10\n\x0cNODE_KTUNNEL\x10\t\x12\x0e\n\nNODE_EMANE\x10\n\x12\x13\n\x0fNODE_TAP_BRIDGE\x10\x0b\x12\x15\n\x11NODE_PEER_TO_PEER\x10\x0c\x12\x14\n\x10NODE_CONTROL_NET\x10\r\x12\x12\n\x0eNODE_EMANE_NET\x10\x0e*c\n\x15ServiceValidationMode\x12\x17\n\x13VALIDATION_BLOCKING\x10\x00\x12\x1b\n\x17VALIDATION_NON_BLOCKING\x10\x01\x12\x14\n\x10VALIDATION_TIMER\x10\x02*_\n\rServiceAction\x12\x11\n\rSERVICE_START\x10\x00\x12\x10\n\x0cSERVICE_STOP\x10\x01\x12\x13\n\x0fSERVICE_RESTART\x10\x02\x12\x14\n\x10SERVICE_VALIDATE\x10\x03*K\n\x0eMobilityAction\x12\x12\n\x0eMOBILITY_START\x10\x00\x12\x12\n\x0eMOBILITY_PAUSE\x10\x01\x12\x11\n\rMOBILITY_STOP\x10\x02*~\n\x0e\x45xceptionLevel\x12\x15\n\x11\x45XCEPTION_DEFAULT\x10\x00\x12\x13\n\x0f\x45XCEPTION_FATAL\x10\x01\x12\x13\n\x0f\x45XCEPTION_ERROR\x10\x02\x12\x15\n\x11\x45XCEPTION_WARNING\x10\x03\x12\x14\n\x10\x45XCEPTION_NOTICE\x10\x04\x32\xe2\x1b\n\x07\x43oreApi\x12J\n\rCreateSession\x12\x1a.core.CreateSessionRequest\x1a\x1b.core.CreateSessionResponse\"\x00\x12J\n\rDeleteSession\x12\x1a.core.DeleteSessionRequest\x1a\x1b.core.DeleteSessionResponse\"\x00\x12\x44\n\x0bGetSessions\x12\x18.core.GetSessionsRequest\x1a\x19.core.GetSessionsResponse\"\x00\x12\x41\n\nGetSession\x12\x17.core.GetSessionRequest\x1a\x18.core.GetSessionResponse\"\x00\x12V\n\x11GetSessionOptions\x12\x1e.core.GetSessionOptionsRequest\x1a\x1f.core.GetSessionOptionsResponse\"\x00\x12V\n\x11SetSessionOptions\x12\x1e.core.SetSessionOptionsRequest\x1a\x1f.core.SetSessionOptionsResponse\"\x00\x12Y\n\x12GetSessionLocation\x12\x1f.core.GetSessionLocationRequest\x1a .core.GetSessionLocationResponse\"\x00\x12Y\n\x12SetSessionLocation\x12\x1f.core.SetSessionLocationRequest\x1a .core.SetSessionLocationResponse\"\x00\x12P\n\x0fSetSessionState\x12\x1c.core.SetSessionStateRequest\x1a\x1d.core.SetSessionStateResponse\"\x00\x12:\n\nNodeEvents\x12\x17.core.NodeEventsRequest\x1a\x0f.core.NodeEvent\"\x00\x30\x01\x12:\n\nLinkEvents\x12\x17.core.LinkEventsRequest\x1a\x0f.core.LinkEvent\"\x00\x30\x01\x12\x43\n\rSessionEvents\x12\x1a.core.SessionEventsRequest\x1a\x12.core.SessionEvent\"\x00\x30\x01\x12@\n\x0c\x43onfigEvents\x12\x19.core.ConfigEventsRequest\x1a\x11.core.ConfigEvent\"\x00\x30\x01\x12I\n\x0f\x45xceptionEvents\x12\x1c.core.ExceptionEventsRequest\x1a\x14.core.ExceptionEvent\"\x00\x30\x01\x12:\n\nFileEvents\x12\x17.core.FileEventsRequest\x1a\x0f.core.FileEvent\"\x00\x30\x01\x12\x38\n\x07\x41\x64\x64Node\x12\x14.core.AddNodeRequest\x1a\x15.core.AddNodeResponse\"\x00\x12\x38\n\x07GetNode\x12\x14.core.GetNodeRequest\x1a\x15.core.GetNodeResponse\"\x00\x12;\n\x08\x45\x64itNode\x12\x15.core.EditNodeRequest\x1a\x16.core.EditNodeResponse\"\x00\x12\x41\n\nDeleteNode\x12\x17.core.DeleteNodeRequest\x1a\x18.core.DeleteNodeResponse\"\x00\x12G\n\x0cGetNodeLinks\x12\x19.core.GetNodeLinksRequest\x1a\x1a.core.GetNodeLinksResponse\"\x00\x12\x38\n\x07\x41\x64\x64Link\x12\x14.core.AddLinkRequest\x1a\x15.core.AddLinkResponse\"\x00\x12;\n\x08\x45\x64itLink\x12\x15.core.EditLinkRequest\x1a\x16.core.EditLinkResponse\"\x00\x12\x41\n\nDeleteLink\x12\x17.core.DeleteLinkRequest\x1a\x18.core.DeleteLinkResponse\"\x00\x12;\n\x08GetHooks\x12\x15.core.GetHooksRequest\x1a\x16.core.GetHooksResponse\"\x00\x12\x38\n\x07\x41\x64\x64Hook\x12\x14.core.AddHookRequest\x1a\x15.core.AddHookResponse\"\x00\x12Y\n\x12GetMobilityConfigs\x12\x1f.core.GetMobilityConfigsRequest\x1a .core.GetMobilityConfigsResponse\"\x00\x12V\n\x11GetMobilityConfig\x12\x1e.core.GetMobilityConfigRequest\x1a\x1f.core.GetMobilityConfigResponse\"\x00\x12V\n\x11SetMobilityConfig\x12\x1e.core.SetMobilityConfigRequest\x1a\x1f.core.SetMobilityConfigResponse\"\x00\x12M\n\x0eMobilityAction\x12\x1b.core.MobilityActionRequest\x1a\x1c.core.MobilityActionResponse\"\x00\x12\x44\n\x0bGetServices\x12\x18.core.GetServicesRequest\x1a\x19.core.GetServicesResponse\"\x00\x12Y\n\x12GetServiceDefaults\x12\x1f.core.GetServiceDefaultsRequest\x1a .core.GetServiceDefaultsResponse\"\x00\x12Y\n\x12SetServiceDefaults\x12\x1f.core.SetServiceDefaultsRequest\x1a .core.SetServiceDefaultsResponse\"\x00\x12M\n\x0eGetNodeService\x12\x1b.core.GetNodeServiceRequest\x1a\x1c.core.GetNodeServiceResponse\"\x00\x12Y\n\x12GetNodeServiceFile\x12\x1f.core.GetNodeServiceFileRequest\x1a .core.GetNodeServiceFileResponse\"\x00\x12M\n\x0eSetNodeService\x12\x1b.core.SetNodeServiceRequest\x1a\x1c.core.SetNodeServiceResponse\"\x00\x12Y\n\x12SetNodeServiceFile\x12\x1f.core.SetNodeServiceFileRequest\x1a .core.SetNodeServiceFileResponse\"\x00\x12J\n\rServiceAction\x12\x1a.core.ServiceActionRequest\x1a\x1b.core.ServiceActionResponse\"\x00\x12J\n\rGetWlanConfig\x12\x1a.core.GetWlanConfigRequest\x1a\x1b.core.GetWlanConfigResponse\"\x00\x12J\n\rSetWlanConfig\x12\x1a.core.SetWlanConfigRequest\x1a\x1b.core.SetWlanConfigResponse\"\x00\x12M\n\x0eGetEmaneConfig\x12\x1b.core.GetEmaneConfigRequest\x1a\x1c.core.GetEmaneConfigResponse\"\x00\x12M\n\x0eSetEmaneConfig\x12\x1b.core.SetEmaneConfigRequest\x1a\x1c.core.SetEmaneConfigResponse\"\x00\x12M\n\x0eGetEmaneModels\x12\x1b.core.GetEmaneModelsRequest\x1a\x1c.core.GetEmaneModelsResponse\"\x00\x12\\\n\x13GetEmaneModelConfig\x12 .core.GetEmaneModelConfigRequest\x1a!.core.GetEmaneModelConfigResponse\"\x00\x12\\\n\x13SetEmaneModelConfig\x12 .core.SetEmaneModelConfigRequest\x1a!.core.SetEmaneModelConfigResponse\"\x00\x12_\n\x14GetEmaneModelConfigs\x12!.core.GetEmaneModelConfigsRequest\x1a\".core.GetEmaneModelConfigsResponse\"\x00\x12\x38\n\x07SaveXml\x12\x14.core.SaveXmlRequest\x1a\x15.core.SaveXmlResponse\"\x00\x12\x38\n\x07OpenXml\x12\x14.core.OpenXmlRequest\x1a\x15.core.OpenXmlResponse\"\x00\x62\x06proto3') +) + +_MESSAGETYPE = _descriptor.EnumDescriptor( + name='MessageType', + full_name='core.MessageType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='MESSAGE_NONE', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MESSAGE_ADD', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MESSAGE_DELETE', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MESSAGE_CRI', index=3, number=4, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MESSAGE_LOCAL', index=4, number=8, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MESSAGE_STRING', index=5, number=16, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MESSAGE_TEXT', index=6, number=32, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MESSAGE_TTY', index=7, number=64, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=8194, + serialized_end=8353, +) +_sym_db.RegisterEnumDescriptor(_MESSAGETYPE) + +MessageType = enum_type_wrapper.EnumTypeWrapper(_MESSAGETYPE) +_LINKTYPE = _descriptor.EnumDescriptor( + name='LinkType', + full_name='core.LinkType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='LINK_WIRELESS', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='LINK_WIRED', index=1, number=1, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=8355, + serialized_end=8400, +) +_sym_db.RegisterEnumDescriptor(_LINKTYPE) + +LinkType = enum_type_wrapper.EnumTypeWrapper(_LINKTYPE) +_SESSIONSTATE = _descriptor.EnumDescriptor( + name='SessionState', + full_name='core.SessionState', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='STATE_NONE', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STATE_DEFINITION', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STATE_CONFIGURATION', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STATE_INSTANTIATION', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STATE_RUNTIME', index=4, number=4, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STATE_DATACOLLECT', index=5, number=5, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='STATE_SHUTDOWN', index=6, number=6, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=8403, + serialized_end=8567, +) +_sym_db.RegisterEnumDescriptor(_SESSIONSTATE) + +SessionState = enum_type_wrapper.EnumTypeWrapper(_SESSIONSTATE) +_NODETYPE = _descriptor.EnumDescriptor( + name='NodeType', + full_name='core.NodeType', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='NODE_DEFAULT', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_PHYSICAL', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_TBD', index=2, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_SWITCH', index=3, number=4, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_HUB', index=4, number=5, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_WIRELESS_LAN', index=5, number=6, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_RJ45', index=6, number=7, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_TUNNEL', index=7, number=8, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_KTUNNEL', index=8, number=9, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_EMANE', index=9, number=10, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_TAP_BRIDGE', index=10, number=11, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_PEER_TO_PEER', index=11, number=12, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_CONTROL_NET', index=12, number=13, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='NODE_EMANE_NET', index=13, number=14, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=8570, + serialized_end=8837, +) +_sym_db.RegisterEnumDescriptor(_NODETYPE) + +NodeType = enum_type_wrapper.EnumTypeWrapper(_NODETYPE) +_SERVICEVALIDATIONMODE = _descriptor.EnumDescriptor( + name='ServiceValidationMode', + full_name='core.ServiceValidationMode', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='VALIDATION_BLOCKING', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='VALIDATION_NON_BLOCKING', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='VALIDATION_TIMER', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=8839, + serialized_end=8938, +) +_sym_db.RegisterEnumDescriptor(_SERVICEVALIDATIONMODE) + +ServiceValidationMode = enum_type_wrapper.EnumTypeWrapper(_SERVICEVALIDATIONMODE) +_SERVICEACTION = _descriptor.EnumDescriptor( + name='ServiceAction', + full_name='core.ServiceAction', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='SERVICE_START', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SERVICE_STOP', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SERVICE_RESTART', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='SERVICE_VALIDATE', index=3, number=3, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=8940, + serialized_end=9035, +) +_sym_db.RegisterEnumDescriptor(_SERVICEACTION) + +ServiceAction = enum_type_wrapper.EnumTypeWrapper(_SERVICEACTION) +_MOBILITYACTION = _descriptor.EnumDescriptor( + name='MobilityAction', + full_name='core.MobilityAction', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='MOBILITY_START', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MOBILITY_PAUSE', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='MOBILITY_STOP', index=2, number=2, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=9037, + serialized_end=9112, +) +_sym_db.RegisterEnumDescriptor(_MOBILITYACTION) + +MobilityAction = enum_type_wrapper.EnumTypeWrapper(_MOBILITYACTION) +_EXCEPTIONLEVEL = _descriptor.EnumDescriptor( + name='ExceptionLevel', + full_name='core.ExceptionLevel', + filename=None, + file=DESCRIPTOR, + values=[ + _descriptor.EnumValueDescriptor( + name='EXCEPTION_DEFAULT', index=0, number=0, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='EXCEPTION_FATAL', index=1, number=1, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='EXCEPTION_ERROR', index=2, number=2, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='EXCEPTION_WARNING', index=3, number=3, + serialized_options=None, + type=None), + _descriptor.EnumValueDescriptor( + name='EXCEPTION_NOTICE', index=4, number=4, + serialized_options=None, + type=None), + ], + containing_type=None, + serialized_options=None, + serialized_start=9114, + serialized_end=9240, +) +_sym_db.RegisterEnumDescriptor(_EXCEPTIONLEVEL) + +ExceptionLevel = enum_type_wrapper.EnumTypeWrapper(_EXCEPTIONLEVEL) +MESSAGE_NONE = 0 +MESSAGE_ADD = 1 +MESSAGE_DELETE = 2 +MESSAGE_CRI = 4 +MESSAGE_LOCAL = 8 +MESSAGE_STRING = 16 +MESSAGE_TEXT = 32 +MESSAGE_TTY = 64 +LINK_WIRELESS = 0 +LINK_WIRED = 1 +STATE_NONE = 0 +STATE_DEFINITION = 1 +STATE_CONFIGURATION = 2 +STATE_INSTANTIATION = 3 +STATE_RUNTIME = 4 +STATE_DATACOLLECT = 5 +STATE_SHUTDOWN = 6 +NODE_DEFAULT = 0 +NODE_PHYSICAL = 1 +NODE_TBD = 3 +NODE_SWITCH = 4 +NODE_HUB = 5 +NODE_WIRELESS_LAN = 6 +NODE_RJ45 = 7 +NODE_TUNNEL = 8 +NODE_KTUNNEL = 9 +NODE_EMANE = 10 +NODE_TAP_BRIDGE = 11 +NODE_PEER_TO_PEER = 12 +NODE_CONTROL_NET = 13 +NODE_EMANE_NET = 14 +VALIDATION_BLOCKING = 0 +VALIDATION_NON_BLOCKING = 1 +VALIDATION_TIMER = 2 +SERVICE_START = 0 +SERVICE_STOP = 1 +SERVICE_RESTART = 2 +SERVICE_VALIDATE = 3 +MOBILITY_START = 0 +MOBILITY_PAUSE = 1 +MOBILITY_STOP = 2 +EXCEPTION_DEFAULT = 0 +EXCEPTION_FATAL = 1 +EXCEPTION_ERROR = 2 +EXCEPTION_WARNING = 3 +EXCEPTION_NOTICE = 4 + + + +_CREATESESSIONREQUEST = _descriptor.Descriptor( + name='CreateSessionRequest', + full_name='core.CreateSessionRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.CreateSessionRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=20, + serialized_end=54, +) + + +_CREATESESSIONRESPONSE = _descriptor.Descriptor( + name='CreateSessionResponse', + full_name='core.CreateSessionResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.CreateSessionResponse.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='state', full_name='core.CreateSessionResponse.state', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=56, + serialized_end=126, +) + + +_DELETESESSIONREQUEST = _descriptor.Descriptor( + name='DeleteSessionRequest', + full_name='core.DeleteSessionRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.DeleteSessionRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=128, + serialized_end=162, +) + + +_DELETESESSIONRESPONSE = _descriptor.Descriptor( + name='DeleteSessionResponse', + full_name='core.DeleteSessionResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.DeleteSessionResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=164, + serialized_end=203, +) + + +_GETSESSIONSREQUEST = _descriptor.Descriptor( + name='GetSessionsRequest', + full_name='core.GetSessionsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=205, + serialized_end=225, +) + + +_GETSESSIONSRESPONSE = _descriptor.Descriptor( + name='GetSessionsResponse', + full_name='core.GetSessionsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='sessions', full_name='core.GetSessionsResponse.sessions', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=227, + serialized_end=288, +) + + +_GETSESSIONREQUEST = _descriptor.Descriptor( + name='GetSessionRequest', + full_name='core.GetSessionRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.GetSessionRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=290, + serialized_end=321, +) + + +_GETSESSIONRESPONSE = _descriptor.Descriptor( + name='GetSessionResponse', + full_name='core.GetSessionResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetSessionResponse.session', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=323, + serialized_end=375, +) + + +_GETSESSIONOPTIONSREQUEST = _descriptor.Descriptor( + name='GetSessionOptionsRequest', + full_name='core.GetSessionOptionsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.GetSessionOptionsRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=377, + serialized_end=415, +) + + +_GETSESSIONOPTIONSRESPONSE = _descriptor.Descriptor( + name='GetSessionOptionsResponse', + full_name='core.GetSessionOptionsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='groups', full_name='core.GetSessionOptionsResponse.groups', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=417, + serialized_end=479, +) + + +_SETSESSIONOPTIONSREQUEST_CONFIGENTRY = _descriptor.Descriptor( + name='ConfigEntry', + full_name='core.SetSessionOptionsRequest.ConfigEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='core.SetSessionOptionsRequest.ConfigEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='core.SetSessionOptionsRequest.ConfigEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=_b('8\001'), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=582, + serialized_end=627, +) + +_SETSESSIONOPTIONSREQUEST = _descriptor.Descriptor( + name='SetSessionOptionsRequest', + full_name='core.SetSessionOptionsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.SetSessionOptionsRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='config', full_name='core.SetSessionOptionsRequest.config', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_SETSESSIONOPTIONSREQUEST_CONFIGENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=482, + serialized_end=627, +) + + +_SETSESSIONOPTIONSRESPONSE = _descriptor.Descriptor( + name='SetSessionOptionsResponse', + full_name='core.SetSessionOptionsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetSessionOptionsResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=629, + serialized_end=672, +) + + +_GETSESSIONLOCATIONREQUEST = _descriptor.Descriptor( + name='GetSessionLocationRequest', + full_name='core.GetSessionLocationRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.GetSessionLocationRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=674, + serialized_end=713, +) + + +_GETSESSIONLOCATIONRESPONSE = _descriptor.Descriptor( + name='GetSessionLocationResponse', + full_name='core.GetSessionLocationResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='position', full_name='core.GetSessionLocationResponse.position', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='scale', full_name='core.GetSessionLocationResponse.scale', index=1, + number=2, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=715, + serialized_end=792, +) + + +_SETSESSIONLOCATIONREQUEST = _descriptor.Descriptor( + name='SetSessionLocationRequest', + full_name='core.SetSessionLocationRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.SetSessionLocationRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='position', full_name='core.SetSessionLocationRequest.position', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='scale', full_name='core.SetSessionLocationRequest.scale', index=2, + number=3, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=794, + serialized_end=882, +) + + +_SETSESSIONLOCATIONRESPONSE = _descriptor.Descriptor( + name='SetSessionLocationResponse', + full_name='core.SetSessionLocationResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetSessionLocationResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=884, + serialized_end=928, +) + + +_SETSESSIONSTATEREQUEST = _descriptor.Descriptor( + name='SetSessionStateRequest', + full_name='core.SetSessionStateRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.SetSessionStateRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='state', full_name='core.SetSessionStateRequest.state', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=930, + serialized_end=1001, +) + + +_SETSESSIONSTATERESPONSE = _descriptor.Descriptor( + name='SetSessionStateResponse', + full_name='core.SetSessionStateResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetSessionStateResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1003, + serialized_end=1044, +) + + +_NODEEVENTSREQUEST = _descriptor.Descriptor( + name='NodeEventsRequest', + full_name='core.NodeEventsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.NodeEventsRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1046, + serialized_end=1077, +) + + +_NODEEVENT = _descriptor.Descriptor( + name='NodeEvent', + full_name='core.NodeEvent', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='node', full_name='core.NodeEvent.node', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1079, + serialized_end=1116, +) + + +_LINKEVENTSREQUEST = _descriptor.Descriptor( + name='LinkEventsRequest', + full_name='core.LinkEventsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.LinkEventsRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1118, + serialized_end=1149, +) + + +_LINKEVENT = _descriptor.Descriptor( + name='LinkEvent', + full_name='core.LinkEvent', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='message_type', full_name='core.LinkEvent.message_type', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='link', full_name='core.LinkEvent.link', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1151, + serialized_end=1229, +) + + +_SESSIONEVENTSREQUEST = _descriptor.Descriptor( + name='SessionEventsRequest', + full_name='core.SessionEventsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.SessionEventsRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1231, + serialized_end=1265, +) + + +_SESSIONEVENT = _descriptor.Descriptor( + name='SessionEvent', + full_name='core.SessionEvent', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='node', full_name='core.SessionEvent.node', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='event', full_name='core.SessionEvent.event', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='core.SessionEvent.name', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data', full_name='core.SessionEvent.data', index=3, + number=4, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='time', full_name='core.SessionEvent.time', index=4, + number=5, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='session', full_name='core.SessionEvent.session', index=5, + number=6, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1267, + serialized_end=1369, +) + + +_CONFIGEVENTSREQUEST = _descriptor.Descriptor( + name='ConfigEventsRequest', + full_name='core.ConfigEventsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.ConfigEventsRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1371, + serialized_end=1404, +) + + +_CONFIGEVENT = _descriptor.Descriptor( + name='ConfigEvent', + full_name='core.ConfigEvent', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='message_type', full_name='core.ConfigEvent.message_type', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='node', full_name='core.ConfigEvent.node', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='object', full_name='core.ConfigEvent.object', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='core.ConfigEvent.type', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data_types', full_name='core.ConfigEvent.data_types', index=4, + number=5, type=5, cpp_type=1, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data_values', full_name='core.ConfigEvent.data_values', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='captions', full_name='core.ConfigEvent.captions', index=6, + number=7, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bitmap', full_name='core.ConfigEvent.bitmap', index=7, + number=8, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='possible_values', full_name='core.ConfigEvent.possible_values', index=8, + number=9, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='groups', full_name='core.ConfigEvent.groups', index=9, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='session', full_name='core.ConfigEvent.session', index=10, + number=11, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface', full_name='core.ConfigEvent.interface', index=11, + number=12, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='network_id', full_name='core.ConfigEvent.network_id', index=12, + number=13, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='opaque', full_name='core.ConfigEvent.opaque', index=13, + number=14, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1407, + serialized_end=1693, +) + + +_EXCEPTIONEVENTSREQUEST = _descriptor.Descriptor( + name='ExceptionEventsRequest', + full_name='core.ExceptionEventsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.ExceptionEventsRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1695, + serialized_end=1731, +) + + +_EXCEPTIONEVENT = _descriptor.Descriptor( + name='ExceptionEvent', + full_name='core.ExceptionEvent', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='node', full_name='core.ExceptionEvent.node', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='session', full_name='core.ExceptionEvent.session', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='level', full_name='core.ExceptionEvent.level', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source', full_name='core.ExceptionEvent.source', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='date', full_name='core.ExceptionEvent.date', index=4, + number=5, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='text', full_name='core.ExceptionEvent.text', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='opaque', full_name='core.ExceptionEvent.opaque', index=6, + number=7, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1734, + serialized_end=1878, +) + + +_FILEEVENTSREQUEST = _descriptor.Descriptor( + name='FileEventsRequest', + full_name='core.FileEventsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.FileEventsRequest.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1880, + serialized_end=1911, +) + + +_FILEEVENT = _descriptor.Descriptor( + name='FileEvent', + full_name='core.FileEvent', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='message_type', full_name='core.FileEvent.message_type', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='node', full_name='core.FileEvent.node', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='core.FileEvent.name', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='mode', full_name='core.FileEvent.mode', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='number', full_name='core.FileEvent.number', index=4, + number=5, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='core.FileEvent.type', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='source', full_name='core.FileEvent.source', index=6, + number=7, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='session', full_name='core.FileEvent.session', index=7, + number=8, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data', full_name='core.FileEvent.data', index=8, + number=9, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='compressed_data', full_name='core.FileEvent.compressed_data', index=9, + number=10, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=1914, + serialized_end=2110, +) + + +_ADDNODEREQUEST = _descriptor.Descriptor( + name='AddNodeRequest', + full_name='core.AddNodeRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.AddNodeRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='node', full_name='core.AddNodeRequest.node', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2112, + serialized_end=2171, +) + + +_ADDNODERESPONSE = _descriptor.Descriptor( + name='AddNodeResponse', + full_name='core.AddNodeResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.AddNodeResponse.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2173, + serialized_end=2202, +) + + +_GETNODEREQUEST = _descriptor.Descriptor( + name='GetNodeRequest', + full_name='core.GetNodeRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetNodeRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.GetNodeRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2204, + serialized_end=2249, +) + + +_GETNODERESPONSE = _descriptor.Descriptor( + name='GetNodeResponse', + full_name='core.GetNodeResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='node', full_name='core.GetNodeResponse.node', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interfaces', full_name='core.GetNodeResponse.interfaces', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2251, + serialized_end=2331, +) + + +_EDITNODEREQUEST = _descriptor.Descriptor( + name='EditNodeRequest', + full_name='core.EditNodeRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.EditNodeRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.EditNodeRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='position', full_name='core.EditNodeRequest.position', index=2, + number=3, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2333, + serialized_end=2413, +) + + +_EDITNODERESPONSE = _descriptor.Descriptor( + name='EditNodeResponse', + full_name='core.EditNodeResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.EditNodeResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2415, + serialized_end=2449, +) + + +_DELETENODEREQUEST = _descriptor.Descriptor( + name='DeleteNodeRequest', + full_name='core.DeleteNodeRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.DeleteNodeRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.DeleteNodeRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2451, + serialized_end=2499, +) + + +_DELETENODERESPONSE = _descriptor.Descriptor( + name='DeleteNodeResponse', + full_name='core.DeleteNodeResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.DeleteNodeResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2501, + serialized_end=2537, +) + + +_GETNODELINKSREQUEST = _descriptor.Descriptor( + name='GetNodeLinksRequest', + full_name='core.GetNodeLinksRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetNodeLinksRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.GetNodeLinksRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2539, + serialized_end=2589, +) + + +_GETNODELINKSRESPONSE = _descriptor.Descriptor( + name='GetNodeLinksResponse', + full_name='core.GetNodeLinksResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='links', full_name='core.GetNodeLinksResponse.links', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2591, + serialized_end=2640, +) + + +_ADDLINKREQUEST = _descriptor.Descriptor( + name='AddLinkRequest', + full_name='core.AddLinkRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.AddLinkRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='link', full_name='core.AddLinkRequest.link', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2642, + serialized_end=2701, +) + + +_ADDLINKRESPONSE = _descriptor.Descriptor( + name='AddLinkResponse', + full_name='core.AddLinkResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.AddLinkResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2703, + serialized_end=2736, +) + + +_EDITLINKREQUEST = _descriptor.Descriptor( + name='EditLinkRequest', + full_name='core.EditLinkRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.EditLinkRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='node_one', full_name='core.EditLinkRequest.node_one', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='node_two', full_name='core.EditLinkRequest.node_two', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface_one', full_name='core.EditLinkRequest.interface_one', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface_two', full_name='core.EditLinkRequest.interface_two', index=4, + number=5, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='options', full_name='core.EditLinkRequest.options', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2739, + serialized_end=2891, +) + + +_EDITLINKRESPONSE = _descriptor.Descriptor( + name='EditLinkResponse', + full_name='core.EditLinkResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.EditLinkResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2893, + serialized_end=2927, +) + + +_DELETELINKREQUEST = _descriptor.Descriptor( + name='DeleteLinkRequest', + full_name='core.DeleteLinkRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.DeleteLinkRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='node_one', full_name='core.DeleteLinkRequest.node_one', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='node_two', full_name='core.DeleteLinkRequest.node_two', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface_one', full_name='core.DeleteLinkRequest.interface_one', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface_two', full_name='core.DeleteLinkRequest.interface_two', index=4, + number=5, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=2929, + serialized_end=3047, +) + + +_DELETELINKRESPONSE = _descriptor.Descriptor( + name='DeleteLinkResponse', + full_name='core.DeleteLinkResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.DeleteLinkResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3049, + serialized_end=3085, +) + + +_GETHOOKSREQUEST = _descriptor.Descriptor( + name='GetHooksRequest', + full_name='core.GetHooksRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetHooksRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3087, + serialized_end=3121, +) + + +_GETHOOKSRESPONSE = _descriptor.Descriptor( + name='GetHooksResponse', + full_name='core.GetHooksResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='hooks', full_name='core.GetHooksResponse.hooks', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3123, + serialized_end=3168, +) + + +_ADDHOOKREQUEST = _descriptor.Descriptor( + name='AddHookRequest', + full_name='core.AddHookRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.AddHookRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='hook', full_name='core.AddHookRequest.hook', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3170, + serialized_end=3229, +) + + +_ADDHOOKRESPONSE = _descriptor.Descriptor( + name='AddHookResponse', + full_name='core.AddHookResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.AddHookResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3231, + serialized_end=3264, +) + + +_GETMOBILITYCONFIGSREQUEST = _descriptor.Descriptor( + name='GetMobilityConfigsRequest', + full_name='core.GetMobilityConfigsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetMobilityConfigsRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3266, + serialized_end=3310, +) + + +_GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG = _descriptor.Descriptor( + name='MobilityConfig', + full_name='core.GetMobilityConfigsResponse.MobilityConfig', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='groups', full_name='core.GetMobilityConfigsResponse.MobilityConfig.groups', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3407, + serialized_end=3458, +) + +_GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY = _descriptor.Descriptor( + name='ConfigsEntry', + full_name='core.GetMobilityConfigsResponse.ConfigsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='core.GetMobilityConfigsResponse.ConfigsEntry.key', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='core.GetMobilityConfigsResponse.ConfigsEntry.value', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=_b('8\001'), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3460, + serialized_end=3555, +) + +_GETMOBILITYCONFIGSRESPONSE = _descriptor.Descriptor( + name='GetMobilityConfigsResponse', + full_name='core.GetMobilityConfigsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='configs', full_name='core.GetMobilityConfigsResponse.configs', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG, _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3313, + serialized_end=3555, +) + + +_GETMOBILITYCONFIGREQUEST = _descriptor.Descriptor( + name='GetMobilityConfigRequest', + full_name='core.GetMobilityConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetMobilityConfigRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.GetMobilityConfigRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3557, + serialized_end=3612, +) + + +_GETMOBILITYCONFIGRESPONSE = _descriptor.Descriptor( + name='GetMobilityConfigResponse', + full_name='core.GetMobilityConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='groups', full_name='core.GetMobilityConfigResponse.groups', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3614, + serialized_end=3676, +) + + +_SETMOBILITYCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( + name='ConfigEntry', + full_name='core.SetMobilityConfigRequest.ConfigEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='core.SetMobilityConfigRequest.ConfigEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='core.SetMobilityConfigRequest.ConfigEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=_b('8\001'), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=582, + serialized_end=627, +) + +_SETMOBILITYCONFIGREQUEST = _descriptor.Descriptor( + name='SetMobilityConfigRequest', + full_name='core.SetMobilityConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.SetMobilityConfigRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.SetMobilityConfigRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='config', full_name='core.SetMobilityConfigRequest.config', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_SETMOBILITYCONFIGREQUEST_CONFIGENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3679, + serialized_end=3841, +) + + +_SETMOBILITYCONFIGRESPONSE = _descriptor.Descriptor( + name='SetMobilityConfigResponse', + full_name='core.SetMobilityConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetMobilityConfigResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3843, + serialized_end=3886, +) + + +_MOBILITYACTIONREQUEST = _descriptor.Descriptor( + name='MobilityActionRequest', + full_name='core.MobilityActionRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.MobilityActionRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.MobilityActionRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='action', full_name='core.MobilityActionRequest.action', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3888, + serialized_end=3978, +) + + +_MOBILITYACTIONRESPONSE = _descriptor.Descriptor( + name='MobilityActionResponse', + full_name='core.MobilityActionResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.MobilityActionResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=3980, + serialized_end=4020, +) + + +_GETSERVICESREQUEST = _descriptor.Descriptor( + name='GetServicesRequest', + full_name='core.GetServicesRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4022, + serialized_end=4042, +) + + +_GETSERVICESRESPONSE = _descriptor.Descriptor( + name='GetServicesResponse', + full_name='core.GetServicesResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='services', full_name='core.GetServicesResponse.services', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4044, + serialized_end=4098, +) + + +_GETSERVICEDEFAULTSREQUEST = _descriptor.Descriptor( + name='GetServiceDefaultsRequest', + full_name='core.GetServiceDefaultsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetServiceDefaultsRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4100, + serialized_end=4144, +) + + +_GETSERVICEDEFAULTSRESPONSE = _descriptor.Descriptor( + name='GetServiceDefaultsResponse', + full_name='core.GetServiceDefaultsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='defaults', full_name='core.GetServiceDefaultsResponse.defaults', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4146, + serialized_end=4215, +) + + +_SETSERVICEDEFAULTSREQUEST = _descriptor.Descriptor( + name='SetServiceDefaultsRequest', + full_name='core.SetServiceDefaultsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.SetServiceDefaultsRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='defaults', full_name='core.SetServiceDefaultsRequest.defaults', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4217, + serialized_end=4302, +) + + +_SETSERVICEDEFAULTSRESPONSE = _descriptor.Descriptor( + name='SetServiceDefaultsResponse', + full_name='core.SetServiceDefaultsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetServiceDefaultsResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4304, + serialized_end=4348, +) + + +_GETNODESERVICEREQUEST = _descriptor.Descriptor( + name='GetNodeServiceRequest', + full_name='core.GetNodeServiceRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetNodeServiceRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.GetNodeServiceRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='service', full_name='core.GetNodeServiceRequest.service', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4350, + serialized_end=4419, +) + + +_GETNODESERVICERESPONSE = _descriptor.Descriptor( + name='GetNodeServiceResponse', + full_name='core.GetNodeServiceResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='service', full_name='core.GetNodeServiceResponse.service', index=0, + number=1, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4421, + serialized_end=4485, +) + + +_GETNODESERVICEFILEREQUEST = _descriptor.Descriptor( + name='GetNodeServiceFileRequest', + full_name='core.GetNodeServiceFileRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetNodeServiceFileRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.GetNodeServiceFileRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='service', full_name='core.GetNodeServiceFileRequest.service', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='file', full_name='core.GetNodeServiceFileRequest.file', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4487, + serialized_end=4574, +) + + +_GETNODESERVICEFILERESPONSE = _descriptor.Descriptor( + name='GetNodeServiceFileResponse', + full_name='core.GetNodeServiceFileResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='data', full_name='core.GetNodeServiceFileResponse.data', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4576, + serialized_end=4618, +) + + +_SETNODESERVICEREQUEST = _descriptor.Descriptor( + name='SetNodeServiceRequest', + full_name='core.SetNodeServiceRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.SetNodeServiceRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.SetNodeServiceRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='service', full_name='core.SetNodeServiceRequest.service', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='startup', full_name='core.SetNodeServiceRequest.startup', index=3, + number=4, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='validate', full_name='core.SetNodeServiceRequest.validate', index=4, + number=5, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='shutdown', full_name='core.SetNodeServiceRequest.shutdown', index=5, + number=6, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4620, + serialized_end=4742, +) + + +_SETNODESERVICERESPONSE = _descriptor.Descriptor( + name='SetNodeServiceResponse', + full_name='core.SetNodeServiceResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetNodeServiceResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4744, + serialized_end=4784, +) + + +_SETNODESERVICEFILEREQUEST = _descriptor.Descriptor( + name='SetNodeServiceFileRequest', + full_name='core.SetNodeServiceFileRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.SetNodeServiceFileRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.SetNodeServiceFileRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='service', full_name='core.SetNodeServiceFileRequest.service', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='file', full_name='core.SetNodeServiceFileRequest.file', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data', full_name='core.SetNodeServiceFileRequest.data', index=4, + number=5, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4786, + serialized_end=4887, +) + + +_SETNODESERVICEFILERESPONSE = _descriptor.Descriptor( + name='SetNodeServiceFileResponse', + full_name='core.SetNodeServiceFileResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetNodeServiceFileResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4889, + serialized_end=4933, +) + + +_SERVICEACTIONREQUEST = _descriptor.Descriptor( + name='ServiceActionRequest', + full_name='core.ServiceActionRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.ServiceActionRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.ServiceActionRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='service', full_name='core.ServiceActionRequest.service', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='action', full_name='core.ServiceActionRequest.action', index=3, + number=4, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=4935, + serialized_end=5040, +) + + +_SERVICEACTIONRESPONSE = _descriptor.Descriptor( + name='ServiceActionResponse', + full_name='core.ServiceActionResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.ServiceActionResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5042, + serialized_end=5081, +) + + +_GETWLANCONFIGREQUEST = _descriptor.Descriptor( + name='GetWlanConfigRequest', + full_name='core.GetWlanConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetWlanConfigRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.GetWlanConfigRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5083, + serialized_end=5134, +) + + +_GETWLANCONFIGRESPONSE = _descriptor.Descriptor( + name='GetWlanConfigResponse', + full_name='core.GetWlanConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='groups', full_name='core.GetWlanConfigResponse.groups', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5136, + serialized_end=5194, +) + + +_SETWLANCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( + name='ConfigEntry', + full_name='core.SetWlanConfigRequest.ConfigEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='core.SetWlanConfigRequest.ConfigEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='core.SetWlanConfigRequest.ConfigEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=_b('8\001'), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=582, + serialized_end=627, +) + +_SETWLANCONFIGREQUEST = _descriptor.Descriptor( + name='SetWlanConfigRequest', + full_name='core.SetWlanConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.SetWlanConfigRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.SetWlanConfigRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='config', full_name='core.SetWlanConfigRequest.config', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_SETWLANCONFIGREQUEST_CONFIGENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5197, + serialized_end=5351, +) + + +_SETWLANCONFIGRESPONSE = _descriptor.Descriptor( + name='SetWlanConfigResponse', + full_name='core.SetWlanConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetWlanConfigResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5353, + serialized_end=5392, +) + + +_GETEMANECONFIGREQUEST = _descriptor.Descriptor( + name='GetEmaneConfigRequest', + full_name='core.GetEmaneConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetEmaneConfigRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5394, + serialized_end=5434, +) + + +_GETEMANECONFIGRESPONSE = _descriptor.Descriptor( + name='GetEmaneConfigResponse', + full_name='core.GetEmaneConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='groups', full_name='core.GetEmaneConfigResponse.groups', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5436, + serialized_end=5495, +) + + +_SETEMANECONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( + name='ConfigEntry', + full_name='core.SetEmaneConfigRequest.ConfigEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='core.SetEmaneConfigRequest.ConfigEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='core.SetEmaneConfigRequest.ConfigEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=_b('8\001'), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=582, + serialized_end=627, +) + +_SETEMANECONFIGREQUEST = _descriptor.Descriptor( + name='SetEmaneConfigRequest', + full_name='core.SetEmaneConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.SetEmaneConfigRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='config', full_name='core.SetEmaneConfigRequest.config', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_SETEMANECONFIGREQUEST_CONFIGENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5498, + serialized_end=5642, +) + + +_SETEMANECONFIGRESPONSE = _descriptor.Descriptor( + name='SetEmaneConfigResponse', + full_name='core.SetEmaneConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetEmaneConfigResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5644, + serialized_end=5684, +) + + +_GETEMANEMODELSREQUEST = _descriptor.Descriptor( + name='GetEmaneModelsRequest', + full_name='core.GetEmaneModelsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetEmaneModelsRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5686, + serialized_end=5726, +) + + +_GETEMANEMODELSRESPONSE = _descriptor.Descriptor( + name='GetEmaneModelsResponse', + full_name='core.GetEmaneModelsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='models', full_name='core.GetEmaneModelsResponse.models', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5728, + serialized_end=5768, +) + + +_GETEMANEMODELCONFIGREQUEST = _descriptor.Descriptor( + name='GetEmaneModelConfigRequest', + full_name='core.GetEmaneModelConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetEmaneModelConfigRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.GetEmaneModelConfigRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface', full_name='core.GetEmaneModelConfigRequest.interface', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='model', full_name='core.GetEmaneModelConfigRequest.model', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5770, + serialized_end=5861, +) + + +_GETEMANEMODELCONFIGRESPONSE = _descriptor.Descriptor( + name='GetEmaneModelConfigResponse', + full_name='core.GetEmaneModelConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='groups', full_name='core.GetEmaneModelConfigResponse.groups', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5863, + serialized_end=5927, +) + + +_SETEMANEMODELCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( + name='ConfigEntry', + full_name='core.SetEmaneModelConfigRequest.ConfigEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='core.SetEmaneModelConfigRequest.ConfigEntry.key', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='core.SetEmaneModelConfigRequest.ConfigEntry.value', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=_b('8\001'), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=582, + serialized_end=627, +) + +_SETEMANEMODELCONFIGREQUEST = _descriptor.Descriptor( + name='SetEmaneModelConfigRequest', + full_name='core.SetEmaneModelConfigRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.SetEmaneModelConfigRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='id', full_name='core.SetEmaneModelConfigRequest.id', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface', full_name='core.SetEmaneModelConfigRequest.interface', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='model', full_name='core.SetEmaneModelConfigRequest.model', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='config', full_name='core.SetEmaneModelConfigRequest.config', index=4, + number=5, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_SETEMANEMODELCONFIGREQUEST_CONFIGENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=5930, + serialized_end=6130, +) + + +_SETEMANEMODELCONFIGRESPONSE = _descriptor.Descriptor( + name='SetEmaneModelConfigResponse', + full_name='core.SetEmaneModelConfigResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.SetEmaneModelConfigResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6132, + serialized_end=6177, +) + + +_GETEMANEMODELCONFIGSREQUEST = _descriptor.Descriptor( + name='GetEmaneModelConfigsRequest', + full_name='core.GetEmaneModelConfigsRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.GetEmaneModelConfigsRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6179, + serialized_end=6225, +) + + +_GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG = _descriptor.Descriptor( + name='ModelConfig', + full_name='core.GetEmaneModelConfigsResponse.ModelConfig', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='model', full_name='core.GetEmaneModelConfigsResponse.ModelConfig.model', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='groups', full_name='core.GetEmaneModelConfigsResponse.ModelConfig.groups', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6326, + serialized_end=6389, +) + +_GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY = _descriptor.Descriptor( + name='ConfigsEntry', + full_name='core.GetEmaneModelConfigsResponse.ConfigsEntry', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='key', full_name='core.GetEmaneModelConfigsResponse.ConfigsEntry.key', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='core.GetEmaneModelConfigsResponse.ConfigsEntry.value', index=1, + number=2, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=_b('8\001'), + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6391, + serialized_end=6485, +) + +_GETEMANEMODELCONFIGSRESPONSE = _descriptor.Descriptor( + name='GetEmaneModelConfigsResponse', + full_name='core.GetEmaneModelConfigsResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='configs', full_name='core.GetEmaneModelConfigsResponse.configs', index=0, + number=1, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[_GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG, _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY, ], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6228, + serialized_end=6485, +) + + +_SAVEXMLREQUEST = _descriptor.Descriptor( + name='SaveXmlRequest', + full_name='core.SaveXmlRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='session', full_name='core.SaveXmlRequest.session', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6487, + serialized_end=6520, +) + + +_SAVEXMLRESPONSE = _descriptor.Descriptor( + name='SaveXmlResponse', + full_name='core.SaveXmlResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='data', full_name='core.SaveXmlResponse.data', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6522, + serialized_end=6553, +) + + +_OPENXMLREQUEST = _descriptor.Descriptor( + name='OpenXmlRequest', + full_name='core.OpenXmlRequest', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='data', full_name='core.OpenXmlRequest.data', index=0, + number=1, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6555, + serialized_end=6585, +) + + +_OPENXMLRESPONSE = _descriptor.Descriptor( + name='OpenXmlResponse', + full_name='core.OpenXmlResponse', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='result', full_name='core.OpenXmlResponse.result', index=0, + number=1, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='session', full_name='core.OpenXmlResponse.session', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6587, + serialized_end=6637, +) + + +_HOOK = _descriptor.Descriptor( + name='Hook', + full_name='core.Hook', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='state', full_name='core.Hook.state', index=0, + number=1, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='file', full_name='core.Hook.file', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='data', full_name='core.Hook.data', index=2, + number=3, type=12, cpp_type=9, label=1, + has_default_value=False, default_value=_b(""), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6639, + serialized_end=6708, +) + + +_SERVICEDEFAULTS = _descriptor.Descriptor( + name='ServiceDefaults', + full_name='core.ServiceDefaults', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='node_type', full_name='core.ServiceDefaults.node_type', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='services', full_name='core.ServiceDefaults.services', index=1, + number=2, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6710, + serialized_end=6764, +) + + +_SERVICE = _descriptor.Descriptor( + name='Service', + full_name='core.Service', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='group', full_name='core.Service.group', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='core.Service.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6766, + serialized_end=6804, +) + + +_NODESERVICEDATA = _descriptor.Descriptor( + name='NodeServiceData', + full_name='core.NodeServiceData', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='executables', full_name='core.NodeServiceData.executables', index=0, + number=1, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='dependencies', full_name='core.NodeServiceData.dependencies', index=1, + number=2, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='dirs', full_name='core.NodeServiceData.dirs', index=2, + number=3, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='configs', full_name='core.NodeServiceData.configs', index=3, + number=4, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='startup', full_name='core.NodeServiceData.startup', index=4, + number=5, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='validate', full_name='core.NodeServiceData.validate', index=5, + number=6, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='validation_mode', full_name='core.NodeServiceData.validation_mode', index=6, + number=7, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='validation_timer', full_name='core.NodeServiceData.validation_timer', index=7, + number=8, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='shutdown', full_name='core.NodeServiceData.shutdown', index=8, + number=9, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='meta', full_name='core.NodeServiceData.meta', index=9, + number=10, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=6807, + serialized_end=7045, +) + + +_CONFIGGROUP = _descriptor.Descriptor( + name='ConfigGroup', + full_name='core.ConfigGroup', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='name', full_name='core.ConfigGroup.name', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='options', full_name='core.ConfigGroup.options', index=1, + number=2, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7047, + serialized_end=7111, +) + + +_CONFIGOPTION = _descriptor.Descriptor( + name='ConfigOption', + full_name='core.ConfigOption', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='label', full_name='core.ConfigOption.label', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='core.ConfigOption.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='value', full_name='core.ConfigOption.value', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='core.ConfigOption.type', index=3, + number=4, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='select', full_name='core.ConfigOption.select', index=4, + number=5, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7113, + serialized_end=7201, +) + + +_SESSION = _descriptor.Descriptor( + name='Session', + full_name='core.Session', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.Session.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='state', full_name='core.Session.state', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='nodes', full_name='core.Session.nodes', index=2, + number=3, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='links', full_name='core.Session.links', index=3, + number=4, type=11, cpp_type=10, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7203, + serialized_end=7313, +) + + +_SESSIONSUMMARY = _descriptor.Descriptor( + name='SessionSummary', + full_name='core.SessionSummary', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.SessionSummary.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='state', full_name='core.SessionSummary.state', index=1, + number=2, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='nodes', full_name='core.SessionSummary.nodes', index=2, + number=3, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7315, + serialized_end=7393, +) + + +_NODE = _descriptor.Descriptor( + name='Node', + full_name='core.Node', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.Node.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='core.Node.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='core.Node.type', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='model', full_name='core.Node.model', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='position', full_name='core.Node.position', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='services', full_name='core.Node.services', index=5, + number=6, type=9, cpp_type=9, label=3, + has_default_value=False, default_value=[], + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='emane', full_name='core.Node.emane', index=6, + number=7, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='icon', full_name='core.Node.icon', index=7, + number=8, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='opaque', full_name='core.Node.opaque', index=8, + number=9, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7396, + serialized_end=7570, +) + + +_LINK = _descriptor.Descriptor( + name='Link', + full_name='core.Link', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='node_one', full_name='core.Link.node_one', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='node_two', full_name='core.Link.node_two', index=1, + number=2, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='type', full_name='core.Link.type', index=2, + number=3, type=14, cpp_type=8, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface_one', full_name='core.Link.interface_one', index=3, + number=4, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='interface_two', full_name='core.Link.interface_two', index=4, + number=5, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='options', full_name='core.Link.options', index=5, + number=6, type=11, cpp_type=10, label=1, + has_default_value=False, default_value=None, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7573, + serialized_end=7761, +) + + +_LINKOPTIONS = _descriptor.Descriptor( + name='LinkOptions', + full_name='core.LinkOptions', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='opaque', full_name='core.LinkOptions.opaque', index=0, + number=1, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='jitter', full_name='core.LinkOptions.jitter', index=1, + number=2, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='key', full_name='core.LinkOptions.key', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='mburst', full_name='core.LinkOptions.mburst', index=3, + number=4, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='mer', full_name='core.LinkOptions.mer', index=4, + number=5, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='per', full_name='core.LinkOptions.per', index=5, + number=6, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='bandwidth', full_name='core.LinkOptions.bandwidth', index=6, + number=7, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='burst', full_name='core.LinkOptions.burst', index=7, + number=8, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='delay', full_name='core.LinkOptions.delay', index=8, + number=9, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='dup', full_name='core.LinkOptions.dup', index=9, + number=10, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='unidirectional', full_name='core.LinkOptions.unidirectional', index=10, + number=11, type=8, cpp_type=7, label=1, + has_default_value=False, default_value=False, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7764, + serialized_end=7950, +) + + +_INTERFACE = _descriptor.Descriptor( + name='Interface', + full_name='core.Interface', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='id', full_name='core.Interface.id', index=0, + number=1, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='name', full_name='core.Interface.name', index=1, + number=2, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='mac', full_name='core.Interface.mac', index=2, + number=3, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ip4', full_name='core.Interface.ip4', index=3, + number=4, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ip4mask', full_name='core.Interface.ip4mask', index=4, + number=5, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ip6', full_name='core.Interface.ip6', index=5, + number=6, type=9, cpp_type=9, label=1, + has_default_value=False, default_value=_b("").decode('utf-8'), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='ip6mask', full_name='core.Interface.ip6mask', index=6, + number=7, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='netid', full_name='core.Interface.netid', index=7, + number=8, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='flowid', full_name='core.Interface.flowid', index=8, + number=9, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='mtu', full_name='core.Interface.mtu', index=9, + number=10, type=5, cpp_type=1, label=1, + has_default_value=False, default_value=0, + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=7953, + serialized_end=8107, +) + + +_POSITION = _descriptor.Descriptor( + name='Position', + full_name='core.Position', + filename=None, + file=DESCRIPTOR, + containing_type=None, + fields=[ + _descriptor.FieldDescriptor( + name='x', full_name='core.Position.x', index=0, + number=1, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='y', full_name='core.Position.y', index=1, + number=2, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='z', full_name='core.Position.z', index=2, + number=3, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='lat', full_name='core.Position.lat', index=3, + number=4, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='lon', full_name='core.Position.lon', index=4, + number=5, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + _descriptor.FieldDescriptor( + name='alt', full_name='core.Position.alt', index=5, + number=6, type=2, cpp_type=6, label=1, + has_default_value=False, default_value=float(0), + message_type=None, enum_type=None, containing_type=None, + is_extension=False, extension_scope=None, + serialized_options=None, file=DESCRIPTOR), + ], + extensions=[ + ], + nested_types=[], + enum_types=[ + ], + serialized_options=None, + is_extendable=False, + syntax='proto3', + extension_ranges=[], + oneofs=[ + ], + serialized_start=8109, + serialized_end=8191, +) + +_CREATESESSIONRESPONSE.fields_by_name['state'].enum_type = _SESSIONSTATE +_GETSESSIONSRESPONSE.fields_by_name['sessions'].message_type = _SESSIONSUMMARY +_GETSESSIONRESPONSE.fields_by_name['session'].message_type = _SESSION +_GETSESSIONOPTIONSRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP +_SETSESSIONOPTIONSREQUEST_CONFIGENTRY.containing_type = _SETSESSIONOPTIONSREQUEST +_SETSESSIONOPTIONSREQUEST.fields_by_name['config'].message_type = _SETSESSIONOPTIONSREQUEST_CONFIGENTRY +_GETSESSIONLOCATIONRESPONSE.fields_by_name['position'].message_type = _POSITION +_SETSESSIONLOCATIONREQUEST.fields_by_name['position'].message_type = _POSITION +_SETSESSIONSTATEREQUEST.fields_by_name['state'].enum_type = _SESSIONSTATE +_NODEEVENT.fields_by_name['node'].message_type = _NODE +_LINKEVENT.fields_by_name['message_type'].enum_type = _MESSAGETYPE +_LINKEVENT.fields_by_name['link'].message_type = _LINK +_CONFIGEVENT.fields_by_name['message_type'].enum_type = _MESSAGETYPE +_EXCEPTIONEVENT.fields_by_name['level'].enum_type = _EXCEPTIONLEVEL +_FILEEVENT.fields_by_name['message_type'].enum_type = _MESSAGETYPE +_ADDNODEREQUEST.fields_by_name['node'].message_type = _NODE +_GETNODERESPONSE.fields_by_name['node'].message_type = _NODE +_GETNODERESPONSE.fields_by_name['interfaces'].message_type = _INTERFACE +_EDITNODEREQUEST.fields_by_name['position'].message_type = _POSITION +_GETNODELINKSRESPONSE.fields_by_name['links'].message_type = _LINK +_ADDLINKREQUEST.fields_by_name['link'].message_type = _LINK +_EDITLINKREQUEST.fields_by_name['options'].message_type = _LINKOPTIONS +_GETHOOKSRESPONSE.fields_by_name['hooks'].message_type = _HOOK +_ADDHOOKREQUEST.fields_by_name['hook'].message_type = _HOOK +_GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG.fields_by_name['groups'].message_type = _CONFIGGROUP +_GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG.containing_type = _GETMOBILITYCONFIGSRESPONSE +_GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY.fields_by_name['value'].message_type = _GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG +_GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY.containing_type = _GETMOBILITYCONFIGSRESPONSE +_GETMOBILITYCONFIGSRESPONSE.fields_by_name['configs'].message_type = _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY +_GETMOBILITYCONFIGRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP +_SETMOBILITYCONFIGREQUEST_CONFIGENTRY.containing_type = _SETMOBILITYCONFIGREQUEST +_SETMOBILITYCONFIGREQUEST.fields_by_name['config'].message_type = _SETMOBILITYCONFIGREQUEST_CONFIGENTRY +_MOBILITYACTIONREQUEST.fields_by_name['action'].enum_type = _MOBILITYACTION +_GETSERVICESRESPONSE.fields_by_name['services'].message_type = _SERVICE +_GETSERVICEDEFAULTSRESPONSE.fields_by_name['defaults'].message_type = _SERVICEDEFAULTS +_SETSERVICEDEFAULTSREQUEST.fields_by_name['defaults'].message_type = _SERVICEDEFAULTS +_GETNODESERVICERESPONSE.fields_by_name['service'].message_type = _NODESERVICEDATA +_SERVICEACTIONREQUEST.fields_by_name['action'].enum_type = _SERVICEACTION +_GETWLANCONFIGRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP +_SETWLANCONFIGREQUEST_CONFIGENTRY.containing_type = _SETWLANCONFIGREQUEST +_SETWLANCONFIGREQUEST.fields_by_name['config'].message_type = _SETWLANCONFIGREQUEST_CONFIGENTRY +_GETEMANECONFIGRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP +_SETEMANECONFIGREQUEST_CONFIGENTRY.containing_type = _SETEMANECONFIGREQUEST +_SETEMANECONFIGREQUEST.fields_by_name['config'].message_type = _SETEMANECONFIGREQUEST_CONFIGENTRY +_GETEMANEMODELCONFIGRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP +_SETEMANEMODELCONFIGREQUEST_CONFIGENTRY.containing_type = _SETEMANEMODELCONFIGREQUEST +_SETEMANEMODELCONFIGREQUEST.fields_by_name['config'].message_type = _SETEMANEMODELCONFIGREQUEST_CONFIGENTRY +_GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG.fields_by_name['groups'].message_type = _CONFIGGROUP +_GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG.containing_type = _GETEMANEMODELCONFIGSRESPONSE +_GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY.fields_by_name['value'].message_type = _GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG +_GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY.containing_type = _GETEMANEMODELCONFIGSRESPONSE +_GETEMANEMODELCONFIGSRESPONSE.fields_by_name['configs'].message_type = _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY +_HOOK.fields_by_name['state'].enum_type = _SESSIONSTATE +_NODESERVICEDATA.fields_by_name['validation_mode'].enum_type = _SERVICEVALIDATIONMODE +_CONFIGGROUP.fields_by_name['options'].message_type = _CONFIGOPTION +_SESSION.fields_by_name['state'].enum_type = _SESSIONSTATE +_SESSION.fields_by_name['nodes'].message_type = _NODE +_SESSION.fields_by_name['links'].message_type = _LINK +_SESSIONSUMMARY.fields_by_name['state'].enum_type = _SESSIONSTATE +_NODE.fields_by_name['type'].enum_type = _NODETYPE +_NODE.fields_by_name['position'].message_type = _POSITION +_LINK.fields_by_name['type'].enum_type = _LINKTYPE +_LINK.fields_by_name['interface_one'].message_type = _INTERFACE +_LINK.fields_by_name['interface_two'].message_type = _INTERFACE +_LINK.fields_by_name['options'].message_type = _LINKOPTIONS +DESCRIPTOR.message_types_by_name['CreateSessionRequest'] = _CREATESESSIONREQUEST +DESCRIPTOR.message_types_by_name['CreateSessionResponse'] = _CREATESESSIONRESPONSE +DESCRIPTOR.message_types_by_name['DeleteSessionRequest'] = _DELETESESSIONREQUEST +DESCRIPTOR.message_types_by_name['DeleteSessionResponse'] = _DELETESESSIONRESPONSE +DESCRIPTOR.message_types_by_name['GetSessionsRequest'] = _GETSESSIONSREQUEST +DESCRIPTOR.message_types_by_name['GetSessionsResponse'] = _GETSESSIONSRESPONSE +DESCRIPTOR.message_types_by_name['GetSessionRequest'] = _GETSESSIONREQUEST +DESCRIPTOR.message_types_by_name['GetSessionResponse'] = _GETSESSIONRESPONSE +DESCRIPTOR.message_types_by_name['GetSessionOptionsRequest'] = _GETSESSIONOPTIONSREQUEST +DESCRIPTOR.message_types_by_name['GetSessionOptionsResponse'] = _GETSESSIONOPTIONSRESPONSE +DESCRIPTOR.message_types_by_name['SetSessionOptionsRequest'] = _SETSESSIONOPTIONSREQUEST +DESCRIPTOR.message_types_by_name['SetSessionOptionsResponse'] = _SETSESSIONOPTIONSRESPONSE +DESCRIPTOR.message_types_by_name['GetSessionLocationRequest'] = _GETSESSIONLOCATIONREQUEST +DESCRIPTOR.message_types_by_name['GetSessionLocationResponse'] = _GETSESSIONLOCATIONRESPONSE +DESCRIPTOR.message_types_by_name['SetSessionLocationRequest'] = _SETSESSIONLOCATIONREQUEST +DESCRIPTOR.message_types_by_name['SetSessionLocationResponse'] = _SETSESSIONLOCATIONRESPONSE +DESCRIPTOR.message_types_by_name['SetSessionStateRequest'] = _SETSESSIONSTATEREQUEST +DESCRIPTOR.message_types_by_name['SetSessionStateResponse'] = _SETSESSIONSTATERESPONSE +DESCRIPTOR.message_types_by_name['NodeEventsRequest'] = _NODEEVENTSREQUEST +DESCRIPTOR.message_types_by_name['NodeEvent'] = _NODEEVENT +DESCRIPTOR.message_types_by_name['LinkEventsRequest'] = _LINKEVENTSREQUEST +DESCRIPTOR.message_types_by_name['LinkEvent'] = _LINKEVENT +DESCRIPTOR.message_types_by_name['SessionEventsRequest'] = _SESSIONEVENTSREQUEST +DESCRIPTOR.message_types_by_name['SessionEvent'] = _SESSIONEVENT +DESCRIPTOR.message_types_by_name['ConfigEventsRequest'] = _CONFIGEVENTSREQUEST +DESCRIPTOR.message_types_by_name['ConfigEvent'] = _CONFIGEVENT +DESCRIPTOR.message_types_by_name['ExceptionEventsRequest'] = _EXCEPTIONEVENTSREQUEST +DESCRIPTOR.message_types_by_name['ExceptionEvent'] = _EXCEPTIONEVENT +DESCRIPTOR.message_types_by_name['FileEventsRequest'] = _FILEEVENTSREQUEST +DESCRIPTOR.message_types_by_name['FileEvent'] = _FILEEVENT +DESCRIPTOR.message_types_by_name['AddNodeRequest'] = _ADDNODEREQUEST +DESCRIPTOR.message_types_by_name['AddNodeResponse'] = _ADDNODERESPONSE +DESCRIPTOR.message_types_by_name['GetNodeRequest'] = _GETNODEREQUEST +DESCRIPTOR.message_types_by_name['GetNodeResponse'] = _GETNODERESPONSE +DESCRIPTOR.message_types_by_name['EditNodeRequest'] = _EDITNODEREQUEST +DESCRIPTOR.message_types_by_name['EditNodeResponse'] = _EDITNODERESPONSE +DESCRIPTOR.message_types_by_name['DeleteNodeRequest'] = _DELETENODEREQUEST +DESCRIPTOR.message_types_by_name['DeleteNodeResponse'] = _DELETENODERESPONSE +DESCRIPTOR.message_types_by_name['GetNodeLinksRequest'] = _GETNODELINKSREQUEST +DESCRIPTOR.message_types_by_name['GetNodeLinksResponse'] = _GETNODELINKSRESPONSE +DESCRIPTOR.message_types_by_name['AddLinkRequest'] = _ADDLINKREQUEST +DESCRIPTOR.message_types_by_name['AddLinkResponse'] = _ADDLINKRESPONSE +DESCRIPTOR.message_types_by_name['EditLinkRequest'] = _EDITLINKREQUEST +DESCRIPTOR.message_types_by_name['EditLinkResponse'] = _EDITLINKRESPONSE +DESCRIPTOR.message_types_by_name['DeleteLinkRequest'] = _DELETELINKREQUEST +DESCRIPTOR.message_types_by_name['DeleteLinkResponse'] = _DELETELINKRESPONSE +DESCRIPTOR.message_types_by_name['GetHooksRequest'] = _GETHOOKSREQUEST +DESCRIPTOR.message_types_by_name['GetHooksResponse'] = _GETHOOKSRESPONSE +DESCRIPTOR.message_types_by_name['AddHookRequest'] = _ADDHOOKREQUEST +DESCRIPTOR.message_types_by_name['AddHookResponse'] = _ADDHOOKRESPONSE +DESCRIPTOR.message_types_by_name['GetMobilityConfigsRequest'] = _GETMOBILITYCONFIGSREQUEST +DESCRIPTOR.message_types_by_name['GetMobilityConfigsResponse'] = _GETMOBILITYCONFIGSRESPONSE +DESCRIPTOR.message_types_by_name['GetMobilityConfigRequest'] = _GETMOBILITYCONFIGREQUEST +DESCRIPTOR.message_types_by_name['GetMobilityConfigResponse'] = _GETMOBILITYCONFIGRESPONSE +DESCRIPTOR.message_types_by_name['SetMobilityConfigRequest'] = _SETMOBILITYCONFIGREQUEST +DESCRIPTOR.message_types_by_name['SetMobilityConfigResponse'] = _SETMOBILITYCONFIGRESPONSE +DESCRIPTOR.message_types_by_name['MobilityActionRequest'] = _MOBILITYACTIONREQUEST +DESCRIPTOR.message_types_by_name['MobilityActionResponse'] = _MOBILITYACTIONRESPONSE +DESCRIPTOR.message_types_by_name['GetServicesRequest'] = _GETSERVICESREQUEST +DESCRIPTOR.message_types_by_name['GetServicesResponse'] = _GETSERVICESRESPONSE +DESCRIPTOR.message_types_by_name['GetServiceDefaultsRequest'] = _GETSERVICEDEFAULTSREQUEST +DESCRIPTOR.message_types_by_name['GetServiceDefaultsResponse'] = _GETSERVICEDEFAULTSRESPONSE +DESCRIPTOR.message_types_by_name['SetServiceDefaultsRequest'] = _SETSERVICEDEFAULTSREQUEST +DESCRIPTOR.message_types_by_name['SetServiceDefaultsResponse'] = _SETSERVICEDEFAULTSRESPONSE +DESCRIPTOR.message_types_by_name['GetNodeServiceRequest'] = _GETNODESERVICEREQUEST +DESCRIPTOR.message_types_by_name['GetNodeServiceResponse'] = _GETNODESERVICERESPONSE +DESCRIPTOR.message_types_by_name['GetNodeServiceFileRequest'] = _GETNODESERVICEFILEREQUEST +DESCRIPTOR.message_types_by_name['GetNodeServiceFileResponse'] = _GETNODESERVICEFILERESPONSE +DESCRIPTOR.message_types_by_name['SetNodeServiceRequest'] = _SETNODESERVICEREQUEST +DESCRIPTOR.message_types_by_name['SetNodeServiceResponse'] = _SETNODESERVICERESPONSE +DESCRIPTOR.message_types_by_name['SetNodeServiceFileRequest'] = _SETNODESERVICEFILEREQUEST +DESCRIPTOR.message_types_by_name['SetNodeServiceFileResponse'] = _SETNODESERVICEFILERESPONSE +DESCRIPTOR.message_types_by_name['ServiceActionRequest'] = _SERVICEACTIONREQUEST +DESCRIPTOR.message_types_by_name['ServiceActionResponse'] = _SERVICEACTIONRESPONSE +DESCRIPTOR.message_types_by_name['GetWlanConfigRequest'] = _GETWLANCONFIGREQUEST +DESCRIPTOR.message_types_by_name['GetWlanConfigResponse'] = _GETWLANCONFIGRESPONSE +DESCRIPTOR.message_types_by_name['SetWlanConfigRequest'] = _SETWLANCONFIGREQUEST +DESCRIPTOR.message_types_by_name['SetWlanConfigResponse'] = _SETWLANCONFIGRESPONSE +DESCRIPTOR.message_types_by_name['GetEmaneConfigRequest'] = _GETEMANECONFIGREQUEST +DESCRIPTOR.message_types_by_name['GetEmaneConfigResponse'] = _GETEMANECONFIGRESPONSE +DESCRIPTOR.message_types_by_name['SetEmaneConfigRequest'] = _SETEMANECONFIGREQUEST +DESCRIPTOR.message_types_by_name['SetEmaneConfigResponse'] = _SETEMANECONFIGRESPONSE +DESCRIPTOR.message_types_by_name['GetEmaneModelsRequest'] = _GETEMANEMODELSREQUEST +DESCRIPTOR.message_types_by_name['GetEmaneModelsResponse'] = _GETEMANEMODELSRESPONSE +DESCRIPTOR.message_types_by_name['GetEmaneModelConfigRequest'] = _GETEMANEMODELCONFIGREQUEST +DESCRIPTOR.message_types_by_name['GetEmaneModelConfigResponse'] = _GETEMANEMODELCONFIGRESPONSE +DESCRIPTOR.message_types_by_name['SetEmaneModelConfigRequest'] = _SETEMANEMODELCONFIGREQUEST +DESCRIPTOR.message_types_by_name['SetEmaneModelConfigResponse'] = _SETEMANEMODELCONFIGRESPONSE +DESCRIPTOR.message_types_by_name['GetEmaneModelConfigsRequest'] = _GETEMANEMODELCONFIGSREQUEST +DESCRIPTOR.message_types_by_name['GetEmaneModelConfigsResponse'] = _GETEMANEMODELCONFIGSRESPONSE +DESCRIPTOR.message_types_by_name['SaveXmlRequest'] = _SAVEXMLREQUEST +DESCRIPTOR.message_types_by_name['SaveXmlResponse'] = _SAVEXMLRESPONSE +DESCRIPTOR.message_types_by_name['OpenXmlRequest'] = _OPENXMLREQUEST +DESCRIPTOR.message_types_by_name['OpenXmlResponse'] = _OPENXMLRESPONSE +DESCRIPTOR.message_types_by_name['Hook'] = _HOOK +DESCRIPTOR.message_types_by_name['ServiceDefaults'] = _SERVICEDEFAULTS +DESCRIPTOR.message_types_by_name['Service'] = _SERVICE +DESCRIPTOR.message_types_by_name['NodeServiceData'] = _NODESERVICEDATA +DESCRIPTOR.message_types_by_name['ConfigGroup'] = _CONFIGGROUP +DESCRIPTOR.message_types_by_name['ConfigOption'] = _CONFIGOPTION +DESCRIPTOR.message_types_by_name['Session'] = _SESSION +DESCRIPTOR.message_types_by_name['SessionSummary'] = _SESSIONSUMMARY +DESCRIPTOR.message_types_by_name['Node'] = _NODE +DESCRIPTOR.message_types_by_name['Link'] = _LINK +DESCRIPTOR.message_types_by_name['LinkOptions'] = _LINKOPTIONS +DESCRIPTOR.message_types_by_name['Interface'] = _INTERFACE +DESCRIPTOR.message_types_by_name['Position'] = _POSITION +DESCRIPTOR.enum_types_by_name['MessageType'] = _MESSAGETYPE +DESCRIPTOR.enum_types_by_name['LinkType'] = _LINKTYPE +DESCRIPTOR.enum_types_by_name['SessionState'] = _SESSIONSTATE +DESCRIPTOR.enum_types_by_name['NodeType'] = _NODETYPE +DESCRIPTOR.enum_types_by_name['ServiceValidationMode'] = _SERVICEVALIDATIONMODE +DESCRIPTOR.enum_types_by_name['ServiceAction'] = _SERVICEACTION +DESCRIPTOR.enum_types_by_name['MobilityAction'] = _MOBILITYACTION +DESCRIPTOR.enum_types_by_name['ExceptionLevel'] = _EXCEPTIONLEVEL +_sym_db.RegisterFileDescriptor(DESCRIPTOR) + +CreateSessionRequest = _reflection.GeneratedProtocolMessageType('CreateSessionRequest', (_message.Message,), dict( + DESCRIPTOR = _CREATESESSIONREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.CreateSessionRequest) + )) +_sym_db.RegisterMessage(CreateSessionRequest) + +CreateSessionResponse = _reflection.GeneratedProtocolMessageType('CreateSessionResponse', (_message.Message,), dict( + DESCRIPTOR = _CREATESESSIONRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.CreateSessionResponse) + )) +_sym_db.RegisterMessage(CreateSessionResponse) + +DeleteSessionRequest = _reflection.GeneratedProtocolMessageType('DeleteSessionRequest', (_message.Message,), dict( + DESCRIPTOR = _DELETESESSIONREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.DeleteSessionRequest) + )) +_sym_db.RegisterMessage(DeleteSessionRequest) + +DeleteSessionResponse = _reflection.GeneratedProtocolMessageType('DeleteSessionResponse', (_message.Message,), dict( + DESCRIPTOR = _DELETESESSIONRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.DeleteSessionResponse) + )) +_sym_db.RegisterMessage(DeleteSessionResponse) + +GetSessionsRequest = _reflection.GeneratedProtocolMessageType('GetSessionsRequest', (_message.Message,), dict( + DESCRIPTOR = _GETSESSIONSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetSessionsRequest) + )) +_sym_db.RegisterMessage(GetSessionsRequest) + +GetSessionsResponse = _reflection.GeneratedProtocolMessageType('GetSessionsResponse', (_message.Message,), dict( + DESCRIPTOR = _GETSESSIONSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetSessionsResponse) + )) +_sym_db.RegisterMessage(GetSessionsResponse) + +GetSessionRequest = _reflection.GeneratedProtocolMessageType('GetSessionRequest', (_message.Message,), dict( + DESCRIPTOR = _GETSESSIONREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetSessionRequest) + )) +_sym_db.RegisterMessage(GetSessionRequest) + +GetSessionResponse = _reflection.GeneratedProtocolMessageType('GetSessionResponse', (_message.Message,), dict( + DESCRIPTOR = _GETSESSIONRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetSessionResponse) + )) +_sym_db.RegisterMessage(GetSessionResponse) + +GetSessionOptionsRequest = _reflection.GeneratedProtocolMessageType('GetSessionOptionsRequest', (_message.Message,), dict( + DESCRIPTOR = _GETSESSIONOPTIONSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetSessionOptionsRequest) + )) +_sym_db.RegisterMessage(GetSessionOptionsRequest) + +GetSessionOptionsResponse = _reflection.GeneratedProtocolMessageType('GetSessionOptionsResponse', (_message.Message,), dict( + DESCRIPTOR = _GETSESSIONOPTIONSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetSessionOptionsResponse) + )) +_sym_db.RegisterMessage(GetSessionOptionsResponse) + +SetSessionOptionsRequest = _reflection.GeneratedProtocolMessageType('SetSessionOptionsRequest', (_message.Message,), dict( + + ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( + DESCRIPTOR = _SETSESSIONOPTIONSREQUEST_CONFIGENTRY, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetSessionOptionsRequest.ConfigEntry) + )) + , + DESCRIPTOR = _SETSESSIONOPTIONSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetSessionOptionsRequest) + )) +_sym_db.RegisterMessage(SetSessionOptionsRequest) +_sym_db.RegisterMessage(SetSessionOptionsRequest.ConfigEntry) + +SetSessionOptionsResponse = _reflection.GeneratedProtocolMessageType('SetSessionOptionsResponse', (_message.Message,), dict( + DESCRIPTOR = _SETSESSIONOPTIONSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetSessionOptionsResponse) + )) +_sym_db.RegisterMessage(SetSessionOptionsResponse) + +GetSessionLocationRequest = _reflection.GeneratedProtocolMessageType('GetSessionLocationRequest', (_message.Message,), dict( + DESCRIPTOR = _GETSESSIONLOCATIONREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetSessionLocationRequest) + )) +_sym_db.RegisterMessage(GetSessionLocationRequest) + +GetSessionLocationResponse = _reflection.GeneratedProtocolMessageType('GetSessionLocationResponse', (_message.Message,), dict( + DESCRIPTOR = _GETSESSIONLOCATIONRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetSessionLocationResponse) + )) +_sym_db.RegisterMessage(GetSessionLocationResponse) + +SetSessionLocationRequest = _reflection.GeneratedProtocolMessageType('SetSessionLocationRequest', (_message.Message,), dict( + DESCRIPTOR = _SETSESSIONLOCATIONREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetSessionLocationRequest) + )) +_sym_db.RegisterMessage(SetSessionLocationRequest) + +SetSessionLocationResponse = _reflection.GeneratedProtocolMessageType('SetSessionLocationResponse', (_message.Message,), dict( + DESCRIPTOR = _SETSESSIONLOCATIONRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetSessionLocationResponse) + )) +_sym_db.RegisterMessage(SetSessionLocationResponse) + +SetSessionStateRequest = _reflection.GeneratedProtocolMessageType('SetSessionStateRequest', (_message.Message,), dict( + DESCRIPTOR = _SETSESSIONSTATEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetSessionStateRequest) + )) +_sym_db.RegisterMessage(SetSessionStateRequest) + +SetSessionStateResponse = _reflection.GeneratedProtocolMessageType('SetSessionStateResponse', (_message.Message,), dict( + DESCRIPTOR = _SETSESSIONSTATERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetSessionStateResponse) + )) +_sym_db.RegisterMessage(SetSessionStateResponse) + +NodeEventsRequest = _reflection.GeneratedProtocolMessageType('NodeEventsRequest', (_message.Message,), dict( + DESCRIPTOR = _NODEEVENTSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.NodeEventsRequest) + )) +_sym_db.RegisterMessage(NodeEventsRequest) + +NodeEvent = _reflection.GeneratedProtocolMessageType('NodeEvent', (_message.Message,), dict( + DESCRIPTOR = _NODEEVENT, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.NodeEvent) + )) +_sym_db.RegisterMessage(NodeEvent) + +LinkEventsRequest = _reflection.GeneratedProtocolMessageType('LinkEventsRequest', (_message.Message,), dict( + DESCRIPTOR = _LINKEVENTSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.LinkEventsRequest) + )) +_sym_db.RegisterMessage(LinkEventsRequest) + +LinkEvent = _reflection.GeneratedProtocolMessageType('LinkEvent', (_message.Message,), dict( + DESCRIPTOR = _LINKEVENT, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.LinkEvent) + )) +_sym_db.RegisterMessage(LinkEvent) + +SessionEventsRequest = _reflection.GeneratedProtocolMessageType('SessionEventsRequest', (_message.Message,), dict( + DESCRIPTOR = _SESSIONEVENTSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SessionEventsRequest) + )) +_sym_db.RegisterMessage(SessionEventsRequest) + +SessionEvent = _reflection.GeneratedProtocolMessageType('SessionEvent', (_message.Message,), dict( + DESCRIPTOR = _SESSIONEVENT, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SessionEvent) + )) +_sym_db.RegisterMessage(SessionEvent) + +ConfigEventsRequest = _reflection.GeneratedProtocolMessageType('ConfigEventsRequest', (_message.Message,), dict( + DESCRIPTOR = _CONFIGEVENTSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ConfigEventsRequest) + )) +_sym_db.RegisterMessage(ConfigEventsRequest) + +ConfigEvent = _reflection.GeneratedProtocolMessageType('ConfigEvent', (_message.Message,), dict( + DESCRIPTOR = _CONFIGEVENT, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ConfigEvent) + )) +_sym_db.RegisterMessage(ConfigEvent) + +ExceptionEventsRequest = _reflection.GeneratedProtocolMessageType('ExceptionEventsRequest', (_message.Message,), dict( + DESCRIPTOR = _EXCEPTIONEVENTSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ExceptionEventsRequest) + )) +_sym_db.RegisterMessage(ExceptionEventsRequest) + +ExceptionEvent = _reflection.GeneratedProtocolMessageType('ExceptionEvent', (_message.Message,), dict( + DESCRIPTOR = _EXCEPTIONEVENT, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ExceptionEvent) + )) +_sym_db.RegisterMessage(ExceptionEvent) + +FileEventsRequest = _reflection.GeneratedProtocolMessageType('FileEventsRequest', (_message.Message,), dict( + DESCRIPTOR = _FILEEVENTSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.FileEventsRequest) + )) +_sym_db.RegisterMessage(FileEventsRequest) + +FileEvent = _reflection.GeneratedProtocolMessageType('FileEvent', (_message.Message,), dict( + DESCRIPTOR = _FILEEVENT, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.FileEvent) + )) +_sym_db.RegisterMessage(FileEvent) + +AddNodeRequest = _reflection.GeneratedProtocolMessageType('AddNodeRequest', (_message.Message,), dict( + DESCRIPTOR = _ADDNODEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.AddNodeRequest) + )) +_sym_db.RegisterMessage(AddNodeRequest) + +AddNodeResponse = _reflection.GeneratedProtocolMessageType('AddNodeResponse', (_message.Message,), dict( + DESCRIPTOR = _ADDNODERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.AddNodeResponse) + )) +_sym_db.RegisterMessage(AddNodeResponse) + +GetNodeRequest = _reflection.GeneratedProtocolMessageType('GetNodeRequest', (_message.Message,), dict( + DESCRIPTOR = _GETNODEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetNodeRequest) + )) +_sym_db.RegisterMessage(GetNodeRequest) + +GetNodeResponse = _reflection.GeneratedProtocolMessageType('GetNodeResponse', (_message.Message,), dict( + DESCRIPTOR = _GETNODERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetNodeResponse) + )) +_sym_db.RegisterMessage(GetNodeResponse) + +EditNodeRequest = _reflection.GeneratedProtocolMessageType('EditNodeRequest', (_message.Message,), dict( + DESCRIPTOR = _EDITNODEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.EditNodeRequest) + )) +_sym_db.RegisterMessage(EditNodeRequest) + +EditNodeResponse = _reflection.GeneratedProtocolMessageType('EditNodeResponse', (_message.Message,), dict( + DESCRIPTOR = _EDITNODERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.EditNodeResponse) + )) +_sym_db.RegisterMessage(EditNodeResponse) + +DeleteNodeRequest = _reflection.GeneratedProtocolMessageType('DeleteNodeRequest', (_message.Message,), dict( + DESCRIPTOR = _DELETENODEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.DeleteNodeRequest) + )) +_sym_db.RegisterMessage(DeleteNodeRequest) + +DeleteNodeResponse = _reflection.GeneratedProtocolMessageType('DeleteNodeResponse', (_message.Message,), dict( + DESCRIPTOR = _DELETENODERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.DeleteNodeResponse) + )) +_sym_db.RegisterMessage(DeleteNodeResponse) + +GetNodeLinksRequest = _reflection.GeneratedProtocolMessageType('GetNodeLinksRequest', (_message.Message,), dict( + DESCRIPTOR = _GETNODELINKSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetNodeLinksRequest) + )) +_sym_db.RegisterMessage(GetNodeLinksRequest) + +GetNodeLinksResponse = _reflection.GeneratedProtocolMessageType('GetNodeLinksResponse', (_message.Message,), dict( + DESCRIPTOR = _GETNODELINKSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetNodeLinksResponse) + )) +_sym_db.RegisterMessage(GetNodeLinksResponse) + +AddLinkRequest = _reflection.GeneratedProtocolMessageType('AddLinkRequest', (_message.Message,), dict( + DESCRIPTOR = _ADDLINKREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.AddLinkRequest) + )) +_sym_db.RegisterMessage(AddLinkRequest) + +AddLinkResponse = _reflection.GeneratedProtocolMessageType('AddLinkResponse', (_message.Message,), dict( + DESCRIPTOR = _ADDLINKRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.AddLinkResponse) + )) +_sym_db.RegisterMessage(AddLinkResponse) + +EditLinkRequest = _reflection.GeneratedProtocolMessageType('EditLinkRequest', (_message.Message,), dict( + DESCRIPTOR = _EDITLINKREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.EditLinkRequest) + )) +_sym_db.RegisterMessage(EditLinkRequest) + +EditLinkResponse = _reflection.GeneratedProtocolMessageType('EditLinkResponse', (_message.Message,), dict( + DESCRIPTOR = _EDITLINKRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.EditLinkResponse) + )) +_sym_db.RegisterMessage(EditLinkResponse) + +DeleteLinkRequest = _reflection.GeneratedProtocolMessageType('DeleteLinkRequest', (_message.Message,), dict( + DESCRIPTOR = _DELETELINKREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.DeleteLinkRequest) + )) +_sym_db.RegisterMessage(DeleteLinkRequest) + +DeleteLinkResponse = _reflection.GeneratedProtocolMessageType('DeleteLinkResponse', (_message.Message,), dict( + DESCRIPTOR = _DELETELINKRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.DeleteLinkResponse) + )) +_sym_db.RegisterMessage(DeleteLinkResponse) + +GetHooksRequest = _reflection.GeneratedProtocolMessageType('GetHooksRequest', (_message.Message,), dict( + DESCRIPTOR = _GETHOOKSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetHooksRequest) + )) +_sym_db.RegisterMessage(GetHooksRequest) + +GetHooksResponse = _reflection.GeneratedProtocolMessageType('GetHooksResponse', (_message.Message,), dict( + DESCRIPTOR = _GETHOOKSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetHooksResponse) + )) +_sym_db.RegisterMessage(GetHooksResponse) + +AddHookRequest = _reflection.GeneratedProtocolMessageType('AddHookRequest', (_message.Message,), dict( + DESCRIPTOR = _ADDHOOKREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.AddHookRequest) + )) +_sym_db.RegisterMessage(AddHookRequest) + +AddHookResponse = _reflection.GeneratedProtocolMessageType('AddHookResponse', (_message.Message,), dict( + DESCRIPTOR = _ADDHOOKRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.AddHookResponse) + )) +_sym_db.RegisterMessage(AddHookResponse) + +GetMobilityConfigsRequest = _reflection.GeneratedProtocolMessageType('GetMobilityConfigsRequest', (_message.Message,), dict( + DESCRIPTOR = _GETMOBILITYCONFIGSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsRequest) + )) +_sym_db.RegisterMessage(GetMobilityConfigsRequest) + +GetMobilityConfigsResponse = _reflection.GeneratedProtocolMessageType('GetMobilityConfigsResponse', (_message.Message,), dict( + + MobilityConfig = _reflection.GeneratedProtocolMessageType('MobilityConfig', (_message.Message,), dict( + DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse.MobilityConfig) + )) + , + + ConfigsEntry = _reflection.GeneratedProtocolMessageType('ConfigsEntry', (_message.Message,), dict( + DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse.ConfigsEntry) + )) + , + DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse) + )) +_sym_db.RegisterMessage(GetMobilityConfigsResponse) +_sym_db.RegisterMessage(GetMobilityConfigsResponse.MobilityConfig) +_sym_db.RegisterMessage(GetMobilityConfigsResponse.ConfigsEntry) + +GetMobilityConfigRequest = _reflection.GeneratedProtocolMessageType('GetMobilityConfigRequest', (_message.Message,), dict( + DESCRIPTOR = _GETMOBILITYCONFIGREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetMobilityConfigRequest) + )) +_sym_db.RegisterMessage(GetMobilityConfigRequest) + +GetMobilityConfigResponse = _reflection.GeneratedProtocolMessageType('GetMobilityConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _GETMOBILITYCONFIGRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetMobilityConfigResponse) + )) +_sym_db.RegisterMessage(GetMobilityConfigResponse) + +SetMobilityConfigRequest = _reflection.GeneratedProtocolMessageType('SetMobilityConfigRequest', (_message.Message,), dict( + + ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( + DESCRIPTOR = _SETMOBILITYCONFIGREQUEST_CONFIGENTRY, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetMobilityConfigRequest.ConfigEntry) + )) + , + DESCRIPTOR = _SETMOBILITYCONFIGREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetMobilityConfigRequest) + )) +_sym_db.RegisterMessage(SetMobilityConfigRequest) +_sym_db.RegisterMessage(SetMobilityConfigRequest.ConfigEntry) + +SetMobilityConfigResponse = _reflection.GeneratedProtocolMessageType('SetMobilityConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _SETMOBILITYCONFIGRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetMobilityConfigResponse) + )) +_sym_db.RegisterMessage(SetMobilityConfigResponse) + +MobilityActionRequest = _reflection.GeneratedProtocolMessageType('MobilityActionRequest', (_message.Message,), dict( + DESCRIPTOR = _MOBILITYACTIONREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.MobilityActionRequest) + )) +_sym_db.RegisterMessage(MobilityActionRequest) + +MobilityActionResponse = _reflection.GeneratedProtocolMessageType('MobilityActionResponse', (_message.Message,), dict( + DESCRIPTOR = _MOBILITYACTIONRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.MobilityActionResponse) + )) +_sym_db.RegisterMessage(MobilityActionResponse) + +GetServicesRequest = _reflection.GeneratedProtocolMessageType('GetServicesRequest', (_message.Message,), dict( + DESCRIPTOR = _GETSERVICESREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetServicesRequest) + )) +_sym_db.RegisterMessage(GetServicesRequest) + +GetServicesResponse = _reflection.GeneratedProtocolMessageType('GetServicesResponse', (_message.Message,), dict( + DESCRIPTOR = _GETSERVICESRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetServicesResponse) + )) +_sym_db.RegisterMessage(GetServicesResponse) + +GetServiceDefaultsRequest = _reflection.GeneratedProtocolMessageType('GetServiceDefaultsRequest', (_message.Message,), dict( + DESCRIPTOR = _GETSERVICEDEFAULTSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetServiceDefaultsRequest) + )) +_sym_db.RegisterMessage(GetServiceDefaultsRequest) + +GetServiceDefaultsResponse = _reflection.GeneratedProtocolMessageType('GetServiceDefaultsResponse', (_message.Message,), dict( + DESCRIPTOR = _GETSERVICEDEFAULTSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetServiceDefaultsResponse) + )) +_sym_db.RegisterMessage(GetServiceDefaultsResponse) + +SetServiceDefaultsRequest = _reflection.GeneratedProtocolMessageType('SetServiceDefaultsRequest', (_message.Message,), dict( + DESCRIPTOR = _SETSERVICEDEFAULTSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetServiceDefaultsRequest) + )) +_sym_db.RegisterMessage(SetServiceDefaultsRequest) + +SetServiceDefaultsResponse = _reflection.GeneratedProtocolMessageType('SetServiceDefaultsResponse', (_message.Message,), dict( + DESCRIPTOR = _SETSERVICEDEFAULTSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetServiceDefaultsResponse) + )) +_sym_db.RegisterMessage(SetServiceDefaultsResponse) + +GetNodeServiceRequest = _reflection.GeneratedProtocolMessageType('GetNodeServiceRequest', (_message.Message,), dict( + DESCRIPTOR = _GETNODESERVICEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetNodeServiceRequest) + )) +_sym_db.RegisterMessage(GetNodeServiceRequest) + +GetNodeServiceResponse = _reflection.GeneratedProtocolMessageType('GetNodeServiceResponse', (_message.Message,), dict( + DESCRIPTOR = _GETNODESERVICERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetNodeServiceResponse) + )) +_sym_db.RegisterMessage(GetNodeServiceResponse) + +GetNodeServiceFileRequest = _reflection.GeneratedProtocolMessageType('GetNodeServiceFileRequest', (_message.Message,), dict( + DESCRIPTOR = _GETNODESERVICEFILEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetNodeServiceFileRequest) + )) +_sym_db.RegisterMessage(GetNodeServiceFileRequest) + +GetNodeServiceFileResponse = _reflection.GeneratedProtocolMessageType('GetNodeServiceFileResponse', (_message.Message,), dict( + DESCRIPTOR = _GETNODESERVICEFILERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetNodeServiceFileResponse) + )) +_sym_db.RegisterMessage(GetNodeServiceFileResponse) + +SetNodeServiceRequest = _reflection.GeneratedProtocolMessageType('SetNodeServiceRequest', (_message.Message,), dict( + DESCRIPTOR = _SETNODESERVICEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetNodeServiceRequest) + )) +_sym_db.RegisterMessage(SetNodeServiceRequest) + +SetNodeServiceResponse = _reflection.GeneratedProtocolMessageType('SetNodeServiceResponse', (_message.Message,), dict( + DESCRIPTOR = _SETNODESERVICERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetNodeServiceResponse) + )) +_sym_db.RegisterMessage(SetNodeServiceResponse) + +SetNodeServiceFileRequest = _reflection.GeneratedProtocolMessageType('SetNodeServiceFileRequest', (_message.Message,), dict( + DESCRIPTOR = _SETNODESERVICEFILEREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetNodeServiceFileRequest) + )) +_sym_db.RegisterMessage(SetNodeServiceFileRequest) + +SetNodeServiceFileResponse = _reflection.GeneratedProtocolMessageType('SetNodeServiceFileResponse', (_message.Message,), dict( + DESCRIPTOR = _SETNODESERVICEFILERESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetNodeServiceFileResponse) + )) +_sym_db.RegisterMessage(SetNodeServiceFileResponse) + +ServiceActionRequest = _reflection.GeneratedProtocolMessageType('ServiceActionRequest', (_message.Message,), dict( + DESCRIPTOR = _SERVICEACTIONREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ServiceActionRequest) + )) +_sym_db.RegisterMessage(ServiceActionRequest) + +ServiceActionResponse = _reflection.GeneratedProtocolMessageType('ServiceActionResponse', (_message.Message,), dict( + DESCRIPTOR = _SERVICEACTIONRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ServiceActionResponse) + )) +_sym_db.RegisterMessage(ServiceActionResponse) + +GetWlanConfigRequest = _reflection.GeneratedProtocolMessageType('GetWlanConfigRequest', (_message.Message,), dict( + DESCRIPTOR = _GETWLANCONFIGREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetWlanConfigRequest) + )) +_sym_db.RegisterMessage(GetWlanConfigRequest) + +GetWlanConfigResponse = _reflection.GeneratedProtocolMessageType('GetWlanConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _GETWLANCONFIGRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetWlanConfigResponse) + )) +_sym_db.RegisterMessage(GetWlanConfigResponse) + +SetWlanConfigRequest = _reflection.GeneratedProtocolMessageType('SetWlanConfigRequest', (_message.Message,), dict( + + ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( + DESCRIPTOR = _SETWLANCONFIGREQUEST_CONFIGENTRY, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetWlanConfigRequest.ConfigEntry) + )) + , + DESCRIPTOR = _SETWLANCONFIGREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetWlanConfigRequest) + )) +_sym_db.RegisterMessage(SetWlanConfigRequest) +_sym_db.RegisterMessage(SetWlanConfigRequest.ConfigEntry) + +SetWlanConfigResponse = _reflection.GeneratedProtocolMessageType('SetWlanConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _SETWLANCONFIGRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetWlanConfigResponse) + )) +_sym_db.RegisterMessage(SetWlanConfigResponse) + +GetEmaneConfigRequest = _reflection.GeneratedProtocolMessageType('GetEmaneConfigRequest', (_message.Message,), dict( + DESCRIPTOR = _GETEMANECONFIGREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneConfigRequest) + )) +_sym_db.RegisterMessage(GetEmaneConfigRequest) + +GetEmaneConfigResponse = _reflection.GeneratedProtocolMessageType('GetEmaneConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _GETEMANECONFIGRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneConfigResponse) + )) +_sym_db.RegisterMessage(GetEmaneConfigResponse) + +SetEmaneConfigRequest = _reflection.GeneratedProtocolMessageType('SetEmaneConfigRequest', (_message.Message,), dict( + + ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( + DESCRIPTOR = _SETEMANECONFIGREQUEST_CONFIGENTRY, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetEmaneConfigRequest.ConfigEntry) + )) + , + DESCRIPTOR = _SETEMANECONFIGREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetEmaneConfigRequest) + )) +_sym_db.RegisterMessage(SetEmaneConfigRequest) +_sym_db.RegisterMessage(SetEmaneConfigRequest.ConfigEntry) + +SetEmaneConfigResponse = _reflection.GeneratedProtocolMessageType('SetEmaneConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _SETEMANECONFIGRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetEmaneConfigResponse) + )) +_sym_db.RegisterMessage(SetEmaneConfigResponse) + +GetEmaneModelsRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelsRequest', (_message.Message,), dict( + DESCRIPTOR = _GETEMANEMODELSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneModelsRequest) + )) +_sym_db.RegisterMessage(GetEmaneModelsRequest) + +GetEmaneModelsResponse = _reflection.GeneratedProtocolMessageType('GetEmaneModelsResponse', (_message.Message,), dict( + DESCRIPTOR = _GETEMANEMODELSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneModelsResponse) + )) +_sym_db.RegisterMessage(GetEmaneModelsResponse) + +GetEmaneModelConfigRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigRequest', (_message.Message,), dict( + DESCRIPTOR = _GETEMANEMODELCONFIGREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigRequest) + )) +_sym_db.RegisterMessage(GetEmaneModelConfigRequest) + +GetEmaneModelConfigResponse = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _GETEMANEMODELCONFIGRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigResponse) + )) +_sym_db.RegisterMessage(GetEmaneModelConfigResponse) + +SetEmaneModelConfigRequest = _reflection.GeneratedProtocolMessageType('SetEmaneModelConfigRequest', (_message.Message,), dict( + + ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( + DESCRIPTOR = _SETEMANEMODELCONFIGREQUEST_CONFIGENTRY, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigRequest.ConfigEntry) + )) + , + DESCRIPTOR = _SETEMANEMODELCONFIGREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigRequest) + )) +_sym_db.RegisterMessage(SetEmaneModelConfigRequest) +_sym_db.RegisterMessage(SetEmaneModelConfigRequest.ConfigEntry) + +SetEmaneModelConfigResponse = _reflection.GeneratedProtocolMessageType('SetEmaneModelConfigResponse', (_message.Message,), dict( + DESCRIPTOR = _SETEMANEMODELCONFIGRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigResponse) + )) +_sym_db.RegisterMessage(SetEmaneModelConfigResponse) + +GetEmaneModelConfigsRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigsRequest', (_message.Message,), dict( + DESCRIPTOR = _GETEMANEMODELCONFIGSREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsRequest) + )) +_sym_db.RegisterMessage(GetEmaneModelConfigsRequest) + +GetEmaneModelConfigsResponse = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigsResponse', (_message.Message,), dict( + + ModelConfig = _reflection.GeneratedProtocolMessageType('ModelConfig', (_message.Message,), dict( + DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse.ModelConfig) + )) + , + + ConfigsEntry = _reflection.GeneratedProtocolMessageType('ConfigsEntry', (_message.Message,), dict( + DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse.ConfigsEntry) + )) + , + DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse) + )) +_sym_db.RegisterMessage(GetEmaneModelConfigsResponse) +_sym_db.RegisterMessage(GetEmaneModelConfigsResponse.ModelConfig) +_sym_db.RegisterMessage(GetEmaneModelConfigsResponse.ConfigsEntry) + +SaveXmlRequest = _reflection.GeneratedProtocolMessageType('SaveXmlRequest', (_message.Message,), dict( + DESCRIPTOR = _SAVEXMLREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SaveXmlRequest) + )) +_sym_db.RegisterMessage(SaveXmlRequest) + +SaveXmlResponse = _reflection.GeneratedProtocolMessageType('SaveXmlResponse', (_message.Message,), dict( + DESCRIPTOR = _SAVEXMLRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SaveXmlResponse) + )) +_sym_db.RegisterMessage(SaveXmlResponse) + +OpenXmlRequest = _reflection.GeneratedProtocolMessageType('OpenXmlRequest', (_message.Message,), dict( + DESCRIPTOR = _OPENXMLREQUEST, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.OpenXmlRequest) + )) +_sym_db.RegisterMessage(OpenXmlRequest) + +OpenXmlResponse = _reflection.GeneratedProtocolMessageType('OpenXmlResponse', (_message.Message,), dict( + DESCRIPTOR = _OPENXMLRESPONSE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.OpenXmlResponse) + )) +_sym_db.RegisterMessage(OpenXmlResponse) + +Hook = _reflection.GeneratedProtocolMessageType('Hook', (_message.Message,), dict( + DESCRIPTOR = _HOOK, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.Hook) + )) +_sym_db.RegisterMessage(Hook) + +ServiceDefaults = _reflection.GeneratedProtocolMessageType('ServiceDefaults', (_message.Message,), dict( + DESCRIPTOR = _SERVICEDEFAULTS, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ServiceDefaults) + )) +_sym_db.RegisterMessage(ServiceDefaults) + +Service = _reflection.GeneratedProtocolMessageType('Service', (_message.Message,), dict( + DESCRIPTOR = _SERVICE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.Service) + )) +_sym_db.RegisterMessage(Service) + +NodeServiceData = _reflection.GeneratedProtocolMessageType('NodeServiceData', (_message.Message,), dict( + DESCRIPTOR = _NODESERVICEDATA, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.NodeServiceData) + )) +_sym_db.RegisterMessage(NodeServiceData) + +ConfigGroup = _reflection.GeneratedProtocolMessageType('ConfigGroup', (_message.Message,), dict( + DESCRIPTOR = _CONFIGGROUP, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ConfigGroup) + )) +_sym_db.RegisterMessage(ConfigGroup) + +ConfigOption = _reflection.GeneratedProtocolMessageType('ConfigOption', (_message.Message,), dict( + DESCRIPTOR = _CONFIGOPTION, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.ConfigOption) + )) +_sym_db.RegisterMessage(ConfigOption) + +Session = _reflection.GeneratedProtocolMessageType('Session', (_message.Message,), dict( + DESCRIPTOR = _SESSION, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.Session) + )) +_sym_db.RegisterMessage(Session) + +SessionSummary = _reflection.GeneratedProtocolMessageType('SessionSummary', (_message.Message,), dict( + DESCRIPTOR = _SESSIONSUMMARY, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.SessionSummary) + )) +_sym_db.RegisterMessage(SessionSummary) + +Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( + DESCRIPTOR = _NODE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.Node) + )) +_sym_db.RegisterMessage(Node) + +Link = _reflection.GeneratedProtocolMessageType('Link', (_message.Message,), dict( + DESCRIPTOR = _LINK, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.Link) + )) +_sym_db.RegisterMessage(Link) + +LinkOptions = _reflection.GeneratedProtocolMessageType('LinkOptions', (_message.Message,), dict( + DESCRIPTOR = _LINKOPTIONS, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.LinkOptions) + )) +_sym_db.RegisterMessage(LinkOptions) + +Interface = _reflection.GeneratedProtocolMessageType('Interface', (_message.Message,), dict( + DESCRIPTOR = _INTERFACE, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.Interface) + )) +_sym_db.RegisterMessage(Interface) + +Position = _reflection.GeneratedProtocolMessageType('Position', (_message.Message,), dict( + DESCRIPTOR = _POSITION, + __module__ = 'core_pb2' + # @@protoc_insertion_point(class_scope:core.Position) + )) +_sym_db.RegisterMessage(Position) + + +_SETSESSIONOPTIONSREQUEST_CONFIGENTRY._options = None +_GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY._options = None +_SETMOBILITYCONFIGREQUEST_CONFIGENTRY._options = None +_SETWLANCONFIGREQUEST_CONFIGENTRY._options = None +_SETEMANECONFIGREQUEST_CONFIGENTRY._options = None +_SETEMANEMODELCONFIGREQUEST_CONFIGENTRY._options = None +_GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY._options = None + +_COREAPI = _descriptor.ServiceDescriptor( + name='CoreApi', + full_name='core.CoreApi', + file=DESCRIPTOR, + index=0, + serialized_options=None, + serialized_start=9243, + serialized_end=12797, + methods=[ + _descriptor.MethodDescriptor( + name='CreateSession', + full_name='core.CoreApi.CreateSession', + index=0, + containing_service=None, + input_type=_CREATESESSIONREQUEST, + output_type=_CREATESESSIONRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='DeleteSession', + full_name='core.CoreApi.DeleteSession', + index=1, + containing_service=None, + input_type=_DELETESESSIONREQUEST, + output_type=_DELETESESSIONRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetSessions', + full_name='core.CoreApi.GetSessions', + index=2, + containing_service=None, + input_type=_GETSESSIONSREQUEST, + output_type=_GETSESSIONSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetSession', + full_name='core.CoreApi.GetSession', + index=3, + containing_service=None, + input_type=_GETSESSIONREQUEST, + output_type=_GETSESSIONRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetSessionOptions', + full_name='core.CoreApi.GetSessionOptions', + index=4, + containing_service=None, + input_type=_GETSESSIONOPTIONSREQUEST, + output_type=_GETSESSIONOPTIONSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetSessionOptions', + full_name='core.CoreApi.SetSessionOptions', + index=5, + containing_service=None, + input_type=_SETSESSIONOPTIONSREQUEST, + output_type=_SETSESSIONOPTIONSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetSessionLocation', + full_name='core.CoreApi.GetSessionLocation', + index=6, + containing_service=None, + input_type=_GETSESSIONLOCATIONREQUEST, + output_type=_GETSESSIONLOCATIONRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetSessionLocation', + full_name='core.CoreApi.SetSessionLocation', + index=7, + containing_service=None, + input_type=_SETSESSIONLOCATIONREQUEST, + output_type=_SETSESSIONLOCATIONRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetSessionState', + full_name='core.CoreApi.SetSessionState', + index=8, + containing_service=None, + input_type=_SETSESSIONSTATEREQUEST, + output_type=_SETSESSIONSTATERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='NodeEvents', + full_name='core.CoreApi.NodeEvents', + index=9, + containing_service=None, + input_type=_NODEEVENTSREQUEST, + output_type=_NODEEVENT, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='LinkEvents', + full_name='core.CoreApi.LinkEvents', + index=10, + containing_service=None, + input_type=_LINKEVENTSREQUEST, + output_type=_LINKEVENT, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SessionEvents', + full_name='core.CoreApi.SessionEvents', + index=11, + containing_service=None, + input_type=_SESSIONEVENTSREQUEST, + output_type=_SESSIONEVENT, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='ConfigEvents', + full_name='core.CoreApi.ConfigEvents', + index=12, + containing_service=None, + input_type=_CONFIGEVENTSREQUEST, + output_type=_CONFIGEVENT, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='ExceptionEvents', + full_name='core.CoreApi.ExceptionEvents', + index=13, + containing_service=None, + input_type=_EXCEPTIONEVENTSREQUEST, + output_type=_EXCEPTIONEVENT, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='FileEvents', + full_name='core.CoreApi.FileEvents', + index=14, + containing_service=None, + input_type=_FILEEVENTSREQUEST, + output_type=_FILEEVENT, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='AddNode', + full_name='core.CoreApi.AddNode', + index=15, + containing_service=None, + input_type=_ADDNODEREQUEST, + output_type=_ADDNODERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetNode', + full_name='core.CoreApi.GetNode', + index=16, + containing_service=None, + input_type=_GETNODEREQUEST, + output_type=_GETNODERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='EditNode', + full_name='core.CoreApi.EditNode', + index=17, + containing_service=None, + input_type=_EDITNODEREQUEST, + output_type=_EDITNODERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='DeleteNode', + full_name='core.CoreApi.DeleteNode', + index=18, + containing_service=None, + input_type=_DELETENODEREQUEST, + output_type=_DELETENODERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetNodeLinks', + full_name='core.CoreApi.GetNodeLinks', + index=19, + containing_service=None, + input_type=_GETNODELINKSREQUEST, + output_type=_GETNODELINKSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='AddLink', + full_name='core.CoreApi.AddLink', + index=20, + containing_service=None, + input_type=_ADDLINKREQUEST, + output_type=_ADDLINKRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='EditLink', + full_name='core.CoreApi.EditLink', + index=21, + containing_service=None, + input_type=_EDITLINKREQUEST, + output_type=_EDITLINKRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='DeleteLink', + full_name='core.CoreApi.DeleteLink', + index=22, + containing_service=None, + input_type=_DELETELINKREQUEST, + output_type=_DELETELINKRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetHooks', + full_name='core.CoreApi.GetHooks', + index=23, + containing_service=None, + input_type=_GETHOOKSREQUEST, + output_type=_GETHOOKSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='AddHook', + full_name='core.CoreApi.AddHook', + index=24, + containing_service=None, + input_type=_ADDHOOKREQUEST, + output_type=_ADDHOOKRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetMobilityConfigs', + full_name='core.CoreApi.GetMobilityConfigs', + index=25, + containing_service=None, + input_type=_GETMOBILITYCONFIGSREQUEST, + output_type=_GETMOBILITYCONFIGSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetMobilityConfig', + full_name='core.CoreApi.GetMobilityConfig', + index=26, + containing_service=None, + input_type=_GETMOBILITYCONFIGREQUEST, + output_type=_GETMOBILITYCONFIGRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetMobilityConfig', + full_name='core.CoreApi.SetMobilityConfig', + index=27, + containing_service=None, + input_type=_SETMOBILITYCONFIGREQUEST, + output_type=_SETMOBILITYCONFIGRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='MobilityAction', + full_name='core.CoreApi.MobilityAction', + index=28, + containing_service=None, + input_type=_MOBILITYACTIONREQUEST, + output_type=_MOBILITYACTIONRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetServices', + full_name='core.CoreApi.GetServices', + index=29, + containing_service=None, + input_type=_GETSERVICESREQUEST, + output_type=_GETSERVICESRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetServiceDefaults', + full_name='core.CoreApi.GetServiceDefaults', + index=30, + containing_service=None, + input_type=_GETSERVICEDEFAULTSREQUEST, + output_type=_GETSERVICEDEFAULTSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetServiceDefaults', + full_name='core.CoreApi.SetServiceDefaults', + index=31, + containing_service=None, + input_type=_SETSERVICEDEFAULTSREQUEST, + output_type=_SETSERVICEDEFAULTSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetNodeService', + full_name='core.CoreApi.GetNodeService', + index=32, + containing_service=None, + input_type=_GETNODESERVICEREQUEST, + output_type=_GETNODESERVICERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetNodeServiceFile', + full_name='core.CoreApi.GetNodeServiceFile', + index=33, + containing_service=None, + input_type=_GETNODESERVICEFILEREQUEST, + output_type=_GETNODESERVICEFILERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetNodeService', + full_name='core.CoreApi.SetNodeService', + index=34, + containing_service=None, + input_type=_SETNODESERVICEREQUEST, + output_type=_SETNODESERVICERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetNodeServiceFile', + full_name='core.CoreApi.SetNodeServiceFile', + index=35, + containing_service=None, + input_type=_SETNODESERVICEFILEREQUEST, + output_type=_SETNODESERVICEFILERESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='ServiceAction', + full_name='core.CoreApi.ServiceAction', + index=36, + containing_service=None, + input_type=_SERVICEACTIONREQUEST, + output_type=_SERVICEACTIONRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetWlanConfig', + full_name='core.CoreApi.GetWlanConfig', + index=37, + containing_service=None, + input_type=_GETWLANCONFIGREQUEST, + output_type=_GETWLANCONFIGRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetWlanConfig', + full_name='core.CoreApi.SetWlanConfig', + index=38, + containing_service=None, + input_type=_SETWLANCONFIGREQUEST, + output_type=_SETWLANCONFIGRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetEmaneConfig', + full_name='core.CoreApi.GetEmaneConfig', + index=39, + containing_service=None, + input_type=_GETEMANECONFIGREQUEST, + output_type=_GETEMANECONFIGRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetEmaneConfig', + full_name='core.CoreApi.SetEmaneConfig', + index=40, + containing_service=None, + input_type=_SETEMANECONFIGREQUEST, + output_type=_SETEMANECONFIGRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetEmaneModels', + full_name='core.CoreApi.GetEmaneModels', + index=41, + containing_service=None, + input_type=_GETEMANEMODELSREQUEST, + output_type=_GETEMANEMODELSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetEmaneModelConfig', + full_name='core.CoreApi.GetEmaneModelConfig', + index=42, + containing_service=None, + input_type=_GETEMANEMODELCONFIGREQUEST, + output_type=_GETEMANEMODELCONFIGRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SetEmaneModelConfig', + full_name='core.CoreApi.SetEmaneModelConfig', + index=43, + containing_service=None, + input_type=_SETEMANEMODELCONFIGREQUEST, + output_type=_SETEMANEMODELCONFIGRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='GetEmaneModelConfigs', + full_name='core.CoreApi.GetEmaneModelConfigs', + index=44, + containing_service=None, + input_type=_GETEMANEMODELCONFIGSREQUEST, + output_type=_GETEMANEMODELCONFIGSRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='SaveXml', + full_name='core.CoreApi.SaveXml', + index=45, + containing_service=None, + input_type=_SAVEXMLREQUEST, + output_type=_SAVEXMLRESPONSE, + serialized_options=None, + ), + _descriptor.MethodDescriptor( + name='OpenXml', + full_name='core.CoreApi.OpenXml', + index=46, + containing_service=None, + input_type=_OPENXMLREQUEST, + output_type=_OPENXMLRESPONSE, + serialized_options=None, + ), +]) +_sym_db.RegisterServiceDescriptor(_COREAPI) + +DESCRIPTOR.services_by_name['CoreApi'] = _COREAPI + +# @@protoc_insertion_point(module_scope) diff --git a/daemon/core/api/grpc/core_pb2_grpc.py b/daemon/core/api/grpc/core_pb2_grpc.py new file mode 100644 index 00000000..5535b32b --- /dev/null +++ b/daemon/core/api/grpc/core_pb2_grpc.py @@ -0,0 +1,828 @@ +# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! +import grpc + +import core_pb2 as core__pb2 + + +class CoreApiStub(object): + # missing associated documentation comment in .proto file + pass + + def __init__(self, channel): + """Constructor. + + Args: + channel: A grpc.Channel. + """ + self.CreateSession = channel.unary_unary( + '/core.CoreApi/CreateSession', + request_serializer=core__pb2.CreateSessionRequest.SerializeToString, + response_deserializer=core__pb2.CreateSessionResponse.FromString, + ) + self.DeleteSession = channel.unary_unary( + '/core.CoreApi/DeleteSession', + request_serializer=core__pb2.DeleteSessionRequest.SerializeToString, + response_deserializer=core__pb2.DeleteSessionResponse.FromString, + ) + self.GetSessions = channel.unary_unary( + '/core.CoreApi/GetSessions', + request_serializer=core__pb2.GetSessionsRequest.SerializeToString, + response_deserializer=core__pb2.GetSessionsResponse.FromString, + ) + self.GetSession = channel.unary_unary( + '/core.CoreApi/GetSession', + request_serializer=core__pb2.GetSessionRequest.SerializeToString, + response_deserializer=core__pb2.GetSessionResponse.FromString, + ) + self.GetSessionOptions = channel.unary_unary( + '/core.CoreApi/GetSessionOptions', + request_serializer=core__pb2.GetSessionOptionsRequest.SerializeToString, + response_deserializer=core__pb2.GetSessionOptionsResponse.FromString, + ) + self.SetSessionOptions = channel.unary_unary( + '/core.CoreApi/SetSessionOptions', + request_serializer=core__pb2.SetSessionOptionsRequest.SerializeToString, + response_deserializer=core__pb2.SetSessionOptionsResponse.FromString, + ) + self.GetSessionLocation = channel.unary_unary( + '/core.CoreApi/GetSessionLocation', + request_serializer=core__pb2.GetSessionLocationRequest.SerializeToString, + response_deserializer=core__pb2.GetSessionLocationResponse.FromString, + ) + self.SetSessionLocation = channel.unary_unary( + '/core.CoreApi/SetSessionLocation', + request_serializer=core__pb2.SetSessionLocationRequest.SerializeToString, + response_deserializer=core__pb2.SetSessionLocationResponse.FromString, + ) + self.SetSessionState = channel.unary_unary( + '/core.CoreApi/SetSessionState', + request_serializer=core__pb2.SetSessionStateRequest.SerializeToString, + response_deserializer=core__pb2.SetSessionStateResponse.FromString, + ) + self.NodeEvents = channel.unary_stream( + '/core.CoreApi/NodeEvents', + request_serializer=core__pb2.NodeEventsRequest.SerializeToString, + response_deserializer=core__pb2.NodeEvent.FromString, + ) + self.LinkEvents = channel.unary_stream( + '/core.CoreApi/LinkEvents', + request_serializer=core__pb2.LinkEventsRequest.SerializeToString, + response_deserializer=core__pb2.LinkEvent.FromString, + ) + self.SessionEvents = channel.unary_stream( + '/core.CoreApi/SessionEvents', + request_serializer=core__pb2.SessionEventsRequest.SerializeToString, + response_deserializer=core__pb2.SessionEvent.FromString, + ) + self.ConfigEvents = channel.unary_stream( + '/core.CoreApi/ConfigEvents', + request_serializer=core__pb2.ConfigEventsRequest.SerializeToString, + response_deserializer=core__pb2.ConfigEvent.FromString, + ) + self.ExceptionEvents = channel.unary_stream( + '/core.CoreApi/ExceptionEvents', + request_serializer=core__pb2.ExceptionEventsRequest.SerializeToString, + response_deserializer=core__pb2.ExceptionEvent.FromString, + ) + self.FileEvents = channel.unary_stream( + '/core.CoreApi/FileEvents', + request_serializer=core__pb2.FileEventsRequest.SerializeToString, + response_deserializer=core__pb2.FileEvent.FromString, + ) + self.AddNode = channel.unary_unary( + '/core.CoreApi/AddNode', + request_serializer=core__pb2.AddNodeRequest.SerializeToString, + response_deserializer=core__pb2.AddNodeResponse.FromString, + ) + self.GetNode = channel.unary_unary( + '/core.CoreApi/GetNode', + request_serializer=core__pb2.GetNodeRequest.SerializeToString, + response_deserializer=core__pb2.GetNodeResponse.FromString, + ) + self.EditNode = channel.unary_unary( + '/core.CoreApi/EditNode', + request_serializer=core__pb2.EditNodeRequest.SerializeToString, + response_deserializer=core__pb2.EditNodeResponse.FromString, + ) + self.DeleteNode = channel.unary_unary( + '/core.CoreApi/DeleteNode', + request_serializer=core__pb2.DeleteNodeRequest.SerializeToString, + response_deserializer=core__pb2.DeleteNodeResponse.FromString, + ) + self.GetNodeLinks = channel.unary_unary( + '/core.CoreApi/GetNodeLinks', + request_serializer=core__pb2.GetNodeLinksRequest.SerializeToString, + response_deserializer=core__pb2.GetNodeLinksResponse.FromString, + ) + self.AddLink = channel.unary_unary( + '/core.CoreApi/AddLink', + request_serializer=core__pb2.AddLinkRequest.SerializeToString, + response_deserializer=core__pb2.AddLinkResponse.FromString, + ) + self.EditLink = channel.unary_unary( + '/core.CoreApi/EditLink', + request_serializer=core__pb2.EditLinkRequest.SerializeToString, + response_deserializer=core__pb2.EditLinkResponse.FromString, + ) + self.DeleteLink = channel.unary_unary( + '/core.CoreApi/DeleteLink', + request_serializer=core__pb2.DeleteLinkRequest.SerializeToString, + response_deserializer=core__pb2.DeleteLinkResponse.FromString, + ) + self.GetHooks = channel.unary_unary( + '/core.CoreApi/GetHooks', + request_serializer=core__pb2.GetHooksRequest.SerializeToString, + response_deserializer=core__pb2.GetHooksResponse.FromString, + ) + self.AddHook = channel.unary_unary( + '/core.CoreApi/AddHook', + request_serializer=core__pb2.AddHookRequest.SerializeToString, + response_deserializer=core__pb2.AddHookResponse.FromString, + ) + self.GetMobilityConfigs = channel.unary_unary( + '/core.CoreApi/GetMobilityConfigs', + request_serializer=core__pb2.GetMobilityConfigsRequest.SerializeToString, + response_deserializer=core__pb2.GetMobilityConfigsResponse.FromString, + ) + self.GetMobilityConfig = channel.unary_unary( + '/core.CoreApi/GetMobilityConfig', + request_serializer=core__pb2.GetMobilityConfigRequest.SerializeToString, + response_deserializer=core__pb2.GetMobilityConfigResponse.FromString, + ) + self.SetMobilityConfig = channel.unary_unary( + '/core.CoreApi/SetMobilityConfig', + request_serializer=core__pb2.SetMobilityConfigRequest.SerializeToString, + response_deserializer=core__pb2.SetMobilityConfigResponse.FromString, + ) + self.MobilityAction = channel.unary_unary( + '/core.CoreApi/MobilityAction', + request_serializer=core__pb2.MobilityActionRequest.SerializeToString, + response_deserializer=core__pb2.MobilityActionResponse.FromString, + ) + self.GetServices = channel.unary_unary( + '/core.CoreApi/GetServices', + request_serializer=core__pb2.GetServicesRequest.SerializeToString, + response_deserializer=core__pb2.GetServicesResponse.FromString, + ) + self.GetServiceDefaults = channel.unary_unary( + '/core.CoreApi/GetServiceDefaults', + request_serializer=core__pb2.GetServiceDefaultsRequest.SerializeToString, + response_deserializer=core__pb2.GetServiceDefaultsResponse.FromString, + ) + self.SetServiceDefaults = channel.unary_unary( + '/core.CoreApi/SetServiceDefaults', + request_serializer=core__pb2.SetServiceDefaultsRequest.SerializeToString, + response_deserializer=core__pb2.SetServiceDefaultsResponse.FromString, + ) + self.GetNodeService = channel.unary_unary( + '/core.CoreApi/GetNodeService', + request_serializer=core__pb2.GetNodeServiceRequest.SerializeToString, + response_deserializer=core__pb2.GetNodeServiceResponse.FromString, + ) + self.GetNodeServiceFile = channel.unary_unary( + '/core.CoreApi/GetNodeServiceFile', + request_serializer=core__pb2.GetNodeServiceFileRequest.SerializeToString, + response_deserializer=core__pb2.GetNodeServiceFileResponse.FromString, + ) + self.SetNodeService = channel.unary_unary( + '/core.CoreApi/SetNodeService', + request_serializer=core__pb2.SetNodeServiceRequest.SerializeToString, + response_deserializer=core__pb2.SetNodeServiceResponse.FromString, + ) + self.SetNodeServiceFile = channel.unary_unary( + '/core.CoreApi/SetNodeServiceFile', + request_serializer=core__pb2.SetNodeServiceFileRequest.SerializeToString, + response_deserializer=core__pb2.SetNodeServiceFileResponse.FromString, + ) + self.ServiceAction = channel.unary_unary( + '/core.CoreApi/ServiceAction', + request_serializer=core__pb2.ServiceActionRequest.SerializeToString, + response_deserializer=core__pb2.ServiceActionResponse.FromString, + ) + self.GetWlanConfig = channel.unary_unary( + '/core.CoreApi/GetWlanConfig', + request_serializer=core__pb2.GetWlanConfigRequest.SerializeToString, + response_deserializer=core__pb2.GetWlanConfigResponse.FromString, + ) + self.SetWlanConfig = channel.unary_unary( + '/core.CoreApi/SetWlanConfig', + request_serializer=core__pb2.SetWlanConfigRequest.SerializeToString, + response_deserializer=core__pb2.SetWlanConfigResponse.FromString, + ) + self.GetEmaneConfig = channel.unary_unary( + '/core.CoreApi/GetEmaneConfig', + request_serializer=core__pb2.GetEmaneConfigRequest.SerializeToString, + response_deserializer=core__pb2.GetEmaneConfigResponse.FromString, + ) + self.SetEmaneConfig = channel.unary_unary( + '/core.CoreApi/SetEmaneConfig', + request_serializer=core__pb2.SetEmaneConfigRequest.SerializeToString, + response_deserializer=core__pb2.SetEmaneConfigResponse.FromString, + ) + self.GetEmaneModels = channel.unary_unary( + '/core.CoreApi/GetEmaneModels', + request_serializer=core__pb2.GetEmaneModelsRequest.SerializeToString, + response_deserializer=core__pb2.GetEmaneModelsResponse.FromString, + ) + self.GetEmaneModelConfig = channel.unary_unary( + '/core.CoreApi/GetEmaneModelConfig', + request_serializer=core__pb2.GetEmaneModelConfigRequest.SerializeToString, + response_deserializer=core__pb2.GetEmaneModelConfigResponse.FromString, + ) + self.SetEmaneModelConfig = channel.unary_unary( + '/core.CoreApi/SetEmaneModelConfig', + request_serializer=core__pb2.SetEmaneModelConfigRequest.SerializeToString, + response_deserializer=core__pb2.SetEmaneModelConfigResponse.FromString, + ) + self.GetEmaneModelConfigs = channel.unary_unary( + '/core.CoreApi/GetEmaneModelConfigs', + request_serializer=core__pb2.GetEmaneModelConfigsRequest.SerializeToString, + response_deserializer=core__pb2.GetEmaneModelConfigsResponse.FromString, + ) + self.SaveXml = channel.unary_unary( + '/core.CoreApi/SaveXml', + request_serializer=core__pb2.SaveXmlRequest.SerializeToString, + response_deserializer=core__pb2.SaveXmlResponse.FromString, + ) + self.OpenXml = channel.unary_unary( + '/core.CoreApi/OpenXml', + request_serializer=core__pb2.OpenXmlRequest.SerializeToString, + response_deserializer=core__pb2.OpenXmlResponse.FromString, + ) + + +class CoreApiServicer(object): + # missing associated documentation comment in .proto file + pass + + def CreateSession(self, request, context): + """session rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteSession(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSessions(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSession(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSessionOptions(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetSessionOptions(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetSessionLocation(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetSessionLocation(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetSessionState(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def NodeEvents(self, request, context): + """event streams + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def LinkEvents(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SessionEvents(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ConfigEvents(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ExceptionEvents(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def FileEvents(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def AddNode(self, request, context): + """node rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetNode(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def EditNode(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteNode(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetNodeLinks(self, request, context): + """link rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def AddLink(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def EditLink(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def DeleteLink(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetHooks(self, request, context): + """hook rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def AddHook(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetMobilityConfigs(self, request, context): + """mobility rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetMobilityConfig(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetMobilityConfig(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def MobilityAction(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetServices(self, request, context): + """service rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetServiceDefaults(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetServiceDefaults(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetNodeService(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetNodeServiceFile(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetNodeService(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetNodeServiceFile(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def ServiceAction(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetWlanConfig(self, request, context): + """wlan rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetWlanConfig(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetEmaneConfig(self, request, context): + """emane rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetEmaneConfig(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetEmaneModels(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetEmaneModelConfig(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SetEmaneModelConfig(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def GetEmaneModelConfigs(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def SaveXml(self, request, context): + """xml rpc + """ + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + def OpenXml(self, request, context): + # missing associated documentation comment in .proto file + pass + context.set_code(grpc.StatusCode.UNIMPLEMENTED) + context.set_details('Method not implemented!') + raise NotImplementedError('Method not implemented!') + + +def add_CoreApiServicer_to_server(servicer, server): + rpc_method_handlers = { + 'CreateSession': grpc.unary_unary_rpc_method_handler( + servicer.CreateSession, + request_deserializer=core__pb2.CreateSessionRequest.FromString, + response_serializer=core__pb2.CreateSessionResponse.SerializeToString, + ), + 'DeleteSession': grpc.unary_unary_rpc_method_handler( + servicer.DeleteSession, + request_deserializer=core__pb2.DeleteSessionRequest.FromString, + response_serializer=core__pb2.DeleteSessionResponse.SerializeToString, + ), + 'GetSessions': grpc.unary_unary_rpc_method_handler( + servicer.GetSessions, + request_deserializer=core__pb2.GetSessionsRequest.FromString, + response_serializer=core__pb2.GetSessionsResponse.SerializeToString, + ), + 'GetSession': grpc.unary_unary_rpc_method_handler( + servicer.GetSession, + request_deserializer=core__pb2.GetSessionRequest.FromString, + response_serializer=core__pb2.GetSessionResponse.SerializeToString, + ), + 'GetSessionOptions': grpc.unary_unary_rpc_method_handler( + servicer.GetSessionOptions, + request_deserializer=core__pb2.GetSessionOptionsRequest.FromString, + response_serializer=core__pb2.GetSessionOptionsResponse.SerializeToString, + ), + 'SetSessionOptions': grpc.unary_unary_rpc_method_handler( + servicer.SetSessionOptions, + request_deserializer=core__pb2.SetSessionOptionsRequest.FromString, + response_serializer=core__pb2.SetSessionOptionsResponse.SerializeToString, + ), + 'GetSessionLocation': grpc.unary_unary_rpc_method_handler( + servicer.GetSessionLocation, + request_deserializer=core__pb2.GetSessionLocationRequest.FromString, + response_serializer=core__pb2.GetSessionLocationResponse.SerializeToString, + ), + 'SetSessionLocation': grpc.unary_unary_rpc_method_handler( + servicer.SetSessionLocation, + request_deserializer=core__pb2.SetSessionLocationRequest.FromString, + response_serializer=core__pb2.SetSessionLocationResponse.SerializeToString, + ), + 'SetSessionState': grpc.unary_unary_rpc_method_handler( + servicer.SetSessionState, + request_deserializer=core__pb2.SetSessionStateRequest.FromString, + response_serializer=core__pb2.SetSessionStateResponse.SerializeToString, + ), + 'NodeEvents': grpc.unary_stream_rpc_method_handler( + servicer.NodeEvents, + request_deserializer=core__pb2.NodeEventsRequest.FromString, + response_serializer=core__pb2.NodeEvent.SerializeToString, + ), + 'LinkEvents': grpc.unary_stream_rpc_method_handler( + servicer.LinkEvents, + request_deserializer=core__pb2.LinkEventsRequest.FromString, + response_serializer=core__pb2.LinkEvent.SerializeToString, + ), + 'SessionEvents': grpc.unary_stream_rpc_method_handler( + servicer.SessionEvents, + request_deserializer=core__pb2.SessionEventsRequest.FromString, + response_serializer=core__pb2.SessionEvent.SerializeToString, + ), + 'ConfigEvents': grpc.unary_stream_rpc_method_handler( + servicer.ConfigEvents, + request_deserializer=core__pb2.ConfigEventsRequest.FromString, + response_serializer=core__pb2.ConfigEvent.SerializeToString, + ), + 'ExceptionEvents': grpc.unary_stream_rpc_method_handler( + servicer.ExceptionEvents, + request_deserializer=core__pb2.ExceptionEventsRequest.FromString, + response_serializer=core__pb2.ExceptionEvent.SerializeToString, + ), + 'FileEvents': grpc.unary_stream_rpc_method_handler( + servicer.FileEvents, + request_deserializer=core__pb2.FileEventsRequest.FromString, + response_serializer=core__pb2.FileEvent.SerializeToString, + ), + 'AddNode': grpc.unary_unary_rpc_method_handler( + servicer.AddNode, + request_deserializer=core__pb2.AddNodeRequest.FromString, + response_serializer=core__pb2.AddNodeResponse.SerializeToString, + ), + 'GetNode': grpc.unary_unary_rpc_method_handler( + servicer.GetNode, + request_deserializer=core__pb2.GetNodeRequest.FromString, + response_serializer=core__pb2.GetNodeResponse.SerializeToString, + ), + 'EditNode': grpc.unary_unary_rpc_method_handler( + servicer.EditNode, + request_deserializer=core__pb2.EditNodeRequest.FromString, + response_serializer=core__pb2.EditNodeResponse.SerializeToString, + ), + 'DeleteNode': grpc.unary_unary_rpc_method_handler( + servicer.DeleteNode, + request_deserializer=core__pb2.DeleteNodeRequest.FromString, + response_serializer=core__pb2.DeleteNodeResponse.SerializeToString, + ), + 'GetNodeLinks': grpc.unary_unary_rpc_method_handler( + servicer.GetNodeLinks, + request_deserializer=core__pb2.GetNodeLinksRequest.FromString, + response_serializer=core__pb2.GetNodeLinksResponse.SerializeToString, + ), + 'AddLink': grpc.unary_unary_rpc_method_handler( + servicer.AddLink, + request_deserializer=core__pb2.AddLinkRequest.FromString, + response_serializer=core__pb2.AddLinkResponse.SerializeToString, + ), + 'EditLink': grpc.unary_unary_rpc_method_handler( + servicer.EditLink, + request_deserializer=core__pb2.EditLinkRequest.FromString, + response_serializer=core__pb2.EditLinkResponse.SerializeToString, + ), + 'DeleteLink': grpc.unary_unary_rpc_method_handler( + servicer.DeleteLink, + request_deserializer=core__pb2.DeleteLinkRequest.FromString, + response_serializer=core__pb2.DeleteLinkResponse.SerializeToString, + ), + 'GetHooks': grpc.unary_unary_rpc_method_handler( + servicer.GetHooks, + request_deserializer=core__pb2.GetHooksRequest.FromString, + response_serializer=core__pb2.GetHooksResponse.SerializeToString, + ), + 'AddHook': grpc.unary_unary_rpc_method_handler( + servicer.AddHook, + request_deserializer=core__pb2.AddHookRequest.FromString, + response_serializer=core__pb2.AddHookResponse.SerializeToString, + ), + 'GetMobilityConfigs': grpc.unary_unary_rpc_method_handler( + servicer.GetMobilityConfigs, + request_deserializer=core__pb2.GetMobilityConfigsRequest.FromString, + response_serializer=core__pb2.GetMobilityConfigsResponse.SerializeToString, + ), + 'GetMobilityConfig': grpc.unary_unary_rpc_method_handler( + servicer.GetMobilityConfig, + request_deserializer=core__pb2.GetMobilityConfigRequest.FromString, + response_serializer=core__pb2.GetMobilityConfigResponse.SerializeToString, + ), + 'SetMobilityConfig': grpc.unary_unary_rpc_method_handler( + servicer.SetMobilityConfig, + request_deserializer=core__pb2.SetMobilityConfigRequest.FromString, + response_serializer=core__pb2.SetMobilityConfigResponse.SerializeToString, + ), + 'MobilityAction': grpc.unary_unary_rpc_method_handler( + servicer.MobilityAction, + request_deserializer=core__pb2.MobilityActionRequest.FromString, + response_serializer=core__pb2.MobilityActionResponse.SerializeToString, + ), + 'GetServices': grpc.unary_unary_rpc_method_handler( + servicer.GetServices, + request_deserializer=core__pb2.GetServicesRequest.FromString, + response_serializer=core__pb2.GetServicesResponse.SerializeToString, + ), + 'GetServiceDefaults': grpc.unary_unary_rpc_method_handler( + servicer.GetServiceDefaults, + request_deserializer=core__pb2.GetServiceDefaultsRequest.FromString, + response_serializer=core__pb2.GetServiceDefaultsResponse.SerializeToString, + ), + 'SetServiceDefaults': grpc.unary_unary_rpc_method_handler( + servicer.SetServiceDefaults, + request_deserializer=core__pb2.SetServiceDefaultsRequest.FromString, + response_serializer=core__pb2.SetServiceDefaultsResponse.SerializeToString, + ), + 'GetNodeService': grpc.unary_unary_rpc_method_handler( + servicer.GetNodeService, + request_deserializer=core__pb2.GetNodeServiceRequest.FromString, + response_serializer=core__pb2.GetNodeServiceResponse.SerializeToString, + ), + 'GetNodeServiceFile': grpc.unary_unary_rpc_method_handler( + servicer.GetNodeServiceFile, + request_deserializer=core__pb2.GetNodeServiceFileRequest.FromString, + response_serializer=core__pb2.GetNodeServiceFileResponse.SerializeToString, + ), + 'SetNodeService': grpc.unary_unary_rpc_method_handler( + servicer.SetNodeService, + request_deserializer=core__pb2.SetNodeServiceRequest.FromString, + response_serializer=core__pb2.SetNodeServiceResponse.SerializeToString, + ), + 'SetNodeServiceFile': grpc.unary_unary_rpc_method_handler( + servicer.SetNodeServiceFile, + request_deserializer=core__pb2.SetNodeServiceFileRequest.FromString, + response_serializer=core__pb2.SetNodeServiceFileResponse.SerializeToString, + ), + 'ServiceAction': grpc.unary_unary_rpc_method_handler( + servicer.ServiceAction, + request_deserializer=core__pb2.ServiceActionRequest.FromString, + response_serializer=core__pb2.ServiceActionResponse.SerializeToString, + ), + 'GetWlanConfig': grpc.unary_unary_rpc_method_handler( + servicer.GetWlanConfig, + request_deserializer=core__pb2.GetWlanConfigRequest.FromString, + response_serializer=core__pb2.GetWlanConfigResponse.SerializeToString, + ), + 'SetWlanConfig': grpc.unary_unary_rpc_method_handler( + servicer.SetWlanConfig, + request_deserializer=core__pb2.SetWlanConfigRequest.FromString, + response_serializer=core__pb2.SetWlanConfigResponse.SerializeToString, + ), + 'GetEmaneConfig': grpc.unary_unary_rpc_method_handler( + servicer.GetEmaneConfig, + request_deserializer=core__pb2.GetEmaneConfigRequest.FromString, + response_serializer=core__pb2.GetEmaneConfigResponse.SerializeToString, + ), + 'SetEmaneConfig': grpc.unary_unary_rpc_method_handler( + servicer.SetEmaneConfig, + request_deserializer=core__pb2.SetEmaneConfigRequest.FromString, + response_serializer=core__pb2.SetEmaneConfigResponse.SerializeToString, + ), + 'GetEmaneModels': grpc.unary_unary_rpc_method_handler( + servicer.GetEmaneModels, + request_deserializer=core__pb2.GetEmaneModelsRequest.FromString, + response_serializer=core__pb2.GetEmaneModelsResponse.SerializeToString, + ), + 'GetEmaneModelConfig': grpc.unary_unary_rpc_method_handler( + servicer.GetEmaneModelConfig, + request_deserializer=core__pb2.GetEmaneModelConfigRequest.FromString, + response_serializer=core__pb2.GetEmaneModelConfigResponse.SerializeToString, + ), + 'SetEmaneModelConfig': grpc.unary_unary_rpc_method_handler( + servicer.SetEmaneModelConfig, + request_deserializer=core__pb2.SetEmaneModelConfigRequest.FromString, + response_serializer=core__pb2.SetEmaneModelConfigResponse.SerializeToString, + ), + 'GetEmaneModelConfigs': grpc.unary_unary_rpc_method_handler( + servicer.GetEmaneModelConfigs, + request_deserializer=core__pb2.GetEmaneModelConfigsRequest.FromString, + response_serializer=core__pb2.GetEmaneModelConfigsResponse.SerializeToString, + ), + 'SaveXml': grpc.unary_unary_rpc_method_handler( + servicer.SaveXml, + request_deserializer=core__pb2.SaveXmlRequest.FromString, + response_serializer=core__pb2.SaveXmlResponse.SerializeToString, + ), + 'OpenXml': grpc.unary_unary_rpc_method_handler( + servicer.OpenXml, + request_deserializer=core__pb2.OpenXmlRequest.FromString, + response_serializer=core__pb2.OpenXmlResponse.SerializeToString, + ), + } + generic_handler = grpc.method_handlers_generic_handler( + 'core.CoreApi', rpc_method_handlers) + server.add_generic_rpc_handlers((generic_handler,)) diff --git a/daemon/core/grpc/server.py b/daemon/core/api/grpc/server.py similarity index 98% rename from daemon/core/grpc/server.py rename to daemon/core/api/grpc/server.py index bc18594a..7cec9010 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -9,13 +9,13 @@ import grpc from concurrent import futures from core.emulator.emudata import NodeOptions, InterfaceData, LinkOptions -from core.enumerations import NodeTypes, EventTypes, LinkTypes -from core.grpc import core_pb2 -from core.grpc import core_pb2_grpc -from core.misc import nodeutils -from core.misc.ipaddress import MacAddress -from core.mobility import BasicRangeModel, Ns2ScriptedMobility -from core.service import ServiceManager +from core.emulator.enumerations import NodeTypes, EventTypes, LinkTypes +from core.api.grpc import core_pb2 +from core.api.grpc import core_pb2_grpc +from core.nodes import nodeutils +from core.nodes.ipaddress import MacAddress +from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility +from core.services.coreservices import ServiceManager _ONE_DAY_IN_SECONDS = 60 * 60 * 24 @@ -68,7 +68,7 @@ def get_emane_model_id(_id, interface): def convert_link(session, link_data): interface_one = None if link_data.interface1_id is not None: - node = session.get_object(link_data.node1_id) + node = session.get_node(link_data.node1_id) interface = node.netif(link_data.interface1_id) interface_one = core_pb2.Interface( id=link_data.interface1_id, name=interface.name, mac=convert_value(link_data.interface1_mac), @@ -77,7 +77,7 @@ def convert_link(session, link_data): interface_two = None if link_data.interface2_id is not None: - node = session.get_object(link_data.node2_id) + node = session.get_node(link_data.node2_id) interface = node.netif(link_data.interface2_id) interface_two = core_pb2.Interface( id=link_data.interface2_id, name=interface.name, mac=convert_value(link_data.interface2_mac), @@ -143,7 +143,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def get_node(self, session, _id, context): try: - return session.get_object(_id) + return session.get_node(_id) except KeyError: context.abort(grpc.StatusCode.NOT_FOUND, "node {} not found".format(_id)) @@ -233,8 +233,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): links = [] nodes = [] - for node_id in session.objects: - node = session.objects[node_id] + for _id in session.nodes: + node = session.nodes[_id] if not isinstance(node.id, int): continue diff --git a/daemon/core/misc/__init__.py b/daemon/core/api/tlv/__init__.py similarity index 100% rename from daemon/core/misc/__init__.py rename to daemon/core/api/tlv/__init__.py diff --git a/daemon/core/broker.py b/daemon/core/api/tlv/broker.py similarity index 96% rename from daemon/core/broker.py rename to daemon/core/api/tlv/broker.py index c20a4a41..fdb28101 100644 --- a/daemon/core/broker.py +++ b/daemon/core/api/tlv/broker.py @@ -10,27 +10,26 @@ import select import socket import threading -from core.api import coreapi -from core.coreobj import PyCoreNet -from core.coreobj import PyCoreNode -from core.enumerations import ConfigDataTypes -from core.enumerations import ConfigFlags -from core.enumerations import ConfigTlvs -from core.enumerations import EventTlvs -from core.enumerations import EventTypes -from core.enumerations import ExecuteTlvs -from core.enumerations import FileTlvs -from core.enumerations import LinkTlvs -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.enumerations import NodeTlvs -from core.enumerations import NodeTypes -from core.enumerations import RegisterTlvs -from core.misc import nodeutils -from core.misc.ipaddress import IpAddress -from core.netns.vif import GreTap -from core.netns.vnet import GreTapBridge -from core.phys.pnodes import PhysicalNode +from core.api.tlv import coreapi +from core.nodes.base import CoreNodeBase, CoreNetworkBase +from core.emulator.enumerations import ConfigDataTypes +from core.emulator.enumerations import ConfigFlags +from core.emulator.enumerations import ConfigTlvs +from core.emulator.enumerations import EventTlvs +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import ExecuteTlvs +from core.emulator.enumerations import FileTlvs +from core.emulator.enumerations import LinkTlvs +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.emulator.enumerations import NodeTlvs +from core.emulator.enumerations import NodeTypes +from core.emulator.enumerations import RegisterTlvs +from core.nodes import nodeutils +from core.nodes.ipaddress import IpAddress +from core.nodes.interface import GreTap +from core.nodes.network import GreTapBridge +from core.nodes.physical import PhysicalNode class CoreDistributedServer(object): @@ -420,7 +419,7 @@ class CoreBroker(object): gt = GreTap(node=None, name=None, session=self.session, remoteip=remoteip, key=key) else: - gt = self.session.add_object(cls=GreTapBridge, _id=_id, policy="ACCEPT", remoteip=remoteip, key=key) + gt = self.session.create_node(cls=GreTapBridge, _id=_id, policy="ACCEPT", remoteip=remoteip, key=key) gt.localnum = localnum gt.remotenum = remotenum self.tunnels[key] = gt @@ -443,7 +442,7 @@ class CoreBroker(object): :rtype: list """ try: - net = self.session.get_object(node_id) + net = self.session.get_node(node_id) logging.info("adding net tunnel for: id(%s) %s", node_id, net) except KeyError: raise KeyError("network node %s not found" % node_id) @@ -517,7 +516,7 @@ class CoreBroker(object): except KeyError: gt = None if gt: - self.session.delete_object(gt.id) + self.session.delete_node(gt.id) del gt def gettunnel(self, n1num, n2num): @@ -757,7 +756,7 @@ class CoreBroker(object): if nodecls is None: logging.warn("broker unimplemented node type %s", nodetype) return handle_locally, servers - if issubclass(nodecls, PyCoreNet) and nodetype != NodeTypes.WIRELESS_LAN.value: + if issubclass(nodecls, CoreNetworkBase) and nodetype != NodeTypes.WIRELESS_LAN.value: # network node replicated on all servers; could be optimized # don"t replicate WLANs, because ebtables rules won"t work servers = self.getservers() @@ -768,7 +767,7 @@ class CoreBroker(object): # do not record server name for networks since network # nodes are replicated across all server return handle_locally, servers - elif issubclass(nodecls, PyCoreNode): + elif issubclass(nodecls, CoreNodeBase): name = message.get_tlv(NodeTlvs.NAME.value) if name: serverfiletxt = "%s %s %s" % (n, name, nodecls) diff --git a/daemon/core/api/coreapi.py b/daemon/core/api/tlv/coreapi.py similarity index 97% rename from daemon/core/api/coreapi.py rename to daemon/core/api/tlv/coreapi.py index cbdf8d97..afc35a11 100644 --- a/daemon/core/api/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -10,22 +10,22 @@ import struct from enum import Enum -from core.enumerations import ConfigTlvs -from core.enumerations import EventTlvs -from core.enumerations import EventTypes -from core.enumerations import ExceptionTlvs -from core.enumerations import ExecuteTlvs -from core.enumerations import FileTlvs -from core.enumerations import InterfaceTlvs -from core.enumerations import LinkTlvs -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.enumerations import NodeTlvs -from core.enumerations import RegisterTlvs -from core.enumerations import SessionTlvs -from core.misc import structutils -from core.misc.ipaddress import IpAddress -from core.misc.ipaddress import MacAddress +from core.emulator.enumerations import ConfigTlvs +from core.emulator.enumerations import EventTlvs +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import ExceptionTlvs +from core.emulator.enumerations import ExecuteTlvs +from core.emulator.enumerations import FileTlvs +from core.emulator.enumerations import InterfaceTlvs +from core.emulator.enumerations import LinkTlvs +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.emulator.enumerations import NodeTlvs +from core.emulator.enumerations import RegisterTlvs +from core.emulator.enumerations import SessionTlvs +from core.api.tlv import structutils +from core.nodes.ipaddress import IpAddress +from core.nodes.ipaddress import MacAddress class CoreTlvData(object): diff --git a/daemon/core/corehandlers.py b/daemon/core/api/tlv/corehandlers.py similarity index 97% rename from daemon/core/corehandlers.py rename to daemon/core/api/tlv/corehandlers.py index 5eaaa4cd..90436806 100644 --- a/daemon/core/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -13,36 +13,34 @@ import threading import time from itertools import repeat -from core.api import coreapi -from core.api import dataconversion -from core.conf import ConfigShim -from core.data import ConfigData, ExceptionData -from core.data import EventData -from core.data import FileData +from core.api.tlv import coreapi, dataconversion, structutils +from core.config import ConfigShim +from core.emulator.data import ConfigData, ExceptionData +from core.emulator.data import EventData +from core.emulator.data import FileData from core.emulator.emudata import InterfaceData from core.emulator.emudata import LinkOptions from core.emulator.emudata import NodeOptions -from core.enumerations import ConfigDataTypes -from core.enumerations import ConfigFlags -from core.enumerations import ConfigTlvs -from core.enumerations import EventTlvs -from core.enumerations import EventTypes -from core.enumerations import ExceptionTlvs -from core.enumerations import ExecuteTlvs -from core.enumerations import FileTlvs -from core.enumerations import LinkTlvs -from core.enumerations import LinkTypes -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.enumerations import NodeTlvs -from core.enumerations import NodeTypes -from core.enumerations import RegisterTlvs -from core.enumerations import SessionTlvs -from core.misc import nodeutils -from core.misc import structutils -from core.misc import utils -from core.service import ServiceManager -from core.service import ServiceShim +from core.emulator.enumerations import ConfigDataTypes +from core.emulator.enumerations import ConfigFlags +from core.emulator.enumerations import ConfigTlvs +from core.emulator.enumerations import EventTlvs +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import ExceptionTlvs +from core.emulator.enumerations import ExecuteTlvs +from core.emulator.enumerations import FileTlvs +from core.emulator.enumerations import LinkTlvs +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.emulator.enumerations import NodeTlvs +from core.emulator.enumerations import NodeTypes +from core.emulator.enumerations import RegisterTlvs +from core.emulator.enumerations import SessionTlvs +from core.nodes import nodeutils +from core import utils +from core.services.coreservices import ServiceManager +from core.services.coreservices import ServiceShim class CoreHandler(SocketServer.BaseRequestHandler): @@ -774,7 +772,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): return () try: - node = self.session.get_object(node_num) + node = self.session.get_node(node_num) # build common TLV items for reply tlv_data = "" @@ -1135,7 +1133,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): if not node_id: return replies - node = self.session.get_object(node_id) + node = self.session.get_node(node_id) if node is None: logging.warn("request to configure service for unknown node %s", node_id) return replies @@ -1418,7 +1416,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): if event_type.value <= EventTypes.SHUTDOWN_STATE.value: if node_id is not None: try: - node = self.session.get_object(node_id) + node = self.session.get_node(node_id) except KeyError: raise KeyError("Event message for unknown node %d" % node_id) @@ -1442,7 +1440,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): self.session.instantiate() # after booting nodes attempt to send emulation id for nodes waiting on status - for _id in self.session.objects: + for _id in self.session.nodes: self.send_node_emulation_id(_id) elif event_type == EventTypes.RUNTIME_STATE: if self.session.master: @@ -1508,7 +1506,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): name = event_data.name try: - node = self.session.get_object(node_id) + node = self.session.get_node(node_id) except KeyError: logging.warn("ignoring event for service '%s', unknown node '%s'", name, node_id) return @@ -1679,13 +1677,13 @@ class CoreHandler(SocketServer.BaseRequestHandler): nodes_data = [] links_data = [] - with self.session._objects_lock: - for obj in self.session.objects.itervalues(): - node_data = obj.data(message_type=MessageFlags.ADD.value) + with self.session._nodes_lock: + for node in self.session.nodes.itervalues(): + node_data = node.data(message_type=MessageFlags.ADD.value) if node_data: nodes_data.append(node_data) - node_links = obj.all_link_data(flags=MessageFlags.ADD.value) + node_links = node.all_link_data(flags=MessageFlags.ADD.value) for link_data in node_links: links_data.append(link_data) @@ -1717,7 +1715,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): for node_id, service in service_configs: opaque = "service:%s" % service.name data_types = tuple(repeat(ConfigDataTypes.STRING.value, len(ServiceShim.keys))) - node = self.session.get_object(node_id) + node = self.session.get_node(node_id) values = ServiceShim.tovaluelist(node, service) config_data = ConfigData( message_type=0, diff --git a/daemon/core/coreserver.py b/daemon/core/api/tlv/coreserver.py similarity index 100% rename from daemon/core/coreserver.py rename to daemon/core/api/tlv/coreserver.py diff --git a/daemon/core/api/dataconversion.py b/daemon/core/api/tlv/dataconversion.py similarity index 94% rename from daemon/core/api/dataconversion.py rename to daemon/core/api/tlv/dataconversion.py index 5bc33fa9..781afbc2 100644 --- a/daemon/core/api/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -2,10 +2,9 @@ Converts CORE data objects into legacy API messages. """ -from core.api import coreapi -from core.enumerations import ConfigTlvs -from core.enumerations import NodeTlvs -from core.misc import structutils +from core.api.tlv import coreapi, structutils +from core.emulator.enumerations import ConfigTlvs +from core.emulator.enumerations import NodeTlvs def convert_node(node_data): diff --git a/daemon/core/misc/structutils.py b/daemon/core/api/tlv/structutils.py similarity index 100% rename from daemon/core/misc/structutils.py rename to daemon/core/api/tlv/structutils.py diff --git a/daemon/core/conf.py b/daemon/core/config.py similarity index 99% rename from daemon/core/conf.py rename to daemon/core/config.py index b4e9c65f..d62ba33b 100644 --- a/daemon/core/conf.py +++ b/daemon/core/config.py @@ -5,7 +5,7 @@ Common support for configurable CORE objects. import logging from collections import OrderedDict -from core.data import ConfigData +from core.emulator.data import ConfigData class ConfigShim(object): diff --git a/daemon/core/coreobj.py b/daemon/core/coreobj.py deleted file mode 100644 index a4ab9467..00000000 --- a/daemon/core/coreobj.py +++ /dev/null @@ -1,758 +0,0 @@ -""" -Defines the basic objects for CORE emulation: the PyCoreObj base class, along with PyCoreNode, -PyCoreNet, and PyCoreNetIf. -""" - -import os -import shutil -import socket -import threading -from socket import AF_INET -from socket import AF_INET6 - -from core.data import NodeData, LinkData -from core.enumerations import LinkTypes -from core.misc import ipaddress - - -class Position(object): - """ - Helper class for Cartesian coordinate position - """ - - def __init__(self, x=None, y=None, z=None): - """ - Creates a Position instance. - - :param x: x position - :param y: y position - :param z: z position - :return: - """ - self.x = x - self.y = y - self.z = z - - def set(self, x=None, y=None, z=None): - """ - Returns True if the position has actually changed. - - :param float x: x position - :param float y: y position - :param float z: z position - :return: True if position changed, False otherwise - :rtype: bool - """ - if self.x == x and self.y == y and self.z == z: - return False - self.x = x - self.y = y - self.z = z - return True - - def get(self): - """ - Retrieve x,y,z position. - - :return: x,y,z position tuple - :rtype: tuple - """ - return self.x, self.y, self.z - - -class PyCoreObj(object): - """ - Base class for CORE objects (nodes and networks) - """ - apitype = None - - # TODO: appears start has no usage, verify and remove - def __init__(self, session, _id=None, name=None, start=True): - """ - Creates a PyCoreObj instance. - - :param core.session.Session session: CORE session object - :param int _id: id - :param str name: object name - :param bool start: start value - :return: - """ - - self.session = session - if _id is None: - _id = session.get_node_id() - self.id = _id - if name is None: - name = "o%s" % self.id - self.name = name - self.type = None - self.server = None - self.services = None - # ifindex is key, PyCoreNetIf instance is value - self._netif = {} - self.ifindex = 0 - self.canvas = None - self.icon = None - self.opaque = None - self.position = Position() - - def startup(self): - """ - Each object implements its own startup method. - - :return: nothing - """ - raise NotImplementedError - - def shutdown(self): - """ - Each object implements its own shutdown method. - - :return: nothing - """ - raise NotImplementedError - - def setposition(self, x=None, y=None, z=None): - """ - Set the (x,y,z) position of the object. - - :param float x: x position - :param float y: y position - :param float z: z position - :return: True if position changed, False otherwise - :rtype: bool - """ - return self.position.set(x=x, y=y, z=z) - - def getposition(self): - """ - Return an (x,y,z) tuple representing this object's position. - - :return: x,y,z position tuple - :rtype: tuple - """ - return self.position.get() - - def ifname(self, ifindex): - """ - Retrieve interface name for index. - - :param int ifindex: interface index - :return: interface name - :rtype: str - """ - return self._netif[ifindex].name - - def netifs(self, sort=False): - """ - Retrieve network interfaces, sorted if desired. - - :param bool sort: boolean used to determine if interfaces should be sorted - :return: network interfaces - :rtype: list - """ - if sort: - return map(lambda k: self._netif[k], sorted(self._netif.keys())) - else: - return self._netif.itervalues() - - def numnetif(self): - """ - Return the attached interface count. - - :return: number of network interfaces - :rtype: int - """ - return len(self._netif) - - def getifindex(self, netif): - """ - Retrieve index for an interface. - - :param PyCoreNetIf netif: interface to get index for - :return: interface index if found, -1 otherwise - :rtype: int - """ - - for ifindex in self._netif: - if self._netif[ifindex] is netif: - return ifindex - - return -1 - - def newifindex(self): - """ - Create a new interface index. - - :return: interface index - :rtype: int - """ - while self.ifindex in self._netif: - self.ifindex += 1 - ifindex = self.ifindex - self.ifindex += 1 - return ifindex - - def data(self, message_type, lat=None, lon=None, alt=None): - """ - Build a data object for this node. - - :param message_type: purpose for the data object we are creating - :param str lat: latitude - :param str lon: longitude - :param str alt: altitude - :return: node data object - :rtype: core.data.NodeData - """ - if self.apitype is None: - return None - - x, y, _ = self.getposition() - model = self.type - emulation_server = self.server - - services = self.services - if services is not None: - services = "|".join([service.name for service in services]) - - node_data = NodeData( - message_type=message_type, - id=self.id, - node_type=self.apitype, - name=self.name, - emulation_id=self.id, - canvas=self.canvas, - icon=self.icon, - opaque=self.opaque, - x_position=x, - y_position=y, - latitude=lat, - longitude=lon, - altitude=alt, - model=model, - emulation_server=emulation_server, - services=services - ) - - return node_data - - def all_link_data(self, flags): - """ - Build CORE Link data for this object. There is no default - method for PyCoreObjs as PyCoreNodes do not implement this but - PyCoreNets do. - - :param flags: message flags - :return: list of link data - :rtype: core.data.LinkData - """ - return [] - - -class PyCoreNode(PyCoreObj): - """ - Base class for CORE nodes. - """ - - def __init__(self, session, _id=None, name=None, start=True): - """ - Create a PyCoreNode instance. - - :param core.session.Session session: CORE session object - :param int _id: object id - :param str name: object name - :param bool start: boolean for starting - """ - super(PyCoreNode, self).__init__(session, _id, name, start=start) - self.services = [] - self.nodedir = None - self.tmpnodedir = False - - def addservice(self, service): - """ - Add a services to the service list. - - :param core.service.CoreService service: service to add - :return: nothing - """ - if service is not None: - self.services.append(service) - - def makenodedir(self): - """ - Create the node directory. - - :return: nothing - """ - if self.nodedir is None: - self.nodedir = os.path.join(self.session.session_dir, self.name + ".conf") - os.makedirs(self.nodedir) - self.tmpnodedir = True - else: - self.tmpnodedir = False - - def rmnodedir(self): - """ - Remove the node directory, unless preserve directory has been set. - - :return: nothing - """ - preserve = self.session.options.get_config("preservedir") == "1" - if preserve: - return - - if self.tmpnodedir: - shutil.rmtree(self.nodedir, ignore_errors=True) - - def addnetif(self, netif, ifindex): - """ - Add network interface to node and set the network interface index if successful. - - :param PyCoreNetIf netif: network interface to add - :param int ifindex: interface index - :return: nothing - """ - if ifindex in self._netif: - raise ValueError("ifindex %s already exists" % ifindex) - self._netif[ifindex] = netif - # TODO: this should have probably been set ahead, seems bad to me, check for failure and fix - netif.netindex = ifindex - - def delnetif(self, ifindex): - """ - Delete a network interface - - :param int ifindex: interface index to delete - :return: nothing - """ - if ifindex not in self._netif: - raise ValueError("ifindex %s does not exist" % ifindex) - netif = self._netif.pop(ifindex) - netif.shutdown() - del netif - - # TODO: net parameter is not used, remove - def netif(self, ifindex, net=None): - """ - Retrieve network interface. - - :param int ifindex: index of interface to retrieve - :param PyCoreNetIf net: network node - :return: network interface, or None if not found - :rtype: PyCoreNetIf - """ - if ifindex in self._netif: - return self._netif[ifindex] - else: - return None - - def attachnet(self, ifindex, net): - """ - Attach a network. - - :param int ifindex: interface of index to attach - :param PyCoreNetIf net: network to attach - :return: - """ - if ifindex not in self._netif: - raise ValueError("ifindex %s does not exist" % ifindex) - self._netif[ifindex].attachnet(net) - - def detachnet(self, ifindex): - """ - Detach network interface. - - :param int ifindex: interface index to detach - :return: nothing - """ - if ifindex not in self._netif: - raise ValueError("ifindex %s does not exist" % ifindex) - self._netif[ifindex].detachnet() - - def setposition(self, x=None, y=None, z=None): - """ - Set position. - - :param x: x position - :param y: y position - :param z: z position - :return: nothing - """ - changed = super(PyCoreNode, self).setposition(x, y, z) - if changed: - for netif in self.netifs(sort=True): - netif.setposition(x, y, z) - - def commonnets(self, obj, want_ctrl=False): - """ - Given another node or net object, return common networks between - this node and that object. A list of tuples is returned, with each tuple - consisting of (network, interface1, interface2). - - :param obj: object to get common network with - :param want_ctrl: flag set to determine if control network are wanted - :return: tuples of common networks - :rtype: list - """ - common = [] - for netif1 in self.netifs(): - if not want_ctrl and hasattr(netif1, "control"): - continue - for netif2 in obj.netifs(): - if netif1.net == netif2.net: - common.append((netif1.net, netif1, netif2)) - - return common - - def check_cmd(self, args): - """ - Runs shell command on node. - - :param list[str]|str args: command to run - :return: combined stdout and stderr - :rtype: str - :raises CoreCommandError: when a non-zero exit status occurs - """ - raise NotImplementedError - - def cmd(self, args, wait=True): - """ - Runs shell command on node, with option to not wait for a result. - - :param list[str]|str args: command to run - :param bool wait: wait for command to exit, defaults to True - :return: exit status for command - :rtype: int - """ - raise NotImplementedError - - def cmd_output(self, args): - """ - Runs shell command on node and get exit status and output. - - :param list[str]|str args: command to run - :return: exit status and combined stdout and stderr - :rtype: tuple[int, str] - """ - raise NotImplementedError - - def termcmdstring(self, sh): - """ - Create a terminal command string. - - :param str sh: shell to execute command in - :return: str - """ - raise NotImplementedError - - -class PyCoreNet(PyCoreObj): - """ - Base class for networks - """ - linktype = LinkTypes.WIRED.value - - def __init__(self, session, _id, name, start=True): - """ - Create a PyCoreNet instance. - - :param core.session.Session session: CORE session object - :param int _id: object id - :param str name: object name - :param bool start: should object start - """ - super(PyCoreNet, self).__init__(session, _id, name, start=start) - self._linked = {} - self._linked_lock = threading.Lock() - - def startup(self): - """ - Each object implements its own startup method. - - :return: nothing - """ - raise NotImplementedError - - def shutdown(self): - """ - Each object implements its own shutdown method. - - :return: nothing - """ - raise NotImplementedError - - def attach(self, netif): - """ - Attach network interface. - - :param PyCoreNetIf netif: network interface to attach - :return: nothing - """ - i = self.newifindex() - self._netif[i] = netif - netif.netifi = i - with self._linked_lock: - self._linked[netif] = {} - - def detach(self, netif): - """ - Detach network interface. - - :param PyCoreNetIf netif: network interface to detach - :return: nothing - """ - del self._netif[netif.netifi] - netif.netifi = None - with self._linked_lock: - del self._linked[netif] - - def all_link_data(self, flags): - """ - Build link data objects for this network. Each link object describes a link - between this network and a node. - """ - all_links = [] - - # build a link message from this network node to each node having a - # connected interface - for netif in self.netifs(sort=True): - if not hasattr(netif, "node"): - continue - linked_node = netif.node - uni = False - if linked_node is None: - # two layer-2 switches/hubs linked together via linknet() - if not hasattr(netif, "othernet"): - continue - linked_node = netif.othernet - if linked_node.id == self.id: - continue - netif.swapparams('_params_up') - upstream_params = netif.getparams() - netif.swapparams('_params_up') - if netif.getparams() != upstream_params: - uni = True - - unidirectional = 0 - if uni: - unidirectional = 1 - - interface2_ip4 = None - interface2_ip4_mask = None - interface2_ip6 = None - interface2_ip6_mask = None - for address in netif.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if ipaddress.is_ipv4_address(ip): - family = AF_INET - ipl = socket.inet_pton(family, ip) - interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) - interface2_ip4_mask = mask - else: - family = AF_INET6 - ipl = socket.inet_pton(family, ip) - interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) - interface2_ip6_mask = mask - - link_data = LinkData( - message_type=flags, - node1_id=self.id, - node2_id=linked_node.id, - link_type=self.linktype, - unidirectional=unidirectional, - interface2_id=linked_node.getifindex(netif), - interface2_mac=netif.hwaddr, - interface2_ip4=interface2_ip4, - interface2_ip4_mask=interface2_ip4_mask, - interface2_ip6=interface2_ip6, - interface2_ip6_mask=interface2_ip6_mask, - delay=netif.getparam("delay"), - bandwidth=netif.getparam("bw"), - dup=netif.getparam("duplicate"), - jitter=netif.getparam("jitter"), - per=netif.getparam("loss") - ) - - all_links.append(link_data) - - if not uni: - continue - - netif.swapparams('_params_up') - link_data = LinkData( - message_type=0, - node1_id=linked_node.id, - node2_id=self.id, - unidirectional=1, - delay=netif.getparam("delay"), - bandwidth=netif.getparam("bw"), - dup=netif.getparam("duplicate"), - jitter=netif.getparam("jitter"), - per=netif.getparam("loss") - ) - netif.swapparams('_params_up') - - all_links.append(link_data) - - return all_links - - -class PyCoreNetIf(object): - """ - Base class for network interfaces. - """ - - def __init__(self, node, name, mtu): - """ - Creates a PyCoreNetIf instance. - - :param core.coreobj.PyCoreNode node: node for interface - :param str name: interface name - :param mtu: mtu value - """ - - self.node = node - self.name = name - if not isinstance(mtu, (int, long)): - raise ValueError - self.mtu = mtu - self.net = None - self._params = {} - self.addrlist = [] - self.hwaddr = None - # placeholder position hook - self.poshook = lambda a, b, c, d: None - # used with EMANE - self.transport_type = None - # interface index on the network - self.netindex = None - # index used to find flow data - self.flow_id = None - - def startup(self): - """ - Startup method for the interface. - - :return: nothing - """ - pass - - def shutdown(self): - """ - Shutdown method for the interface. - - :return: nothing - """ - pass - - def attachnet(self, net): - """ - Attach network. - - :param core.coreobj.PyCoreNet net: network to attach - :return: nothing - """ - if self.net: - self.detachnet() - self.net = None - - net.attach(self) - self.net = net - - def detachnet(self): - """ - Detach from a network. - - :return: nothing - """ - if self.net is not None: - self.net.detach(self) - - def addaddr(self, addr): - """ - Add address. - - :param str addr: address to add - :return: nothing - """ - - self.addrlist.append(addr) - - def deladdr(self, addr): - """ - Delete address. - - :param str addr: address to delete - :return: nothing - """ - self.addrlist.remove(addr) - - def sethwaddr(self, addr): - """ - Set hardware address. - - :param core.misc.ipaddress.MacAddress addr: hardware address to set to. - :return: nothing - """ - self.hwaddr = addr - - def getparam(self, key): - """ - Retrieve a parameter from the, or None if the parameter does not exist. - - :param key: parameter to get value for - :return: parameter value - """ - return self._params.get(key) - - def getparams(self): - """ - Return (key, value) pairs for parameters. - """ - parameters = [] - for k in sorted(self._params.keys()): - parameters.append((k, self._params[k])) - return parameters - - def setparam(self, key, value): - """ - Set a parameter value, returns True if the parameter has changed. - - :param key: parameter name to set - :param value: parameter value - :return: True if parameter changed, False otherwise - """ - # treat None and 0 as unchanged values - current_value = self._params.get(key) - if current_value == value or current_value <= 0 and value <= 0: - return False - - self._params[key] = value - return True - - def swapparams(self, name): - """ - Swap out parameters dict for name. If name does not exist, - intialize it. This is for supporting separate upstream/downstream - parameters when two layer-2 nodes are linked together. - - :param str name: name of parameter to swap - :return: nothing - """ - tmp = self._params - if not hasattr(self, name): - setattr(self, name, {}) - self._params = getattr(self, name) - setattr(self, name, tmp) - - def setposition(self, x, y, z): - """ - Dispatch position hook handler. - - :param x: x position - :param y: y position - :param z: z position - :return: nothing - """ - self.poshook(self, x, y, z) diff --git a/daemon/core/emane/bypass.py b/daemon/core/emane/bypass.py index 91a01b37..824bfc68 100644 --- a/daemon/core/emane/bypass.py +++ b/daemon/core/emane/bypass.py @@ -1,10 +1,10 @@ """ EMANE Bypass model for CORE """ -from core.conf import ConfigGroup -from core.conf import Configuration +from core.config import ConfigGroup +from core.config import Configuration from core.emane import emanemodel -from core.enumerations import ConfigDataTypes +from core.emulator.enumerations import ConfigDataTypes class EmaneBypassModel(emanemodel.EmaneModel): diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 62676c16..e9d9c9fe 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -7,7 +7,7 @@ import os from lxml import etree -from core.conf import ConfigGroup +from core.config import ConfigGroup from core.emane import emanemanifest from core.emane import emanemodel from core.xml import emanexml @@ -80,7 +80,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): transport_type = "virtual" if interface and interface.transport_type == "raw": transport_type = "raw" - transport_file = emanexml.transport_file_name(self.object_id, transport_type) + transport_file = emanexml.transport_file_name(self.id, transport_type) etree.SubElement(nem_element, "transport", definition=transport_file) # set shim configuration @@ -125,7 +125,7 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): # TODO: batch these into multiple events per transmission # TODO: may want to split out seconds portion of delay and jitter event = CommEffectEvent() - emane_node = self.session.get_object(self.object_id) + emane_node = self.session.get_node(self.id) nemid = emane_node.getnemid(netif) nemid2 = emane_node.getnemid(netif2) mbw = bw diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 44e23831..2dc0d191 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -6,14 +6,13 @@ import logging import os import threading -from core import CoreCommandError +from core import CoreCommandError, utils from core import constants -from core.api import coreapi -from core.api import dataconversion -from core.conf import ConfigGroup -from core.conf import ConfigShim -from core.conf import Configuration -from core.conf import ModelManager +from core.api.tlv import coreapi, dataconversion +from core.config import ConfigGroup +from core.config import ConfigShim +from core.config import Configuration +from core.config import ModelManager from core.emane import emanemanifest from core.emane.bypass import EmaneBypassModel from core.emane.commeffect import EmaneCommEffectModel @@ -21,15 +20,14 @@ from core.emane.emanemodel import EmaneModel from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.rfpipe import EmaneRfPipeModel from core.emane.tdma import EmaneTdmaModel -from core.enumerations import ConfigDataTypes -from core.enumerations import ConfigFlags -from core.enumerations import ConfigTlvs -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.enumerations import NodeTypes -from core.enumerations import RegisterTlvs -from core.misc import nodeutils -from core.misc import utils +from core.emulator.enumerations import ConfigDataTypes +from core.emulator.enumerations import ConfigFlags +from core.emulator.enumerations import ConfigTlvs +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.emulator.enumerations import NodeTypes +from core.emulator.enumerations import RegisterTlvs +from core.nodes import nodeutils from core.xml import emanexml try: @@ -251,8 +249,8 @@ class EmaneManager(ModelManager): logging.debug("emane setup") # TODO: drive this from the session object - with self.session._objects_lock: - for node in self.session.objects.itervalues(): + with self.session._nodes_lock: + for node in self.session.nodes.itervalues(): if nodeutils.is_node(node, NodeTypes.EMANE): logging.debug("adding emane node: id(%s) name(%s)", node.id, node.name) self.add_node(node) @@ -845,7 +843,7 @@ class EmaneManager(ModelManager): # generate a node message for this location update try: - node = self.session.get_object(n) + node = self.session.get_node(n) except KeyError: logging.exception("location event NEM %s has no corresponding node %s" % (nemid, n)) return False @@ -906,8 +904,8 @@ class EmaneGlobalModel(EmaneModel): ConfigGroup("NEM Parameters", emulator_len + 1, config_len) ] - def __init__(self, session, object_id=None): - super(EmaneGlobalModel, self).__init__(session, object_id) + def __init__(self, session, _id=None): + super(EmaneGlobalModel, self).__init__(session, _id) def build_xml_files(self, config, interface=None): raise NotImplementedError diff --git a/daemon/core/emane/emanemanifest.py b/daemon/core/emane/emanemanifest.py index b07833c9..8f10737c 100644 --- a/daemon/core/emane/emanemanifest.py +++ b/daemon/core/emane/emanemanifest.py @@ -1,7 +1,7 @@ import logging -from core.conf import Configuration -from core.enumerations import ConfigDataTypes +from core.config import Configuration +from core.emulator.enumerations import ConfigDataTypes manifest = None try: diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 01bf1835..6e5b641d 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -4,11 +4,11 @@ Defines Emane Models used within CORE. import logging import os -from core.conf import ConfigGroup -from core.conf import Configuration +from core.config import ConfigGroup +from core.config import Configuration from core.emane import emanemanifest -from core.enumerations import ConfigDataTypes -from core.mobility import WirelessModel +from core.emulator.enumerations import ConfigDataTypes +from core.location.mobility import WirelessModel from core.xml import emanexml @@ -105,7 +105,7 @@ class EmaneModel(WirelessModel): transport_type = "virtual" if interface and interface.transport_type == "raw": transport_type = "raw" - transport_name = emanexml.transport_file_name(self.object_id, transport_type) + transport_name = emanexml.transport_file_name(self.id, transport_type) # create nem xml file nem_file = os.path.join(self.session.session_dir, nem_name) @@ -138,7 +138,7 @@ class EmaneModel(WirelessModel): :return: """ try: - wlan = self.session.get_object(self.object_id) + wlan = self.session.get_node(self.id) wlan.setnempositions(moved_netifs) except KeyError: logging.exception("error during update") diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 016dc031..cbbec693 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -6,10 +6,10 @@ share the same MAC+PHY model. import logging -from core.coreobj import PyCoreNet -from core.enumerations import LinkTypes -from core.enumerations import NodeTypes -from core.enumerations import RegisterTlvs +from core.nodes.base import CoreNetworkBase +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import NodeTypes +from core.emulator.enumerations import RegisterTlvs try: from emane.events import LocationEvent @@ -20,7 +20,7 @@ except ImportError: logging.debug("compatible emane python bindings not installed") -class EmaneNet(PyCoreNet): +class EmaneNet(CoreNetworkBase): """ EMANE network base class. """ @@ -80,10 +80,10 @@ class EmaneNode(EmaneNet): if model.config_type == RegisterTlvs.WIRELESS.value: # EmaneModel really uses values from ConfigurableManager # when buildnemxml() is called, not during init() - self.model = model(session=self.session, object_id=self.id) + self.model = model(session=self.session, _id=self.id) self.model.update_config(config) elif model.config_type == RegisterTlvs.MOBILITY.value: - self.mobility = model(session=self.session, object_id=self.id) + self.mobility = model(session=self.session, _id=self.id) self.mobility.update_config(config) def setnemid(self, netif, nemid): diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index b599638d..5507ae65 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -5,11 +5,10 @@ tdma.py: EMANE TDMA model bindings for CORE import logging import os -from core import constants -from core.conf import Configuration +from core import constants, utils +from core.config import Configuration from core.emane import emanemodel -from core.enumerations import ConfigDataTypes -from core.misc import utils +from core.emulator.enumerations import ConfigDataTypes class EmaneTdmaModel(emanemodel.EmaneModel): @@ -49,7 +48,7 @@ class EmaneTdmaModel(emanemodel.EmaneModel): :return: nothing """ # get configured schedule - config = self.session.emane.get_configs(node_id=self.object_id, config_type=self.name) + config = self.session.emane.get_configs(node_id=self.id, config_type=self.name) if not config: return schedule = config[self.schedule_name] diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index a95a347d..467ea145 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -5,18 +5,16 @@ import signal import sys import core.services -from core.coreobj import PyCoreNet -from core.coreobj import PyCoreNode -from core.data import NodeData +from core.nodes.base import CoreNodeBase, CoreNetworkBase +from core.emulator.data import NodeData from core.emulator.emudata import LinkOptions from core.emulator.emudata import NodeOptions -from core.enumerations import EventTypes -from core.enumerations import LinkTypes -from core.enumerations import NodeTypes -from core.misc import nodemaps -from core.misc import nodeutils -from core.service import ServiceManager -from core.session import Session +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import NodeTypes +from core.nodes import nodeutils, nodemaps +from core.services.coreservices import ServiceManager +from core.emulator.session import Session from core.xml.corexml import CoreXmlReader, CoreXmlWriter @@ -94,7 +92,7 @@ def is_net_node(node): :return: True if object is an instance of a network node, False otherwise :rtype: bool """ - return isinstance(node, PyCoreNet) + return isinstance(node, CoreNetworkBase) def is_core_node(node): @@ -105,7 +103,7 @@ def is_core_node(node): :return: True if object is an instance of a core node, False otherwise :rtype: bool """ - return isinstance(node, PyCoreNode) + return isinstance(node, CoreNodeBase) class IdGen(object): @@ -149,8 +147,8 @@ class EmuSession(Session): net_two = None # retrieve node one - node_one = self.get_object(node_one_id) - node_two = self.get_object(node_two_id) + node_one = self.get_node(node_one_id) + node_two = self.get_node(node_two_id) # both node ids are provided tunnel = self.broker.gettunnel(node_one_id, node_two_id) @@ -245,7 +243,7 @@ class EmuSession(Session): logging.info("adding link for peer to peer nodes: %s - %s", node_one.name, node_two.name) ptp_class = nodeutils.get_node_class(NodeTypes.PEER_TO_PEER) start = self.state > EventTypes.DEFINITION_STATE.value - net_one = self.add_object(cls=ptp_class, start=start) + net_one = self.create_node(cls=ptp_class, start=start) # node to network if node_one and net_one: @@ -366,7 +364,7 @@ class EmuSession(Session): interface_one.detachnet() interface_two.detachnet() if net_one.numnetif() == 0: - self.delete_object(net_one.id) + self.delete_node(net_one.id) node_one.delnetif(interface_one.netindex) node_two.delnetif(interface_two.netindex) finally: @@ -483,7 +481,7 @@ class EmuSession(Session): if not _id: while True: _id = self.node_id_gen.next() - if _id not in self.objects: + if _id not in self.nodes: break # generate name if not provided @@ -493,7 +491,7 @@ class EmuSession(Session): # create node logging.info("creating node(%s) id(%s) name(%s) start(%s)", node_class.__name__, _id, name, start) - node = self.add_object(cls=node_class, _id=_id, name=name, start=start) + node = self.create_node(cls=node_class, _id=_id, name=name, start=start) # set node attributes node.icon = node_options.icon @@ -510,9 +508,9 @@ class EmuSession(Session): self.services.add_services(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) + is_boot_node = isinstance(node, CoreNodeBase) and not nodeutils.is_node(node, NodeTypes.RJ45) if self.state == EventTypes.RUNTIME_STATE.value and is_boot_node: - self.write_objects() + self.write_nodes() self.add_remove_control_interface(node=node, remove=False) self.services.boot_services(node) @@ -530,7 +528,7 @@ class EmuSession(Session): result = False try: # get node to update - node = self.get_object(node_id) + node = self.get_node(node_id) # set node position and broadcast it self.set_node_position(node, node_options) @@ -546,20 +544,6 @@ class EmuSession(Session): return result - def delete_node(self, node_id): - """ - Delete a node from the session and check if session should shutdown, if no nodes are left. - - :param int node_id: id of node to delete - :return: True if node deleted, False otherwise - :rtype: bool - """ - # delete node and check for session shutdown if a node was removed - result = self.custom_delete_object(node_id) - if result: - self.check_shutdown() - return result - def set_node_position(self, node, node_options): """ Set position for a node, use lat/lon/alt if needed. @@ -625,21 +609,6 @@ class EmuSession(Session): self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True) super(EmuSession, self).shutdown() - def custom_delete_object(self, object_id): - """ - Remove an emulation object. - - :param int object_id: object id to remove - :return: True if object deleted, False otherwise - """ - result = False - with self._objects_lock: - if object_id in self.objects: - obj = self.objects.pop(object_id) - obj.shutdown() - result = True - return result - def is_active(self): """ Determine if this session is considered to be active. (Runtime or Data collect states) @@ -704,7 +673,7 @@ class EmuSession(Session): :return: nothing """ - node = self.get_object(node_id) + node = self.get_node(node_id) if source_name is not None: node.addfile(source_name, file_name) @@ -717,7 +686,7 @@ class EmuSession(Session): :return: nothing """ - self.delete_objects() + self.delete_nodes() self.del_hooks() self.broker.reset() self.emane.reset() diff --git a/daemon/core/data.py b/daemon/core/emulator/data.py similarity index 100% rename from daemon/core/data.py rename to daemon/core/emulator/data.py diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 437220da..6e5ebbfe 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -1,7 +1,7 @@ -from core.enumerations import LinkTypes -from core.misc.ipaddress import Ipv4Prefix -from core.misc.ipaddress import Ipv6Prefix -from core.misc.ipaddress import MacAddress +from core.emulator.enumerations import LinkTypes +from core.nodes.ipaddress import Ipv4Prefix +from core.nodes.ipaddress import Ipv6Prefix +from core.nodes.ipaddress import MacAddress class NodeOptions(object): diff --git a/daemon/core/enumerations.py b/daemon/core/emulator/enumerations.py similarity index 100% rename from daemon/core/enumerations.py rename to daemon/core/emulator/enumerations.py diff --git a/daemon/core/session.py b/daemon/core/emulator/session.py similarity index 86% rename from daemon/core/session.py rename to daemon/core/emulator/session.py index 43500b2c..b4ca45d1 100644 --- a/daemon/core/session.py +++ b/daemon/core/emulator/session.py @@ -5,6 +5,7 @@ that manages a CORE session. import logging import os +import pwd import random import shutil import subprocess @@ -13,31 +14,29 @@ import threading import time from multiprocessing.pool import ThreadPool -import pwd - -from core import constants -from core.api import coreapi -from core.broker import CoreBroker -from core.conf import ConfigurableManager -from core.conf import ConfigurableOptions -from core.conf import Configuration -from core.data import EventData -from core.data import ExceptionData +import core.nodes.base +from core import constants, utils +from core.api.tlv import coreapi +from core.api.tlv.broker import CoreBroker +from core.config import ConfigurableManager +from core.config import ConfigurableOptions +from core.config import Configuration from core.emane.emanemanager import EmaneManager -from core.enumerations import ConfigDataTypes -from core.enumerations import EventTypes -from core.enumerations import ExceptionLevels -from core.enumerations import NodeTypes -from core.enumerations import RegisterTlvs -from core.location import CoreLocation -from core.misc import nodeutils -from core.misc import utils -from core.misc.event import EventLoop -from core.misc.ipaddress import MacAddress -from core.mobility import MobilityManager -from core.netns import nodes -from core.sdt import Sdt -from core.service import CoreServices +from core.emulator.data import EventData +from core.emulator.data import ExceptionData +from core.emulator.enumerations import ConfigDataTypes +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import ExceptionLevels +from core.emulator.enumerations import NodeTypes +from core.emulator.enumerations import RegisterTlvs +from core.location.corelocation import CoreLocation +from core.location.event import EventLoop +from core.location.mobility import MobilityManager +from core.nodes import nodeutils +from core.nodes.base import CoreNodeBase +from core.nodes.ipaddress import MacAddress +from core.plugins.sdt import Sdt +from core.services.coreservices import CoreServices from core.xml import corexml, corexmldeployment @@ -67,9 +66,9 @@ class Session(object): self.user = None self.event_loop = EventLoop() - # dict of objects: all nodes and nets - self.objects = {} - self._objects_lock = threading.Lock() + # dict of nodes: all nodes and nets + self.nodes = {} + self._nodes_lock = threading.Lock() # TODO: should the default state be definition? self.state = EventTypes.NONE.value @@ -110,15 +109,15 @@ class Session(object): def shutdown(self): """ - Shutdown all emulation objects and remove the session directory. + Shutdown all session nodes and remove the session directory. """ # shutdown/cleanup feature helpers self.emane.shutdown() self.broker.shutdown() self.sdt.shutdown() - # delete all current objects - self.delete_objects() + # delete all current nodes + self.delete_nodes() # remove this sessions working directory preserve = self.options.get_config("preservedir") == "1" @@ -464,93 +463,87 @@ class Session(object): """ Return a unique, new node id. """ - with self._objects_lock: + with self._nodes_lock: while True: - object_id = random.randint(1, 0xFFFF) - if object_id not in self.objects: + node_id = random.randint(1, 0xFFFF) + if node_id not in self.nodes: break - return object_id + return node_id - def add_object(self, cls, *clsargs, **clskwds): + def create_node(self, cls, *clsargs, **clskwds): """ Create an emulation node. - :param class cls: object class to add + :param class cls: node class to create :param list clsargs: list of arguments for the class to create :param dict clskwds: dictionary of arguments for the class to create - :return: the created class instance + :return: the created node instance """ node = cls(self, *clsargs, **clskwds) - with self._objects_lock: - if node.id in self.objects: + with self._nodes_lock: + if node.id in self.nodes: node.shutdown() raise KeyError("duplicate node id %s for %s" % (node.id, node.name)) - self.objects[node.id] = node + self.nodes[node.id] = node return node - def get_object(self, object_id): + def get_node(self, _id): """ - Get an emulation object. + Get a session node. - :param int object_id: object id to retrieve - :return: object for the given id - :rtype: core.coreobj.PyCoreNode + :param int _id: node id to retrieve + :return: node for the given id + :rtype: core.nodes.base.CoreNode """ - if object_id not in self.objects: - raise KeyError("unknown object id %s" % object_id) - return self.objects[object_id] + if _id not in self.nodes: + raise KeyError("unknown node id %s" % _id) + return self.nodes[_id] - def get_object_by_name(self, name): + def delete_node(self, _id): """ - Get an emulation object using its name attribute. + Delete a node from the session and check if session should shutdown, if no nodes are left. - :param str name: name of object to retrieve - :return: object for the name given + :param int _id: id of node to delete + :return: True if node deleted, False otherwise + :rtype: bool """ - with self._objects_lock: - for obj in self.objects.itervalues(): - if hasattr(obj, "name") and obj.name == name: - return obj - raise KeyError("unknown object with name %s" % name) + # delete node and check for session shutdown if a node was removed + result = False + with self._nodes_lock: + if _id in self.nodes: + node = self.nodes.pop(_id) + node.shutdown() + result = True - def delete_object(self, object_id): - """ - Remove an emulation object. + if result: + self.check_shutdown() - :param int object_id: object id to remove - :return: nothing - """ - with self._objects_lock: - try: - obj = self.objects.pop(object_id) - obj.shutdown() - except KeyError: - logging.error("failed to remove object, object with id was not found: %s", object_id) + return result - def delete_objects(self): + def delete_nodes(self): """ - Clear the objects dictionary, and call shutdown for each object. + Clear the nodes dictionary, and call shutdown for each node. """ - with self._objects_lock: - while self.objects: - _, obj = self.objects.popitem() - obj.shutdown() + with self._nodes_lock: + while self.nodes: + _, node = self.nodes.popitem() + node.shutdown() - def write_objects(self): + def write_nodes(self): """ - Write objects to a 'nodes' file in the session dir. + Write nodes to a 'nodes' file in the session dir. The 'nodes' file lists: number, name, api-type, class-type """ try: - nodes_file = open(os.path.join(self.session_dir, "nodes"), "w") - with self._objects_lock: - for object_id in sorted(self.objects.keys()): - obj = self.objects[object_id] - nodes_file.write("%s %s %s %s\n" % (object_id, obj.name, obj.apitype, type(obj))) - nodes_file.close() + with self._nodes_lock: + file_path = os.path.join(self.session_dir, "nodes") + with open(file_path, "w") as f: + for _id in sorted(self.nodes.keys()): + node = self.nodes[_id] + f.write("%s %s %s %s\n" % (_id, node.name, node.apitype, type(node))) except IOError: logging.exception("error writing nodes file") @@ -560,21 +553,21 @@ class Session(object): """ logging.info("session id=%s name=%s state=%s", self.id, self.name, self.state) logging.info("file=%s thumbnail=%s node_count=%s/%s", - self.file_name, self.thumbnail, self.get_node_count(), len(self.objects)) + self.file_name, self.thumbnail, self.get_node_count(), len(self.nodes)) - def exception(self, level, source, object_id, text): + def exception(self, level, source, node_id, text): """ Generate and broadcast an exception event. :param str level: exception level :param str source: source name - :param int object_id: object id + :param int node_id: node related to exception :param str text: exception message :return: nothing """ exception_data = ExceptionData( - node=object_id, + node=node_id, session=str(self.id), level=level, source=source, @@ -591,8 +584,8 @@ class Session(object): for transition to the runtime state. """ - # write current objects out to session directory file - self.write_objects() + # write current nodes out to session directory file + self.write_nodes() # controlnet may be needed by some EMANE models self.add_remove_control_interface(node=None, remove=False) @@ -626,12 +619,12 @@ class Session(object): that are not considered in the GUI's node count. """ - with self._objects_lock: - count = len([x for x in self.objects.itervalues() + with self._nodes_lock: + count = len([x for x in self.nodes.itervalues() if not nodeutils.is_node(x, (NodeTypes.PEER_TO_PEER, NodeTypes.CONTROL_NET))]) # on Linux, GreTapBridges are auto-created, not part of GUI's node count - count -= len([x for x in self.objects.itervalues() + count -= len([x for x in self.nodes.itervalues() if nodeutils.is_node(x, NodeTypes.TAP_BRIDGE) and not nodeutils.is_node(x, NodeTypes.TUNNEL)]) return count @@ -668,10 +661,10 @@ class Session(object): self.event_loop.stop() # stop node services - with self._objects_lock: - for obj in self.objects.itervalues(): + with self._nodes_lock: + for obj in self.nodes.itervalues(): # TODO: determine if checking for CoreNode alone is ok - if isinstance(obj, nodes.PyCoreNode): + if isinstance(obj, core.nodes.base.CoreNodeBase): self.services.stop_services(obj) # shutdown emane @@ -715,14 +708,14 @@ class Session(object): messages to the GUI for node messages that had the status request flag. """ - with self._objects_lock: + with self._nodes_lock: pool = ThreadPool() results = [] start = time.time() - for obj in self.objects.itervalues(): + for obj in self.nodes.itervalues(): # TODO: PyCoreNode is not the type to check - if isinstance(obj, nodes.PyCoreNode) and not nodeutils.is_node(obj, NodeTypes.RJ45): + if isinstance(obj, CoreNodeBase) and not nodeutils.is_node(obj, NodeTypes.RJ45): # add a control interface if configured logging.info("booting node: %s", obj.name) self.add_remove_control_interface(node=obj, remove=False) @@ -786,10 +779,10 @@ class Session(object): return index return -1 - def get_control_net_object(self, net_index): + def get_control_net(self, net_index): # TODO: all nodes use an integer id and now this wants to use a string - object_id = "ctrl%dnet" % net_index - return self.get_object(object_id) + _id = "ctrl%dnet" % net_index + return self.get_node(_id) def add_remove_control_net(self, net_index, remove=False, conf_required=True): """ @@ -801,8 +794,8 @@ class Session(object): :param int net_index: network index :param bool remove: flag to check if it should be removed :param bool conf_required: flag to check if conf is required - :return: control net object - :rtype: core.netns.nodes.CtrlNet + :return: control net node + :rtype: core.nodes.network.CtrlNet """ logging.debug("add/remove control net: index(%s) remove(%s) conf_required(%s)", net_index, remove, conf_required) prefix_spec_list = self.get_control_net_prefixes() @@ -820,10 +813,10 @@ class Session(object): # return any existing controlnet bridge try: - control_net = self.get_control_net_object(net_index) + control_net = self.get_control_net(net_index) if remove: - self.delete_object(control_net.id) + self.delete_node(control_net.id) return None return control_net @@ -832,7 +825,7 @@ class Session(object): return None # build a new controlnet bridge - object_id = "ctrl%dnet" % net_index + _id = "ctrl%dnet" % net_index # use the updown script for control net 0 only. updown_script = None @@ -887,16 +880,16 @@ class Session(object): prefix = prefixes[0] control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET) - control_net = self.add_object(cls=control_net_class, _id=object_id, prefix=prefix, - assign_address=assign_address, - updown_script=updown_script, serverintf=server_interface) + control_net = self.create_node(cls=control_net_class, _id=_id, prefix=prefix, + assign_address=assign_address, + updown_script=updown_script, serverintf=server_interface) # tunnels between controlnets will be built with Broker.addnettunnels() - # TODO: potentially remove documentation saying object ids are ints + # TODO: potentially remove documentation saying node ids are ints # TODO: need to move broker code out of the session object - self.broker.addnet(object_id) + self.broker.addnet(_id) for server in self.broker.getservers(): - self.broker.addnodemap(server, object_id) + self.broker.addnodemap(server, _id) return control_net @@ -908,7 +901,7 @@ class Session(object): If conf_reqd is False, the control network may be built even when the user has not configured one (e.g. for EMANE.) - :param core.netns.nodes.CoreNode node: node to add or remove control interface + :param core.netns.vnode.CoreNode node: node to add or remove control interface :param int net_index: network index :param bool remove: flag to check if it should be removed :param bool conf_required: flag to check if conf is required @@ -954,9 +947,9 @@ class Session(object): return try: - control_net = self.get_control_net_object(net_index) + control_net = self.get_control_net(net_index) except KeyError: - logging.exception("error retrieving control net object") + logging.exception("error retrieving control net node") return header = "CORE session %s host entries" % self.id @@ -991,7 +984,7 @@ class Session(object): start of the runtime state. :param event_time: event time - :param core.netns.nodes.CoreNode node: node to add event for + :param core.netns.vnode.CoreNode node: node to add event for :param str name: name of event :param data: data for event :return: nothing @@ -1029,13 +1022,13 @@ class Session(object): if not node_id: utils.mute_detach(data) else: - node = self.get_object(node_id) + node = self.get_node(node_id) node.cmd(data, wait=False) class SessionConfig(ConfigurableManager, ConfigurableOptions): """ - Session configuration object. + Provides session configuration. """ name = "session" options = [ diff --git a/daemon/core/netns/__init__.py b/daemon/core/location/__init__.py similarity index 100% rename from daemon/core/netns/__init__.py rename to daemon/core/location/__init__.py diff --git a/daemon/core/location.py b/daemon/core/location/corelocation.py similarity index 99% rename from daemon/core/location.py rename to daemon/core/location/corelocation.py index b95a8670..a65a4d72 100644 --- a/daemon/core/location.py +++ b/daemon/core/location/corelocation.py @@ -7,8 +7,8 @@ https://pypi.python.org/pypi/utm (version 0.3.0). import logging -from core.enumerations import RegisterTlvs -from core.misc import utm +from core.emulator.enumerations import RegisterTlvs +from core.location import utm class CoreLocation(object): diff --git a/daemon/core/misc/event.py b/daemon/core/location/event.py similarity index 100% rename from daemon/core/misc/event.py rename to daemon/core/location/event.py diff --git a/daemon/core/mobility.py b/daemon/core/location/mobility.py similarity index 95% rename from daemon/core/mobility.py rename to daemon/core/location/mobility.py index 3f2cc4e5..2890e9c2 100644 --- a/daemon/core/mobility.py +++ b/daemon/core/location/mobility.py @@ -9,22 +9,22 @@ import os import threading import time -from core.conf import ConfigGroup -from core.conf import ConfigurableOptions -from core.conf import Configuration -from core.conf import ModelManager -from core.coreobj import PyCoreNode -from core.data import EventData -from core.data import LinkData -from core.enumerations import ConfigDataTypes -from core.enumerations import EventTypes -from core.enumerations import LinkTypes -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.enumerations import NodeTlvs -from core.enumerations import RegisterTlvs -from core.misc import utils -from core.misc.ipaddress import IpAddress +from core.config import ConfigGroup +from core.config import ConfigurableOptions +from core.config import Configuration +from core.config import ModelManager +from core.nodes.base import CoreNodeBase +from core.emulator.data import EventData +from core.emulator.data import LinkData +from core.emulator.enumerations import ConfigDataTypes +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.emulator.enumerations import NodeTlvs +from core.emulator.enumerations import RegisterTlvs +from core import utils +from core.nodes.ipaddress import IpAddress class MobilityManager(ModelManager): @@ -67,7 +67,7 @@ class MobilityManager(ModelManager): logging.info("node mobility configurations: %s", self.get_all_configs(node_id)) try: - node = self.session.get_object(node_id) + node = self.session.get_node(node_id) except KeyError: logging.warn("skipping mobility configuration for unknown node: %s", node_id) continue @@ -98,7 +98,7 @@ class MobilityManager(ModelManager): name = event_data.name try: - node = self.session.get_object(node_id) + node = self.session.get_node(node_id) except KeyError: logging.exception("Ignoring event for model '%s', unknown node '%s'", name, node_id) return @@ -152,7 +152,7 @@ class MobilityManager(ModelManager): data += " end=%d" % int(model.endtime) event_data = EventData( - node=model.object_id, + node=model.id, event_type=event_type, name="mobility:%s" % model.name, data=data, @@ -173,7 +173,7 @@ class MobilityManager(ModelManager): """ for node_id in self.nodes(): try: - node = self.session.get_object(node_id) + node = self.session.get_node(node_id) except KeyError: continue if node.model: @@ -213,7 +213,7 @@ class MobilityManager(ModelManager): return if nn[1] in self.session.broker.physical_nodes: # record the fact that this PhysicalNode is linked to a net - dummy = PyCoreNode(session=self.session, _id=nn[1], name="n%d" % nn[1], start=False) + dummy = CoreNodeBase(session=self.session, _id=nn[1], name="n%d" % nn[1], start=False) self.addphys(nn[0], dummy) # TODO: remove need to handling old style messages @@ -265,15 +265,15 @@ class WirelessModel(ConfigurableOptions): bitmap = None position_callback = None - def __init__(self, session, object_id): + def __init__(self, session, _id): """ Create a WirelessModel instance. :param core.session.Session session: core session we are tied to - :param int object_id: object id + :param int _id: object id """ self.session = session - self.object_id = object_id + self.id = _id def all_link_data(self, flags): """ @@ -329,17 +329,17 @@ class BasicRangeModel(WirelessModel): ConfigGroup("Basic Range Parameters", 1, len(cls.configurations())) ] - def __init__(self, session, object_id): + def __init__(self, session, _id): """ Create a BasicRangeModel instance. :param core.session.Session session: related core session - :param int object_id: object id + :param int _id: object id :param dict config: values """ - super(BasicRangeModel, self).__init__(session=session, object_id=object_id) + super(BasicRangeModel, self).__init__(session=session, _id=_id) self.session = session - self.wlan = session.get_object(object_id) + self.wlan = session.get_node(_id) self._netifs = {} self._netifslock = threading.Lock() @@ -605,15 +605,15 @@ class WayPointMobility(WirelessModel): STATE_RUNNING = 1 STATE_PAUSED = 2 - def __init__(self, session, object_id): + def __init__(self, session, _id): """ Create a WayPointMobility instance. :param core.session.Session session: CORE session instance - :param int object_id: object id + :param int _id: object id :return: """ - super(WayPointMobility, self).__init__(session=session, object_id=object_id) + super(WayPointMobility, self).__init__(session=session, _id=_id) self.state = self.STATE_STOPPED self.queue = [] @@ -622,7 +622,7 @@ class WayPointMobility(WirelessModel): self.initial = {} self.lasttime = None self.endtime = None - self.wlan = session.get_object(object_id) + self.wlan = session.get_node(_id) # these are really set in child class via confmatrix self.loop = False self.refresh_ms = 50 @@ -700,7 +700,7 @@ class WayPointMobility(WirelessModel): Calculate next node location and update its coordinates. Returns True if the node's position has changed. - :param core.netns.nodes.CoreNode node: node to move + :param core.netns.vnode.CoreNode node: node to move :param dt: move factor :return: True if node was moved, False otherwise :rtype: bool @@ -830,7 +830,7 @@ class WayPointMobility(WirelessModel): without invoking the interface poshook callback that may perform range calculation. - :param core.netns.nodes.CoreNode node: node to set position for + :param core.netns.vnode.CoreNode node: node to set position for :param x: x position :param y: y position :param z: z position @@ -923,15 +923,15 @@ class Ns2ScriptedMobility(WayPointMobility): ConfigGroup("ns-2 Mobility Script Parameters", 1, len(cls.configurations())) ] - def __init__(self, session, object_id): + def __init__(self, session, _id): """ Creates a Ns2ScriptedMobility instance. :param core.session.Session session: CORE session instance - :param int object_id: object id + :param int _id: object id :param config: values """ - super(Ns2ScriptedMobility, self).__init__(session=session, object_id=object_id) + super(Ns2ScriptedMobility, self).__init__(session=session, _id=_id) self._netifs = {} self._netifslock = threading.Lock() @@ -946,7 +946,7 @@ class Ns2ScriptedMobility(WayPointMobility): def update_config(self, config): self.file = config["file"] - logging.info("ns-2 scripted mobility configured for WLAN %d using file: %s", self.object_id, self.file) + logging.info("ns-2 scripted mobility configured for WLAN %d using file: %s", self.id, self.file) self.refresh_ms = int(config["refresh_ms"]) self.loop = config["loop"].lower() == "on" self.autostart = config["autostart"] diff --git a/daemon/core/misc/utm.py b/daemon/core/location/utm.py similarity index 100% rename from daemon/core/misc/utm.py rename to daemon/core/location/utm.py diff --git a/daemon/core/misc/LatLongUTMconversion.py b/daemon/core/misc/LatLongUTMconversion.py deleted file mode 100755 index 4f7c13dc..00000000 --- a/daemon/core/misc/LatLongUTMconversion.py +++ /dev/null @@ -1,246 +0,0 @@ -#!/usr/bin/env python -# this file is from http://pygps.org/ - -# Lat Long - UTM, UTM - Lat Long conversions - -from math import pi, sin, cos, tan, sqrt - -# LatLong- UTM conversion..h -# definitions for lat/long to UTM and UTM to lat/lng conversions -# include - -_deg2rad = pi / 180.0 -_rad2deg = 180.0 / pi - -_EquatorialRadius = 2 -_eccentricitySquared = 3 - -_ellipsoid = [ - # id, Ellipsoid name, Equatorial Radius, square of eccentricity - # first once is a placeholder only, To allow array indices to match id numbers - [-1, "Placeholder", 0, 0], - [1, "Airy", 6377563, 0.00667054], - [2, "Australian National", 6378160, 0.006694542], - [3, "Bessel 1841", 6377397, 0.006674372], - [4, "Bessel 1841 (Nambia] ", 6377484, 0.006674372], - [5, "Clarke 1866", 6378206, 0.006768658], - [6, "Clarke 1880", 6378249, 0.006803511], - [7, "Everest", 6377276, 0.006637847], - [8, "Fischer 1960 (Mercury] ", 6378166, 0.006693422], - [9, "Fischer 1968", 6378150, 0.006693422], - [10, "GRS 1967", 6378160, 0.006694605], - [11, "GRS 1980", 6378137, 0.00669438], - [12, "Helmert 1906", 6378200, 0.006693422], - [13, "Hough", 6378270, 0.00672267], - [14, "International", 6378388, 0.00672267], - [15, "Krassovsky", 6378245, 0.006693422], - [16, "Modified Airy", 6377340, 0.00667054], - [17, "Modified Everest", 6377304, 0.006637847], - [18, "Modified Fischer 1960", 6378155, 0.006693422], - [19, "South American 1969", 6378160, 0.006694542], - [20, "WGS 60", 6378165, 0.006693422], - [21, "WGS 66", 6378145, 0.006694542], - [22, "WGS-72", 6378135, 0.006694318], - [23, "WGS-84", 6378137, 0.00669438] -] - - -# Reference ellipsoids derived from Peter H. Dana's website- -# http://www.utexas.edu/depts/grg/gcraft/notes/datum/elist.html -# Department of Geography, University of Texas at Austin -# Internet: pdana@mail.utexas.edu -# 3/22/95 - -# Source -# Defense Mapping Agency. 1987b. DMA Technical Report: Supplement to Department of Defense World Geodetic System -# 1984 Technical Report. Part I and II. Washington, DC: Defense Mapping Agency - -# def LLtoUTM(int ReferenceEllipsoid, const double Lat, const double Long, -# double &UTMNorthing, double &UTMEasting, char* UTMZone) - -def LLtoUTM(ReferenceEllipsoid, Lat, Long, zone=None): - """converts lat/long to UTM coords. Equations from USGS Bulletin 1532 - East Longitudes are positive, West longitudes are negative. - North latitudes are positive, South latitudes are negative - Lat and Long are in decimal degrees - Written by Chuck Gantz- chuck.gantz@globalstar.com""" - - a = _ellipsoid[ReferenceEllipsoid][_EquatorialRadius] - eccSquared = _ellipsoid[ReferenceEllipsoid][_eccentricitySquared] - k0 = 0.9996 - - # Make sure the longitude is between -180.00 .. 179.9 - LongTemp = (Long + 180) - int((Long + 180) / 360) * 360 - 180 # -180.00 .. 179.9 - - LatRad = Lat * _deg2rad - LongRad = LongTemp * _deg2rad - - if zone is None: - ZoneNumber = int((LongTemp + 180) / 6) + 1 - else: - ZoneNumber = zone - - if Lat >= 56.0 and Lat < 64.0 and LongTemp >= 3.0 and LongTemp < 12.0: - ZoneNumber = 32 - - # Special zones for Svalbard - if Lat >= 72.0 and Lat < 84.0: - if LongTemp >= 0.0 and LongTemp < 9.0: - ZoneNumber = 31 - elif LongTemp >= 9.0 and LongTemp < 21.0: - ZoneNumber = 33 - elif LongTemp >= 21.0 and LongTemp < 33.0: - ZoneNumber = 35 - elif LongTemp >= 33.0 and LongTemp < 42.0: - ZoneNumber = 37 - - LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3 # +3 puts origin in middle of zone - LongOriginRad = LongOrigin * _deg2rad - - # compute the UTM Zone from the latitude and longitude - UTMZone = "%d%c" % (ZoneNumber, _UTMLetterDesignator(Lat)) - - eccPrimeSquared = (eccSquared) / (1 - eccSquared) - N = a / sqrt(1 - eccSquared * sin(LatRad) * sin(LatRad)) - T = tan(LatRad) * tan(LatRad) - C = eccPrimeSquared * cos(LatRad) * cos(LatRad) - A = cos(LatRad) * (LongRad - LongOriginRad) - - M = a * ((1 - - eccSquared / 4 - - 3 * eccSquared * eccSquared / 64 - - 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad - - (3 * eccSquared / 8 - + 3 * eccSquared * eccSquared / 32 - + 45 * eccSquared * eccSquared * eccSquared / 1024) * sin(2 * LatRad) - + (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * sin(4 * LatRad) - - (35 * eccSquared * eccSquared * eccSquared / 3072) * sin(6 * LatRad)) - - UTMEasting = (k0 * N * (A + (1 - T + C) * A * A * A / 6 - + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120) - + 500000.0) - - UTMNorthing = (k0 * (M + N * tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24 - + (61 - - 58 * T - + T * T - + 600 * C - - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720))) - - if Lat < 0: - UTMNorthing = UTMNorthing + 10000000.0; # 10000000 meter offset for southern hemisphere - return (UTMZone, UTMEasting, UTMNorthing) - - -def _UTMLetterDesignator(Lat): - """This routine determines the correct UTM letter designator for the given - latitude returns 'Z' if latitude is outside the UTM limits of 84N to 80S - Written by Chuck Gantz- chuck.gantz@globalstar.com""" - - if 84 >= Lat >= 72: - return 'X' - elif 72 > Lat >= 64: - return 'W' - elif 64 > Lat >= 56: - return 'V' - elif 56 > Lat >= 48: - return 'U' - elif 48 > Lat >= 40: - return 'T' - elif 40 > Lat >= 32: - return 'S' - elif 32 > Lat >= 24: - return 'R' - elif 24 > Lat >= 16: - return 'Q' - elif 16 > Lat >= 8: - return 'P' - elif 8 > Lat >= 0: - return 'N' - elif 0 > Lat >= -8: - return 'M' - elif -8 > Lat >= -16: - return 'L' - elif -16 > Lat >= -24: - return 'K' - elif -24 > Lat >= -32: - return 'J' - elif -32 > Lat >= -40: - return 'H' - elif -40 > Lat >= -48: - return 'G' - elif -48 > Lat >= -56: - return 'F' - elif -56 > Lat >= -64: - return 'E' - elif -64 > Lat >= -72: - return 'D' - elif -72 > Lat >= -80: - return 'C' - else: - return 'Z' # if the Latitude is outside the UTM limits - - -# void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone, -# double& Lat, double& Long ) - -def UTMtoLL(ReferenceEllipsoid, northing, easting, zone): - """converts UTM coords to lat/long. Equations from USGS Bulletin 1532 -East Longitudes are positive, West longitudes are negative. -North latitudes are positive, South latitudes are negative -Lat and Long are in decimal degrees. -Written by Chuck Gantz- chuck.gantz@globalstar.com -Converted to Python by Russ Nelson """ - - k0 = 0.9996 - a = _ellipsoid[ReferenceEllipsoid][_EquatorialRadius] - eccSquared = _ellipsoid[ReferenceEllipsoid][_eccentricitySquared] - e1 = (1 - sqrt(1 - eccSquared)) / (1 + sqrt(1 - eccSquared)) - # NorthernHemisphere; //1 for northern hemispher, 0 for southern - - x = easting - 500000.0 # remove 500,000 meter offset for longitude - y = northing - - ZoneLetter = zone[-1] - ZoneNumber = int(zone[:-1]) - if ZoneLetter >= 'N': - NorthernHemisphere = 1 # point is in northern hemisphere - else: - NorthernHemisphere = 0 # point is in southern hemisphere - y -= 10000000.0 # remove 10,000,000 meter offset used for southern hemisphere - - LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3 # +3 puts origin in middle of zone - - eccPrimeSquared = (eccSquared) / (1 - eccSquared) - - M = y / k0 - mu = M / ( - a * (1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256)) - - phi1Rad = (mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * sin(2 * mu) - + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * sin(4 * mu) - + (151 * e1 * e1 * e1 / 96) * sin(6 * mu)) - phi1 = phi1Rad * _rad2deg; - - N1 = a / sqrt(1 - eccSquared * sin(phi1Rad) * sin(phi1Rad)) - T1 = tan(phi1Rad) * tan(phi1Rad) - C1 = eccPrimeSquared * cos(phi1Rad) * cos(phi1Rad) - R1 = a * (1 - eccSquared) / pow(1 - eccSquared * sin(phi1Rad) * sin(phi1Rad), 1.5) - D = x / (N1 * k0) - - Lat = phi1Rad - (N1 * tan(phi1Rad) / R1) * ( - D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24 - + (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720) - Lat = Lat * _rad2deg - - Long = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + ( - 5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1) - * D * D * D * D * D / 120) / cos(phi1Rad) - Long = LongOrigin + Long * _rad2deg - return (Lat, Long) - - -if __name__ == '__main__': - (z, e, n) = LLtoUTM(23, 45.00, -75.00) - print z, e, n - print UTMtoLL(23, n, e, z) diff --git a/daemon/core/misc/nodemaps.py b/daemon/core/misc/nodemaps.py deleted file mode 100644 index 8c3d0c37..00000000 --- a/daemon/core/misc/nodemaps.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Provides default node maps that can be used to run core with. -""" - -from core.emane.nodes import EmaneNet -from core.emane.nodes import EmaneNode -from core.enumerations import NodeTypes -from core.netns import nodes -from core.netns.vnet import GreTapBridge -from core.phys import pnodes - -# legacy core nodes, that leverage linux bridges -NODES = { - NodeTypes.DEFAULT: nodes.CoreNode, - NodeTypes.PHYSICAL: pnodes.PhysicalNode, - NodeTypes.TBD: None, - NodeTypes.SWITCH: nodes.SwitchNode, - NodeTypes.HUB: nodes.HubNode, - NodeTypes.WIRELESS_LAN: nodes.WlanNode, - NodeTypes.RJ45: nodes.RJ45Node, - NodeTypes.TUNNEL: nodes.TunnelNode, - NodeTypes.KTUNNEL: None, - NodeTypes.EMANE: EmaneNode, - NodeTypes.EMANE_NET: EmaneNet, - NodeTypes.TAP_BRIDGE: GreTapBridge, - NodeTypes.PEER_TO_PEER: nodes.PtpNet, - NodeTypes.CONTROL_NET: nodes.CtrlNet -} diff --git a/daemon/core/misc/quagga.py b/daemon/core/misc/quagga.py deleted file mode 100644 index 9887277d..00000000 --- a/daemon/core/misc/quagga.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -quagga.py: helper class for generating Quagga configuration. -""" - -from string import Template - -from core.misc import utils - - -def addrstr(x): - if x.find(".") >= 0: - return "ip address %s" % x - elif x.find(":") >= 0: - return "ipv6 address %s" % x - else: - raise ValueError("invalid address: %s" % x) - - -class NetIf(object): - """ - Represents a network interface. - """ - - def __init__(self, name, addrlist=None): - """ - Create a NetIf instance. - - :param str name: interface name - :param addrlist: address list for the interface - """ - self.name = name - - if addrlist: - self.addrlist = addrlist - else: - self.addrlist = [] - - -class Conf(object): - """ - Provides a configuration object. - """ - - template = Template("") - - def __init__(self, **kwargs): - """ - Create a Conf instance. - - :param dict kwargs: configuration keyword arguments - """ - self.kwargs = kwargs - - def __str__(self): - """ - Provides a string representation of a configuration object. - - :return: string representation - :rtype: str - """ - tmp = self.template.substitute(**self.kwargs) - if tmp[-1] == "\n": - tmp = tmp[:-1] - return tmp - - -class QuaggaOSPF6Interface(Conf): - """ - Provides quagga ospf6 interface functionality. - """ - AF_IPV6_ID = 0 - AF_IPV4_ID = 65 - - template = Template("""\ -interface $interface - $addr - ipv6 ospf6 instance-id $instanceid - ipv6 ospf6 hello-interval 2 - ipv6 ospf6 dead-interval 11 - ipv6 ospf6 retransmit-interval 5 - ipv6 ospf6 network $network - ipv6 ospf6 diffhellos - ipv6 ospf6 adjacencyconnectivity uniconnected - ipv6 ospf6 lsafullness mincostlsa -""") - - # ip address $ipaddr/32 - # ipv6 ospf6 simhelloLLtoULRecv :$simhelloport - # !$ipaddr:$simhelloport - - def __init__(self, netif, instanceid=AF_IPV4_ID, network="manet-designated-router", **kwargs): - """ - Create a QuaggaOSPF6Interface instance. - - :param netif: network interface - :param int instanceid: instance id - :param network: network - :param dict kwargs: keyword arguments - """ - self.netif = netif - addr = "\n ".join(map(addrstr, netif.addrlist)) - self.instanceid = instanceid - self.network = network - Conf.__init__(self, interface=netif.name, addr=addr, - instanceid=instanceid, network=network, **kwargs) - - def name(self): - """ - Retrieve network interface name. - - :return: network interface name - :rtype: str - """ - return self.netif.name - - -class QuaggaOSPF6(Conf): - """ - Provides quagga ospf6 functionality. - """ - template = Template("""\ -$interfaces -! -router ospf6 - router-id $routerid - $ospfifs - $redistribute -""") - - def __init__(self, ospf6ifs, area, routerid, redistribute="! no redistribute"): - """ - Create a QuaggaOSPF6 instance. - - :param list ospf6ifs: ospf6 interfaces - :param area: area - :param routerid: router id - :param str redistribute: redistribute value - """ - ospf6ifs = utils.make_tuple(ospf6ifs) - interfaces = "\n!\n".join(map(str, ospf6ifs)) - ospfifs = "\n ".join(map(lambda x: "interface %s area %s" % (x.name(), area), ospf6ifs)) - Conf.__init__(self, interfaces=interfaces, routerid=routerid, ospfifs=ospfifs, redistribute=redistribute) - - -class QuaggaConf(Conf): - """ - Provides quagga configuration functionality. - """ - template = Template("""\ -log file $logfile -$debugs -! -$routers -! -$forwarding -""") - - def __init__(self, routers, logfile, debugs=()): - """ - Create a QuaggaConf instance. - - :param list routers: routers - :param str logfile: log file name - :param debugs: debug options - """ - routers = "\n!\n".join(map(str, utils.make_tuple(routers))) - if debugs: - debugs = "\n".join(utils.make_tuple(debugs)) - else: - debugs = "! no debugs" - forwarding = "ip forwarding\nipv6 forwarding" - Conf.__init__(self, logfile=logfile, debugs=debugs, routers=routers, forwarding=forwarding) diff --git a/daemon/core/netns/nodes.py b/daemon/core/netns/nodes.py deleted file mode 100644 index df7b18b4..00000000 --- a/daemon/core/netns/nodes.py +++ /dev/null @@ -1,739 +0,0 @@ -""" -Definition of LxcNode, CoreNode, and other node classes that inherit from the CoreNode, -implementing specific node types. -""" - -import logging -import socket -import threading -from socket import AF_INET -from socket import AF_INET6 - -from core import CoreCommandError -from core import constants -from core.coreobj import PyCoreNetIf -from core.coreobj import PyCoreNode -from core.coreobj import PyCoreObj -from core.data import LinkData -from core.enumerations import LinkTypes -from core.enumerations import NodeTypes -from core.enumerations import RegisterTlvs -from core.misc import ipaddress -from core.misc import utils -from core.netns.vnet import GreTapBridge -from core.netns.vnet import LxBrNet -from core.netns.vnode import LxcNode - - -class CtrlNet(LxBrNet): - """ - Control network functionality. - """ - policy = "ACCEPT" - # base control interface index - CTRLIF_IDX_BASE = 99 - DEFAULT_PREFIX_LIST = [ - "172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24", - "172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24", - "172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24", - "172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24" - ] - - def __init__(self, session, _id="ctrlnet", name=None, prefix=None, - hostid=None, start=True, assign_address=True, - updown_script=None, serverintf=None): - """ - Creates a CtrlNet instance. - - :param core.session.Session session: core session instance - :param int _id: node id - :param str name: node namee - :param prefix: control network ipv4 prefix - :param hostid: host id - :param bool start: start flag - :param str assign_address: assigned address - :param str updown_script: updown script - :param serverintf: server interface - :return: - """ - self.prefix = ipaddress.Ipv4Prefix(prefix) - self.hostid = hostid - self.assign_address = assign_address - self.updown_script = updown_script - self.serverintf = serverintf - LxBrNet.__init__(self, session, _id=_id, name=name, start=start) - - def startup(self): - """ - Startup functionality for the control network. - - :return: nothing - :raises CoreCommandError: when there is a command exception - """ - if self.detectoldbridge(): - return - - LxBrNet.startup(self) - - if self.hostid: - addr = self.prefix.addr(self.hostid) - else: - addr = self.prefix.max_addr() - - logging.info("added control network bridge: %s %s", self.brname, self.prefix) - - if self.assign_address: - addrlist = ["%s/%s" % (addr, self.prefix.prefixlen)] - self.addrconfig(addrlist=addrlist) - logging.info("address %s", addr) - - if self.updown_script: - logging.info("interface %s updown script (%s startup) called", self.brname, self.updown_script) - utils.check_cmd([self.updown_script, self.brname, "startup"]) - - if self.serverintf: - # sets the interface as a port of the bridge - utils.check_cmd([constants.BRCTL_BIN, "addif", self.brname, self.serverintf]) - - # bring interface up - utils.check_cmd([constants.IP_BIN, "link", "set", self.serverintf, "up"]) - - def detectoldbridge(self): - """ - Occassionally, control net bridges from previously closed sessions are not cleaned up. - Check if there are old control net bridges and delete them - - :return: True if an old bridge was detected, False otherwise - :rtype: bool - """ - status, output = utils.cmd_output([constants.BRCTL_BIN, "show"]) - if status != 0: - logging.error("Unable to retrieve list of installed bridges") - else: - lines = output.split("\n") - for line in lines[1:]: - cols = line.split("\t") - oldbr = cols[0] - flds = cols[0].split(".") - if len(flds) == 3: - if flds[0] == "b" and flds[1] == self.id: - logging.error( - "error: An active control net bridge (%s) found. " - "An older session might still be running. " - "Stop all sessions and, if needed, delete %s to continue.", oldbr, oldbr - ) - return True - return False - - def shutdown(self): - """ - Control network shutdown. - - :return: nothing - """ - if self.serverintf is not None: - try: - utils.check_cmd([constants.BRCTL_BIN, "delif", self.brname, self.serverintf]) - except CoreCommandError: - logging.exception("error deleting server interface %s from bridge %s", self.serverintf, self.brname) - - if self.updown_script is not None: - try: - logging.info("interface %s updown script (%s shutdown) called", self.brname, self.updown_script) - utils.check_cmd([self.updown_script, self.brname, "shutdown"]) - except CoreCommandError: - logging.exception("error issuing shutdown script shutdown") - - LxBrNet.shutdown(self) - - def all_link_data(self, flags): - """ - Do not include CtrlNet in link messages describing this session. - - :param flags: message flags - :return: list of link data - :rtype: list[core.data.LinkData] - """ - return [] - - -class CoreNode(LxcNode): - """ - Basic core node class for nodes to extend. - """ - apitype = NodeTypes.DEFAULT.value - - -class PtpNet(LxBrNet): - """ - Peer to peer network node. - """ - policy = "ACCEPT" - - def attach(self, netif): - """ - Attach a network interface, but limit attachment to two interfaces. - - :param core.netns.vif.VEth netif: network interface - :return: nothing - """ - if len(self._netif) >= 2: - raise ValueError("Point-to-point links support at most 2 network interfaces") - - LxBrNet.attach(self, netif) - - def data(self, message_type, lat=None, lon=None, alt=None): - """ - Do not generate a Node Message for point-to-point links. They are - built using a link message instead. - - :param message_type: purpose for the data object we are creating - :param float lat: latitude - :param float lon: longitude - :param float alt: altitude - :return: node data object - :rtype: core.data.NodeData - """ - return None - - def all_link_data(self, flags): - """ - Build CORE API TLVs for a point-to-point link. One Link message - describes this network. - - :param flags: message flags - :return: list of link data - :rtype: list[core.data.LinkData] - """ - - all_links = [] - - if len(self._netif) != 2: - return all_links - - if1, if2 = self._netif.values() - - unidirectional = 0 - if if1.getparams() != if2.getparams(): - unidirectional = 1 - - interface1_ip4 = None - interface1_ip4_mask = None - interface1_ip6 = None - interface1_ip6_mask = None - for address in if1.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if ipaddress.is_ipv4_address(ip): - family = AF_INET - ipl = socket.inet_pton(family, ip) - interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl) - interface1_ip4_mask = mask - else: - family = AF_INET6 - ipl = socket.inet_pton(family, ip) - interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl) - interface1_ip6_mask = mask - - interface2_ip4 = None - interface2_ip4_mask = None - interface2_ip6 = None - interface2_ip6_mask = None - for address in if2.addrlist: - ip, _sep, mask = address.partition("/") - mask = int(mask) - if ipaddress.is_ipv4_address(ip): - family = AF_INET - ipl = socket.inet_pton(family, ip) - interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) - interface2_ip4_mask = mask - else: - family = AF_INET6 - ipl = socket.inet_pton(family, ip) - interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) - interface2_ip6_mask = mask - - link_data = LinkData( - message_type=flags, - node1_id=if1.node.id, - node2_id=if2.node.id, - link_type=self.linktype, - unidirectional=unidirectional, - delay=if1.getparam("delay"), - bandwidth=if1.getparam("bw"), - dup=if1.getparam("duplicate"), - jitter=if1.getparam("jitter"), - interface1_id=if1.node.getifindex(if1), - interface1_mac=if1.hwaddr, - interface1_ip4=interface1_ip4, - interface1_ip4_mask=interface1_ip4_mask, - interface1_ip6=interface1_ip6, - interface1_ip6_mask=interface1_ip6_mask, - interface2_id=if2.node.getifindex(if2), - interface2_mac=if2.hwaddr, - interface2_ip4=interface2_ip4, - interface2_ip4_mask=interface2_ip4_mask, - interface2_ip6=interface2_ip6, - interface2_ip6_mask=interface2_ip6_mask, - ) - - all_links.append(link_data) - - # build a 2nd link message for the upstream link parameters - # (swap if1 and if2) - if unidirectional: - link_data = LinkData( - message_type=0, - node1_id=if2.node.id, - node2_id=if1.node.id, - delay=if1.getparam("delay"), - bandwidth=if1.getparam("bw"), - dup=if1.getparam("duplicate"), - jitter=if1.getparam("jitter"), - unidirectional=1, - interface1_id=if2.node.getifindex(if2), - interface2_id=if1.node.getifindex(if1) - ) - all_links.append(link_data) - - return all_links - - -class SwitchNode(LxBrNet): - """ - Provides switch functionality within a core node. - """ - apitype = NodeTypes.SWITCH.value - policy = "ACCEPT" - type = "lanswitch" - - -class HubNode(LxBrNet): - """ - Provides hub functionality within a core node, forwards packets to all bridge - ports by turning off MAC address learning. - """ - apitype = NodeTypes.HUB.value - policy = "ACCEPT" - type = "hub" - - def __init__(self, session, _id=None, name=None, start=True): - """ - Creates a HubNode instance. - - :param core.session.Session session: core session instance - :param int _id: node id - :param str name: node namee - :param bool start: start flag - :raises CoreCommandError: when there is a command exception - """ - LxBrNet.__init__(self, session, _id, name, start) - - # TODO: move to startup method - if start: - utils.check_cmd([constants.BRCTL_BIN, "setageing", self.brname, "0"]) - - -class WlanNode(LxBrNet): - """ - Provides wireless lan functionality within a core node. - """ - apitype = NodeTypes.WIRELESS_LAN.value - linktype = LinkTypes.WIRELESS.value - policy = "DROP" - type = "wlan" - - def __init__(self, session, _id=None, name=None, start=True, policy=None): - """ - Create a WlanNode instance. - - :param core.session.Session session: core session instance - :param int _id: node id - :param str name: node name - :param bool start: start flag - :param policy: wlan policy - """ - LxBrNet.__init__(self, session, _id, name, start, policy) - # wireless model such as basic range - self.model = None - # mobility model such as scripted - self.mobility = None - - def attach(self, netif): - """ - Attach a network interface. - - :param core.netns.vif.VEth netif: network interface - :return: nothing - """ - LxBrNet.attach(self, netif) - if self.model: - netif.poshook = self.model.position_callback - if netif.node is None: - return - x, y, z = netif.node.position.get() - # invokes any netif.poshook - netif.setposition(x, y, z) - - def setmodel(self, model, config): - """ - Sets the mobility and wireless model. - - :param core.mobility.WirelessModel.cls model: wireless model to set to - :param dict config: configuration for model being set - :return: nothing - """ - logging.info("adding model: %s", model.name) - if model.config_type == RegisterTlvs.WIRELESS.value: - self.model = model(session=self.session, object_id=self.id) - self.model.update_config(config) - if self.model.position_callback: - for netif in self.netifs(): - netif.poshook = self.model.position_callback - if netif.node is not None: - x, y, z = netif.node.position.get() - netif.poshook(netif, x, y, z) - self.model.setlinkparams() - elif model.config_type == RegisterTlvs.MOBILITY.value: - self.mobility = model(session=self.session, object_id=self.id) - self.mobility.update_config(config) - - def update_mobility(self, config): - if not self.mobility: - raise ValueError("no mobility set to update for node(%s)", self.id) - self.mobility.set_configs(config, node_id=self.id) - - def updatemodel(self, config): - if not self.model: - raise ValueError("no model set to update for node(%s)", self.id) - logging.info("node(%s) updating model(%s): %s", self.id, self.model.name, config) - self.model.set_configs(config, node_id=self.id) - if self.model.position_callback: - for netif in self.netifs(): - netif.poshook = self.model.position_callback - if netif.node is not None: - x, y, z = netif.node.position.get() - netif.poshook(netif, x, y, z) - self.model.updateconfig() - - def all_link_data(self, flags): - """ - Retrieve all link data. - - :param flags: message flags - :return: list of link data - :rtype: list[core.data.LinkData] - """ - all_links = LxBrNet.all_link_data(self, flags) - - if self.model: - all_links.extend(self.model.all_link_data(flags)) - - return all_links - - -class RJ45Node(PyCoreNode, PyCoreNetIf): - """ - RJ45Node is a physical interface on the host linked to the emulated - network. - """ - apitype = NodeTypes.RJ45.value - type = "rj45" - - def __init__(self, session, _id=None, name=None, mtu=1500, start=True): - """ - Create an RJ45Node instance. - - :param core.session.Session session: core session instance - :param int _id: node id - :param str name: node name - :param mtu: rj45 mtu - :param bool start: start flag - :return: - """ - PyCoreNode.__init__(self, session, _id, name, start=start) - PyCoreNetIf.__init__(self, node=self, name=name, mtu=mtu) - self.up = False - self.lock = threading.RLock() - self.ifindex = None - # the following are PyCoreNetIf attributes - self.transport_type = "raw" - self.localname = name - self.old_up = False - self.old_addrs = [] - - if start: - self.startup() - - def startup(self): - """ - Set the interface in the up state. - - :return: nothing - :raises CoreCommandError: when there is a command exception - """ - # interface will also be marked up during net.attach() - self.savestate() - utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "up"]) - self.up = True - - def shutdown(self): - """ - Bring the interface down. Remove any addresses and queuing - disciplines. - - :return: nothing - """ - if not self.up: - return - - try: - utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "down"]) - utils.check_cmd([constants.IP_BIN, "addr", "flush", "dev", self.localname]) - utils.check_cmd([constants.TC_BIN, "qdisc", "del", "dev", self.localname, "root"]) - except CoreCommandError: - logging.exception("error shutting down") - - self.up = False - self.restorestate() - - # TODO: issue in that both classes inherited from provide the same method with different signatures - def attachnet(self, net): - """ - Attach a network. - - :param core.coreobj.PyCoreNet net: network to attach - :return: nothing - """ - PyCoreNetIf.attachnet(self, net) - - # TODO: issue in that both classes inherited from provide the same method with different signatures - def detachnet(self): - """ - Detach a network. - - :return: nothing - """ - PyCoreNetIf.detachnet(self) - - def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): - """ - This is called when linking with another node. Since this node - represents an interface, we do not create another object here, - but attach ourselves to the given network. - - :param core.coreobj.PyCoreNet net: new network instance - :param list[str] addrlist: address list - :param str hwaddr: hardware address - :param int ifindex: interface index - :param str ifname: interface name - :return: interface index - :rtype: int - :raises ValueError: when an interface has already been created, one max - """ - with self.lock: - if ifindex is None: - ifindex = 0 - - if self.net is not None: - raise ValueError("RJ45 nodes support at most 1 network interface") - - self._netif[ifindex] = self - # PyCoreNetIf.node is self - self.node = self - self.ifindex = ifindex - - if net is not None: - self.attachnet(net) - - if addrlist: - for addr in utils.make_tuple(addrlist): - self.addaddr(addr) - - return ifindex - - def delnetif(self, ifindex): - """ - Delete a network interface. - - :param int ifindex: interface index to delete - :return: nothing - """ - if ifindex is None: - ifindex = 0 - - self._netif.pop(ifindex) - - if ifindex == self.ifindex: - self.shutdown() - else: - raise ValueError("ifindex %s does not exist" % ifindex) - - def netif(self, ifindex, net=None): - """ - This object is considered the network interface, so we only - return self here. This keeps the RJ45Node compatible with - real nodes. - - :param int ifindex: interface index to retrieve - :param net: network to retrieve - :return: a network interface - :rtype: core.coreobj.PyCoreNetIf - """ - if net is not None and net == self.net: - return self - - if ifindex is None: - ifindex = 0 - - if ifindex == self.ifindex: - return self - - return None - - def getifindex(self, netif): - """ - Retrieve network interface index. - - :param core.coreobj.PyCoreNetIf netif: network interface to retrieve index for - :return: interface index, None otherwise - :rtype: int - """ - if netif != self: - return None - - return self.ifindex - - def addaddr(self, addr): - """ - Add address to to network interface. - - :param str addr: address to add - :return: nothing - :raises CoreCommandError: when there is a command exception - """ - if self.up: - utils.check_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name]) - - PyCoreNetIf.addaddr(self, addr) - - def deladdr(self, addr): - """ - Delete address from network interface. - - :param str addr: address to delete - :return: nothing - :raises CoreCommandError: when there is a command exception - """ - if self.up: - utils.check_cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.name]) - - PyCoreNetIf.deladdr(self, addr) - - def savestate(self): - """ - Save the addresses and other interface state before using the - interface for emulation purposes. TODO: save/restore the PROMISC flag - - :return: nothing - :raises CoreCommandError: when there is a command exception - """ - self.old_up = False - self.old_addrs = [] - args = [constants.IP_BIN, "addr", "show", "dev", self.localname] - output = utils.check_cmd(args) - for line in output.split("\n"): - items = line.split() - if len(items) < 2: - continue - - if items[1] == "%s:" % self.localname: - flags = items[2][1:-1].split(",") - if "UP" in flags: - self.old_up = True - elif items[0] == "inet": - self.old_addrs.append((items[1], items[3])) - elif items[0] == "inet6": - if items[1][:4] == "fe80": - continue - self.old_addrs.append((items[1], None)) - - def restorestate(self): - """ - Restore the addresses and other interface state after using it. - - :return: nothing - :raises CoreCommandError: when there is a command exception - """ - for addr in self.old_addrs: - if addr[1] is None: - utils.check_cmd([constants.IP_BIN, "addr", "add", addr[0], "dev", self.localname]) - else: - utils.check_cmd([constants.IP_BIN, "addr", "add", addr[0], "brd", addr[1], "dev", self.localname]) - - if self.old_up: - utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "up"]) - - def setposition(self, x=None, y=None, z=None): - """ - Uses setposition from both parent classes. - - :param float x: x position - :param float y: y position - :param float z: z position - :return: True if position changed, False otherwise - :rtype: bool - """ - result = PyCoreObj.setposition(self, x, y, z) - PyCoreNetIf.setposition(self, x, y, z) - return result - - def check_cmd(self, args): - """ - Runs shell command on node. - - :param list[str]|str args: command to run - :return: exist status and combined stdout and stderr - :rtype: tuple[int, str] - :raises CoreCommandError: when a non-zero exit status occurs - """ - raise NotImplementedError - - def cmd(self, args, wait=True): - """ - Runs shell command on node, with option to not wait for a result. - - :param list[str]|str args: command to run - :param bool wait: wait for command to exit, defaults to True - :return: exit status for command - :rtype: int - """ - raise NotImplementedError - - def cmd_output(self, args): - """ - Runs shell command on node and get exit status and output. - - :param list[str]|str args: command to run - :return: exit status and combined stdout and stderr - :rtype: tuple[int, str] - """ - raise NotImplementedError - - def termcmdstring(self, sh): - """ - Create a terminal command string. - - :param str sh: shell to execute command in - :return: str - """ - raise NotImplementedError - - -class TunnelNode(GreTapBridge): - """ - Provides tunnel functionality in a core node. - """ - apitype = NodeTypes.TUNNEL.value - policy = "ACCEPT" - type = "tunnel" diff --git a/daemon/core/netns/vnode.py b/daemon/core/netns/vnode.py deleted file mode 100644 index d56418d0..00000000 --- a/daemon/core/netns/vnode.py +++ /dev/null @@ -1,609 +0,0 @@ -""" -PyCoreNode and LxcNode classes that implement the network namespac virtual node. -""" - -import errno -import logging -import os -import random -import shutil -import signal -import string -import threading - -from core import CoreCommandError -from core import constants -from core.coreobj import PyCoreNetIf -from core.coreobj import PyCoreNode -from core.enumerations import NodeTypes -from core.misc import nodeutils -from core.misc import utils -from core.misc.ipaddress import MacAddress -from core.netns import vnodeclient -from core.netns.vif import TunTap -from core.netns.vif import VEth - -_DEFAULT_MTU = 1500 - -utils.check_executables([constants.IP_BIN]) - - -class SimpleLxcNode(PyCoreNode): - """ - Provides simple lxc functionality for core nodes. - - :var nodedir: str - :var ctrlchnlname: str - :var client: core.netns.vnodeclient.VnodeClient - :var pid: int - :var up: bool - :var lock: threading.RLock - :var _mounts: list[tuple[str, str]] - """ - valid_address_types = {"inet", "inet6", "inet6link"} - - def __init__(self, session, _id=None, name=None, nodedir=None, start=True): - """ - Create a SimpleLxcNode instance. - - :param core.session.Session session: core session instance - :param int _id: object id - :param str name: object name - :param str nodedir: node directory - :param bool start: start flag - """ - PyCoreNode.__init__(self, session, _id, name, start=start) - self.nodedir = nodedir - self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name)) - self.client = None - self.pid = None - self.up = False - self.lock = threading.RLock() - self._mounts = [] - - def alive(self): - """ - Check if the node is alive. - - :return: True if node is alive, False otherwise - :rtype: bool - """ - try: - os.kill(self.pid, 0) - except OSError: - return False - - return True - - def startup(self): - """ - Start a new namespace node by invoking the vnoded process that - allocates a new namespace. Bring up the loopback device and set - the hostname. - - :return: nothing - """ - if self.up: - raise ValueError("starting a node that is already up") - - # create a new namespace for this node using vnoded - vnoded = [ - constants.VNODED_BIN, - "-v", - "-c", self.ctrlchnlname, - "-l", self.ctrlchnlname + ".log", - "-p", self.ctrlchnlname + ".pid" - ] - if self.nodedir: - vnoded += ["-C", self.nodedir] - env = self.session.get_environment(state=False) - env["NODE_NUMBER"] = str(self.id) - env["NODE_NAME"] = str(self.name) - - output = utils.check_cmd(vnoded, env=env) - self.pid = int(output) - - # create vnode client - self.client = vnodeclient.VnodeClient(self.name, self.ctrlchnlname) - - # bring up the loopback interface - logging.debug("bringing up loopback interface") - self.check_cmd([constants.IP_BIN, "link", "set", "lo", "up"]) - - # set hostname for node - logging.debug("setting hostname: %s", self.name) - self.check_cmd(["hostname", self.name]) - - # mark node as up - self.up = True - - def shutdown(self): - """ - Shutdown logic for simple lxc nodes. - - :return: nothing - """ - # nothing to do if node is not up - if not self.up: - return - - # unmount all targets (NOTE: non-persistent mount namespaces are - # removed by the kernel when last referencing process is killed) - self._mounts = [] - - # shutdown all interfaces - for netif in self.netifs(): - netif.shutdown() - - # attempt to kill node process and wait for termination of children - try: - os.kill(self.pid, signal.SIGTERM) - os.waitpid(self.pid, 0) - except OSError as e: - if e.errno != 10: - logging.exception("error killing process") - - # remove node directory if present - try: - os.unlink(self.ctrlchnlname) - except OSError as e: - # no such file or directory - if e.errno != errno.ENOENT: - logging.exception("error removing node directory") - - # clear interface data, close client, and mark self and not up - self._netif.clear() - self.client.close() - self.up = False - - def cmd(self, args, wait=True): - """ - Runs shell command on node, with option to not wait for a result. - - :param list[str]|str args: command to run - :param bool wait: wait for command to exit, defaults to True - :return: exit status for command - :rtype: int - """ - return self.client.cmd(args, wait) - - def cmd_output(self, args): - """ - Runs shell command on node and get exit status and output. - - :param list[str]|str args: command to run - :return: exit status and combined stdout and stderr - :rtype: tuple[int, str] - """ - return self.client.cmd_output(args) - - def check_cmd(self, args): - """ - Runs shell command on node. - - :param list[str]|str args: command to run - :return: combined stdout and stderr - :rtype: str - :raises CoreCommandError: when a non-zero exit status occurs - """ - return self.client.check_cmd(args) - - def termcmdstring(self, sh="/bin/sh"): - """ - Create a terminal command string. - - :param str sh: shell to execute command in - :return: str - """ - return self.client.termcmdstring(sh) - - def mount(self, source, target): - """ - Create and mount a directory. - - :param str source: source directory to mount - :param str target: target directory to create - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - source = os.path.abspath(source) - logging.info("node(%s) mounting: %s at %s", self.name, source, target) - cmd = 'mkdir -p "%s" && %s -n --bind "%s" "%s"' % (target, constants.MOUNT_BIN, source, target) - status, output = self.client.shcmd_result(cmd) - if status: - raise CoreCommandError(status, cmd, output) - self._mounts.append((source, target)) - - def newifindex(self): - """ - Retrieve a new interface index. - - :return: new interface index - :rtype: int - """ - with self.lock: - return super(SimpleLxcNode, self).newifindex() - - def newveth(self, ifindex=None, ifname=None, net=None): - """ - Create a new interface. - - :param int ifindex: index for the new interface - :param str ifname: name for the new interface - :param net: network to associate interface with - :return: nothing - """ - with self.lock: - if ifindex is None: - ifindex = self.newifindex() - - if ifname is None: - ifname = "eth%d" % ifindex - - sessionid = self.session.short_session_id() - - try: - suffix = "%x.%s.%s" % (self.id, ifindex, sessionid) - except TypeError: - suffix = "%s.%s.%s" % (self.id, ifindex, sessionid) - - localname = "veth" + suffix - if len(localname) >= 16: - raise ValueError("interface local name (%s) too long" % localname) - - name = localname + "p" - if len(name) >= 16: - raise ValueError("interface name (%s) too long" % name) - - veth = VEth(node=self, name=name, localname=localname, net=net, start=self.up) - - if self.up: - utils.check_cmd([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)]) - self.check_cmd([constants.IP_BIN, "link", "set", veth.name, "name", ifname]) - - veth.name = ifname - - if self.up: - # TODO: potentially find better way to query interface ID - # retrieve interface information - output = self.check_cmd(["ip", "link", "show", veth.name]) - logging.debug("interface command output: %s", output) - output = output.split("\n") - veth.flow_id = int(output[0].strip().split(":")[0]) + 1 - logging.debug("interface flow index: %s - %s", veth.name, veth.flow_id) - veth.hwaddr = MacAddress.from_string(output[1].strip().split()[1]) - logging.debug("interface mac: %s - %s", veth.name, veth.hwaddr) - - try: - self.addnetif(veth, ifindex) - except ValueError as e: - veth.shutdown() - del veth - raise e - - return ifindex - - def newtuntap(self, ifindex=None, ifname=None, net=None): - """ - Create a new tunnel tap. - - :param int ifindex: interface index - :param str ifname: interface name - :param net: network to associate with - :return: interface index - :rtype: int - """ - with self.lock: - if ifindex is None: - ifindex = self.newifindex() - - if ifname is None: - ifname = "eth%d" % ifindex - - sessionid = self.session.short_session_id() - localname = "tap%s.%s.%s" % (self.id, ifindex, sessionid) - name = ifname - tuntap = TunTap(node=self, name=name, localname=localname, net=net, start=self.up) - - try: - self.addnetif(tuntap, ifindex) - except ValueError as e: - tuntap.shutdown() - del tuntap - raise e - - return ifindex - - def sethwaddr(self, ifindex, addr): - """ - Set hardware addres for an interface. - - :param int ifindex: index of interface to set hardware address for - :param core.misc.ipaddress.MacAddress addr: hardware address to set - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - self._netif[ifindex].sethwaddr(addr) - if self.up: - args = [constants.IP_BIN, "link", "set", "dev", self.ifname(ifindex), "address", str(addr)] - self.check_cmd(args) - - def addaddr(self, ifindex, addr): - """ - Add interface address. - - :param int ifindex: index of interface to add address to - :param str addr: address to add to interface - :return: nothing - """ - if self.up: - # check if addr is ipv6 - if ":" in str(addr): - args = [constants.IP_BIN, "addr", "add", str(addr), "dev", self.ifname(ifindex)] - self.check_cmd(args) - else: - args = [constants.IP_BIN, "addr", "add", str(addr), "broadcast", "+", "dev", self.ifname(ifindex)] - self.check_cmd(args) - - self._netif[ifindex].addaddr(addr) - - def deladdr(self, ifindex, addr): - """ - Delete address from an interface. - - :param int ifindex: index of interface to delete address from - :param str addr: address to delete from interface - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - try: - self._netif[ifindex].deladdr(addr) - except ValueError: - logging.exception("trying to delete unknown address: %s" % addr) - - if self.up: - self.check_cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)]) - - def delalladdr(self, ifindex, address_types=None): - """ - Delete all addresses from an interface. - - :param int ifindex: index of interface to delete address types from - :param tuple[str] address_types: address types to delete - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - if not address_types: - address_types = self.valid_address_types - - interface_name = self.ifname(ifindex) - addresses = self.client.getaddr(interface_name, rescan=True) - - for address_type in address_types: - if address_type not in self.valid_address_types: - raise ValueError("addr type must be in: %s" % " ".join(self.valid_address_types)) - for address in addresses[address_type]: - self.deladdr(ifindex, address) - - # update cached information - self.client.getaddr(interface_name, rescan=True) - - def ifup(self, ifindex): - """ - Bring an interface up. - - :param int ifindex: index of interface to bring up - :return: nothing - """ - if self.up: - self.check_cmd([constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"]) - - def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): - """ - Create a new network interface. - - :param net: network to associate with - :param list addrlist: addresses to add on the interface - :param core.misc.ipaddress.MacAddress hwaddr: hardware address to set for interface - :param int ifindex: index of interface to create - :param str ifname: name for interface - :return: interface index - :rtype: int - """ - if not addrlist: - addrlist = [] - - with self.lock: - # TODO: see if you can move this to emane specific code - if nodeutils.is_node(net, NodeTypes.EMANE): - ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname, net=net) - # TUN/TAP is not ready for addressing yet; the device may - # take some time to appear, and installing it into a - # namespace after it has been bound removes addressing; - # save addresses with the interface now - self.attachnet(ifindex, net) - netif = self.netif(ifindex) - netif.sethwaddr(hwaddr) - for address in utils.make_tuple(addrlist): - netif.addaddr(address) - return ifindex - else: - ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net) - - if net is not None: - self.attachnet(ifindex, net) - - if hwaddr: - self.sethwaddr(ifindex, hwaddr) - - for address in utils.make_tuple(addrlist): - self.addaddr(ifindex, address) - - self.ifup(ifindex) - return ifindex - - def connectnode(self, ifname, othernode, otherifname): - """ - Connect a node. - - :param str ifname: name of interface to connect - :param core.netns.nodes.LxcNode othernode: node to connect to - :param str otherifname: interface name to connect to - :return: nothing - """ - tmplen = 8 - tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in xrange(tmplen)]) - tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in xrange(tmplen)]) - utils.check_cmd([constants.IP_BIN, "link", "add", "name", tmp1, "type", "veth", "peer", "name", tmp2]) - - utils.check_cmd([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)]) - self.check_cmd([constants.IP_BIN, "link", "set", tmp1, "name", ifname]) - interface = PyCoreNetIf(node=self, name=ifname, mtu=_DEFAULT_MTU) - self.addnetif(interface, self.newifindex()) - - utils.check_cmd([constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)]) - othernode.check_cmd([constants.IP_BIN, "link", "set", tmp2, "name", otherifname]) - other_interface = PyCoreNetIf(node=othernode, name=otherifname, mtu=_DEFAULT_MTU) - othernode.addnetif(other_interface, othernode.newifindex()) - - def addfile(self, srcname, filename): - """ - Add a file. - - :param str srcname: source file name - :param str filename: file name to add - :return: nothing - :raises CoreCommandError: when a non-zero exit status occurs - """ - logging.info("adding file from %s to %s", srcname, filename) - directory = os.path.dirname(filename) - - cmd = 'mkdir -p "%s" && mv "%s" "%s" && sync' % (directory, srcname, filename) - status, output = self.client.shcmd_result(cmd) - if status: - raise CoreCommandError(status, cmd, output) - - -class LxcNode(SimpleLxcNode): - """ - Provides lcx node functionality for core nodes. - """ - - def __init__(self, session, _id=None, name=None, nodedir=None, bootsh="boot.sh", start=True): - """ - Create a LxcNode instance. - - :param core.session.Session session: core session instance - :param int _id: object id - :param str name: object name - :param str nodedir: node directory - :param bootsh: boot shell - :param bool start: start flag - """ - super(LxcNode, self).__init__(session=session, _id=_id, name=name, nodedir=nodedir, start=start) - self.bootsh = bootsh - if start: - self.startup() - - def startup(self): - """ - Startup logic for the node. - - :return: nothing - """ - with self.lock: - self.makenodedir() - super(LxcNode, self).startup() - self.privatedir("/var/run") - self.privatedir("/var/log") - - def shutdown(self): - """ - Shutdown logic for the node. - - :return: nothing - """ - if not self.up: - return - - with self.lock: - try: - super(LxcNode, self).shutdown() - except OSError: - logging.exception("error during shutdown") - finally: - self.rmnodedir() - - def privatedir(self, path): - """ - Create a private directory. - - :param str path: path to create - :return: nothing - """ - if path[0] != "/": - raise ValueError("path not fully qualified: %s" % path) - hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip("/").replace("/", ".")) - os.mkdir(hostpath) - self.mount(hostpath, path) - - def hostfilename(self, filename): - """ - Return the name of a node"s file on the host filesystem. - - :param str filename: host file name - :return: path to file - """ - dirname, basename = os.path.split(filename) - if not basename: - raise ValueError("no basename for filename: %s" % filename) - if dirname and dirname[0] == "/": - dirname = dirname[1:] - dirname = dirname.replace("/", ".") - dirname = os.path.join(self.nodedir, dirname) - return os.path.join(dirname, basename) - - def opennodefile(self, filename, mode="w"): - """ - Open a node file, within it"s directory. - - :param str filename: file name to open - :param str mode: mode to open file in - :return: open file - :rtype: file - """ - hostfilename = self.hostfilename(filename) - dirname, _basename = os.path.split(hostfilename) - if not os.path.isdir(dirname): - os.makedirs(dirname, mode=0755) - return open(hostfilename, mode) - - def nodefile(self, filename, contents, mode=0644): - """ - Create a node file with a given mode. - - :param str filename: name of file to create - :param contents: contents of file - :param int mode: mode for file - :return: nothing - """ - with self.opennodefile(filename, "w") as open_file: - open_file.write(contents) - os.chmod(open_file.name, mode) - logging.info("node(%s) added file: %s; mode: 0%o", self.name, open_file.name, mode) - - def nodefilecopy(self, filename, srcfilename, mode=None): - """ - Copy a file to a node, following symlinks and preserving metadata. - Change file mode if specified. - - :param str filename: file name to copy file to - :param str srcfilename: file to copy - :param int mode: mode to copy to - :return: nothing - """ - hostfilename = self.hostfilename(filename) - shutil.copy2(srcfilename, hostfilename) - if mode is not None: - os.chmod(hostfilename, mode) - logging.info("node(%s) copied file: %s; mode: %s", self.name, hostfilename, mode) diff --git a/daemon/core/phys/__init__.py b/daemon/core/nodes/__init__.py similarity index 100% rename from daemon/core/phys/__init__.py rename to daemon/core/nodes/__init__.py diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py new file mode 100644 index 00000000..a5d5a70a --- /dev/null +++ b/daemon/core/nodes/base.py @@ -0,0 +1,1158 @@ +""" +PyCoreNode and LxcNode classes that implement the network namespac virtual node. +""" + +import errno +import logging +import os +import random +import shutil +import signal +import socket +import string +import threading +from socket import AF_INET, AF_INET6 + +from core import CoreCommandError, utils +from core import constants +from core.emulator.data import NodeData, LinkData +from core.emulator.enumerations import NodeTypes, LinkTypes +from core.nodes.ipaddress import MacAddress +from core.nodes import client, nodeutils, ipaddress +from core.nodes.interface import TunTap, CoreInterface +from core.nodes.interface import Veth + +_DEFAULT_MTU = 1500 + +utils.check_executables([constants.IP_BIN]) + + +class NodeBase(object): + """ + Base class for CORE nodes (nodes and networks) + """ + apitype = None + + # TODO: appears start has no usage, verify and remove + def __init__(self, session, _id=None, name=None, start=True): + """ + Creates a PyCoreObj instance. + + :param core.session.Session session: CORE session object + :param int _id: id + :param str name: object name + :param bool start: start value + :return: + """ + + self.session = session + if _id is None: + _id = session.get_node_id() + self.id = _id + if name is None: + name = "o%s" % self.id + self.name = name + self.type = None + self.server = None + self.services = None + # ifindex is key, PyCoreNetIf instance is value + self._netif = {} + self.ifindex = 0 + self.canvas = None + self.icon = None + self.opaque = None + self.position = Position() + + def startup(self): + """ + Each object implements its own startup method. + + :return: nothing + """ + raise NotImplementedError + + def shutdown(self): + """ + Each object implements its own shutdown method. + + :return: nothing + """ + raise NotImplementedError + + def setposition(self, x=None, y=None, z=None): + """ + Set the (x,y,z) position of the object. + + :param float x: x position + :param float y: y position + :param float z: z position + :return: True if position changed, False otherwise + :rtype: bool + """ + return self.position.set(x=x, y=y, z=z) + + def getposition(self): + """ + Return an (x,y,z) tuple representing this object's position. + + :return: x,y,z position tuple + :rtype: tuple + """ + return self.position.get() + + def ifname(self, ifindex): + """ + Retrieve interface name for index. + + :param int ifindex: interface index + :return: interface name + :rtype: str + """ + return self._netif[ifindex].name + + def netifs(self, sort=False): + """ + Retrieve network interfaces, sorted if desired. + + :param bool sort: boolean used to determine if interfaces should be sorted + :return: network interfaces + :rtype: list + """ + if sort: + return map(lambda k: self._netif[k], sorted(self._netif.keys())) + else: + return self._netif.itervalues() + + def numnetif(self): + """ + Return the attached interface count. + + :return: number of network interfaces + :rtype: int + """ + return len(self._netif) + + def getifindex(self, netif): + """ + Retrieve index for an interface. + + :param core.nodes.interface.CoreInterface netif: interface to get index for + :return: interface index if found, -1 otherwise + :rtype: int + """ + + for ifindex in self._netif: + if self._netif[ifindex] is netif: + return ifindex + + return -1 + + def newifindex(self): + """ + Create a new interface index. + + :return: interface index + :rtype: int + """ + while self.ifindex in self._netif: + self.ifindex += 1 + ifindex = self.ifindex + self.ifindex += 1 + return ifindex + + def data(self, message_type, lat=None, lon=None, alt=None): + """ + Build a data object for this node. + + :param message_type: purpose for the data object we are creating + :param str lat: latitude + :param str lon: longitude + :param str alt: altitude + :return: node data object + :rtype: core.data.NodeData + """ + if self.apitype is None: + return None + + x, y, _ = self.getposition() + model = self.type + emulation_server = self.server + + services = self.services + if services is not None: + services = "|".join([service.name for service in services]) + + node_data = NodeData( + message_type=message_type, + id=self.id, + node_type=self.apitype, + name=self.name, + emulation_id=self.id, + canvas=self.canvas, + icon=self.icon, + opaque=self.opaque, + x_position=x, + y_position=y, + latitude=lat, + longitude=lon, + altitude=alt, + model=model, + emulation_server=emulation_server, + services=services + ) + + return node_data + + def all_link_data(self, flags): + """ + Build CORE Link data for this object. There is no default + method for PyCoreObjs as PyCoreNodes do not implement this but + PyCoreNets do. + + :param flags: message flags + :return: list of link data + :rtype: core.data.LinkData + """ + return [] + + +class CoreNodeBase(NodeBase): + """ + Base class for CORE nodes. + """ + + def __init__(self, session, _id=None, name=None, start=True): + """ + Create a CoreNodeBase instance. + + :param core.session.Session session: CORE session object + :param int _id: object id + :param str name: object name + :param bool start: boolean for starting + """ + super(CoreNodeBase, self).__init__(session, _id, name, start=start) + self.services = [] + self.nodedir = None + self.tmpnodedir = False + + def addservice(self, service): + """ + Add a services to the service list. + + :param core.services.coreservices.CoreService service: service to add + :return: nothing + """ + if service is not None: + self.services.append(service) + + def makenodedir(self): + """ + Create the node directory. + + :return: nothing + """ + if self.nodedir is None: + self.nodedir = os.path.join(self.session.session_dir, self.name + ".conf") + os.makedirs(self.nodedir) + self.tmpnodedir = True + else: + self.tmpnodedir = False + + def rmnodedir(self): + """ + Remove the node directory, unless preserve directory has been set. + + :return: nothing + """ + preserve = self.session.options.get_config("preservedir") == "1" + if preserve: + return + + if self.tmpnodedir: + shutil.rmtree(self.nodedir, ignore_errors=True) + + def addnetif(self, netif, ifindex): + """ + Add network interface to node and set the network interface index if successful. + + :param core.nodes.interface.CoreInterface netif: network interface to add + :param int ifindex: interface index + :return: nothing + """ + if ifindex in self._netif: + raise ValueError("ifindex %s already exists" % ifindex) + self._netif[ifindex] = netif + # TODO: this should have probably been set ahead, seems bad to me, check for failure and fix + netif.netindex = ifindex + + def delnetif(self, ifindex): + """ + Delete a network interface + + :param int ifindex: interface index to delete + :return: nothing + """ + if ifindex not in self._netif: + raise ValueError("ifindex %s does not exist" % ifindex) + netif = self._netif.pop(ifindex) + netif.shutdown() + del netif + + # TODO: net parameter is not used, remove + def netif(self, ifindex, net=None): + """ + Retrieve network interface. + + :param int ifindex: index of interface to retrieve + :param core.nodes.interface.CoreInterface net: network node + :return: network interface, or None if not found + :rtype: core.nodes.interface.CoreInterface + """ + if ifindex in self._netif: + return self._netif[ifindex] + else: + return None + + def attachnet(self, ifindex, net): + """ + Attach a network. + + :param int ifindex: interface of index to attach + :param core.nodes.interface.CoreInterface net: network to attach + :return: + """ + if ifindex not in self._netif: + raise ValueError("ifindex %s does not exist" % ifindex) + self._netif[ifindex].attachnet(net) + + def detachnet(self, ifindex): + """ + Detach network interface. + + :param int ifindex: interface index to detach + :return: nothing + """ + if ifindex not in self._netif: + raise ValueError("ifindex %s does not exist" % ifindex) + self._netif[ifindex].detachnet() + + def setposition(self, x=None, y=None, z=None): + """ + Set position. + + :param x: x position + :param y: y position + :param z: z position + :return: nothing + """ + changed = super(CoreNodeBase, self).setposition(x, y, z) + if changed: + for netif in self.netifs(sort=True): + netif.setposition(x, y, z) + + def commonnets(self, obj, want_ctrl=False): + """ + Given another node or net object, return common networks between + this node and that object. A list of tuples is returned, with each tuple + consisting of (network, interface1, interface2). + + :param obj: object to get common network with + :param want_ctrl: flag set to determine if control network are wanted + :return: tuples of common networks + :rtype: list + """ + common = [] + for netif1 in self.netifs(): + if not want_ctrl and hasattr(netif1, "control"): + continue + for netif2 in obj.netifs(): + if netif1.net == netif2.net: + common.append((netif1.net, netif1, netif2)) + + return common + + def check_cmd(self, args): + """ + Runs shell command on node. + + :param list[str]|str args: command to run + :return: combined stdout and stderr + :rtype: str + :raises CoreCommandError: when a non-zero exit status occurs + """ + raise NotImplementedError + + def cmd(self, args, wait=True): + """ + Runs shell command on node, with option to not wait for a result. + + :param list[str]|str args: command to run + :param bool wait: wait for command to exit, defaults to True + :return: exit status for command + :rtype: int + """ + raise NotImplementedError + + def cmd_output(self, args): + """ + Runs shell command on node and get exit status and output. + + :param list[str]|str args: command to run + :return: exit status and combined stdout and stderr + :rtype: tuple[int, str] + """ + raise NotImplementedError + + def termcmdstring(self, sh): + """ + Create a terminal command string. + + :param str sh: shell to execute command in + :return: str + """ + raise NotImplementedError + + +class CoreNode(CoreNodeBase): + """ + Provides standard core node logic. + + :var nodedir: str + :var ctrlchnlname: str + :var client: core.netns.vnodeclient.VnodeClient + :var pid: int + :var up: bool + :var lock: threading.RLock + :var _mounts: list[tuple[str, str]] + """ + apitype = NodeTypes.DEFAULT.value + valid_address_types = {"inet", "inet6", "inet6link"} + + def __init__(self, session, _id=None, name=None, nodedir=None, bootsh="boot.sh", start=True): + """ + Create a CoreNode instance. + + :param core.session.Session session: core session instance + :param int _id: object id + :param str name: object name + :param str nodedir: node directory + :param str bootsh: boot shell to use + :param bool start: start flag + """ + super(CoreNode, self).__init__(session, _id, name, start) + self.nodedir = nodedir + self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name)) + self.client = None + self.pid = None + self.up = False + self.lock = threading.RLock() + self._mounts = [] + self.bootsh = bootsh + if start: + self.startup() + + def alive(self): + """ + Check if the node is alive. + + :return: True if node is alive, False otherwise + :rtype: bool + """ + try: + os.kill(self.pid, 0) + except OSError: + return False + + return True + + def startup(self): + """ + Start a new namespace node by invoking the vnoded process that + allocates a new namespace. Bring up the loopback device and set + the hostname. + + :return: nothing + """ + with self.lock: + self.makenodedir() + if self.up: + raise ValueError("starting a node that is already up") + + # create a new namespace for this node using vnoded + vnoded = [ + constants.VNODED_BIN, + "-v", + "-c", self.ctrlchnlname, + "-l", self.ctrlchnlname + ".log", + "-p", self.ctrlchnlname + ".pid" + ] + if self.nodedir: + vnoded += ["-C", self.nodedir] + env = self.session.get_environment(state=False) + env["NODE_NUMBER"] = str(self.id) + env["NODE_NAME"] = str(self.name) + + output = utils.check_cmd(vnoded, env=env) + self.pid = int(output) + + # create vnode client + self.client = client.VnodeClient(self.name, self.ctrlchnlname) + + # bring up the loopback interface + logging.debug("bringing up loopback interface") + self.check_cmd([constants.IP_BIN, "link", "set", "lo", "up"]) + + # set hostname for node + logging.debug("setting hostname: %s", self.name) + self.check_cmd(["hostname", self.name]) + + # mark node as up + self.up = True + + # create private directories + self.privatedir("/var/run") + self.privatedir("/var/log") + + def shutdown(self): + """ + Shutdown logic for simple lxc nodes. + + :return: nothing + """ + # nothing to do if node is not up + if not self.up: + return + + with self.lock: + try: + # unmount all targets (NOTE: non-persistent mount namespaces are + # removed by the kernel when last referencing process is killed) + self._mounts = [] + + # shutdown all interfaces + for netif in self.netifs(): + netif.shutdown() + + # attempt to kill node process and wait for termination of children + try: + os.kill(self.pid, signal.SIGTERM) + os.waitpid(self.pid, 0) + except OSError as e: + if e.errno != 10: + logging.exception("error killing process") + + # remove node directory if present + try: + os.unlink(self.ctrlchnlname) + except OSError as e: + # no such file or directory + if e.errno != errno.ENOENT: + logging.exception("error removing node directory") + + # clear interface data, close client, and mark self and not up + self._netif.clear() + self.client.close() + self.up = False + except OSError: + logging.exception("error during shutdown") + finally: + self.rmnodedir() + + + def cmd(self, args, wait=True): + """ + Runs shell command on node, with option to not wait for a result. + + :param list[str]|str args: command to run + :param bool wait: wait for command to exit, defaults to True + :return: exit status for command + :rtype: int + """ + return self.client.cmd(args, wait) + + def cmd_output(self, args): + """ + Runs shell command on node and get exit status and output. + + :param list[str]|str args: command to run + :return: exit status and combined stdout and stderr + :rtype: tuple[int, str] + """ + return self.client.cmd_output(args) + + def check_cmd(self, args): + """ + Runs shell command on node. + + :param list[str]|str args: command to run + :return: combined stdout and stderr + :rtype: str + :raises CoreCommandError: when a non-zero exit status occurs + """ + return self.client.check_cmd(args) + + def termcmdstring(self, sh="/bin/sh"): + """ + Create a terminal command string. + + :param str sh: shell to execute command in + :return: str + """ + return self.client.termcmdstring(sh) + + def privatedir(self, path): + """ + Create a private directory. + + :param str path: path to create + :return: nothing + """ + if path[0] != "/": + raise ValueError("path not fully qualified: %s" % path) + hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip("/").replace("/", ".")) + os.mkdir(hostpath) + self.mount(hostpath, path) + + def mount(self, source, target): + """ + Create and mount a directory. + + :param str source: source directory to mount + :param str target: target directory to create + :return: nothing + :raises CoreCommandError: when a non-zero exit status occurs + """ + source = os.path.abspath(source) + logging.info("node(%s) mounting: %s at %s", self.name, source, target) + cmd = 'mkdir -p "%s" && %s -n --bind "%s" "%s"' % (target, constants.MOUNT_BIN, source, target) + status, output = self.client.shcmd_result(cmd) + if status: + raise CoreCommandError(status, cmd, output) + self._mounts.append((source, target)) + + def newifindex(self): + """ + Retrieve a new interface index. + + :return: new interface index + :rtype: int + """ + with self.lock: + return super(CoreNode, self).newifindex() + + def newveth(self, ifindex=None, ifname=None, net=None): + """ + Create a new interface. + + :param int ifindex: index for the new interface + :param str ifname: name for the new interface + :param net: network to associate interface with + :return: nothing + """ + with self.lock: + if ifindex is None: + ifindex = self.newifindex() + + if ifname is None: + ifname = "eth%d" % ifindex + + sessionid = self.session.short_session_id() + + try: + suffix = "%x.%s.%s" % (self.id, ifindex, sessionid) + except TypeError: + suffix = "%s.%s.%s" % (self.id, ifindex, sessionid) + + localname = "veth" + suffix + if len(localname) >= 16: + raise ValueError("interface local name (%s) too long" % localname) + + name = localname + "p" + if len(name) >= 16: + raise ValueError("interface name (%s) too long" % name) + + veth = Veth(node=self, name=name, localname=localname, net=net, start=self.up) + + if self.up: + utils.check_cmd([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)]) + self.check_cmd([constants.IP_BIN, "link", "set", veth.name, "name", ifname]) + + veth.name = ifname + + if self.up: + # TODO: potentially find better way to query interface ID + # retrieve interface information + output = self.check_cmd(["ip", "link", "show", veth.name]) + logging.debug("interface command output: %s", output) + output = output.split("\n") + veth.flow_id = int(output[0].strip().split(":")[0]) + 1 + logging.debug("interface flow index: %s - %s", veth.name, veth.flow_id) + veth.hwaddr = MacAddress.from_string(output[1].strip().split()[1]) + logging.debug("interface mac: %s - %s", veth.name, veth.hwaddr) + + try: + self.addnetif(veth, ifindex) + except ValueError as e: + veth.shutdown() + del veth + raise e + + return ifindex + + def newtuntap(self, ifindex=None, ifname=None, net=None): + """ + Create a new tunnel tap. + + :param int ifindex: interface index + :param str ifname: interface name + :param net: network to associate with + :return: interface index + :rtype: int + """ + with self.lock: + if ifindex is None: + ifindex = self.newifindex() + + if ifname is None: + ifname = "eth%d" % ifindex + + sessionid = self.session.short_session_id() + localname = "tap%s.%s.%s" % (self.id, ifindex, sessionid) + name = ifname + tuntap = TunTap(node=self, name=name, localname=localname, net=net, start=self.up) + + try: + self.addnetif(tuntap, ifindex) + except ValueError as e: + tuntap.shutdown() + del tuntap + raise e + + return ifindex + + def sethwaddr(self, ifindex, addr): + """ + Set hardware addres for an interface. + + :param int ifindex: index of interface to set hardware address for + :param core.misc.ipaddress.MacAddress addr: hardware address to set + :return: nothing + :raises CoreCommandError: when a non-zero exit status occurs + """ + self._netif[ifindex].sethwaddr(addr) + if self.up: + args = [constants.IP_BIN, "link", "set", "dev", self.ifname(ifindex), "address", str(addr)] + self.check_cmd(args) + + def addaddr(self, ifindex, addr): + """ + Add interface address. + + :param int ifindex: index of interface to add address to + :param str addr: address to add to interface + :return: nothing + """ + if self.up: + # check if addr is ipv6 + if ":" in str(addr): + args = [constants.IP_BIN, "addr", "add", str(addr), "dev", self.ifname(ifindex)] + self.check_cmd(args) + else: + args = [constants.IP_BIN, "addr", "add", str(addr), "broadcast", "+", "dev", self.ifname(ifindex)] + self.check_cmd(args) + + self._netif[ifindex].addaddr(addr) + + def deladdr(self, ifindex, addr): + """ + Delete address from an interface. + + :param int ifindex: index of interface to delete address from + :param str addr: address to delete from interface + :return: nothing + :raises CoreCommandError: when a non-zero exit status occurs + """ + try: + self._netif[ifindex].deladdr(addr) + except ValueError: + logging.exception("trying to delete unknown address: %s" % addr) + + if self.up: + self.check_cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)]) + + def delalladdr(self, ifindex, address_types=None): + """ + Delete all addresses from an interface. + + :param int ifindex: index of interface to delete address types from + :param tuple[str] address_types: address types to delete + :return: nothing + :raises CoreCommandError: when a non-zero exit status occurs + """ + if not address_types: + address_types = self.valid_address_types + + interface_name = self.ifname(ifindex) + addresses = self.client.getaddr(interface_name, rescan=True) + + for address_type in address_types: + if address_type not in self.valid_address_types: + raise ValueError("addr type must be in: %s" % " ".join(self.valid_address_types)) + for address in addresses[address_type]: + self.deladdr(ifindex, address) + + # update cached information + self.client.getaddr(interface_name, rescan=True) + + def ifup(self, ifindex): + """ + Bring an interface up. + + :param int ifindex: index of interface to bring up + :return: nothing + """ + if self.up: + self.check_cmd([constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"]) + + def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): + """ + Create a new network interface. + + :param net: network to associate with + :param list addrlist: addresses to add on the interface + :param core.misc.ipaddress.MacAddress hwaddr: hardware address to set for interface + :param int ifindex: index of interface to create + :param str ifname: name for interface + :return: interface index + :rtype: int + """ + if not addrlist: + addrlist = [] + + with self.lock: + # TODO: see if you can move this to emane specific code + if nodeutils.is_node(net, NodeTypes.EMANE): + ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname, net=net) + # TUN/TAP is not ready for addressing yet; the device may + # take some time to appear, and installing it into a + # namespace after it has been bound removes addressing; + # save addresses with the interface now + self.attachnet(ifindex, net) + netif = self.netif(ifindex) + netif.sethwaddr(hwaddr) + for address in utils.make_tuple(addrlist): + netif.addaddr(address) + return ifindex + else: + ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net) + + if net is not None: + self.attachnet(ifindex, net) + + if hwaddr: + self.sethwaddr(ifindex, hwaddr) + + for address in utils.make_tuple(addrlist): + self.addaddr(ifindex, address) + + self.ifup(ifindex) + return ifindex + + def connectnode(self, ifname, othernode, otherifname): + """ + Connect a node. + + :param str ifname: name of interface to connect + :param core.netns.nodes.LxcNode othernode: node to connect to + :param str otherifname: interface name to connect to + :return: nothing + """ + tmplen = 8 + tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in xrange(tmplen)]) + tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in xrange(tmplen)]) + utils.check_cmd([constants.IP_BIN, "link", "add", "name", tmp1, "type", "veth", "peer", "name", tmp2]) + + utils.check_cmd([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)]) + self.check_cmd([constants.IP_BIN, "link", "set", tmp1, "name", ifname]) + interface = CoreInterface(node=self, name=ifname, mtu=_DEFAULT_MTU) + self.addnetif(interface, self.newifindex()) + + utils.check_cmd([constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)]) + othernode.check_cmd([constants.IP_BIN, "link", "set", tmp2, "name", otherifname]) + other_interface = CoreInterface(node=othernode, name=otherifname, mtu=_DEFAULT_MTU) + othernode.addnetif(other_interface, othernode.newifindex()) + + def addfile(self, srcname, filename): + """ + Add a file. + + :param str srcname: source file name + :param str filename: file name to add + :return: nothing + :raises CoreCommandError: when a non-zero exit status occurs + """ + logging.info("adding file from %s to %s", srcname, filename) + directory = os.path.dirname(filename) + + cmd = 'mkdir -p "%s" && mv "%s" "%s" && sync' % (directory, srcname, filename) + status, output = self.client.shcmd_result(cmd) + if status: + raise CoreCommandError(status, cmd, output) + + def hostfilename(self, filename): + """ + Return the name of a node"s file on the host filesystem. + + :param str filename: host file name + :return: path to file + """ + dirname, basename = os.path.split(filename) + if not basename: + raise ValueError("no basename for filename: %s" % filename) + if dirname and dirname[0] == "/": + dirname = dirname[1:] + dirname = dirname.replace("/", ".") + dirname = os.path.join(self.nodedir, dirname) + return os.path.join(dirname, basename) + + def opennodefile(self, filename, mode="w"): + """ + Open a node file, within it"s directory. + + :param str filename: file name to open + :param str mode: mode to open file in + :return: open file + :rtype: file + """ + hostfilename = self.hostfilename(filename) + dirname, _basename = os.path.split(hostfilename) + if not os.path.isdir(dirname): + os.makedirs(dirname, mode=0755) + return open(hostfilename, mode) + + def nodefile(self, filename, contents, mode=0644): + """ + Create a node file with a given mode. + + :param str filename: name of file to create + :param contents: contents of file + :param int mode: mode for file + :return: nothing + """ + with self.opennodefile(filename, "w") as open_file: + open_file.write(contents) + os.chmod(open_file.name, mode) + logging.info("node(%s) added file: %s; mode: 0%o", self.name, open_file.name, mode) + + def nodefilecopy(self, filename, srcfilename, mode=None): + """ + Copy a file to a node, following symlinks and preserving metadata. + Change file mode if specified. + + :param str filename: file name to copy file to + :param str srcfilename: file to copy + :param int mode: mode to copy to + :return: nothing + """ + hostfilename = self.hostfilename(filename) + shutil.copy2(srcfilename, hostfilename) + if mode is not None: + os.chmod(hostfilename, mode) + logging.info("node(%s) copied file: %s; mode: %s", self.name, hostfilename, mode) + + +class CoreNetworkBase(NodeBase): + """ + Base class for networks + """ + linktype = LinkTypes.WIRED.value + + def __init__(self, session, _id, name, start=True): + """ + Create a PyCoreNet instance. + + :param core.session.Session session: CORE session object + :param int _id: object id + :param str name: object name + :param bool start: should object start + """ + super(CoreNetworkBase, self).__init__(session, _id, name, start=start) + self._linked = {} + self._linked_lock = threading.Lock() + + def startup(self): + """ + Each object implements its own startup method. + + :return: nothing + """ + raise NotImplementedError + + def shutdown(self): + """ + Each object implements its own shutdown method. + + :return: nothing + """ + raise NotImplementedError + + def attach(self, netif): + """ + Attach network interface. + + :param core.nodes.interface.CoreInterface netif: network interface to attach + :return: nothing + """ + i = self.newifindex() + self._netif[i] = netif + netif.netifi = i + with self._linked_lock: + self._linked[netif] = {} + + def detach(self, netif): + """ + Detach network interface. + + :param core.nodes.interface.CoreInterface netif: network interface to detach + :return: nothing + """ + del self._netif[netif.netifi] + netif.netifi = None + with self._linked_lock: + del self._linked[netif] + + def all_link_data(self, flags): + """ + Build link data objects for this network. Each link object describes a link + between this network and a node. + """ + all_links = [] + + # build a link message from this network node to each node having a + # connected interface + for netif in self.netifs(sort=True): + if not hasattr(netif, "node"): + continue + linked_node = netif.node + uni = False + if linked_node is None: + # two layer-2 switches/hubs linked together via linknet() + if not hasattr(netif, "othernet"): + continue + linked_node = netif.othernet + if linked_node.id == self.id: + continue + netif.swapparams('_params_up') + upstream_params = netif.getparams() + netif.swapparams('_params_up') + if netif.getparams() != upstream_params: + uni = True + + unidirectional = 0 + if uni: + unidirectional = 1 + + interface2_ip4 = None + interface2_ip4_mask = None + interface2_ip6 = None + interface2_ip6_mask = None + for address in netif.addrlist: + ip, _sep, mask = address.partition("/") + mask = int(mask) + if ipaddress.is_ipv4_address(ip): + family = AF_INET + ipl = socket.inet_pton(family, ip) + interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) + interface2_ip4_mask = mask + else: + family = AF_INET6 + ipl = socket.inet_pton(family, ip) + interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) + interface2_ip6_mask = mask + + link_data = LinkData( + message_type=flags, + node1_id=self.id, + node2_id=linked_node.id, + link_type=self.linktype, + unidirectional=unidirectional, + interface2_id=linked_node.getifindex(netif), + interface2_mac=netif.hwaddr, + interface2_ip4=interface2_ip4, + interface2_ip4_mask=interface2_ip4_mask, + interface2_ip6=interface2_ip6, + interface2_ip6_mask=interface2_ip6_mask, + delay=netif.getparam("delay"), + bandwidth=netif.getparam("bw"), + dup=netif.getparam("duplicate"), + jitter=netif.getparam("jitter"), + per=netif.getparam("loss") + ) + + all_links.append(link_data) + + if not uni: + continue + + netif.swapparams('_params_up') + link_data = LinkData( + message_type=0, + node1_id=linked_node.id, + node2_id=self.id, + unidirectional=1, + delay=netif.getparam("delay"), + bandwidth=netif.getparam("bw"), + dup=netif.getparam("duplicate"), + jitter=netif.getparam("jitter"), + per=netif.getparam("loss") + ) + netif.swapparams('_params_up') + + all_links.append(link_data) + + return all_links + + +class Position(object): + """ + Helper class for Cartesian coordinate position + """ + + def __init__(self, x=None, y=None, z=None): + """ + Creates a Position instance. + + :param x: x position + :param y: y position + :param z: z position + :return: + """ + self.x = x + self.y = y + self.z = z + + def set(self, x=None, y=None, z=None): + """ + Returns True if the position has actually changed. + + :param float x: x position + :param float y: y position + :param float z: z position + :return: True if position changed, False otherwise + :rtype: bool + """ + if self.x == x and self.y == y and self.z == z: + return False + self.x = x + self.y = y + self.z = z + return True + + def get(self): + """ + Retrieve x,y,z position. + + :return: x,y,z position tuple + :rtype: tuple + """ + return self.x, self.y, self.z diff --git a/daemon/core/netns/vnodeclient.py b/daemon/core/nodes/client.py similarity index 98% rename from daemon/core/netns/vnodeclient.py rename to daemon/core/nodes/client.py index 11669a59..7ef7a005 100644 --- a/daemon/core/netns/vnodeclient.py +++ b/daemon/core/nodes/client.py @@ -1,5 +1,5 @@ """ -vnodeclient.py: implementation of the VnodeClient class for issuing commands +client.py: implementation of the VnodeClient class for issuing commands over a control channel to the vnoded process running in a network namespace. The control channel can be accessed via calls to the vcmd Python module or by invoking the vcmd shell command. @@ -10,9 +10,8 @@ import os import vcmd -from core import CoreCommandError +from core import CoreCommandError, utils from core import constants -from core.misc import utils class VnodeClient(object): diff --git a/daemon/core/netns/vif.py b/daemon/core/nodes/interface.py similarity index 69% rename from daemon/core/netns/vif.py rename to daemon/core/nodes/interface.py index b7e8f23d..8d5df152 100644 --- a/daemon/core/netns/vif.py +++ b/daemon/core/nodes/interface.py @@ -5,17 +5,175 @@ virtual ethernet classes that implement the interfaces available under Linux. import logging import time -from core import CoreCommandError +from core import CoreCommandError, utils from core import constants -from core.coreobj import PyCoreNetIf -from core.enumerations import NodeTypes -from core.misc import nodeutils -from core.misc import utils +from core.emulator.enumerations import NodeTypes +from core.nodes import nodeutils utils.check_executables([constants.IP_BIN]) -class VEth(PyCoreNetIf): +class CoreInterface(object): + """ + Base class for network interfaces. + """ + + def __init__(self, node, name, mtu): + """ + Creates a PyCoreNetIf instance. + + :param core.coreobj.PyCoreNode node: node for interface + :param str name: interface name + :param mtu: mtu value + """ + + self.node = node + self.name = name + if not isinstance(mtu, (int, long)): + raise ValueError + self.mtu = mtu + self.net = None + self._params = {} + self.addrlist = [] + self.hwaddr = None + # placeholder position hook + self.poshook = lambda a, b, c, d: None + # used with EMANE + self.transport_type = None + # interface index on the network + self.netindex = None + # index used to find flow data + self.flow_id = None + + def startup(self): + """ + Startup method for the interface. + + :return: nothing + """ + pass + + def shutdown(self): + """ + Shutdown method for the interface. + + :return: nothing + """ + pass + + def attachnet(self, net): + """ + Attach network. + + :param core.coreobj.PyCoreNet net: network to attach + :return: nothing + """ + if self.net: + self.detachnet() + self.net = None + + net.attach(self) + self.net = net + + def detachnet(self): + """ + Detach from a network. + + :return: nothing + """ + if self.net is not None: + self.net.detach(self) + + def addaddr(self, addr): + """ + Add address. + + :param str addr: address to add + :return: nothing + """ + + self.addrlist.append(addr) + + def deladdr(self, addr): + """ + Delete address. + + :param str addr: address to delete + :return: nothing + """ + self.addrlist.remove(addr) + + def sethwaddr(self, addr): + """ + Set hardware address. + + :param core.misc.ipaddress.MacAddress addr: hardware address to set to. + :return: nothing + """ + self.hwaddr = addr + + def getparam(self, key): + """ + Retrieve a parameter from the, or None if the parameter does not exist. + + :param key: parameter to get value for + :return: parameter value + """ + return self._params.get(key) + + def getparams(self): + """ + Return (key, value) pairs for parameters. + """ + parameters = [] + for k in sorted(self._params.keys()): + parameters.append((k, self._params[k])) + return parameters + + def setparam(self, key, value): + """ + Set a parameter value, returns True if the parameter has changed. + + :param key: parameter name to set + :param value: parameter value + :return: True if parameter changed, False otherwise + """ + # treat None and 0 as unchanged values + current_value = self._params.get(key) + if current_value == value or current_value <= 0 and value <= 0: + return False + + self._params[key] = value + return True + + def swapparams(self, name): + """ + Swap out parameters dict for name. If name does not exist, + intialize it. This is for supporting separate upstream/downstream + parameters when two layer-2 nodes are linked together. + + :param str name: name of parameter to swap + :return: nothing + """ + tmp = self._params + if not hasattr(self, name): + setattr(self, name, {}) + self._params = getattr(self, name) + setattr(self, name, tmp) + + def setposition(self, x, y, z): + """ + Dispatch position hook handler. + + :param x: x position + :param y: y position + :param z: z position + :return: nothing + """ + self.poshook(self, x, y, z) + + +class Veth(CoreInterface): """ Provides virtual ethernet functionality for core nodes. """ @@ -34,7 +192,7 @@ class VEth(PyCoreNetIf): :raises CoreCommandError: when there is a command exception """ # note that net arg is ignored - PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu) + CoreInterface.__init__(self, node=node, name=name, mtu=mtu) self.localname = localname self.up = False if start: @@ -76,7 +234,7 @@ class VEth(PyCoreNetIf): self.up = False -class TunTap(PyCoreNetIf): +class TunTap(CoreInterface): """ TUN/TAP virtual device in TAP mode """ @@ -93,7 +251,7 @@ class TunTap(PyCoreNetIf): :param net: related network :param bool start: start flag """ - PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu) + CoreInterface.__init__(self, node=node, name=name, mtu=mtu) self.localname = localname self.up = False self.transport_type = "virtual" @@ -233,7 +391,7 @@ class TunTap(PyCoreNetIf): self.node.check_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name]) -class GreTap(PyCoreNetIf): +class GreTap(CoreInterface): """ GRE TAP device for tunneling between emulation servers. Uses the "gretap" tunnel device type from Linux which is a GRE device @@ -258,7 +416,7 @@ class GreTap(PyCoreNetIf): :param bool start: start flag :raises CoreCommandError: when there is a command exception """ - PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu) + CoreInterface.__init__(self, node=node, name=name, mtu=mtu) self.session = session if _id is None: # from PyCoreObj diff --git a/daemon/core/misc/ipaddress.py b/daemon/core/nodes/ipaddress.py similarity index 100% rename from daemon/core/misc/ipaddress.py rename to daemon/core/nodes/ipaddress.py diff --git a/daemon/core/netns/vnet.py b/daemon/core/nodes/network.py similarity index 58% rename from daemon/core/netns/vnet.py rename to daemon/core/nodes/network.py index a7be41b5..b76a035a 100644 --- a/daemon/core/netns/vnet.py +++ b/daemon/core/nodes/network.py @@ -5,15 +5,19 @@ Linux Ethernet bridging and ebtables rules. import logging import os +import socket import threading import time +from socket import AF_INET, AF_INET6 -from core import CoreCommandError +from core import CoreCommandError, utils from core import constants -from core.coreobj import PyCoreNet -from core.misc import utils -from core.netns.vif import GreTap -from core.netns.vif import VEth +from core.nodes.base import CoreNetworkBase +from core.emulator.data import LinkData +from core.emulator.enumerations import NodeTypes, LinkTypes, RegisterTlvs +from core.nodes import ipaddress +from core.nodes.interface import GreTap +from core.nodes.interface import Veth utils.check_executables([ constants.BRCTL_BIN, @@ -236,9 +240,9 @@ def ebtablescmds(call, cmds): call(args) -class LxBrNet(PyCoreNet): +class CoreNetwork(CoreNetworkBase): """ - Provides linux bridge network functionlity for core nodes. + Provides linux bridge network functionality for core nodes. """ policy = "DROP" @@ -252,7 +256,7 @@ class LxBrNet(PyCoreNet): :param bool start: start flag :param policy: network policy """ - PyCoreNet.__init__(self, session, _id, name, start) + CoreNetworkBase.__init__(self, session, _id, name, start) if name is None: name = str(self.id) if policy is not None: @@ -333,7 +337,7 @@ class LxBrNet(PyCoreNet): utils.check_cmd([constants.BRCTL_BIN, "addif", self.brname, netif.localname]) utils.check_cmd([constants.IP_BIN, "link", "set", netif.localname, "up"]) - PyCoreNet.attach(self, netif) + CoreNetworkBase.attach(self, netif) def detach(self, netif): """ @@ -345,7 +349,7 @@ class LxBrNet(PyCoreNet): if self.up: utils.check_cmd([constants.BRCTL_BIN, "delif", self.brname, netif.localname]) - PyCoreNet.detach(self, netif) + CoreNetworkBase.detach(self, netif) def linked(self, netif1, netif2): """ @@ -520,7 +524,7 @@ class LxBrNet(PyCoreNet): if len(name) >= 16: raise ValueError("interface name %s too long" % name) - netif = VEth(node=None, name=name, localname=localname, mtu=1500, net=self, start=self.up) + netif = Veth(node=None, name=name, localname=localname, mtu=1500, net=self, start=self.up) self.attach(netif) if net.up: # this is similar to net.attach() but uses netif.name instead @@ -564,7 +568,7 @@ class LxBrNet(PyCoreNet): utils.check_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.brname]) -class GreTapBridge(LxBrNet): +class GreTapBridge(CoreNetwork): """ A network consisting of a bridge with a gretap device for tunneling to another system. @@ -586,7 +590,7 @@ class GreTapBridge(LxBrNet): :param bool start: start flag :return: """ - LxBrNet.__init__(self, session=session, _id=_id, name=name, policy=policy, start=False) + CoreNetwork.__init__(self, session=session, _id=_id, name=name, policy=policy, start=False) self.grekey = key if self.grekey is None: self.grekey = self.session.id ^ self.id @@ -609,7 +613,7 @@ class GreTapBridge(LxBrNet): :return: nothing """ - LxBrNet.startup(self) + CoreNetwork.startup(self) if self.gretap: self.attach(self.gretap) @@ -623,7 +627,7 @@ class GreTapBridge(LxBrNet): self.detach(self.gretap) self.gretap.shutdown() self.gretap = None - LxBrNet.shutdown(self) + CoreNetwork.shutdown(self) def addrconfig(self, addrlist): """ @@ -654,3 +658,412 @@ class GreTapBridge(LxBrNet): :return: nothing """ self.grekey = key + + +class CtrlNet(CoreNetwork): + """ + Control network functionality. + """ + policy = "ACCEPT" + # base control interface index + CTRLIF_IDX_BASE = 99 + DEFAULT_PREFIX_LIST = [ + "172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24", + "172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24", + "172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24", + "172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24" + ] + + def __init__(self, session, _id="ctrlnet", name=None, prefix=None, + hostid=None, start=True, assign_address=True, + updown_script=None, serverintf=None): + """ + Creates a CtrlNet instance. + + :param core.session.Session session: core session instance + :param int _id: node id + :param str name: node namee + :param prefix: control network ipv4 prefix + :param hostid: host id + :param bool start: start flag + :param str assign_address: assigned address + :param str updown_script: updown script + :param serverintf: server interface + :return: + """ + self.prefix = ipaddress.Ipv4Prefix(prefix) + self.hostid = hostid + self.assign_address = assign_address + self.updown_script = updown_script + self.serverintf = serverintf + CoreNetwork.__init__(self, session, _id=_id, name=name, start=start) + + def startup(self): + """ + Startup functionality for the control network. + + :return: nothing + :raises CoreCommandError: when there is a command exception + """ + if self.detectoldbridge(): + return + + CoreNetwork.startup(self) + + if self.hostid: + addr = self.prefix.addr(self.hostid) + else: + addr = self.prefix.max_addr() + + logging.info("added control network bridge: %s %s", self.brname, self.prefix) + + if self.assign_address: + addrlist = ["%s/%s" % (addr, self.prefix.prefixlen)] + self.addrconfig(addrlist=addrlist) + logging.info("address %s", addr) + + if self.updown_script: + logging.info("interface %s updown script (%s startup) called", self.brname, self.updown_script) + utils.check_cmd([self.updown_script, self.brname, "startup"]) + + if self.serverintf: + # sets the interface as a port of the bridge + utils.check_cmd([constants.BRCTL_BIN, "addif", self.brname, self.serverintf]) + + # bring interface up + utils.check_cmd([constants.IP_BIN, "link", "set", self.serverintf, "up"]) + + def detectoldbridge(self): + """ + Occassionally, control net bridges from previously closed sessions are not cleaned up. + Check if there are old control net bridges and delete them + + :return: True if an old bridge was detected, False otherwise + :rtype: bool + """ + status, output = utils.cmd_output([constants.BRCTL_BIN, "show"]) + if status != 0: + logging.error("Unable to retrieve list of installed bridges") + else: + lines = output.split("\n") + for line in lines[1:]: + cols = line.split("\t") + oldbr = cols[0] + flds = cols[0].split(".") + if len(flds) == 3: + if flds[0] == "b" and flds[1] == self.id: + logging.error( + "error: An active control net bridge (%s) found. " + "An older session might still be running. " + "Stop all sessions and, if needed, delete %s to continue.", oldbr, oldbr + ) + return True + return False + + def shutdown(self): + """ + Control network shutdown. + + :return: nothing + """ + if self.serverintf is not None: + try: + utils.check_cmd([constants.BRCTL_BIN, "delif", self.brname, self.serverintf]) + except CoreCommandError: + logging.exception("error deleting server interface %s from bridge %s", self.serverintf, self.brname) + + if self.updown_script is not None: + try: + logging.info("interface %s updown script (%s shutdown) called", self.brname, self.updown_script) + utils.check_cmd([self.updown_script, self.brname, "shutdown"]) + except CoreCommandError: + logging.exception("error issuing shutdown script shutdown") + + CoreNetwork.shutdown(self) + + def all_link_data(self, flags): + """ + Do not include CtrlNet in link messages describing this session. + + :param flags: message flags + :return: list of link data + :rtype: list[core.data.LinkData] + """ + return [] + + +class PtpNet(CoreNetwork): + """ + Peer to peer network node. + """ + policy = "ACCEPT" + + def attach(self, netif): + """ + Attach a network interface, but limit attachment to two interfaces. + + :param core.netns.vif.VEth netif: network interface + :return: nothing + """ + if len(self._netif) >= 2: + raise ValueError("Point-to-point links support at most 2 network interfaces") + + CoreNetwork.attach(self, netif) + + def data(self, message_type, lat=None, lon=None, alt=None): + """ + Do not generate a Node Message for point-to-point links. They are + built using a link message instead. + + :param message_type: purpose for the data object we are creating + :param float lat: latitude + :param float lon: longitude + :param float alt: altitude + :return: node data object + :rtype: core.data.NodeData + """ + return None + + def all_link_data(self, flags): + """ + Build CORE API TLVs for a point-to-point link. One Link message + describes this network. + + :param flags: message flags + :return: list of link data + :rtype: list[core.data.LinkData] + """ + + all_links = [] + + if len(self._netif) != 2: + return all_links + + if1, if2 = self._netif.values() + + unidirectional = 0 + if if1.getparams() != if2.getparams(): + unidirectional = 1 + + interface1_ip4 = None + interface1_ip4_mask = None + interface1_ip6 = None + interface1_ip6_mask = None + for address in if1.addrlist: + ip, _sep, mask = address.partition("/") + mask = int(mask) + if ipaddress.is_ipv4_address(ip): + family = AF_INET + ipl = socket.inet_pton(family, ip) + interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl) + interface1_ip4_mask = mask + else: + family = AF_INET6 + ipl = socket.inet_pton(family, ip) + interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl) + interface1_ip6_mask = mask + + interface2_ip4 = None + interface2_ip4_mask = None + interface2_ip6 = None + interface2_ip6_mask = None + for address in if2.addrlist: + ip, _sep, mask = address.partition("/") + mask = int(mask) + if ipaddress.is_ipv4_address(ip): + family = AF_INET + ipl = socket.inet_pton(family, ip) + interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) + interface2_ip4_mask = mask + else: + family = AF_INET6 + ipl = socket.inet_pton(family, ip) + interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) + interface2_ip6_mask = mask + + link_data = LinkData( + message_type=flags, + node1_id=if1.node.id, + node2_id=if2.node.id, + link_type=self.linktype, + unidirectional=unidirectional, + delay=if1.getparam("delay"), + bandwidth=if1.getparam("bw"), + dup=if1.getparam("duplicate"), + jitter=if1.getparam("jitter"), + interface1_id=if1.node.getifindex(if1), + interface1_mac=if1.hwaddr, + interface1_ip4=interface1_ip4, + interface1_ip4_mask=interface1_ip4_mask, + interface1_ip6=interface1_ip6, + interface1_ip6_mask=interface1_ip6_mask, + interface2_id=if2.node.getifindex(if2), + interface2_mac=if2.hwaddr, + interface2_ip4=interface2_ip4, + interface2_ip4_mask=interface2_ip4_mask, + interface2_ip6=interface2_ip6, + interface2_ip6_mask=interface2_ip6_mask, + ) + + all_links.append(link_data) + + # build a 2nd link message for the upstream link parameters + # (swap if1 and if2) + if unidirectional: + link_data = LinkData( + message_type=0, + node1_id=if2.node.id, + node2_id=if1.node.id, + delay=if1.getparam("delay"), + bandwidth=if1.getparam("bw"), + dup=if1.getparam("duplicate"), + jitter=if1.getparam("jitter"), + unidirectional=1, + interface1_id=if2.node.getifindex(if2), + interface2_id=if1.node.getifindex(if1) + ) + all_links.append(link_data) + + return all_links + + +class SwitchNode(CoreNetwork): + """ + Provides switch functionality within a core node. + """ + apitype = NodeTypes.SWITCH.value + policy = "ACCEPT" + type = "lanswitch" + + +class HubNode(CoreNetwork): + """ + Provides hub functionality within a core node, forwards packets to all bridge + ports by turning off MAC address learning. + """ + apitype = NodeTypes.HUB.value + policy = "ACCEPT" + type = "hub" + + def __init__(self, session, _id=None, name=None, start=True): + """ + Creates a HubNode instance. + + :param core.session.Session session: core session instance + :param int _id: node id + :param str name: node namee + :param bool start: start flag + :raises CoreCommandError: when there is a command exception + """ + CoreNetwork.__init__(self, session, _id, name, start) + + # TODO: move to startup method + if start: + utils.check_cmd([constants.BRCTL_BIN, "setageing", self.brname, "0"]) + + +class WlanNode(CoreNetwork): + """ + Provides wireless lan functionality within a core node. + """ + apitype = NodeTypes.WIRELESS_LAN.value + linktype = LinkTypes.WIRELESS.value + policy = "DROP" + type = "wlan" + + def __init__(self, session, _id=None, name=None, start=True, policy=None): + """ + Create a WlanNode instance. + + :param core.session.Session session: core session instance + :param int _id: node id + :param str name: node name + :param bool start: start flag + :param policy: wlan policy + """ + CoreNetwork.__init__(self, session, _id, name, start, policy) + # wireless model such as basic range + self.model = None + # mobility model such as scripted + self.mobility = None + + def attach(self, netif): + """ + Attach a network interface. + + :param core.netns.vif.VEth netif: network interface + :return: nothing + """ + CoreNetwork.attach(self, netif) + if self.model: + netif.poshook = self.model.position_callback + if netif.node is None: + return + x, y, z = netif.node.position.get() + # invokes any netif.poshook + netif.setposition(x, y, z) + + def setmodel(self, model, config): + """ + Sets the mobility and wireless model. + + :param core.mobility.WirelessModel.cls model: wireless model to set to + :param dict config: configuration for model being set + :return: nothing + """ + logging.info("adding model: %s", model.name) + if model.config_type == RegisterTlvs.WIRELESS.value: + self.model = model(session=self.session, _id=self.id) + self.model.update_config(config) + if self.model.position_callback: + for netif in self.netifs(): + netif.poshook = self.model.position_callback + if netif.node is not None: + x, y, z = netif.node.position.get() + netif.poshook(netif, x, y, z) + self.model.setlinkparams() + elif model.config_type == RegisterTlvs.MOBILITY.value: + self.mobility = model(session=self.session, _id=self.id) + self.mobility.update_config(config) + + def update_mobility(self, config): + if not self.mobility: + raise ValueError("no mobility set to update for node(%s)", self.id) + self.mobility.set_configs(config, node_id=self.id) + + def updatemodel(self, config): + if not self.model: + raise ValueError("no model set to update for node(%s)", self.id) + logging.info("node(%s) updating model(%s): %s", self.id, self.model.name, config) + self.model.set_configs(config, node_id=self.id) + if self.model.position_callback: + for netif in self.netifs(): + netif.poshook = self.model.position_callback + if netif.node is not None: + x, y, z = netif.node.position.get() + netif.poshook(netif, x, y, z) + self.model.updateconfig() + + def all_link_data(self, flags): + """ + Retrieve all link data. + + :param flags: message flags + :return: list of link data + :rtype: list[core.data.LinkData] + """ + all_links = CoreNetwork.all_link_data(self, flags) + + if self.model: + all_links.extend(self.model.all_link_data(flags)) + + return all_links + + +class TunnelNode(GreTapBridge): + """ + Provides tunnel functionality in a core node. + """ + apitype = NodeTypes.TUNNEL.value + policy = "ACCEPT" + type = "tunnel" diff --git a/daemon/core/nodes/nodemaps.py b/daemon/core/nodes/nodemaps.py new file mode 100644 index 00000000..525ab43d --- /dev/null +++ b/daemon/core/nodes/nodemaps.py @@ -0,0 +1,29 @@ +""" +Provides default node maps that can be used to run core with. +""" +import core.nodes.base +import core.nodes.network +import core.nodes.physical +from core.emane.nodes import EmaneNet +from core.emane.nodes import EmaneNode +from core.emulator.enumerations import NodeTypes +from core.nodes.network import GreTapBridge +from core.nodes import physical + +# legacy core nodes, that leverage linux bridges +NODES = { + NodeTypes.DEFAULT: core.nodes.base.CoreNode, + NodeTypes.PHYSICAL: physical.PhysicalNode, + NodeTypes.TBD: None, + NodeTypes.SWITCH: core.nodes.network.SwitchNode, + NodeTypes.HUB: core.nodes.network.HubNode, + NodeTypes.WIRELESS_LAN: core.nodes.network.WlanNode, + NodeTypes.RJ45: core.nodes.physical.Rj45Node, + NodeTypes.TUNNEL: core.nodes.network.TunnelNode, + NodeTypes.KTUNNEL: None, + NodeTypes.EMANE: EmaneNode, + NodeTypes.EMANE_NET: EmaneNet, + NodeTypes.TAP_BRIDGE: GreTapBridge, + NodeTypes.PEER_TO_PEER: core.nodes.network.PtpNet, + NodeTypes.CONTROL_NET: core.nodes.network.CtrlNet +} diff --git a/daemon/core/misc/nodeutils.py b/daemon/core/nodes/nodeutils.py similarity index 100% rename from daemon/core/misc/nodeutils.py rename to daemon/core/nodes/nodeutils.py diff --git a/daemon/core/netns/openvswitch.py b/daemon/core/nodes/openvswitch.py similarity index 96% rename from daemon/core/netns/openvswitch.py rename to daemon/core/nodes/openvswitch.py index c79c2835..facff596 100644 --- a/daemon/core/netns/openvswitch.py +++ b/daemon/core/nodes/openvswitch.py @@ -8,19 +8,18 @@ import threading from socket import AF_INET from socket import AF_INET6 -from core import CoreCommandError +from core import CoreCommandError, utils from core import constants -from core.coreobj import PyCoreNet -from core.data import LinkData -from core.enumerations import LinkTypes -from core.enumerations import NodeTypes -from core.enumerations import RegisterTlvs -from core.misc import ipaddress -from core.misc import utils -from core.netns.vif import GreTap -from core.netns.vif import VEth -from core.netns.vnet import EbtablesQueue -from core.netns.vnet import GreTapBridge +from core.nodes.base import CoreNetworkBase +from core.emulator.data import LinkData +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import NodeTypes +from core.emulator.enumerations import RegisterTlvs +from core.nodes import ipaddress +from core.nodes.interface import GreTap +from core.nodes.interface import Veth +from core.nodes.network import EbtablesQueue +from core.nodes.network import GreTapBridge # a global object because all WLANs share the same queue # cannot have multiple threads invoking the ebtables commnd @@ -41,7 +40,7 @@ def ebtables_commands(call, commands): call(command) -class OvsNet(PyCoreNet): +class OvsNet(CoreNetworkBase): """ Used to be LxBrNet. @@ -62,7 +61,7 @@ class OvsNet(PyCoreNet): :return: """ - PyCoreNet.__init__(self, session, _id, name, start) + CoreNetworkBase.__init__(self, session, _id, name, start) if policy: self.policy = policy @@ -129,13 +128,13 @@ class OvsNet(PyCoreNet): utils.check_cmd([constants.OVS_BIN, "add-port", self.bridge_name, interface.localname]) utils.check_cmd([constants.IP_BIN, "link", "set", interface.localname, "up"]) - PyCoreNet.attach(self, interface) + CoreNetworkBase.attach(self, interface) def detach(self, interface): if self.up: utils.check_cmd([constants.OVS_BIN, "del-port", self.bridge_name, interface.localname]) - PyCoreNet.detach(self, interface) + CoreNetworkBase.detach(self, interface) def linked(self, interface_one, interface_two): # check if the network interfaces are attached to this network @@ -297,7 +296,7 @@ class OvsNet(PyCoreNet): if len(name) >= 16: raise ValueError("interface name %s too long" % name) - interface = VEth(node=None, name=name, localname=localname, mtu=1500, net=self, start=self.up) + interface = Veth(node=None, name=name, localname=localname, mtu=1500, net=self, start=self.up) self.attach(interface) if network.up: # this is similar to net.attach() but uses netif.name instead @@ -598,7 +597,7 @@ class OvsWlanNode(OvsNet): logging.info("adding model %s", model.name) if model.type == RegisterTlvs.WIRELESS.value: - self.model = model(session=self.session, object_id=self.id, config=config) + self.model = model(session=self.session, _id=self.id, config=config) if self.model.position_callback: for interface in self.netifs(): interface.poshook = self.model.position_callback @@ -607,7 +606,7 @@ class OvsWlanNode(OvsNet): interface.poshook(interface, x, y, z) self.model.setlinkparams() elif model.type == RegisterTlvs.MOBILITY.value: - self.mobility = model(session=self.session, object_id=self.id, config=config) + self.mobility = model(session=self.session, _id=self.id, config=config) def updatemodel(self, config): if not self.model: diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py new file mode 100644 index 00000000..1e9d3a89 --- /dev/null +++ b/daemon/core/nodes/physical.py @@ -0,0 +1,544 @@ +""" +PhysicalNode class for including real systems in the emulated network. +""" + +import logging +import os +import subprocess +import threading + +from core import CoreCommandError, utils +from core import constants +from core.nodes.base import CoreNodeBase +from core.nodes.interface import CoreInterface +from core.emulator.enumerations import NodeTypes +from core.nodes.network import GreTap +from core.nodes.network import CoreNetwork + + +class PhysicalNode(CoreNodeBase): + def __init__(self, session, _id=None, name=None, nodedir=None, start=True): + CoreNodeBase.__init__(self, session, _id, name, start=start) + self.nodedir = nodedir + self.up = start + self.lock = threading.RLock() + self._mounts = [] + if start: + self.startup() + + def startup(self): + with self.lock: + self.makenodedir() + + def shutdown(self): + if not self.up: + return + + with self.lock: + while self._mounts: + _source, target = self._mounts.pop(-1) + self.umount(target) + + for netif in self.netifs(): + netif.shutdown() + + self.rmnodedir() + + def termcmdstring(self, sh="/bin/sh"): + """ + Create a terminal command string. + + :param str sh: shell to execute command in + :return: str + """ + return sh + + def cmd(self, args, wait=True): + """ + Runs shell command on node, with option to not wait for a result. + + :param list[str]|str args: command to run + :param bool wait: wait for command to exit, defaults to True + :return: exit status for command + :rtype: int + """ + os.chdir(self.nodedir) + status = utils.cmd(args, wait) + return status + + def cmd_output(self, args): + """ + Runs shell command on node and get exit status and output. + + :param list[str]|str args: command to run + :return: exit status and combined stdout and stderr + :rtype: tuple[int, str] + """ + os.chdir(self.nodedir) + p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + stdout, _ = p.communicate() + status = p.wait() + return status, stdout.strip() + + def check_cmd(self, args): + """ + Runs shell command on node. + + :param list[str]|str args: command to run + :return: combined stdout and stderr + :rtype: str + :raises CoreCommandError: when a non-zero exit status occurs + """ + status, output = self.cmd_output(args) + if status: + raise CoreCommandError(status, args, output) + return output.strip() + + def shcmd(self, cmdstr, sh="/bin/sh"): + return self.cmd([sh, "-c", cmdstr]) + + def sethwaddr(self, ifindex, addr): + """ + same as SimpleLxcNode.sethwaddr() + """ + self._netif[ifindex].sethwaddr(addr) + ifname = self.ifname(ifindex) + if self.up: + self.check_cmd([constants.IP_BIN, "link", "set", "dev", ifname, "address", str(addr)]) + + def addaddr(self, ifindex, addr): + """ + same as SimpleLxcNode.addaddr() + """ + if self.up: + self.check_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.ifname(ifindex)]) + + self._netif[ifindex].addaddr(addr) + + def deladdr(self, ifindex, addr): + """ + same as SimpleLxcNode.deladdr() + """ + try: + self._netif[ifindex].deladdr(addr) + except ValueError: + logging.exception("trying to delete unknown address: %s", addr) + + if self.up: + self.check_cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)]) + + def adoptnetif(self, netif, ifindex, hwaddr, addrlist): + """ + The broker builds a GreTap tunnel device to this physical node. + When a link message is received linking this node to another part of + the emulation, no new interface is created; instead, adopt the + GreTap netif as the node interface. + """ + netif.name = "gt%d" % ifindex + netif.node = self + self.addnetif(netif, ifindex) + + # use a more reasonable name, e.g. "gt0" instead of "gt.56286.150" + if self.up: + self.check_cmd([constants.IP_BIN, "link", "set", "dev", netif.localname, "down"]) + self.check_cmd([constants.IP_BIN, "link", "set", netif.localname, "name", netif.name]) + + netif.localname = netif.name + + if hwaddr: + self.sethwaddr(ifindex, hwaddr) + + for addr in utils.make_tuple(addrlist): + self.addaddr(ifindex, addr) + + if self.up: + self.check_cmd([constants.IP_BIN, "link", "set", "dev", netif.localname, "up"]) + + def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None, jitter=None, netif2=None): + """ + Apply tc queing disciplines using LxBrNet.linkconfig() + """ + # borrow the tc qdisc commands from LxBrNet.linkconfig() + linux_bridge = CoreNetwork(session=self.session, start=False) + linux_bridge.up = True + linux_bridge.linkconfig(netif, bw=bw, delay=delay, loss=loss, duplicate=duplicate, jitter=jitter, netif2=netif2) + del linux_bridge + + def newifindex(self): + with self.lock: + while self.ifindex in self._netif: + self.ifindex += 1 + ifindex = self.ifindex + self.ifindex += 1 + return ifindex + + def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): + logging.info("creating interface") + if not addrlist: + addrlist = [] + + if self.up and net is None: + raise NotImplementedError + + if ifindex is None: + ifindex = self.newifindex() + + if self.up: + # this is reached when this node is linked to a network node + # tunnel to net not built yet, so build it now and adopt it + gt = self.session.broker.addnettunnel(net.id) + if gt is None or len(gt) != 1: + raise ValueError("error building tunnel from adding a new network interface: %s" % gt) + gt = gt[0] + net.detach(gt) + self.adoptnetif(gt, ifindex, hwaddr, addrlist) + return ifindex + + # this is reached when configuring services (self.up=False) + if ifname is None: + ifname = "gt%d" % ifindex + + netif = GreTap(node=self, name=ifname, session=self.session, start=False) + self.adoptnetif(netif, ifindex, hwaddr, addrlist) + return ifindex + + def privatedir(self, path): + if path[0] != "/": + raise ValueError("path not fully qualified: %s" % path) + hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.')) + os.mkdir(hostpath) + self.mount(hostpath, path) + + def mount(self, source, target): + source = os.path.abspath(source) + logging.info("mounting %s at %s", source, target) + os.makedirs(target) + self.check_cmd([constants.MOUNT_BIN, "--bind", source, target]) + self._mounts.append((source, target)) + + def umount(self, target): + logging.info("unmounting '%s'" % target) + try: + self.check_cmd([constants.UMOUNT_BIN, "-l", target]) + except CoreCommandError: + logging.exception("unmounting failed for %s", target) + + def opennodefile(self, filename, mode="w"): + dirname, basename = os.path.split(filename) + if not basename: + raise ValueError("no basename for filename: " + filename) + + if dirname and dirname[0] == "/": + dirname = dirname[1:] + + dirname = dirname.replace("/", ".") + dirname = os.path.join(self.nodedir, dirname) + if not os.path.isdir(dirname): + os.makedirs(dirname, mode=0755) + + hostfilename = os.path.join(dirname, basename) + return open(hostfilename, mode) + + def nodefile(self, filename, contents, mode=0644): + with self.opennodefile(filename, "w") as node_file: + node_file.write(contents) + os.chmod(node_file.name, mode) + logging.info("created nodefile: '%s'; mode: 0%o", node_file.name, mode) + + +class Rj45Node(CoreNodeBase, CoreInterface): + """ + RJ45Node is a physical interface on the host linked to the emulated + network. + """ + apitype = NodeTypes.RJ45.value + type = "rj45" + + def __init__(self, session, _id=None, name=None, mtu=1500, start=True): + """ + Create an RJ45Node instance. + + :param core.session.Session session: core session instance + :param int _id: node id + :param str name: node name + :param mtu: rj45 mtu + :param bool start: start flag + :return: + """ + CoreNodeBase.__init__(self, session, _id, name, start=start) + CoreInterface.__init__(self, node=self, name=name, mtu=mtu) + self.up = False + self.lock = threading.RLock() + self.ifindex = None + # the following are PyCoreNetIf attributes + self.transport_type = "raw" + self.localname = name + self.old_up = False + self.old_addrs = [] + + if start: + self.startup() + + def startup(self): + """ + Set the interface in the up state. + + :return: nothing + :raises CoreCommandError: when there is a command exception + """ + # interface will also be marked up during net.attach() + self.savestate() + utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "up"]) + self.up = True + + def shutdown(self): + """ + Bring the interface down. Remove any addresses and queuing + disciplines. + + :return: nothing + """ + if not self.up: + return + + try: + utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "down"]) + utils.check_cmd([constants.IP_BIN, "addr", "flush", "dev", self.localname]) + utils.check_cmd([constants.TC_BIN, "qdisc", "del", "dev", self.localname, "root"]) + except CoreCommandError: + logging.exception("error shutting down") + + self.up = False + self.restorestate() + + # TODO: issue in that both classes inherited from provide the same method with different signatures + def attachnet(self, net): + """ + Attach a network. + + :param core.coreobj.PyCoreNet net: network to attach + :return: nothing + """ + CoreInterface.attachnet(self, net) + + # TODO: issue in that both classes inherited from provide the same method with different signatures + def detachnet(self): + """ + Detach a network. + + :return: nothing + """ + CoreInterface.detachnet(self) + + def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): + """ + This is called when linking with another node. Since this node + represents an interface, we do not create another object here, + but attach ourselves to the given network. + + :param core.coreobj.PyCoreNet net: new network instance + :param list[str] addrlist: address list + :param str hwaddr: hardware address + :param int ifindex: interface index + :param str ifname: interface name + :return: interface index + :rtype: int + :raises ValueError: when an interface has already been created, one max + """ + with self.lock: + if ifindex is None: + ifindex = 0 + + if self.net is not None: + raise ValueError("RJ45 nodes support at most 1 network interface") + + self._netif[ifindex] = self + # PyCoreNetIf.node is self + self.node = self + self.ifindex = ifindex + + if net is not None: + self.attachnet(net) + + if addrlist: + for addr in utils.make_tuple(addrlist): + self.addaddr(addr) + + return ifindex + + def delnetif(self, ifindex): + """ + Delete a network interface. + + :param int ifindex: interface index to delete + :return: nothing + """ + if ifindex is None: + ifindex = 0 + + self._netif.pop(ifindex) + + if ifindex == self.ifindex: + self.shutdown() + else: + raise ValueError("ifindex %s does not exist" % ifindex) + + def netif(self, ifindex, net=None): + """ + This object is considered the network interface, so we only + return self here. This keeps the RJ45Node compatible with + real nodes. + + :param int ifindex: interface index to retrieve + :param net: network to retrieve + :return: a network interface + :rtype: core.coreobj.PyCoreNetIf + """ + if net is not None and net == self.net: + return self + + if ifindex is None: + ifindex = 0 + + if ifindex == self.ifindex: + return self + + return None + + def getifindex(self, netif): + """ + Retrieve network interface index. + + :param core.coreobj.PyCoreNetIf netif: network interface to retrieve index for + :return: interface index, None otherwise + :rtype: int + """ + if netif != self: + return None + + return self.ifindex + + def addaddr(self, addr): + """ + Add address to to network interface. + + :param str addr: address to add + :return: nothing + :raises CoreCommandError: when there is a command exception + """ + if self.up: + utils.check_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name]) + + CoreInterface.addaddr(self, addr) + + def deladdr(self, addr): + """ + Delete address from network interface. + + :param str addr: address to delete + :return: nothing + :raises CoreCommandError: when there is a command exception + """ + if self.up: + utils.check_cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.name]) + + CoreInterface.deladdr(self, addr) + + def savestate(self): + """ + Save the addresses and other interface state before using the + interface for emulation purposes. TODO: save/restore the PROMISC flag + + :return: nothing + :raises CoreCommandError: when there is a command exception + """ + self.old_up = False + self.old_addrs = [] + args = [constants.IP_BIN, "addr", "show", "dev", self.localname] + output = utils.check_cmd(args) + for line in output.split("\n"): + items = line.split() + if len(items) < 2: + continue + + if items[1] == "%s:" % self.localname: + flags = items[2][1:-1].split(",") + if "UP" in flags: + self.old_up = True + elif items[0] == "inet": + self.old_addrs.append((items[1], items[3])) + elif items[0] == "inet6": + if items[1][:4] == "fe80": + continue + self.old_addrs.append((items[1], None)) + + def restorestate(self): + """ + Restore the addresses and other interface state after using it. + + :return: nothing + :raises CoreCommandError: when there is a command exception + """ + for addr in self.old_addrs: + if addr[1] is None: + utils.check_cmd([constants.IP_BIN, "addr", "add", addr[0], "dev", self.localname]) + else: + utils.check_cmd([constants.IP_BIN, "addr", "add", addr[0], "brd", addr[1], "dev", self.localname]) + + if self.old_up: + utils.check_cmd([constants.IP_BIN, "link", "set", self.localname, "up"]) + + def setposition(self, x=None, y=None, z=None): + """ + Uses setposition from both parent classes. + + :param float x: x position + :param float y: y position + :param float z: z position + :return: True if position changed, False otherwise + :rtype: bool + """ + result = CoreNodeBase.setposition(self, x, y, z) + CoreInterface.setposition(self, x, y, z) + return result + + def check_cmd(self, args): + """ + Runs shell command on node. + + :param list[str]|str args: command to run + :return: exist status and combined stdout and stderr + :rtype: tuple[int, str] + :raises CoreCommandError: when a non-zero exit status occurs + """ + raise NotImplementedError + + def cmd(self, args, wait=True): + """ + Runs shell command on node, with option to not wait for a result. + + :param list[str]|str args: command to run + :param bool wait: wait for command to exit, defaults to True + :return: exit status for command + :rtype: int + """ + raise NotImplementedError + + def cmd_output(self, args): + """ + Runs shell command on node and get exit status and output. + + :param list[str]|str args: command to run + :return: exit status and combined stdout and stderr + :rtype: tuple[int, str] + """ + raise NotImplementedError + + def termcmdstring(self, sh): + """ + Create a terminal command string. + + :param str sh: shell to execute command in + :return: str + """ + raise NotImplementedError diff --git a/daemon/core/phys/pnodes.py b/daemon/core/phys/pnodes.py deleted file mode 100644 index a2313e97..00000000 --- a/daemon/core/phys/pnodes.py +++ /dev/null @@ -1,245 +0,0 @@ -""" -PhysicalNode class for including real systems in the emulated network. -""" - -import logging -import os -import subprocess -import threading - -from core import CoreCommandError -from core import constants -from core.coreobj import PyCoreNode -from core.misc import utils -from core.netns.vnet import GreTap -from core.netns.vnet import LxBrNet - - -class PhysicalNode(PyCoreNode): - def __init__(self, session, _id=None, name=None, nodedir=None, start=True): - PyCoreNode.__init__(self, session, _id, name, start=start) - self.nodedir = nodedir - self.up = start - self.lock = threading.RLock() - self._mounts = [] - if start: - self.startup() - - def startup(self): - with self.lock: - self.makenodedir() - - def shutdown(self): - if not self.up: - return - - with self.lock: - while self._mounts: - _source, target = self._mounts.pop(-1) - self.umount(target) - - for netif in self.netifs(): - netif.shutdown() - - self.rmnodedir() - - def termcmdstring(self, sh="/bin/sh"): - """ - Create a terminal command string. - - :param str sh: shell to execute command in - :return: str - """ - return sh - - def cmd(self, args, wait=True): - """ - Runs shell command on node, with option to not wait for a result. - - :param list[str]|str args: command to run - :param bool wait: wait for command to exit, defaults to True - :return: exit status for command - :rtype: int - """ - os.chdir(self.nodedir) - status = utils.cmd(args, wait) - return status - - def cmd_output(self, args): - """ - Runs shell command on node and get exit status and output. - - :param list[str]|str args: command to run - :return: exit status and combined stdout and stderr - :rtype: tuple[int, str] - """ - os.chdir(self.nodedir) - p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - stdout, _ = p.communicate() - status = p.wait() - return status, stdout.strip() - - def check_cmd(self, args): - """ - Runs shell command on node. - - :param list[str]|str args: command to run - :return: combined stdout and stderr - :rtype: str - :raises CoreCommandError: when a non-zero exit status occurs - """ - status, output = self.cmd_output(args) - if status: - raise CoreCommandError(status, args, output) - return output.strip() - - def shcmd(self, cmdstr, sh="/bin/sh"): - return self.cmd([sh, "-c", cmdstr]) - - def sethwaddr(self, ifindex, addr): - """ - same as SimpleLxcNode.sethwaddr() - """ - self._netif[ifindex].sethwaddr(addr) - ifname = self.ifname(ifindex) - if self.up: - self.check_cmd([constants.IP_BIN, "link", "set", "dev", ifname, "address", str(addr)]) - - def addaddr(self, ifindex, addr): - """ - same as SimpleLxcNode.addaddr() - """ - if self.up: - self.check_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.ifname(ifindex)]) - - self._netif[ifindex].addaddr(addr) - - def deladdr(self, ifindex, addr): - """ - same as SimpleLxcNode.deladdr() - """ - try: - self._netif[ifindex].deladdr(addr) - except ValueError: - logging.exception("trying to delete unknown address: %s", addr) - - if self.up: - self.check_cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)]) - - def adoptnetif(self, netif, ifindex, hwaddr, addrlist): - """ - The broker builds a GreTap tunnel device to this physical node. - When a link message is received linking this node to another part of - the emulation, no new interface is created; instead, adopt the - GreTap netif as the node interface. - """ - netif.name = "gt%d" % ifindex - netif.node = self - self.addnetif(netif, ifindex) - - # use a more reasonable name, e.g. "gt0" instead of "gt.56286.150" - if self.up: - self.check_cmd([constants.IP_BIN, "link", "set", "dev", netif.localname, "down"]) - self.check_cmd([constants.IP_BIN, "link", "set", netif.localname, "name", netif.name]) - - netif.localname = netif.name - - if hwaddr: - self.sethwaddr(ifindex, hwaddr) - - for addr in utils.make_tuple(addrlist): - self.addaddr(ifindex, addr) - - if self.up: - self.check_cmd([constants.IP_BIN, "link", "set", "dev", netif.localname, "up"]) - - def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None, jitter=None, netif2=None): - """ - Apply tc queing disciplines using LxBrNet.linkconfig() - """ - # borrow the tc qdisc commands from LxBrNet.linkconfig() - linux_bridge = LxBrNet(session=self.session, start=False) - linux_bridge.up = True - linux_bridge.linkconfig(netif, bw=bw, delay=delay, loss=loss, duplicate=duplicate, jitter=jitter, netif2=netif2) - del linux_bridge - - def newifindex(self): - with self.lock: - while self.ifindex in self._netif: - self.ifindex += 1 - ifindex = self.ifindex - self.ifindex += 1 - return ifindex - - def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): - logging.info("creating interface") - if not addrlist: - addrlist = [] - - if self.up and net is None: - raise NotImplementedError - - if ifindex is None: - ifindex = self.newifindex() - - if self.up: - # this is reached when this node is linked to a network node - # tunnel to net not built yet, so build it now and adopt it - gt = self.session.broker.addnettunnel(net.id) - if gt is None or len(gt) != 1: - raise ValueError("error building tunnel from adding a new network interface: %s" % gt) - gt = gt[0] - net.detach(gt) - self.adoptnetif(gt, ifindex, hwaddr, addrlist) - return ifindex - - # this is reached when configuring services (self.up=False) - if ifname is None: - ifname = "gt%d" % ifindex - - netif = GreTap(node=self, name=ifname, session=self.session, start=False) - self.adoptnetif(netif, ifindex, hwaddr, addrlist) - return ifindex - - def privatedir(self, path): - if path[0] != "/": - raise ValueError("path not fully qualified: %s" % path) - hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.')) - os.mkdir(hostpath) - self.mount(hostpath, path) - - def mount(self, source, target): - source = os.path.abspath(source) - logging.info("mounting %s at %s", source, target) - os.makedirs(target) - self.check_cmd([constants.MOUNT_BIN, "--bind", source, target]) - self._mounts.append((source, target)) - - def umount(self, target): - logging.info("unmounting '%s'" % target) - try: - self.check_cmd([constants.UMOUNT_BIN, "-l", target]) - except CoreCommandError: - logging.exception("unmounting failed for %s", target) - - def opennodefile(self, filename, mode="w"): - dirname, basename = os.path.split(filename) - if not basename: - raise ValueError("no basename for filename: " + filename) - - if dirname and dirname[0] == "/": - dirname = dirname[1:] - - dirname = dirname.replace("/", ".") - dirname = os.path.join(self.nodedir, dirname) - if not os.path.isdir(dirname): - os.makedirs(dirname, mode=0755) - - hostfilename = os.path.join(dirname, basename) - return open(hostfilename, mode) - - def nodefile(self, filename, contents, mode=0644): - with self.opennodefile(filename, "w") as node_file: - node_file.write(contents) - os.chmod(node_file.name, mode) - logging.info("created nodefile: '%s'; mode: 0%o", node_file.name, mode) diff --git a/daemon/core/plugins/__init__.py b/daemon/core/plugins/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/daemon/core/sdt.py b/daemon/core/plugins/sdt.py similarity index 94% rename from daemon/core/sdt.py rename to daemon/core/plugins/sdt.py index fd9c9b14..ae65dfc5 100644 --- a/daemon/core/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -7,16 +7,15 @@ import socket from urlparse import urlparse from core import constants -from core.coreobj import PyCoreNet -from core.coreobj import PyCoreObj -from core.enumerations import EventTypes -from core.enumerations import LinkTlvs -from core.enumerations import LinkTypes -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.enumerations import NodeTlvs -from core.enumerations import NodeTypes -from core.misc import nodeutils +from core.nodes.base import NodeBase, CoreNetworkBase +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import LinkTlvs +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.emulator.enumerations import NodeTlvs +from core.emulator.enumerations import NodeTypes +from core.nodes import nodeutils # TODO: A named tuple may be more appropriate, than abusing a class dict like this @@ -321,22 +320,20 @@ class Sdt(object): :return: nothing """ nets = [] - with self.session._objects_lock: - for obj in self.session.objects.itervalues(): - if isinstance(obj, PyCoreNet): - nets.append(obj) - if not isinstance(obj, PyCoreObj): + with self.session._nodes_lock: + for node in self.session.nodes.itervalues(): + if isinstance(node, CoreNetworkBase): + nets.append(node) + if not isinstance(node, NodeBase): continue - (x, y, z) = obj.getposition() + (x, y, z) = node.getposition() if x is None or y is None: continue - self.updatenode(obj.id, MessageFlags.ADD.value, x, y, z, - obj.name, obj.type, obj.icon) + self.updatenode(node.id, MessageFlags.ADD.value, x, y, z, node.name, node.type, node.icon) for nodenum in sorted(self.remotes.keys()): r = self.remotes[nodenum] x, y, z = r.pos - self.updatenode(nodenum, MessageFlags.ADD.value, x, y, z, - r.name, r.type, r.icon) + self.updatenode(nodenum, MessageFlags.ADD.value, x, y, z, r.name, r.type, r.icon) for net in nets: all_links = net.all_link_data(flags=MessageFlags.ADD.value) @@ -411,7 +408,7 @@ class Sdt(object): nodetype = None try: - node = self.session.get_object(nodenum) + node = self.session.get_node(nodenum) except KeyError: node = None if node: @@ -472,7 +469,7 @@ class Sdt(object): return True else: try: - n = self.session.get_object(nodenum) + n = self.session.get_node(nodenum) except KeyError: return False if nodeutils.is_node(n, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)): diff --git a/daemon/core/services/__init__.py b/daemon/core/services/__init__.py index 974cd03e..6c0c18ed 100644 --- a/daemon/core/services/__init__.py +++ b/daemon/core/services/__init__.py @@ -6,7 +6,7 @@ __all__ is automatically loaded by the main core module. """ import os -from core.service import ServiceManager +from core.services.coreservices import ServiceManager _PATH = os.path.abspath(os.path.dirname(__file__)) diff --git a/daemon/core/services/bird.py b/daemon/core/services/bird.py index d0b3d2e9..448cff41 100644 --- a/daemon/core/services/bird.py +++ b/daemon/core/services/bird.py @@ -2,7 +2,7 @@ bird.py: defines routing services provided by the BIRD Internet Routing Daemon. """ -from core.service import CoreService +from core.services.coreservices import CoreService class Bird(CoreService): diff --git a/daemon/core/service.py b/daemon/core/services/coreservices.py similarity index 98% rename from daemon/core/service.py rename to daemon/core/services/coreservices.py index 86a8e3a4..9b762fe1 100644 --- a/daemon/core/service.py +++ b/daemon/core/services/coreservices.py @@ -14,11 +14,10 @@ from multiprocessing.pool import ThreadPool import enum from core.constants import which -from core import CoreCommandError -from core.data import FileData -from core.enumerations import MessageFlags -from core.enumerations import RegisterTlvs -from core.misc import utils +from core import CoreCommandError, utils +from core.emulator.data import FileData +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import RegisterTlvs class ServiceBootError(Exception): @@ -58,7 +57,7 @@ class ServiceDependencies(object): Generates the boot paths for the services provided to the class. :return: list of services to boot, in order - :rtype: list[core.service.CoreService] + :rtype: list[core.coreservices.CoreService] """ paths = [] for service in self.node_services.itervalues(): @@ -126,7 +125,7 @@ class ServiceShim(object): 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 core.netns.vnode.CoreNode node: node to get value list for :param CoreService service: service to get value list for :return: value list string :rtype: str @@ -556,7 +555,7 @@ class CoreServices(object): """ Stop all services on a node. - :param core.netns.nodes.CoreNode node: node to stop services on + :param core.netns.vnode.CoreNode node: node to stop services on :return: nothing """ for service in node.services: diff --git a/daemon/core/services/dockersvc.py b/daemon/core/services/dockersvc.py index 75a31c54..101e30a9 100644 --- a/daemon/core/services/dockersvc.py +++ b/daemon/core/services/dockersvc.py @@ -99,8 +99,8 @@ Limitations: import logging -from core.service import CoreService -from core.service import ServiceManager +from core.services.coreservices import CoreService +from core.services.coreservices import ServiceManager try: from docker import Client diff --git a/daemon/core/services/emaneservices.py b/daemon/core/services/emaneservices.py index 62010e8b..8fb9befe 100644 --- a/daemon/core/services/emaneservices.py +++ b/daemon/core/services/emaneservices.py @@ -1,6 +1,6 @@ -from core.enumerations import NodeTypes -from core.misc import nodeutils -from core.service import CoreService +from core.emulator.enumerations import NodeTypes +from core.nodes import nodeutils +from core.services.coreservices import CoreService from core.xml import emanexml @@ -21,7 +21,7 @@ class EmaneTransportService(CoreService): if filename == cls.configs[0]: transport_commands = [] for interface in node.netifs(sort=True): - network_node = node.session.get_object(interface.net.id) + network_node = node.session.get_node(interface.net.id) if nodeutils.is_node(network_node, NodeTypes.EMANE): config = node.session.emane.get_configs(network_node.id, network_node.model.name) if config and emanexml.is_external(config): diff --git a/daemon/core/services/frr.py b/daemon/core/services/frr.py index 4b51706c..116f9ff6 100644 --- a/daemon/core/services/frr.py +++ b/daemon/core/services/frr.py @@ -4,10 +4,9 @@ Assumes installation of FRR via https://deb.frrouting.org/ """ from core import constants -from core.enumerations import LinkTypes, NodeTypes -from core.misc import ipaddress -from core.misc import nodeutils -from core.service import CoreService +from core.emulator.enumerations import LinkTypes, NodeTypes +from core.nodes import nodeutils, ipaddress +from core.services.coreservices import CoreService class FRRZebra(CoreService): diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index a036b732..62f0b9e3 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -3,9 +3,9 @@ nrl.py: defines services provided by NRL protolib tools hosted here: http://www.nrl.navy.mil/itd/ncs/products """ -from core.misc import utils -from core.misc.ipaddress import Ipv4Prefix -from core.service import CoreService +from core import utils +from core.nodes.ipaddress import Ipv4Prefix +from core.services.coreservices import CoreService class NrlService(CoreService): diff --git a/daemon/core/services/quagga.py b/daemon/core/services/quagga.py index e036a06d..841585ad 100644 --- a/daemon/core/services/quagga.py +++ b/daemon/core/services/quagga.py @@ -3,10 +3,9 @@ quagga.py: defines routing services provided by Quagga. """ from core import constants -from core.enumerations import LinkTypes, NodeTypes -from core.misc import ipaddress -from core.misc import nodeutils -from core.service import CoreService +from core.emulator.enumerations import LinkTypes, NodeTypes +from core.nodes import nodeutils, ipaddress +from core.services.coreservices import CoreService class Zebra(CoreService): diff --git a/daemon/core/services/sdn.py b/daemon/core/services/sdn.py index edbb8dfc..f2b0c084 100644 --- a/daemon/core/services/sdn.py +++ b/daemon/core/services/sdn.py @@ -4,7 +4,7 @@ sdn.py defines services to start Open vSwitch and the Ryu SDN Controller. import re -from core.service import CoreService +from core.services.coreservices import CoreService class SdnService(CoreService): diff --git a/daemon/core/services/security.py b/daemon/core/services/security.py index 9ed3fc39..1fd486d5 100644 --- a/daemon/core/services/security.py +++ b/daemon/core/services/security.py @@ -6,7 +6,7 @@ firewall) import logging from core import constants -from core.service import CoreService +from core.services.coreservices import CoreService class VPNClient(CoreService): diff --git a/daemon/core/services/ucarp.py b/daemon/core/services/ucarp.py index 31657861..ca5bf942 100644 --- a/daemon/core/services/ucarp.py +++ b/daemon/core/services/ucarp.py @@ -2,7 +2,7 @@ ucarp.py: defines high-availability IP address controlled by ucarp """ -from core.service import CoreService +from core.services.coreservices import CoreService UCARP_ETC = "/usr/local/etc/ucarp" diff --git a/daemon/core/services/utility.py b/daemon/core/services/utility.py index 366973a2..a39fa6a6 100644 --- a/daemon/core/services/utility.py +++ b/daemon/core/services/utility.py @@ -4,12 +4,11 @@ utility.py: defines miscellaneous utility services. import os -from core import CoreCommandError +from core import CoreCommandError, utils from core import constants -from core.misc import utils -from core.misc.ipaddress import Ipv4Prefix -from core.misc.ipaddress import Ipv6Prefix -from core.service import CoreService +from core.nodes.ipaddress import Ipv4Prefix +from core.nodes.ipaddress import Ipv6Prefix +from core.services.coreservices import CoreService class UtilService(CoreService): diff --git a/daemon/core/services/xorp.py b/daemon/core/services/xorp.py index 49425ab7..c1564a31 100644 --- a/daemon/core/services/xorp.py +++ b/daemon/core/services/xorp.py @@ -4,7 +4,7 @@ xorp.py: defines routing services provided by the XORP routing suite. import logging -from core.service import CoreService +from core.services.coreservices import CoreService class XorpRtrmgr(CoreService): diff --git a/daemon/core/misc/utils.py b/daemon/core/utils.py similarity index 100% rename from daemon/core/misc/utils.py rename to daemon/core/utils.py diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index ff063d35..d689f022 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -1,15 +1,14 @@ import logging - from lxml import etree -from core import coreobj +import core.nodes.base +import core.nodes.physical from core.emulator.emudata import InterfaceData from core.emulator.emudata import LinkOptions from core.emulator.emudata import NodeOptions -from core.enumerations import NodeTypes -from core.misc import nodeutils -from core.misc.ipaddress import MacAddress -from core.netns import nodes +from core.emulator.enumerations import NodeTypes +from core.nodes import nodeutils +from core.nodes.ipaddress import MacAddress def write_xml_file(xml_element, file_path, doctype=None): @@ -377,12 +376,12 @@ class CoreXmlWriter(object): self.devices = etree.SubElement(self.scenario, "devices") links = [] - for node in self.session.objects.itervalues(): + for node in self.session.nodes.itervalues(): # network node - if isinstance(node, (coreobj.PyCoreNet, nodes.RJ45Node)) and not nodeutils.is_node(node, NodeTypes.CONTROL_NET): + if isinstance(node, (core.nodes.base.CoreNetworkBase, core.nodes.physical.Rj45Node)) and not nodeutils.is_node(node, NodeTypes.CONTROL_NET): self.write_network(node) # device node - elif isinstance(node, nodes.PyCoreNode): + elif isinstance(node, core.nodes.base.CoreNodeBase): self.write_device(node) # add known links @@ -432,7 +431,7 @@ class CoreXmlWriter(object): # check for interface one if link_data.interface1_id is not None: interface_one = etree.Element("interface_one") - node = self.session.get_object(link_data.node1_id) + node = self.session.get_node(link_data.node1_id) node_interface = node.netif(link_data.interface1_id) add_attribute(interface_one, "id", link_data.interface1_id) @@ -453,7 +452,7 @@ class CoreXmlWriter(object): # check for interface two if link_data.interface2_id is not None: interface_two = etree.Element("interface_two") - node = self.session.get_object(link_data.node2_id) + node = self.session.get_node(link_data.node2_id) node_interface = node.netif(link_data.interface2_id) add_attribute(interface_two, "id", link_data.interface2_id) diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index ea1dfc72..aff7b1ae 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -3,10 +3,10 @@ import socket from lxml import etree -from core import constants -from core.coreobj import PyCoreNode -from core.enumerations import NodeTypes -from core.misc import utils, nodeutils, ipaddress +from core import constants, utils +from core.nodes.base import CoreNodeBase +from core.emulator.enumerations import NodeTypes +from core.nodes import nodeutils, ipaddress def add_type(parent_element, name): @@ -100,8 +100,8 @@ class CoreXmlDeployment(object): # servers = self.session.broker.getservernames() # servers.remove("localhost") - for node in self.session.objects.itervalues(): - if isinstance(node, PyCoreNode): + for node in self.session.nodes.itervalues(): + if isinstance(node, CoreNodeBase): self.add_virtual_host(physical_host, node) def add_physical_host(self, name): @@ -119,7 +119,7 @@ class CoreXmlDeployment(object): return host_element def add_virtual_host(self, physical_host, node): - if not isinstance(node, PyCoreNode): + if not isinstance(node, CoreNodeBase): raise TypeError("invalid node type: %s" % node) # create virtual host element diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 3fb3945c..1696f790 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -3,8 +3,8 @@ import os from lxml import etree -from core.misc import utils -from core.misc.ipaddress import MacAddress +from core import utils +from core.nodes.ipaddress import MacAddress from core.xml import corexml _hwaddr_prefix = "02:02" @@ -101,7 +101,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x Create platform xml for a specific node. :param core.emane.emanemanager.EmaneManager emane_manager: emane manager with emane configurations - :param core.netns.nodes.CtrlNet control_net: control net node for this emane network + :param core.nodes.network.CtrlNet control_net: control net node for this emane network :param core.emane.nodes.EmaneNode node: node to write platform xml for :param int nem_id: nem id to use for interfaces for this node :param dict platform_xmls: stores platform xml elements to append nem entries to @@ -121,7 +121,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x nem_element = etree.Element("nem", id=str(nem_id), name=netif.localname, definition=nem_definition) # check if this is an external transport, get default config if an interface specific one does not exist - config = emane_manager.getifcconfig(node.model.object_id, netif, node.model.name) + config = emane_manager.getifcconfig(node.model.id, netif, node.model.name) if is_external(config): nem_element.set("transport", "external") @@ -214,7 +214,7 @@ def build_xml_files(emane_manager, node): return # get model configurations - config = emane_manager.get_configs(node.model.object_id, node.model.name) + config = emane_manager.get_configs(node.model.id, node.model.name) if not config: return @@ -229,7 +229,7 @@ def build_xml_files(emane_manager, node): for netif in node.netifs(): # check for interface specific emane configuration and write xml files, if needed - config = emane_manager.getifcconfig(node.model.object_id, netif, node.model.name) + config = emane_manager.getifcconfig(node.model.id, netif, node.model.name) if config: node.model.build_xml_files(config, netif) @@ -379,7 +379,7 @@ def _basename(emane_model, interface=None): :return: basename used for file creation :rtype: str """ - name = "n%s" % emane_model.object_id + name = "n%s" % emane_model.id if interface: node_id = interface.node.id diff --git a/daemon/examples/api/emane80211.py b/daemon/examples/api/emane80211.py index c0ecf11e..c111962c 100644 --- a/daemon/examples/api/emane80211.py +++ b/daemon/examples/api/emane80211.py @@ -9,7 +9,7 @@ from core import load_logging_config from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emulator.coreemu import CoreEmu from core.emulator.emudata import IpPrefixes -from core.enumerations import EventTypes +from core.emulator.enumerations import EventTypes load_logging_config() @@ -43,7 +43,7 @@ def example(options): session.instantiate() # start a shell on the first node - node = session.get_object(2) + node = session.get_node(2) node.client.term("bash") # shutdown session diff --git a/daemon/examples/api/switch.py b/daemon/examples/api/switch.py index 1bab2fff..633d9b46 100644 --- a/daemon/examples/api/switch.py +++ b/daemon/examples/api/switch.py @@ -11,7 +11,7 @@ import parser from core import load_logging_config from core.emulator.coreemu import CoreEmu from core.emulator.emudata import IpPrefixes -from core.enumerations import NodeTypes, EventTypes +from core.emulator.enumerations import NodeTypes, EventTypes load_logging_config() @@ -40,8 +40,8 @@ def example(options): session.instantiate() # get nodes to run example - first_node = session.get_object(2) - last_node = session.get_object(options.nodes + 1) + first_node = session.get_node(2) + last_node = session.get_node(options.nodes + 1) print "starting iperf server on node: %s" % first_node.name first_node.cmd(["iperf", "-s", "-D"]) diff --git a/daemon/examples/api/switch_inject.py b/daemon/examples/api/switch_inject.py index 7e9300a6..0c80b0f9 100644 --- a/daemon/examples/api/switch_inject.py +++ b/daemon/examples/api/switch_inject.py @@ -6,7 +6,7 @@ # nodestep from core import load_logging_config from core.emulator.emudata import IpPrefixes -from core.enumerations import NodeTypes, EventTypes +from core.emulator.enumerations import NodeTypes, EventTypes load_logging_config() diff --git a/daemon/examples/api/wlan.py b/daemon/examples/api/wlan.py index 1cbd9dd9..aa3c7a03 100644 --- a/daemon/examples/api/wlan.py +++ b/daemon/examples/api/wlan.py @@ -11,8 +11,8 @@ import parser from core import load_logging_config from core.emulator.coreemu import CoreEmu from core.emulator.emudata import IpPrefixes, NodeOptions -from core.enumerations import NodeTypes, EventTypes -from core.mobility import BasicRangeModel +from core.emulator.enumerations import NodeTypes, EventTypes +from core.location.mobility import BasicRangeModel load_logging_config() @@ -44,8 +44,8 @@ def example(options): session.instantiate() # get nodes for example run - first_node = session.get_object(2) - last_node = session.get_object(options.nodes + 1) + first_node = session.get_node(2) + last_node = session.get_node(options.nodes + 1) print "starting iperf server on node: %s" % first_node.name first_node.cmd(["iperf", "-s", "-D"]) diff --git a/daemon/examples/eventloop.py b/daemon/examples/eventloop.py index 7c1b0f8f..d602ee50 100644 --- a/daemon/examples/eventloop.py +++ b/daemon/examples/eventloop.py @@ -1,7 +1,7 @@ import logging import time -from core.misc.event import EventLoop +from core.location.event import EventLoop def main(): diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 354e7b66..bad993f1 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -1,7 +1,6 @@ import logging -from core.grpc import client -from core.grpc import core_pb2 +from core.api.grpc import client, core_pb2 def log_event(event): diff --git a/daemon/examples/myservices/sample.py b/daemon/examples/myservices/sample.py index 843e59ef..3a58345d 100644 --- a/daemon/examples/myservices/sample.py +++ b/daemon/examples/myservices/sample.py @@ -2,8 +2,8 @@ Sample user-defined service. """ -from core.service import CoreService -from core.service import ServiceMode +from core.services.coreservices import CoreService +from core.services.coreservices import ServiceMode ## Custom CORE Service diff --git a/daemon/examples/netns/daemonnodes.py b/daemon/examples/netns/daemonnodes.py index 5d1e2d3c..23061fc2 100755 --- a/daemon/examples/netns/daemonnodes.py +++ b/daemon/examples/netns/daemonnodes.py @@ -14,23 +14,23 @@ import datetime import optparse import sys -from core.api import coreapi -from core.api import dataconversion -from core.api.coreapi import CoreExecuteTlv -from core.enumerations import CORE_API_PORT -from core.enumerations import EventTlvs -from core.enumerations import EventTypes -from core.enumerations import ExecuteTlvs -from core.enumerations import LinkTlvs -from core.enumerations import LinkTypes -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.misc import ipaddress -from core.netns import nodes +import core.nodes.base +import core.nodes.network +from core.api.tlv import coreapi, dataconversion +from core.api.tlv.coreapi import CoreExecuteTlv +from core.emulator.enumerations import CORE_API_PORT +from core.emulator.enumerations import EventTlvs +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import ExecuteTlvs +from core.emulator.enumerations import LinkTlvs +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.nodes import ipaddress # declare classes for use with Broker -from core.session import Session +from core.emulator.session import Session # node list (count from 1) n = [None] @@ -136,7 +136,7 @@ def main(): session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata)) flags = MessageFlags.ADD.value - switch = nodes.SwitchNode(session=session, name="switch", start=False) + switch = core.nodes.network.SwitchNode(session=session, name="switch", start=False) switch.setposition(x=80, y=50) switch.server = daemon switch_data = switch.data(flags) @@ -149,7 +149,7 @@ def main(): # create remote nodes via API for i in xrange(1, number_of_nodes + 1): - node = nodes.CoreNode(session=session, _id=i, name="n%d" % i, start=False) + node = core.nodes.base.CoreNode(session=session, _id=i, name="n%d" % i, start=False) node.setposition(x=150 * i, y=150) node.server = daemon node_data = node.data(flags) diff --git a/daemon/examples/netns/distributed.py b/daemon/examples/netns/distributed.py index 3c092b66..2bd55d3e 100755 --- a/daemon/examples/netns/distributed.py +++ b/daemon/examples/netns/distributed.py @@ -13,12 +13,13 @@ import datetime import optparse import sys +import core.nodes.base +import core.nodes.network from core import constants -from core.api import coreapi, dataconversion -from core.enumerations import CORE_API_PORT, EventTypes, EventTlvs, LinkTlvs, LinkTypes, MessageFlags -from core.misc import ipaddress -from core.netns import nodes -from core.session import Session +from core.api.tlv import coreapi, dataconversion +from core.emulator.enumerations import CORE_API_PORT, EventTypes, EventTlvs, LinkTlvs, LinkTypes, MessageFlags +from core.nodes import ipaddress +from core.emulator.session import Session # node list (count from 1) n = [None] @@ -73,14 +74,14 @@ def main(): tlvdata = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value, EventTypes.CONFIGURATION_STATE.value) session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata)) - switch = session.add_object(cls=nodes.SwitchNode, name="switch") + switch = session.create_node(cls=core.nodes.network.SwitchNode, name="switch") switch.setposition(x=80, y=50) num_local = options.numnodes / 2 num_remote = options.numnodes / 2 + options.numnodes % 2 print "creating %d (%d local / %d remote) nodes with addresses from %s" % \ (options.numnodes, num_local, num_remote, prefix) for i in xrange(1, num_local + 1): - node = session.add_object(cls=nodes.CoreNode, name="n%d" % i, _id=i) + node = session.create_node(cls=core.nodes.base.CoreNode, name="n%d" % i, _id=i) node.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) node.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) node.setposition(x=150 * i, y=150) @@ -91,7 +92,7 @@ def main(): # create remote nodes via API for i in xrange(num_local + 1, options.numnodes + 1): - node = nodes.CoreNode(session=session, _id=i, name="n%d" % i, start=False) + node = core.nodes.base.CoreNode(session=session, _id=i, name="n%d" % i, start=False) node.setposition(x=150 * i, y=150) node.server = slave n.append(node) diff --git a/daemon/examples/netns/howmanynodes.py b/daemon/examples/netns/howmanynodes.py index 851baeb6..bf709a57 100755 --- a/daemon/examples/netns/howmanynodes.py +++ b/daemon/examples/netns/howmanynodes.py @@ -19,10 +19,11 @@ import shutil import sys import time +import core.nodes.base +import core.nodes.network from core import constants -from core.misc import ipaddress -from core.netns import nodes -from core.session import Session +from core.nodes import ipaddress +from core.emulator.session import Session GBD = 1024.0 * 1024.0 @@ -135,7 +136,7 @@ def main(): lfp.flush() session = Session(1) - switch = session.add_object(cls=nodes.SwitchNode) + switch = session.create_node(cls=core.nodes.network.SwitchNode) switchlist.append(switch) print "Added bridge %s (%d)." % (switch.brname, len(switchlist)) @@ -146,7 +147,7 @@ def main(): # optionally add a bridge (options.bridges nodes per bridge) try: if 0 < options.bridges <= switch.numnetif(): - switch = session.add_object(cls=nodes.SwitchNode) + switch = session.create_node(cls=core.nodes.network.SwitchNode) switchlist.append(switch) print "\nAdded bridge %s (%d) for node %d." % (switch.brname, len(switchlist), i) except Exception, e: @@ -155,7 +156,7 @@ def main(): # create a node try: - n = session.add_object(cls=nodes.LxcNode, name="n%d" % i) + n = session.create_node(cls=core.nodes.base.CoreNode, name="n%d" % i) 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: diff --git a/daemon/examples/netns/ospfmanetmdrtest.py b/daemon/examples/netns/ospfmanetmdrtest.py index ed6b3ba8..3bf4f7d0 100755 --- a/daemon/examples/netns/ospfmanetmdrtest.py +++ b/daemon/examples/netns/ospfmanetmdrtest.py @@ -15,14 +15,13 @@ import sys import time from string import Template +import core.nodes.base +import core.nodes.network from core.constants import QUAGGA_STATE_DIR - -from core.misc import ipaddress -from core.misc.utils import check_cmd -from core.netns import nodes - +from core.nodes import ipaddress +from core.utils import check_cmd # this is the /etc/core/core.conf default -from core.session import Session +from core.emulator.session import Session quagga_sbin_search = ("/usr/local/sbin", "/usr/sbin", "/usr/lib/quagga") quagga_path = "zebra" @@ -39,7 +38,7 @@ except OSError: sys.exit(1) -class ManetNode(nodes.LxcNode): +class ManetNode(core.nodes.base.CoreNode): """ An Lxc namespace node configured for Quagga OSPFv3 MANET MDR """ conftemp = Template("""\ @@ -69,7 +68,7 @@ ip forwarding routerid = ipaddr.split("/")[0] self.ipaddr = ipaddr self.routerid = routerid - nodes.LxcNode.__init__(self, core, _id, name, nodedir) + core.nodes.base.CoreBaseNode.__init__(self, core, _id, name, nodedir) self.privatedir(self.confdir) self.privatedir(QUAGGA_STATE_DIR) @@ -243,10 +242,10 @@ class ManetExperiment(object): prefix = ipaddress.Ipv4Prefix("10.14.0.0/16") self.session = Session(1) # emulated network - self.net = self.session.add_object(cls=nodes.WlanNode) + self.net = self.session.create_node(cls=core.nodes.network.WlanNode) for i in xrange(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) - tmp = self.session.add_object(cls=ManetNode, ipaddr=addr, _id="%d" % i, name="n%d" % i) + tmp = self.session.create_node(cls=ManetNode, ipaddr=addr, _id="%d" % i, name="n%d" % i) tmp.newnetif(self.net, [addr]) self.nodes.append(tmp) # connect nodes with probability linkprob diff --git a/daemon/examples/netns/wlanemanetests.py b/daemon/examples/netns/wlanemanetests.py index 0d78d792..b0870deb 100755 --- a/daemon/examples/netns/wlanemanetests.py +++ b/daemon/examples/netns/wlanemanetests.py @@ -37,13 +37,14 @@ import os import sys import time +import core.nodes.base +import core.nodes.network from core import emane from core.emane.bypass import EmaneBypassModel from core.emane.nodes import EmaneNode from core.emane.rfpipe import EmaneRfPipeModel -from core.misc import ipaddress -from core.netns import nodes -from core.session import Session +from core.nodes import ipaddress +from core.emulator.session import Session try: import emaneeventservice @@ -413,11 +414,11 @@ class Experiment(object): prefix = ipaddress.Ipv4Prefix("10.0.0.0/16") self.session = Session(1) # emulated network - self.net = self.session.add_object(cls=nodes.WlanNode, name="wlan1") + self.net = self.session.create_node(cls=core.nodes.network.WlanNode, name="wlan1") prev = None for i in xrange(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) - tmp = self.session.add_object(cls=nodes.CoreNode, _id=i, name="n%d" % i) + tmp = self.session.create_node(cls=core.nodes.base.CoreNode, _id=i, name="n%d" % i) tmp.newnetif(self.net, [addr]) self.nodes.append(tmp) self.session.services.add_services(tmp, "router", "IPForward") @@ -440,12 +441,12 @@ class Experiment(object): self.session.location.setrefgeo(47.57917, -122.13232, 2.00000) self.session.location.refscale = 150.0 self.session.emane.loadmodels() - self.net = self.session.add_object(cls=EmaneNode, _id=numnodes + 1, name="wlan1") + self.net = self.session.create_node(cls=EmaneNode, _id=numnodes + 1, name="wlan1") self.net.verbose = verbose # self.session.emane.addobj(self.net) for i in xrange(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) - tmp = self.session.add_object(cls=nodes.CoreNode, _id=i, name="n%d" % i) + tmp = self.session.create_node(cls=core.nodes.base.CoreNode, _id=i, name="n%d" % i) # tmp.setposition(i * 20, 50, None) tmp.setposition(50, 50, None) tmp.newnetif(self.net, [addr]) diff --git a/daemon/examples/stopsession.py b/daemon/examples/stopsession.py index 9cbfc2cb..d6b2349d 100755 --- a/daemon/examples/stopsession.py +++ b/daemon/examples/stopsession.py @@ -8,8 +8,8 @@ import optparse import socket -from core.api import coreapi -from core.enumerations import MessageFlags, SessionTlvs, CORE_API_PORT +from core.api.tlv import coreapi +from core.emulator.enumerations import MessageFlags, SessionTlvs, CORE_API_PORT def main(): diff --git a/daemon/proto/Makefile.am b/daemon/proto/Makefile.am index bcf62a2c..59665a6f 100644 --- a/daemon/proto/Makefile.am +++ b/daemon/proto/Makefile.am @@ -1,5 +1,5 @@ all: - $(PYTHON) -m grpc_tools.protoc -I . --python_out=../core/grpc --grpc_python_out=../core/grpc core.proto + $(PYTHON) -m grpc_tools.protoc -I . --python_out=../core/api/grpc --grpc_python_out=../core/api/grpc core.proto clean: - -rm -f ../core/grpc/core_pb2* + -rm -f ../core/api/grpc/core_pb2* diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index a1c14cec..e40ace9b 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -14,11 +14,11 @@ import time from core import load_logging_config from core import constants -from core import enumerations -from core.corehandlers import CoreHandler -from core.coreserver import CoreServer -from core.grpc.server import CoreGrpcServer -from core.misc.utils import close_onexec +from core.emulator import enumerations +from core.api.tlv.corehandlers import CoreHandler +from core.api.tlv.coreserver import CoreServer +from core.api.grpc.server import CoreGrpcServer +from core.utils import close_onexec load_logging_config() @@ -48,7 +48,7 @@ def cored(cfg): try: server = CoreServer((host, port), CoreHandler, cfg) if cfg["ovs"] == "True": - from core.netns.openvswitch import OVS_NODES + from core.nodes.openvswitch import OVS_NODES server.coreemu.update_nodes(OVS_NODES) except: logging.exception("error starting main server on: %s:%s", host, port) diff --git a/daemon/scripts/coresendmsg b/daemon/scripts/coresendmsg index be6e030b..d8d8a2d3 100644 --- a/daemon/scripts/coresendmsg +++ b/daemon/scripts/coresendmsg @@ -8,11 +8,11 @@ import os import socket import sys -from core.api import coreapi -from core.enumerations import CORE_API_PORT -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.enumerations import SessionTlvs +from core.api.tlv import coreapi +from core.emulator.enumerations import CORE_API_PORT +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.emulator.enumerations import SessionTlvs def print_available_tlvs(t, tlv_class): diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 4e8a2871..221700d5 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -9,30 +9,30 @@ import time import pytest from mock.mock import MagicMock -from core.api.coreapi import CoreConfMessage -from core.api.coreapi import CoreEventMessage -from core.api.coreapi import CoreExecMessage -from core.api.coreapi import CoreLinkMessage -from core.api.coreapi import CoreNodeMessage -from core.corehandlers import CoreHandler -from core.coreserver import CoreServer +from core.api.tlv.coreapi import CoreConfMessage +from core.api.tlv.coreapi import CoreEventMessage +from core.api.tlv.coreapi import CoreExecMessage +from core.api.tlv.coreapi import CoreLinkMessage +from core.api.tlv.coreapi import CoreNodeMessage +from core.api.tlv.corehandlers import CoreHandler +from core.api.tlv.coreserver import CoreServer from core.emulator.coreemu import CoreEmu from core.emulator.emudata import IpPrefixes -from core.enumerations import CORE_API_PORT -from core.enumerations import ConfigTlvs -from core.enumerations import EventTlvs -from core.enumerations import EventTypes -from core.enumerations import ExecuteTlvs -from core.enumerations import LinkTlvs -from core.enumerations import LinkTypes -from core.enumerations import MessageFlags -from core.enumerations import NodeTlvs -from core.enumerations import NodeTypes -from core.grpc.client import InterfaceHelper -from core.grpc.server import CoreGrpcServer -from core.misc import ipaddress -from core.misc.ipaddress import MacAddress -from core.service import ServiceManager +from core.emulator.enumerations import CORE_API_PORT +from core.emulator.enumerations import ConfigTlvs +from core.emulator.enumerations import EventTlvs +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import ExecuteTlvs +from core.emulator.enumerations import LinkTlvs +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import NodeTlvs +from core.emulator.enumerations import NodeTypes +from core.api.grpc.client import InterfaceHelper +from core.api.grpc.server import CoreGrpcServer +from core.nodes import ipaddress +from core.nodes.ipaddress import MacAddress +from core.services.coreservices import ServiceManager EMANE_SERVICES = "zebra|OSPFv3MDR|IPForward" diff --git a/daemon/tests/distributed/test_distributed.py b/daemon/tests/distributed/test_distributed.py index 0a808f0f..925c4b7f 100644 --- a/daemon/tests/distributed/test_distributed.py +++ b/daemon/tests/distributed/test_distributed.py @@ -4,12 +4,12 @@ Unit tests for testing CORE with distributed networks. import conftest -from core.api.coreapi import CoreExecMessage -from core.enumerations import EventTypes -from core.enumerations import ExecuteTlvs -from core.enumerations import MessageFlags -from core.enumerations import NodeTypes -from core.misc.ipaddress import IpAddress +from core.api.tlv.coreapi import CoreExecMessage +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import ExecuteTlvs +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import NodeTypes +from core.nodes.ipaddress import IpAddress def validate_response(replies, _): @@ -89,7 +89,7 @@ class TestDistributed: cored.request_handler.handle_message(message) # test a ping command - node_one = cored.session.get_object(1) + node_one = cored.session.get_node(1) message = conftest.command_message(node_one, "ping -c 5 %s" % ip4_address) cored.request_handler.dispatch_replies = validate_response cored.request_handler.handle_message(message) @@ -155,7 +155,7 @@ class TestDistributed: cored.request_handler.handle_message(message) # test a ping command - node_one = cored.session.get_object(1) + node_one = cored.session.get_node(1) message = conftest.command_message(node_one, "ping -c 5 %s" % ip4_address) cored.request_handler.dispatch_replies = validate_response cored.request_handler.handle_message(message) diff --git a/daemon/tests/myservices/sample.py b/daemon/tests/myservices/sample.py index e6673670..5a12c795 100644 --- a/daemon/tests/myservices/sample.py +++ b/daemon/tests/myservices/sample.py @@ -2,7 +2,7 @@ Sample user-defined services for testing. """ -from core.service import CoreService +from core.services.coreservices import CoreService class MyService(CoreService): diff --git a/daemon/tests/test_conf.py b/daemon/tests/test_conf.py index 15b17300..0d2dc78f 100644 --- a/daemon/tests/test_conf.py +++ b/daemon/tests/test_conf.py @@ -1,13 +1,13 @@ import pytest -from core.conf import ConfigurableManager -from core.conf import ConfigurableOptions -from core.conf import Configuration -from core.conf import ModelManager +from core.config import ConfigurableManager +from core.config import ConfigurableOptions +from core.config import Configuration +from core.config import ModelManager from core.emane.ieee80211abg import EmaneIeee80211abgModel -from core.enumerations import ConfigDataTypes -from core.enumerations import NodeTypes -from core.mobility import BasicRangeModel +from core.emulator.enumerations import ConfigDataTypes +from core.emulator.enumerations import NodeTypes +from core.location.mobility import BasicRangeModel class TestConfigurableOptions(ConfigurableOptions): diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 04f05897..176bcdd1 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -10,11 +10,11 @@ import pytest from mock import MagicMock from core.emulator.emudata import NodeOptions -from core.enumerations import MessageFlags -from core.enumerations import NodeTypes -from core.mobility import BasicRangeModel -from core.mobility import Ns2ScriptedMobility -from core.netns.vnodeclient import VnodeClient +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import NodeTypes +from core.location.mobility import BasicRangeModel +from core.location.mobility import Ns2ScriptedMobility +from core.nodes.client import VnodeClient _PATH = os.path.abspath(os.path.dirname(__file__)) _MOBILITY_FILE = os.path.join(_PATH, "mobility.scen") diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 9b4cc0fa..22cd3afe 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -5,13 +5,13 @@ from Queue import Queue import grpc import pytest -from core.conf import ConfigShim -from core.data import EventData +from core.config import ConfigShim +from core.emulator.data import EventData from core.emane.ieee80211abg import EmaneIeee80211abgModel -from core.enumerations import NodeTypes, EventTypes, ConfigFlags, ExceptionLevels -from core.grpc import core_pb2 -from core.grpc.client import CoreGrpcClient -from core.mobility import BasicRangeModel, Ns2ScriptedMobility +from core.emulator.enumerations import NodeTypes, EventTypes, ConfigFlags, ExceptionLevels +from core.api.grpc import core_pb2 +from core.api.grpc.client import CoreGrpcClient +from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility class TestGrpc: @@ -182,7 +182,7 @@ class TestGrpc: # then assert response.id is not None - assert session.get_object(response.id) is not None + assert session.get_node(response.id) is not None def test_get_node(self, grpc_server): # given @@ -237,7 +237,7 @@ class TestGrpc: assert response.result is expected if expected is True: with pytest.raises(KeyError): - assert session.get_object(node.id) + assert session.get_node(node.id) def test_get_hooks(self, grpc_server): # given @@ -390,8 +390,8 @@ class TestGrpc: interface_two = ip_prefixes.create_interface(node_two) session.add_link(node_one.id, node_two.id, interface_one, interface_two) link_node = None - for node_id in session.objects: - node = session.objects[node_id] + for node_id in session.nodes: + node = session.nodes[node_id] if node.id not in {node_one.id, node_two.id}: link_node = node break diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index 95962739..a06d61a9 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -4,17 +4,17 @@ Unit tests for testing with a CORE switch. import threading -from core.api import coreapi, dataconversion -from core.api.coreapi import CoreExecuteTlv -from core.enumerations import CORE_API_PORT, NodeTypes -from core.enumerations import EventTlvs -from core.enumerations import EventTypes -from core.enumerations import ExecuteTlvs -from core.enumerations import LinkTlvs -from core.enumerations import LinkTypes -from core.enumerations import MessageFlags -from core.enumerations import MessageTypes -from core.misc import ipaddress +from core.api.tlv import coreapi, dataconversion +from core.api.tlv.coreapi import CoreExecuteTlv +from core.emulator.enumerations import CORE_API_PORT, NodeTypes +from core.emulator.enumerations import EventTlvs +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import ExecuteTlvs +from core.emulator.enumerations import LinkTlvs +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import MessageFlags +from core.emulator.enumerations import MessageTypes +from core.nodes import ipaddress def command_message(node, command): diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index c032c4a6..fa3a3a5c 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -1,6 +1,6 @@ from core.emulator.emudata import LinkOptions -from core.enumerations import NodeTypes -from core.misc import utils +from core.emulator.enumerations import NodeTypes +from core import utils def create_ptp_network(session, ip_prefixes): diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index a361ff6c..bebf7dbc 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -4,8 +4,8 @@ import time import pytest from core.emulator.emudata import NodeOptions -from core.enumerations import NodeTypes -from core.misc import utils +from core.emulator.enumerations import NodeTypes +from core import utils MODELS = [ "router", @@ -63,7 +63,7 @@ class TestNodes: # then with pytest.raises(KeyError): - session.get_object(node.id) + session.get_node(node.id) @pytest.mark.parametrize("net_type", NET_TYPES) def test_net(self, session, net_type): diff --git a/daemon/tests/test_services.py b/daemon/tests/test_services.py index 42c10634..d6960dfc 100644 --- a/daemon/tests/test_services.py +++ b/daemon/tests/test_services.py @@ -2,9 +2,9 @@ import os import pytest -from core.service import CoreService -from core.service import ServiceDependencies -from core.service import ServiceManager +from core.services.coreservices import CoreService +from core.services.coreservices import ServiceDependencies +from core.services.coreservices import ServiceManager _PATH = os.path.abspath(os.path.dirname(__file__)) _SERVICES_PATH = os.path.join(_PATH, "myservices") diff --git a/daemon/tests/test_utils.py b/daemon/tests/test_utils.py index 997dce0f..571a376d 100644 --- a/daemon/tests/test_utils.py +++ b/daemon/tests/test_utils.py @@ -1,4 +1,4 @@ -from core.misc import utils +from core import utils class TestUtils: diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 04db4c0e..f093b225 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -4,8 +4,8 @@ import pytest from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emulator.emudata import NodeOptions -from core.enumerations import NodeTypes -from core.mobility import BasicRangeModel +from core.emulator.enumerations import NodeTypes +from core.location.mobility import BasicRangeModel from core.services.utility import SshService @@ -87,16 +87,16 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(KeyError): - assert not session.get_object(n1_id) + assert not session.get_node(n1_id) with pytest.raises(KeyError): - assert not session.get_object(n2_id) + assert not session.get_node(n2_id) # load saved xml session.open_xml(file_path, start=True) # verify nodes have been recreated - assert session.get_object(n1_id) - assert session.get_object(n2_id) + assert session.get_node(n1_id) + assert session.get_node(n2_id) def test_xml_ptp_services(self, session, tmpdir, ip_prefixes): """ @@ -147,9 +147,9 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(KeyError): - assert not session.get_object(n1_id) + assert not session.get_node(n1_id) with pytest.raises(KeyError): - assert not session.get_object(n2_id) + assert not session.get_node(n2_id) # load saved xml session.open_xml(file_path, start=True) @@ -158,8 +158,8 @@ class TestXml: service = session.services.get_service(node_one.id, SshService.name) # verify nodes have been recreated - assert session.get_object(n1_id) - assert session.get_object(n2_id) + assert session.get_node(n1_id) + assert session.get_node(n2_id) assert service.config_data.get(service_file) == file_data def test_xml_mobility(self, session, tmpdir, ip_prefixes): @@ -208,9 +208,9 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(KeyError): - assert not session.get_object(n1_id) + assert not session.get_node(n1_id) with pytest.raises(KeyError): - assert not session.get_object(n2_id) + assert not session.get_node(n2_id) # load saved xml session.open_xml(file_path, start=True) @@ -219,9 +219,9 @@ class TestXml: value = str(session.mobility.get_config("test", wlan_id, BasicRangeModel.name)) # verify nodes and configuration were restored - assert session.get_object(n1_id) - assert session.get_object(n2_id) - assert session.get_object(wlan_id) + assert session.get_node(n1_id) + assert session.get_node(n2_id) + assert session.get_node(wlan_id) assert value == "1" def test_xml_emane(self, session, tmpdir, ip_prefixes): @@ -275,9 +275,9 @@ class TestXml: # verify nodes have been removed from session with pytest.raises(KeyError): - assert not session.get_object(n1_id) + assert not session.get_node(n1_id) with pytest.raises(KeyError): - assert not session.get_object(n2_id) + assert not session.get_node(n2_id) # load saved xml session.open_xml(file_path, start=True) @@ -286,7 +286,7 @@ class TestXml: value = str(session.emane.get_config("test", emane_id, EmaneIeee80211abgModel.name)) # verify nodes and configuration were restored - assert session.get_object(n1_id) - assert session.get_object(n2_id) - assert session.get_object(emane_id) + assert session.get_node(n1_id) + assert session.get_node(n2_id) + assert session.get_node(emane_id) assert value == "1" diff --git a/ns3/corens3/obj.py b/ns3/corens3/obj.py index 1920fd62..e23acc46 100644 --- a/ns3/corens3/obj.py +++ b/ns3/corens3/obj.py @@ -17,14 +17,13 @@ import ns.wifi import ns.wimax from core import constants -from core.coreobj import PyCoreNet -from core.enumerations import EventTypes -from core.enumerations import LinkTypes -from core.enumerations import NodeTypes -from core.misc.utils import make_tuple -from core.mobility import WayPointMobility -from core.netns.nodes import CoreNode -from core.session import Session +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import NodeTypes +from core.utils import make_tuple +from core.location.mobility import WayPointMobility +from core.nodes.base import CoreNode, CoreNetworkBase +from core.emulator.session import Session ns.core.GlobalValue.Bind( "SimulatorImplementationType", @@ -107,7 +106,7 @@ class CoreNs3Node(CoreNode, ns.network.Node): self.warn("ns-3 mobility model not found, not setting position") -class CoreNs3Net(PyCoreNet): +class CoreNs3Net(CoreNetworkBase): """ The CoreNs3Net is a helper PyCoreNet object. Networks are represented entirely in simulation with the TunTap device bridging the emulated and @@ -119,7 +118,7 @@ class CoreNs3Net(PyCoreNet): type = "wlan" def __init__(self, session, _id=None, name=None, start=True, policy=None): - PyCoreNet.__init__(self, session, _id, name) + CoreNetworkBase.__init__(self, session, _id, name) self.tapbridge = ns.tap_bridge.TapBridgeHelper() self._ns3devs = {} self._tapdevs = {} @@ -402,7 +401,7 @@ class Ns3Session(Session): A convenience helper for Session.addobj(), for adding CoreNs3Nodes to this session. Keeps a NodeContainer for later use. """ - n = self.add_object(cls=CoreNs3Node, name=name) + n = self.create_node(cls=CoreNs3Node, name=name) self.nodes.Add(n) return n @@ -488,7 +487,7 @@ class Ns3Session(Session): Start a tracing thread using the ASCII output from the ns3 mobility helper. """ - net.mobility = WayPointMobility(session=self, object_id=net.id) + net.mobility = WayPointMobility(session=self, _id=net.id) net.mobility.setendtime() net.mobility.refresh_ms = 300 net.mobility.empty_queue_stop = False diff --git a/ns3/examples/ns3lte.py b/ns3/examples/ns3lte.py index ef3a6fc2..2cb17407 100644 --- a/ns3/examples/ns3lte.py +++ b/ns3/examples/ns3lte.py @@ -11,9 +11,7 @@ import sys import ns.core import ns.mobility -from core.misc import ipaddress -from core.misc import nodemaps -from core.misc import nodeutils +from core.nodes import nodeutils, nodemaps, ipaddress from corens3.obj import Ns3LteNet from corens3.obj import Ns3Session @@ -24,7 +22,7 @@ def ltesession(opt): """ nodeutils.set_node_map(nodemaps.NODES) session = Ns3Session(1, persistent=True, duration=opt.duration) - lte = session.add_object(cls=Ns3LteNet, name="wlan1") + lte = session.create_node(cls=Ns3LteNet, name="wlan1") lte.setsubchannels(range(25), range(50, 100)) if opt.verbose: ascii_helper = ns.network.AsciiTraceHelper() diff --git a/ns3/examples/ns3wifi.py b/ns3/examples/ns3wifi.py index 759c5675..747b184b 100644 --- a/ns3/examples/ns3wifi.py +++ b/ns3/examples/ns3wifi.py @@ -28,9 +28,7 @@ import sys import ns.core -from core.misc import ipaddress -from core.misc import nodeutils -from core.misc import nodemaps +from core.nodes import nodeutils, nodemaps, ipaddress from corens3.obj import Ns3Session from corens3.obj import Ns3WifiNet @@ -59,7 +57,7 @@ def wifisession(opt): session.node_count = str(opt.numnodes + 1) add_to_server(session) - wifi = session.add_object(cls=Ns3WifiNet, name="wlan1") + wifi = session.create_node(cls=Ns3WifiNet, name="wlan1") wifi.setposition(30, 30, 0) wifi.phy.Set("RxGain", ns.core.DoubleValue(18.0)) diff --git a/ns3/examples/ns3wifirandomwalk.py b/ns3/examples/ns3wifirandomwalk.py index 2946f359..a96f37f6 100644 --- a/ns3/examples/ns3wifirandomwalk.py +++ b/ns3/examples/ns3wifirandomwalk.py @@ -20,9 +20,7 @@ import ns.network from corens3.obj import Ns3Session from corens3.obj import Ns3WifiNet -from core.misc import ipaddress -from core.misc import nodemaps -from core.misc import nodeutils +from core.nodes import nodeutils, nodemaps, ipaddress def add_to_server(session): @@ -48,7 +46,7 @@ def wifisession(opt): session.filename = session.name + ".py" session.node_count = str(opt.numnodes + 1) add_to_server(session) - wifi = session.add_object(cls=Ns3WifiNet, name="wlan1", rate="OfdmRate12Mbps") + wifi = session.create_node(cls=Ns3WifiNet, name="wlan1", rate="OfdmRate12Mbps") wifi.setposition(30, 30, 0) # for improved connectivity wifi.phy.Set("RxGain", ns.core.DoubleValue(18.0)) diff --git a/ns3/examples/ns3wimax.py b/ns3/examples/ns3wimax.py index e939e934..b1d7212c 100644 --- a/ns3/examples/ns3wimax.py +++ b/ns3/examples/ns3wimax.py @@ -13,9 +13,7 @@ import logging import optparse import sys -from core.misc import ipaddress -from core.misc import nodemaps -from core.misc import nodeutils +from core.nodes import nodeutils, nodemaps, ipaddress from corens3.obj import Ns3Session from corens3.obj import Ns3WimaxNet @@ -26,7 +24,7 @@ def wimaxsession(opt): """ nodeutils.set_node_map(nodemaps.NODES) session = Ns3Session(1, persistent=True, duration=opt.duration) - wimax = session.add_object(cls=Ns3WimaxNet, name="wlan1") + wimax = session.create_node(cls=Ns3WimaxNet, name="wlan1") # wimax.wimax.EnableLogComponents() prefix = ipaddress.Ipv4Prefix("10.0.0.0/16") From e79fd6c7cec78d28becd0edb151e4a4c3782d94e Mon Sep 17 00:00:00 2001 From: bharnden Date: Wed, 1 May 2019 23:17:46 -0700 Subject: [PATCH 0005/1992] merged EmuSession and Session, small cleanup --- daemon/core/emulator/coreemu.py | 741 +------------------------- daemon/core/emulator/emudata.py | 70 +++ daemon/core/emulator/session.py | 723 ++++++++++++++++++++++--- daemon/core/emulator/sessionconfig.py | 62 +++ 4 files changed, 792 insertions(+), 804 deletions(-) create mode 100644 daemon/core/emulator/sessionconfig.py diff --git a/daemon/core/emulator/coreemu.py b/daemon/core/emulator/coreemu.py index 467ea145..70fd86ee 100644 --- a/daemon/core/emulator/coreemu.py +++ b/daemon/core/emulator/coreemu.py @@ -5,17 +5,11 @@ import signal import sys import core.services -from core.nodes.base import CoreNodeBase, CoreNetworkBase -from core.emulator.data import NodeData -from core.emulator.emudata import LinkOptions -from core.emulator.emudata import NodeOptions -from core.emulator.enumerations import EventTypes -from core.emulator.enumerations import LinkTypes -from core.emulator.enumerations import NodeTypes -from core.nodes import nodeutils, nodemaps -from core.services.coreservices import ServiceManager +from core.emulator.emudata import IdGen from core.emulator.session import Session -from core.xml.corexml import CoreXmlReader, CoreXmlWriter +from core.nodes import nodemaps +from core.nodes import nodeutils +from core.services.coreservices import ServiceManager def signal_handler(signal_number, _): @@ -37,711 +31,6 @@ signal.signal(signal.SIGUSR1, signal_handler) signal.signal(signal.SIGUSR2, signal_handler) -def create_interface(node, network, interface_data): - """ - Create an interface for a node on a network using provided interface data. - - :param node: node to create interface for - :param network: network to associate interface with - :param core.emulator.emudata.InterfaceData interface_data: interface data - :return: created interface - """ - node.newnetif( - network, - addrlist=interface_data.get_addresses(), - hwaddr=interface_data.mac, - ifindex=interface_data.id, - ifname=interface_data.name - ) - return node.netif(interface_data.id, network) - - -def link_config(network, interface, link_options, devname=None, interface_two=None): - """ - Convenience method for configuring a link, - - :param network: network to configure link for - :param interface: interface to configure - :param core.emulator.emudata.LinkOptions link_options: data to configure link with - :param str devname: device name, default is None - :param interface_two: other interface associated, default is None - :return: nothing - """ - config = { - "netif": interface, - "bw": link_options.bandwidth, - "delay": link_options.delay, - "loss": link_options.per, - "duplicate": link_options.dup, - "jitter": link_options.jitter, - "netif2": interface_two - } - - # hacky check here, because physical and emane nodes do not conform to the same linkconfig interface - if not nodeutils.is_node(network, [NodeTypes.EMANE, NodeTypes.PHYSICAL]): - config["devname"] = devname - - network.linkconfig(**config) - - -def is_net_node(node): - """ - Convenience method for testing if a legacy core node is considered a network node. - - :param object node: object to test against - :return: True if object is an instance of a network node, False otherwise - :rtype: bool - """ - return isinstance(node, CoreNetworkBase) - - -def is_core_node(node): - """ - Convenience method for testing if a legacy core node is considered a core node. - - :param object node: object to test against - :return: True if object is an instance of a core node, False otherwise - :rtype: bool - """ - return isinstance(node, CoreNodeBase) - - -class IdGen(object): - def __init__(self, _id=0): - self.id = _id - - def next(self): - self.id += 1 - return self.id - - -class EmuSession(Session): - def __init__(self, _id, config=None, mkdir=True): - super(EmuSession, self).__init__(_id, config, mkdir) - - # object management - self.node_id_gen = IdGen() - - # set default services - self.services.default_services = { - "mdr": ("zebra", "OSPFv3MDR", "IPForward"), - "PC": ("DefaultRoute",), - "prouter": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), - "router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), - "host": ("DefaultRoute", "SSH"), - } - - def _link_nodes(self, node_one_id, node_two_id): - """ - Convenience method for retrieving nodes within link data. - - :param int node_one_id: node one id - :param int node_two_id: node two id - :return: nodes, network nodes if present, and tunnel if present - :rtype: tuple - """ - logging.debug("link message between node1(%s) and node2(%s)", node_one_id, node_two_id) - - # values to fill - net_one = None - net_two = None - - # retrieve node one - node_one = self.get_node(node_one_id) - node_two = self.get_node(node_two_id) - - # both node ids are provided - tunnel = self.broker.gettunnel(node_one_id, node_two_id) - logging.debug("tunnel between nodes: %s", tunnel) - if nodeutils.is_node(tunnel, NodeTypes.TAP_BRIDGE): - net_one = tunnel - if tunnel.remotenum == node_one_id: - node_one = None - else: - node_two = None - # physical node connected via gre tap tunnel - elif tunnel: - if tunnel.remotenum == node_one_id: - node_one = None - else: - node_two = None - - if is_net_node(node_one): - if not net_one: - net_one = node_one - else: - net_two = node_one - node_one = None - - if is_net_node(node_two): - if not net_one: - net_one = node_two - else: - net_two = node_two - node_two = None - - logging.debug("link node types n1(%s) n2(%s) net1(%s) net2(%s) tunnel(%s)", - node_one, node_two, net_one, net_two, tunnel) - return node_one, node_two, net_one, net_two, tunnel - - # TODO: this doesn't appear to ever be used, EMANE or basic wireless range - def _link_wireless(self, objects, connect): - """ - Objects to deal with when connecting/disconnecting wireless links. - - :param list objects: possible objects to deal with - :param bool connect: link interfaces if True, unlink otherwise - :return: nothing - """ - objects = [x for x in objects if x] - if len(objects) < 2: - raise ValueError("wireless link failure: %s", objects) - logging.debug("handling wireless linking objects(%s) connect(%s)", objects, connect) - common_networks = objects[0].commonnets(objects[1]) - if not common_networks: - raise ValueError("no common network found for wireless link/unlink") - - for common_network, interface_one, interface_two in common_networks: - if not nodeutils.is_node(common_network, [NodeTypes.WIRELESS_LAN, NodeTypes.EMANE]): - logging.info("skipping common network that is not wireless/emane: %s", common_network) - continue - - logging.info("wireless linking connect(%s): %s - %s", connect, interface_one, interface_two) - if connect: - common_network.link(interface_one, interface_two) - else: - common_network.unlink(interface_one, interface_two) - - def add_link(self, node_one_id, node_two_id, interface_one=None, interface_two=None, link_options=LinkOptions()): - """ - Add a link between nodes. - - :param int node_one_id: node one id - :param int node_two_id: node two id - :param core.emulator.emudata.InterfaceData interface_one: node one interface data, defaults to none - :param core.emulator.emudata.InterfaceData interface_two: node two interface data, defaults to none - :param core.emulator.emudata.LinkOptions link_options: data for creating link, defaults to no options - :return: - """ - # get node objects identified by link data - node_one, node_two, net_one, net_two, tunnel = self._link_nodes(node_one_id, node_two_id) - - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - try: - # wireless link - if link_options.type == LinkTypes.WIRELESS: - objects = [node_one, node_two, net_one, net_two] - self._link_wireless(objects, connect=True) - # wired link - else: - # 2 nodes being linked, ptp network - if all([node_one, node_two]) and not net_one: - logging.info("adding link for peer to peer nodes: %s - %s", node_one.name, node_two.name) - ptp_class = nodeutils.get_node_class(NodeTypes.PEER_TO_PEER) - start = self.state > EventTypes.DEFINITION_STATE.value - net_one = self.create_node(cls=ptp_class, start=start) - - # node to network - if node_one and net_one: - logging.info("adding link from node to network: %s - %s", node_one.name, net_one.name) - interface = create_interface(node_one, net_one, interface_one) - link_config(net_one, interface, link_options) - - # network to node - if node_two and net_one: - logging.info("adding link from network to node: %s - %s", node_two.name, net_one.name) - interface = create_interface(node_two, net_one, interface_two) - if not link_options.unidirectional: - link_config(net_one, interface, link_options) - - # network to network - if net_one and net_two: - logging.info("adding link from network to network: %s - %s", net_one.name, net_two.name) - if nodeutils.is_node(net_two, NodeTypes.RJ45): - interface = net_two.linknet(net_one) - else: - interface = net_one.linknet(net_two) - - link_config(net_one, interface, link_options) - - if not link_options.unidirectional: - interface.swapparams("_params_up") - link_config(net_two, interface, link_options, devname=interface.name) - interface.swapparams("_params_up") - - # a tunnel node was found for the nodes - addresses = [] - if not node_one and all([net_one, interface_one]): - addresses.extend(interface_one.get_addresses()) - - if not node_two and all([net_two, interface_two]): - addresses.extend(interface_two.get_addresses()) - - # tunnel node logic - key = link_options.key - if key and nodeutils.is_node(net_one, NodeTypes.TUNNEL): - logging.info("setting tunnel key for: %s", net_one.name) - net_one.setkey(key) - if addresses: - net_one.addrconfig(addresses) - if key and nodeutils.is_node(net_two, NodeTypes.TUNNEL): - logging.info("setting tunnel key for: %s", net_two.name) - net_two.setkey(key) - if addresses: - net_two.addrconfig(addresses) - - # physical node connected with tunnel - if not net_one and not net_two and (node_one or node_two): - if node_one and nodeutils.is_node(node_one, NodeTypes.PHYSICAL): - logging.info("adding link for physical node: %s", node_one.name) - addresses = interface_one.get_addresses() - node_one.adoptnetif(tunnel, interface_one.id, interface_one.mac, addresses) - link_config(node_one, tunnel, link_options) - elif node_two and nodeutils.is_node(node_two, NodeTypes.PHYSICAL): - logging.info("adding link for physical node: %s", node_two.name) - addresses = interface_two.get_addresses() - node_two.adoptnetif(tunnel, interface_two.id, interface_two.mac, addresses) - link_config(node_two, tunnel, link_options) - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() - - def delete_link(self, node_one_id, node_two_id, interface_one_id, interface_two_id, link_type=LinkTypes.WIRED): - """ - Delete a link between nodes. - - :param int node_one_id: node one id - :param int node_two_id: node two id - :param int interface_one_id: interface id for node one - :param int interface_two_id: interface id for node two - :param core.enumerations.LinkTypes link_type: link type to delete - :return: nothing - """ - # get node objects identified by link data - node_one, node_two, net_one, net_two, _tunnel = self._link_nodes(node_one_id, node_two_id) - - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - try: - # wireless link - if link_type == LinkTypes.WIRELESS: - objects = [node_one, node_two, net_one, net_two] - self._link_wireless(objects, connect=False) - # wired link - else: - if all([node_one, node_two]): - # TODO: fix this for the case where ifindex[1,2] are not specified - # a wired unlink event, delete the connecting bridge - interface_one = node_one.netif(interface_one_id) - interface_two = node_two.netif(interface_two_id) - - # get interfaces from common network, if no network node - # otherwise get interfaces between a node and network - if not interface_one and not interface_two: - common_networks = node_one.commonnets(node_two) - for network, common_interface_one, common_interface_two in common_networks: - if (net_one and network == net_one) or not net_one: - interface_one = common_interface_one - interface_two = common_interface_two - break - - if all([interface_one, interface_two]) and any([interface_one.net, interface_two.net]): - if interface_one.net != interface_two.net and all([interface_one.up, interface_two.up]): - raise ValueError("no common network found") - - logging.info("deleting link node(%s):interface(%s) node(%s):interface(%s)", - node_one.name, interface_one.name, node_two.name, interface_two.name) - net_one = interface_one.net - interface_one.detachnet() - interface_two.detachnet() - if net_one.numnetif() == 0: - self.delete_node(net_one.id) - node_one.delnetif(interface_one.netindex) - node_two.delnetif(interface_two.netindex) - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() - - def update_link(self, node_one_id, node_two_id, interface_one_id=None, interface_two_id=None, - link_options=LinkOptions()): - """ - Update link information between nodes. - - :param int node_one_id: node one id - :param int node_two_id: node two id - :param int interface_one_id: interface id for node one - :param int interface_two_id: interface id for node two - :param core.emulator.emudata.LinkOptions link_options: data to update link with - :return: nothing - """ - # get node objects identified by link data - node_one, node_two, net_one, net_two, _tunnel = self._link_nodes(node_one_id, node_two_id) - - if node_one: - node_one.lock.acquire() - if node_two: - node_two.lock.acquire() - - try: - # wireless link - if link_options.type == LinkTypes.WIRELESS.value: - raise ValueError("cannot update wireless link") - else: - if not node_one and not node_two: - if net_one and net_two: - # modify link between nets - interface = net_one.getlinknetif(net_two) - upstream = False - - if not interface: - upstream = True - interface = net_two.getlinknetif(net_one) - - if not interface: - raise ValueError("modify unknown link between nets") - - if upstream: - interface.swapparams("_params_up") - link_config(net_one, interface, link_options, devname=interface.name) - interface.swapparams("_params_up") - else: - link_config(net_one, interface, link_options) - - if not link_options.unidirectional: - if upstream: - link_config(net_two, interface, link_options) - else: - interface.swapparams("_params_up") - link_config(net_two, interface, link_options, devname=interface.name) - interface.swapparams("_params_up") - else: - raise ValueError("modify link for unknown nodes") - elif not node_one: - # node1 = layer 2node, node2 = layer3 node - interface = node_two.netif(interface_two_id, net_one) - link_config(net_one, interface, link_options) - elif not node_two: - # node2 = layer 2node, node1 = layer3 node - interface = node_one.netif(interface_one_id, net_one) - link_config(net_one, interface, link_options) - else: - common_networks = node_one.commonnets(node_two) - if not common_networks: - raise ValueError("no common network found") - - for net_one, interface_one, interface_two in common_networks: - if interface_one_id is not None and interface_one_id != node_one.getifindex(interface_one): - continue - - link_config(net_one, interface_one, link_options, interface_two=interface_two) - if not link_options.unidirectional: - link_config(net_one, interface_two, link_options, interface_two=interface_one) - - finally: - if node_one: - node_one.lock.release() - if node_two: - node_two.lock.release() - - def add_node(self, _type=NodeTypes.DEFAULT, _id=None, node_options=NodeOptions()): - """ - Add a node to the session, based on the provided node data. - - :param core.enumerations.NodeTypes _type: type of node to create - :param int _id: id for node, defaults to None for generated id - :param core.emulator.emudata.NodeOptions node_options: data to create node with - :return: created node - """ - - # retrieve node class for given node type - try: - node_class = nodeutils.get_node_class(_type) - except KeyError: - logging.error("invalid node type to create: %s", _type) - return None - - # set node start based on current session state, override and check when rj45 - start = self.state > EventTypes.DEFINITION_STATE.value - enable_rj45 = self.options.get_config("enablerj45") == "1" - if _type == NodeTypes.RJ45 and not enable_rj45: - start = False - - # determine node id - if not _id: - while True: - _id = self.node_id_gen.next() - if _id not in self.nodes: - break - - # generate name if not provided - name = node_options.name - if not name: - name = "%s%s" % (node_class.__name__, _id) - - # create node - logging.info("creating node(%s) id(%s) name(%s) start(%s)", node_class.__name__, _id, name, start) - node = self.create_node(cls=node_class, _id=_id, name=name, start=start) - - # set node attributes - node.icon = node_options.icon - node.canvas = node_options.canvas - node.opaque = node_options.opaque - - # set node position and broadcast it - self.set_node_position(node, node_options) - - # add services to default and physical nodes only - if _type in [NodeTypes.DEFAULT, NodeTypes.PHYSICAL]: - node.type = node_options.model - logging.debug("set node type: %s", node.type) - self.services.add_services(node, node.type, node_options.services) - - # boot nodes if created after runtime, LcxNodes, Physical, and RJ45 are all PyCoreNodes - is_boot_node = isinstance(node, CoreNodeBase) and not nodeutils.is_node(node, NodeTypes.RJ45) - if self.state == EventTypes.RUNTIME_STATE.value and is_boot_node: - self.write_nodes() - self.add_remove_control_interface(node=node, remove=False) - self.services.boot_services(node) - - return node - - def update_node(self, node_id, node_options): - """ - Update node information. - - :param int node_id: id of node to update - :param core.emulator.emudata.NodeOptions node_options: data to update node with - :return: True if node updated, False otherwise - :rtype: bool - """ - result = False - try: - # get node to update - node = self.get_node(node_id) - - # set node position and broadcast it - self.set_node_position(node, node_options) - - # update attributes - node.canvas = node_options.canvas - node.icon = node_options.icon - - # set node as updated successfully - result = True - except KeyError: - logging.error("failure to update node that does not exist: %s", node_id) - - return result - - def set_node_position(self, node, node_options): - """ - Set position for a node, use lat/lon/alt if needed. - - :param node: node to set position for - :param core.emulator.emudata.NodeOptions node_options: data for node - :return: nothing - """ - # extract location values - x = node_options.x - y = node_options.y - lat = node_options.lat - lon = node_options.lon - alt = node_options.alt - - # check if we need to generate position from lat/lon/alt - has_empty_position = all(i is None for i in [x, y]) - has_lat_lon_alt = all(i is not None for i in [lat, lon, alt]) - using_lat_lon_alt = has_empty_position and has_lat_lon_alt - if using_lat_lon_alt: - x, y, _ = self.location.getxyz(lat, lon, alt) - - # set position and broadcast - if None not in [x, y]: - node.setposition(x, y, None) - - # broadcast updated location when using lat/lon/alt - if using_lat_lon_alt: - self.broadcast_node_location(node) - - def broadcast_node_location(self, node): - """ - Broadcast node location to all listeners. - - :param core.netns.nodes.PyCoreObj node: node to broadcast location for - :return: nothing - """ - node_data = NodeData( - message_type=0, - id=node.id, - x_position=node.position.x, - y_position=node.position.y - ) - self.broadcast_node(node_data) - - def start_mobility(self, node_ids=None): - """ - Start mobility for the provided node ids. - - :param list[int] node_ids: nodes to start mobility for - :return: nothing - """ - self.mobility.startup(node_ids) - - def shutdown(self): - """ - Shutdown session. - - :return: nothing - """ - logging.info("session(%s) shutting down", self.id) - self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True) - self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True) - super(EmuSession, self).shutdown() - - def is_active(self): - """ - Determine if this session is considered to be active. (Runtime or Data collect states) - - :return: True if active, False otherwise - """ - result = self.state in {EventTypes.RUNTIME_STATE.value, EventTypes.DATACOLLECT_STATE.value} - logging.info("session(%s) checking if active: %s", self.id, result) - return result - - def open_xml(self, file_name, start=False): - """ - Import a session from the EmulationScript XML format. - - :param str file_name: xml file to load session from - :param bool start: instantiate session if true, false otherwise - :return: nothing - """ - # clear out existing session - self.clear() - - # write out xml file - CoreXmlReader(self).read(file_name) - - # start session if needed - if start: - self.name = os.path.basename(file_name) - self.file_name = file_name - self.instantiate() - - def save_xml(self, file_name): - """ - Export a session to the EmulationScript XML format. - - :param str file_name: file name to write session xml to - :return: nothing - """ - CoreXmlWriter(self).write(file_name) - - def add_hook(self, state, file_name, source_name, data): - """ - Store a hook from a received file message. - - :param int state: when to run hook - :param str file_name: file name for hook - :param str source_name: source name - :param data: hook data - :return: nothing - """ - # hack to conform with old logic until updated - state = ":%s" % state - self.set_hook(state, file_name, source_name, data) - - def add_node_file(self, node_id, source_name, file_name, data): - """ - Add a file to a node. - - :param int node_id: node to add file to - :param str source_name: source file name - :param str file_name: file name to add - :param str data: file data - :return: nothing - """ - - node = self.get_node(node_id) - - if source_name is not None: - node.addfile(source_name, file_name) - elif data is not None: - node.nodefile(file_name, data) - - def clear(self): - """ - Clear all CORE session data. (objects, hooks, broker) - - :return: nothing - """ - self.delete_nodes() - self.del_hooks() - self.broker.reset() - self.emane.reset() - - def start_events(self): - """ - Start event loop. - - :return: nothing - """ - self.event_loop.run() - - def mobility_event(self, event_data): - """ - Handle a mobility event. - - :param core.data.EventData event_data: event data to handle - :return: nothing - """ - self.mobility.handleevent(event_data) - - def create_wireless_node(self, _id=None, node_options=NodeOptions()): - """ - Create a wireless node for use within an wireless/EMANE networks. - - :param int _id: int for node, defaults to None and will be generated - :param core.emulator.emudata.NodeOptions node_options: options for emane node, model will always be "mdr" - :return: new emane node - :rtype: core.netns.nodes.CoreNode - """ - node_options.model = "mdr" - return self.add_node(_type=NodeTypes.DEFAULT, _id=_id, node_options=node_options) - - def create_emane_network(self, model, geo_reference, geo_scale=None, node_options=NodeOptions(), config=None): - """ - Convenience method for creating an emane network. - - :param model: emane model to use for emane network - :param geo_reference: geo reference point to use for emane node locations - :param geo_scale: geo scale to use for emane node locations, defaults to 1.0 - :param core.emulator.emudata.NodeOptions node_options: options for emane node being created - :param dict config: emane model configuration - :return: create emane network - """ - # required to be set for emane to function properly - self.location.setrefgeo(*geo_reference) - if geo_scale: - self.location.refscale = geo_scale - - # create and return network - emane_network = self.add_node(_type=NodeTypes.EMANE, node_options=node_options) - self.emane.set_model(emane_network, model, config) - return emane_network - - class CoreEmu(object): """ Provides logic for creating and configuring CORE sessions and the nodes within them. @@ -762,7 +51,7 @@ class CoreEmu(object): self.config = config # session management - self.session_id_gen = IdGen(_id=59999) + self.session_id_gen = IdGen(_id=0) self.sessions = {} # set default nodes @@ -807,33 +96,31 @@ class CoreEmu(object): logging.info("shutting down all sessions") sessions = self.sessions.copy() self.sessions.clear() - for session in sessions.itervalues(): + for _id in sessions: + session = sessions[_id] session.shutdown() - def create_session(self, _id=None, master=True, _cls=EmuSession): + def create_session(self, _id=None, master=True): """ Create a new CORE session, set to master if running standalone. :param int _id: session id for new session :param bool master: sets session to master - :param class _cls: EmuSession class to use :return: created session :rtype: EmuSession """ - - session_id = _id - if not session_id: + if not _id: while True: - session_id = self.session_id_gen.next() - if session_id not in self.sessions: + _id = self.session_id_gen.next() + if _id not in self.sessions: break - session = _cls(session_id, config=self.config) - logging.info("created session: %s", session_id) + session = Session(_id, config=self.config) + logging.info("created session: %s", _id) if master: session.master = True - self.sessions[session_id] = session + self.sessions[_id] = session return session def delete_session(self, _id): diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 6e5ebbfe..a46f548a 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -1,9 +1,79 @@ from core.emulator.enumerations import LinkTypes +from core.emulator.enumerations import NodeTypes +from core.nodes import nodeutils +from core.nodes.base import CoreNetworkBase from core.nodes.ipaddress import Ipv4Prefix from core.nodes.ipaddress import Ipv6Prefix from core.nodes.ipaddress import MacAddress +class IdGen(object): + def __init__(self, _id=0): + self.id = _id + + def next(self): + self.id += 1 + return self.id + + +def is_net_node(node): + """ + Convenience method for testing if a legacy core node is considered a network node. + + :param object node: object to test against + :return: True if object is an instance of a network node, False otherwise + :rtype: bool + """ + return isinstance(node, CoreNetworkBase) + + +def create_interface(node, network, interface_data): + """ + Create an interface for a node on a network using provided interface data. + + :param node: node to create interface for + :param network: network to associate interface with + :param core.emulator.emudata.InterfaceData interface_data: interface data + :return: created interface + """ + node.newnetif( + network, + addrlist=interface_data.get_addresses(), + hwaddr=interface_data.mac, + ifindex=interface_data.id, + ifname=interface_data.name + ) + return node.netif(interface_data.id, network) + + +def link_config(network, interface, link_options, devname=None, interface_two=None): + """ + Convenience method for configuring a link, + + :param network: network to configure link for + :param interface: interface to configure + :param core.emulator.emudata.LinkOptions link_options: data to configure link with + :param str devname: device name, default is None + :param interface_two: other interface associated, default is None + :return: nothing + """ + config = { + "netif": interface, + "bw": link_options.bandwidth, + "delay": link_options.delay, + "loss": link_options.per, + "duplicate": link_options.dup, + "jitter": link_options.jitter, + "netif2": interface_two + } + + # hacky check here, because physical and emane nodes do not conform to the same linkconfig interface + if not nodeutils.is_node(network, [NodeTypes.EMANE, NodeTypes.PHYSICAL]): + config["devname"] = devname + + network.linkconfig(**config) + + class NodeOptions(object): """ Options for creating and updating nodes within core. diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index b4ca45d1..46c5ffc7 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -15,20 +15,23 @@ import time from multiprocessing.pool import ThreadPool import core.nodes.base -from core import constants, utils +from core import constants +from core import utils from core.api.tlv import coreapi from core.api.tlv.broker import CoreBroker -from core.config import ConfigurableManager -from core.config import ConfigurableOptions -from core.config import Configuration from core.emane.emanemanager import EmaneManager -from core.emulator.data import EventData +from core.emulator.data import EventData, NodeData from core.emulator.data import ExceptionData -from core.emulator.enumerations import ConfigDataTypes -from core.emulator.enumerations import EventTypes +from core.emulator.emudata import LinkOptions, NodeOptions +from core.emulator.emudata import IdGen +from core.emulator.emudata import is_net_node +from core.emulator.emudata import create_interface +from core.emulator.emudata import link_config +from core.emulator.enumerations import EventTypes, LinkTypes from core.emulator.enumerations import ExceptionLevels from core.emulator.enumerations import NodeTypes -from core.emulator.enumerations import RegisterTlvs +from core.emulator.sessionconfig import SessionConfig +from core.emulator.sessionconfig import SessionMetaData from core.location.corelocation import CoreLocation from core.location.event import EventLoop from core.location.mobility import MobilityManager @@ -37,7 +40,9 @@ from core.nodes.base import CoreNodeBase from core.nodes.ipaddress import MacAddress from core.plugins.sdt import Sdt from core.services.coreservices import CoreServices -from core.xml import corexml, corexmldeployment +from core.xml import corexml +from core.xml import corexmldeployment +from core.xml.corexml import CoreXmlReader, CoreXmlWriter class Session(object): @@ -54,6 +59,7 @@ class Session(object): :param bool mkdir: flag to determine if a directory should be made """ self.id = _id + self.master = False # define and create session directory when desired self.session_dir = os.path.join(tempfile.gettempdir(), "pycore.%s" % self.id) @@ -67,6 +73,7 @@ class Session(object): self.event_loop = EventLoop() # dict of nodes: all nodes and nets + self.node_id_gen = IdGen() self.nodes = {} self._nodes_lock = threading.Lock() @@ -75,13 +82,11 @@ class Session(object): self._state_time = time.time() self._state_file = os.path.join(self.session_dir, "state") + # hooks handlers self._hooks = {} self._state_hooks = {} - self.add_state_hook(state=EventTypes.RUNTIME_STATE.value, hook=self.runtime_state_hook) - self.master = False - # handlers for broadcasting information self.event_handlers = [] self.exception_handlers = [] @@ -107,10 +112,629 @@ class Session(object): self.emane = EmaneManager(session=self) self.sdt = Sdt(session=self) + # initialize default node services + self.services.default_services = { + "mdr": ("zebra", "OSPFv3MDR", "IPForward"), + "PC": ("DefaultRoute",), + "prouter": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), + "router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"), + "host": ("DefaultRoute", "SSH"), + } + + def _link_nodes(self, node_one_id, node_two_id): + """ + Convenience method for retrieving nodes within link data. + + :param int node_one_id: node one id + :param int node_two_id: node two id + :return: nodes, network nodes if present, and tunnel if present + :rtype: tuple + """ + logging.debug("link message between node1(%s) and node2(%s)", node_one_id, node_two_id) + + # values to fill + net_one = None + net_two = None + + # retrieve node one + node_one = self.get_node(node_one_id) + node_two = self.get_node(node_two_id) + + # both node ids are provided + tunnel = self.broker.gettunnel(node_one_id, node_two_id) + logging.debug("tunnel between nodes: %s", tunnel) + if nodeutils.is_node(tunnel, NodeTypes.TAP_BRIDGE): + net_one = tunnel + if tunnel.remotenum == node_one_id: + node_one = None + else: + node_two = None + # physical node connected via gre tap tunnel + elif tunnel: + if tunnel.remotenum == node_one_id: + node_one = None + else: + node_two = None + + if is_net_node(node_one): + if not net_one: + net_one = node_one + else: + net_two = node_one + node_one = None + + if is_net_node(node_two): + if not net_one: + net_one = node_two + else: + net_two = node_two + node_two = None + + logging.debug("link node types n1(%s) n2(%s) net1(%s) net2(%s) tunnel(%s)", + node_one, node_two, net_one, net_two, tunnel) + return node_one, node_two, net_one, net_two, tunnel + + # TODO: this doesn't appear to ever be used, EMANE or basic wireless range + def _link_wireless(self, objects, connect): + """ + Objects to deal with when connecting/disconnecting wireless links. + + :param list objects: possible objects to deal with + :param bool connect: link interfaces if True, unlink otherwise + :return: nothing + """ + objects = [x for x in objects if x] + if len(objects) < 2: + raise ValueError("wireless link failure: %s", objects) + logging.debug("handling wireless linking objects(%s) connect(%s)", objects, connect) + common_networks = objects[0].commonnets(objects[1]) + if not common_networks: + raise ValueError("no common network found for wireless link/unlink") + + for common_network, interface_one, interface_two in common_networks: + if not nodeutils.is_node(common_network, [NodeTypes.WIRELESS_LAN, NodeTypes.EMANE]): + logging.info("skipping common network that is not wireless/emane: %s", common_network) + continue + + logging.info("wireless linking connect(%s): %s - %s", connect, interface_one, interface_two) + if connect: + common_network.link(interface_one, interface_two) + else: + common_network.unlink(interface_one, interface_two) + + def add_link(self, node_one_id, node_two_id, interface_one=None, interface_two=None, link_options=None): + """ + Add a link between nodes. + + :param int node_one_id: node one id + :param int node_two_id: node two id + :param core.emulator.emudata.InterfaceData interface_one: node one interface data, defaults to none + :param core.emulator.emudata.InterfaceData interface_two: node two interface data, defaults to none + :param core.emulator.emudata.LinkOptions link_options: data for creating link, defaults to no options + :return: + """ + if not link_options: + link_options = LinkOptions() + + # get node objects identified by link data + node_one, node_two, net_one, net_two, tunnel = self._link_nodes(node_one_id, node_two_id) + + if node_one: + node_one.lock.acquire() + if node_two: + node_two.lock.acquire() + + try: + # wireless link + if link_options.type == LinkTypes.WIRELESS: + objects = [node_one, node_two, net_one, net_two] + self._link_wireless(objects, connect=True) + # wired link + else: + # 2 nodes being linked, ptp network + if all([node_one, node_two]) and not net_one: + logging.info("adding link for peer to peer nodes: %s - %s", node_one.name, node_two.name) + ptp_class = nodeutils.get_node_class(NodeTypes.PEER_TO_PEER) + start = self.state > EventTypes.DEFINITION_STATE.value + net_one = self.create_node(cls=ptp_class, start=start) + + # node to network + if node_one and net_one: + logging.info("adding link from node to network: %s - %s", node_one.name, net_one.name) + interface = create_interface(node_one, net_one, interface_one) + link_config(net_one, interface, link_options) + + # network to node + if node_two and net_one: + logging.info("adding link from network to node: %s - %s", node_two.name, net_one.name) + interface = create_interface(node_two, net_one, interface_two) + if not link_options.unidirectional: + link_config(net_one, interface, link_options) + + # network to network + if net_one and net_two: + logging.info("adding link from network to network: %s - %s", net_one.name, net_two.name) + if nodeutils.is_node(net_two, NodeTypes.RJ45): + interface = net_two.linknet(net_one) + else: + interface = net_one.linknet(net_two) + + link_config(net_one, interface, link_options) + + if not link_options.unidirectional: + interface.swapparams("_params_up") + link_config(net_two, interface, link_options, devname=interface.name) + interface.swapparams("_params_up") + + # a tunnel node was found for the nodes + addresses = [] + if not node_one and all([net_one, interface_one]): + addresses.extend(interface_one.get_addresses()) + + if not node_two and all([net_two, interface_two]): + addresses.extend(interface_two.get_addresses()) + + # tunnel node logic + key = link_options.key + if key and nodeutils.is_node(net_one, NodeTypes.TUNNEL): + logging.info("setting tunnel key for: %s", net_one.name) + net_one.setkey(key) + if addresses: + net_one.addrconfig(addresses) + if key and nodeutils.is_node(net_two, NodeTypes.TUNNEL): + logging.info("setting tunnel key for: %s", net_two.name) + net_two.setkey(key) + if addresses: + net_two.addrconfig(addresses) + + # physical node connected with tunnel + if not net_one and not net_two and (node_one or node_two): + if node_one and nodeutils.is_node(node_one, NodeTypes.PHYSICAL): + logging.info("adding link for physical node: %s", node_one.name) + addresses = interface_one.get_addresses() + node_one.adoptnetif(tunnel, interface_one.id, interface_one.mac, addresses) + link_config(node_one, tunnel, link_options) + elif node_two and nodeutils.is_node(node_two, NodeTypes.PHYSICAL): + logging.info("adding link for physical node: %s", node_two.name) + addresses = interface_two.get_addresses() + node_two.adoptnetif(tunnel, interface_two.id, interface_two.mac, addresses) + link_config(node_two, tunnel, link_options) + finally: + if node_one: + node_one.lock.release() + if node_two: + node_two.lock.release() + + def delete_link(self, node_one_id, node_two_id, interface_one_id, interface_two_id, link_type=LinkTypes.WIRED): + """ + Delete a link between nodes. + + :param int node_one_id: node one id + :param int node_two_id: node two id + :param int interface_one_id: interface id for node one + :param int interface_two_id: interface id for node two + :param core.enumerations.LinkTypes link_type: link type to delete + :return: nothing + """ + # get node objects identified by link data + node_one, node_two, net_one, net_two, _tunnel = self._link_nodes(node_one_id, node_two_id) + + if node_one: + node_one.lock.acquire() + if node_two: + node_two.lock.acquire() + + try: + # wireless link + if link_type == LinkTypes.WIRELESS: + objects = [node_one, node_two, net_one, net_two] + self._link_wireless(objects, connect=False) + # wired link + else: + if all([node_one, node_two]): + # TODO: fix this for the case where ifindex[1,2] are not specified + # a wired unlink event, delete the connecting bridge + interface_one = node_one.netif(interface_one_id) + interface_two = node_two.netif(interface_two_id) + + # get interfaces from common network, if no network node + # otherwise get interfaces between a node and network + if not interface_one and not interface_two: + common_networks = node_one.commonnets(node_two) + for network, common_interface_one, common_interface_two in common_networks: + if (net_one and network == net_one) or not net_one: + interface_one = common_interface_one + interface_two = common_interface_two + break + + if all([interface_one, interface_two]) and any([interface_one.net, interface_two.net]): + if interface_one.net != interface_two.net and all([interface_one.up, interface_two.up]): + raise ValueError("no common network found") + + logging.info("deleting link node(%s):interface(%s) node(%s):interface(%s)", + node_one.name, interface_one.name, node_two.name, interface_two.name) + net_one = interface_one.net + interface_one.detachnet() + interface_two.detachnet() + if net_one.numnetif() == 0: + self.delete_node(net_one.id) + node_one.delnetif(interface_one.netindex) + node_two.delnetif(interface_two.netindex) + finally: + if node_one: + node_one.lock.release() + if node_two: + node_two.lock.release() + + def update_link(self, node_one_id, node_two_id, interface_one_id=None, interface_two_id=None, + link_options=LinkOptions()): + """ + Update link information between nodes. + + :param int node_one_id: node one id + :param int node_two_id: node two id + :param int interface_one_id: interface id for node one + :param int interface_two_id: interface id for node two + :param core.emulator.emudata.LinkOptions link_options: data to update link with + :return: nothing + """ + # get node objects identified by link data + node_one, node_two, net_one, net_two, _tunnel = self._link_nodes(node_one_id, node_two_id) + + if node_one: + node_one.lock.acquire() + if node_two: + node_two.lock.acquire() + + try: + # wireless link + if link_options.type == LinkTypes.WIRELESS.value: + raise ValueError("cannot update wireless link") + else: + if not node_one and not node_two: + if net_one and net_two: + # modify link between nets + interface = net_one.getlinknetif(net_two) + upstream = False + + if not interface: + upstream = True + interface = net_two.getlinknetif(net_one) + + if not interface: + raise ValueError("modify unknown link between nets") + + if upstream: + interface.swapparams("_params_up") + link_config(net_one, interface, link_options, devname=interface.name) + interface.swapparams("_params_up") + else: + link_config(net_one, interface, link_options) + + if not link_options.unidirectional: + if upstream: + link_config(net_two, interface, link_options) + else: + interface.swapparams("_params_up") + link_config(net_two, interface, link_options, devname=interface.name) + interface.swapparams("_params_up") + else: + raise ValueError("modify link for unknown nodes") + elif not node_one: + # node1 = layer 2node, node2 = layer3 node + interface = node_two.netif(interface_two_id, net_one) + link_config(net_one, interface, link_options) + elif not node_two: + # node2 = layer 2node, node1 = layer3 node + interface = node_one.netif(interface_one_id, net_one) + link_config(net_one, interface, link_options) + else: + common_networks = node_one.commonnets(node_two) + if not common_networks: + raise ValueError("no common network found") + + for net_one, interface_one, interface_two in common_networks: + if interface_one_id is not None and interface_one_id != node_one.getifindex(interface_one): + continue + + link_config(net_one, interface_one, link_options, interface_two=interface_two) + if not link_options.unidirectional: + link_config(net_one, interface_two, link_options, interface_two=interface_one) + + finally: + if node_one: + node_one.lock.release() + if node_two: + node_two.lock.release() + + def add_node(self, _type=NodeTypes.DEFAULT, _id=None, node_options=None): + """ + Add a node to the session, based on the provided node data. + + :param core.enumerations.NodeTypes _type: type of node to create + :param int _id: id for node, defaults to None for generated id + :param core.emulator.emudata.NodeOptions node_options: data to create node with + :return: created node + """ + + # retrieve node class for given node type + try: + node_class = nodeutils.get_node_class(_type) + except KeyError: + logging.error("invalid node type to create: %s", _type) + return None + + # set node start based on current session state, override and check when rj45 + start = self.state > EventTypes.DEFINITION_STATE.value + enable_rj45 = self.options.get_config("enablerj45") == "1" + if _type == NodeTypes.RJ45 and not enable_rj45: + start = False + + # determine node id + if not _id: + while True: + _id = self.node_id_gen.next() + if _id not in self.nodes: + break + + # generate name if not provided + if not node_options: + node_options = NodeOptions() + name = node_options.name + if not name: + name = "%s%s" % (node_class.__name__, _id) + + # create node + logging.info("creating node(%s) id(%s) name(%s) start(%s)", node_class.__name__, _id, name, start) + node = self.create_node(cls=node_class, _id=_id, name=name, start=start) + + # set node attributes + node.icon = node_options.icon + node.canvas = node_options.canvas + node.opaque = node_options.opaque + + # set node position and broadcast it + self.set_node_position(node, node_options) + + # add services to default and physical nodes only + if _type in [NodeTypes.DEFAULT, NodeTypes.PHYSICAL]: + node.type = node_options.model + logging.debug("set node type: %s", node.type) + self.services.add_services(node, node.type, node_options.services) + + # boot nodes if created after runtime, LcxNodes, Physical, and RJ45 are all PyCoreNodes + is_boot_node = isinstance(node, CoreNodeBase) and not nodeutils.is_node(node, NodeTypes.RJ45) + if self.state == EventTypes.RUNTIME_STATE.value and is_boot_node: + self.write_nodes() + self.add_remove_control_interface(node=node, remove=False) + self.services.boot_services(node) + + return node + + def update_node(self, node_id, node_options): + """ + Update node information. + + :param int node_id: id of node to update + :param core.emulator.emudata.NodeOptions node_options: data to update node with + :return: True if node updated, False otherwise + :rtype: bool + """ + result = False + try: + # get node to update + node = self.get_node(node_id) + + # set node position and broadcast it + self.set_node_position(node, node_options) + + # update attributes + node.canvas = node_options.canvas + node.icon = node_options.icon + + # set node as updated successfully + result = True + except KeyError: + logging.error("failure to update node that does not exist: %s", node_id) + + return result + + def set_node_position(self, node, node_options): + """ + Set position for a node, use lat/lon/alt if needed. + + :param node: node to set position for + :param core.emulator.emudata.NodeOptions node_options: data for node + :return: nothing + """ + # extract location values + x = node_options.x + y = node_options.y + lat = node_options.lat + lon = node_options.lon + alt = node_options.alt + + # check if we need to generate position from lat/lon/alt + has_empty_position = all(i is None for i in [x, y]) + has_lat_lon_alt = all(i is not None for i in [lat, lon, alt]) + using_lat_lon_alt = has_empty_position and has_lat_lon_alt + if using_lat_lon_alt: + x, y, _ = self.location.getxyz(lat, lon, alt) + + # set position and broadcast + if None not in [x, y]: + node.setposition(x, y, None) + + # broadcast updated location when using lat/lon/alt + if using_lat_lon_alt: + self.broadcast_node_location(node) + + def broadcast_node_location(self, node): + """ + Broadcast node location to all listeners. + + :param core.netns.nodes.PyCoreObj node: node to broadcast location for + :return: nothing + """ + node_data = NodeData( + message_type=0, + id=node.id, + x_position=node.position.x, + y_position=node.position.y + ) + self.broadcast_node(node_data) + + def start_mobility(self, node_ids=None): + """ + Start mobility for the provided node ids. + + :param list[int] node_ids: nodes to start mobility for + :return: nothing + """ + self.mobility.startup(node_ids) + + def is_active(self): + """ + Determine if this session is considered to be active. (Runtime or Data collect states) + + :return: True if active, False otherwise + """ + result = self.state in {EventTypes.RUNTIME_STATE.value, EventTypes.DATACOLLECT_STATE.value} + logging.info("session(%s) checking if active: %s", self.id, result) + return result + + def open_xml(self, file_name, start=False): + """ + Import a session from the EmulationScript XML format. + + :param str file_name: xml file to load session from + :param bool start: instantiate session if true, false otherwise + :return: nothing + """ + # clear out existing session + self.clear() + + # write out xml file + CoreXmlReader(self).read(file_name) + + # start session if needed + if start: + self.name = os.path.basename(file_name) + self.file_name = file_name + self.instantiate() + + def save_xml(self, file_name): + """ + Export a session to the EmulationScript XML format. + + :param str file_name: file name to write session xml to + :return: nothing + """ + CoreXmlWriter(self).write(file_name) + + def add_hook(self, state, file_name, source_name, data): + """ + Store a hook from a received file message. + + :param int state: when to run hook + :param str file_name: file name for hook + :param str source_name: source name + :param data: hook data + :return: nothing + """ + # hack to conform with old logic until updated + state = ":%s" % state + self.set_hook(state, file_name, source_name, data) + + def add_node_file(self, node_id, source_name, file_name, data): + """ + Add a file to a node. + + :param int node_id: node to add file to + :param str source_name: source file name + :param str file_name: file name to add + :param str data: file data + :return: nothing + """ + + node = self.get_node(node_id) + + if source_name is not None: + node.addfile(source_name, file_name) + elif data is not None: + node.nodefile(file_name, data) + + def clear(self): + """ + Clear all CORE session data. (objects, hooks, broker) + + :return: nothing + """ + self.delete_nodes() + self.del_hooks() + self.broker.reset() + self.emane.reset() + + def start_events(self): + """ + Start event loop. + + :return: nothing + """ + self.event_loop.run() + + def mobility_event(self, event_data): + """ + Handle a mobility event. + + :param core.data.EventData event_data: event data to handle + :return: nothing + """ + self.mobility.handleevent(event_data) + + def create_wireless_node(self, _id=None, node_options=None): + """ + Create a wireless node for use within an wireless/EMANE networks. + + :param int _id: int for node, defaults to None and will be generated + :param core.emulator.emudata.NodeOptions node_options: options for emane node, model will always be "mdr" + :return: new emane node + :rtype: core.netns.nodes.CoreNode + """ + if not node_options: + node_options = NodeOptions() + node_options.model = "mdr" + return self.add_node(_type=NodeTypes.DEFAULT, _id=_id, node_options=node_options) + + def create_emane_network(self, model, geo_reference, geo_scale=None, node_options=NodeOptions(), config=None): + """ + Convenience method for creating an emane network. + + :param model: emane model to use for emane network + :param geo_reference: geo reference point to use for emane node locations + :param geo_scale: geo scale to use for emane node locations, defaults to 1.0 + :param core.emulator.emudata.NodeOptions node_options: options for emane node being created + :param dict config: emane model configuration + :return: create emane network + """ + # required to be set for emane to function properly + self.location.setrefgeo(*geo_reference) + if geo_scale: + self.location.refscale = geo_scale + + # create and return network + emane_network = self.add_node(_type=NodeTypes.EMANE, node_options=node_options) + self.emane.set_model(emane_network, model, config) + return emane_network + def shutdown(self): """ Shutdown all session nodes and remove the session directory. """ + logging.info("session(%s) shutting down", self.id) + self.set_state(EventTypes.DATACOLLECT_STATE, send_event=True) + self.set_state(EventTypes.SHUTDOWN_STATE, send_event=True) + # shutdown/cleanup feature helpers self.emane.shutdown() self.broker.shutdown() @@ -713,13 +1337,14 @@ class Session(object): results = [] start = time.time() - for obj in self.nodes.itervalues(): + for _id in self.nodes: + node = self.nodes[_id] # TODO: PyCoreNode is not the type to check - if isinstance(obj, CoreNodeBase) and not nodeutils.is_node(obj, NodeTypes.RJ45): + if isinstance(node, CoreNodeBase) and not nodeutils.is_node(node, NodeTypes.RJ45): # add a control interface if configured - logging.info("booting node: %s", obj.name) - self.add_remove_control_interface(node=obj, remove=False) - result = pool.apply_async(self.services.boot_services, (obj,)) + logging.info("booting node: %s", node.name) + self.add_remove_control_interface(node=node, remove=False) + result = pool.apply_async(self.services.boot_services, (node,)) results.append(result) pool.close() @@ -992,9 +1617,9 @@ class Session(object): event_time = float(event_time) current_time = self.runtime() - if current_time > 0.0: - if time <= current_time: - logging.warn("could not schedule past event for time %s (run time is now %s)", time, current_time) + if current_time > 0: + if event_time <= current_time: + logging.warn("could not schedule past event for time %s (run time is now %s)", event_time, current_time) return event_time = event_time - current_time @@ -1018,65 +1643,9 @@ class Session(object): if not name: name = "" - logging.info("running event %s at time %s cmd=%s" % (name, now, data)) + logging.info("running event %s at time %s cmd=%s", name, now, data) if not node_id: utils.mute_detach(data) else: node = self.get_node(node_id) node.cmd(data, wait=False) - - -class SessionConfig(ConfigurableManager, ConfigurableOptions): - """ - Provides session configuration. - """ - name = "session" - options = [ - Configuration(_id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network"), - Configuration(_id="controlnet0", _type=ConfigDataTypes.STRING, label="Control Network 0"), - Configuration(_id="controlnet1", _type=ConfigDataTypes.STRING, label="Control Network 1"), - Configuration(_id="controlnet2", _type=ConfigDataTypes.STRING, label="Control Network 2"), - Configuration(_id="controlnet3", _type=ConfigDataTypes.STRING, label="Control Network 3"), - Configuration(_id="controlnet_updown_script", _type=ConfigDataTypes.STRING, label="Control Network Script"), - Configuration(_id="enablerj45", _type=ConfigDataTypes.BOOL, default="1", options=["On", "Off"], - label="Enable RJ45s"), - Configuration(_id="preservedir", _type=ConfigDataTypes.BOOL, default="0", options=["On", "Off"], - label="Preserve session dir"), - Configuration(_id="enablesdt", _type=ConfigDataTypes.BOOL, default="0", options=["On", "Off"], - label="Enable SDT3D output"), - Configuration(_id="sdturl", _type=ConfigDataTypes.STRING, default=Sdt.DEFAULT_SDT_URL, label="SDT3D URL") - ] - config_type = RegisterTlvs.UTILITY.value - - def __init__(self): - super(SessionConfig, self).__init__() - self.set_configs(self.default_values()) - - def get_config(self, _id, node_id=ConfigurableManager._default_node, - config_type=ConfigurableManager._default_type, default=None): - value = super(SessionConfig, self).get_config(_id, node_id, config_type, default) - if value == "": - value = default - return value - - def get_config_bool(self, name, default=None): - value = self.get_config(name) - if value is None: - return default - return value.lower() == "true" - - def get_config_int(self, name, default=None): - value = self.get_config(name, default=default) - if value is not None: - value = int(value) - return value - - -class SessionMetaData(ConfigurableManager): - """ - Metadata is simply stored in a configs[] dict. Key=value pairs are - passed in from configure messages destined to the "metadata" object. - The data is not otherwise interpreted or processed. - """ - name = "metadata" - config_type = RegisterTlvs.UTILITY.value diff --git a/daemon/core/emulator/sessionconfig.py b/daemon/core/emulator/sessionconfig.py new file mode 100644 index 00000000..888b7df8 --- /dev/null +++ b/daemon/core/emulator/sessionconfig.py @@ -0,0 +1,62 @@ +from core.config import ConfigurableManager +from core.config import ConfigurableOptions +from core.config import Configuration +from core.emulator.enumerations import ConfigDataTypes +from core.emulator.enumerations import RegisterTlvs +from core.plugins.sdt import Sdt + + +class SessionConfig(ConfigurableManager, ConfigurableOptions): + """ + Provides session configuration. + """ + name = "session" + options = [ + Configuration(_id="controlnet", _type=ConfigDataTypes.STRING, label="Control Network"), + Configuration(_id="controlnet0", _type=ConfigDataTypes.STRING, label="Control Network 0"), + Configuration(_id="controlnet1", _type=ConfigDataTypes.STRING, label="Control Network 1"), + Configuration(_id="controlnet2", _type=ConfigDataTypes.STRING, label="Control Network 2"), + Configuration(_id="controlnet3", _type=ConfigDataTypes.STRING, label="Control Network 3"), + Configuration(_id="controlnet_updown_script", _type=ConfigDataTypes.STRING, label="Control Network Script"), + Configuration(_id="enablerj45", _type=ConfigDataTypes.BOOL, default="1", options=["On", "Off"], + label="Enable RJ45s"), + Configuration(_id="preservedir", _type=ConfigDataTypes.BOOL, default="0", options=["On", "Off"], + label="Preserve session dir"), + Configuration(_id="enablesdt", _type=ConfigDataTypes.BOOL, default="0", options=["On", "Off"], + label="Enable SDT3D output"), + Configuration(_id="sdturl", _type=ConfigDataTypes.STRING, default=Sdt.DEFAULT_SDT_URL, label="SDT3D URL") + ] + config_type = RegisterTlvs.UTILITY.value + + def __init__(self): + super(SessionConfig, self).__init__() + self.set_configs(self.default_values()) + + def get_config(self, _id, node_id=ConfigurableManager._default_node, + config_type=ConfigurableManager._default_type, default=None): + value = super(SessionConfig, self).get_config(_id, node_id, config_type, default) + if value == "": + value = default + return value + + def get_config_bool(self, name, default=None): + value = self.get_config(name) + if value is None: + return default + return value.lower() == "true" + + def get_config_int(self, name, default=None): + value = self.get_config(name, default=default) + if value is not None: + value = int(value) + return value + + +class SessionMetaData(ConfigurableManager): + """ + Metadata is simply stored in a configs[] dict. Key=value pairs are + passed in from configure messages destined to the "metadata" object. + The data is not otherwise interpreted or processed. + """ + name = "metadata" + config_type = RegisterTlvs.UTILITY.value From 41a78397e3a5263808c018963c6af9f812baef3b Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Sat, 4 May 2019 22:44:41 -0700 Subject: [PATCH 0006/1992] initial changes to support python2/3 building for netns --- netns/netnsmodule.c | 36 +++++++++++++++++++++++++++++++++--- netns/vcmdmodule.c | 40 +++++++++++++++++++++++++++++++--------- 2 files changed, 64 insertions(+), 12 deletions(-) diff --git a/netns/netnsmodule.c b/netns/netnsmodule.c index 93222322..f491af16 100644 --- a/netns/netnsmodule.c +++ b/netns/netnsmodule.c @@ -82,7 +82,11 @@ static PyObject *netns_nsexecvp(PyObject *self, PyObject *args) if (pid < 0) return PyErr_SetFromErrno(PyExc_OSError); else +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong(pid); +#else return PyInt_FromLong(pid); +#endif } static PyObject *netns_nsfork(PyObject *self, PyObject *args) @@ -100,7 +104,11 @@ static PyObject *netns_nsfork(PyObject *self, PyObject *args) if (pid == 0) /* child */ PyOS_AfterFork(); +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong(pid); +#else return PyInt_FromLong(pid); +#endif } static PyMethodDef netns_methods[] = { @@ -120,13 +128,35 @@ static PyMethodDef netns_methods[] = { {NULL, NULL, 0, NULL}, }; +#if PY_MAJOR_VERSION >= 3 +#define INITERROR return NULL + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "netns", /* m_name */ + "netns module", /* m_doc */ + -1, /* m_size */ + netns_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ + }; +#else +#define INITERROR return +#endif + PyMODINIT_FUNC initnetns(void) { PyObject *m; - m = Py_InitModule("netns", netns_methods); + #if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&moduledef); + #else + m = Py_InitModule("netns", netns_methods); + #endif + if (m == NULL) - return; + INITERROR; #define MODADDINT(x) \ do { \ @@ -142,5 +172,5 @@ PyMODINIT_FUNC initnetns(void) #undef MODADDINT - return; + INITERROR; } diff --git a/netns/vcmdmodule.c b/netns/vcmdmodule.c index ed76bccd..7f0faada 100644 --- a/netns/vcmdmodule.c +++ b/netns/vcmdmodule.c @@ -236,7 +236,7 @@ static void VCmdWait_dealloc(VCmdWait *self) if (self->_vcmd != NULL) Py_DECREF(self->_vcmd); - self->ob_type->tp_free((PyObject *)self); + Py_TYPE(self)->tp_free((PyObject *)self); return; } @@ -363,7 +363,7 @@ static PyMethodDef VCmdWait_methods[] = { }; static PyTypeObject vcmd_VCmdWaitType = { - PyObject_HEAD_INIT(NULL) + PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "vcmd.VCmdWait", .tp_basicsize = sizeof(VCmdWait), .tp_dealloc = (destructor)VCmdWait_dealloc, @@ -491,7 +491,7 @@ static void VCmd_dealloc(VCmd *self) call_asyncfunc(async_delclientreq, &delclreq); } - self->ob_type->tp_free((PyObject *)self); + Py_TYPE(self)->tp_free((PyObject *)self); return; } @@ -902,7 +902,7 @@ static PyMethodDef VCmd_methods[] = { }; static PyTypeObject vcmd_VCmdType = { - PyObject_HEAD_INIT(NULL) + PyVarObject_HEAD_INIT(NULL, 0) .tp_name = "vcmd.VCmd", .tp_basicsize = sizeof(VCmd), .tp_dealloc = (destructor)VCmd_dealloc, @@ -932,19 +932,41 @@ static PyMethodDef vcmd_methods[] = { {NULL, NULL, 0, NULL}, }; +#if PY_MAJOR_VERSION >= 3 +#define INITERROR return NULL + static struct PyModuleDef moduledef = { + PyModuleDef_HEAD_INIT, + "vcmd", /* m_name */ + "vcmd module that does stuff...", /* m_doc */ + -1, /* m_size */ + vcmd_methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ + }; +#else +#define INITERROR return +#endif + PyMODINIT_FUNC initvcmd(void) { PyObject *m; if (PyType_Ready(&vcmd_VCmdType) < 0) - return; + INITERROR; if (PyType_Ready(&vcmd_VCmdWaitType) < 0) - return; + INITERROR; - m = Py_InitModule3("vcmd", vcmd_methods, "vcmd module that does stuff..."); + #if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&moduledef); + #else + m = Py_InitModule3("vcmd", vcmd_methods, "vcmd module that does stuff..."); + #endif + if (!m) - return; + INITERROR; Py_INCREF(&vcmd_VCmdType); PyModule_AddObject(m, "VCmd", (PyObject *)&vcmd_VCmdType); @@ -952,5 +974,5 @@ PyMODINIT_FUNC initvcmd(void) Py_INCREF(&vcmd_VCmdWaitType); PyModule_AddObject(m, "VCmdWait", (PyObject *)&vcmd_VCmdWaitType); - return; + INITERROR; } From b7534f7cc58a1187e870b85978e01c7c50b9e514 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Sat, 4 May 2019 23:38:55 -0700 Subject: [PATCH 0007/1992] updated netns builds to properly place data_files from setup.py --- netns/Makefile.am | 1 + netns/setup.py.in | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/netns/Makefile.am b/netns/Makefile.am index d000cb8e..53117d9d 100644 --- a/netns/Makefile.am +++ b/netns/Makefile.am @@ -42,6 +42,7 @@ install-exec-local: --root=/$(DESTDIR) \ --prefix=$(prefix) \ --install-lib=$(pythondir) \ + --install-data=$(prefix) \ --single-version-externally-managed \ --no-compile diff --git a/netns/setup.py.in b/netns/setup.py.in index 0866fe5c..917923c4 100644 --- a/netns/setup.py.in +++ b/netns/setup.py.in @@ -31,7 +31,7 @@ setup( name="core-netns", version="@PACKAGE_VERSION@", description="Extension modules to support virtual nodes using Linux network namespaces", - scripts=["vcmd", "vnoded", "netns"], + data_files=[("bin", ["vcmd", "vnoded", "netns"])], ext_modules=[ netns, vcmd From e58cbe94211571510371b4fa307d6ac4aa84c0f8 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 5 May 2019 16:19:12 -0700 Subject: [PATCH 0008/1992] added future library to support python2/3, updated xrange calls to leverage builtins range --- daemon/core/api/tlv/corehandlers.py | 5 +++-- daemon/core/nodes/base.py | 7 ++++--- daemon/core/nodes/interface.py | 3 ++- daemon/core/nodes/ipaddress.py | 7 ++++--- daemon/examples/api/emane80211.py | 3 ++- daemon/examples/api/switch.py | 3 ++- daemon/examples/api/switch_inject.py | 4 +++- daemon/examples/api/wlan.py | 3 ++- daemon/examples/grpc/switch.py | 3 ++- daemon/examples/netns/daemonnodes.py | 8 ++++---- daemon/examples/netns/distributed.py | 9 +++++---- daemon/examples/netns/ospfmanetmdrtest.py | 15 ++++++++------- daemon/examples/netns/wlanemanetests.py | 12 ++++++------ daemon/requirements.txt | 3 ++- daemon/setup.py.in | 1 + 15 files changed, 50 insertions(+), 36 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 90436806..3c33444b 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -11,8 +11,10 @@ import shutil import sys import threading import time +from builtins import range from itertools import repeat +from core import utils from core.api.tlv import coreapi, dataconversion, structutils from core.config import ConfigShim from core.emulator.data import ConfigData, ExceptionData @@ -38,7 +40,6 @@ from core.emulator.enumerations import NodeTypes from core.emulator.enumerations import RegisterTlvs from core.emulator.enumerations import SessionTlvs from core.nodes import nodeutils -from core import utils from core.services.coreservices import ServiceManager from core.services.coreservices import ServiceShim @@ -79,7 +80,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): raise ValueError("invalid number of threads: %s" % num_threads) logging.debug("launching core server handler threads: %s", num_threads) - for _ in xrange(num_threads): + for _ in range(num_threads): thread = threading.Thread(target=self.handler_thread) self.handler_threads.append(thread) thread.start() diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index a5d5a70a..49e54a99 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -11,16 +11,17 @@ import signal import socket import string import threading +from builtins import range from socket import AF_INET, AF_INET6 from core import CoreCommandError, utils from core import constants from core.emulator.data import NodeData, LinkData from core.emulator.enumerations import NodeTypes, LinkTypes -from core.nodes.ipaddress import MacAddress from core.nodes import client, nodeutils, ipaddress from core.nodes.interface import TunTap, CoreInterface from core.nodes.interface import Veth +from core.nodes.ipaddress import MacAddress _DEFAULT_MTU = 1500 @@ -868,8 +869,8 @@ class CoreNode(CoreNodeBase): :return: nothing """ tmplen = 8 - tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in xrange(tmplen)]) - tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in xrange(tmplen)]) + tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in range(tmplen)]) + tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase) for _ in range(tmplen)]) utils.check_cmd([constants.IP_BIN, "link", "add", "name", tmp1, "type", "veth", "peer", "name", tmp2]) utils.check_cmd([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)]) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 8d5df152..14ab64ab 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -4,6 +4,7 @@ virtual ethernet classes that implement the interfaces available under Linux. import logging import time +from builtins import range from core import CoreCommandError, utils from core import constants @@ -300,7 +301,7 @@ class TunTap(CoreInterface): """ delay = 0.01 result = False - for i in xrange(1, attempts + 1): + for i in range(1, attempts + 1): r = func() if r == 0: result = True diff --git a/daemon/core/nodes/ipaddress.py b/daemon/core/nodes/ipaddress.py index 4da49642..a378631a 100644 --- a/daemon/core/nodes/ipaddress.py +++ b/daemon/core/nodes/ipaddress.py @@ -6,6 +6,7 @@ import logging import random import socket import struct +from builtins import range from socket import AF_INET from socket import AF_INET6 @@ -154,7 +155,7 @@ class IpAddress(object): return NotImplemented tmp = [ord(x) for x in self.addr] - for i in xrange(len(tmp) - 1, -1, -1): + for i in range(len(tmp) - 1, -1, -1): x = tmp[i] + carry tmp[i] = x & 0xff carry = x >> 8 @@ -235,7 +236,7 @@ class IpPrefix(object): addrbits = self.addrlen - self.prefixlen netmask = ((1L << self.prefixlen) - 1) << addrbits prefix = "" - for i in xrange(-1, -(addrbits >> 3) - 2, -1): + for i in range(-1, -(addrbits >> 3) - 2, -1): prefix = chr(ord(self.prefix[i]) & (netmask & 0xff)) + prefix netmask >>= 8 self.prefix = self.prefix[:i] + prefix @@ -319,7 +320,7 @@ class IpPrefix(object): addr = "" prefix_endpoint = -1 - for i in xrange(-1, -(self.addrlen >> 3) - 1, -1): + for i in range(-1, -(self.addrlen >> 3) - 1, -1): prefix_endpoint = i addr = chr(ord(self.prefix[i]) | (tmp & 0xff)) + addr tmp >>= 8 diff --git a/daemon/examples/api/emane80211.py b/daemon/examples/api/emane80211.py index c111962c..390d84c9 100644 --- a/daemon/examples/api/emane80211.py +++ b/daemon/examples/api/emane80211.py @@ -3,6 +3,7 @@ # Example CORE Python script that attaches N nodes to an EMANE 802.11abg network. import datetime +from builtins import range import parser from core import load_logging_config @@ -33,7 +34,7 @@ def example(options): emane_network.setposition(x=80, y=50) # create nodes - for i in xrange(options.nodes): + for i in range(options.nodes): node = session.create_wireless_node() node.setposition(x=150 * (i + 1), y=150) interface = prefixes.create_interface(node) diff --git a/daemon/examples/api/switch.py b/daemon/examples/api/switch.py index 633d9b46..4e83cbe9 100644 --- a/daemon/examples/api/switch.py +++ b/daemon/examples/api/switch.py @@ -6,6 +6,7 @@ # nodestep import datetime +from builtins import range import parser from core import load_logging_config @@ -31,7 +32,7 @@ def example(options): switch = session.add_node(_type=NodeTypes.SWITCH) # create nodes - for _ in xrange(options.nodes): + for _ in range(options.nodes): node = session.add_node() interface = prefixes.create_interface(node) session.add_link(node.id, switch.id, interface_one=interface) diff --git a/daemon/examples/api/switch_inject.py b/daemon/examples/api/switch_inject.py index 0c80b0f9..cd741a75 100644 --- a/daemon/examples/api/switch_inject.py +++ b/daemon/examples/api/switch_inject.py @@ -4,6 +4,8 @@ # n nodes are connected to a virtual wlan; run test for testsec # and repeat for minnodes <= n <= maxnodes with a step size of # nodestep +from builtins import range + from core import load_logging_config from core.emulator.emudata import IpPrefixes from core.emulator.enumerations import NodeTypes, EventTypes @@ -26,7 +28,7 @@ def example(nodes): switch = session.add_node(_type=NodeTypes.SWITCH) # create nodes - for _ in xrange(nodes): + for _ in range(nodes): node = session.add_node() interface = prefixes.create_interface(node) session.add_link(node.id, switch.id, interface_one=interface) diff --git a/daemon/examples/api/wlan.py b/daemon/examples/api/wlan.py index aa3c7a03..8536cbf5 100644 --- a/daemon/examples/api/wlan.py +++ b/daemon/examples/api/wlan.py @@ -6,6 +6,7 @@ # nodestep import datetime +from builtins import range import parser from core import load_logging_config @@ -35,7 +36,7 @@ def example(options): # create nodes, must set a position for wlan basic range model node_options = NodeOptions() node_options.set_position(0, 0) - for _ in xrange(options.nodes): + for _ in range(options.nodes): node = session.add_node(node_options=node_options) interface = prefixes.create_interface(node) session.add_link(node.id, wlan.id, interface_one=interface) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index bad993f1..bff65a70 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -1,4 +1,5 @@ import logging +from builtins import range from core.api.grpc import client, core_pb2 @@ -36,7 +37,7 @@ def main(): # helper to create interfaces interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") - for i in xrange(2): + for i in range(2): # create node position = core_pb2.Position(x=50 + 50 * i, y=50) node = core_pb2.Node(position=position) diff --git a/daemon/examples/netns/daemonnodes.py b/daemon/examples/netns/daemonnodes.py index 23061fc2..3dec17dc 100755 --- a/daemon/examples/netns/daemonnodes.py +++ b/daemon/examples/netns/daemonnodes.py @@ -13,6 +13,7 @@ import datetime import optparse import sys +from builtins import range import core.nodes.base import core.nodes.network @@ -26,12 +27,11 @@ from core.emulator.enumerations import LinkTlvs from core.emulator.enumerations import LinkTypes from core.emulator.enumerations import MessageFlags from core.emulator.enumerations import MessageTypes +from core.emulator.session import Session from core.nodes import ipaddress # declare classes for use with Broker -from core.emulator.session import Session - # node list (count from 1) n = [None] exec_num = 1 @@ -148,7 +148,7 @@ def main(): print "creating %d remote nodes with addresses from %s" % (options.numnodes, prefix) # create remote nodes via API - for i in xrange(1, number_of_nodes + 1): + for i in range(1, number_of_nodes + 1): node = core.nodes.base.CoreNode(session=session, _id=i, name="n%d" % i, start=False) node.setposition(x=150 * i, y=150) node.server = daemon @@ -158,7 +158,7 @@ def main(): n.append(node) # create remote links via API - for i in xrange(1, number_of_nodes + 1): + for i in range(1, number_of_nodes + 1): tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.id) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value) diff --git a/daemon/examples/netns/distributed.py b/daemon/examples/netns/distributed.py index 2bd55d3e..b4fe7b22 100755 --- a/daemon/examples/netns/distributed.py +++ b/daemon/examples/netns/distributed.py @@ -12,14 +12,15 @@ import datetime import optparse import sys +from builtins import range import core.nodes.base import core.nodes.network from core import constants from core.api.tlv import coreapi, dataconversion from core.emulator.enumerations import CORE_API_PORT, EventTypes, EventTlvs, LinkTlvs, LinkTypes, MessageFlags -from core.nodes import ipaddress from core.emulator.session import Session +from core.nodes import ipaddress # node list (count from 1) n = [None] @@ -80,7 +81,7 @@ def main(): num_remote = options.numnodes / 2 + options.numnodes % 2 print "creating %d (%d local / %d remote) nodes with addresses from %s" % \ (options.numnodes, num_local, num_remote, prefix) - for i in xrange(1, num_local + 1): + for i in range(1, num_local + 1): node = session.create_node(cls=core.nodes.base.CoreNode, name="n%d" % i, _id=i) node.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) node.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) @@ -91,7 +92,7 @@ def main(): session.broker.handlerawmsg(switch.tonodemsg(flags=flags)) # create remote nodes via API - for i in xrange(num_local + 1, options.numnodes + 1): + for i in range(num_local + 1, options.numnodes + 1): node = core.nodes.base.CoreNode(session=session, _id=i, name="n%d" % i, start=False) node.setposition(x=150 * i, y=150) node.server = slave @@ -101,7 +102,7 @@ def main(): session.broker.handlerawmsg(node_message) # create remote links via API - for i in xrange(num_local + 1, options.numnodes + 1): + for i in range(num_local + 1, options.numnodes + 1): tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.id) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value) diff --git a/daemon/examples/netns/ospfmanetmdrtest.py b/daemon/examples/netns/ospfmanetmdrtest.py index 3bf4f7d0..aa31f1b4 100755 --- a/daemon/examples/netns/ospfmanetmdrtest.py +++ b/daemon/examples/netns/ospfmanetmdrtest.py @@ -13,15 +13,16 @@ import os import random import sys import time +from builtins import range from string import Template import core.nodes.base import core.nodes.network from core.constants import QUAGGA_STATE_DIR -from core.nodes import ipaddress -from core.utils import check_cmd # this is the /etc/core/core.conf default from core.emulator.session import Session +from core.nodes import ipaddress +from core.utils import check_cmd quagga_sbin_search = ("/usr/local/sbin", "/usr/sbin", "/usr/lib/quagga") quagga_path = "zebra" @@ -243,14 +244,14 @@ class ManetExperiment(object): self.session = Session(1) # emulated network self.net = self.session.create_node(cls=core.nodes.network.WlanNode) - for i in xrange(1, numnodes + 1): + for i in range(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) tmp = self.session.create_node(cls=ManetNode, ipaddr=addr, _id="%d" % i, name="n%d" % i) tmp.newnetif(self.net, [addr]) self.nodes.append(tmp) # connect nodes with probability linkprob - for i in xrange(numnodes): - for j in xrange(i + 1, numnodes): + for i in range(numnodes): + for j in range(i + 1, numnodes): r = random.random() if r < linkprob: if self.verbose: @@ -265,7 +266,7 @@ class ManetExperiment(object): self.net.link(self.nodes[i].netif(0), self.nodes[j].netif(0)) self.nodes[i].boot() # run the boot.sh script on all nodes to start Quagga - for i in xrange(numnodes): + for i in range(numnodes): self.nodes[i].cmd(["./%s" % self.nodes[i].bootsh]) def compareroutes(self, node, kr, zr): @@ -472,7 +473,7 @@ class ZebraRoutes(VtyshCmd): args = "show ip route" def parse(self): - for i in xrange(0, 3): + for i in range(0, 3): # skip first three lines self.out.readline() r = [] diff --git a/daemon/examples/netns/wlanemanetests.py b/daemon/examples/netns/wlanemanetests.py index b0870deb..a0786ec4 100755 --- a/daemon/examples/netns/wlanemanetests.py +++ b/daemon/examples/netns/wlanemanetests.py @@ -43,8 +43,8 @@ from core import emane from core.emane.bypass import EmaneBypassModel from core.emane.nodes import EmaneNode from core.emane.rfpipe import EmaneRfPipeModel -from core.nodes import ipaddress from core.emulator.session import Session +from core.nodes import ipaddress try: import emaneeventservice @@ -416,7 +416,7 @@ class Experiment(object): # emulated network self.net = self.session.create_node(cls=core.nodes.network.WlanNode, name="wlan1") prev = None - for i in xrange(1, numnodes + 1): + for i in range(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) tmp = self.session.create_node(cls=core.nodes.base.CoreNode, _id=i, name="n%d" % i) tmp.newnetif(self.net, [addr]) @@ -444,7 +444,7 @@ class Experiment(object): self.net = self.session.create_node(cls=EmaneNode, _id=numnodes + 1, name="wlan1") self.net.verbose = verbose # self.session.emane.addobj(self.net) - for i in xrange(1, numnodes + 1): + for i in range(1, numnodes + 1): addr = "%s/%s" % (prefix.addr(i), 32) tmp = self.session.create_node(cls=core.nodes.base.CoreNode, _id=i, name="n%d" % i) # tmp.setposition(i * 20, 50, None) @@ -461,7 +461,7 @@ class Experiment(object): self.info("waiting %s sec (TAP bring-up)" % 2) time.sleep(2) - for i in xrange(1, numnodes + 1): + for i in range(1, numnodes + 1): tmp = self.nodes[i - 1] self.session.services.boot_services(tmp) self.staticroutes(i, prefix, numnodes) @@ -496,7 +496,7 @@ class Experiment(object): self.warn("failed to add interface route: %s" % cmd) # add static routes to all other nodes via left/right neighbors - for j in xrange(1, numnodes + 1): + for j in range(1, numnodes + 1): if abs(j - i) < 2: continue addr = "%s" % prefix.addr(j) @@ -525,7 +525,7 @@ class Experiment(object): otachannel=None) old = False - for i in xrange(1, numnodes + 1): + for i in range(1, numnodes + 1): rxnem = i # inform rxnem that it can hear node to the left with 10dB noise txnem = rxnem - 1 diff --git a/daemon/requirements.txt b/daemon/requirements.txt index ae84c527..5f76f49c 100644 --- a/daemon/requirements.txt +++ b/daemon/requirements.txt @@ -1,5 +1,6 @@ enum34==1.1.6 +future==0.17.1 futures==3.2.0 grpcio==1.18.0 -lxml==3.5.0 +lxml==4.3.3 six==1.12.0 diff --git a/daemon/setup.py.in b/daemon/setup.py.in index 89be649e..599640fb 100644 --- a/daemon/setup.py.in +++ b/daemon/setup.py.in @@ -43,6 +43,7 @@ setup( packages=find_packages(), install_requires=[ "enum34", + "future", "lxml" ], tests_require=[ From feebd8704f3dbcd3a41b7ffa41d032aea1e54787 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 5 May 2019 16:52:55 -0700 Subject: [PATCH 0009/1992] updated all print calls to use print() method for 2/3 --- daemon/examples/api/emane80211.py | 4 +- daemon/examples/api/switch.py | 8 +-- daemon/examples/api/wlan.py | 8 +-- daemon/examples/netns/daemonnodes.py | 16 +++-- daemon/examples/netns/distributed.py | 12 ++-- daemon/examples/netns/howmanynodes.py | 40 ++++++------- daemon/examples/netns/ospfmanetmdrtest.py | 10 ++-- daemon/examples/netns/wlanemanetests.py | 10 ++-- daemon/examples/stopsession.py | 2 +- daemon/tests/test_gui.py | 2 +- scripts/perf/perflogserver.py | 72 +++++++++++------------ 11 files changed, 89 insertions(+), 95 deletions(-) diff --git a/daemon/examples/api/emane80211.py b/daemon/examples/api/emane80211.py index 390d84c9..1804d50d 100644 --- a/daemon/examples/api/emane80211.py +++ b/daemon/examples/api/emane80211.py @@ -55,9 +55,9 @@ def example(options): def main(): options = parser.parse_options("emane80211") start = datetime.datetime.now() - print "running emane 80211 example: nodes(%s) time(%s)" % (options.nodes, options.time) + print("running emane 80211 example: nodes(%s) time(%s)" % (options.nodes, options.time)) example(options) - print "elapsed time: %s" % (datetime.datetime.now() - start) + print("elapsed time: %s" % (datetime.datetime.now() - start)) if __name__ == "__main__" or __name__ == "__builtin__": diff --git a/daemon/examples/api/switch.py b/daemon/examples/api/switch.py index 4e83cbe9..f8419a47 100644 --- a/daemon/examples/api/switch.py +++ b/daemon/examples/api/switch.py @@ -44,10 +44,10 @@ def example(options): first_node = session.get_node(2) last_node = session.get_node(options.nodes + 1) - print "starting iperf server on node: %s" % first_node.name + print("starting iperf server on node: %s" % first_node.name) first_node.cmd(["iperf", "-s", "-D"]) first_node_address = prefixes.ip4_address(first_node) - print "node %s connecting to %s" % (last_node.name, first_node_address) + print("node %s connecting to %s" % (last_node.name, first_node_address)) last_node.client.icmd(["iperf", "-t", str(options.time), "-c", first_node_address]) first_node.cmd(["killall", "-9", "iperf"]) @@ -59,9 +59,9 @@ def main(): options = parser.parse_options("switch") start = datetime.datetime.now() - print "running switch example: nodes(%s) time(%s)" % (options.nodes, options.time) + print("running switch example: nodes(%s) time(%s)" % (options.nodes, options.time)) example(options) - print "elapsed time: %s" % (datetime.datetime.now() - start) + print("elapsed time: %s" % (datetime.datetime.now() - start)) if __name__ == "__main__": diff --git a/daemon/examples/api/wlan.py b/daemon/examples/api/wlan.py index 8536cbf5..9ac516b7 100644 --- a/daemon/examples/api/wlan.py +++ b/daemon/examples/api/wlan.py @@ -48,10 +48,10 @@ def example(options): first_node = session.get_node(2) last_node = session.get_node(options.nodes + 1) - print "starting iperf server on node: %s" % first_node.name + print("starting iperf server on node: %s" % first_node.name) first_node.cmd(["iperf", "-s", "-D"]) address = prefixes.ip4_address(first_node) - print "node %s connecting to %s" % (last_node.name, address) + print("node %s connecting to %s" % (last_node.name, address)) last_node.client.icmd(["iperf", "-t", str(options.time), "-c", address]) first_node.cmd(["killall", "-9", "iperf"]) @@ -63,9 +63,9 @@ def main(): options = parser.parse_options("wlan") start = datetime.datetime.now() - print "running wlan example: nodes(%s) time(%s)" % (options.nodes, options.time) + print("running wlan example: nodes(%s) time(%s)" % (options.nodes, options.time)) example(options) - print "elapsed time: %s" % (datetime.datetime.now() - start) + print("elapsed time: %s" % (datetime.datetime.now() - start)) if __name__ == "__main__": diff --git a/daemon/examples/netns/daemonnodes.py b/daemon/examples/netns/daemonnodes.py index 3dec17dc..87b9564f 100755 --- a/daemon/examples/netns/daemonnodes.py +++ b/daemon/examples/netns/daemonnodes.py @@ -65,7 +65,7 @@ def cmd(node, exec_cmd): msgdata = server.sock.recv(msglen) # If we get the right response return the results - print "received response message: %s" % MessageTypes(msgtype) + print("received response message: %s" % MessageTypes(msgtype)) if msgtype == MessageTypes.EXECUTE.value: msg = coreapi.CoreExecMessage(msgflags, msghdr, msgdata) result = msg.get_tlv(ExecuteTlvs.RESULT.value) @@ -120,7 +120,7 @@ def main(): port = int(daemonport[1]) else: port = CORE_API_PORT - print "connecting to daemon at %s:%d" % (daemon, port) + print("connecting to daemon at %s:%d" % (daemon, port)) session.broker.addserver(daemon, daemonip, port) # Set the local session id to match the port. @@ -145,7 +145,7 @@ def main(): number_of_nodes = options.numnodes - print "creating %d remote nodes with addresses from %s" % (options.numnodes, prefix) + print("creating %d remote nodes with addresses from %s" % (options.numnodes, prefix)) # create remote nodes via API for i in range(1, number_of_nodes + 1): @@ -175,13 +175,11 @@ def main(): session.broker.handlerawmsg(msg) # Get the ip or last node and ping it from the first - print "Pinging from the first to the last node" + print("Pinging from the first to the last node") pingip = cmd(n[-1], "ip -4 -o addr show dev eth0").split()[3].split("/")[0] - print cmd(n[1], "ping -c 5 " + pingip) - - print "elapsed time: %s" % (datetime.datetime.now() - start) - - print "To stop this session, use the core-cleanup script on the remote daemon server." + print(cmd(n[1], "ping -c 5 " + pingip)) + print("elapsed time: %s" % (datetime.datetime.now() - start)) + print("To stop this session, use the core-cleanup script on the remote daemon server.") raw_input("press enter to exit") diff --git a/daemon/examples/netns/distributed.py b/daemon/examples/netns/distributed.py index b4fe7b22..ca6aec1a 100755 --- a/daemon/examples/netns/distributed.py +++ b/daemon/examples/netns/distributed.py @@ -68,7 +68,7 @@ def main(): port = int(slaveport[1]) else: port = CORE_API_PORT - print "connecting to slave at %s:%d" % (slave, port) + print("connecting to slave at %s:%d" % (slave, port)) session.broker.addserver(slave, slave, port) session.broker.setupserver(slave) session.set_state(EventTypes.CONFIGURATION_STATE) @@ -79,8 +79,8 @@ def main(): switch.setposition(x=80, y=50) num_local = options.numnodes / 2 num_remote = options.numnodes / 2 + options.numnodes % 2 - print "creating %d (%d local / %d remote) nodes with addresses from %s" % \ - (options.numnodes, num_local, num_remote, prefix) + print("creating %d (%d local / %d remote) nodes with addresses from %s" % \ + (options.numnodes, num_local, num_remote, prefix)) for i in range(1, num_local + 1): node = session.create_node(cls=core.nodes.base.CoreNode, name="n%d" % i, _id=i) node.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) @@ -120,9 +120,9 @@ def main(): # start a shell on node 1 n[1].client.term("bash") - print "elapsed time: %s" % (datetime.datetime.now() - start) - print "To stop this session, use the 'core-cleanup' script on this server" - print "and on the remote slave server." + print("elapsed time: %s" % (datetime.datetime.now() - start)) + print("To stop this session, use the 'core-cleanup' script on this server") + print("and on the remote slave server.") if __name__ == "__main__" or __name__ == "__builtin__": diff --git a/daemon/examples/netns/howmanynodes.py b/daemon/examples/netns/howmanynodes.py index bf709a57..e5989a3c 100755 --- a/daemon/examples/netns/howmanynodes.py +++ b/daemon/examples/netns/howmanynodes.py @@ -115,16 +115,16 @@ def main(): start = datetime.datetime.now() prefix = ipaddress.Ipv4Prefix("10.83.0.0/16") - print "Testing how many network namespace nodes this machine can create." - print " - %s" % linuxversion() + print("Testing how many network namespace nodes this machine can create.") + print(" - %s" % linuxversion()) mem = memfree() - print " - %.02f GB total memory (%.02f GB swap)" % (mem["total"] / GBD, mem["stotal"] / GBD) - print " - using IPv4 network prefix %s" % prefix - print " - using wait time of %s" % options.waittime - print " - using %d nodes per bridge" % options.bridges - print " - will retry %d times on failure" % options.retries - print " - adding these services to each node: %s" % options.services - print " " + print(" - %.02f GB total memory (%.02f GB swap)" % (mem["total"] / GBD, mem["stotal"] / GBD)) + print(" - using IPv4 network prefix %s" % prefix) + print(" - using wait time of %s" % options.waittime) + print(" - using %d nodes per bridge" % options.bridges) + print(" - will retry %d times on failure" % options.retries) + print(" - adding these services to each node: %s" % options.services) + print(" ") lfp = None if options.logfile is not None: @@ -138,7 +138,7 @@ def main(): session = Session(1) switch = session.create_node(cls=core.nodes.network.SwitchNode) switchlist.append(switch) - print "Added bridge %s (%d)." % (switch.brname, len(switchlist)) + print("Added bridge %s (%d)." % (switch.brname, len(switchlist))) i = 0 retry_count = options.retries @@ -149,9 +149,9 @@ def main(): if 0 < options.bridges <= switch.numnetif(): switch = session.create_node(cls=core.nodes.network.SwitchNode) switchlist.append(switch) - print "\nAdded bridge %s (%d) for node %d." % (switch.brname, len(switchlist), i) + print("\nAdded bridge %s (%d) for node %d." % (switch.brname, len(switchlist), i)) except Exception, e: - print "At %d bridges (%d nodes) caught exception:\n%s\n" % (len(switchlist), i - 1, e) + print("At %d bridges (%d nodes) caught exception:\n%s\n" % (len(switchlist), i - 1, e)) break # create a node @@ -164,11 +164,11 @@ def main(): session.services.boot_services(n) nodelist.append(n) if i % 25 == 0: - print "\n%s nodes created " % i, + print("\n%s nodes created " % i,) mem = memfree() free = mem["free"] + mem["buff"] + mem["cached"] swap = mem["stotal"] - mem["sfree"] - print "(%.02f/%.02f GB free/swap)" % (free / GBD, swap / GBD), + print("(%.02f/%.02f GB free/swap)" % (free / GBD, swap / GBD),) if lfp: lfp.write("%d," % i) lfp.write("%s\n" % ",".join(str(mem[x]) for x in MEMKEYS)) @@ -178,20 +178,20 @@ def main(): sys.stdout.flush() time.sleep(options.waittime) except Exception, e: - print "At %d nodes caught exception:\n" % i, e + print("At %d nodes caught exception:\n" % i, e) if retry_count > 0: - print "\nWill retry creating node %d." % i + print("\nWill retry creating node %d." % i) shutil.rmtree(n.nodedir, ignore_errors=True) retry_count -= 1 i -= 1 time.sleep(options.waittime) continue else: - print "Stopping at %d nodes!" % i + print("Stopping at %d nodes!" % i) break if i == options.numnodes: - print "Stopping at %d nodes due to numnodes option." % i + print("Stopping at %d nodes due to numnodes option." % i) break # node creation was successful at this point retry_count = options.retries @@ -200,8 +200,8 @@ def main(): lfp.flush() lfp.close() - print "elapsed time: %s" % (datetime.datetime.now() - start) - print "Use the core-cleanup script to remove nodes and bridges." + print("elapsed time: %s" % (datetime.datetime.now() - start)) + print("Use the core-cleanup script to remove nodes and bridges.") if __name__ == "__main__": diff --git a/daemon/examples/netns/ospfmanetmdrtest.py b/daemon/examples/netns/ospfmanetmdrtest.py index aa31f1b4..89f5f9e3 100755 --- a/daemon/examples/netns/ospfmanetmdrtest.py +++ b/daemon/examples/netns/ospfmanetmdrtest.py @@ -171,13 +171,13 @@ class ManetExperiment(object): def info(self, msg): ''' Utility method for writing output to stdout. ''' - print msg + print(msg) sys.stdout.flush() self.log(msg) def warn(self, msg): ''' Utility method for writing output to stderr. ''' - print >> sys.stderr, msg + sys.stderr.write(msg) sys.stderr.flush() self.log(msg) @@ -204,7 +204,7 @@ class ManetExperiment(object): """ Write to the log file, if any. """ if not self.logfp: return - print >> self.logfp, msg + self.logfp.write(msg) def logdata(self, nbrs, mdrs, lsdbs, krs, zrs): """ Dump experiment parameters and data to the log file. """ @@ -368,12 +368,12 @@ class Cmd: def info(self, msg): ''' Utility method for writing output to stdout.''' - print msg + print(msg) sys.stdout.flush() def warn(self, msg): ''' Utility method for writing output to stderr. ''' - print >> sys.stderr, "XXX %s:" % self.node.routerid, msg + sys.stderr.write("XXX %s:" % self.node.routerid, msg) sys.stderr.flush() def run(self): diff --git a/daemon/examples/netns/wlanemanetests.py b/daemon/examples/netns/wlanemanetests.py index a0786ec4..452f1049 100755 --- a/daemon/examples/netns/wlanemanetests.py +++ b/daemon/examples/netns/wlanemanetests.py @@ -114,12 +114,12 @@ class Cmd(object): def info(self, msg): """ Utility method for writing output to stdout.""" - print msg + print(msg) sys.stdout.flush() def warn(self, msg): """ Utility method for writing output to stderr. """ - print >> sys.stderr, "XXX %s:" % self.node.name, msg + sys.stderr.write("XXX %s:" % self.node.name, msg) sys.stderr.flush() def run(self): @@ -358,13 +358,13 @@ class Experiment(object): def info(self, msg): """ Utility method for writing output to stdout. """ - print msg + print(msg) sys.stdout.flush() self.log(msg) def warn(self, msg): """ Utility method for writing output to stderr. """ - print >> sys.stderr, msg + sys.stderr.write(msg) sys.stderr.flush() self.log(msg) @@ -394,7 +394,7 @@ class Experiment(object): """ Write to the log file, if any. """ if not self.logfp: return - print >> self.logfp, msg + self.logfp.write(msg) def reset(self): """ Prepare for another experiment run. diff --git a/daemon/examples/stopsession.py b/daemon/examples/stopsession.py index d6b2349d..3deaae53 100755 --- a/daemon/examples/stopsession.py +++ b/daemon/examples/stopsession.py @@ -40,7 +40,7 @@ def main(): data = sock.recv(msglen) message = coreapi.CoreMessage(msgflags, hdr, data) sessions = message.get_tlv(coreapi.SessionTlvs.NUMBER.value) - print "sessions:", sessions + print("sessions: {}".format(sessions)) sock.close() diff --git a/daemon/tests/test_gui.py b/daemon/tests/test_gui.py index a06d61a9..3083ea2d 100644 --- a/daemon/tests/test_gui.py +++ b/daemon/tests/test_gui.py @@ -90,7 +90,7 @@ def run_cmd(node, exec_cmd): message_data = server.sock.recv(message_length) # If we get the right response return the results - print "received response message: %s" % message_type + print("received response message: %s" % message_type) if message_type == MessageTypes.EXECUTE.value: message = coreapi.CoreExecMessage(message_flags, message_header, message_data) result = message.get_tlv(ExecuteTlvs.RESULT.value) diff --git a/scripts/perf/perflogserver.py b/scripts/perf/perflogserver.py index a4c2c469..cc682703 100755 --- a/scripts/perf/perflogserver.py +++ b/scripts/perf/perflogserver.py @@ -23,8 +23,8 @@ def readfile(fname): f = open(fname, "r") except IOError: if options.timestamp == True: - print str(time.time()), - print "ERROR: failed to open file %s\n" % fname + print(str(time.time()),) + print("ERROR: failed to open file %s\n" % fname) else: lines = f.readlines() f.close() @@ -42,8 +42,7 @@ def numcpus(): def handler(signum, frame): - print "stop timestamp:", str( - time.time()) + ", cyclecount=", cyclecount, ", caught signal", signum + print("stop timestamp:", str(time.time()) + ", cyclecount=", cyclecount, ", caught signal", signum) sys.exit(0) @@ -120,15 +119,15 @@ def checkserverthreshold(metricval): alarm = ["server", os.uname()[1], str(ind) + key, "%.2f" % pcpus[ind], ">", serverthresholds.getvalue(key)] if options.timestamp: - print str(time.time()) + ",", - print ", ".join(str(x) for x in alarm) + print(str(time.time()) + ",",) + print(", ".join(str(x) for x in alarm)) else: if metricval.getvalue(key) > serverthresholds.getvalue(key): alarm = ["server", os.uname()[1], key, "%.2f" % metricval.getvalue(key), ">", serverthresholds.getvalue(key)] if options.timestamp: - print str(time.time()) + ",", - print ", ".join(str(x) for x in alarm) + print(str(time.time()) + ",",) + print(", ".join(str(x) for x in alarm)) def collectservercputimes(): @@ -310,8 +309,8 @@ class LogSession(object): for nname in self.pids: if self.pids[nname] == "": if options.timestamp == True: - print str(time.time()), - print "ERROR: null vnoded pid of node: %s" % nname + print(str(time.time()),) + print("ERROR: null vnoded pid of node: %s" % nname) else: childprocs = [] ppid = self.pids[nname] @@ -331,13 +330,13 @@ class LogSession(object): if self.pids[pp] != []: for ap in range(len(self.pids[pp]) - 1): # ap pid - print ", " + self.pids[pp][ap][0], + print(", " + self.pids[pp][ap][0],) # ap cmd - print ", " + self.pids[pp][ap][1], + print(", " + self.pids[pp][ap][1],) procmetrics = [str(x) for x in self.pids[pp][ap][-1]] - print ", " + ", ".join(procmetrics), + print(", " + ", ".join(procmetrics),) nodemetrics = [str(x) for x in self.pids[pp][-1]] - print ", " + ", ".join(nodemetrics) + print(", " + ", ".join(nodemetrics)) def getprocessmetrics(self, pid): """ @@ -401,9 +400,9 @@ class LogSession(object): procm = self.getprocessmetrics(self.pids[nod][ap][0]) if procm == []: if options.timestamp == True: - print str(time.time()), - print "WARNING: transient process", self.pids[nod][ap][1], \ - "/", self.pids[nod][ap][0], "on node %s" % nod + print(str(time.time()),) + print("WARNING: transient process", self.pids[nod][ap][1], + "/", self.pids[nod][ap][0], "on node %s" % nod) else: nodeapps[ap] = procm self.pids[nod][ap].append(nodeapps[ap]) @@ -436,8 +435,8 @@ class LogSession(object): for k in self.nodemetricsC: if options.timestamp: - print str(time.time()) + ",", - print k, ",", mm[k].tocsv() + print(str(time.time()) + ",",) + print(k, ",", mm[k].tocsv()) def readnodethresholds(self, filename): if filename is None: @@ -458,8 +457,8 @@ class LogSession(object): alarm = ["node", nname + "/" + self.pids[nname][0][0], keyname, calcm.getvalue(keyname), ">", self.nodethresholds.getvalue(keyname)] if options.timestamp: - print str(time.time()) + ",", - print ", ".join(str(x) for x in alarm) + print(str(time.time()) + ",",) + print(", ".join(str(x) for x in alarm)) def calcnodemetrics(self, cputimea, cputimeb, mems): """ @@ -472,10 +471,10 @@ class LogSession(object): hostusedcpu = p[0] + p[1] hostusedmem = mems[0] - mems[1] if hostusedcpu == 0: - print "WARNING: host used cpu = 0, ", p[0], p[1] + print("WARNING: host used cpu = 0, ", p[0], p[1]) hostusedcpu = 1 if hostusedmem == 0: - print "WARNING: host used mem = 0, ", mems[0], mems[1] + print("WARNING: host used mem = 0, ", mems[0], mems[1]) hostusedmem = 1 nodesa = self.nodemetricsA @@ -490,8 +489,8 @@ class LogSession(object): (False == isinstance(nodesb[nod], NodeMetrics)) | \ (False == isinstance(nodesa[nod], NodeMetrics)): if options.timestamp == True: - print str(time.time()), - print "Warning: nodes %s is not fully instanciated" % nod + print(str(time.time()),) + print("Warning: nodes %s is not fully instanciated" % nod) else: # calc throughput kbps calcm.setvalue("nodethroughput", "%.2f" % (8 * (nodesb[nod].getvalue("nodethroughput") @@ -519,7 +518,7 @@ class LogSession(object): except IndexError: pass else: - print "Warning: transient node %s " % nod + print("Warning: transient node %s " % nod) return nodesb @@ -531,21 +530,18 @@ def main(): configfile="/etc/core/perflogserver.conf", alarm=True, session=None) parser.add_option("-i", "--interval", dest="interval", type=int, - help="seconds to wait between samples; default=%s" % - parser.defaults["interval"]) + help="seconds to wait between samples; default=%s" % parser.defaults["interval"]) parser.add_option("-t", "--timestamp", action="store_true", dest="timestamp", help="include timestamp on each line") parser.add_option("-c", "--configfile", dest="configfile", type="string", - help="read threshold values from the specified file;\ - default=%s" % parser.defaults["configfile"]) + help="read threshold values from the specified file;default=%s" % parser.defaults["configfile"]) parser.add_option("-a", "--alarm", action="store_true", dest="alarm", help="generate alarms based threshold check on each cycle") parser.add_option("-s", "--session", dest="session", type=int, - help="CORE session id; default=%s" % - parser.defaults["session"]) + help="CORE session id; default=%s" % parser.defaults["session"]) global options global ncpus global serverthresholds @@ -573,12 +569,12 @@ def main(): logsession = LogSession() # mark host log baseline - print "server: ", ", ".join(str(x) for x in os.uname()), ",", ncpus, "CPU cores" - print "start timestamp:", time.time(), ", baseline data: " - print csvserverbaseline() - print "server metrics: ", ", ".join(str(x) for x in serverthresholds.getkeys()) + print("server: ", ", ".join(str(x) for x in os.uname()), ",", ncpus, "CPU cores") + print("start timestamp:", time.time(), ", baseline data: ") + print(csvserverbaseline()) + print("server metrics: ", ", ".join(str(x) for x in serverthresholds.getkeys())) if options.session is not None: - print "node metrics: nodename, ", ", ".join(str(x) for x in logsession.nodethresholds.getkeys()) + print("node metrics: nodename, ", ", ".join(str(x) for x in logsession.nodethresholds.getkeys())) cyclecount = 0 while True: @@ -591,7 +587,7 @@ def main(): calccputime = calcservercputimes(cputimea, cputimeb) m = csvservermetrics(collectservermetrics( calccputime, mems, options.alarm)) - print m + print(m) if options.session is not None: nodesb = logsession.getnodemetrics('b') From b42b5b1a5a201aa4b6563dc065f9cd075b2c94f1 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 5 May 2019 16:56:18 -0700 Subject: [PATCH 0010/1992] fixed updated raised exceptions to be 2/3 compatible --- daemon/core/nodes/interface.py | 2 +- daemon/examples/netns/ospfmanetmdrtest.py | 3 +-- ns3/corens3/obj.py | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 14ab64ab..855d8dfd 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -432,7 +432,7 @@ class GreTap(CoreInterface): return if remoteip is None: - raise ValueError, "missing remote IP required for GRE TAP device" + raise ValueError("missing remote IP required for GRE TAP device") args = [constants.IP_BIN, "link", "add", self.localname, "type", "gretap", "remote", str(remoteip)] if localip: diff --git a/daemon/examples/netns/ospfmanetmdrtest.py b/daemon/examples/netns/ospfmanetmdrtest.py index 89f5f9e3..ee2ee215 100755 --- a/daemon/examples/netns/ospfmanetmdrtest.py +++ b/daemon/examples/netns/ospfmanetmdrtest.py @@ -130,8 +130,7 @@ class Route(object): try: self.prefix = ipaddress.Ipv4Prefix(prefix) except Exception, e: - raise ValueError, "Invalid prefix given to Route object: %s\n%s" % \ - (prefix, e) + raise ValueError("Invalid prefix given to Route object: %s\n%s" % (prefix, e)) self.gw = gw self.metric = metric diff --git a/ns3/corens3/obj.py b/ns3/corens3/obj.py index e23acc46..3dc23052 100644 --- a/ns3/corens3/obj.py +++ b/ns3/corens3/obj.py @@ -329,7 +329,7 @@ class Ns3WimaxNet(CoreNs3Net): netif1, ns3dev1 = self.findns3dev(node1) netif2, ns3dev2 = self.findns3dev(node2) if not netif1 or not netif2: - raise ValueError, "interface not found" + raise ValueError("interface not found") addr1, mask1 = self.ipv4netifaddr(netif1) addr2, mask2 = self.ipv4netifaddr(netif2) clargs1 = (addr1, mask1, addr2, mask2) + downclass From 5b7453b906457d96656703b8f61f81641854b815 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 5 May 2019 16:58:08 -0700 Subject: [PATCH 0011/1992] updated caught exceptions to be 2/3 compliant --- daemon/examples/netns/howmanynodes.py | 4 ++-- daemon/examples/netns/ospfmanetmdrtest.py | 2 +- daemon/examples/netns/wlanemanetests.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/daemon/examples/netns/howmanynodes.py b/daemon/examples/netns/howmanynodes.py index e5989a3c..00e7d9f7 100755 --- a/daemon/examples/netns/howmanynodes.py +++ b/daemon/examples/netns/howmanynodes.py @@ -150,7 +150,7 @@ def main(): switch = session.create_node(cls=core.nodes.network.SwitchNode) switchlist.append(switch) print("\nAdded bridge %s (%d) for node %d." % (switch.brname, len(switchlist), i)) - except Exception, e: + except Exception as e: print("At %d bridges (%d nodes) caught exception:\n%s\n" % (len(switchlist), i - 1, e)) break @@ -177,7 +177,7 @@ def main(): sys.stdout.write(".") sys.stdout.flush() time.sleep(options.waittime) - except Exception, e: + except Exception as e: print("At %d nodes caught exception:\n" % i, e) if retry_count > 0: print("\nWill retry creating node %d." % i) diff --git a/daemon/examples/netns/ospfmanetmdrtest.py b/daemon/examples/netns/ospfmanetmdrtest.py index ee2ee215..9689fa86 100755 --- a/daemon/examples/netns/ospfmanetmdrtest.py +++ b/daemon/examples/netns/ospfmanetmdrtest.py @@ -129,7 +129,7 @@ class Route(object): def __init__(self, prefix=None, gw=None, metric=None): try: self.prefix = ipaddress.Ipv4Prefix(prefix) - except Exception, e: + except Exception as e: raise ValueError("Invalid prefix given to Route object: %s\n%s" % (prefix, e)) self.gw = gw self.metric = metric diff --git a/daemon/examples/netns/wlanemanetests.py b/daemon/examples/netns/wlanemanetests.py index 452f1049..25338cf3 100755 --- a/daemon/examples/netns/wlanemanetests.py +++ b/daemon/examples/netns/wlanemanetests.py @@ -49,11 +49,11 @@ from core.nodes import ipaddress try: import emaneeventservice import emaneeventpathloss -except Exception, e: +except Exception as e: try: from emanesh.events import EventService from emanesh.events import PathlossEvent - except Exception, e2: + except Exception as e2: raise ImportError("failed to import EMANE Python bindings:\n%s\n%s" % (e, e2)) # global Experiment object (for interaction with "python -i") @@ -256,7 +256,7 @@ class IperfCmd(ClientServerCmd): lines = self.out.readlines() try: bps = int(lines[-1].split(",")[-1].strip("\n")) - except Exception, e: + except Exception as e: self.warn("iperf parsing exception: %s" % e) bps = 0 return bps From 8d6bf54a169fdb6a702e4604f5f7d2b317973535 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 5 May 2019 17:11:07 -0700 Subject: [PATCH 0012/1992] removed usage of longs, updated int instance checking and octal constants to be 2/3 compliant --- daemon/core/api/grpc/server.py | 7 ++++--- daemon/core/api/tlv/coreapi.py | 2 +- daemon/core/location/mobility.py | 5 +++-- daemon/core/nodes/base.py | 5 ++--- daemon/core/nodes/interface.py | 1 + daemon/core/nodes/ipaddress.py | 12 ++++++------ daemon/core/nodes/physical.py | 4 ++-- daemon/core/services/coreservices.py | 2 +- daemon/examples/netns/ospfmanetmdrtest.py | 2 +- daemon/tests/test_grpc.py | 10 +++++----- ns3/corens3/obj.py | 2 +- 11 files changed, 27 insertions(+), 25 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 7cec9010..2c8f4f0a 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -4,17 +4,18 @@ import os import tempfile import time from Queue import Queue, Empty +from builtins import int import grpc from concurrent import futures -from core.emulator.emudata import NodeOptions, InterfaceData, LinkOptions -from core.emulator.enumerations import NodeTypes, EventTypes, LinkTypes from core.api.grpc import core_pb2 from core.api.grpc import core_pb2_grpc +from core.emulator.emudata import NodeOptions, InterfaceData, LinkOptions +from core.emulator.enumerations import NodeTypes, EventTypes, LinkTypes +from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.nodes import nodeutils from core.nodes.ipaddress import MacAddress -from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.services.coreservices import ServiceManager _ONE_DAY_IN_SECONDS = 60 * 60 * 24 diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index afc35a11..4a556ade 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -788,7 +788,7 @@ class CoreMessage(object): :rtype: str """ message_flags = [] - flag = 1L + flag = 1 while True: if self.flags & flag: diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 2890e9c2..f627a0a7 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -8,12 +8,13 @@ import math import os import threading import time +from builtins import int +from core import utils from core.config import ConfigGroup from core.config import ConfigurableOptions from core.config import Configuration from core.config import ModelManager -from core.nodes.base import CoreNodeBase from core.emulator.data import EventData from core.emulator.data import LinkData from core.emulator.enumerations import ConfigDataTypes @@ -23,7 +24,7 @@ from core.emulator.enumerations import MessageFlags from core.emulator.enumerations import MessageTypes from core.emulator.enumerations import NodeTlvs from core.emulator.enumerations import RegisterTlvs -from core import utils +from core.nodes.base import CoreNodeBase from core.nodes.ipaddress import IpAddress diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 49e54a99..06c6a735 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -559,7 +559,6 @@ class CoreNode(CoreNodeBase): finally: self.rmnodedir() - def cmd(self, args, wait=True): """ Runs shell command on node, with option to not wait for a result. @@ -928,10 +927,10 @@ class CoreNode(CoreNodeBase): hostfilename = self.hostfilename(filename) dirname, _basename = os.path.split(hostfilename) if not os.path.isdir(dirname): - os.makedirs(dirname, mode=0755) + os.makedirs(dirname, mode=0o755) return open(hostfilename, mode) - def nodefile(self, filename, contents, mode=0644): + def nodefile(self, filename, contents, mode=0o644): """ Create a node file with a given mode. diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 855d8dfd..1b628408 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -4,6 +4,7 @@ virtual ethernet classes that implement the interfaces available under Linux. import logging import time +from builtins import int from builtins import range from core import CoreCommandError, utils diff --git a/daemon/core/nodes/ipaddress.py b/daemon/core/nodes/ipaddress.py index a378631a..864b0296 100644 --- a/daemon/core/nodes/ipaddress.py +++ b/daemon/core/nodes/ipaddress.py @@ -44,12 +44,12 @@ class MacAddress(object): if not self.addr: return IpAddress.from_string("::") tmp = struct.unpack("!Q", "\x00\x00" + self.addr)[0] - nic = long(tmp) & 0x000000FFFFFFL - oui = long(tmp) & 0xFFFFFF000000L + nic = long(tmp) & 0x000000FFFFFF + oui = long(tmp) & 0xFFFFFF000000 # toggle U/L bit - oui ^= 0x020000000000L + oui ^= 0x020000000000 # append EUI-48 octets - oui = (oui << 16) | 0xFFFE000000L + oui = (oui << 16) | 0xFFFE000000 return IpAddress(AF_INET6, struct.pack("!QQ", 0xfe80 << 48, oui | nic)) @classmethod @@ -234,7 +234,7 @@ class IpPrefix(object): self.prefix = socket.inet_pton(self.af, tmp[0]) if self.addrlen > self.prefixlen: addrbits = self.addrlen - self.prefixlen - netmask = ((1L << self.prefixlen) - 1) << addrbits + netmask = ((1 << self.prefixlen) - 1) << addrbits prefix = "" for i in range(-1, -(addrbits >> 3) - 2, -1): prefix = chr(ord(self.prefix[i]) & (netmask & 0xff)) + prefix @@ -376,7 +376,7 @@ class IpPrefix(object): :rtype: str """ addrbits = self.addrlen - self.prefixlen - netmask = ((1L << self.prefixlen) - 1) << addrbits + netmask = ((1 << self.prefixlen) - 1) << addrbits netmaskbytes = struct.pack("!L", netmask) return IpAddress(af=AF_INET, address=netmaskbytes).__str__() diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 1e9d3a89..388cddb6 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -234,12 +234,12 @@ class PhysicalNode(CoreNodeBase): dirname = dirname.replace("/", ".") dirname = os.path.join(self.nodedir, dirname) if not os.path.isdir(dirname): - os.makedirs(dirname, mode=0755) + os.makedirs(dirname, mode=0o755) hostfilename = os.path.join(dirname, basename) return open(hostfilename, mode) - def nodefile(self, filename, contents, mode=0644): + def nodefile(self, filename, contents, mode=0o644): with self.opennodefile(filename, "w") as node_file: node_file.write(contents) os.chmod(node_file.name, mode) diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 9b762fe1..a12c0a70 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -520,7 +520,7 @@ class CoreServices(object): src = src.split('\n')[0] src = utils.expand_corepath(src, node.session, node) # TODO: glob here - node.nodefilecopy(filename, src, mode=0644) + node.nodefilecopy(filename, src, mode=0o644) return True return False diff --git a/daemon/examples/netns/ospfmanetmdrtest.py b/daemon/examples/netns/ospfmanetmdrtest.py index 9689fa86..4ba85ca5 100755 --- a/daemon/examples/netns/ospfmanetmdrtest.py +++ b/daemon/examples/netns/ospfmanetmdrtest.py @@ -84,7 +84,7 @@ ip forwarding f.close() tmp = self.bootscript() if tmp: - self.nodefile(self.bootsh, tmp, mode=0755) + self.nodefile(self.bootsh, tmp, mode=0o755) def boot(self): self.config() diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 22cd3afe..7e2186b4 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -1,16 +1,16 @@ import time - from Queue import Queue +from builtins import int import grpc import pytest -from core.config import ConfigShim -from core.emulator.data import EventData -from core.emane.ieee80211abg import EmaneIeee80211abgModel -from core.emulator.enumerations import NodeTypes, EventTypes, ConfigFlags, ExceptionLevels from core.api.grpc import core_pb2 from core.api.grpc.client import CoreGrpcClient +from core.config import ConfigShim +from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emulator.data import EventData +from core.emulator.enumerations import NodeTypes, EventTypes, ConfigFlags, ExceptionLevels from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility diff --git a/ns3/corens3/obj.py b/ns3/corens3/obj.py index 3dc23052..70291d3b 100644 --- a/ns3/corens3/obj.py +++ b/ns3/corens3/obj.py @@ -491,7 +491,7 @@ class Ns3Session(Session): net.mobility.setendtime() net.mobility.refresh_ms = 300 net.mobility.empty_queue_stop = False - of = ns.network.OutputStreamWrapper(filename, filemode=777) + of = ns.network.OutputStreamWrapper(filename, filemode=0o777) self.mobhelper.EnableAsciiAll(of) self.mobilitytracethread = threading.Thread( target=self.mobilitytrace, From 1e98175e35feb42c4c4f7aeafb7733e4d9d712f3 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 5 May 2019 21:23:43 -0700 Subject: [PATCH 0013/1992] refactored usages of iteritems, itervalues, and iterkeys to be 2/3 compliant --- daemon/core/api/grpc/server.py | 19 +++++++---- daemon/core/api/tlv/broker.py | 12 ++++--- daemon/core/api/tlv/coreapi.py | 3 +- daemon/core/api/tlv/corehandlers.py | 48 ++++++++++++++++++---------- daemon/core/config.py | 8 +++-- daemon/core/emane/emanemanager.py | 9 ++++-- daemon/core/emulator/session.py | 23 +++++++------ daemon/core/location/mobility.py | 2 +- daemon/core/nodes/base.py | 4 +-- daemon/core/nodes/nodeutils.py | 5 +-- daemon/core/plugins/sdt.py | 3 +- daemon/core/services/coreservices.py | 11 ++++--- daemon/core/utils.py | 3 +- daemon/core/xml/corexml.py | 29 +++++++++++------ daemon/core/xml/corexmldeployment.py | 3 +- 15 files changed, 116 insertions(+), 66 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 2c8f4f0a..ed4e0f7d 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -466,7 +466,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): node = self.get_node(session, request.id, context) interfaces = [] - for interface_id, interface in node._netif.iteritems(): + for interface_id in node._netif: + interface = node._netif[interface_id] net_id = None if interface.net: net_id = interface.net.id @@ -628,7 +629,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("get hooks: %s", request) session = self.get_session(request.session, context) hooks = [] - for state, state_hooks in session._hooks.iteritems(): + for state in session._hooks: + state_hooks = session._hooks[state] for file_name, file_data in state_hooks: hook = core_pb2.Hook(state=state, file=file_name, data=file_data) hooks.append(hook) @@ -645,10 +647,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("get mobility configs: %s", request) session = self.get_session(request.session, context) response = core_pb2.GetMobilityConfigsResponse() - for node_id, model_config in session.mobility.node_configurations.iteritems(): + for node_id in session.mobility.node_configurations: + model_config = session.mobility.node_configurations[node_id] if node_id == -1: continue - for model_name in model_config.iterkeys(): + for model_name in model_config: if model_name != Ns2ScriptedMobility.name: continue config = session.mobility.get_model_config(node_id, model_name) @@ -687,7 +690,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetServices(self, request, context): logging.debug("get services: %s", request) services = [] - for service in ServiceManager.services.itervalues(): + for name in ServiceManager.services: + service = ServiceManager.services[name] service_proto = core_pb2.Service(group=service.group, name=service.name) services.append(service_proto) return core_pb2.GetServicesResponse(services=services) @@ -846,11 +850,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): logging.debug("get emane model configs: %s", request) session = self.get_session(request.session, context) response = core_pb2.GetEmaneModelConfigsResponse() - for node_id, model_config in session.emane.node_configurations.iteritems(): + for node_id in session.emane.node_configurations: + model_config = session.emane.node_configurations[node_id] if node_id == -1: continue - for model_name in model_config.iterkeys(): + for model_name in model_config: model = session.emane.models[model_name] config = session.emane.get_model_config(node_id, model_name) config_groups = get_config_groups(config, model) diff --git a/daemon/core/api/tlv/broker.py b/daemon/core/api/tlv/broker.py index fdb28101..c168cde4 100644 --- a/daemon/core/api/tlv/broker.py +++ b/daemon/core/api/tlv/broker.py @@ -160,7 +160,8 @@ class CoreBroker(object): logging.info("clearing state") self.nodemap_lock.acquire() self.nodemap.clear() - for server, count in self.nodecounts.iteritems(): + for server in self.nodecounts: + count = self.nodecounts[server] if count < 1: self.delserver(server) self.nodecounts.clear() @@ -200,7 +201,8 @@ class CoreBroker(object): rlist = [] with self.servers_lock: # build a socket list for select call - for server in self.servers.itervalues(): + for name in self.servers: + server = self.servers[name] if server.sock is not None: rlist.append(server.sock) r, _w, _x = select.select(rlist, [], [], 1.0) @@ -349,7 +351,8 @@ class CoreBroker(object): :rtype: CoreDistributedServer """ with self.servers_lock: - for server in self.servers.itervalues(): + for name in self.servers: + server = self.servers[name] if server.sock == sock: return server return None @@ -1041,7 +1044,8 @@ class CoreBroker(object): :rtype: bool """ with self.servers_lock: - for server in self.servers.itervalues(): + for name in self.servers: + server = self.servers[name] if not server.instantiation_complete: return False return True diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index 4a556ade..ec88ce39 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -811,7 +811,8 @@ class CoreMessage(object): """ result = "%s " % (self.__class__.__name__, self.type_str(), self.flag_str()) - for key, value in self.tlv_data.iteritems(): + for key in self.tlv_data: + value = self.tlv_data[key] try: tlv_type = self.tlv_class.tlv_type_map(key).name except ValueError: diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 3c33444b..d7aa0958 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -158,7 +158,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): num_sessions = 0 with self._sessions_lock: - for _id, session in self.coreemu.sessions.iteritems(): + for _id in self.coreemu.sessions: + session = self.coreemu.sessions[_id] num_sessions += 1 id_list.append(str(_id)) @@ -167,10 +168,10 @@ class CoreHandler(SocketServer.BaseRequestHandler): name = "" name_list.append(name) - file = session.file_name - if not file: - file = "" - file_list.append(file) + file_name = session.file_name + if not file_name: + file_name = "" + file_list.append(file_name) node_count_list.append(str(session.get_node_count())) @@ -378,11 +379,13 @@ class CoreHandler(SocketServer.BaseRequestHandler): tlv_data += coreapi.CoreRegisterTlv.pack(self.session.broker.config_type, self.session.broker.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.location.config_type, self.session.location.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.mobility.config_type, self.session.mobility.name) - for model_class in self.session.mobility.models.itervalues(): + for model_name in self.session.mobility.models: + model_class = self.session.mobility.models[model_name] tlv_data += coreapi.CoreRegisterTlv.pack(model_class.config_type, model_class.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.services.config_type, self.session.services.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.emane.config_type, self.session.emane.name) - for model_class in self.session.emane.models.itervalues(): + for model_name in self.session.emane.models: + model_class = self.session.emane.models[model_name] tlv_data += coreapi.CoreRegisterTlv.pack(model_class.config_type, model_class.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.options.config_type, self.session.options.name) tlv_data += coreapi.CoreRegisterTlv.pack(self.session.metadata.config_type, self.session.metadata.name) @@ -904,7 +907,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): self.master = True # find the session containing this client and set the session to master - for session in self.coreemu.sessions.itervalues(): + for _id in self.coreemu.sessions: + session = self.coreemu.sessions[_id] if self in session.broker.session_clients: logging.debug("setting session to master: %s", session.id) session.master = True @@ -995,7 +999,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): replies.append(config_response) elif message_type != ConfigFlags.RESET and config_data.data_values: values = ConfigShim.str_to_dict(config_data.data_values) - for key, value in values.iteritems(): + for key in values: + value = values[key] self.session.options.set_config(key, value) return replies @@ -1025,7 +1030,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): replies = [] if message_type == ConfigFlags.REQUEST: node_id = config_data.node - data_values = "|".join(["%s=%s" % item for item in self.session.metadata.get_configs().iteritems()]) + metadata_configs = self.session.metadata.get_configs() + data_values = "|".join(["%s=%s" % (x, metadata_configs[x]) for x in metadata_configs]) data_types = tuple(ConfigDataTypes.STRING.value for _ in self.session.metadata.get_configs()) config_response = ConfigData( message_type=0, @@ -1038,7 +1044,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): replies.append(config_response) elif message_type != ConfigFlags.RESET and config_data.data_values: values = ConfigShim.str_to_dict(config_data.data_values) - for key, value in values.iteritems(): + for key in values: + value = values[key] self.session.metadata.set_config(key, value) return replies @@ -1097,7 +1104,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): # sort groups by name and map services to groups groups = set() group_map = {} - for service_name in ServiceManager.services.itervalues(): + for name in ServiceManager.services: + service_name = ServiceManager.services[name] group = service_name.group groups.add(group) group_map.setdefault(group, []).append(service_name) @@ -1212,7 +1220,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): raise ValueError("custom service(%s) for node(%s) does not exist", service_name, node_id) values = ConfigShim.str_to_dict(values) - for name, value in values.iteritems(): + for name in values: + value = values[name] ServiceShim.setvalue(service, name, value) return replies @@ -1679,7 +1688,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): nodes_data = [] links_data = [] with self.session._nodes_lock: - for node in self.session.nodes.itervalues(): + for node_id in self.session.nodes: + node = self.session.nodes[node_id] node_data = node.data(message_type=MessageFlags.ADD.value) if node_data: nodes_data.append(node_data) @@ -1697,7 +1707,9 @@ class CoreHandler(SocketServer.BaseRequestHandler): # send mobility model info for node_id in self.session.mobility.nodes(): - for model_name, config in self.session.mobility.get_all_configs(node_id).iteritems(): + mobility_configs = self.session.mobility.get_all_configs(node_id) + for model_name in mobility_configs: + config = mobility_configs[model_name] model_class = self.session.mobility.models[model_name] logging.debug("mobility config: node(%s) class(%s) values(%s)", node_id, model_class, config) config_data = ConfigShim.config_data(0, node_id, ConfigFlags.UPDATE.value, model_class, config) @@ -1705,7 +1717,9 @@ class CoreHandler(SocketServer.BaseRequestHandler): # send emane model info for node_id in self.session.emane.nodes(): - for model_name, config in self.session.emane.get_all_configs(node_id).iteritems(): + emane_configs = self.session.emane.get_all_configs(node_id) + for model_name in emane_configs: + config = emane_configs[model_name] model_class = self.session.emane.models[model_name] logging.debug("emane config: node(%s) class(%s) values(%s)", node_id, model_class, config) config_data = ConfigShim.config_data(0, node_id, ConfigFlags.UPDATE.value, model_class, config) @@ -1761,7 +1775,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): # send session metadata metadata_configs = self.session.metadata.get_configs() if metadata_configs: - data_values = "|".join(["%s=%s" % item for item in metadata_configs.iteritems()]) + data_values = "|".join(["%s=%s" % (x, metadata_configs[x]) for x in metadata_configs]) data_types = tuple(ConfigDataTypes.STRING.value for _ in self.session.metadata.get_configs()) config_data = ConfigData( message_type=0, diff --git a/daemon/core/config.py b/daemon/core/config.py index d62ba33b..e62bdcfd 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -150,7 +150,7 @@ class ConfigurableManager(object): :return: list of node ids :rtype: list """ - return [node_id for node_id in self.node_configurations.iterkeys() if node_id != self._default_node] + return [x for x in self.node_configurations if x != self._default_node] def config_reset(self, node_id=None): """ @@ -329,7 +329,8 @@ class ModelManager(ConfigurableManager): model_config = self.get_model_config(node_id, model_name) if not config: config = {} - for key, value in config.iteritems(): + for key in config: + value = config[key] model_config[key] = value # set as node model for startup @@ -388,7 +389,8 @@ class ModelManager(ConfigurableManager): all_configs = {} models = [] - for model_name, config in all_configs.iteritems(): + for model_name in all_configs: + config = all_configs[model_name] if model_name == ModelManager._default_node: continue model_class = self.models[model_name] diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 2dc0d191..54099a35 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -250,7 +250,8 @@ class EmaneManager(ModelManager): # TODO: drive this from the session object with self.session._nodes_lock: - for node in self.session.nodes.itervalues(): + for node_id in self.session.nodes: + node = self.session.nodes[node_id] if nodeutils.is_node(node, NodeTypes.EMANE): logging.debug("adding emane node: id(%s) name(%s)", node.id, node.name) self.add_node(node) @@ -318,7 +319,8 @@ class EmaneManager(ModelManager): self.startdaemons() self.installnetifs() - for emane_node in self._emane_nodes.itervalues(): + for node_id in self._emane_nodes: + emane_node = self._emane_nodes[node_id] for netif in emane_node.netifs(): nems.append((netif.node.name, netif.name, emane_node.getnemid(netif))) @@ -552,7 +554,8 @@ class EmaneManager(ModelManager): Return the number of NEMs emulated locally. """ count = 0 - for emane_node in self._emane_nodes.itervalues(): + for node_id in self._emane_nodes: + emane_node = self._emane_nodes[node_id] count += len(emane_node.netifs()) return count diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 46c5ffc7..0e500d4d 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -100,7 +100,8 @@ class Session(object): self.options = SessionConfig() if not config: config = {} - for key, value in config.iteritems(): + for key in config: + value = config[key] self.options.set_config(key, value) self.metadata = SessionMetaData() @@ -1244,12 +1245,15 @@ class Session(object): """ with self._nodes_lock: - count = len([x for x in self.nodes.itervalues() - if not nodeutils.is_node(x, (NodeTypes.PEER_TO_PEER, NodeTypes.CONTROL_NET))]) + count = 0 + for node_id in self.nodes: + node = self.nodes[node_id] + is_p2p_ctrlnet = nodeutils.is_node(node, (NodeTypes.PEER_TO_PEER, NodeTypes.CONTROL_NET)) + is_tap = nodeutils.is_node(node, NodeTypes.TAP_BRIDGE) and not nodeutils.is_node(x, NodeTypes.TUNNEL) + if is_p2p_ctrlnet or is_tap: + continue - # on Linux, GreTapBridges are auto-created, not part of GUI's node count - count -= len([x for x in self.nodes.itervalues() - if nodeutils.is_node(x, NodeTypes.TAP_BRIDGE) and not nodeutils.is_node(x, NodeTypes.TUNNEL)]) + count += 1 return count @@ -1286,10 +1290,11 @@ class Session(object): # stop node services with self._nodes_lock: - for obj in self.nodes.itervalues(): + for node_id in self.nodes: + node = self.nodes[node_id] # TODO: determine if checking for CoreNode alone is ok - if isinstance(obj, core.nodes.base.CoreNodeBase): - self.services.stop_services(obj) + if isinstance(node, core.nodes.base.CoreNodeBase): + self.services.stop_services(node) # shutdown emane self.emane.shutdown() diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index f627a0a7..c5f786ac 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -73,7 +73,7 @@ class MobilityManager(ModelManager): logging.warn("skipping mobility configuration for unknown node: %s", node_id) continue - for model_name in self.models.iterkeys(): + for model_name in self.models: config = self.get_configs(node_id, model_name) if not config: continue diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 06c6a735..9cbd646a 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -120,9 +120,9 @@ class NodeBase(object): :rtype: list """ if sort: - return map(lambda k: self._netif[k], sorted(self._netif.keys())) + return [self._netif[x] for x in sorted(self._netif)] else: - return self._netif.itervalues() + return self._netif.values() def numnetif(self): """ diff --git a/daemon/core/nodes/nodeutils.py b/daemon/core/nodes/nodeutils.py index ef4dacb6..2aa88b8f 100644 --- a/daemon/core/nodes/nodeutils.py +++ b/daemon/core/nodes/nodeutils.py @@ -9,7 +9,8 @@ _NODE_MAP = None def _log_map(): global _NODE_MAP - for key, value in _NODE_MAP.iteritems(): + for key in _NODE_MAP: + value = _NODE_MAP[key] name = None if value: name = value.__name__ @@ -72,7 +73,7 @@ def get_node_type(node_class): :rtype: core.enumerations.NodeTypes """ global _NODE_MAP - node_type_map = {v: k for k, v in _NODE_MAP.iteritems()} + node_type_map = {_NODE_MAP[x]: x for x in _NODE_MAP} return node_type_map.get(node_class) diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index ae65dfc5..dabf3453 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -321,7 +321,8 @@ class Sdt(object): """ nets = [] with self.session._nodes_lock: - for node in self.session.nodes.itervalues(): + for node_id in self.session.nodes: + node = self.session.nodes[node_id] if isinstance(node, CoreNetworkBase): nets.append(node) if not isinstance(node, NodeBase): diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index a12c0a70..378b1a78 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -60,7 +60,8 @@ class ServiceDependencies(object): :rtype: list[core.coreservices.CoreService] """ paths = [] - for service in self.node_services.itervalues(): + for name in self.node_services: + service = self.node_services[name] if service.name in self.booted: logging.debug("skipping service that will already be booted: %s", service.name) continue @@ -69,7 +70,7 @@ class ServiceDependencies(object): if path: paths.append(path) - if self.booted != set(self.node_services.iterkeys()): + if self.booted != set(self.node_services): raise ValueError("failure to boot all services: %s != %s" % (self.booted, self.node_services.keys())) return paths @@ -389,8 +390,10 @@ class CoreServices(object): :rtype: list[tuple] """ configs = [] - for node_id in self.custom_services.iterkeys(): - for service in self.custom_services[node_id].itervalues(): + for node_id in self.custom_services: + custom_services = self.custom_services[node_id] + for name in custom_services: + service = custom_services[name] configs.append((node_id, service)) return configs diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 1306badc..e2c0fedc 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -252,8 +252,7 @@ def hex_dump(s, bytes_per_word=2, words_per_line=8): while s: line = s[:total_bytes] s = s[total_bytes:] - tmp = map(lambda x: ("%02x" * bytes_per_word) % x, - zip(*[iter(map(ord, line))] * bytes_per_word)) + tmp = map(lambda x: ("%02x" * bytes_per_word) % x, zip(*[iter(map(ord, line))] * bytes_per_word)) if len(line) % 2: tmp.append("%x" % ord(line[-1])) dump += "0x%08x: %s\n" % (count, " ".join(tmp)) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index d689f022..df9101c1 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -148,7 +148,8 @@ class ServiceElement(object): def add_files(self): # get custom files file_elements = etree.Element("files") - for file_name, data in self.service.config_data.iteritems(): + for file_name in self.service.config_data: + data = self.service.config_data[file_name] file_element = etree.SubElement(file_elements, "file") add_attribute(file_element, "name", file_name) file_element.text = data @@ -291,7 +292,9 @@ class CoreXmlWriter(object): if not options_config: return - for _id, default_value in self.session.options.default_values().iteritems(): + default_options = self.session.options.default_values() + for _id in default_options: + default_value = default_options[_id] # TODO: should we just save the current config regardless, since it may change? value = options_config[_id] if value != default_value: @@ -307,7 +310,8 @@ class CoreXmlWriter(object): if not config: return - for _id, value in config.iteritems(): + for _id in config: + value = config[_id] add_configuration(metadata_elements, _id, value) if metadata_elements.getchildren(): @@ -320,7 +324,8 @@ class CoreXmlWriter(object): if not all_configs: continue - for model_name, config in all_configs.iteritems(): + for model_name in all_configs: + config = all_configs[model_name] logging.info("writing emane config node(%s) model(%s)", node_id, model_name) if model_name == -1: emane_configuration = create_emane_config(node_id, self.session.emane.emane_config, config) @@ -339,12 +344,14 @@ class CoreXmlWriter(object): if not all_configs: continue - for model_name, config in all_configs.iteritems(): + for model_name in all_configs: + config = all_configs[model_name] logging.info("writing mobility config node(%s) model(%s)", node_id, model_name) mobility_configuration = etree.SubElement(mobility_configurations, "mobility_configuration") add_attribute(mobility_configuration, "node", node_id) add_attribute(mobility_configuration, "model", model_name) - for name, value in config.iteritems(): + for name in config: + value = config[name] add_configuration(mobility_configuration, name, value) if mobility_configurations.getchildren(): @@ -363,7 +370,8 @@ class CoreXmlWriter(object): def write_default_services(self): node_types = etree.Element("default_services") - for node_type, services in self.session.services.default_services.iteritems(): + for node_type in self.session.services.default_services: + services = self.session.services.default_services[node_type] node_type = etree.SubElement(node_types, "node", type=node_type) for service in services: etree.SubElement(node_type, "service", name=service) @@ -376,9 +384,12 @@ class CoreXmlWriter(object): self.devices = etree.SubElement(self.scenario, "devices") links = [] - for node in self.session.nodes.itervalues(): + for node_id in self.session.nodes: + node = self.session.nodes[node_id] # network node - if isinstance(node, (core.nodes.base.CoreNetworkBase, core.nodes.physical.Rj45Node)) and not nodeutils.is_node(node, NodeTypes.CONTROL_NET): + is_network_or_rj45 = isinstance(node, (core.nodes.base.CoreNetworkBase, core.nodes.physical.Rj45Node)) + is_controlnet = nodeutils.is_node(node, NodeTypes.CONTROL_NET) + if is_network_or_rj45 and not is_controlnet: self.write_network(node) # device node elif isinstance(node, core.nodes.base.CoreNodeBase): diff --git a/daemon/core/xml/corexmldeployment.py b/daemon/core/xml/corexmldeployment.py index aff7b1ae..342e1294 100644 --- a/daemon/core/xml/corexmldeployment.py +++ b/daemon/core/xml/corexmldeployment.py @@ -100,7 +100,8 @@ class CoreXmlDeployment(object): # servers = self.session.broker.getservernames() # servers.remove("localhost") - for node in self.session.nodes.itervalues(): + for node_id in self.session.nodes: + node = self.session.nodes[node_id] if isinstance(node, CoreNodeBase): self.add_virtual_host(physical_host, node) From b58d32c156b64786ada14836e2fab147be4a7a46 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 5 May 2019 21:37:45 -0700 Subject: [PATCH 0014/1992] updated socketserver to 2/3 compliant --- daemon/core/api/tlv/corehandlers.py | 10 +++++----- daemon/core/api/tlv/coreserver.py | 8 ++++---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index d7aa0958..63d9343d 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -3,11 +3,11 @@ socket server request handlers leveraged by core servers. """ import Queue -import SocketServer import logging import os import shlex import shutil +import socketserver import sys import threading import time @@ -44,9 +44,9 @@ from core.services.coreservices import ServiceManager from core.services.coreservices import ServiceShim -class CoreHandler(SocketServer.BaseRequestHandler): +class CoreHandler(socketserver.BaseRequestHandler): """ - The SocketServer class uses the RequestHandler class for servicing requests. + The CoreHandler class uses the RequestHandler class for servicing requests. """ def __init__(self, request, client_address, server): @@ -92,7 +92,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): self.coreemu = server.coreemu utils.close_onexec(request.fileno()) - SocketServer.BaseRequestHandler.__init__(self, request, client_address, server) + socketserver.BaseRequestHandler.__init__(self, request, client_address, server) def setup(self): """ @@ -140,7 +140,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): logging.info("no session clients left and not active, initiating shutdown") self.coreemu.delete_session(self.session.id) - return SocketServer.BaseRequestHandler.finish(self) + return socketserver.BaseRequestHandler.finish(self) def session_message(self, flags=0): """ diff --git a/daemon/core/api/tlv/coreserver.py b/daemon/core/api/tlv/coreserver.py index 2c052b05..389ef78f 100644 --- a/daemon/core/api/tlv/coreserver.py +++ b/daemon/core/api/tlv/coreserver.py @@ -2,12 +2,12 @@ Defines core server for handling TCP connections. """ -import SocketServer +import socketserver from core.emulator.coreemu import CoreEmu -class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): +class CoreServer(socketserver.ThreadingMixIn, socketserver.TCPServer): """ TCP server class, manages sessions and spawns request handlers for incoming connections. @@ -18,7 +18,7 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): def __init__(self, server_address, handler_class, config=None): """ Server class initialization takes configuration data and calls - the SocketServer constructor + the socketserver constructor. :param tuple[str, int] server_address: server host and port to use :param class handler_class: request handler @@ -27,4 +27,4 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): """ self.coreemu = CoreEmu(config) self.config = config - SocketServer.TCPServer.__init__(self, server_address, handler_class) + socketserver.TCPServer.__init__(self, server_address, handler_class) From 864c7b69a1d04d9a413aedfb4048afc37790d08b Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 5 May 2019 21:49:42 -0700 Subject: [PATCH 0015/1992] updated Queue and ConfigParser to use 2/3 compatible imports --- daemon/core/api/grpc/server.py | 2 +- daemon/core/api/tlv/corehandlers.py | 6 +++--- daemon/requirements.txt | 1 + daemon/scripts/core-daemon | 4 ++-- daemon/setup.py.in | 1 + daemon/tests/test_grpc.py | 2 +- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index ed4e0f7d..6e5c8734 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -3,8 +3,8 @@ import logging import os import tempfile import time -from Queue import Queue, Empty from builtins import int +from queue import Queue, Empty import grpc from concurrent import futures diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 63d9343d..f9d505f1 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -2,9 +2,9 @@ socket server request handlers leveraged by core servers. """ -import Queue import logging import os +import queue import shlex import shutil import socketserver @@ -69,7 +69,7 @@ class CoreHandler(socketserver.BaseRequestHandler): MessageTypes.EVENT.value: self.handle_event_message, MessageTypes.SESSION.value: self.handle_session_message, } - self.message_queue = Queue.Queue() + self.message_queue = queue.Queue() self.node_status_request = {} self._shutdown_lock = threading.Lock() self._sessions_lock = threading.Lock() @@ -466,7 +466,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: message = self.message_queue.get(timeout=1) self.handle_message(message) - except Queue.Empty: + except queue.Empty: pass def handle_message(self, message): diff --git a/daemon/requirements.txt b/daemon/requirements.txt index 5f76f49c..9678f4ea 100644 --- a/daemon/requirements.txt +++ b/daemon/requirements.txt @@ -1,4 +1,5 @@ enum34==1.1.6 +configparser=3.5.0 future==0.17.1 futures==3.2.0 grpcio==1.18.0 diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index e40ace9b..eff52e81 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -6,7 +6,7 @@ message handlers are defined and some support for sending messages. """ import argparse -import ConfigParser +import configparser import logging import sys import threading @@ -100,7 +100,7 @@ def get_merged_config(filename): if args.configfile is not None: filename = args.configfile del args.configfile - cfg = ConfigParser.SafeConfigParser(defaults) + cfg = configparser.ConfigParser(defaults) cfg.read(filename) section = "core-daemon" diff --git a/daemon/setup.py.in b/daemon/setup.py.in index 599640fb..65f7c729 100644 --- a/daemon/setup.py.in +++ b/daemon/setup.py.in @@ -43,6 +43,7 @@ setup( packages=find_packages(), install_requires=[ "enum34", + "configparser", "future", "lxml" ], diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 7e2186b4..286ea7ef 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -1,6 +1,6 @@ import time -from Queue import Queue from builtins import int +from queue import Queue import grpc import pytest From 1d8419d5920a73f1cd916a650901b6dfe774934d Mon Sep 17 00:00:00 2001 From: Kevlar Date: Thu, 23 May 2019 15:45:37 -0700 Subject: [PATCH 0016/1992] Added image of CORE --- docs/static/core_screenshot.png | Bin 0 -> 771398 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/static/core_screenshot.png diff --git a/docs/static/core_screenshot.png b/docs/static/core_screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..410754e969d46512d567698f04711c2ef988e65c GIT binary patch literal 771398 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

T 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

lDiKESqyRxu#yG>Bac<5!=;+`IxG*ZefYlhE z2;*h%#U;2Sz;)bv9+c9Kh+r`)!Xx*c5hc4%y{|Mc2iM_kei>3e?YyDRzUaZL-vo<`vSUXk4j zP>ogru8u)}t1@MB@}*0z>m|df_G!uRK!Meqg`u<};s%*~A-Lo=6~G2746oG<{@+aT zwuQnizMla$T-~H-%`9c2(UOIHS*!e@5`h=K;O<{yX&KJw8r9dB~u8Oec?a8Njxs9QeW89eVh<%g5WUD6MebL}p{Y z6k?cd(+590=Jfz{5mg2#u8M&Zl*hxjbJRah@MO+M1(Z{6d#MTFI|@<%VU%_NtS6Bn zVEtr)>y80jA276@FtF-Fz!fFKFtp;a$1uNrl9e*yQeXAA8o@O#KZ3Z$cqu+&=n#7u zxT1U<{m|0)szv5B`@u^ch0nSRe>iZ}^IG)*C*jse^%W(g{JN0M3aH~rH`K>e8GVl^ zCk{!5s{m@>F7RpuSLHN3Y}WMglnGZ1PQv43ZyaKfRPP3<4u!R?-eaQVrxl*iT0FCS z&JV~os-S07pw)Yt*Q&b__nZSz{W!t3tjEyEb-M!Cwnvrnw%kfBXNXNQ0GeGhWcAKd zGUujzIn2ifWYF?Cr{ttH(bj_$0R~nVgDR9tfUB>>5Ln!jNGxA2K<1#C%wd?V#TcadTvUup z&ugK=bD#>^cPhNyn^bAk_&lAYWI029es4#Q`+QtYit3Hh?Feu0fcP4y{MI0#HK)OA z9>BRtuFN7|F3a@~tGFs~-4w+YrNVk-&9VllVvb~fQ{Ys3Spj95p%Kb{1xVrA>rxRe ziyoEX3guQ*QiocHg4!v04nUfe>YCn=$D@aJV)bPOuBJD&_?iiUHyAd}YVkX&cXeX0 zi~>HT$O7_20rsd8XFxU-SiNKzrM0>;E3U#?>Y@smL3Ar2anz+P0Ln z?HrYuDT7+mYVv*6lLQC0%CP2XVX0IQb? z16HFohYYz*>vNSJSLC{$GiB1%Rx|l|gli^BFsR0m_J z2%gXKBq(DcXV^qG_a@D+PSe8H6cu=%L?4|hzf99H1Lz6E=#zI3>75Ub>HYVP`B-|u zfOSBvc8${c45iY%t+G*>JY`E^Dpn&@Z6g@_| ziz@9sWRQQDqWXSR`g-F=aMdz=;S#K0H)gmF44LXz1Xry;Hie$ojz`>je-+@`5cM@7 zJ)5nexE!Up`o*BAwU;7yWL4sEQ0Ca^N44l1*8pA>TpGZ&ylPZ=1Y1UQg@DcKr-FYk~&u&Al5aVf8bnNbD(n?v9WgDSY~mO8eNRdJPA9X?K# zba3sO`90ysFAL98P`2zAxCD%&ulioEuc)`AUc<-fN~iWBA%JF~CR%Wpo%u*=`woeo`_c$L2KmtU6t*(xuCo>l;Jp<$7}T=cx=`1=Cb z4`to767bQj%@BE09X>Aa(T`raLDOzNrq|1KaI{0a``c8j=BZj{usp~p6;;aRV+?=+ z-uEK3dzhrvwRr|-KCcvAw7nmf<<5?A;F{LJmG9d@sc>AJwJSY=3bxA$0c$F9JFR#A$P(< zgzDnwO6gV#<63B16j=1a0%#F$qe@_JNjWf#pP;}32%(??xS*Wk z|2OAekV}*i18()~RY2>E2G4-odzwOtzE^-JN{s=2^O|z2E5+hGy!pz&fJsc5G$5R_ ztNv8LB}#4qxKoyEG{w*?YAp<)P%ex(8}B``hu0WBZ_%3bJ}n^n#&M12-3+xbP;yTQ zeBycVoG24sN7=ONHd*|Oa^7BZ%#$^^OtWT{E4%1Xkw7z=qPy%>|KrC2{q^Gqf)zvs z@p=HOO8|cuGiebx^O}N;?E*#m?$XUqCb$Av)9EyQ_OqXrW?VP|!({1M04zWh4pp}% zCg=~p_yzh;pZ-1i?C<G&{=UzV|arXSi+NinSC zj)T^bKf0WG9t~WZ_+i8&BU$&$Z5b_Z8PKY$tVzJA46h7 zz0m0QM9(CE4@zaN=aoFy;eJ*Hu08{}I({v{70$uAx=G-y-iwNXk=?(*-_JP;CblH6 z7SLKo&uAwkWx)W!vN#f-s;vM+5zcD zg*sm3<75@tygLPorG2!xdWTjv?nnu7OQvF(5u&{Xks(M zU=ZVN+Ms^FMYkpycxGnk=&&lKzJSo(gER$#Rtkk3)M&=2(@D_rg96RYO;Z==kfNV< z1xQtS@b+F(`eF4_VSp<}XYUkT^(Ls#0YrvRDr8!z-Fnsh9cc zrNFvjk@TucKI=`bpH%_ol6`_!@xE*m+WpE13YVJl_(d$h)vCf_10GPK@Xe2X?ct2A%r?cl)s z*|g^^>0yNm3s6V7HDDa?4ciR=&uYr^X;1CspgdXi{NAG5i#Noa=@VNjj2Hd&uYaAs z@P#i(h7a6;;Sj86wtkoa2>#v+FVL$mzDT?25czMuM46kf(B7-h(GNcV8T#6P{+2-U zIB>O%16Q6X$v6kDO*(v7AAl=*IgQ|P23#+R&-hTT!edH5jqA#Zt+@uMg9&!xbb<|_ ztw&WvNk1>;n@~KUyu#kR)Xs>5Tvk^E+rh!eY30Dfpfjp8Slt&A@Xg7jJajqmi~}}9 z$*2B40ImRuLT@~{j&d*_1+Mz-!%C>peCX28aujq9sjZims*Wo~zUcX{Pqgk(%tCtye;9#99GpGVy>pa_!L?_ zl&CXsR9zIWVYoGL^$y~1#5EYXt#yYg6V@Ds1y~!dL58b|sCr`P2hW`9(U!xzs z_(SqXJrqeXboKI*Q`qIt7YZuW(^56TFxp`lJrYOYci!8j$M09dx+sb;ayopwCjFZ*C6XS`$LRIU%Xq*8@i6-*N!pdt;ONje3#&U1 z;SdX=8nX5xuNVUfwO)uD=R5VYu5W z7vb~w75KXgb-UUDwE85Y7Dh(JW=Mdw*z_~#!EION(w->`?w&0Zt}@Jy%5MEQdRvD_ zxoeS8Y+DAm0pQX5ir!rqH|47#a(P#2%W|Jm8JqkVQEvH_SrdL_&@UNWt33a(1s3V$ zH@{2I|L|+HWt|ro&NWqeaHYl2%ivmX*_26Dm%lT7EVrj3bl2*k$*mAgu7w#Ovh@0# zi*9cvY2BNkV!1@OChk(c6z4f%lR-2$a1Mq^Q?3xCaKz5=YNu?@N1bkx4vz|SZ|WXZ z`8?R)PK}7WQASMqfa2O61+F{zQ7SCp>Ss9g=XjJDB1(4urWR4_AeLX};k zAVX~c9snf=Upn})4!+GU5oht5SG)@_0$Jp-x_CHvn8=k~8^Ez=+{kY}6IgZUHU{f( zYgw=WSCsLpEY(-#63Qlk?v2^+i@We+_Od9iDrR?}8U+@8tleIZre|hoX?~8@-uy9nU;iF?zVZ9y_~ut= z?~Ui_jo<$?eeGM{z64xt18{wJ6u1t-tMtB})TPfA;0h(iIPo26v~yXU%d-Mky#%(c z;%Wxa+763TX$F&HT{Y$9Q3{N>n+8uV8@(LG(md$^$npSSpu98RjcKsTlj;GuM$VNV zj{;XI=M`uTXr-UQvE{TE#kd$tb@h@>Yh4%x)c_wq9D&ebp@45mg1oi(9)=EenE<$I<-!Rm54OvQF<3?_Ce@dy zsjPrhj9Alo4om=}P+Y?RQpDpl?K~_LZ}a^;fkUP%dKRI``by|;wMteXN}7>>S!g8~ zP95||fBJb!BqN02v0wb{FUVrI(Ko*J4aydITUB$EEkt=j8K87FK$Ut*)YP}(DEwH( z+Z-NO`1s4PbzG(gd@Oy$$JG-ak7P7OT}31g6jq7GVPHLyaTZ!1JTB6)0bVhV9_487 zBry_PchK*ujok-uK29>gMnCc33X`To4O;j4_U?lWRrey|sGLM^vH@IARn`vytp;!n z)y9D9MT+Z30N3*)qV=^#4I?I(qg{m=T6=DWR-c$Mp;r$kP06B5MdS1%a{qbv;AiY8 z{0vN)punPhS$bCay=*zB$r)G@V8zHcy`p5oun9m~(tBYwMR@?P43D~^Tx)y8s7Qy` zkHeIy+_CU`G4XY)7OTUziOTAb9})0cP|7QLs|=ek)N+M3X?^P+S)H?#;bT&Rw*jJj z@E+>?p6h^6ULHNo<%WxLHMe};4JftS@Ky-$+{Pl?z0fqvbJw)qS}3-8J_d(MQCjo` z=&m_LZ*9itzB$Dc{uz3G$;aTDU}#8@)9s_x^;LfU9PJ-BG;mE*vz?%3M`f@!TQRyd zImz&96Xg{^Yj2Up$&)9wCD5vr z+$~rDdih8O=)d0*UH(sL~d_R`K^FYbHuQ~ZbeHF*z%{MgoeMQQKaUCiv zuH!bq=z36KxOWDBj(s&$Gs9Ww&4D-MQ9#|@gbzD{$lYtpB>pqVJ)c>vE_;k)$Ot>@|A|L(t~ zAO7%r^!y9Y(Zv1hbbIkN-Yyd~=ebU^PFlS2{4S9Tb$VUn@ZO9bW#6 zfB1)FT3Vpo+)bMM>~E3(+kZv}*M30n+t=vEbKjw_e&d@Lf~(bPqc01%exKg^$WK&OYu3 zhO^)rRS`+;ux8X0wHFU&hR6KQ8^!F z$Ti#_Jg%&H6u9ax#TOZ_UCC(}@1bQhdy0x-Q@W|3RlZw@$kEhS`QE~y3D7!FTw$!k z&{{D`7AG=SV?2@#y8_ob18Ys|Uu5tMmdzAm5DhV`hHAJp^D-6&VJjtgk{4?^WMKK2 z<|G^B<@^k;0iLWR>kghAZSo{9OwoLRf(aje|Ht2<|DV10{%-WX(mely*`Igkm)Wy> z&dl!goZX$CnGSY$&$N5p>vng$+a2t-ygdjJA&#)}u<&wKBEp4X1PjA%SepqfDa z#ag`h%5#V&{RF8QT)DO;D!?3ti8G+Mv zDgR9=Q021Vt)x7z3#=xz#~6WIRD#uY*XZ>X0oqsrT$i_f3REkAs{+=5#3IG~h6i(- zzv-2h1%7laTnfkQsYI%Hoa2=@tu9lDK;d(3JI&sKw5|pYkVD@U| ze$MnPd$K>s1bALTt!C@)yj?pzTnJ4JxjIzKdXJ-j|>3NLK?{Ib)`33eMRZtqs zv6m}D@CZHcqvJ9_$Bm}VkBXNpd))@pE`o&#G>llJEcpCzkOuEj)L!hwi4Gg;hTIsj z2Vt^0V0T%itwF$bIfBu#7Sz_g1~Z*kvotnbS`NYEHNjx)CvbHOs7}*wNqa{Nk}0Qv zYt4pyFRJtmt`lLcw>8$M1erLZy|58<+M`@4LjW42_X_c<(K`aVo{!x82(<9>l!BmK=)I%chZ_GZv((ca|d0x)iGHgokCWVv+(`yW( zJS~G4i!E`VDQ998WY!3z*0bdfd%KFzk}5Iga$)YBmGWa+9}6@!Xr8aCv{;qaGE8^T z_UhiVQYK9E^xGOm@Q9bm%j>n(QqcMaj!MaHCqd=dL?=dU-DvJ57#(jT*lm{5;*N1z z&e#dG8Bc3AJ1R4(+X403Yw*yM-^HmjuOpk!!e}$$hd;UxCmW8?Hk=Y~)>adj|I+rG z3Ht4IlBH!>?WO(Tp4P5$dQNaXU~Q!J(Ee!I<8E3C>|D#gJJocpAIiful!{!eSmr$YDdYuO05!o`vJcGuUds083jlo_OLXcvGL-Zit3ZU+LEnlj}%XT%fd{H(urv|_N zDSH82c`(l4nwp)|gqS7~%>)Tv9rBz-n<8`p4;Pg|>%Fa$Q`J3EEBdYGlr>6Z+Otm# zsaRk~&!8S;$)ZoM`(o;4!o?+;C2)B_Kc`X@_Lo49K0o)k7J5;$)m3^wGPoY5Z?uXS z)V|*0qH^a$^`(}9S*6di0Kr9B?t+_|T>8RWj9+1z%;Cz^b|d zF?iAK{bJCB= z;JUUM!TMHIo^S5P>GhcWy}lK}##KBOgWepXfRf#fv65whU#nY-@uD1azS1%tPm%dldtcKGzbPv_R8yO?;_DCOVTzW=F zOUi+Hy}3o&@A>i4GGVPGI7QcyR3YQ~>$Ab({VDZ$Ww9m(Ob0U4K7wfJ{A}UAitiPGQ~F~S?6Q50Ino6O!y>=VU>oVxGP3INX4zJ~_f_wM z<^Ebr_~V1CA=oXMO%@Q6a@$dr!4x=TV6vVUpyj{W&SoswB@or7`H2ejYGOzy+m6UQ z47(>cL!w zd~x`+2B->Jn>6bj20keV)?mBP6HCii0#K(38fyp+Pl$a@%g9-bI6GkW4xx#AyN5Z` z+JtUHtz=l)tCd5ys!;%4FQwD$zuIj*izkmffKz8*hs)~&CsBe!8I`zl>XN!gKO4#C?DGLa@BnxJ5{U# z94JwJEfLkxEljGErIw5E6#I2n^xv$Yn5%=BzAd^ZtqxlZtSq7=LQ3g9k)}%7+>$cc zL%?hKHXatD4pO<*Gma(TdPtDInU85X_*Xp!H|g0f_Gi`SWyNZNS32JkGhw(UZ8V5b z@XG$K!KCugWx$JYnJ^75%k6`T_nAK{zTt4Q^69GLS~n?uuJk!nwJWMr(l2V#6Ovh- zEDDuvbqK&lX6TsAa9Kb1xhjbCviMTQEWNKmO=(yFmO*xkfIhDTn}P%+L4wvv0tml& zb&k>eWAG4wGRS(w!_KeOe6>3Vc^J`r8GN~qwfHHCftXu`kl=3=t-C*E9Hwj zk;sMM4BBY07=SZm#P~!PjMh$gf)=c7Okrg`i?^<=YJI9pxJt3TJww;@IaMmWJ%u&8 zmad7=Qkt%(NxH7aB{tdOhjVgBdL+|x%Jzm!hBGtff?(0d2iH-GF%>rf*Esu$iWrlzD!3-KeoseW5WOQ8 zkRy=E%udkq#`oZ@7+mMgNH5rx$dhDLOM6)pzitbhB4CYird5EM%at7httlFt1i}ss zjP;$_3cVSp=|M<|v=ZCxXx zaUVfy2rkbM{?mW@51?@|UVHOdWau}6g_wGJ+tJsrGO!G$Tr$iV*L23cuT)r%MR^Q< zJaDZN=Gz@by{*cR}!dXKFtAFda1DDF{h89O1pyP{%j ztV7!h)a2}wO%ck0G7R3 z+f65Fo)$dy{9}0J@gLxUNAAI~(?>91YeoM=v#2%?I9t(UX{Klq5bf0ZTE*H&_nS2; zi=KySnlG zb3cXM*n_on7v>Tpn4*i?+EM~D)6*F2>k|>Ersn3GstPl>{(!-iONH6TRR>qCzqJ@# z#TI8nt3a?s!?UW69TMzrNR@1GQ~)a z05anhfXL2i;F|S|$7UkuQ{Z|F8N!mxW?5OMz#?Z{=ln&!mhOVkQZa;9gStX#?9Bdh z;{%nPi!*4`rNCFbPKF;HS_|N+XC@Eop*;uyi%Oo$k4@zab@k(146SC7xC9pcM zT>Yk5^U_}3VrAsR2#ztJh0DNIj%#{CAT>1?#$?I^FP(o;f!RqZ+484sYM&YIGS+?B z_hOiqzF2x5DHBX8FZI4@*>icRb0rVpGSAk+HgKjswRgj#yb?pftD`9Fg;1)(HUUzPiz8jUEd;bT_=Fu#L`*@^UFzGyRnJwompJEJTL0(YkY0p zQT8_LT0bk7yfVyg?xwV0xaCkK#0;t2=ei=XC)UJ&D{GuERtfADEA+UEAQQ#%rVlHd zpB`}CEY7$RxaL>Gh|&PR+`F|nI9soJUvDqC#^>z^W?ZK~0=Q1yW^hf;8I&)prZCKk zv}}zNPFY2`DKo2jVHs|?yjGPDe_U{7Z`k3RW?W+go>BV#MrX!VPpURQ!a0MIR*6k{N>PE9O2B69E-i__A;{4KP!E+ zqx5;3#4;*0QQTqg)loj85w96h=U!c88p5pC_wSY8QLgw}-vptTaX+Qbg$Hv?R6SAVMn z0W6CM8bduIcEnJrvaY8EKp7lWM)ZVa1v@P&Ysyf{Fsb*as2>WkHKX6$0h`|dvv&k-1NCU`tHFr99o>ct z=omdKO2S=c22%yCk}bA3pxe=?iAvRI;CdP+S1-=i9jEVBGg^q*#vJ|h8*f6t{UZ7( zI>sB&VQfT?xe2`%dbDvgNEVrGcvx|mGuN_QI{tjTmG>-*GM$n24-o+O+ZzeE&!UZe zc00~|Iy0_1yk5V4T{iq{U;7%8$>hGWVI6QyW(%Hr;t4F!U~VUT1kusQFyng_*Vha% znoKf3LojDs%eP}6xDE}gQsLKL#MQU=GOinXNiYHGsuoL|X)J6cF~6R~+!_rKmcmFa zOv)fu_vWk!4!09r%fTt{uBvictn3?=fvqO6Bo-rTdqP<*(WGt6cqH=`PPizXVwIDi zn9O=^EhAVdqga+zEiOAPDaV}lR)o6pW$RWr(K5fzYT#F1s#{VDTTrZ>P?@HrruLd~ z>^sK$;I%B{dNbeJl4%aIcR2`Ji;iP4w3e4k7Xx|;yk#rj04W!0sx*xbt%c)r3*Z{! zGFV+DxU4!MM9XA&P0UQfAD@6bVv`#wvAmzoP0j?m<#UP8RxTSaK5n`d9PQs6Est$)2srtMK!BDXqhk}N*W zuk?I*D@I@%!Xjr?x4c-|_9=zoO%E2g)!%E|lVYK>!qaIzuJaqLEbRKN1lM9=rUl8i z^7X)WFt}#M1YSQbxE8}JgDV5T^rG`XE1X*b*ZJEIu6nO)mcA=&6O*7|aU)ldpoXqf zS+T}ZD#DqC@lUkB^=4(kY!y>9Ufc@>txkn3h5EfvyhWICb3zYTn{ag3AC6^s2vATO0S>8rXC783TN}X82DMKmutg7s!5@%BOHfQC1-d3KrNxV^&XjH3# z)-^)lN{^j1U%#~#Chs7|JtOEIYsJOx^XMFD!JxeZ?IQ%EhO;8Z#L&vVtqhK0eWO_b z={7uVnu2kch4z;qxUu6rhRpOmGqj0MaZUR%v<#j_ubJT0(n_%0L~#*=))tI7S_o*H zXnWYRwN7pzbQ5H^447vK$YQjjUHFDSlZ5DZk_FKq5`W~g4Og~5HSXZ zDR0$2%u1FbpA@*3drFoF?r3G2#i0-2IiniRxFqW;3d6Hu1S59IxTfj(WY&G_;96W7 zTL!HquvyK=^nkKUeR(m>zg3{E!m%z@v{g`ANsYM}T1!F$vtqE0l$8`0EO(|W08Nz= z-}3#dRO($#_(%5>jTi5GS%$s%Wl6s}rA)F8?p5d4V{=a0M())bM`+q0{;LeGlld`%igA^(rNLAv?~xl-zJvo4 zVG|8}M&Kbxors&@r0}H71iPH+CIF`OgpxEM=CbazTMZ`Dek`ozv9zAS_T@a*ccus^ zX9z48(b`#yfBKidK{^|z>+2G(ytR#NHi0kx?dJ)4U1A$ya}L97AEW`R7u#%mb7dZ@ zw5&{?24*XqWu2kv@*;S$L+`QL`KYlSi_0kj-c=EMV$fZ`M8Hd6yRwy$ZP?sV0Hi=$ zzv54;biH2Qj1ibdqz86=SHUco9ow;D!bp3lqi>Se-QMxGx1*csP-b~rhc2trMlKTgQM zmHRy9`7Fb$UF=oNt|1t$z0$tG0!KQymMRN>R9NLqsY%LsRhSCd)6%F6uX^uidQK_l ziYOCpb6ek8c%uf%8mFd|Z4N`OWEBe~$E+kwz&mf&%8<2~)}t6uxn!4xpV&WkOd`c4 zxzi?rJC&(brN+qvfHqMjyQ=%e>KRYA8R9H!jIN6|p8*%UYA{FxbZ>-TpCCY=5LoBZ z=x8BZYE*u-Q^u;mdJwpVr^aa+ynHiV(@Y{rl_21cu@zX}P9S4K!;CeJQX1wmVeV~>(=}Jm zz82fx6hD@O>%0c5mBDrBUl&|&bGCI{>xrEpXq`w7OMfc|Tix_%Vn&?lV?R+HF8Ps5 zhB?#9eXC+gGk8K}XGc|;FTvUsC*X_>@2w$`Dj>3&^GkQw+GfY~3mQB%e} z-*;SKnZZ`FyIOy%D$A`^AgcAQDi2eouFFu$+ra&!ye^e}RsO9_n%XgM2Tv;iY&=cC z*dV^FX8!<&t=$ByHE8Q6khFARz}A8Ko|Dpl+Gga^tH1}PA!8w3|@pd@;LwNqx$MD95Bj_D(K^H;mpp}7k1pVeFoM}9Y9zzqx zTs`O~sO=nU!~i`G+1m-A+b}%Q0h6a225&d|#v5qfCJZ}T(PyeBc&(#l65w~$!sT{; zy1})#x3?m=uB@y|Z)@F!Ml{sCfopR^*b2Ug!0?mUNY&uR2JQ6VMVYs!y#}+>S%TC{ zhXB_h+<*V~@#v1ja#bp)Oa8_kprx)Y!PFj%61#b?nRlHUU*<4+bRkJG5 z?Mg}ju!;g~HFzzEDi!&+R_rw_nYRpFE5MtSziOeEeOCn6X>WTlIEuAC9|OG&hywqru7Boy$g=Uf)73pNEZ zH29jP!5yn>(qMaXhI`me6h?Y~3?3S|`O?D((R5K7l!azED@>7cYHH4`_&tI6679zv zt%nB0AzEILruAhAehEZEX&UHd7&_vZUrG?TI*^}w?KxE|FkY)S!^ z;ZcBf)-2XGY)h1-^I|eN28V|R(4$@G8EVEvV2G~sYF}h|MOjr5Uy}8irCeAC^t~99 zY-*I%ggIzh?s1Lg)q8sIG;vlSCTNYNjj~@!?#-UzwhJeQJ!S$N1J3k0Q8VbL@M0o1 zO~-ACKw%PR`aNhEbi)zKp!R$-JZ>vp7d3Eo4-mNi9|EpN-~1_3S!H*_qD-tTTzCFF za`f9Vr@i3BtYQMu2Pg@!o8E8n8;`&ILl9Cf`%Wm{VWSk?CHUanly+oEMI6Dm}QylLV~FGDitN)hKmj4XWG5Ys6nwGQ?x&&}(T%>p(raMp|H;=pndlf;}*Z zXI_2`C+c1mDCKa6$8gu9U&Fuq-~Si9M!(6mO>d&3?>v6^qr1`Ec?P{B4Y<(q20HrB zqow-{Mywq;eg0L9Il5pE8F8`eEKXlMCc;tUzCj#2`6`^Q3CzsQd^*9EGo%)aMc^}) zN)^DB6^IuXmvHa*??Yc#J=W7LSPMLdl;wFWMNZ?FSG^eSt-*88zkoO1cmr!|>$oKI zZX60+@4N3_yz=Uc*txbuu$q-J;OUhpT^vFRRyl)H(yO*tUb?>=u(IOnp);vBdp)}! zs4CgZ95%#{mHk$;F3Gq~FT~+zOC0WVoe3jUtyEZFMz#J@ZQI1QW-d^o-dk-u_jzts zfcj!JV7*tiqN1>t9rRV`H@vA*bGEY8&^|CNgQu#cwg-6BhN@T#mH5YMSzUD;0~H~; zQWQ@U)BsD*2zt}pOR3;VfL4prGTNI%>tO|^^l20rx(dFmdIdm?_LED4!vv`rI`4Aa zZ=G<9RZem?gh0YBfLpi$QK^r&!VQK>+1BFoLkCU<$`Xa*BA?%q688$dt$XL0U6tTg zy#Y`LuR6FE^|?xhRSOZcno@nQle#EV-lnMxTj*~qZIhXTg+*-A^U8KPtSs!&!kgs5 z91VDve3IGaUR8h0A>Zo=SBucHJed*cfAw*>umr(x+9bY-*?B(}xFmQzCmGOS#D;|> z?)9G%a9!D8c*``Q%L1ldDM@Kt!MGT!*}8ShrWxUu{Nx40*AEA;w+^m*b~wufu3`y9z$(411m_EzG0d)pWk4IH zbHvNmMp~(G3A7evTYV{t^vFjB*L^;&W#GEc7U#Bs>%=XCYo?@Bn6s{<6eE&#l?>}l z5GLCo%#I;M(oO;-t3WP85v%bgKPI@Q1YGU(=nyc|fmNOt!n_Y$^AV9l9IR^^>ypf&8vwCW;O46LlqtiaY-0OXXWgdC^qklZ{Psz=}0 zML2xJH2&agjY|ZvV$-7qU@ec*p4B~Q<$Vovg~-&nl&fzDu5&CSrc@O&CHk==#81UkJzaLE_A;cE1z@1v6=K-AR_Y#=!wQ~{a zY>FZzf^>_^Ap+OIB5-|9GOi4+UY`lAZB0lMz)mes()koXUVwD6D1yk->r)FpI_Eu@ zUGQRR(MzxSXj&hpmi#5brQ(TMJKS+)Q)B)#WLq^AVa~L=6T`3*yt3dDXFrv#%{lQl z)k}A|jF;h(m4q4Cbk$xR9vLRtvZkIMRsZS<0!H>~9!YdAYg*aHIA*gE6m{%ZktU9izWr7Qp3gW3i%U%>qXkaAL6T zA^;zAw_}Wea?C!6uHlPl>OPI$@n&?4&_2gc@s^_F6xOwy>+6<>?{xhn7*07i1 z<;-rUneBII8|kr&t#QWA6R5VLspmY*?hy>zI?y~+3%h>+KYis#INk6%`V4I_yLxe| z{uTVqKYR{-#t!`LKYkH47mnl3d+)#xAHA1;hmYY~-}?%_@Hd~sUw`>?_`;X|0{`*% z{sT_eA4mIOBmVB6zkomei$BJhhBxUNbrHS8EqLVd2M~=$FgrV26+FvJ#eiA{l&fBm9nn-F%o!B?UjW~wsmU%=c_}oe#51tpE_Tv zW?Za3y}#nYRVa(!iwW&D$9{OtsgPr&DF`byVFj(++v=x*S5Pc)xQ}&6QxP^RuUC5C z9Uq4~X3=b&xOdx1$JQp*BHT9?&hRlX(`RUqtSrwTqIpB~osQ;|XwCFWh+rrxqI9cU z(^BT^3yx!ba}H~pSrJa!xx!__(^y-};@b69^o?}l`w!eja66Cpe)c{tUA}}=Hi5tW z@)zN9nP9OF(GTHCJpb~O`0`i&3V-^!KZMOSjHdP)Ja^go zZx^N!%c#Z$Qdhj(Idq#X@SXYf7xksUR4V_>w6 zroS7H{P#;adY&1+TL! zE-bG3B>TIv6{7EM6pNb?ENq17bDAW$4IoDY+89Anga`2iEx}nj2e`kPB0z(Bf1b|bu)4}aK-enSU*F)QK( zaY@b|GKkGtH80nX6GpLN;d0v)m%9>7r3q+7C0KjhuO7_SExhgt@tI8$^l?VrU~8A2 zT&2Eil?-N_rj5^z3vfnq!wAxMI*=X}h4Cl@>y%kkrh_>JaN%hcA$e}0Yn)Z!FO(mb z*BCY#aJk%A!K{H^<80+#uWC8C>QKo(xxCMrSuW`%h$L8NAuR62^+k>78LdZiPYuk@ zKH4w#P90bCaIfhB&{`hF;2I&I=3wg^7N_Hw%yQ<{EbS9))5QBXN!!T6Td_R*ylO&M zxpApz@#VbebJ%cUZ~}D$0i5e4a2<^xIK6`*XBubewUdJ(oFDbmx|XoKGAGx)6}onC z4{I^FzVXI$$mUc@@GSji=jbmGcJ8dsZN| zRVzp4{ZtuNm0fKkxNT-nR?8XM7YaiSS_jW#$ig>^T418z+m<0N<7J5L#?!Anic>YO zpx@L9S8xQ!PCbvup1dFZ^jpr*`S6eK$Ky{wij(J#VZ_vjA3k;;I{TV%*L~l@k)zMx zxtE{7V^2JYpFI6T@po_OxPUMH<6ok&xdz|4=d1YcUEf3^5f`?L#bTc>aAlz6Y^%%V z5+&eb_~aet=N(-=80|QRr1=6emUD<1PvP3S3m0mS;l&qUlrm$U?`G@S2d?+tjpvU% zi<$K_;(3phEviAK%C5>_R2yLLg?-;(^uT`4TZGnwJYUP9wOlktm6*D34zMivB4wLi z`tk5ehFLsa^I-x8T1Sf2g@b$GS}bf+q?B8+98>Q_Empieq|$9ATasd-oV~J5pKO1n zj4AgL`UuqgDNAXYb(yHoLC3wkH&x%C5@9JFUdqbGD+x+f%QBZMF_$a+7H3>}pAX6+ z7MD5e{nred%iDAK#fQJZjrXqO`nzv~|NZ=f_wdVK{0txd@)y8|AJQwoLixWBfAt}L zLGQo+!TWgU{TsM;<0>v)+rj4Lb*%0z(x7>U2IpDimJ&!2_{L|0B9K(*{md$G@(Jit zWNaGr+Qh;|GKpEML^$h^a$y!~5^<(n5W%TQ0zBA#{adhf-lK@^Iyh zD*LXw2{^r63QXaO_rsOwr*(`ApnB-McPA_&v=mIa;0e=y60jxt{F!sp=fQw$qJixg z!R!cu84m<40<@V09!yPQW|e?wla*>S*xcbh)>$mBrs4IQu(`cJ*V!qRbrdz=Jhpe| z2wc|*7(4NUAAS!zmv(Xe#@o1h?FyEb7U+lUuMrG7@S`UlpdYhuNCx%(hwj85eC9t( zDR67od4kg?kj#Xzy|awF@B21-2Ac8i&vylCpMB}aaQZD6w~ycl1g>BG#^2-E$(Lx5 z7s6|Ayo48CdYaZVgRPBuJn+N2as1>F434%7xN=WzH0j2j_uN5{dlIAO9(?ESucM28 ze8;T=_?K^f8Lgdl=<0943$HzaC>^)u%{1=&;kVJ)c8Y%NFW|2G@4(;u{pWG?#81)C zavER%);|&Sein~D@qM)R)d)bYZ1Qm8L3#*(3_hEq` zmLJ*Ql|xtZ)!{Eg0GXoTj9Y>tO#@$!)EvRWEW?gPvQr#k4h>``^W5XiF^(Yp&Hd2~ zyk3G@4(XjGaCOspbqKv>a9#N1z*V=oQDGJj)C7IEhb`T5!y=kv;3Als zQ@*GSxF1JRIJ+<^zM-kvA!HZF=zGS3OT+YgJ0jCm0$0wyrltvA2>8--bWNr2wb|K? zzOe@Q;{<4QJ;)NY=IEHm@-)w!SwN1#HIN*XY`Rj99+T|4CoxRd2aXYVQyiReAEWDr zL2hPn={QfvW#kOG46Y$AKNeA=y*|}Kzp9kx8YNG;lO?1*V$4cW3FM*?-J{z-ERy?ZWmY}s#j7X>+*U_RfVS1$Vl>K_cxlxKCx_;2{ z&l|;F$Q_$Nx6MJ|<3w#gf$Kn+AU1#&W0ZizhjV=jT}BK9>5 zuD|zts>k(s5x8>9E%A+|B3NBg<-X}zwk6~?8TKhR)8|T`Q*1~wsho9m(`UuX@~kpH zN!R>v#z@<(Za(3T*=d`J8pV#C6)sb@eN2yS_Ley&^DT-3Z%&vd*h8C)(k z9;NlEUQ;EUBmmm4pp+q(AM1Kw$GNUo1Q2;SIxshD%E2lldrIC@J+chAdiid>JgR=! z)AHCw!O&{<4PeyLgNxm?PXw4f;|*ei(=>2`0F#B9SbXUWLGDRiC^@kR6uw&Q>M_x}zjYmVc`&pv`v#95=`{q&o9FIu`9(VwEer54}# z&R6mB>(Aky`@fB^ed{0a+5i6EaHj4}0@?>~|AY4-nM!^-!Ik@0UwP#feCu1^68O{s zl(VosTHmYJ+>4;O6Yl=AnDO@D>RJrBq!-^O@H}?xI6nB`gF^vwIk?_+_xJGJi_g&D zATC)SRTf+dAQ8<L+4(}mk7eY{+%nVm~$;7S+Q`8ZuHCn{#oip6QlOJU0aGgK}*bPK+v`^28g zGL(AOtB?T{OB;)TtEk21OEce5&BHZ1vj%On?SsPJwV1E%ImgdXXpf%7F?Q*J9x`9aIa-(M6@vl~lP8D#Nd; z23-WorNT@OsQ?H_Ms=bDTuZ}I0<9|JS`Myec+y5X76cHn0r77QbEY+6l$%k~cgx2r zYKJ>yrE8j>2FGEVW47fZ9iqc?}`^C6~FTdc!%t{<{YiWWo8jNkF1zOj3IkTFR zZ0pQ?l)%+YP`WIBtQ*^DDOKI%UefJpQ3~e%*EdeQijDO}ymS3BuD^W=AO7M7?z;Ee zNTvhQ%X;qv-^GugdI&##;YaxVU;Q~u)_!#K*2~@7Y(9!Dg5>Yr`waruCcO9aZ7BnO zhNc}dbPzPwpslMO=NnJpFTU_+I9>M!PSX$S<4-<}{8XF8QaEAG-(s5J6tjg@5?U-=Mjz23Ds5fBu($f*(BkJpl_AfSO-*D|o%FJ+6CM z*O}!Bg48`&)njg%!&87)elCAY&}$U@ULmv|)ZCJbK*}YME1)~$B4E<&`$&AkYsKNaI(firY_1>EAvG?tp$kwD8fv?PH;7fl}u*N4PVrN0b>(ga|mp* zoavg7GTn~@u8HXpIS*ZDPqCU1)UpBZik;3dHGrw0(wBQuCeihuyGL9)0ZO`Vswh5LDuF$e zj0VHa=pJd3GQ?nFoQ{K2Ws;}39l@Yr^cJA?=HRL#R6(4HON3e6DVQU$of?uG2Wfhp z!POP>pnKeg^MiJr8}i}oPynay1gjbUjb7g8PVkHjYrQggx9>0-SaV7GZKy zxCClP;7pGc6nQIwYjGy_c7yA<2A-9{wf~SpOuY(nSzL+$+(o}*44?f3q1MSBTp)Nf zg?psObu7>+indLn9IS+ySa^uDj=eNJtLSoORe)9VT|L!yMEX`4CK)Oj>ZD(_?+t-Y zhCPPXZmrLh|K{FTo~KI6m9j8rQoC)OU9A&9 z<#J}u7I#`W!>X)udd3N64fK8X^kQ&amGJhPI_ULAnO_u#HL)cIRq2^)R{y(MrF2y@pPL)qV_E8pLkLJUNIqg5@7S z`w+f+|2Oc@-~19?V>?ztV5?8QgvUcX8hX-$x>u_;i9RXIYOPJ&Jqp zy_W_PTLo}cf=xYIZWZAL_v>s)vKu!+4|?!)Cb+N^!qqVc@Qb z--IibQWiyw`%PxGnDL146srs87Lo|iMSL>B-mWnL`C@1+&t6snvtqB&XjvcXEk%X0 z-jY7UBGq7q5xu9iP`;Tf?Kds~5)A^2%REcKqpX)xhnr&WP#sL9Bst@#E&x@n=k);V zoN{p0KkMRUGpxH3<5J?gdu>~z#Si6yVe{Ixtpb3SMy1yd8{4-KMc{j*68L^_F!<8z zd_DQ#gP-A@_io@V0pBfbUS5+@=fzDeq5mR?E1S-y|uwU8+<9UTz_@ClE%^ow64CTPG`qqb%EMD+#?+SjvS7R(Wt8 z;pMTw6^kpez*F2R!d9V_2l2cQ(Hw1ihBMA~fz}Ak7o+8{ujACBTY9PH)*@Ki&SGtQ zMihcs*l2S{K`VFP4UX$ThE5GKP843BnUY9=nCO#{Onn4U}GgI`|8aRSVt z(QbT*w_%wW#G7Yc7GWlaNY0$T`0`VD=J_9C+}a1<YBAjr;;vB(!4}KRepM`!u7I63XzlF!1xu2lVPv>G(ppw;w z88ih_KTfIe^5MbtpwJ<cG_Q9U$p=GGBYmW@OZUJ0g7vH>4<*lp)oStz@NwCq}itc_IG~0R*N~+8;0~_Bc z6Va<#V~O(cuLE3@0LwN@#CLJ{Jx1nZef&kTzQv*IUjQY`Hj^O+# zXIm$wgt)%X4SO(6-^UrOtflBXJjj(m~qwS`y_&)(^Lgq z4;%V6GPH8rfra*m;j_?56!!qD>5H45$pPoWW$(U$(O87ARCbt~#W0 z)|AVI*#d`Qw8c;(8C33X3V!1P_`dZm;r;FCnVLDX+R~1U_m@~0G z1gHb_J+%7A(AM9AQDUb-M>B1Ey@*AzeT~7}1%r={agffdN%~Cu^c^Rl^~S99_ZXd% zM%V(Q@Fr-i8M9!>)-CTfcAi6@u^oM;c0BjWPtej`hyU@HpT*z*(--i?FMSRZUNgRX z?;Uvc*vm*~(x2XpYi(^U?z-zP5oIa`SI)S8mDbUWsNaF5sSvUW4<3BrJ_64{%+1Z= zt+(Df)H?Tp>%buHzUMAH^~{eEPVrqZ9|7DX{Ydz=vPI1{hDBQF!YhiKIWBW7%DPq& zQ!4j^JPfq%o2JN4CO%uLD7z0@WpJ&st-5M!xQh6X{yv9fUFioUMgv?f(d3NlWWozy zgoPAXO*v55x8f{op-i|;g|!$C_1@M)_1#v;UX=^s=)H%==dr!J zMAz;OT)jfq@XK`V-Co4T)+{~FVQXiB0CokJudUMOu}&bof~(h-@%B3#xN>cY;CB)4 zzQ2L%@2umkx7XzToy!EpG|krTEFuKuBgPJR{NuE&8JczzeuCG>pLzh_x%+Ek$#ePY z0s*UufcGT?LN>hj^DVse%9A2A;qZ)zz{G=(+(iS-=kWNG57HpJ2V)dp`NrR&w*C!V zY&|CeQ0E&@qN(j1e)7!2h^9OU#yt4)SN;~AT^A9JdhyNgegzNx=pJMU@D{h?0+ov! z1c6%Cb>0tG}nU(#GjUhFiCQE|&u9 zP`L+~d!YE6<>6WZTsgx!YbMCDXh2#7ud@#N{qw@)?59CxH-W2zVAKAQ!IhVpo9Buq z529(hw$tD*o^&9Yo{)&scQsAl-*nzX>!I&()C&8=0F2fiif(wqV*;~$Q-ML3ZxV1; zGo`8ha$jgf)PfbbvYM_Z(@(%kfXdKHVM`2A3?3K^59S!bQEZ4FM+qXd*BNkSS^WgC zy%G+ZZbGuQgK)-;@TAQMq-ndRY5YXjmr+YQdWOzplm_GBs0q__JhKd~(=O@XEZF9h zDH7kFGU0>4HN}uBJ+XA$SgDxaPjSC1U0cl|2O7uBI5XfONDZTAFaT$A4c^=`8ckuG zAaJc62%=%Yg;6(WP-n5UniOzdSaief>MoQDzy9Xa1g;Z>zE+0T*fcv)7zOyP41G}+ zBkJD^&a4W!(lI8WbI|*C{!HR~;iAvdNuQNf13wdi9jowiSoF+m-A~gIUz5Vek!s)!M#e1&bL5 zJkF{zgx2;Q$N7Ob2-H+R>_yFcRr*1>T-Vtw^PFmbS!HOovSKCXbc$g2b$N|J`XU4D z;7N2!c2$)iGvHnvJ)^-ZL9ne>`cb*8nZc8RlS_Tu$4{ffa0WdDi`^sj;-y+o@W9hh@V@;vgm2UtrX04>gL?g6S>{4zPp z%Y)0oHO9~?8CQxtfo48H7xpL((k9_2a1A9rs)U#Z^CiBYm0+`i*igAT?#-aJq+~W< zq5reYYqK0&58A%UHYF9nv6AgjF}P}3=YmSFWP9z^dt7hU$6H)>TqG7%UanjyeWmqy zQWgQQ4?cJo@4b6XfUbIW_0tx&2Yi1~l=1!5hrcB5tpdKk{JE4m-+2GJWPC3%_+Hrq z->H=}vP%TLb0I{h{p#~cSf$@o)SFoxDP@(cFPA#|=f@2=2iv_R{g zClH>IxOROWE9-GA(EhIxaQj0B$p*b~>LrZZdT3kcWS>u;dl_H-`rpvwt9am{J25ca z0+Xc|$7#Am#*6JM3wZhHPet+gxtE{9AO6Yj@=`6jCVDOctSx-p*x7e=fi!gO{Zg4$j$QmdB`UD(0}Ls<2y8H3_zyE*X-~5~Z31@0wLT<{Xz?FhO$5758zN=i4ZPQ?Nz}Jf| zR}XqUz36uLqSe}s^P`<;G#e)+1Hb;FA20Ups{)|XIFxPwyr47OQ|$Z$C+r(YV&d> z%WFQb%~P++skJOHXPbF_oGtI6<@M3>hiJYrZ!6xcc@@d@Z)B

}^pM=B(+$!U7(8 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

yYOp{DGxe{i( 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

66tnZISX)0%7x3$JalV8npFU3D`po$cu5=-N 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 z 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 GNAm$yNg&Vu#}4s?B;C)rN!G3C!RAmwS= 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ø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

    #ec2 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 RDT@2%)z;8gFIKZ5{~y+FJnR4f literal 0 HcmV?d00001 From 0b84f21b478b3cf128a402470085d826314cfad9 Mon Sep 17 00:00:00 2001 From: Kevin Larson Date: Thu, 23 May 2019 16:01:47 -0700 Subject: [PATCH 0017/1992] Updated copyright date --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c5f7e182..76435683 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ CORE: Common Open Research Emulator -Copyright (c)2005-2018 the Boeing Company. +Copyright (c)2005-2019 the Boeing Company. See the LICENSE file included in this distribution. From d9bf4f5926784526be2b9eac4f23e0bc2de8f127 Mon Sep 17 00:00:00 2001 From: Kevin Larson Date: Thu, 23 May 2019 16:02:30 -0700 Subject: [PATCH 0018/1992] Added core screenshot image and a few other small changes to the usage doc --- docs/usage.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/usage.md b/docs/usage.md index 7c070eb1..95b18715 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,16 +1,26 @@ + # Using the CORE GUI * Table of Contents {:toc} +The following image shows the CORE GUI: +![](static/core_screenshot.png) + + ## Overview CORE can be used via the GUI or [Python_Scripting](scripting.md). Often the GUI is used to draw nodes and network devices on the canvas. A Python script could also be written, that imports the CORE Python module, to configure and instantiate nodes and networks. This chapter primarily covers usage of the CORE GUI. +The following image shows the various phases of a CORE session: ![](static/core-workflow.jpg) +After pressing the start button, CORE will proceed through these phases, staying in the **runtime** phase. After the session is stopped, CORE will proceed to the **data collection** phase before tearing down the emulated state. + CORE can be customized to perform any action at each phase in the workflow above. See the *Hooks...* entry on the **Session Menu** for details about when these session states are reached. +__Note: The CORE GUI is currently in a state of transition. The replacement candidate is currently in an alpha version, and can be found [here](https://github.com/coreemu/core/wiki/CORE-GUI-Update).__ + ## Prerequisites Beyond instaling CORE, you must have the CORE daemon running. This is done on the command line with either Systemd or SysV @@ -23,6 +33,12 @@ sudo systemctl start core-daemon sudo service core-daemon start ``` +You can also invoke the daemon directly from the command line, which can be useful if you'd like to see the logging output directly. +``` +# direct invocation +sudo core-daemon +``` + ## Modes of Operation The CORE GUI has two primary modes of operation, **Edit** and **Execute** modes. Running the GUI, by typing **core-gui** with no options, starts in Edit mode. Nodes are drawn on a blank canvas using the toolbar on the left and configured from right-click menus or by double-clicking them. The GUI does not need to be run as root. From dee09cae716295dfc89560c7e5fc1c70f73c6e8d Mon Sep 17 00:00:00 2001 From: Kevin Larson Date: Fri, 24 May 2019 10:02:08 -0700 Subject: [PATCH 0019/1992] Starting to add acronym expansion to the docs --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 95b18715..fa029e2c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -486,7 +486,7 @@ address of the tunnel peer. This is the IP address of the other CORE machine or physical machine, not an IP address of another virtual node. **NOTE:** - Be aware of possible MTU issues with GRE devices. The *gretap* device + Be aware of possible MTU (Maximum Transmission Unit) issues with GRE devices. The *gretap* device has an interface MTU of 1,458 bytes; when joined to a Linux bridge, the bridge's MTU becomes 1,458 bytes. The Linux bridge will not perform fragmentation for From a593289f1b9d9bb6f64450d3f87be7018ac538df Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 25 May 2019 10:16:50 -0700 Subject: [PATCH 0020/1992] initial commit supporting converting corefx to using grpc from previous rest client --- .gitignore | 3 + corefx/pom.xml | 156 ++++ corefx/src/main/java/com/core/Controller.java | 543 +++++++++++ corefx/src/main/java/com/core/Main.java | 72 ++ .../java/com/core/client/ICoreClient.java | 118 +++ .../com/core/client/grpc/CoreGrpcClient.java | 677 ++++++++++++++ .../com/core/client/rest/CoreRestClient.java | 415 +++++++++ .../java/com/core/client/rest/GetConfig.java | 12 + .../core/client/rest/GetDefaultServices.java | 16 + .../com/core/client/rest/GetEmaneModels.java | 11 + .../java/com/core/client/rest/GetHooks.java | 12 + .../core/client/rest/GetMobilityConfigs.java | 12 + .../com/core/client/rest/GetServices.java | 13 + .../com/core/client/rest/GetSessions.java | 14 + .../com/core/client/rest/ServiceFile.java | 13 + .../java/com/core/client/rest/SetConfig.java | 16 + .../com/core/client/rest/SetEmaneConfig.java | 13 + .../core/client/rest/SetEmaneModelConfig.java | 14 + .../java/com/core/client/rest/WlanConfig.java | 12 + .../java/com/core/data/BridgeThroughput.java | 9 + .../java/com/core/data/ConfigDataType.java | 40 + .../main/java/com/core/data/ConfigGroup.java | 12 + .../main/java/com/core/data/ConfigOption.java | 15 + .../main/java/com/core/data/CoreEvent.java | 19 + .../java/com/core/data/CoreInterface.java | 19 + .../src/main/java/com/core/data/CoreLink.java | 56 ++ .../java/com/core/data/CoreLinkOptions.java | 21 + .../src/main/java/com/core/data/CoreNode.java | 59 ++ .../main/java/com/core/data/CoreService.java | 23 + .../main/java/com/core/data/EventType.java | 45 + corefx/src/main/java/com/core/data/Hook.java | 13 + .../com/core/data/InterfaceThroughput.java | 12 + .../main/java/com/core/data/LinkTypes.java | 31 + .../src/main/java/com/core/data/Location.java | 10 + .../java/com/core/data/LocationConfig.java | 10 + .../main/java/com/core/data/MessageFlags.java | 36 + .../java/com/core/data/MobilityConfig.java | 25 + .../src/main/java/com/core/data/NodeType.java | 86 ++ .../src/main/java/com/core/data/Position.java | 12 + .../src/main/java/com/core/data/Session.java | 15 + .../java/com/core/data/SessionOverview.java | 13 + .../main/java/com/core/data/SessionState.java | 38 + .../main/java/com/core/data/Throughputs.java | 12 + .../main/java/com/core/datavis/CoreGraph.java | 11 + .../java/com/core/datavis/CoreGraphAxis.java | 15 + .../java/com/core/datavis/CoreGraphData.java | 15 + .../com/core/datavis/CoreGraphWrapper.java | 157 ++++ .../main/java/com/core/datavis/GraphType.java | 11 + .../core/graph/AbstractNodeContextMenu.java | 22 + .../com/core/graph/BackgroundPaintable.java | 51 ++ .../java/com/core/graph/CoreAddresses.java | 41 + .../graph/CoreAnnotatingGraphMousePlugin.java | 57 ++ .../graph/CoreEditingModalGraphMouse.java | 17 + .../com/core/graph/CoreObservableGraph.java | 31 + .../core/graph/CorePopupGraphMousePlugin.java | 78 ++ .../core/graph/CoreVertexLabelRenderer.java | 43 + .../java/com/core/graph/EmaneContextMenu.java | 26 + .../java/com/core/graph/GraphContextMenu.java | 22 + .../java/com/core/graph/LinkContextMenu.java | 21 + .../java/com/core/graph/NetworkGraph.java | 492 ++++++++++ .../java/com/core/graph/NodeContextMenu.java | 116 +++ .../main/java/com/core/graph/RadioIcon.java | 31 + .../com/core/graph/UndirectedSimpleGraph.java | 24 + .../java/com/core/graph/WlanContextMenu.java | 26 + .../java/com/core/ui/AnnotationToolbar.java | 104 +++ .../main/java/com/core/ui/GraphToolbar.java | 297 ++++++ .../main/java/com/core/ui/LinkDetails.java | 178 ++++ .../main/java/com/core/ui/NodeDetails.java | 181 ++++ .../main/java/com/core/ui/ServiceItem.java | 15 + corefx/src/main/java/com/core/ui/Toast.java | 52 ++ .../com/core/ui/dialogs/BackgroundDialog.java | 86 ++ .../java/com/core/ui/dialogs/ChartDialog.java | 217 +++++ .../com/core/ui/dialogs/ConfigDialog.java | 91 ++ .../com/core/ui/dialogs/ConnectDialog.java | 39 + .../com/core/ui/dialogs/CoreFoenixDialog.java | 58 ++ .../java/com/core/ui/dialogs/GeoDialog.java | 28 + .../core/ui/dialogs/GuiPreferencesDialog.java | 80 ++ .../java/com/core/ui/dialogs/HookDialog.java | 77 ++ .../java/com/core/ui/dialogs/HooksDialog.java | 117 +++ .../com/core/ui/dialogs/LocationDialog.java | 73 ++ .../com/core/ui/dialogs/MobilityDialog.java | 113 +++ .../core/ui/dialogs/MobilityPlayerDialog.java | 89 ++ .../com/core/ui/dialogs/NodeEmaneDialog.java | 103 +++ .../core/ui/dialogs/NodeServicesDialog.java | 148 +++ .../core/ui/dialogs/NodeTypeCreateDialog.java | 74 ++ .../com/core/ui/dialogs/NodeTypesDialog.java | 136 +++ .../com/core/ui/dialogs/NodeWlanDialog.java | 76 ++ .../com/core/ui/dialogs/ServiceDialog.java | 119 +++ .../com/core/ui/dialogs/SessionsDialog.java | 177 ++++ .../core/ui/dialogs/SessionsFoenixDialog.java | 70 ++ .../java/com/core/ui/dialogs/StageDialog.java | 132 +++ .../com/core/ui/dialogs/TerminalDialog.java | 72 ++ .../com/core/ui/textfields/DoubleFilter.java | 15 + .../main/java/com/core/utils/ConfigUtils.java | 122 +++ .../java/com/core/utils/Configuration.java | 23 + .../main/java/com/core/utils/FxmlUtils.java | 22 + .../main/java/com/core/utils/IconUtils.java | 65 ++ .../main/java/com/core/utils/JsonUtils.java | 53 ++ .../java/com/core/utils/NodeTypeConfig.java | 20 + .../main/java/com/core/utils/WebUtils.java | 174 ++++ .../com/core/websocket/CoreWebSocket.java | 130 +++ corefx/src/main/proto/core.proto | 1 + corefx/src/main/resources/config.json | 7 + corefx/src/main/resources/core-icon.png | Bin 0 -> 2931 bytes .../main/resources/css/images/layers-2x.png | Bin 0 -> 1259 bytes .../src/main/resources/css/images/layers.png | Bin 0 -> 696 bytes .../resources/css/images/marker-icon-2x.png | Bin 0 -> 2464 bytes .../main/resources/css/images/marker-icon.png | Bin 0 -> 1466 bytes .../resources/css/images/marker-shadow.png | Bin 0 -> 618 bytes corefx/src/main/resources/css/leaflet.css | 635 +++++++++++++ corefx/src/main/resources/css/main.css | 138 +++ .../resources/fxml/annotation_toolbar.fxml | 26 + .../resources/fxml/background_dialog.fxml | 35 + .../src/main/resources/fxml/chart_dialog.fxml | 19 + .../main/resources/fxml/config_dialog.fxml | 22 + .../main/resources/fxml/connect_dialog.fxml | 18 + .../src/main/resources/fxml/geo_dialog.fxml | 23 + .../main/resources/fxml/graph_toolbar.fxml | 18 + .../main/resources/fxml/gui_preferences.fxml | 54 ++ .../src/main/resources/fxml/hook_dialog.fxml | 35 + .../src/main/resources/fxml/hooks_dialog.fxml | 28 + .../src/main/resources/fxml/link_details.fxml | 34 + .../main/resources/fxml/location_dialog.fxml | 56 ++ corefx/src/main/resources/fxml/main.fxml | 81 ++ .../main/resources/fxml/mobility_dialog.fxml | 96 ++ .../main/resources/fxml/mobility_player.fxml | 26 + .../src/main/resources/fxml/node_details.fxml | 33 + .../resources/fxml/node_emane_dialog.fxml | 28 + .../resources/fxml/node_services_dialog.fxml | 75 ++ .../fxml/node_type_create_dialog.fxml | 18 + .../resources/fxml/node_types_dialog.fxml | 80 ++ .../main/resources/fxml/service_dialog.fxml | 68 ++ .../main/resources/fxml/sessions_dialog.fxml | 20 + .../main/resources/fxml/terminal_dialog.fxml | 19 + .../src/main/resources/fxml/wlan_dialog.fxml | 37 + corefx/src/main/resources/html/geo.html | 56 ++ corefx/src/main/resources/icons/emane-100.png | Bin 0 -> 1624 bytes corefx/src/main/resources/icons/host-100.png | Bin 0 -> 942 bytes corefx/src/main/resources/icons/hub-100.png | Bin 0 -> 2386 bytes .../main/resources/icons/icomoon_material.svg | 855 ++++++++++++++++++ corefx/src/main/resources/icons/pc-100.png | Bin 0 -> 741 bytes .../src/main/resources/icons/router-100.png | Bin 0 -> 2103 bytes .../src/main/resources/icons/switch-100.png | Bin 0 -> 1460 bytes corefx/src/main/resources/icons/wlan-100.png | Bin 0 -> 1509 bytes corefx/src/main/resources/js/leaflet.js | 5 + corefx/src/main/resources/log4j2.xml | 17 + daemon/proto/core.proto | 3 + 147 files changed, 10372 insertions(+) create mode 100644 corefx/pom.xml create mode 100644 corefx/src/main/java/com/core/Controller.java create mode 100644 corefx/src/main/java/com/core/Main.java create mode 100644 corefx/src/main/java/com/core/client/ICoreClient.java create mode 100644 corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java create mode 100644 corefx/src/main/java/com/core/client/rest/CoreRestClient.java create mode 100644 corefx/src/main/java/com/core/client/rest/GetConfig.java create mode 100644 corefx/src/main/java/com/core/client/rest/GetDefaultServices.java create mode 100644 corefx/src/main/java/com/core/client/rest/GetEmaneModels.java create mode 100644 corefx/src/main/java/com/core/client/rest/GetHooks.java create mode 100644 corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java create mode 100644 corefx/src/main/java/com/core/client/rest/GetServices.java create mode 100644 corefx/src/main/java/com/core/client/rest/GetSessions.java create mode 100644 corefx/src/main/java/com/core/client/rest/ServiceFile.java create mode 100644 corefx/src/main/java/com/core/client/rest/SetConfig.java create mode 100644 corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java create mode 100644 corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java create mode 100644 corefx/src/main/java/com/core/client/rest/WlanConfig.java create mode 100644 corefx/src/main/java/com/core/data/BridgeThroughput.java create mode 100644 corefx/src/main/java/com/core/data/ConfigDataType.java create mode 100644 corefx/src/main/java/com/core/data/ConfigGroup.java create mode 100644 corefx/src/main/java/com/core/data/ConfigOption.java create mode 100644 corefx/src/main/java/com/core/data/CoreEvent.java create mode 100644 corefx/src/main/java/com/core/data/CoreInterface.java create mode 100644 corefx/src/main/java/com/core/data/CoreLink.java create mode 100644 corefx/src/main/java/com/core/data/CoreLinkOptions.java create mode 100644 corefx/src/main/java/com/core/data/CoreNode.java create mode 100644 corefx/src/main/java/com/core/data/CoreService.java create mode 100644 corefx/src/main/java/com/core/data/EventType.java create mode 100644 corefx/src/main/java/com/core/data/Hook.java create mode 100644 corefx/src/main/java/com/core/data/InterfaceThroughput.java create mode 100644 corefx/src/main/java/com/core/data/LinkTypes.java create mode 100644 corefx/src/main/java/com/core/data/Location.java create mode 100644 corefx/src/main/java/com/core/data/LocationConfig.java create mode 100644 corefx/src/main/java/com/core/data/MessageFlags.java create mode 100644 corefx/src/main/java/com/core/data/MobilityConfig.java create mode 100644 corefx/src/main/java/com/core/data/NodeType.java create mode 100644 corefx/src/main/java/com/core/data/Position.java create mode 100644 corefx/src/main/java/com/core/data/Session.java create mode 100644 corefx/src/main/java/com/core/data/SessionOverview.java create mode 100644 corefx/src/main/java/com/core/data/SessionState.java create mode 100644 corefx/src/main/java/com/core/data/Throughputs.java create mode 100644 corefx/src/main/java/com/core/datavis/CoreGraph.java create mode 100644 corefx/src/main/java/com/core/datavis/CoreGraphAxis.java create mode 100644 corefx/src/main/java/com/core/datavis/CoreGraphData.java create mode 100644 corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java create mode 100644 corefx/src/main/java/com/core/datavis/GraphType.java create mode 100644 corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java create mode 100644 corefx/src/main/java/com/core/graph/BackgroundPaintable.java create mode 100644 corefx/src/main/java/com/core/graph/CoreAddresses.java create mode 100644 corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java create mode 100644 corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java create mode 100644 corefx/src/main/java/com/core/graph/CoreObservableGraph.java create mode 100644 corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java create mode 100644 corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java create mode 100644 corefx/src/main/java/com/core/graph/EmaneContextMenu.java create mode 100644 corefx/src/main/java/com/core/graph/GraphContextMenu.java create mode 100644 corefx/src/main/java/com/core/graph/LinkContextMenu.java create mode 100644 corefx/src/main/java/com/core/graph/NetworkGraph.java create mode 100644 corefx/src/main/java/com/core/graph/NodeContextMenu.java create mode 100644 corefx/src/main/java/com/core/graph/RadioIcon.java create mode 100644 corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java create mode 100644 corefx/src/main/java/com/core/graph/WlanContextMenu.java create mode 100644 corefx/src/main/java/com/core/ui/AnnotationToolbar.java create mode 100644 corefx/src/main/java/com/core/ui/GraphToolbar.java create mode 100644 corefx/src/main/java/com/core/ui/LinkDetails.java create mode 100644 corefx/src/main/java/com/core/ui/NodeDetails.java create mode 100644 corefx/src/main/java/com/core/ui/ServiceItem.java create mode 100644 corefx/src/main/java/com/core/ui/Toast.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/BackgroundDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/ChartDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/ConfigDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/ConnectDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/CoreFoenixDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/GeoDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/GuiPreferencesDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/HookDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/HooksDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/LocationDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/MobilityDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/MobilityPlayerDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/NodeEmaneDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/NodeServicesDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/NodeTypeCreateDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/NodeTypesDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/NodeWlanDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/ServiceDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/SessionsDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/SessionsFoenixDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/StageDialog.java create mode 100644 corefx/src/main/java/com/core/ui/dialogs/TerminalDialog.java create mode 100644 corefx/src/main/java/com/core/ui/textfields/DoubleFilter.java create mode 100644 corefx/src/main/java/com/core/utils/ConfigUtils.java create mode 100644 corefx/src/main/java/com/core/utils/Configuration.java create mode 100644 corefx/src/main/java/com/core/utils/FxmlUtils.java create mode 100644 corefx/src/main/java/com/core/utils/IconUtils.java create mode 100644 corefx/src/main/java/com/core/utils/JsonUtils.java create mode 100644 corefx/src/main/java/com/core/utils/NodeTypeConfig.java create mode 100644 corefx/src/main/java/com/core/utils/WebUtils.java create mode 100644 corefx/src/main/java/com/core/websocket/CoreWebSocket.java create mode 120000 corefx/src/main/proto/core.proto create mode 100644 corefx/src/main/resources/config.json create mode 100644 corefx/src/main/resources/core-icon.png create mode 100644 corefx/src/main/resources/css/images/layers-2x.png create mode 100644 corefx/src/main/resources/css/images/layers.png create mode 100644 corefx/src/main/resources/css/images/marker-icon-2x.png create mode 100644 corefx/src/main/resources/css/images/marker-icon.png create mode 100644 corefx/src/main/resources/css/images/marker-shadow.png create mode 100644 corefx/src/main/resources/css/leaflet.css create mode 100644 corefx/src/main/resources/css/main.css create mode 100644 corefx/src/main/resources/fxml/annotation_toolbar.fxml create mode 100644 corefx/src/main/resources/fxml/background_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/chart_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/config_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/connect_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/geo_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/graph_toolbar.fxml create mode 100644 corefx/src/main/resources/fxml/gui_preferences.fxml create mode 100644 corefx/src/main/resources/fxml/hook_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/hooks_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/link_details.fxml create mode 100644 corefx/src/main/resources/fxml/location_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/main.fxml create mode 100644 corefx/src/main/resources/fxml/mobility_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/mobility_player.fxml create mode 100644 corefx/src/main/resources/fxml/node_details.fxml create mode 100644 corefx/src/main/resources/fxml/node_emane_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/node_services_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/node_type_create_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/node_types_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/service_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/sessions_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/terminal_dialog.fxml create mode 100644 corefx/src/main/resources/fxml/wlan_dialog.fxml create mode 100644 corefx/src/main/resources/html/geo.html create mode 100644 corefx/src/main/resources/icons/emane-100.png create mode 100644 corefx/src/main/resources/icons/host-100.png create mode 100644 corefx/src/main/resources/icons/hub-100.png create mode 100644 corefx/src/main/resources/icons/icomoon_material.svg create mode 100644 corefx/src/main/resources/icons/pc-100.png create mode 100644 corefx/src/main/resources/icons/router-100.png create mode 100644 corefx/src/main/resources/icons/switch-100.png create mode 100644 corefx/src/main/resources/icons/wlan-100.png create mode 100644 corefx/src/main/resources/js/leaflet.js create mode 100644 corefx/src/main/resources/log4j2.xml diff --git a/.gitignore b/.gitignore index dbf9e4c3..56f130ee 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ coverage.xml netns/setup.py daemon/setup.py ns3/setup.py + +# ignore corefx build +corefx/target diff --git a/corefx/pom.xml b/corefx/pom.xml new file mode 100644 index 00000000..149d4ac1 --- /dev/null +++ b/corefx/pom.xml @@ -0,0 +1,156 @@ + + + 4.0.0 + + com.core + corefx + 1.0-SNAPSHOT + + + UTF-8 + 1.8 + 1.8 + 2.1.1 + 2.9.6 + 1.20.0 + + + + + net.sf.jung + jung-api + ${jung.version} + + + net.sf.jung + jung-graph-impl + ${jung.version} + + + net.sf.jung + jung-algorithms + ${jung.version} + + + net.sf.jung + jung-io + ${jung.version} + + + net.sf.jung + jung-visualization + ${jung.version} + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + + + com.squareup.okhttp3 + okhttp + 3.11.0 + + + org.apache.logging.log4j + log4j-api + 2.9.0 + + + org.apache.logging.log4j + log4j-core + 2.9.0 + + + io.socket + socket.io-client + 0.8.3 + + + org.projectlombok + lombok + 1.18.0 + provided + + + commons-net + commons-net + 3.6 + + + com.jfoenix + jfoenix + 8.0.7 + + + io.grpc + grpc-netty-shaded + ${grpc.version} + + + io.grpc + grpc-protobuf + ${grpc.version} + + + io.grpc + grpc-stub + ${grpc.version} + + + com.google.guava + guava + 20.0 + + + + + + + kr.motd.maven + os-maven-plugin + 1.5.0.Final + + + + + com.zenjava + javafx-maven-plugin + 8.8.3 + + com.core.Main + + + + org.xolstice.maven.plugins + protobuf-maven-plugin + 0.5.1 + + com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier} + grpc-java + io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier} + + + + + compile + compile-custom + + + + + + + \ No newline at end of file diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java new file mode 100644 index 00000000..bfff6bf2 --- /dev/null +++ b/corefx/src/main/java/com/core/Controller.java @@ -0,0 +1,543 @@ +package com.core; + +import com.core.client.ICoreClient; +import com.core.client.grpc.CoreGrpcClient; +import com.core.data.*; +import com.core.graph.NetworkGraph; +import com.core.ui.*; +import com.core.ui.dialogs.*; +import com.core.utils.ConfigUtils; +import com.core.utils.Configuration; +import com.core.utils.NodeTypeConfig; +import com.core.websocket.CoreWebSocket; +import com.jfoenix.controls.JFXDecorator; +import com.jfoenix.controls.JFXProgressBar; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.concurrent.Task; +import javafx.embed.swing.SwingNode; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.fxml.Initializable; +import javafx.scene.control.CheckMenuItem; +import javafx.scene.control.MenuItem; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.StackPane; +import javafx.scene.layout.VBox; +import javafx.stage.FileChooser; +import javafx.stage.Stage; +import lombok.Data; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.event.ItemEvent; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +@Data +public class Controller implements Initializable { + private static final Logger logger = LogManager.getLogger(); + @FXML private StackPane stackPane; + @FXML private BorderPane borderPane; + @FXML private VBox top; + @FXML private VBox bottom; + @FXML private SwingNode swingNode; + @FXML private MenuItem saveXmlMenuItem; + @FXML private JFXProgressBar progressBar; + @FXML private CheckMenuItem throughputMenuItem; + + private final ExecutorService executorService = Executors.newSingleThreadExecutor(); + private final Map mobilityScripts = new HashMap<>(); + private final Map mobilityPlayerDialogs = new HashMap<>(); + private Application application; + private JFXDecorator decorator; + private Stage window; + private Configuration configuration; + private Map> defaultServices = new HashMap<>(); + + // core client utilities + private ICoreClient coreClient = new CoreGrpcClient(); + private CoreWebSocket coreWebSocket; + + // ui elements + private NetworkGraph networkGraph = new NetworkGraph(this); + private AnnotationToolbar annotationToolbar = new AnnotationToolbar(networkGraph); + private NodeDetails nodeDetails = new NodeDetails(this); + private LinkDetails linkDetails = new LinkDetails(this); + private GraphToolbar graphToolbar = new GraphToolbar(this); + + // dialogs + private SessionsDialog sessionsDialog = new SessionsDialog(this); + private ServiceDialog serviceDialog = new ServiceDialog(this); + private NodeServicesDialog nodeServicesDialog = new NodeServicesDialog(this); + private NodeEmaneDialog nodeEmaneDialog = new NodeEmaneDialog(this); + private NodeWlanDialog nodeWlanDialog = new NodeWlanDialog(this); + private ConfigDialog configDialog = new ConfigDialog(this); + private HooksDialog hooksDialog = new HooksDialog(this); + private MobilityDialog mobilityDialog = new MobilityDialog(this); + private ChartDialog chartDialog = new ChartDialog(this); + private NodeTypesDialog nodeTypesDialog = new NodeTypesDialog(this); + private BackgroundDialog backgroundDialog = new BackgroundDialog(this); + private LocationDialog locationDialog = new LocationDialog(this); + private GeoDialog geoDialog = new GeoDialog(this); + private ConnectDialog connectDialog = new ConnectDialog(this); + private GuiPreferencesDialog guiPreferencesDialog = new GuiPreferencesDialog(this); + private NodeTypeCreateDialog nodeTypeCreateDialog = new NodeTypeCreateDialog(this); + + public void connectToCore(String address, int port) { + coreWebSocket.stop(); + + ExecutorService executorService = Executors.newSingleThreadExecutor(); + executorService.submit(() -> { + try { + coreWebSocket.start(address, port); + coreClient.setConnection(address, port); + initialJoin(); + } catch (IOException | URISyntaxException ex) { + Toast.error(String.format("Connection failure: %s", ex.getMessage()), ex); + Platform.runLater(() -> connectDialog.showDialog()); + } + }); + } + + private void initialJoin() throws IOException { + Map> serviceGroups = coreClient.getServices(); + logger.info("core services: {}", serviceGroups); + nodeServicesDialog.setServices(serviceGroups); + nodeTypeCreateDialog.setServices(serviceGroups); + + logger.info("initial core session join"); + List sessions = coreClient.getSessions(); + + logger.info("existing sessions: {}", sessions); + Integer sessionId; + if (sessions.isEmpty()) { + logger.info("creating initial session"); + SessionOverview sessionOverview = coreClient.createSession(); + sessionId = sessionOverview.getId(); + Toast.info(String.format("Created Session %s", sessionId)); + } else { + SessionOverview sessionOverview = sessions.get(0); + sessionId = sessionOverview.getId(); + Toast.info(String.format("Joined Session %s", sessionId)); + } + + joinSession(sessionId); + + // set emane models + List emaneModels = coreClient.getEmaneModels(); + nodeEmaneDialog.setModels(emaneModels); + } + + public void joinSession(Integer sessionId) throws IOException { + // clear graph + networkGraph.reset(); + + // clear out any previously set information + mobilityPlayerDialogs.clear(); + mobilityScripts.clear(); + mobilityDialog.setNode(null); + Platform.runLater(() -> borderPane.setRight(null)); + + // get session to join + Session session = coreClient.getSession(sessionId); + SessionState sessionState = SessionState.get(session.getState()); + + // update client to use this session + coreClient.updateSession(sessionId); + coreClient.updateState(sessionState); + + // display all nodes + logger.info("joining core session({}) state({}): {}", sessionId, sessionState, session); + for (CoreNode node : session.getNodes()) { + NodeType nodeType = NodeType.find(node.getType(), node.getModel()); + if (nodeType == null) { + logger.info(String.format("failed to find node type(%s) model(%s): %s", + node.getType(), node.getModel(), node.getName())); + continue; + } + + node.setNodeType(nodeType); + networkGraph.addNode(node); + } + + // display all links + for (CoreLink link : session.getLinks()) { + if (link.getInterfaceOne() != null || link.getInterfaceTwo() != null) { + link.setType(LinkTypes.WIRED.getValue()); + } + + networkGraph.addLink(link); + } + + // refresh graph + networkGraph.getGraphViewer().repaint(); + + // update other components for new session + graphToolbar.setRunButton(coreClient.isRunning()); + hooksDialog.updateHooks(); + + // update session default services + setCoreDefaultServices(); + + // retrieve current mobility script configurations and show dialogs + Map mobilityConfigMap = coreClient.getMobilityConfigs(); + mobilityScripts.putAll(mobilityConfigMap); + showMobilityScriptDialogs(); + + Platform.runLater(() -> decorator.setTitle(String.format("CORE (Session %s)", sessionId))); + } + + public boolean startSession() throws IOException { + // force nodes to get latest positions + networkGraph.updatePositions(); + + // retrieve items for creation/start + Collection nodes = networkGraph.getGraph().getVertices(); + Collection links = networkGraph.getGraph().getEdges(); + List hooks = hooksDialog.getHooks(); + + // start/create session + progressBar.setVisible(true); + boolean result = coreClient.start(nodes, links, hooks); + progressBar.setVisible(false); + if (result) { + showMobilityScriptDialogs(); + saveXmlMenuItem.setDisable(false); + } + return result; + } + + public boolean stopSession() throws IOException { + // clear out any drawn wireless links + List wirelessLinks = networkGraph.getGraph().getEdges().stream() + .filter(CoreLink::isWireless) + .collect(Collectors.toList()); + wirelessLinks.forEach(networkGraph::removeWirelessLink); + networkGraph.getGraphViewer().repaint(); + + // stop session + progressBar.setVisible(true); + boolean result = coreClient.stop(); + progressBar.setVisible(false); + if (result) { + saveXmlMenuItem.setDisable(true); + } + return result; + } + + public void handleThroughputs(Throughputs throughputs) { + for (InterfaceThroughput interfaceThroughput : throughputs.getInterfaces()) { + int nodeId = interfaceThroughput.getNode(); + CoreNode node = networkGraph.getVertex(nodeId); + Collection links = networkGraph.getGraph().getIncidentEdges(node); + int interfaceId = interfaceThroughput.getNodeInterface(); + for (CoreLink link : links) { + if (nodeId == link.getNodeOne()) { + if (interfaceId == link.getInterfaceOne().getId()) { + link.setThroughput(interfaceThroughput.getThroughput()); + } + } else { + if (interfaceId == link.getInterfaceTwo().getId()) { + link.setThroughput(interfaceThroughput.getThroughput()); + } + } + } + } + networkGraph.getGraphViewer().repaint(); + } + + private void setCoreDefaultServices() { + try { + coreClient.setDefaultServices(defaultServices); + } catch (IOException ex) { + Toast.error("Error updating core default services", ex); + } + } + + public void updateNodeTypes() { + graphToolbar.setupNodeTypes(); + setCoreDefaultServices(); + try { + ConfigUtils.save(configuration); + } catch (IOException ex) { + Toast.error("Error saving configuration", ex); + } + } + + public void deleteNode(CoreNode node) { + networkGraph.removeNode(node); + CoreNode mobilityNode = mobilityDialog.getNode(); + if (mobilityNode != null && mobilityNode.getId().equals(node.getId())) { + mobilityDialog.setNode(null); + } + } + + void setWindow(Stage window) { + this.window = window; + sessionsDialog.setOwner(window); + hooksDialog.setOwner(window); + nodeServicesDialog.setOwner(window); + serviceDialog.setOwner(window); + nodeWlanDialog.setOwner(window); + nodeEmaneDialog.setOwner(window); + configDialog.setOwner(window); + mobilityDialog.setOwner(window); + nodeTypesDialog.setOwner(window); + backgroundDialog.setOwner(window); + locationDialog.setOwner(window); + connectDialog.setOwner(window); + guiPreferencesDialog.setOwner(window); + nodeTypeCreateDialog.setOwner(window); + } + + private void showMobilityScriptDialogs() { + for (Map.Entry entry : mobilityScripts.entrySet()) { + Integer nodeId = entry.getKey(); + CoreNode node = networkGraph.getVertex(nodeId); + MobilityConfig mobilityConfig = entry.getValue(); + Platform.runLater(() -> { + MobilityPlayerDialog mobilityPlayerDialog = new MobilityPlayerDialog(this, node); + mobilityPlayerDialog.setOwner(window); + mobilityPlayerDialogs.put(nodeId, mobilityPlayerDialog); + mobilityPlayerDialog.showDialog(mobilityConfig); + }); + } + } + + @FXML + private void onCoreMenuConnect(ActionEvent event) { + logger.info("showing connect!"); + connectDialog.showDialog(); + } + + @FXML + private void onOptionsMenuNodeTypes(ActionEvent event) { + nodeTypesDialog.showDialog(); + } + + @FXML + private void onOptionsMenuBackground(ActionEvent event) { + backgroundDialog.showDialog(); + } + + @FXML + private void onOptionsMenuLocation(ActionEvent event) { + locationDialog.showDialog(); + } + + @FXML + private void onOptionsMenuPreferences(ActionEvent event) { + guiPreferencesDialog.showDialog(); + } + + @FXML + private void onHelpMenuWebsite(ActionEvent event) { + application.getHostServices().showDocument("https://github.com/coreemu/core"); + } + + @FXML + private void onHelpMenuDocumentation(ActionEvent event) { + application.getHostServices().showDocument("http://coreemu.github.io/core/"); + } + + @FXML + private void onHelpMenuMailingList(ActionEvent event) { + application.getHostServices().showDocument("https://publists.nrl.navy.mil/mailman/listinfo/core-users"); + } + + @FXML + private void onOpenXmlAction() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Open Session"); + fileChooser.setInitialDirectory(new File(configuration.getXmlPath())); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); + try { + File file = fileChooser.showOpenDialog(window); + if (file != null) { + openXml(file); + } + } catch (IllegalArgumentException ex) { + Toast.error(String.format("Invalid XML directory: %s", configuration.getXmlPath())); + } + } + + private void openXml(File file) { + logger.info("opening session xml: {}", file.getPath()); + try { + SessionOverview sessionOverview = coreClient.openSession(file); + Integer sessionId = sessionOverview.getId(); + joinSession(sessionId); + Toast.info(String.format("Joined Session %s", sessionId)); + } catch (IOException ex) { + Toast.error("Error opening session xml", ex); + } + } + + @FXML + private void onSaveXmlAction() { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Save Session"); + fileChooser.setInitialFileName("session.xml"); + fileChooser.setInitialDirectory(new File(configuration.getXmlPath())); + fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); + File file = fileChooser.showSaveDialog(window); + if (file != null) { + logger.info("saving session xml: {}", file.getPath()); + try { + coreClient.saveSession(file); + } catch (IOException ex) { + Toast.error("Error saving session xml", ex); + } + } + } + + @FXML + private void onSessionMenu(ActionEvent event) { + logger.info("sessions menu clicked"); + try { + sessionsDialog.showDialog(); + } catch (IOException ex) { + Toast.error("Error retrieving sessions", ex); + } + } + + @FXML + private void onSessionNodesMenu(ActionEvent event) { + + } + + @FXML + private void onSessionHooksMenu(ActionEvent event) { + hooksDialog.showDialog(); + } + + @FXML + private void onSessionOptionsMenu(ActionEvent event) { + try { + List configGroups = coreClient.getSessionConfig(); + configDialog.showDialog("Session Options", configGroups, () -> { + List options = configDialog.getOptions(); + try { + boolean result = coreClient.setSessionConfig(options); + if (result) { + Toast.info("Session options saved"); + } else { + Toast.error("Failure to set session config"); + } + } catch (IOException ex) { + logger.error("error getting session config"); + } + }); + } catch (IOException ex) { + logger.error("error getting session config"); + } + } + + @FXML + private void onTestMenuCharts(ActionEvent event) { + chartDialog.show(); + } + + @FXML + private void onTestMenuGeo(ActionEvent event) { + geoDialog.showDialog(); + } + + @Override + public void initialize(URL location, ResourceBundle resources) { + coreWebSocket = new CoreWebSocket(this); + configuration = ConfigUtils.load(); + String address = configuration.getCoreAddress(); + int port = configuration.getCorePort(); + logger.info("core connection: {}:{}", address, port); + connectDialog.setAddress(address); + connectDialog.setPort(port); + connectToCore(address, port); + + logger.info("controller initialize"); + swingNode.setContent(networkGraph.getGraphViewer()); + + // update graph preferences + networkGraph.updatePreferences(configuration); + + // set node types / default services + graphToolbar.setupNodeTypes(); + defaultServices = configuration.getNodeTypeConfigs().stream() + .collect(Collectors.toMap(NodeTypeConfig::getModel, NodeTypeConfig::getServices)); + + // set graph toolbar + borderPane.setLeft(graphToolbar); + + // setup snackbar + Toast.setSnackbarRoot(stackPane); + + // setup throughput menu item + throughputMenuItem.setOnAction(event -> executorService.submit(new ChangeThroughputTask())); + + // node details + networkGraph.getGraphViewer().getPickedVertexState().addItemListener(event -> { + CoreNode node = (CoreNode) event.getItem(); + logger.info("picked: {}", node.getName()); + if (event.getStateChange() == ItemEvent.SELECTED) { + Platform.runLater(() -> { + nodeDetails.setNode(node); + borderPane.setRight(nodeDetails); + }); + } else { + Platform.runLater(() -> borderPane.setRight(null)); + } + }); + + // edge details + networkGraph.getGraphViewer().getPickedEdgeState().addItemListener(event -> { + CoreLink link = (CoreLink) event.getItem(); + logger.info("picked: {} - {}", link.getNodeOne(), link.getNodeTwo()); + if (event.getStateChange() == ItemEvent.SELECTED) { + Platform.runLater(() -> { + linkDetails.setLink(link); + borderPane.setRight(linkDetails); + }); + } else { + Platform.runLater(() -> borderPane.setRight(null)); + } + }); + } + + private class ChangeThroughputTask extends Task { + @Override + protected Boolean call() throws Exception { + if (throughputMenuItem.isSelected()) { + return coreClient.startThroughput(); + } else { + return coreClient.stopThroughput(); + } + } + + @Override + protected void succeeded() { + if (getValue()) { + if (throughputMenuItem.isSelected()) { + networkGraph.setShowThroughput(true); + } else { + networkGraph.setShowThroughput(false); + networkGraph.getGraph().getEdges().forEach(edge -> edge.setThroughput(0)); + networkGraph.getGraphViewer().repaint(); + } + } else { + Toast.error("Failure changing throughput"); + } + } + + @Override + protected void failed() { + Toast.error("Error changing throughput", new RuntimeException(getException())); + } + } +} diff --git a/corefx/src/main/java/com/core/Main.java b/corefx/src/main/java/com/core/Main.java new file mode 100644 index 00000000..4834278f --- /dev/null +++ b/corefx/src/main/java/com/core/Main.java @@ -0,0 +1,72 @@ +package com.core; + +import com.core.utils.ConfigUtils; +import com.jfoenix.controls.JFXDecorator; +import com.jfoenix.svg.SVGGlyphLoader; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.text.Font; +import javafx.stage.Stage; + +import java.nio.file.Path; +import java.nio.file.Paths; + +public class Main extends Application { + private static final Path LOG_FILE = Paths.get(System.getProperty("user.home"), ".core", "core.log"); + + @Override + public void start(Stage window) throws Exception { + // set core dir property for logging + System.setProperty("core_log", LOG_FILE.toString()); + + // check for and create gui home directory + ConfigUtils.checkHomeDirectory(); + + // load svg icons + SVGGlyphLoader.loadGlyphsFont(getClass().getResourceAsStream("/icons/icomoon_material.svg"), + "icomoon.svg"); + + // load font + Font.loadFont(getClass().getResourceAsStream("/font/roboto/Roboto-Regular.ttf"), 10); + + // load main fxml + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml")); + Parent root = loader.load(); + + // window decorator + JFXDecorator decorator = new JFXDecorator(window, root); + decorator.setCustomMaximize(true); + decorator.setMaximized(true); + decorator.setTitle("CORE"); + Image coreIcon = new Image(getClass().getResourceAsStream("/core-icon.png")); + decorator.setGraphic(new ImageView(coreIcon)); + window.getIcons().add(coreIcon); + + // create scene and set as current scene within window + Scene scene = new Scene(decorator); + scene.getStylesheets().add(getClass().getResource("/css/main.css").toExternalForm()); + window.setScene(scene); + + // update controller + Controller controller = loader.getController(); + controller.setApplication(this); + controller.setWindow(window); + controller.setDecorator(decorator); + + // configure window + window.setOnCloseRequest(event -> { + Platform.exit(); + System.exit(0); + }); + window.show(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/corefx/src/main/java/com/core/client/ICoreClient.java b/corefx/src/main/java/com/core/client/ICoreClient.java new file mode 100644 index 00000000..fa06395d --- /dev/null +++ b/corefx/src/main/java/com/core/client/ICoreClient.java @@ -0,0 +1,118 @@ +package com.core.client; + +import com.core.client.rest.ServiceFile; +import com.core.client.rest.WlanConfig; +import com.core.data.*; + +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public interface ICoreClient { + void setConnection(String address, int port); + + boolean isLocalConnection(); + + Integer currentSession(); + + boolean startThroughput() throws IOException; + + boolean stopThroughput() throws IOException; + + void updateSession(Integer sessionId); + + void updateState(SessionState state); + + SessionOverview createSession() throws IOException; + + boolean deleteSession(Integer sessionId) throws IOException; + + List getSessions() throws IOException; + + Session getSession(Integer sessionId) throws IOException; + + boolean start(Collection nodes, Collection links, List hooks) throws IOException; + + boolean stop() throws IOException; + + boolean setState(SessionState state) throws IOException; + + Map> getServices() throws IOException; + + Map> getDefaultServices() throws IOException; + + boolean setDefaultServices(Map> defaults) throws IOException; + + CoreService getService(CoreNode node, String serviceName) throws IOException; + + boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException; + + String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException; + + boolean startService(CoreNode node, String serviceName) throws IOException; + + boolean stopService(CoreNode node, String serviceName) throws IOException; + + boolean restartService(CoreNode node, String serviceName) throws IOException; + + boolean validateService(CoreNode node, String serviceName) throws IOException; + + boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException; + + List getEmaneConfig(CoreNode node) throws IOException; + + List getEmaneModels() throws IOException; + + boolean setEmaneConfig(CoreNode node, List options) throws IOException; + + List getEmaneModelConfig(Integer id, String model) throws IOException; + + boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException; + + boolean isRunning(); + + void saveSession(File file) throws IOException; + + SessionOverview openSession(File file) throws IOException; + + List getSessionConfig() throws IOException; + + boolean setSessionConfig(List configOptions) throws IOException; + + boolean createNode(CoreNode node) throws IOException; + + String nodeCommand(CoreNode node, String command) throws IOException; + + boolean editNode(CoreNode node) throws IOException; + + boolean deleteNode(CoreNode node) throws IOException; + + boolean createLink(CoreLink link) throws IOException; + + boolean editLink(CoreLink link) throws IOException; + + boolean createHook(Hook hook) throws IOException; + + List getHooks() throws IOException; + + WlanConfig getWlanConfig(CoreNode node) throws IOException; + + boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException; + + String getTerminalCommand(CoreNode node) throws IOException; + + Map getMobilityConfigs() throws IOException; + + boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException; + + MobilityConfig getMobilityConfig(CoreNode node) throws IOException; + + boolean mobilityAction(CoreNode node, String action) throws IOException; + + LocationConfig getLocationConfig() throws IOException; + + boolean setLocationConfig(LocationConfig config) throws IOException; +} diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java new file mode 100644 index 00000000..fc759102 --- /dev/null +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -0,0 +1,677 @@ +package com.core.client.grpc; + +import com.core.client.ICoreClient; +import com.core.client.rest.ServiceFile; +import com.core.client.rest.WlanConfig; +import com.core.data.*; +import com.google.protobuf.ByteString; +import io.grpc.ManagedChannel; +import io.grpc.ManagedChannelBuilder; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +public class CoreGrpcClient implements ICoreClient { + private static final Logger logger = LogManager.getLogger(); + private String address; + private int port; + private Integer sessionId; + private SessionState sessionState; + private CoreApiGrpc.CoreApiBlockingStub blockingStub; + private ManagedChannel channel; + + private CoreProto.Node nodeToProto(CoreNode node) { + CoreProto.Position position = CoreProto.Position.newBuilder() + .setX(node.getPosition().getX().floatValue()) + .setY(node.getPosition().getY().floatValue()) + .build(); + CoreProto.Node.Builder builder = CoreProto.Node.newBuilder() + .addAllServices(node.getServices()) + .setType(CoreProto.NodeType.forNumber(node.getType())) + .setPosition(position); + if (node.getId() != null) { + builder.setId(node.getId()); + } + if (node.getName() != null) { + builder.setName(node.getName()); + } + if (node.getEmane() != null) { + builder.setEmane(node.getEmane()); + } + if (node.getModel() != null) { + builder.setModel(node.getModel()); + } + if (node.getIcon() != null) { + builder.setIcon(node.getIcon()); + } + + return builder.build(); + } + + private CoreProto.LinkOptions linkOptionsToProto(CoreLinkOptions options) { + CoreProto.LinkOptions.Builder builder = CoreProto.LinkOptions.newBuilder(); + boolean unidirectional = false; + if (options.getUnidirectional() != null && options.getUnidirectional() == 1) { + unidirectional = true; + } + if (options.getBandwidth() != null) { + builder.setBandwidth(options.getBandwidth().floatValue()); + } + if (options.getBurst() != null) { + builder.setBurst(options.getBurst().floatValue()); + } + if (options.getDelay() != null) { + builder.setDelay(options.getDelay().floatValue()); + } + if (options.getDup() != null) { + builder.setDup(options.getDup().floatValue()); + } + if (options.getJitter() != null) { + builder.setJitter(options.getJitter().floatValue()); + } + if (options.getMburst() != null) { + builder.setMburst(options.getMburst().floatValue()); + } + if (options.getMer() != null) { + builder.setMer(options.getMer().floatValue()); + } + if (options.getPer() != null) { + builder.setPer(options.getPer().floatValue()); + } + if (options.getKey() != null) { + builder.setKey(options.getKey().toString()); + } + if (options.getOpaque() != null) { + builder.setOpaque(options.getOpaque()); + } + builder.setUnidirectional(unidirectional); + return builder.build(); + } + + private CoreProto.Interface interfaceToProto(CoreInterface coreInterface) { + CoreProto.Interface.Builder builder = CoreProto.Interface.newBuilder(); + if (coreInterface.getName() != null) { + builder.setName(coreInterface.getName()); + } + if (coreInterface.getMac() != null) { + builder.setMac(coreInterface.getMac()); + } + if (coreInterface.getIp4() != null) { + builder.setIp4(coreInterface.getIp4()); + } + if (coreInterface.getIp4Mask() != null) { + builder.setIp4Mask(coreInterface.getIp4Mask()); + } + if (coreInterface.getIp6() != null) { + builder.setIp6(coreInterface.getIp6()); + } + if (coreInterface.getIp6Mask() != null) { + builder.setIp6Mask(Integer.parseInt(coreInterface.getIp6Mask())); + } + return builder.build(); + } + + @Override + public void setConnection(String address, int port) { + this.address = address; + this.port = port; + logger.info("set connection: {}:{}", this.address, this.port); + channel = ManagedChannelBuilder.forAddress(this.address, this.port).usePlaintext().build(); + logger.info("channel: {}", channel); + blockingStub = CoreApiGrpc.newBlockingStub(channel); + logger.info("stub: {}", blockingStub); + } + + @Override + public boolean isLocalConnection() { + return address.equals("127.0.0.1") || address.equals("localhost"); + } + + @Override + public Integer currentSession() { + return sessionId; + } + + @Override + public boolean startThroughput() throws IOException { + return false; + } + + @Override + public boolean stopThroughput() throws IOException { + return false; + } + + @Override + public void updateSession(Integer sessionId) { + this.sessionId = sessionId; + } + + @Override + public void updateState(SessionState state) { + sessionState = state; + } + + @Override + public SessionOverview createSession() throws IOException { + CoreProto.CreateSessionRequest request = CoreProto.CreateSessionRequest.newBuilder().build(); + CoreProto.CreateSessionResponse response = blockingStub.createSession(request); + SessionOverview overview = new SessionOverview(); + overview.setId(response.getId()); + overview.setState(response.getStateValue()); + overview.setNodes(0); + return overview; + } + + @Override + public boolean deleteSession(Integer sessionId) throws IOException { + CoreProto.DeleteSessionRequest request = CoreProto.DeleteSessionRequest.newBuilder().setId(sessionId).build(); + return blockingStub.deleteSession(request).getResult(); + } + + @Override + public List getSessions() throws IOException { + CoreProto.GetSessionsRequest request = CoreProto.GetSessionsRequest.newBuilder().build(); + CoreProto.GetSessionsResponse response = blockingStub.getSessions(request); + List sessions = new ArrayList<>(); + for (CoreProto.SessionSummary summary : response.getSessionsList()) { + SessionOverview overview = new SessionOverview(); + overview.setId(summary.getId()); + overview.setNodes(summary.getNodes()); + overview.setState(summary.getStateValue()); + sessions.add(overview); + } + return sessions; + } + + @Override + public Session getSession(Integer sessionId) throws IOException { + CoreProto.GetSessionRequest request = CoreProto.GetSessionRequest.newBuilder().setId(sessionId).build(); + CoreProto.GetSessionResponse response = blockingStub.getSession(request); + Session session = new Session(); + for (CoreProto.Node protoNode : response.getSession().getNodesList()) { + CoreNode node = new CoreNode(protoNode.getId()); + node.setName(protoNode.getName()); + node.setEmane(protoNode.getEmane()); + node.setIcon(protoNode.getIcon()); + node.setModel(protoNode.getModel()); + node.setServices(new HashSet<>(protoNode.getServicesList())); + node.getPosition().setX((double) protoNode.getPosition().getX()); + node.getPosition().setY((double) protoNode.getPosition().getY()); + node.getPosition().setZ((double) protoNode.getPosition().getZ()); + node.setNodeType(NodeType.get(protoNode.getTypeValue())); + } + for (CoreProto.Link linkProto : response.getSession().getLinksList()) { + CoreLink link = new CoreLink(); + link.setNodeOne(linkProto.getNodeOne()); + link.setNodeTwo(linkProto.getNodeOne()); + CoreProto.Interface interfaceOneProto = linkProto.getInterfaceOne(); + CoreInterface interfaceOne = new CoreInterface(); + interfaceOne.setId(interfaceOneProto.getId()); + interfaceOne.setName(interfaceOneProto.getName()); + interfaceOne.setMac(interfaceOneProto.getMac()); + interfaceOne.setIp4(interfaceOneProto.getIp4()); + interfaceOne.setIp4Mask(interfaceOneProto.getIp4Mask()); + interfaceOne.setIp6(interfaceOneProto.getIp6()); + interfaceOne.setIp6Mask(Integer.toString(interfaceOneProto.getIp6Mask())); + link.setInterfaceOne(interfaceOne); + + CoreProto.Interface interfaceTwoProto = linkProto.getInterfaceTwo(); + CoreInterface interfaceTwo = new CoreInterface(); + interfaceTwo.setId(interfaceTwoProto.getId()); + interfaceTwo.setName(interfaceTwoProto.getName()); + interfaceTwo.setMac(interfaceTwoProto.getMac()); + interfaceTwo.setIp4(interfaceTwoProto.getIp4()); + interfaceTwo.setIp4Mask(interfaceTwoProto.getIp4Mask()); + interfaceTwo.setIp6(interfaceTwoProto.getIp6()); + interfaceTwo.setIp6Mask(Integer.toString(interfaceTwoProto.getIp6Mask())); + link.setInterfaceTwo(interfaceTwo); + + CoreLinkOptions options = new CoreLinkOptions(); + CoreProto.LinkOptions protoOptions = linkProto.getOptions(); + options.setBandwidth((double) protoOptions.getBandwidth()); + options.setDelay((double) protoOptions.getDelay()); + options.setDup((double) protoOptions.getDup()); + options.setJitter((double) protoOptions.getJitter()); + options.setPer((double) protoOptions.getPer()); + options.setBurst((double) protoOptions.getBurst()); + options.setKey(Integer.parseInt(protoOptions.getKey())); + options.setMburst((double) protoOptions.getMburst()); + options.setMer((double) protoOptions.getMer()); + options.setOpaque(protoOptions.getOpaque()); + options.setUnidirectional(protoOptions.getUnidirectional() ? 1 : 0); + link.setOptions(options); + } + session.setState(response.getSession().getStateValue()); + return session; + } + + @Override + public boolean start(Collection nodes, Collection links, List hooks) throws IOException { + boolean result = setState(SessionState.DEFINITION); + if (!result) { + return false; + } + + result = setState(SessionState.CONFIGURATION); + if (!result) { + return false; + } + + for (Hook hook : hooks) { + if (!createHook(hook)) { + return false; + } + } + + for (CoreNode node : nodes) { + // must pre-configure wlan nodes, if not already + if (node.getNodeType().getValue() == NodeType.WLAN) { + WlanConfig config = getWlanConfig(node); + setWlanConfig(node, config); + } + + if (!createNode(node)) { + return false; + } + } + + for (CoreLink link : links) { + if (!createLink(link)) { + return false; + } + } + + return setState(SessionState.INSTANTIATION); + } + + @Override + public boolean stop() throws IOException { + return setState(SessionState.SHUTDOWN); + } + + @Override + public boolean setState(SessionState state) throws IOException { + CoreProto.SetSessionStateRequest request = CoreProto.SetSessionStateRequest.newBuilder() + .setId(sessionId) + .setStateValue(state.getValue()) + .build(); + CoreProto.SetSessionStateResponse response = blockingStub.setSessionState(request); + return response.getResult(); + } + + @Override + public Map> getServices() throws IOException { + CoreProto.GetServicesRequest request = CoreProto.GetServicesRequest.newBuilder().build(); + CoreProto.GetServicesResponse response = blockingStub.getServices(request); + Map> servicesMap = new HashMap<>(); + for (CoreProto.Service protoService : response.getServicesList()) { + List services = servicesMap.computeIfAbsent(protoService.getGroup(), x -> new ArrayList<>()); + services.add(protoService.getName()); + } + return servicesMap; + } + + @Override + public Map> getDefaultServices() throws IOException { + CoreProto.GetServiceDefaultsRequest request = CoreProto.GetServiceDefaultsRequest.newBuilder().build(); + CoreProto.GetServiceDefaultsResponse response = blockingStub.getServiceDefaults(request); + Map> servicesMap = new HashMap<>(); + for (CoreProto.ServiceDefaults serviceDefaults : response.getDefaultsList()) { + servicesMap.put(serviceDefaults.getNodeType(), serviceDefaults.getServicesList()); + } + return servicesMap; + } + + @Override + public boolean setDefaultServices(Map> defaults) throws IOException { + List allDefaults = new ArrayList<>(); + for (Map.Entry> entry : defaults.entrySet()) { + String nodeType = entry.getKey(); + Set services = entry.getValue(); + CoreProto.ServiceDefaults serviceDefaults = CoreProto.ServiceDefaults.newBuilder() + .setNodeType(nodeType) + .addAllServices(services) + .build(); + allDefaults.add(serviceDefaults); + } + + CoreProto.SetServiceDefaultsRequest request = CoreProto.SetServiceDefaultsRequest.newBuilder() + .setSession(sessionId) + .addAllDefaults(allDefaults) + .build(); + CoreProto.SetServiceDefaultsResponse response = blockingStub.setServiceDefaults(request); + return response.getResult(); + } + + @Override + public CoreService getService(CoreNode node, String serviceName) throws IOException { + CoreProto.GetNodeServiceRequest request = CoreProto.GetNodeServiceRequest.newBuilder().build(); + CoreProto.GetNodeServiceResponse response = blockingStub.getNodeService(request); + CoreProto.NodeServiceData nodeServiceData = response.getService(); + CoreService service = new CoreService(); + service.setShutdown(nodeServiceData.getShutdownList()); + service.setStartup(nodeServiceData.getStartupList()); + service.setValidate(nodeServiceData.getValidateList()); + service.setConfigs(nodeServiceData.getConfigsList()); + service.setDependencies(nodeServiceData.getDependenciesList()); + service.setDirs(nodeServiceData.getDirsList()); + service.setExecutables(nodeServiceData.getExecutablesList()); + service.setMeta(nodeServiceData.getMeta()); + service.setValidationMode(nodeServiceData.getValidationMode().name()); + service.setValidationTimer(Integer.toString(nodeServiceData.getValidationTimer())); + return service; + } + + @Override + public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException { + CoreProto.SetNodeServiceRequest request = CoreProto.SetNodeServiceRequest.newBuilder() + .setId(node.getId()) + .setSession(sessionId) + .setService(serviceName) + .build(); + request.getShutdownList().addAll(service.getShutdown()); + request.getValidateList().addAll(service.getValidate()); + request.getStartupList().addAll(service.getStartup()); + return blockingStub.setNodeService(request).getResult(); + } + + @Override + public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException { + CoreProto.GetNodeServiceFileRequest request = CoreProto.GetNodeServiceFileRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .build(); + CoreProto.GetNodeServiceFileResponse response = blockingStub.getNodeServiceFile(request); + return response.getData().toStringUtf8(); + } + + @Override + public boolean startService(CoreNode node, String serviceName) throws IOException { + CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setAction(CoreProto.ServiceAction.SERVICE_START) + .build(); + return blockingStub.serviceAction(request).getResult(); + } + + @Override + public boolean stopService(CoreNode node, String serviceName) throws IOException { + CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setAction(CoreProto.ServiceAction.SERVICE_STOP) + .build(); + return blockingStub.serviceAction(request).getResult(); + } + + @Override + public boolean restartService(CoreNode node, String serviceName) throws IOException { + CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setAction(CoreProto.ServiceAction.SERVICE_RESTART) + .build(); + return blockingStub.serviceAction(request).getResult(); + } + + @Override + public boolean validateService(CoreNode node, String serviceName) throws IOException { + CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setAction(CoreProto.ServiceAction.SERVICE_VALIDATE) + .build(); + return blockingStub.serviceAction(request).getResult(); + } + + @Override + public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException { + CoreProto.SetNodeServiceFileRequest request = CoreProto.SetNodeServiceFileRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setService(serviceName) + .setFile(serviceFile.getName()) + .setData(ByteString.copyFromUtf8(serviceFile.getData())) + .build(); + CoreProto.SetNodeServiceFileResponse response = blockingStub.setNodeServiceFile(request); + return response.getResult(); + } + + @Override + public List getEmaneConfig(CoreNode node) throws IOException { + return null; + } + + @Override + public List getEmaneModels() throws IOException { + return null; + } + + @Override + public boolean setEmaneConfig(CoreNode node, List options) throws IOException { + return false; + } + + @Override + public List getEmaneModelConfig(Integer id, String model) throws IOException { + return null; + } + + @Override + public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException { + return false; + } + + @Override + public boolean isRunning() { + return sessionState == SessionState.RUNTIME; + } + + @Override + public void saveSession(File file) throws IOException { + + } + + @Override + public SessionOverview openSession(File file) throws IOException { + return null; + } + + @Override + public List getSessionConfig() throws IOException { + return null; + } + + @Override + public boolean setSessionConfig(List configOptions) throws IOException { + return false; + } + + @Override + public boolean createNode(CoreNode node) throws IOException { + CoreProto.Node protoNode = nodeToProto(node); + CoreProto.AddNodeRequest request = CoreProto.AddNodeRequest.newBuilder() + .setSession(sessionId) + .setNode(protoNode) + .build(); + blockingStub.addNode(request); + return true; + } + + @Override + public String nodeCommand(CoreNode node, String command) throws IOException { + return null; + } + + @Override + public boolean editNode(CoreNode node) throws IOException { + CoreProto.Position position = CoreProto.Position.newBuilder() + .setX(node.getPosition().getX().floatValue()) + .setY(node.getPosition().getY().floatValue()) + .build(); + CoreProto.EditNodeRequest request = CoreProto.EditNodeRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setPosition(position) + .build(); + CoreProto.EditNodeResponse response = blockingStub.editNode(request); + return response.getResult(); + } + + @Override + public boolean deleteNode(CoreNode node) throws IOException { + CoreProto.DeleteNodeRequest request = CoreProto.DeleteNodeRequest.newBuilder() + .build(); + CoreProto.DeleteNodeResponse response = blockingStub.deleteNode(request); + return response.getResult(); + } + + @Override + public boolean createLink(CoreLink link) throws IOException { + CoreProto.Link.Builder builder = CoreProto.Link.newBuilder() + .setTypeValue(link.getType()); + if (link.getNodeOne() != null) { + builder.setNodeOne(link.getNodeOne()); + } + if (link.getNodeTwo() != null) { + builder.setNodeTwo(link.getNodeTwo()); + } + if (link.getInterfaceOne() != null) { + builder.setInterfaceOne(interfaceToProto(link.getInterfaceOne())); + } + if (link.getInterfaceTwo() != null) { + builder.setInterfaceTwo(interfaceToProto(link.getInterfaceTwo())); + } + if (link.getOptions() != null) { + builder.setOptions(linkOptionsToProto(link.getOptions())); + } + CoreProto.Link protoLink = builder.build(); + CoreProto.AddLinkRequest request = CoreProto.AddLinkRequest.newBuilder() + .setSession(sessionId) + .setLink(protoLink) + .build(); + CoreProto.AddLinkResponse response = blockingStub.addLink(request); + return response.getResult(); + } + + @Override + public boolean editLink(CoreLink link) throws IOException { + CoreProto.EditLinkRequest.Builder builder = CoreProto.EditLinkRequest.newBuilder() + .setSession(sessionId); + if (link.getNodeOne() != null) { + builder.setNodeOne(link.getNodeOne()); + } + if (link.getNodeTwo() != null) { + builder.setNodeTwo(link.getNodeTwo()); + } + if (link.getInterfaceOne() != null) { + builder.setInterfaceOne(link.getInterfaceOne().getId()); + } + if (link.getInterfaceTwo() != null) { + builder.setInterfaceTwo(link.getInterfaceTwo().getId()); + } + if (link.getOptions() != null) { + CoreProto.LinkOptions protoOptions = linkOptionsToProto(link.getOptions()); + builder.setOptions(protoOptions); + } + CoreProto.EditLinkRequest request = builder.build(); + CoreProto.EditLinkResponse response = blockingStub.editLink(request); + return response.getResult(); + } + + @Override + public boolean createHook(Hook hook) throws IOException { + CoreProto.Hook hookProto = CoreProto.Hook.newBuilder() + .setStateValue(hook.getState()) + .setData(ByteString.copyFromUtf8(hook.getData())) + .setFile(hook.getFile()) + .build(); + CoreProto.AddHookRequest request = CoreProto.AddHookRequest.newBuilder() + .setHook(hookProto) + .build(); + CoreProto.AddHookResponse response = blockingStub.addHook(request); + return response.getResult(); + } + + @Override + public List getHooks() throws IOException { + CoreProto.GetHooksRequest request = CoreProto.GetHooksRequest.newBuilder().setSession(sessionId).build(); + CoreProto.GetHooksResponse response = blockingStub.getHooks(request); + List hooks = new ArrayList<>(); + for (CoreProto.Hook protoHook : response.getHooksList()) { + Hook hook = new Hook(); + hook.setFile(protoHook.getFile()); + hook.setData(protoHook.getData().toStringUtf8()); + hook.setState(protoHook.getStateValue()); + hooks.add(hook); + } + return hooks; + } + + @Override + public WlanConfig getWlanConfig(CoreNode node) throws IOException { + return null; + } + + @Override + public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { + return false; + } + + @Override + public String getTerminalCommand(CoreNode node) throws IOException { + return null; + } + + @Override + public Map getMobilityConfigs() throws IOException { + CoreProto.GetMobilityConfigsRequest request = CoreProto.GetMobilityConfigsRequest.newBuilder() + .setSession(sessionId).build(); + CoreProto.GetMobilityConfigsResponse response = blockingStub.getMobilityConfigs(request); + + Map mobilityConfigs = new HashMap<>(); + for (Integer nodeId : response.getConfigsMap().keySet()) { + CoreProto.GetMobilityConfigsResponse.MobilityConfig protoMobilityConfig = response.getConfigsMap() + .get(nodeId); + MobilityConfig mobilityConfig = new MobilityConfig(); + CoreProto.ConfigGroup configGroup = protoMobilityConfig.getGroups(0); + mobilityConfigs.put(nodeId, mobilityConfig); + } + return mobilityConfigs; + } + + @Override + public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException { + return false; + } + + @Override + public MobilityConfig getMobilityConfig(CoreNode node) throws IOException { + return null; + } + + @Override + public boolean mobilityAction(CoreNode node, String action) throws IOException { + return false; + } + + @Override + public LocationConfig getLocationConfig() throws IOException { + return null; + } + + @Override + public boolean setLocationConfig(LocationConfig config) throws IOException { + return false; + } +} diff --git a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java new file mode 100644 index 00000000..3e963583 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java @@ -0,0 +1,415 @@ +package com.core.client.rest; + +import com.core.client.ICoreClient; +import com.core.data.*; +import com.core.utils.WebUtils; +import lombok.Data; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +@Data +public class CoreRestClient implements ICoreClient { + private static final Logger logger = LogManager.getLogger(); + private String address; + private int port; + private Integer sessionId; + private SessionState sessionState; + + @Override + public void setConnection(String address, int port) { + this.address = address; + this.port = port; + } + + @Override + public boolean isLocalConnection() { + return address.equals("127.0.0.1") || address.equals("localhost"); + } + + @Override + public Integer currentSession() { + return sessionId; + } + + @Override + public void updateState(SessionState state) { + sessionState = state; + } + + @Override + public void updateSession(Integer sessionId) { + this.sessionId = sessionId; + } + + private String getUrl(String path) { + return String.format("http://%s:%s/%s", address, port, path); + } + + @Override + public SessionOverview createSession() throws IOException { + String url = getUrl("sessions"); + return WebUtils.post(url, SessionOverview.class); + } + + @Override + public boolean deleteSession(Integer sessionId) throws IOException { + String path = String.format("sessions/%s", sessionId); + String url = getUrl(path); + return WebUtils.delete(url); + } + + public Map> getServices() throws IOException { + String url = getUrl("services"); + GetServices getServices = WebUtils.getJson(url, GetServices.class); + return getServices.getGroups(); + } + + @Override + public Session getSession(Integer sessionId) throws IOException { + String path = String.format("sessions/%s", sessionId); + String url = getUrl(path); + return WebUtils.getJson(url, Session.class); + } + + @Override + public List getSessions() throws IOException { + String url = getUrl("sessions"); + GetSessions getSessions = WebUtils.getJson(url, GetSessions.class); + return getSessions.getSessions(); + } + + @Override + public boolean start(Collection nodes, Collection links, List hooks) throws IOException { + boolean result = setState(SessionState.DEFINITION); + if (!result) { + return false; + } + + result = setState(SessionState.CONFIGURATION); + if (!result) { + return false; + } + + for (Hook hook : hooks) { + if (!createHook(hook)) { + return false; + } + } + + for (CoreNode node : nodes) { + // must pre-configure wlan nodes, if not already + if (node.getNodeType().getValue() == NodeType.WLAN) { + WlanConfig config = getWlanConfig(node); + setWlanConfig(node, config); + } + + if (!createNode(node)) { + return false; + } + } + + for (CoreLink link : links) { + if (!createLink(link)) { + return false; + } + } + + return setState(SessionState.INSTANTIATION); + } + + @Override + public boolean stop() throws IOException { + return setState(SessionState.SHUTDOWN); + } + + @Override + public boolean setState(SessionState state) throws IOException { + String url = getUrl(String.format("sessions/%s/state", sessionId)); + Map data = new HashMap<>(); + data.put("state", state.getValue()); + boolean result = WebUtils.putJson(url, data); + + if (result) { + sessionState = state; + } + return result; + } + + private boolean uploadFile(File file) throws IOException { + String url = getUrl("upload"); + return WebUtils.postFile(url, file); + } + + @Override + public boolean startThroughput() throws IOException { + String url = getUrl("throughput/start"); + return WebUtils.putJson(url); + } + + @Override + public boolean stopThroughput() throws IOException { + String url = getUrl("throughput/stop"); + return WebUtils.putJson(url); + } + + @Override + public Map> getDefaultServices() throws IOException { + String url = getUrl(String.format("sessions/%s/services/default", sessionId)); + GetDefaultServices getDefaultServices = WebUtils.getJson(url, GetDefaultServices.class); + return getDefaultServices.getDefaults(); + } + + @Override + public boolean setDefaultServices(Map> defaults) throws IOException { + String url = getUrl(String.format("sessions/%s/services/default", sessionId)); + return WebUtils.postJson(url, defaults); + } + + @Override + public CoreService getService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName)); + return WebUtils.getJson(url, CoreService.class); + } + + @Override + public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName)); + return WebUtils.putJson(url, service); + } + + @Override + public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(), + serviceName)); + Map args = new HashMap<>(); + args.put("file", fileName); + return WebUtils.getJson(url, String.class, args); + } + + @Override + public boolean startService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/start", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean stopService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/stop", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean restartService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/restart", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean validateService(CoreNode node, String serviceName) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/validate", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url); + } + + @Override + public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(), + serviceName)); + return WebUtils.putJson(url, serviceFile); + } + + @Override + public List getEmaneModels() throws IOException { + String url = getUrl(String.format("sessions/%s/emane/models", sessionId)); + GetEmaneModels getEmaneModels = WebUtils.getJson(url, GetEmaneModels.class); + return getEmaneModels.getModels(); + } + + @Override + public List getEmaneModelConfig(Integer id, String model) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId)); + Map args = new HashMap<>(); + args.put("node", id.toString()); + args.put("name", model); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args); + return getConfig.getGroups(); + } + + @Override + public List getEmaneConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/config", sessionId)); + Map args = new HashMap<>(); + args.put("node", node.getId().toString()); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args); + return getConfig.getGroups(); + } + + @Override + public boolean setEmaneConfig(CoreNode node, List options) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/config", sessionId)); + SetEmaneConfig setEmaneConfig = new SetEmaneConfig(); + setEmaneConfig.setNode(node.getId()); + setEmaneConfig.setValues(options); + return WebUtils.putJson(url, setEmaneConfig); + } + + @Override + public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException { + String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId)); + SetEmaneModelConfig setEmaneModelConfig = new SetEmaneModelConfig(); + setEmaneModelConfig.setNode(id); + setEmaneModelConfig.setName(model); + setEmaneModelConfig.setValues(options); + return WebUtils.putJson(url, setEmaneModelConfig); + } + + @Override + public boolean isRunning() { + return sessionState == SessionState.RUNTIME; + } + + @Override + public void saveSession(File file) throws IOException { + String path = String.format("sessions/%s/xml", sessionId); + String url = getUrl(path); + WebUtils.getFile(url, file); + } + + @Override + public SessionOverview openSession(File file) throws IOException { + String url = getUrl("sessions/xml"); + return WebUtils.postFile(url, file, SessionOverview.class); + } + + @Override + public List getSessionConfig() throws IOException { + String url = getUrl(String.format("sessions/%s/options", sessionId)); + GetConfig getConfig = WebUtils.getJson(url, GetConfig.class); + return getConfig.getGroups(); + } + + @Override + public boolean setSessionConfig(List configOptions) throws IOException { + String url = getUrl(String.format("sessions/%s/options", sessionId)); + SetConfig setConfig = new SetConfig(configOptions); + return WebUtils.putJson(url, setConfig); + } + + @Override + public LocationConfig getLocationConfig() throws IOException { + String url = getUrl(String.format("sessions/%s/location", sessionId)); + return WebUtils.getJson(url, LocationConfig.class); + } + + @Override + public boolean setLocationConfig(LocationConfig config) throws IOException { + String url = getUrl(String.format("sessions/%s/location", sessionId)); + return WebUtils.putJson(url, config); + } + + @Override + public String nodeCommand(CoreNode node, String command) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/command", sessionId, node.getId())); + return WebUtils.putJson(url, command, String.class); + } + + @Override + public boolean createNode(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes", sessionId)); + return WebUtils.postJson(url, node); + } + + + @Override + public boolean editNode(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s", sessionId, node.getId())); + return WebUtils.putJson(url, node); + } + + @Override + public boolean deleteNode(CoreNode node) throws IOException { + String url = getUrl(String.format("/sessions/%s/nodes/%s", sessionId, node.getId())); + return WebUtils.delete(url); + } + + @Override + public boolean createLink(CoreLink link) throws IOException { + String url = getUrl(String.format("sessions/%s/links", sessionId)); + return WebUtils.postJson(url, link); + } + + @Override + public boolean editLink(CoreLink link) throws IOException { + String url = getUrl(String.format("sessions/%s/links", sessionId)); + return WebUtils.putJson(url, link); + } + + @Override + public boolean createHook(Hook hook) throws IOException { + String url = getUrl(String.format("sessions/%s/hooks", sessionId)); + return WebUtils.postJson(url, hook); + } + + @Override + public List getHooks() throws IOException { + String url = getUrl(String.format("sessions/%s/hooks", sessionId)); + GetHooks getHooks = WebUtils.getJson(url, GetHooks.class); + return getHooks.getHooks(); + } + + @Override + public WlanConfig getWlanConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId())); + return WebUtils.getJson(url, WlanConfig.class); + } + + @Override + public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId())); + return WebUtils.putJson(url, config); + } + + @Override + public String getTerminalCommand(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/terminal", sessionId, node.getId())); + return WebUtils.getJson(url, String.class); + } + + @Override + public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException { + boolean uploaded = uploadFile(config.getScriptFile()); + if (!uploaded) { + throw new IOException("failed to upload mobility script"); + } + + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId())); + config.setFile(config.getScriptFile().getName()); + return WebUtils.postJson(url, config); + } + + @Override + public Map getMobilityConfigs() throws IOException { + String url = getUrl(String.format("sessions/%s/mobility/configs", sessionId)); + GetMobilityConfigs getMobilityConfigs = WebUtils.getJson(url, GetMobilityConfigs.class); + return getMobilityConfigs.getConfigurations(); + } + + @Override + public MobilityConfig getMobilityConfig(CoreNode node) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId())); + return WebUtils.getJson(url, MobilityConfig.class); + } + + @Override + public boolean mobilityAction(CoreNode node, String action) throws IOException { + String url = getUrl(String.format("sessions/%s/nodes/%s/mobility/%s", sessionId, node.getId(), action)); + return WebUtils.putJson(url); + } +} diff --git a/corefx/src/main/java/com/core/client/rest/GetConfig.java b/corefx/src/main/java/com/core/client/rest/GetConfig.java new file mode 100644 index 00000000..61a843b6 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetConfig.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.ConfigGroup; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetConfig { + private List groups = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java b/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java new file mode 100644 index 00000000..b134cbc0 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java @@ -0,0 +1,16 @@ +package com.core.client.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class GetDefaultServices { + private Map> defaults = new HashMap<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java b/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java new file mode 100644 index 00000000..43cef1c6 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java @@ -0,0 +1,11 @@ +package com.core.client.rest; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetEmaneModels { + private List models = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetHooks.java b/corefx/src/main/java/com/core/client/rest/GetHooks.java new file mode 100644 index 00000000..fe26e828 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetHooks.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.Hook; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class GetHooks { + private List hooks = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java b/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java new file mode 100644 index 00000000..e5905b7e --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import com.core.data.MobilityConfig; +import lombok.Data; + +import java.util.HashMap; +import java.util.Map; + +@Data +public class GetMobilityConfigs { + private Map configurations = new HashMap<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/GetServices.java b/corefx/src/main/java/com/core/client/rest/GetServices.java new file mode 100644 index 00000000..17e04ddc --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetServices.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; +import java.util.Map; + +@Data +@NoArgsConstructor +public class GetServices { + private Map> groups; +} diff --git a/corefx/src/main/java/com/core/client/rest/GetSessions.java b/corefx/src/main/java/com/core/client/rest/GetSessions.java new file mode 100644 index 00000000..674fc83a --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/GetSessions.java @@ -0,0 +1,14 @@ +package com.core.client.rest; + +import com.core.data.SessionOverview; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class GetSessions { + private List sessions = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/ServiceFile.java b/corefx/src/main/java/com/core/client/rest/ServiceFile.java new file mode 100644 index 00000000..977ed984 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/ServiceFile.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class ServiceFile { + private String name; + private String data; +} diff --git a/corefx/src/main/java/com/core/client/rest/SetConfig.java b/corefx/src/main/java/com/core/client/rest/SetConfig.java new file mode 100644 index 00000000..0d3f11ea --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetConfig.java @@ -0,0 +1,16 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class SetConfig { + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java b/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java new file mode 100644 index 00000000..ee947c51 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java @@ -0,0 +1,13 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SetEmaneConfig { + private Integer node; + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java b/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java new file mode 100644 index 00000000..e5c37092 --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java @@ -0,0 +1,14 @@ +package com.core.client.rest; + +import com.core.data.ConfigOption; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class SetEmaneModelConfig { + private Integer node; + private String name; + private List values = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/client/rest/WlanConfig.java b/corefx/src/main/java/com/core/client/rest/WlanConfig.java new file mode 100644 index 00000000..45b9805f --- /dev/null +++ b/corefx/src/main/java/com/core/client/rest/WlanConfig.java @@ -0,0 +1,12 @@ +package com.core.client.rest; + +import lombok.Data; + +@Data +public class WlanConfig { + private String range; + private String bandwidth; + private String jitter; + private String delay; + private String error; +} diff --git a/corefx/src/main/java/com/core/data/BridgeThroughput.java b/corefx/src/main/java/com/core/data/BridgeThroughput.java new file mode 100644 index 00000000..167fb402 --- /dev/null +++ b/corefx/src/main/java/com/core/data/BridgeThroughput.java @@ -0,0 +1,9 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class BridgeThroughput { + private int node; + private Double throughput; +} diff --git a/corefx/src/main/java/com/core/data/ConfigDataType.java b/corefx/src/main/java/com/core/data/ConfigDataType.java new file mode 100644 index 00000000..3dd44864 --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigDataType.java @@ -0,0 +1,40 @@ +package com.core.data; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public enum ConfigDataType { + UINT8(1), + UINT16(2), + UINT32(3), + UINT64(4), + INT8(5), + INT16(6), + INT32(7), + INT64(8), + FLOAT(9), + STRING(10), + BOOL(11); + + private static final Map LOOKUP = new HashMap<>(); + + static { + Arrays.stream(ConfigDataType.values()).forEach(x -> LOOKUP.put(x.getValue(), x)); + } + + private final int value; + + ConfigDataType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + + public static ConfigDataType get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/ConfigGroup.java b/corefx/src/main/java/com/core/data/ConfigGroup.java new file mode 100644 index 00000000..9b45bdb7 --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigGroup.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ConfigGroup { + private String name; + private List options = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/ConfigOption.java b/corefx/src/main/java/com/core/data/ConfigOption.java new file mode 100644 index 00000000..77af265e --- /dev/null +++ b/corefx/src/main/java/com/core/data/ConfigOption.java @@ -0,0 +1,15 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ConfigOption { + private String label; + private String name; + private String value; + private Integer type; + private List select = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/CoreEvent.java b/corefx/src/main/java/com/core/data/CoreEvent.java new file mode 100644 index 00000000..9ac8ed03 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreEvent.java @@ -0,0 +1,19 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonSetter; +import lombok.Data; + +@Data +public class CoreEvent { + private Integer session; + private Integer node; + private String name; + private Double time; + private EventType eventType; + private String data; + + @JsonSetter("event_type") + public void setEventType(int value) { + eventType = EventType.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/CoreInterface.java b/corefx/src/main/java/com/core/data/CoreInterface.java new file mode 100644 index 00000000..f159f10e --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreInterface.java @@ -0,0 +1,19 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class CoreInterface { + private Integer id; + private String name; + private String mac; + private String ip4; + @JsonProperty("ip4mask") + private Integer ip4Mask; + private String ip6; + @JsonProperty("ip6mask") + private String ip6Mask; +} diff --git a/corefx/src/main/java/com/core/data/CoreLink.java b/corefx/src/main/java/com/core/data/CoreLink.java new file mode 100644 index 00000000..f3d4a215 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreLink.java @@ -0,0 +1,56 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class CoreLink { + @EqualsAndHashCode.Include + private Integer id; + + @JsonIgnore + private Float weight = 1.0f; + + @JsonIgnore + private boolean loaded = true; + + @JsonIgnore + private double throughput; + + @JsonIgnore + private boolean visible = true; + + @JsonProperty("message_type") + private Integer messageType; + + private Integer type = 1; + + @JsonProperty("node_one") + private Integer nodeOne; + + @JsonProperty("node_two") + private Integer nodeTwo; + + @JsonProperty("interface_one") + private CoreInterface interfaceOne; + + @JsonProperty("interface_two") + private CoreInterface interfaceTwo; + + private CoreLinkOptions options = new CoreLinkOptions(); + + public CoreLink(Integer id) { + this.id = id; + this.weight = (float) id; + this.loaded = false; + } + + public boolean isWireless() { + return interfaceOne == null && interfaceTwo == null; + } +} diff --git a/corefx/src/main/java/com/core/data/CoreLinkOptions.java b/corefx/src/main/java/com/core/data/CoreLinkOptions.java new file mode 100644 index 00000000..6556d3c0 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreLinkOptions.java @@ -0,0 +1,21 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class CoreLinkOptions { + private String opaque; + private Integer session; + private Double jitter; + private Integer key; + private Double mburst; + private Double mer; + private Double per; + private Double bandwidth; + private Double burst; + private Double delay; + private Double dup; + private Integer unidirectional; +} diff --git a/corefx/src/main/java/com/core/data/CoreNode.java b/corefx/src/main/java/com/core/data/CoreNode.java new file mode 100644 index 00000000..f441bd69 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreNode.java @@ -0,0 +1,59 @@ +package com.core.data; + +import com.core.graph.RadioIcon; +import com.core.utils.IconUtils; +import com.fasterxml.jackson.annotation.JsonIgnore; +import edu.uci.ics.jung.visualization.LayeredIcon; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashSet; +import java.util.Set; + +@Data +@NoArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class CoreNode { + private static final Logger logger = LogManager.getLogger(); + @EqualsAndHashCode.Include + private Integer id; + private String name; + private Integer type; + private String model; + private Position position = new Position(); + private Set services = new HashSet<>(); + private String emane; + private String url; + @JsonIgnore + private NodeType nodeType; + @JsonIgnore + private String icon; + @JsonIgnore + private boolean loaded = true; + @JsonIgnore + private LayeredIcon graphIcon; + @JsonIgnore + private RadioIcon radioIcon = new RadioIcon(); + + public CoreNode(Integer id) { + this.id = id; + this.name = String.format("Node%s", this.id); + this.loaded = false; + } + + public void setNodeType(NodeType nodeType) { + type = nodeType.getValue(); + model = nodeType.getModel(); + icon = nodeType.getIcon(); + if (icon.startsWith("file:")) { + graphIcon = IconUtils.getExternalLayeredIcon(icon); + } else { + graphIcon = IconUtils.getLayeredIcon(icon); + } + graphIcon.add(radioIcon); + this.nodeType = nodeType; + } +} diff --git a/corefx/src/main/java/com/core/data/CoreService.java b/corefx/src/main/java/com/core/data/CoreService.java new file mode 100644 index 00000000..ed9dd230 --- /dev/null +++ b/corefx/src/main/java/com/core/data/CoreService.java @@ -0,0 +1,23 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class CoreService { + private List executables = new ArrayList<>(); + private List dependencies = new ArrayList<>(); + private List dirs = new ArrayList<>(); + private List configs = new ArrayList<>(); + private List startup = new ArrayList<>(); + private List validate = new ArrayList<>(); + @JsonProperty("validation_mode") + private String validationMode; + @JsonProperty("validation_timer") + private String validationTimer; + private List shutdown = new ArrayList<>(); + private String meta; +} diff --git a/corefx/src/main/java/com/core/data/EventType.java b/corefx/src/main/java/com/core/data/EventType.java new file mode 100644 index 00000000..487de4c1 --- /dev/null +++ b/corefx/src/main/java/com/core/data/EventType.java @@ -0,0 +1,45 @@ +package com.core.data; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public enum EventType { + NONE(0), + DEFINITION_STATE(1), + CONFIGURATION_STATE(2), + INSTANTIATION_STATE(3), + RUNTIME_STATE(4), + DATACOLLECT_STATE(5), + SHUTDOWN_STATE(6), + START(7), + STOP(8), + PAUSE(9), + RESTART(10), + FILE_OPEN(11), + FILE_SAVE(12), + SCHEDULED(13), + RECONFIGURE(14), + INSTANTIATION_COMPLETE(15); + + private static final Map LOOKUP = new HashMap<>(); + + static { + Arrays.stream(EventType.values()).forEach(x -> LOOKUP.put(x.getValue(), x)); + } + + private final int value; + + EventType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + + public static EventType get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Hook.java b/corefx/src/main/java/com/core/data/Hook.java new file mode 100644 index 00000000..35b1a934 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Hook.java @@ -0,0 +1,13 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; + +@Data +public class Hook { + private String file; + private Integer state; + @JsonIgnore + private String stateDisplay; + private String data; +} diff --git a/corefx/src/main/java/com/core/data/InterfaceThroughput.java b/corefx/src/main/java/com/core/data/InterfaceThroughput.java new file mode 100644 index 00000000..1c6303e0 --- /dev/null +++ b/corefx/src/main/java/com/core/data/InterfaceThroughput.java @@ -0,0 +1,12 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +@Data +public class InterfaceThroughput { + private int node; + @JsonProperty("interface") + private int nodeInterface; + private double throughput; +} diff --git a/corefx/src/main/java/com/core/data/LinkTypes.java b/corefx/src/main/java/com/core/data/LinkTypes.java new file mode 100644 index 00000000..4bfaf1dc --- /dev/null +++ b/corefx/src/main/java/com/core/data/LinkTypes.java @@ -0,0 +1,31 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum LinkTypes { + WIRELESS(0), + WIRED(1); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (LinkTypes state : LinkTypes.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + LinkTypes(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static LinkTypes get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Location.java b/corefx/src/main/java/com/core/data/Location.java new file mode 100644 index 00000000..f5c399c4 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Location.java @@ -0,0 +1,10 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class Location { + private Double latitude = 0.0; + private Double longitude = 0.0; + private Double altitude = 0.0; +} diff --git a/corefx/src/main/java/com/core/data/LocationConfig.java b/corefx/src/main/java/com/core/data/LocationConfig.java new file mode 100644 index 00000000..10ba11ac --- /dev/null +++ b/corefx/src/main/java/com/core/data/LocationConfig.java @@ -0,0 +1,10 @@ +package com.core.data; + +import lombok.Data; + +@Data +public class LocationConfig { + private Position position = new Position(); + private Location location = new Location(); + private Double scale; +} diff --git a/corefx/src/main/java/com/core/data/MessageFlags.java b/corefx/src/main/java/com/core/data/MessageFlags.java new file mode 100644 index 00000000..6272099b --- /dev/null +++ b/corefx/src/main/java/com/core/data/MessageFlags.java @@ -0,0 +1,36 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum MessageFlags { + ADD(1), + DELETE(2), + CRI(4), + LOCAL(8), + STRING(16), + TEXT(32), + TTY(64); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (MessageFlags state : MessageFlags.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + MessageFlags(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static MessageFlags get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/MobilityConfig.java b/corefx/src/main/java/com/core/data/MobilityConfig.java new file mode 100644 index 00000000..67b5d769 --- /dev/null +++ b/corefx/src/main/java/com/core/data/MobilityConfig.java @@ -0,0 +1,25 @@ +package com.core.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.File; + +@Data +public class MobilityConfig { + private String file; + @JsonIgnore + private File scriptFile; + @JsonProperty("refresh_ms") + private Integer refresh; + private String loop; + private String autostart; + private String map; + @JsonProperty("script_start") + private String startScript; + @JsonProperty("script_pause") + private String pauseScript; + @JsonProperty("script_stop") + private String stopScript; +} diff --git a/corefx/src/main/java/com/core/data/NodeType.java b/corefx/src/main/java/com/core/data/NodeType.java new file mode 100644 index 00000000..4af7dc3d --- /dev/null +++ b/corefx/src/main/java/com/core/data/NodeType.java @@ -0,0 +1,86 @@ +package com.core.data; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; +import java.util.concurrent.atomic.AtomicInteger; + +@Data +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +public class NodeType { + private static final Logger logger = LogManager.getLogger(); + private static final AtomicInteger idGenerator = new AtomicInteger(0); + private static final Map ID_LOOKUP = new HashMap<>(); + public static final int DEFAULT = 0; + public static final int SWITCH = 4; + public static final int HUB = 5; + public static final int WLAN = 6; + public static final int EMANE = 10; + @EqualsAndHashCode.Include + private final int id; + private final int value; + private final Set services = new TreeSet<>(); + private String display; + private String model; + private String icon; + + // PHYSICAL = 1 +// RJ45 = 7 +// TUNNEL = 8 +// KTUNNEL = 9 +// EMANE = 10 +// TAP_BRIDGE = 11 +// PEER_TO_PEER = 12 +// CONTROL_NET = 13 +// EMANE_NET = 14; + + static { + add(new NodeType(SWITCH, "lanswitch", "Switch", "/icons/switch-100.png")); + add(new NodeType(HUB, "hub", "Hub", "/icons/hub-100.png")); + add(new NodeType(WLAN, "wlan", "WLAN", "/icons/wlan-100.png")); + add(new NodeType(EMANE, "wlan", "EMANE", "/icons/emane-100.png")); + } + + + public NodeType(int value, String model, String display, String icon) { + this.id = idGenerator.incrementAndGet(); + this.value = value; + this.model = model; + this.display = display; + this.icon = icon; + } + + public static void add(NodeType nodeType) { + ID_LOOKUP.put(nodeType.getId(), nodeType); + } + + public static void remove(NodeType nodeType) { + ID_LOOKUP.remove(nodeType.getId()); + } + + public static NodeType get(Integer id) { + return ID_LOOKUP.get(id); + } + + public static Collection getAll() { + return ID_LOOKUP.values(); + } + + public static NodeType find(Integer type, String model) { + return ID_LOOKUP.values().stream() + .filter(nodeType -> { + boolean sameType = nodeType.getValue() == type; + boolean sameModel; + if (model != null) { + sameModel = model.equals(nodeType.getModel()); + } else { + sameModel = nodeType.getModel() == null; + } + return sameType && sameModel; + }) + .findFirst().orElse(null); + } +} diff --git a/corefx/src/main/java/com/core/data/Position.java b/corefx/src/main/java/com/core/data/Position.java new file mode 100644 index 00000000..c5bfa728 --- /dev/null +++ b/corefx/src/main/java/com/core/data/Position.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class Position { + private Double x; + private Double y; + private Double z; +} diff --git a/corefx/src/main/java/com/core/data/Session.java b/corefx/src/main/java/com/core/data/Session.java new file mode 100644 index 00000000..374e379d --- /dev/null +++ b/corefx/src/main/java/com/core/data/Session.java @@ -0,0 +1,15 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Data +@NoArgsConstructor +public class Session { + private Integer state; + private List nodes = new ArrayList<>(); + private List links = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/data/SessionOverview.java b/corefx/src/main/java/com/core/data/SessionOverview.java new file mode 100644 index 00000000..47773338 --- /dev/null +++ b/corefx/src/main/java/com/core/data/SessionOverview.java @@ -0,0 +1,13 @@ +package com.core.data; + +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +public class SessionOverview { + private Integer id; + private Integer state; + private Integer nodes = 0; + private String url; +} diff --git a/corefx/src/main/java/com/core/data/SessionState.java b/corefx/src/main/java/com/core/data/SessionState.java new file mode 100644 index 00000000..c9e92904 --- /dev/null +++ b/corefx/src/main/java/com/core/data/SessionState.java @@ -0,0 +1,38 @@ +package com.core.data; + +import java.util.HashMap; +import java.util.Map; + +public enum SessionState { + DEFINITION(1), + CONFIGURATION(2), + INSTANTIATION(3), + RUNTIME(4), + DATA_COLLECT(5), + SHUTDOWN(6), + START(7), + STOP(8), + PAUSE(9); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (SessionState state : SessionState.values()) { + LOOKUP.put(state.getValue(), state); + } + } + + private final int value; + + SessionState(int value) { + this.value = value; + } + + public int getValue() { + return this.value; + } + + public static SessionState get(int value) { + return LOOKUP.get(value); + } +} diff --git a/corefx/src/main/java/com/core/data/Throughputs.java b/corefx/src/main/java/com/core/data/Throughputs.java new file mode 100644 index 00000000..c02a7f6f --- /dev/null +++ b/corefx/src/main/java/com/core/data/Throughputs.java @@ -0,0 +1,12 @@ +package com.core.data; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class Throughputs { + private List interfaces = new ArrayList<>(); + private List bridges = new ArrayList<>(); +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraph.java b/corefx/src/main/java/com/core/datavis/CoreGraph.java new file mode 100644 index 00000000..f5a0c0fd --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraph.java @@ -0,0 +1,11 @@ +package com.core.datavis; + +import lombok.Data; + +@Data +public class CoreGraph { + private String title; + private CoreGraphAxis xAxis; + private CoreGraphAxis yAxis; + private GraphType graphType; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java b/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java new file mode 100644 index 00000000..8631f3a1 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphAxis.java @@ -0,0 +1,15 @@ +package com.core.datavis; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CoreGraphAxis { + private String label; + private Double lower; + private Double upper; + private Double tick; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphData.java b/corefx/src/main/java/com/core/datavis/CoreGraphData.java new file mode 100644 index 00000000..ff0d4c77 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphData.java @@ -0,0 +1,15 @@ +package com.core.datavis; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class CoreGraphData { + private String name; + private Double x; + private Double y; + private Double weight; +} diff --git a/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java b/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java new file mode 100644 index 00000000..eae92e8f --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/CoreGraphWrapper.java @@ -0,0 +1,157 @@ +package com.core.datavis; + +import javafx.scene.chart.*; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class CoreGraphWrapper { + private static final Logger logger = LogManager.getLogger(); + private final GraphType graphType; + private PieChart pieChart; + private final Map pieData = new HashMap<>(); + private BarChart barChart; + private final Map> barMap = new HashMap<>(); + private XYChart xyChart; + private final XYChart.Series series = new XYChart.Series<>(); + private final XYChart.Series barSeries = new XYChart.Series<>(); + private AtomicInteger timeValue = new AtomicInteger(0); + + public CoreGraphWrapper(CoreGraph coreGraph) { + graphType = coreGraph.getGraphType(); + createChart(coreGraph); + } + + public Chart getChart() { + switch (graphType) { + case PIE: + return pieChart; + case BAR: + return barChart; + default: + return xyChart; + } + } + + public void add(CoreGraphData coreGraphData) { + switch (graphType) { + case PIE: + case BAR: + add(coreGraphData.getName(), coreGraphData.getY()); + break; + case TIME: + add(coreGraphData.getY()); + break; + case BUBBLE: + add(coreGraphData.getX(), coreGraphData.getY(), coreGraphData.getWeight()); + break; + default: + add(coreGraphData.getX(), coreGraphData.getY()); + } + } + + public void add(String name, double value) { + if (GraphType.PIE == graphType) { + PieChart.Data data = pieData.computeIfAbsent(name, x -> { + PieChart.Data newData = new PieChart.Data(x, value); + pieChart.getData().add(newData); + return newData; + }); + data.setPieValue(value); + } else { + XYChart.Data data = barMap.computeIfAbsent(name, x -> { + XYChart.Data newData = new XYChart.Data<>(name, value); + barSeries.getData().add(newData); + return newData; + }); + data.setYValue(value); + } + } + + public void add(Number y) { + series.getData().add(new XYChart.Data<>(timeValue.getAndIncrement(), y)); + } + + public void add(Number x, Number y) { + series.getData().add(new XYChart.Data<>(x, y)); + } + + public void add(Number x, Number y, Number weight) { + series.getData().add(new XYChart.Data<>(x, y, weight)); + } + + private NumberAxis getAxis(CoreGraphAxis graphAxis) { + return new NumberAxis(graphAxis.getLabel(), graphAxis.getLower(), + graphAxis.getUpper(), graphAxis.getTick()); + } + + private void createChart(CoreGraph coreGraph) { + NumberAxis xAxis; + NumberAxis yAxis; + + switch (coreGraph.getGraphType()) { + case AREA: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new AreaChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case TIME: + xAxis = new NumberAxis(); + xAxis.setLabel(coreGraph.getXAxis().getLabel()); + xAxis.setTickUnit(1); + xAxis.setLowerBound(0); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new LineChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case LINE: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new LineChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case BUBBLE: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new BubbleChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case SCATTER: + xAxis = getAxis(coreGraph.getXAxis()); + yAxis = getAxis(coreGraph.getYAxis()); + xyChart = new ScatterChart<>(xAxis, yAxis); + xyChart.setTitle(coreGraph.getTitle()); + xyChart.setLegendVisible(false); + xyChart.getData().add(series); + break; + case PIE: + pieChart = new PieChart(); + pieChart.setTitle(coreGraph.getTitle()); + break; + case BAR: + CategoryAxis categoryAxis = new CategoryAxis(); + categoryAxis.setLabel(coreGraph.getXAxis().getLabel()); + yAxis = getAxis(coreGraph.getYAxis()); + barChart = new BarChart<>(categoryAxis, yAxis); + barChart.setLegendVisible(false); + barChart.setTitle(coreGraph.getTitle()); + barChart.getData().add(barSeries); + break; + default: + throw new IllegalArgumentException(String.format("unknown graph type: %s", + coreGraph.getGraphType())); + } + } +} diff --git a/corefx/src/main/java/com/core/datavis/GraphType.java b/corefx/src/main/java/com/core/datavis/GraphType.java new file mode 100644 index 00000000..7609eee3 --- /dev/null +++ b/corefx/src/main/java/com/core/datavis/GraphType.java @@ -0,0 +1,11 @@ +package com.core.datavis; + +public enum GraphType { + PIE, + LINE, + TIME, + AREA, + BAR, + SCATTER, + BUBBLE +} diff --git a/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java b/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java new file mode 100644 index 00000000..d04097c2 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/AbstractNodeContextMenu.java @@ -0,0 +1,22 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.MenuItem; + +abstract class AbstractNodeContextMenu extends GraphContextMenu { + final CoreNode coreNode; + + AbstractNodeContextMenu(Controller controller, CoreNode coreNode) { + super(controller); + this.coreNode = coreNode; + } + + void addMenuItem(String text, EventHandler handler) { + MenuItem menuItem = new MenuItem(text); + menuItem.setOnAction(handler); + getItems().add(menuItem); + } +} diff --git a/corefx/src/main/java/com/core/graph/BackgroundPaintable.java b/corefx/src/main/java/com/core/graph/BackgroundPaintable.java new file mode 100644 index 00000000..b0cfe6a3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/BackgroundPaintable.java @@ -0,0 +1,51 @@ +package com.core.graph; + +import edu.uci.ics.jung.visualization.Layer; +import edu.uci.ics.jung.visualization.VisualizationViewer; + +import javax.imageio.ImageIO; +import javax.swing.*; +import java.awt.*; +import java.awt.geom.AffineTransform; +import java.io.IOException; +import java.nio.file.Paths; + +public class BackgroundPaintable implements VisualizationViewer.Paintable { + private final ImageIcon imageIcon; + private final VisualizationViewer vv; + private final String imagePath; + + public BackgroundPaintable(String imagePath, VisualizationViewer vv) throws IOException { + this.imagePath = imagePath; + Image image = ImageIO.read(Paths.get(imagePath).toFile()); + imageIcon = new ImageIcon(image); + this.vv = vv; + } + + public String getImage() { + return imagePath; + } + + @Override + public void paint(Graphics g) { + Graphics2D g2d = (Graphics2D) g; + AffineTransform oldXform = g2d.getTransform(); + AffineTransform lat = + vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform(); + AffineTransform vat = + vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform(); + AffineTransform at = new AffineTransform(); + at.concatenate(g2d.getTransform()); + at.concatenate(vat); + at.concatenate(lat); + g2d.setTransform(at); + g.drawImage(imageIcon.getImage(), 0, 0, + imageIcon.getIconWidth(), imageIcon.getIconHeight(), vv); + g2d.setTransform(oldXform); + } + + @Override + public boolean useTransform() { + return false; + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreAddresses.java b/corefx/src/main/java/com/core/graph/CoreAddresses.java new file mode 100644 index 00000000..5db724b6 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreAddresses.java @@ -0,0 +1,41 @@ +package com.core.graph; + +import com.core.data.CoreInterface; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Collection; + +public class CoreAddresses { + private static final Logger logger = LogManager.getLogger(); + public static final int IP4_MASK = 24; + public static final int IP4_INDEX = (IP4_MASK / 8) - 1; + + private String ip4Base; + + public CoreAddresses(String ip4Base) { + this.ip4Base = ip4Base; + } + + public int getSubnet(Collection nodeOneInterfaces, Collection nodeTwoInterfaces) { + int subOne = getMaxSubnet(nodeOneInterfaces); + int subTwo = getMaxSubnet(nodeTwoInterfaces); + logger.info("next subnet: {} - {}", subOne, subTwo); + return Math.max(subOne, subTwo) + 1; + } + + private int getMaxSubnet(Collection coreInterfaces) { + int sub = 0; + for (CoreInterface coreInterface : coreInterfaces) { + String[] values = coreInterface.getIp4().split("\\."); + int currentSub = Integer.parseInt(values[IP4_INDEX]); + logger.info("checking {} value {}", coreInterface.getIp4(), currentSub); + sub = Math.max(currentSub, sub); + } + return sub; + } + + public String getIp4Address(int sub, int id) { + return String.format("%s.%s.%s", ip4Base, sub, id); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java b/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java new file mode 100644 index 00000000..6cbd1bf8 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreAnnotatingGraphMousePlugin.java @@ -0,0 +1,57 @@ +package com.core.graph; + +import com.core.Controller; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.VisualizationViewer; +import edu.uci.ics.jung.visualization.annotations.AnnotatingGraphMousePlugin; +import edu.uci.ics.jung.visualization.annotations.Annotation; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; +import java.awt.geom.RectangularShape; + +public class CoreAnnotatingGraphMousePlugin extends AnnotatingGraphMousePlugin { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private JFrame frame = new JFrame(); + + public CoreAnnotatingGraphMousePlugin(Controller controller, RenderContext renderContext) { + super(renderContext); + this.controller = controller; + frame.setVisible(false); + frame.setAlwaysOnTop(true); + } + + @Override + public void mouseReleased(MouseEvent e) { + VisualizationViewer vv = (VisualizationViewer) e.getSource(); + if (e.isPopupTrigger()) { + frame.setLocationRelativeTo(vv); + String annotationString = JOptionPane.showInputDialog(frame, "Annotation:", + "Annotation Label", JOptionPane.PLAIN_MESSAGE); + if (annotationString != null && annotationString.length() > 0) { + Point2D p = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(this.down); + Annotation annotation = new Annotation(annotationString, this.layer, + this.annotationColor, this.fill, p); + this.annotationManager.add(this.layer, annotation); + } + } else if (e.getModifiers() == this.modifiers && this.down != null) { + Point2D out = e.getPoint(); + RectangularShape arect = (RectangularShape) this.rectangularShape.clone(); + arect.setFrameFromDiagonal(this.down, out); + Shape s = vv.getRenderContext().getMultiLayerTransformer().inverseTransform(arect); + Annotation annotation = new Annotation(s, this.layer, this.annotationColor, this.fill, out); + this.annotationManager.add(this.layer, annotation); + } + + this.down = null; + vv.removePostRenderPaintable(this.lensPaintable); + vv.repaint(); + } + + +} diff --git a/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java b/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java new file mode 100644 index 00000000..9d80183e --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreEditingModalGraphMouse.java @@ -0,0 +1,17 @@ +package com.core.graph; + +import com.core.Controller; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse; + +public class CoreEditingModalGraphMouse extends EditingModalGraphMouse { + public CoreEditingModalGraphMouse(Controller controller, NetworkGraph networkGraph, + RenderContext rc, Supplier vertexFactory, Supplier edgeFactory) { + super(rc, vertexFactory, edgeFactory); + remove(annotatingPlugin); + remove(popupEditingPlugin); + annotatingPlugin = new CoreAnnotatingGraphMousePlugin<>(controller, rc); + popupEditingPlugin = new CorePopupGraphMousePlugin<>(controller, networkGraph, vertexFactory, edgeFactory); + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreObservableGraph.java b/corefx/src/main/java/com/core/graph/CoreObservableGraph.java new file mode 100644 index 00000000..cbf678f0 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreObservableGraph.java @@ -0,0 +1,31 @@ +package com.core.graph; + +import edu.uci.ics.jung.graph.Graph; +import edu.uci.ics.jung.graph.ObservableGraph; +import edu.uci.ics.jung.graph.util.EdgeType; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class CoreObservableGraph extends ObservableGraph { + private static final Logger logger = LogManager.getLogger(); + + public CoreObservableGraph(Graph graph) { + super(graph); + } + + @Override + public boolean addEdge(E e, V v1, V v2, EdgeType edgeType) { + if (v1 == null || v2 == null) { + return false; + } + return super.addEdge(e, v1, v2, edgeType); + } + + @Override + public boolean addEdge(E e, V v1, V v2) { + if (v1 == null || v2 == null) { + return false; + } + return super.addEdge(e, v1, v2); + } +} diff --git a/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java b/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java new file mode 100644 index 00000000..3b96c9fa --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CorePopupGraphMousePlugin.java @@ -0,0 +1,78 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreLink; +import com.core.data.CoreNode; +import com.core.data.NodeType; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.algorithms.layout.GraphElementAccessor; +import edu.uci.ics.jung.algorithms.layout.Layout; +import edu.uci.ics.jung.visualization.control.EditingPopupGraphMousePlugin; +import javafx.application.Platform; +import javafx.scene.control.ContextMenu; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.event.MouseEvent; +import java.awt.geom.Point2D; + +public class CorePopupGraphMousePlugin extends EditingPopupGraphMousePlugin { + private static final Logger logger = LogManager.getLogger(); + private final Controller controller; + private final NetworkGraph networkGraph; + private final Layout graphLayout; + private final GraphElementAccessor pickSupport; + + public CorePopupGraphMousePlugin(Controller controller, NetworkGraph networkGraph, + Supplier vertexFactory, Supplier edgeFactory) { + super(vertexFactory, edgeFactory); + this.controller = controller; + this.networkGraph = networkGraph; + graphLayout = this.networkGraph.getGraphLayout(); + pickSupport = this.networkGraph.getGraphViewer().getPickSupport(); + } + + @Override + protected void handlePopup(MouseEvent e) { + logger.info("showing popup!"); + final Point2D p = e.getPoint(); + + final CoreNode node = pickSupport.getVertex(graphLayout, p.getX(), p.getY()); + final CoreLink link = pickSupport.getEdge(graphLayout, p.getX(), p.getY()); + + final ContextMenu contextMenu; + if (node != null) { + contextMenu = handleNodeContext(node); + } else if (link != null) { + contextMenu = new LinkContextMenu(controller, link); + } else { + contextMenu = new ContextMenu(); + } + + if (!contextMenu.getItems().isEmpty()) { + logger.info("showing context menu"); + Platform.runLater(() -> contextMenu.show(controller.getWindow(), + e.getXOnScreen(), e.getYOnScreen())); + } + } + + private ContextMenu handleNodeContext(final CoreNode node) { + ContextMenu contextMenu = new ContextMenu(); + switch (node.getType()) { + case NodeType.DEFAULT: + contextMenu = new NodeContextMenu(controller, node); + break; + case NodeType.WLAN: + contextMenu = new WlanContextMenu(controller, node); + break; + case NodeType.EMANE: + contextMenu = new EmaneContextMenu(controller, node); + break; + default: + logger.warn("no context menu for node: {}", node.getType()); + break; + } + + return contextMenu; + } +} diff --git a/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java b/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java new file mode 100644 index 00000000..25c49fa3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/CoreVertexLabelRenderer.java @@ -0,0 +1,43 @@ +package com.core.graph; + +import edu.uci.ics.jung.visualization.renderers.DefaultVertexLabelRenderer; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; + +public class CoreVertexLabelRenderer extends DefaultVertexLabelRenderer { + private Color foregroundColor = Color.WHITE; + private Color backgroundColor = Color.BLACK; + + CoreVertexLabelRenderer() { + super(Color.YELLOW); + } + + public void setColors(Color foregroundColor, Color backgroundColor) { + this.foregroundColor = foregroundColor; + this.backgroundColor = backgroundColor; + } + + @Override + public Component getVertexLabelRendererComponent(JComponent vv, Object value, Font font, boolean isSelected, V vertex) { + super.setForeground(foregroundColor); + if (isSelected) { + this.setForeground(this.pickedVertexLabelColor); + } + + super.setBackground(backgroundColor); + if (font != null) { + this.setFont(font); + } else { + this.setFont(vv.getFont()); + } + + this.setIcon(null); + EmptyBorder padding = new EmptyBorder(5, 5, 5, 5); + this.setBorder(padding); + this.setValue(value); + setFont(getFont().deriveFont(Font.BOLD)); + return this; + } +} diff --git a/corefx/src/main/java/com/core/graph/EmaneContextMenu.java b/corefx/src/main/java/com/core/graph/EmaneContextMenu.java new file mode 100644 index 00000000..848bbfb3 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/EmaneContextMenu.java @@ -0,0 +1,26 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.dialogs.MobilityPlayerDialog; + +class EmaneContextMenu extends AbstractNodeContextMenu { + EmaneContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private void setup() { + addMenuItem("EMANE Settings", event -> controller.getNodeEmaneDialog().showDialog(coreNode)); + if (controller.getCoreClient().isRunning()) { + MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(coreNode.getId()); + if (mobilityPlayerDialog != null && !mobilityPlayerDialog.getStage().isShowing()) { + addMenuItem("Mobility Script", event -> mobilityPlayerDialog.show()); + } + } else { + addMenuItem("Mobility", event -> controller.getMobilityDialog().showDialog(coreNode)); + addMenuItem("Link MDRs", event -> controller.getNetworkGraph().linkMdrs(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/GraphContextMenu.java b/corefx/src/main/java/com/core/graph/GraphContextMenu.java new file mode 100644 index 00000000..c93ecba9 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/GraphContextMenu.java @@ -0,0 +1,22 @@ +package com.core.graph; + +import com.core.Controller; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.MenuItem; + +abstract class GraphContextMenu extends ContextMenu { + final Controller controller; + + GraphContextMenu(Controller controller) { + super(); + this.controller = controller; + } + + void addMenuItem(String text, EventHandler handler) { + MenuItem menuItem = new MenuItem(text); + menuItem.setOnAction(handler); + getItems().add(menuItem); + } +} diff --git a/corefx/src/main/java/com/core/graph/LinkContextMenu.java b/corefx/src/main/java/com/core/graph/LinkContextMenu.java new file mode 100644 index 00000000..d9b11115 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/LinkContextMenu.java @@ -0,0 +1,21 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreLink; + +class LinkContextMenu extends GraphContextMenu { + final CoreLink coreLink; + + LinkContextMenu(Controller controller, CoreLink coreLink) { + super(controller); + this.coreLink = coreLink; + setup(); + } + + private void setup() { + if (!controller.getCoreClient().isRunning()) { + addMenuItem("Delete Link", + event -> controller.getNetworkGraph().removeLink(coreLink)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/NetworkGraph.java b/corefx/src/main/java/com/core/graph/NetworkGraph.java new file mode 100644 index 00000000..7d55cdb2 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/NetworkGraph.java @@ -0,0 +1,492 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.*; +import com.core.ui.Toast; +import com.core.ui.dialogs.TerminalDialog; +import com.core.utils.Configuration; +import com.core.utils.IconUtils; +import com.google.common.base.Supplier; +import edu.uci.ics.jung.algorithms.layout.StaticLayout; +import edu.uci.ics.jung.graph.ObservableGraph; +import edu.uci.ics.jung.graph.event.GraphEvent; +import edu.uci.ics.jung.graph.event.GraphEventListener; +import edu.uci.ics.jung.graph.util.Pair; +import edu.uci.ics.jung.visualization.RenderContext; +import edu.uci.ics.jung.visualization.VisualizationViewer; +import edu.uci.ics.jung.visualization.annotations.AnnotationControls; +import edu.uci.ics.jung.visualization.control.EditingModalGraphMouse; +import edu.uci.ics.jung.visualization.control.GraphMouseListener; +import edu.uci.ics.jung.visualization.control.ModalGraphMouse; +import edu.uci.ics.jung.visualization.decorators.EdgeShape; +import edu.uci.ics.jung.visualization.renderers.Renderer; +import javafx.application.Platform; +import lombok.Data; +import org.apache.commons.net.util.SubnetUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.MouseEvent; +import java.awt.geom.Ellipse2D; +import java.io.IOException; +import java.util.*; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + + +@Data +public class NetworkGraph { + private static final Logger logger = LogManager.getLogger(); + private static final int EDGE_LABEL_OFFSET = -5; + private static final int EDGE_WIDTH = 5; + private Controller controller; + private ObservableGraph graph; + private StaticLayout graphLayout; + private VisualizationViewer graphViewer; + private EditingModalGraphMouse graphMouse; + private AnnotationControls annotationControls; + + private SubnetUtils subnetUtils = new SubnetUtils("10.0.0.0/24"); + private CoreAddresses coreAddresses = new CoreAddresses("10.0"); + private NodeType nodeType; + private Map nodeMap = new ConcurrentHashMap<>(); + private int vertexId = 1; + private int linkId = 1; + private Supplier vertexFactory = () -> new CoreNode(vertexId++); + private Supplier linkFactory = () -> new CoreLink(linkId++); + private CorePopupGraphMousePlugin customPopupPlugin; + private CoreAnnotatingGraphMousePlugin customAnnotatingPlugin; + private BackgroundPaintable backgroundPaintable; + private CoreVertexLabelRenderer nodeLabelRenderer = new CoreVertexLabelRenderer(); + + // display options + private boolean showThroughput = false; + private Double throughputLimit = null; + private int throughputWidth = 10; + + public NetworkGraph(Controller controller) { + this.controller = controller; + graph = new CoreObservableGraph<>(new UndirectedSimpleGraph<>()); + graph.addGraphEventListener(graphEventListener); + graphLayout = new StaticLayout<>(graph); + graphViewer = new VisualizationViewer<>(graphLayout); + graphViewer.setBackground(Color.WHITE); + graphViewer.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.S); + + RenderContext renderContext = graphViewer.getRenderContext(); + + // node render properties + renderContext.setVertexLabelTransformer(CoreNode::getName); + renderContext.setVertexLabelRenderer(nodeLabelRenderer); + renderContext.setVertexShapeTransformer(node -> { + double offset = -(IconUtils.ICON_SIZE / 2.0); + return new Ellipse2D.Double(offset, offset, IconUtils.ICON_SIZE, IconUtils.ICON_SIZE); + }); + renderContext.setVertexIconTransformer(vertex -> { + long wirelessLinks = wirelessLinkCount(vertex); + vertex.getRadioIcon().setWiressLinks(wirelessLinks); + return vertex.getGraphIcon(); + }); + + // link render properties + renderContext.setEdgeLabelTransformer(edge -> { + if (!showThroughput || edge == null) { + return null; + } + double kbps = edge.getThroughput() / 1000.0; + return String.format("%.2f kbps", kbps); + }); + renderContext.setLabelOffset(EDGE_LABEL_OFFSET); + renderContext.setEdgeStrokeTransformer(edge -> { + // determine edge width + int width = EDGE_WIDTH; + if (throughputLimit != null && edge.getThroughput() > throughputLimit) { + width = throughputWidth; + } + + LinkTypes linkType = LinkTypes.get(edge.getType()); + if (LinkTypes.WIRELESS == linkType) { + float[] dash = {15.0f}; + return new BasicStroke(width, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_ROUND, + 0, dash, 0); + } else { + return new BasicStroke(width); + } + }); + renderContext.setEdgeShapeTransformer(EdgeShape.line(graph)); + renderContext.setEdgeDrawPaintTransformer(edge -> { + LinkTypes linkType = LinkTypes.get(edge.getType()); + if (LinkTypes.WIRELESS == linkType) { + return Color.BLUE; + } else { + return Color.BLACK; + } + }); + renderContext.setEdgeIncludePredicate(predicate -> predicate.element.isVisible()); + + graphViewer.setVertexToolTipTransformer(renderContext.getVertexLabelTransformer()); + graphMouse = new CoreEditingModalGraphMouse<>(controller, this, renderContext, + vertexFactory, linkFactory); + graphViewer.setGraphMouse(graphMouse); + + // mouse events + graphViewer.addGraphMouseListener(new GraphMouseListener() { + @Override + public void graphClicked(CoreNode node, MouseEvent mouseEvent) { + // double click + logger.info("click count: {}, running?: {}", mouseEvent.getClickCount(), + controller.getCoreClient().isRunning()); + + if (mouseEvent.getClickCount() == 2 && controller.getCoreClient().isRunning()) { + if (controller.getCoreClient().isLocalConnection()) { + try { + String shellCommand = controller.getConfiguration().getShellCommand(); + String terminalCommand = controller.getCoreClient().getTerminalCommand(node); + terminalCommand = String.format("%s %s", shellCommand, terminalCommand); + logger.info("launching node terminal: {}", terminalCommand); + String[] commands = terminalCommand.split("\\s+"); + logger.info("launching node terminal: {}", Arrays.toString(commands)); + Process p = new ProcessBuilder(commands).start(); + try { + if (!p.waitFor(5, TimeUnit.SECONDS)) { + Toast.error("Node terminal command failed"); + } + } catch (InterruptedException ex) { + logger.error("error waiting for terminal to start", ex); + } + } catch (IOException ex) { + logger.error("error launching terminal", ex); + Toast.error("Node terminal failed to start"); + } + } else { + Platform.runLater(() -> { + TerminalDialog terminalDialog = new TerminalDialog(controller); + terminalDialog.setOwner(controller.getWindow()); + terminalDialog.showDialog(node); + }); + } + } + } + + @Override + public void graphPressed(CoreNode node, MouseEvent mouseEvent) { + logger.debug("graph pressed: {} - {}", node, mouseEvent); + } + + @Override + public void graphReleased(CoreNode node, MouseEvent mouseEvent) { + if (SwingUtilities.isLeftMouseButton(mouseEvent)) { + Double newX = graphLayout.getX(node); + Double newY = graphLayout.getY(node); + Double oldX = node.getPosition().getX(); + Double oldY = node.getPosition().getY(); + if (newX.equals(oldX) && newY.equals(oldY)) { + return; + } + logger.debug("graph moved node({}): {},{}", node.getName(), newX, newY); + node.getPosition().setX(newX); + node.getPosition().setY(newY); + + // upate node when session is active + if (controller.getCoreClient().isRunning()) { + try { + controller.getCoreClient().editNode(node); + } catch (IOException ex) { + Toast.error("failed to update node location"); + } + } + } + } + }); + } + + private Color convertJfxColor(String hexValue) { + javafx.scene.paint.Color color = javafx.scene.paint.Color.web(hexValue); + return new Color((float) color.getRed(), (float) color.getGreen(), (float) color.getBlue()); + } + + public void updatePreferences(Configuration configuration) { + Color nodeLabelColor = convertJfxColor(configuration.getNodeLabelColor()); + Color nodeLabelBackgroundColor = convertJfxColor(configuration.getNodeLabelBackgroundColor()); + nodeLabelRenderer.setColors(nodeLabelColor, nodeLabelBackgroundColor); + throughputLimit = configuration.getThroughputLimit(); + if (configuration.getThroughputWidth() != null) { + throughputWidth = configuration.getThroughputWidth(); + } + graphViewer.repaint(); + } + + public void setBackground(String imagePath) { + try { + backgroundPaintable = new BackgroundPaintable<>(imagePath, graphViewer); + graphViewer.addPreRenderPaintable(backgroundPaintable); + graphViewer.repaint(); + } catch (IOException ex) { + logger.error("error setting background", ex); + } + } + + public void removeBackground() { + if (backgroundPaintable != null) { + graphViewer.removePreRenderPaintable(backgroundPaintable); + graphViewer.repaint(); + backgroundPaintable = null; + } + } + + public void setMode(ModalGraphMouse.Mode mode) { + graphMouse.setMode(mode); + } + + public void reset() { + logger.info("network graph reset"); + vertexId = 1; + linkId = 1; + for (CoreNode node : nodeMap.values()) { + graph.removeVertex(node); + } + nodeMap.clear(); + graphViewer.repaint(); + } + + public void updatePositions() { + for (CoreNode node : graph.getVertices()) { + Double x = graphLayout.getX(node); + Double y = graphLayout.getY(node); + node.getPosition().setX(x); + node.getPosition().setY(y); + logger.debug("updating node position node({}): {},{}", node, x, y); + } + } + + public CoreNode getVertex(int id) { + return nodeMap.get(id); + } + + private GraphEventListener graphEventListener = graphEvent -> { + logger.info("graph event: {}", graphEvent.getType()); + switch (graphEvent.getType()) { + case EDGE_ADDED: + handleEdgeAdded((GraphEvent.Edge) graphEvent); + break; + case EDGE_REMOVED: + handleEdgeRemoved((GraphEvent.Edge) graphEvent); + break; + case VERTEX_ADDED: + handleVertexAdded((GraphEvent.Vertex) graphEvent); + break; + case VERTEX_REMOVED: + handleVertexRemoved((GraphEvent.Vertex) graphEvent); + break; + } + }; + + private void handleEdgeAdded(GraphEvent.Edge edgeEvent) { + CoreLink link = edgeEvent.getEdge(); + if (!link.isLoaded()) { + Pair endpoints = graph.getEndpoints(link); + + CoreNode nodeOne = endpoints.getFirst(); + CoreNode nodeTwo = endpoints.getSecond(); + + // create interfaces for nodes + int sub = coreAddresses.getSubnet( + getInterfaces(nodeOne), + getInterfaces(nodeTwo) + ); + + link.setNodeOne(nodeOne.getId()); + if (isNode(nodeOne)) { + int interfaceOneId = nextInterfaceId(nodeOne); + CoreInterface interfaceOne = createInterface(nodeOne, sub, interfaceOneId); + link.setInterfaceOne(interfaceOne); + } + + link.setNodeTwo(nodeTwo.getId()); + if (isNode(nodeTwo)) { + int interfaceTwoId = nextInterfaceId(nodeTwo); + CoreInterface interfaceTwo = createInterface(nodeTwo, sub, interfaceTwoId); + link.setInterfaceTwo(interfaceTwo); + } + + boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo); + link.setVisible(isVisible); + + logger.info("adding user created edge: {}", link); + } + } + + public List getInterfaces(CoreNode node) { + return graph.getIncidentEdges(node).stream() + .map(link -> { + if (node.getId().equals(link.getNodeOne())) { + return link.getInterfaceOne(); + } else { + return link.getInterfaceTwo(); + } + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + private int nextInterfaceId(CoreNode node) { + Set interfaceIds = graph.getIncidentEdges(node).stream() + .map(link -> { + if (node.getId().equals(link.getNodeOne())) { + return link.getInterfaceOne(); + } else { + return link.getInterfaceTwo(); + } + }) + .filter(Objects::nonNull) + .map(CoreInterface::getId) + .collect(Collectors.toSet()); + + int i = 0; + while (true) { + if (!interfaceIds.contains(i)) { + return i; + } + + i += 1; + } + } + + private boolean isNode(CoreNode node) { + return node.getType() == NodeType.DEFAULT; + } + + private CoreInterface createInterface(CoreNode node, int sub, int interfaceId) { + CoreInterface coreInterface = new CoreInterface(); + coreInterface.setId(interfaceId); + coreInterface.setName(String.format("eth%s", interfaceId)); + String nodeOneIp4 = coreAddresses.getIp4Address(sub, node.getId()); + coreInterface.setIp4(nodeOneIp4); + coreInterface.setIp4Mask(CoreAddresses.IP4_MASK); + return coreInterface; + } + + private void handleEdgeRemoved(GraphEvent.Edge edgeEvent) { + CoreLink link = edgeEvent.getEdge(); + logger.info("removed edge: {}", link); + } + + private void handleVertexAdded(GraphEvent.Vertex vertexEvent) { + CoreNode node = vertexEvent.getVertex(); + if (!node.isLoaded()) { + node.setNodeType(nodeType); + if (node.getType() == NodeType.EMANE) { + String emaneModel = controller.getNodeEmaneDialog().getModels().get(0); + node.setEmane(emaneModel); + } + + logger.info("adding user created node: {}", node); + nodeMap.put(node.getId(), node); + } + } + + private void handleVertexRemoved(GraphEvent.Vertex vertexEvent) { + CoreNode node = vertexEvent.getVertex(); + logger.info("removed vertex: {}", node); + nodeMap.remove(node.getId()); + } + + public void addNode(CoreNode node) { + vertexId = Math.max(node.getId() + 1, node.getId()); + double x = Math.abs(node.getPosition().getX()); + double y = Math.abs(node.getPosition().getY()); + logger.info("adding session node: {}", node); + graph.addVertex(node); + graphLayout.setLocation(node, x, y); + nodeMap.put(node.getId(), node); + } + + public void setNodeLocation(CoreNode nodeData) { + // update actual graph node + CoreNode node = nodeMap.get(nodeData.getId()); + node.getPosition().setX(nodeData.getPosition().getX()); + node.getPosition().setY(nodeData.getPosition().getY()); + + // set graph node location + double x = Math.abs(node.getPosition().getX()); + double y = Math.abs(node.getPosition().getY()); + graphLayout.setLocation(node, x, y); + graphViewer.repaint(); + } + + public void removeNode(CoreNode node) { + try { + controller.getCoreClient().deleteNode(node); + } catch (IOException ex) { + logger.error("error deleting node"); + Toast.error(String.format("Error deleting node: %s", node.getName())); + } + graphViewer.getPickedVertexState().pick(node, false); + graph.removeVertex(node); + graphViewer.repaint(); + } + + private boolean isWirelessNode(CoreNode node) { + return node.getType() == NodeType.EMANE || node.getType() == NodeType.WLAN; + } + + private boolean checkForWirelessNode(CoreNode nodeOne, CoreNode nodeTwo) { + boolean result = isWirelessNode(nodeOne); + return result || isWirelessNode(nodeTwo); + } + + private long wirelessLinkCount(CoreNode node) { + return graph.getNeighbors(node).stream() + .filter(this::isWirelessNode) + .count(); + } + + public void addLink(CoreLink link) { + logger.info("adding session link: {}", link); + link.setId(linkId++); + CoreNode nodeOne = nodeMap.get(link.getNodeOne()); + CoreNode nodeTwo = nodeMap.get(link.getNodeTwo()); + + boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo); + link.setVisible(isVisible); + + graph.addEdge(link, nodeOne, nodeTwo); + } + + public void removeWirelessLink(CoreLink link) { + logger.info("deleting link: {}", link); + CoreNode nodeOne = nodeMap.get(link.getNodeOne()); + CoreNode nodeTwo = nodeMap.get(link.getNodeTwo()); + + CoreLink existingLink = graph.findEdge(nodeOne, nodeTwo); + if (existingLink != null) { + graph.removeEdge(existingLink); + } + } + + public void removeLink(CoreLink link) { + graphViewer.getPickedEdgeState().pick(link, false); + graph.removeEdge(link); + graphViewer.repaint(); + } + + public void linkMdrs(CoreNode node) { + for (CoreNode currentNode : graph.getVertices()) { + if (!"mdr".equals(currentNode.getModel())) { + continue; + } + + // only links mdrs we have not already linked + Collection links = graph.findEdgeSet(node, currentNode); + if (links.isEmpty()) { + CoreLink link = linkFactory.get(); + graph.addEdge(link, currentNode, node); + graphViewer.repaint(); + } + } + } +} diff --git a/corefx/src/main/java/com/core/graph/NodeContextMenu.java b/corefx/src/main/java/com/core/graph/NodeContextMenu.java new file mode 100644 index 00000000..d50cd58a --- /dev/null +++ b/corefx/src/main/java/com/core/graph/NodeContextMenu.java @@ -0,0 +1,116 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.Toast; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuItem; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +class NodeContextMenu extends AbstractNodeContextMenu { + private static final Logger logger = LogManager.getLogger(); + + NodeContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private MenuItem createStartItem(String service) { + MenuItem menuItem = new MenuItem("Start"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().startService(coreNode, service); + if (result) { + Toast.success("Started " + service); + } else { + Toast.error("Failure to start " + service); + } + } catch (IOException ex) { + Toast.error("Error starting " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createStopItem(String service) { + MenuItem menuItem = new MenuItem("Stop"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().stopService(coreNode, service); + if (result) { + Toast.success("Stopped " + service); + } else { + Toast.error("Failure to stop " + service); + } + } catch (IOException ex) { + Toast.error("Error stopping " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createRestartItem(String service) { + MenuItem menuItem = new MenuItem("Restart"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().restartService(coreNode, service); + if (result) { + Toast.success("Restarted " + service); + } else { + Toast.error("Failure to restart " + service); + } + } catch (IOException ex) { + Toast.error("Error restarting " + service, ex); + } + }); + return menuItem; + } + + private MenuItem createValidateItem(String service) { + MenuItem menuItem = new MenuItem("Validate"); + menuItem.setOnAction(event -> { + try { + boolean result = controller.getCoreClient().validateService(coreNode, service); + if (result) { + Toast.success("Validated " + service); + } else { + Toast.error("Validation failed for " + service); + } + } catch (IOException ex) { + Toast.error("Error validating " + service, ex); + } + }); + return menuItem; + } + + private void setup() { + if (controller.getCoreClient().isRunning()) { + Set services = coreNode.getServices(); + if (services.isEmpty()) { + services = controller.getDefaultServices().getOrDefault(coreNode.getModel(), Collections.emptySet()); + } + + if (!services.isEmpty()) { + Menu menu = new Menu("Manage Services"); + for (String service : services) { + Menu serviceMenu = new Menu(service); + MenuItem startItem = createStartItem(service); + MenuItem stopItem = createStopItem(service); + MenuItem restartItem = createRestartItem(service); + MenuItem validateItem = createValidateItem(service); + serviceMenu.getItems().addAll(startItem, stopItem, restartItem, validateItem); + menu.getItems().add(serviceMenu); + } + getItems().add(menu); + } + } else { + addMenuItem("Services", event -> controller.getNodeServicesDialog().showDialog(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/RadioIcon.java b/corefx/src/main/java/com/core/graph/RadioIcon.java new file mode 100644 index 00000000..e3139eaf --- /dev/null +++ b/corefx/src/main/java/com/core/graph/RadioIcon.java @@ -0,0 +1,31 @@ +package com.core.graph; + +import com.core.utils.IconUtils; +import lombok.Data; + +import javax.swing.*; +import java.awt.*; + +@Data +public class RadioIcon implements Icon { + private long wiressLinks = 0; + + @Override + public int getIconHeight() { + return IconUtils.ICON_SIZE; + } + + @Override + public int getIconWidth() { + return IconUtils.ICON_SIZE; + } + + @Override + public void paintIcon(Component c, Graphics g, int x, int y) { + g.setColor(Color.black); + for (int i = 0; i < wiressLinks; i++) { + g.fillOval(x, y, 10, 10); + x += 15; + } + } +} diff --git a/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java b/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java new file mode 100644 index 00000000..355b44a0 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/UndirectedSimpleGraph.java @@ -0,0 +1,24 @@ +package com.core.graph; + +import edu.uci.ics.jung.graph.UndirectedSparseGraph; +import edu.uci.ics.jung.graph.util.EdgeType; +import edu.uci.ics.jung.graph.util.Pair; + +public class UndirectedSimpleGraph extends UndirectedSparseGraph { + @Override + public boolean addEdge(E edge, Pair endpoints, EdgeType edgeType) { + Pair newEndpoints = getValidatedEndpoints(edge, endpoints); + if (newEndpoints == null) { + return false; + } + + V first = newEndpoints.getFirst(); + V second = newEndpoints.getSecond(); + + if (first.equals(second)) { + return false; + } else { + return super.addEdge(edge, endpoints, edgeType); + } + } +} diff --git a/corefx/src/main/java/com/core/graph/WlanContextMenu.java b/corefx/src/main/java/com/core/graph/WlanContextMenu.java new file mode 100644 index 00000000..937eef15 --- /dev/null +++ b/corefx/src/main/java/com/core/graph/WlanContextMenu.java @@ -0,0 +1,26 @@ +package com.core.graph; + +import com.core.Controller; +import com.core.data.CoreNode; +import com.core.ui.dialogs.MobilityPlayerDialog; + +class WlanContextMenu extends AbstractNodeContextMenu { + WlanContextMenu(Controller controller, CoreNode coreNode) { + super(controller, coreNode); + setup(); + } + + private void setup() { + addMenuItem("WLAN Settings", event -> controller.getNodeWlanDialog().showDialog(coreNode)); + if (controller.getCoreClient().isRunning()) { + MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(coreNode.getId()); + if (mobilityPlayerDialog != null && !mobilityPlayerDialog.getStage().isShowing()) { + addMenuItem("Mobility Script", event -> mobilityPlayerDialog.show()); + } + } else { + addMenuItem("Mobility", event -> controller.getMobilityDialog().showDialog(coreNode)); + addMenuItem("Link MDRs", event -> controller.getNetworkGraph().linkMdrs(coreNode)); + addMenuItem("Delete Node", event -> controller.deleteNode(coreNode)); + } + } +} diff --git a/corefx/src/main/java/com/core/ui/AnnotationToolbar.java b/corefx/src/main/java/com/core/ui/AnnotationToolbar.java new file mode 100644 index 00000000..b32d3c8b --- /dev/null +++ b/corefx/src/main/java/com/core/ui/AnnotationToolbar.java @@ -0,0 +1,104 @@ +package com.core.ui; + +import com.core.graph.NetworkGraph; +import com.core.utils.FxmlUtils; +import com.jfoenix.controls.JFXColorPicker; +import com.jfoenix.controls.JFXComboBox; +import com.jfoenix.controls.JFXToggleButton; +import edu.uci.ics.jung.visualization.annotations.Annotation; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.layout.GridPane; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.awt.*; +import java.awt.geom.Ellipse2D; +import java.awt.geom.RectangularShape; +import java.awt.geom.RoundRectangle2D; + +public class AnnotationToolbar extends GridPane { + private static final Logger logger = LogManager.getLogger(); + private static final String RECTANGLE = "Rectangle"; + private static final String ROUND_RECTANGLE = "RoundRectangle"; + private static final String ELLIPSE = "Ellipse"; + private static final String UPPER_LAYER = "Upper"; + private static final String LOWER_LAYER = "Lower"; + private NetworkGraph graph; + @FXML private JFXComboBox shapeCombo; + @FXML private JFXColorPicker colorPicker; + @FXML private JFXComboBox layerCombo; + @FXML private JFXToggleButton fillToggle; + + public AnnotationToolbar(NetworkGraph graph) { + this.graph = graph; + FxmlUtils.loadRootController(this, "/fxml/annotation_toolbar.fxml"); + + // setup annotation shape combo + shapeCombo.getItems().addAll(RECTANGLE, ROUND_RECTANGLE, ELLIPSE); + shapeCombo.setOnAction(this::shapeChange); + shapeCombo.getSelectionModel().selectFirst(); + + // setup annotation layer combo + layerCombo.getItems().addAll(LOWER_LAYER, UPPER_LAYER); + layerCombo.setOnAction(this::layerChange); + layerCombo.getSelectionModel().selectFirst(); + + // setup annotation color picker + colorPicker.setOnAction(this::colorChange); + colorPicker.setValue(javafx.scene.paint.Color.AQUA); + colorPicker.fireEvent(new ActionEvent()); + + // setup annotation toggle fill + fillToggle.setOnAction(this::fillChange); + } + + private void fillChange(ActionEvent event) { + boolean selected = fillToggle.isSelected(); + graph.getGraphMouse().getAnnotatingPlugin().setFill(selected); + } + + private void colorChange(ActionEvent event) { + javafx.scene.paint.Color fxColor = colorPicker.getValue(); + java.awt.Color color = new java.awt.Color( + (float) fxColor.getRed(), + (float) fxColor.getGreen(), + (float) fxColor.getBlue(), + (float) fxColor.getOpacity() + ); + logger.info("color selected: {}", fxColor); + graph.getGraphMouse().getAnnotatingPlugin().setAnnotationColor(color); + } + + private void layerChange(ActionEvent event) { + String selected = layerCombo.getSelectionModel().getSelectedItem(); + logger.info("annotation layer selected: {}", selected); + Annotation.Layer layer; + if (LOWER_LAYER.equals(selected)) { + layer = Annotation.Layer.LOWER; + } else { + layer = Annotation.Layer.UPPER; + } + graph.getGraphMouse().getAnnotatingPlugin().setLayer(layer); + } + + private void shapeChange(ActionEvent event) { + String selected = shapeCombo.getSelectionModel().getSelectedItem(); + logger.info("annotation shape selected: {}", selected); + RectangularShape shape = new Rectangle(); + switch (selected) { + case RECTANGLE: + shape = new Rectangle(); + break; + case ROUND_RECTANGLE: + shape = new RoundRectangle2D.Double(0, 0, 0, 0, 50.0, 50.0); + break; + case ELLIPSE: + shape = new Ellipse2D.Double(); + break; + default: + Toast.error("Unknown annotation shape " + selected); + } + graph.getGraphMouse().getAnnotatingPlugin().setRectangularShape(shape); + } +} diff --git a/corefx/src/main/java/com/core/ui/GraphToolbar.java b/corefx/src/main/java/com/core/ui/GraphToolbar.java new file mode 100644 index 00000000..f64ca73a --- /dev/null +++ b/corefx/src/main/java/com/core/ui/GraphToolbar.java @@ -0,0 +1,297 @@ +package com.core.ui; + +import com.core.Controller; +import com.core.data.NodeType; +import com.core.utils.FxmlUtils; +import com.core.utils.IconUtils; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXListView; +import com.jfoenix.controls.JFXPopup; +import com.jfoenix.svg.SVGGlyph; +import edu.uci.ics.jung.visualization.control.ModalGraphMouse; +import javafx.application.Platform; +import javafx.css.PseudoClass; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.ComboBox; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.image.ImageView; +import javafx.scene.layout.VBox; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; + +public class GraphToolbar extends VBox { + private static final Logger logger = LogManager.getLogger(); + private static final int ICON_SIZE = 40; + private static final int NODES_ICON_SIZE = 20; + private static final PseudoClass START_CLASS = PseudoClass.getPseudoClass("start"); + private static final PseudoClass STOP_CLASS = PseudoClass.getPseudoClass("stop"); + private static final PseudoClass SELECTED_CLASS = PseudoClass.getPseudoClass("selected"); + private final Controller controller; + private final Map labelMap = new HashMap<>(); + private SVGGlyph startIcon; + private SVGGlyph stopIcon; + private JFXListViewLeaflet'},initialize:function(t){l(this,t),this._attributions={}},onAdd:function(t){t.attributionControl=this,this._container=G("div","leaflet-control-attribution"),wt(this._container);for(var i in t._layers)t._layers[i].getAttribution&&this.addAttribution(t._layers[i].getAttribution());return this._update(),this._container},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):this},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):this},_update:function(){if(this._map){var t=[];for(var i in this._attributions)this._attributions[i]&&t.push(i);var e=[];this.options.prefix&&e.push(this.options.prefix),t.length&&e.push(t.join(", ")),this._container.innerHTML=e.join(" | ")}}});be.mergeOptions({attributionControl:!0}),be.addInitHook(function(){this.options.attributionControl&&(new Ze).addTo(this)});Te.Layers=Me,Te.Zoom=Ce,Te.Scale=Se,Te.Attribution=Ze,ze.layers=function(t,i,e){return new Me(t,i,e)},ze.zoom=function(t){return new Ce(t)},ze.scale=function(t){return new Se(t)},ze.attribution=function(t){return new Ze(t)};var Ee=v.extend({initialize:function(t){this._map=t},enable:function(){return this._enabled?this:(this._enabled=!0,this.addHooks(),this)},disable:function(){return this._enabled?(this._enabled=!1,this.removeHooks(),this):this},enabled:function(){return!!this._enabled}});Ee.addTo=function(t,i){return t.addHandler(i,this),this};var ke,Ae={Events:li},Be=qi?"touchstart mousedown":"mousedown",Ie={mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},Oe={mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"},Re=ci.extend({options:{clickTolerance:3},initialize:function(t,i,e,n){l(this,n),this._element=t,this._dragStartTarget=i||t,this._preventOutline=e},enable:function(){this._enabled||(mt(this._dragStartTarget,Be,this._onDown,this),this._enabled=!0)},disable:function(){this._enabled&&(Re._dragging===this&&this.finishDrag(),ft(this._dragStartTarget,Be,this._onDown,this),this._enabled=!1,this._moved=!1)},_onDown:function(t){if(!t._simulated&&this._enabled&&(this._moved=!1,!$(this._element,"leaflet-zoom-anim")&&!(Re._dragging||t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(Re._dragging=this,this._preventOutline&&ct(this._element),ut(),fi(),this._moving)))){this.fire("down");var i=t.touches?t.touches[0]:t,e=dt(this._element);this._startPoint=new x(i.clientX,i.clientY),this._parentScale=pt(e),mt(document,Oe[t.type],this._onMove,this),mt(document,Ie[t.type],this._onUp,this)}},_onMove:function(t){if(!t._simulated&&this._enabled)if(t.touches&&t.touches.length>1)this._moved=!0;else{var i=t.touches&&1===t.touches.length?t.touches[0]:t,e=new x(i.clientX,i.clientY)._subtract(this._startPoint);(e.x||e.y)&&(Math.abs(e.x)+Math.abs(e.y)1e-7;h++)i=s*Math.sin(a),i=Math.pow((1-i)/(1+i),s/2),a+=u=Math.PI/2-2*Math.atan(r*i)-a;return new M(a*e,t.x*e/n)}},He=(Object.freeze||Object)({LonLat:je,Mercator:We,SphericalMercator:mi}),Fe=i({},pi,{code:"EPSG:3395",projection:We,transformation:function(){var t=.5/(Math.PI*We.R);return Z(t,.5,-t,.5)}()}),Ue=i({},pi,{code:"EPSG:4326",projection:je,transformation:Z(1/180,1,-1/180,.5)}),Ve=i({},di,{projection:je,transformation:Z(1,0,-1,0),scale:function(t){return Math.pow(2,t)},zoom:function(t){return Math.log(t)/Math.LN2},distance:function(t,i){var e=i.lng-t.lng,n=i.lat-t.lat;return Math.sqrt(e*e+n*n)},infinite:!0});di.Earth=pi,di.EPSG3395=Fe,di.EPSG3857=yi,di.EPSG900913=xi,di.EPSG4326=Ue,di.Simple=Ve;var qe=ci.extend({options:{pane:"overlayPane",attribution:null,bubblingMouseEvents:!0},addTo:function(t){return t.addLayer(this),this},remove:function(){return this.removeFrom(this._map||this._mapToAdd)},removeFrom:function(t){return t&&t.removeLayer(this),this},getPane:function(t){return this._map.getPane(t?this.options[t]||t:this.options.pane)},addInteractiveTarget:function(t){return this._map._targets[n(t)]=this,this},removeInteractiveTarget:function(t){return delete this._map._targets[n(t)],this},getAttribution:function(){return this.options.attribution},_layerAdd:function(t){var i=t.target;if(i.hasLayer(this)){if(this._map=i,this._zoomAnimated=i._zoomAnimated,this.getEvents){var e=this.getEvents();i.on(e,this),this.once("remove",function(){i.off(e,this)},this)}this.onAdd(i),this.getAttribution&&i.attributionControl&&i.attributionControl.addAttribution(this.getAttribution()),this.fire("add"),i.fire("layeradd",{layer:this})}}});be.include({addLayer:function(t){if(!t._layerAdd)throw new Error("The provided object is not a Layer.");var i=n(t);return this._layers[i]?this:(this._layers[i]=t,t._mapToAdd=this,t.beforeAdd&&t.beforeAdd(this),this.whenReady(t._layerAdd,t),this)},removeLayer:function(t){var i=n(t);return this._layers[i]?(this._loaded&&t.onRemove(this),t.getAttribution&&this.attributionControl&&this.attributionControl.removeAttribution(t.getAttribution()),delete this._layers[i],this._loaded&&(this.fire("layerremove",{layer:t}),t.fire("remove")),t._map=t._mapToAdd=null,this):this},hasLayer:function(t){return!!t&&n(t)in this._layers},eachLayer:function(t,i){for(var e in this._layers)t.call(i,this._layers[e]);return this},_addLayers:function(t){for(var i=0,e=(t=t?oi(t)?t:[t]:[]).length;ithis._layersMaxZoom&&this.setZoom(this._layersMaxZoom),void 0===this.options.minZoom&&this._layersMinZoom&&this.getZoom()i)return r=(n-i)/e,this._map.layerPointToLatLng([s.x-r*(s.x-o.x),s.y-r*(s.y-o.y)])},getBounds:function(){return this._bounds},addLatLng:function(t,i){return i=i||this._defaultShape(),t=C(t),i.push(t),this._bounds.extend(t),this.redraw()},_setLatLngs:function(t){this._bounds=new T,this._latlngs=this._convertLatLngs(t)},_defaultShape:function(){return jt(this._latlngs)?this._latlngs:this._latlngs[0]},_convertLatLngs:function(t){for(var i=[],e=jt(t),n=0,o=t.length;n=2&&i[0]instanceof M&&i[0].equals(i[e-1])&&i.pop(),i},_setLatLngs:function(t){nn.prototype._setLatLngs.call(this,t),jt(this._latlngs)&&(this._latlngs=[this._latlngs])},_defaultShape:function(){return jt(this._latlngs[0])?this._latlngs[0]:this._latlngs[0][0]},_clipPoints:function(){var t=this._renderer._bounds,i=this.options.weight,e=new x(i,i);if(t=new P(t.min.subtract(e),t.max.add(e)),this._parts=[],this._pxBounds&&this._pxBounds.intersects(t))if(this.options.noClip)this._parts=this._rings;else for(var n,o=0,s=this._rings.length;ot.y!=n.y>t.y&&t.x<(n.x-e.x)*(t.y-e.y)/(n.y-e.y)+e.x&&(u=!u);return u||nn.prototype._containsPoint.call(this,t,!0)}}),sn=Ke.extend({initialize:function(t,i){l(this,i),this._layers={},t&&this.addData(t)},addData:function(t){var i,e,n,o=oi(t)?t:t.features;if(o){for(i=0,e=o.length;i0?o:[i.src]}else{oi(this._url)||(this._url=[this._url]),i.autoplay=!!this.options.autoplay,i.loop=!!this.options.loop;for(var a=0;ao?(i.height=o+"px",Q(t,"leaflet-popup-scrolled")):tt(t,"leaflet-popup-scrolled"),this._containerWidth=this._container.offsetWidth},_animateZoom:function(t){var i=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center),e=this._getAnchor();at(this._container,i.add(e))},_adjustPan:function(){if(!(!this.options.autoPan||this._map._panAnim&&this._map._panAnim._inProgress)){var t=this._map,i=parseInt(q(this._container,"marginBottom"),10)||0,e=this._container.offsetHeight+i,n=this._containerWidth,o=new x(this._containerLeft,-e-this._containerBottom);o._add(ht(this._container));var s=t.layerPointToContainerPoint(o),r=w(this.options.autoPanPadding),a=w(this.options.autoPanPaddingTopLeft||r),h=w(this.options.autoPanPaddingBottomRight||r),u=t.getSize(),l=0,c=0;s.x+n+h.x>u.x&&(l=s.x+n-u.x+h.x),s.x-l-a.x<0&&(l=s.x-a.x),s.y+e+h.y>u.y&&(c=s.y+e-u.y+h.y),s.y-c-a.y<0&&(c=s.y-a.y),(l||c)&&t.fire("autopanstart").panBy([l,c])}},_onCloseButtonClick:function(t){this._close(),Lt(t)},_getAnchor:function(){return w(this._source&&this._source._getPopupAnchor?this._source._getPopupAnchor():[0,0])}});be.mergeOptions({closePopupOnClick:!0}),be.include({openPopup:function(t,i,e){return t instanceof cn||(t=new cn(e).setContent(t)),i&&t.setLatLng(i),this.hasLayer(t)?this:(this._popup&&this._popup.options.autoClose&&this.closePopup(),this._popup=t,this.addLayer(t))},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&this.removeLayer(t),this}}),qe.include({bindPopup:function(t,i){return t instanceof cn?(l(t,i),this._popup=t,t._source=this):(this._popup&&!i||(this._popup=new cn(i,this)),this._popup.setContent(t)),this._popupHandlersAdded||(this.on({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!0),this},unbindPopup:function(){return this._popup&&(this.off({click:this._openPopup,keypress:this._onKeyPress,remove:this.closePopup,move:this._movePopup}),this._popupHandlersAdded=!1,this._popup=null),this},openPopup:function(t,i){if(t instanceof qe||(i=t,t=this),t instanceof Ke)for(var e in this._layers){t=this._layers[e];break}return i||(i=t.getCenter?t.getCenter():t.getLatLng()),this._popup&&this._map&&(this._popup._source=t,this._popup.update(),this._map.openPopup(this._popup,i)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(t){return this._popup&&(this._popup._map?this.closePopup():this.openPopup(t)),this},isPopupOpen:function(){return!!this._popup&&this._popup.isOpen()},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},getPopup:function(){return this._popup},_openPopup:function(t){var i=t.layer||t.target;this._popup&&this._map&&(Lt(t),i instanceof Qe?this.openPopup(t.layer||t.target,t.latlng):this._map.hasLayer(this._popup)&&this._popup._source===i?this.closePopup():this.openPopup(i,t.latlng))},_movePopup:function(t){this._popup.setLatLng(t.latlng)},_onKeyPress:function(t){13===t.originalEvent.keyCode&&this._openPopup(t)}});var _n=ln.extend({options:{pane:"tooltipPane",offset:[0,0],direction:"auto",permanent:!1,sticky:!1,interactive:!1,opacity:.9},onAdd:function(t){ln.prototype.onAdd.call(this,t),this.setOpacity(this.options.opacity),t.fire("tooltipopen",{tooltip:this}),this._source&&this._source.fire("tooltipopen",{tooltip:this},!0)},onRemove:function(t){ln.prototype.onRemove.call(this,t),t.fire("tooltipclose",{tooltip:this}),this._source&&this._source.fire("tooltipclose",{tooltip:this},!0)},getEvents:function(){var t=ln.prototype.getEvents.call(this);return qi&&!this.options.permanent&&(t.preclick=this._close),t},_close:function(){this._map&&this._map.closeTooltip(this)},_initLayout:function(){var t="leaflet-tooltip "+(this.options.className||"")+" leaflet-zoom-"+(this._zoomAnimated?"animated":"hide");this._contentNode=this._container=G("div",t)},_updateLayout:function(){},_adjustPan:function(){},_setPosition:function(t){var i=this._map,e=this._container,n=i.latLngToContainerPoint(i.getCenter()),o=i.layerPointToContainerPoint(t),s=this.options.direction,r=e.offsetWidth,a=e.offsetHeight,h=w(this.options.offset),u=this._getAnchor();"top"===s?t=t.add(w(-r/2+h.x,-a+h.y+u.y,!0)):"bottom"===s?t=t.subtract(w(r/2-h.x,-h.y,!0)):"center"===s?t=t.subtract(w(r/2+h.x,a/2-u.y+h.y,!0)):"right"===s||"auto"===s&&o.xthis.options.maxZoom||en&&this._retainParent(o,s,r,n))},_retainChildren:function(t,i,e,n){for(var o=2*t;o<2*t+2;o++)for(var s=2*i;s<2*i+2;s++){var r=new x(o,s);r.z=e+1;var a=this._tileCoordsToKey(r),h=this._tiles[a];h&&h.active?h.retain=!0:(h&&h.loaded&&(h.retain=!0),e+1this.options.maxZoom||void 0!==this.options.minZoom&&o1)this._setView(t,e);else{for(var c=o.min.y;c<=o.max.y;c++)for(var _=o.min.x;_<=o.max.x;_++){var d=new x(_,c);if(d.z=this._tileZoom,this._isValidTile(d)){var p=this._tiles[this._tileCoordsToKey(d)];p?p.current=!0:r.push(d)}}if(r.sort(function(t,i){return t.distanceTo(s)-i.distanceTo(s)}),0!==r.length){this._loading||(this._loading=!0,this.fire("loading"));var m=document.createDocumentFragment();for(_=0;_e.max.x)||!i.wrapLat&&(t.ye.max.y))return!1}if(!this.options.bounds)return!0;var n=this._tileCoordsToBounds(t);return z(this.options.bounds).overlaps(n)},_keyToBounds:function(t){return this._tileCoordsToBounds(this._keyToTileCoords(t))},_tileCoordsToNwSe:function(t){var i=this._map,e=this.getTileSize(),n=t.scaleBy(e),o=n.add(e);return[i.unproject(n,t.z),i.unproject(o,t.z)]},_tileCoordsToBounds:function(t){var i=this._tileCoordsToNwSe(t),e=new T(i[0],i[1]);return this.options.noWrap||(e=this._map.wrapLatLngBounds(e)),e},_tileCoordsToKey:function(t){return t.x+":"+t.y+":"+t.z},_keyToTileCoords:function(t){var i=t.split(":"),e=new x(+i[0],+i[1]);return e.z=+i[2],e},_removeTile:function(t){var i=this._tiles[t];i&&(K(i.el),delete this._tiles[t],this.fire("tileunload",{tile:i.el,coords:this._keyToTileCoords(t)}))},_initTile:function(t){Q(t,"leaflet-tile");var i=this.getTileSize();t.style.width=i.x+"px",t.style.height=i.y+"px",t.onselectstart=r,t.onmousemove=r,Li&&this.options.opacity<1&&nt(t,this.options.opacity),zi&&!Mi&&(t.style.WebkitBackfaceVisibility="hidden")},_addTile:function(t,i){var n=this._getTilePos(t),o=this._tileCoordsToKey(t),s=this.createTile(this._wrapCoords(t),e(this._tileReady,this,t));this._initTile(s),this.createTile.length<2&&f(e(this._tileReady,this,t,null,s)),at(s,n),this._tiles[o]={el:s,coords:t,current:!0},i.appendChild(s),this.fire("tileloadstart",{tile:s,coords:t})},_tileReady:function(t,i,n){i&&this.fire("tileerror",{error:i,tile:n,coords:t});var o=this._tileCoordsToKey(t);(n=this._tiles[o])&&(n.loaded=+new Date,this._map._fadeAnimated?(nt(n.el,0),g(this._fadeFrame),this._fadeFrame=f(this._updateOpacity,this)):(n.active=!0,this._pruneTiles()),i||(Q(n.el,"leaflet-tile-loaded"),this.fire("tileload",{tile:n.el,coords:t})),this._noTilesToLoad()&&(this._loading=!1,this.fire("load"),Li||!this._map._fadeAnimated?f(this._pruneTiles,this):setTimeout(e(this._pruneTiles,this),250)))},_getTilePos:function(t){return t.scaleBy(this.getTileSize()).subtract(this._level.origin)},_wrapCoords:function(t){var i=new x(this._wrapX?s(t.x,this._wrapX):t.x,this._wrapY?s(t.y,this._wrapY):t.y);return i.z=t.z,i},_pxBoundsToTileRange:function(t){var i=this.getTileSize();return new P(t.min.unscaleBy(i).floor(),t.max.unscaleBy(i).ceil().subtract([1,1]))},_noTilesToLoad:function(){for(var t in this._tiles)if(!this._tiles[t].loaded)return!1;return!0}}),mn=pn.extend({options:{minZoom:0,maxZoom:18,subdomains:"abc",errorTileUrl:"",zoomOffset:0,tms:!1,zoomReverse:!1,detectRetina:!1,crossOrigin:!1},initialize:function(t,i){this._url=t,(i=l(this,i)).detectRetina&&Yi&&i.maxZoom>0&&(i.tileSize=Math.floor(i.tileSize/2),i.zoomReverse?(i.zoomOffset--,i.minZoom++):(i.zoomOffset++,i.maxZoom--),i.minZoom=Math.max(0,i.minZoom)),"string"==typeof i.subdomains&&(i.subdomains=i.subdomains.split("")),zi||this.on("tileunload",this._onTileRemove)},setUrl:function(t,i){return this._url=t,i||this.redraw(),this},createTile:function(t,i){var n=document.createElement("img");return mt(n,"load",e(this._tileOnLoad,this,i,n)),mt(n,"error",e(this._tileOnError,this,i,n)),(this.options.crossOrigin||""===this.options.crossOrigin)&&(n.crossOrigin=!0===this.options.crossOrigin?"":this.options.crossOrigin),n.alt="",n.setAttribute("role","presentation"),n.src=this.getTileUrl(t),n},getTileUrl:function(t){var e={r:Yi?"@2x":"",s:this._getSubdomain(t),x:t.x,y:t.y,z:this._getZoomForUrl()};if(this._map&&!this._map.options.crs.infinite){var n=this._globalTileRange.max.y-t.y;this.options.tms&&(e.y=n),e["-y"]=n}return _(this._url,i(e,this.options))},_tileOnLoad:function(t,i){Li?setTimeout(e(t,this,null,i),0):t(null,i)},_tileOnError:function(t,i,e){var n=this.options.errorTileUrl;n&&i.getAttribute("src")!==n&&(i.src=n),t(e,i)},_onTileRemove:function(t){t.tile.onload=null},_getZoomForUrl:function(){var t=this._tileZoom,i=this.options.maxZoom,e=this.options.zoomReverse,n=this.options.zoomOffset;return e&&(t=i-t),t+n},_getSubdomain:function(t){var i=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[i]},_abortLoading:function(){var t,i;for(t in this._tiles)this._tiles[t].coords.z!==this._tileZoom&&((i=this._tiles[t].el).onload=r,i.onerror=r,i.complete||(i.src=si,K(i),delete this._tiles[t]))},_removeTile:function(t){var i=this._tiles[t];if(i)return Si||i.el.setAttribute("src",si),pn.prototype._removeTile.call(this,t)},_tileReady:function(t,i,e){if(this._map&&(!e||e.getAttribute("src")!==si))return pn.prototype._tileReady.call(this,t,i,e)}}),fn=mn.extend({defaultWmsParams:{service:"WMS",request:"GetMap",layers:"",styles:"",format:"image/jpeg",transparent:!1,version:"1.1.1"},options:{crs:null,uppercase:!1},initialize:function(t,e){this._url=t;var n=i({},this.defaultWmsParams);for(var o in e)o in this.options||(n[o]=e[o]);var s=(e=l(this,e)).detectRetina&&Yi?2:1,r=this.getTileSize();n.width=r.x*s,n.height=r.y*s,this.wmsParams=n},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var i=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[i]=this._crs.code,mn.prototype.onAdd.call(this,t)},getTileUrl:function(t){var i=this._tileCoordsToNwSe(t),e=this._crs,n=b(e.project(i[0]),e.project(i[1])),o=n.min,s=n.max,r=(this._wmsVersion>=1.3&&this._crs===Ue?[o.y,o.x,s.y,s.x]:[o.x,o.y,s.x,s.y]).join(","),a=mn.prototype.getTileUrl.call(this,t);return a+c(this.wmsParams,a,this.options.uppercase)+(this.options.uppercase?"&BBOX=":"&bbox=")+r},setParams:function(t,e){return i(this.wmsParams,t),e||this.redraw(),this}});mn.WMS=fn,Jt.wms=function(t,i){return new fn(t,i)};var gn=qe.extend({options:{padding:.1,tolerance:0},initialize:function(t){l(this,t),n(this),this._layers=this._layers||{}},onAdd:function(){this._container||(this._initContainer(),this._zoomAnimated&&Q(this._container,"leaflet-zoom-animated")),this.getPane().appendChild(this._container),this._update(),this.on("update",this._updatePaths,this)},onRemove:function(){this.off("update",this._updatePaths,this),this._destroyContainer()},getEvents:function(){var t={viewreset:this._reset,zoom:this._onZoom,moveend:this._update,zoomend:this._onZoomEnd};return this._zoomAnimated&&(t.zoomanim=this._onAnimZoom),t},_onAnimZoom:function(t){this._updateTransform(t.center,t.zoom)},_onZoom:function(){this._updateTransform(this._map.getCenter(),this._map.getZoom())},_updateTransform:function(t,i){var e=this._map.getZoomScale(i,this._zoom),n=ht(this._container),o=this._map.getSize().multiplyBy(.5+this.options.padding),s=this._map.project(this._center,i),r=this._map.project(t,i).subtract(s),a=o.multiplyBy(-e).add(n).add(o).subtract(r);ji?rt(this._container,a,e):at(this._container,a)},_reset:function(){this._update(),this._updateTransform(this._center,this._zoom);for(var t in this._layers)this._layers[t]._reset()},_onZoomEnd:function(){for(var t in this._layers)this._layers[t]._project()},_updatePaths:function(){for(var t in this._layers)this._layers[t]._update()},_update:function(){var t=this.options.padding,i=this._map.getSize(),e=this._map.containerPointToLayerPoint(i.multiplyBy(-t)).round();this._bounds=new P(e,e.add(i.multiplyBy(1+2*t)).round()),this._center=this._map.getCenter(),this._zoom=this._map.getZoom()}}),vn=gn.extend({getEvents:function(){var t=gn.prototype.getEvents.call(this);return t.viewprereset=this._onViewPreReset,t},_onViewPreReset:function(){this._postponeUpdatePaths=!0},onAdd:function(){gn.prototype.onAdd.call(this),this._draw()},_initContainer:function(){var t=this._container=document.createElement("canvas");mt(t,"mousemove",o(this._onMouseMove,32,this),this),mt(t,"click dblclick mousedown mouseup contextmenu",this._onClick,this),mt(t,"mouseout",this._handleMouseOut,this),this._ctx=t.getContext("2d")},_destroyContainer:function(){g(this._redrawRequest),delete this._ctx,K(this._container),ft(this._container),delete this._container},_updatePaths:function(){if(!this._postponeUpdatePaths){this._redrawBounds=null;for(var t in this._layers)this._layers[t]._update();this._redraw()}},_update:function(){if(!this._map._animatingZoom||!this._bounds){this._drawnLayers={},gn.prototype._update.call(this);var t=this._bounds,i=this._container,e=t.getSize(),n=Yi?2:1;at(i,t.min),i.width=n*e.x,i.height=n*e.y,i.style.width=e.x+"px",i.style.height=e.y+"px",Yi&&this._ctx.scale(2,2),this._ctx.translate(-t.min.x,-t.min.y),this.fire("update")}},_reset:function(){gn.prototype._reset.call(this),this._postponeUpdatePaths&&(this._postponeUpdatePaths=!1,this._updatePaths())},_initPath:function(t){this._updateDashArray(t),this._layers[n(t)]=t;var i=t._order={layer:t,prev:this._drawLast,next:null};this._drawLast&&(this._drawLast.next=i),this._drawLast=i,this._drawFirst=this._drawFirst||this._drawLast},_addPath:function(t){this._requestRedraw(t)},_removePath:function(t){var i=t._order,e=i.next,o=i.prev;e?e.prev=o:this._drawLast=o,o?o.next=e:this._drawFirst=e,delete this._drawnLayers[t._leaflet_id],delete t._order,delete this._layers[n(t)],this._requestRedraw(t)},_updatePath:function(t){this._extendRedrawBounds(t),t._project(),t._update(),this._requestRedraw(t)},_updateStyle:function(t){this._updateDashArray(t),this._requestRedraw(t)},_updateDashArray:function(t){if("string"==typeof t.options.dashArray){var i,e=t.options.dashArray.split(/[, ]+/),n=[];for(i=0;i')}}catch(t){return function(t){return document.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),xn={_initContainer:function(){this._container=G("div","leaflet-vml-container")},_update:function(){this._map._animatingZoom||(gn.prototype._update.call(this),this.fire("update"))},_initPath:function(t){var i=t._container=yn("shape");Q(i,"leaflet-vml-shape "+(this.options.className||"")),i.coordsize="1 1",t._path=yn("path"),i.appendChild(t._path),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){var i=t._container;this._container.appendChild(i),t.options.interactive&&t.addInteractiveTarget(i)},_removePath:function(t){var i=t._container;K(i),t.removeInteractiveTarget(i),delete this._layers[n(t)]},_updateStyle:function(t){var i=t._stroke,e=t._fill,n=t.options,o=t._container;o.stroked=!!n.stroke,o.filled=!!n.fill,n.stroke?(i||(i=t._stroke=yn("stroke")),o.appendChild(i),i.weight=n.weight+"px",i.color=n.color,i.opacity=n.opacity,n.dashArray?i.dashStyle=oi(n.dashArray)?n.dashArray.join(" "):n.dashArray.replace(/( *, *)/g," "):i.dashStyle="",i.endcap=n.lineCap.replace("butt","flat"),i.joinstyle=n.lineJoin):i&&(o.removeChild(i),t._stroke=null),n.fill?(e||(e=t._fill=yn("fill")),o.appendChild(e),e.color=n.fillColor||n.color,e.opacity=n.fillOpacity):e&&(o.removeChild(e),t._fill=null)},_updateCircle:function(t){var i=t._point.round(),e=Math.round(t._radius),n=Math.round(t._radiusY||e);this._setPath(t,t._empty()?"M0 0":"AL "+i.x+","+i.y+" "+e+","+n+" 0,23592600")},_setPath:function(t,i){t._path.v=i},_bringToFront:function(t){X(t._container)},_bringToBack:function(t){J(t._container)}},wn=$i?yn:E,Pn=gn.extend({getEvents:function(){var t=gn.prototype.getEvents.call(this);return t.zoomstart=this._onZoomStart,t},_initContainer:function(){this._container=wn("svg"),this._container.setAttribute("pointer-events","none"),this._rootGroup=wn("g"),this._container.appendChild(this._rootGroup)},_destroyContainer:function(){K(this._container),ft(this._container),delete this._container,delete this._rootGroup,delete this._svgSize},_onZoomStart:function(){this._update()},_update:function(){if(!this._map._animatingZoom||!this._bounds){gn.prototype._update.call(this);var t=this._bounds,i=t.getSize(),e=this._container;this._svgSize&&this._svgSize.equals(i)||(this._svgSize=i,e.setAttribute("width",i.x),e.setAttribute("height",i.y)),at(e,t.min),e.setAttribute("viewBox",[t.min.x,t.min.y,i.x,i.y].join(" ")),this.fire("update")}},_initPath:function(t){var i=t._path=wn("path");t.options.className&&Q(i,t.options.className),t.options.interactive&&Q(i,"leaflet-interactive"),this._updateStyle(t),this._layers[n(t)]=t},_addPath:function(t){this._rootGroup||this._initContainer(),this._rootGroup.appendChild(t._path),t.addInteractiveTarget(t._path)},_removePath:function(t){K(t._path),t.removeInteractiveTarget(t._path),delete this._layers[n(t)]},_updatePath:function(t){t._project(),t._update()},_updateStyle:function(t){var i=t._path,e=t.options;i&&(e.stroke?(i.setAttribute("stroke",e.color),i.setAttribute("stroke-opacity",e.opacity),i.setAttribute("stroke-width",e.weight),i.setAttribute("stroke-linecap",e.lineCap),i.setAttribute("stroke-linejoin",e.lineJoin),e.dashArray?i.setAttribute("stroke-dasharray",e.dashArray):i.removeAttribute("stroke-dasharray"),e.dashOffset?i.setAttribute("stroke-dashoffset",e.dashOffset):i.removeAttribute("stroke-dashoffset")):i.setAttribute("stroke","none"),e.fill?(i.setAttribute("fill",e.fillColor||e.color),i.setAttribute("fill-opacity",e.fillOpacity),i.setAttribute("fill-rule",e.fillRule||"evenodd")):i.setAttribute("fill","none"))},_updatePoly:function(t,i){this._setPath(t,k(t._parts,i))},_updateCircle:function(t){var i=t._point,e=Math.max(Math.round(t._radius),1),n="a"+e+","+(Math.max(Math.round(t._radiusY),1)||e)+" 0 1,0 ",o=t._empty()?"M0 0":"M"+(i.x-e)+","+i.y+n+2*e+",0 "+n+2*-e+",0 ";this._setPath(t,o)},_setPath:function(t,i){t._path.setAttribute("d",i)},_bringToFront:function(t){X(t._path)},_bringToBack:function(t){J(t._path)}});$i&&Pn.include(xn),be.include({getRenderer:function(t){var i=t.options.renderer||this._getPaneRenderer(t.options.pane)||this.options.renderer||this._renderer;return i||(i=this._renderer=this._createRenderer()),this.hasLayer(i)||this.addLayer(i),i},_getPaneRenderer:function(t){if("overlayPane"===t||void 0===t)return!1;var i=this._paneRenderers[t];return void 0===i&&(i=this._createRenderer({pane:t}),this._paneRenderers[t]=i),i},_createRenderer:function(t){return this.options.preferCanvas&&$t(t)||Qt(t)}});var Ln=on.extend({initialize:function(t,i){on.prototype.initialize.call(this,this._boundsToLatLngs(t),i)},setBounds:function(t){return this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=z(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}});Pn.create=wn,Pn.pointsToPath=k,sn.geometryToLayer=Ft,sn.coordsToLatLng=Ut,sn.coordsToLatLngs=Vt,sn.latLngToCoords=qt,sn.latLngsToCoords=Gt,sn.getFeature=Kt,sn.asFeature=Yt,be.mergeOptions({boxZoom:!0});var bn=Ee.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._resetStateTimeout=0,t.on("unload",this._destroy,this)},addHooks:function(){mt(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){ft(this._container,"mousedown",this._onMouseDown,this)},moved:function(){return this._moved},_destroy:function(){K(this._pane),delete this._pane},_resetState:function(){this._resetStateTimeout=0,this._moved=!1},_clearDeferredResetState:function(){0!==this._resetStateTimeout&&(clearTimeout(this._resetStateTimeout),this._resetStateTimeout=0)},_onMouseDown:function(t){if(!t.shiftKey||1!==t.which&&1!==t.button)return!1;this._clearDeferredResetState(),this._resetState(),fi(),ut(),this._startPoint=this._map.mouseEventToContainerPoint(t),mt(document,{contextmenu:Lt,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseMove:function(t){this._moved||(this._moved=!0,this._box=G("div","leaflet-zoom-box",this._container),Q(this._container,"leaflet-crosshair"),this._map.fire("boxzoomstart")),this._point=this._map.mouseEventToContainerPoint(t);var i=new P(this._point,this._startPoint),e=i.getSize();at(this._box,i.min),this._box.style.width=e.x+"px",this._box.style.height=e.y+"px"},_finish:function(){this._moved&&(K(this._box),tt(this._container,"leaflet-crosshair")),gi(),lt(),ft(document,{contextmenu:Lt,mousemove:this._onMouseMove,mouseup:this._onMouseUp,keydown:this._onKeyDown},this)},_onMouseUp:function(t){if((1===t.which||1===t.button)&&(this._finish(),this._moved)){this._clearDeferredResetState(),this._resetStateTimeout=setTimeout(e(this._resetState,this),0);var i=new T(this._map.containerPointToLatLng(this._startPoint),this._map.containerPointToLatLng(this._point));this._map.fitBounds(i).fire("boxzoomend",{boxZoomBounds:i})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}});be.addInitHook("addHandler","boxZoom",bn),be.mergeOptions({doubleClickZoom:!0});var Tn=Ee.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var i=this._map,e=i.getZoom(),n=i.options.zoomDelta,o=t.originalEvent.shiftKey?e-n:e+n;"center"===i.options.doubleClickZoom?i.setZoom(o):i.setZoomAround(t.containerPoint,o)}});be.addInitHook("addHandler","doubleClickZoom",Tn),be.mergeOptions({dragging:!0,inertia:!Mi,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,easeLinearity:.2,worldCopyJump:!1,maxBoundsViscosity:0});var zn=Ee.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new Re(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),this._draggable.on("predrag",this._onPreDragLimit,this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDragWrap,this),t.on("zoomend",this._onZoomEnd,this),t.whenReady(this._onZoomEnd,this))}Q(this._map._container,"leaflet-grab leaflet-touch-drag"),this._draggable.enable(),this._positions=[],this._times=[]},removeHooks:function(){tt(this._map._container,"leaflet-grab"),tt(this._map._container,"leaflet-touch-drag"),this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},moving:function(){return this._draggable&&this._draggable._moving},_onDragStart:function(){var t=this._map;if(t._stop(),this._map.options.maxBounds&&this._map.options.maxBoundsViscosity){var i=z(this._map.options.maxBounds);this._offsetLimit=b(this._map.latLngToContainerPoint(i.getNorthWest()).multiplyBy(-1),this._map.latLngToContainerPoint(i.getSouthEast()).multiplyBy(-1).add(this._map.getSize())),this._viscosity=Math.min(1,Math.max(0,this._map.options.maxBoundsViscosity))}else this._offsetLimit=null;t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(t){if(this._map.options.inertia){var i=this._lastTime=+new Date,e=this._lastPos=this._draggable._absPos||this._draggable._newPos;this._positions.push(e),this._times.push(i),this._prunePositions(i)}this._map.fire("move",t).fire("drag",t)},_prunePositions:function(t){for(;this._positions.length>1&&t-this._times[0]>50;)this._positions.shift(),this._times.shift()},_onZoomEnd:function(){var t=this._map.getSize().divideBy(2),i=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=i.subtract(t).x,this._worldWidth=this._map.getPixelWorldBounds().getSize().x},_viscousLimit:function(t,i){return t-(t-i)*this._viscosity},_onPreDragLimit:function(){if(this._viscosity&&this._offsetLimit){var t=this._draggable._newPos.subtract(this._draggable._startPos),i=this._offsetLimit;t.xi.max.x&&(t.x=this._viscousLimit(t.x,i.max.x)),t.y>i.max.y&&(t.y=this._viscousLimit(t.y,i.max.y)),this._draggable._newPos=this._draggable._startPos.add(t)}},_onPreDragWrap:function(){var t=this._worldWidth,i=Math.round(t/2),e=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-i+e)%t+i-e,s=(n+i+e)%t-i-e,r=Math.abs(o+e)0?s:-s))-i;this._delta=0,this._startTime=null,r&&("center"===t.options.scrollWheelZoom?t.setZoom(i+r):t.setZoomAround(this._lastMousePos,i+r))}});be.addInitHook("addHandler","scrollWheelZoom",Cn),be.mergeOptions({tap:!0,tapTolerance:15});var Sn=Ee.extend({addHooks:function(){mt(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){ft(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(Pt(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new x(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&Q(n,"leaflet-active"),this._holdTimeout=setTimeout(e(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),this._simulateEvent("mousedown",i),mt(document,{touchmove:this._onMove,touchend:this._onUp},this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),ft(document,{touchmove:this._onMove,touchend:this._onUp},this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],e=i.target;e&&e.tagName&&"a"===e.tagName.toLowerCase()&&tt(e,"leaflet-active"),this._simulateEvent("mouseup",i),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var i=t.touches[0];this._newPos=new x(i.clientX,i.clientY),this._simulateEvent("mousemove",i)},_simulateEvent:function(t,i){var e=document.createEvent("MouseEvents");e._simulated=!0,i.target._simulatedClick=!0,e.initMouseEvent(t,!0,!0,window,1,i.screenX,i.screenY,i.clientX,i.clientY,!1,!1,!1,!1,0,null),i.target.dispatchEvent(e)}});qi&&!Vi&&be.addInitHook("addHandler","tap",Sn),be.mergeOptions({touchZoom:qi&&!Mi,bounceAtZoomLimits:!0});var Zn=Ee.extend({addHooks:function(){Q(this._map._container,"leaflet-touch-zoom"),mt(this._map._container,"touchstart",this._onTouchStart,this)},removeHooks:function(){tt(this._map._container,"leaflet-touch-zoom"),ft(this._map._container,"touchstart",this._onTouchStart,this)},_onTouchStart:function(t){var i=this._map;if(t.touches&&2===t.touches.length&&!i._animatingZoom&&!this._zooming){var e=i.mouseEventToContainerPoint(t.touches[0]),n=i.mouseEventToContainerPoint(t.touches[1]);this._centerPoint=i.getSize()._divideBy(2),this._startLatLng=i.containerPointToLatLng(this._centerPoint),"center"!==i.options.touchZoom&&(this._pinchStartLatLng=i.containerPointToLatLng(e.add(n)._divideBy(2))),this._startDist=e.distanceTo(n),this._startZoom=i.getZoom(),this._moved=!1,this._zooming=!0,i._stop(),mt(document,"touchmove",this._onTouchMove,this),mt(document,"touchend",this._onTouchEnd,this),Pt(t)}},_onTouchMove:function(t){if(t.touches&&2===t.touches.length&&this._zooming){var i=this._map,n=i.mouseEventToContainerPoint(t.touches[0]),o=i.mouseEventToContainerPoint(t.touches[1]),s=n.distanceTo(o)/this._startDist;if(this._zoom=i.getScaleZoom(s,this._startZoom),!i.options.bounceAtZoomLimits&&(this._zoomi.getMaxZoom()&&s>1)&&(this._zoom=i._limitZoom(this._zoom)),"center"===i.options.touchZoom){if(this._center=this._startLatLng,1===s)return}else{var r=n._add(o)._divideBy(2)._subtract(this._centerPoint);if(1===s&&0===r.x&&0===r.y)return;this._center=i.unproject(i.project(this._pinchStartLatLng,this._zoom).subtract(r),this._zoom)}this._moved||(i._moveStart(!0,!1),this._moved=!0),g(this._animRequest);var a=e(i._move,i,this._center,this._zoom,{pinch:!0,round:!1});this._animRequest=f(a,this,!0),Pt(t)}},_onTouchEnd:function(){this._moved&&this._zooming?(this._zooming=!1,g(this._animRequest),ft(document,"touchmove",this._onTouchMove),ft(document,"touchend",this._onTouchEnd),this._map.options.zoomAnimation?this._map._animateZoom(this._center,this._map._limitZoom(this._zoom),!0,this._map.options.zoomSnap):this._map._resetView(this._center,this._map._limitZoom(this._zoom))):this._zooming=!1}});be.addInitHook("addHandler","touchZoom",Zn),be.BoxZoom=bn,be.DoubleClickZoom=Tn,be.Drag=zn,be.Keyboard=Mn,be.ScrollWheelZoom=Cn,be.Tap=Sn,be.TouchZoom=Zn,Object.freeze=ti,t.version="1.3.4+HEAD.0e566b2",t.Control=Te,t.control=ze,t.Browser=Qi,t.Evented=ci,t.Mixin=Ae,t.Util=ui,t.Class=v,t.Handler=Ee,t.extend=i,t.bind=e,t.stamp=n,t.setOptions=l,t.DomEvent=Pe,t.DomUtil=ve,t.PosAnimation=Le,t.Draggable=Re,t.LineUtil=Ne,t.PolyUtil=De,t.Point=x,t.point=w,t.Bounds=P,t.bounds=b,t.Transformation=S,t.transformation=Z,t.Projection=He,t.LatLng=M,t.latLng=C,t.LatLngBounds=T,t.latLngBounds=z,t.CRS=di,t.GeoJSON=sn,t.geoJSON=Xt,t.geoJson=an,t.Layer=qe,t.LayerGroup=Ge,t.layerGroup=function(t,i){return new Ge(t,i)},t.FeatureGroup=Ke,t.featureGroup=function(t){return new Ke(t)},t.ImageOverlay=hn,t.imageOverlay=function(t,i,e){return new hn(t,i,e)},t.VideoOverlay=un,t.videoOverlay=function(t,i,e){return new un(t,i,e)},t.DivOverlay=ln,t.Popup=cn,t.popup=function(t,i){return new cn(t,i)},t.Tooltip=_n,t.tooltip=function(t,i){return new _n(t,i)},t.Icon=Ye,t.icon=function(t){return new Ye(t)},t.DivIcon=dn,t.divIcon=function(t){return new dn(t)},t.Marker=$e,t.marker=function(t,i){return new $e(t,i)},t.TileLayer=mn,t.tileLayer=Jt,t.GridLayer=pn,t.gridLayer=function(t){return new pn(t)},t.SVG=Pn,t.svg=Qt,t.Renderer=gn,t.Canvas=vn,t.canvas=$t,t.Path=Qe,t.CircleMarker=tn,t.circleMarker=function(t,i){return new tn(t,i)},t.Circle=en,t.circle=function(t,i,e){return new en(t,i,e)},t.Polyline=nn,t.polyline=function(t,i){return new nn(t,i)},t.Polygon=on,t.polygon=function(t,i){return new on(t,i)},t.Rectangle=Ln,t.rectangle=function(t,i){return new Ln(t,i)},t.Map=be,t.map=function(t,i){return new be(t,i)};var En=window.L;t.noConflict=function(){return window.L=En,this},window.L=t}); \ No newline at end of file diff --git a/corefx/src/main/resources/log4j2.xml b/corefx/src/main/resources/log4j2.xml new file mode 100644 index 00000000..0ff056d0 --- /dev/null +++ b/corefx/src/main/resources/log4j2.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/daemon/proto/core.proto b/daemon/proto/core.proto index 45fd597e..7172ead3 100644 --- a/daemon/proto/core.proto +++ b/daemon/proto/core.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package core; +option java_package = "com.core.client.grpc"; +option java_outer_classname = "CoreProto"; + service CoreApi { // session rpc rpc CreateSession (CreateSessionRequest) returns (CreateSessionResponse) { From 295fdddaf9d35868a4a5b8bd80e27e3a5f069eac Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 25 May 2019 21:06:50 -0700 Subject: [PATCH 0021/1992] further updates to implement more grpc calls --- .../com/core/client/grpc/CoreGrpcClient.java | 43 ++++++++++++++++--- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index fc759102..8572711c 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -11,7 +11,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; +import java.io.FileInputStream; import java.io.IOException; +import java.io.PrintWriter; import java.util.*; public class CoreGrpcClient implements ICoreClient { @@ -189,10 +191,16 @@ public class CoreGrpcClient implements ICoreClient { @Override public Session getSession(Integer sessionId) throws IOException { + logger.info("getting session: {}", sessionId); CoreProto.GetSessionRequest request = CoreProto.GetSessionRequest.newBuilder().setId(sessionId).build(); CoreProto.GetSessionResponse response = blockingStub.getSession(request); Session session = new Session(); for (CoreProto.Node protoNode : response.getSession().getNodesList()) { + if (CoreProto.NodeType.NODE_PEER_TO_PEER == protoNode.getType()) { + continue; + } + + logger.info("adding node: {}", protoNode); CoreNode node = new CoreNode(protoNode.getId()); node.setName(protoNode.getName()); node.setEmane(protoNode.getEmane()); @@ -201,13 +209,14 @@ public class CoreGrpcClient implements ICoreClient { node.setServices(new HashSet<>(protoNode.getServicesList())); node.getPosition().setX((double) protoNode.getPosition().getX()); node.getPosition().setY((double) protoNode.getPosition().getY()); - node.getPosition().setZ((double) protoNode.getPosition().getZ()); - node.setNodeType(NodeType.get(protoNode.getTypeValue())); + node.setType(protoNode.getTypeValue()); + session.getNodes().add(node); } for (CoreProto.Link linkProto : response.getSession().getLinksList()) { + logger.info("adding link: {} - {}", linkProto.getNodeOne(), linkProto.getNodeTwo()); CoreLink link = new CoreLink(); link.setNodeOne(linkProto.getNodeOne()); - link.setNodeTwo(linkProto.getNodeOne()); + link.setNodeTwo(linkProto.getNodeTwo()); CoreProto.Interface interfaceOneProto = linkProto.getInterfaceOne(); CoreInterface interfaceOne = new CoreInterface(); interfaceOne.setId(interfaceOneProto.getId()); @@ -238,12 +247,15 @@ public class CoreGrpcClient implements ICoreClient { options.setJitter((double) protoOptions.getJitter()); options.setPer((double) protoOptions.getPer()); options.setBurst((double) protoOptions.getBurst()); - options.setKey(Integer.parseInt(protoOptions.getKey())); + if (!protoOptions.getKey().isEmpty()) { + options.setKey(Integer.parseInt(protoOptions.getKey())); + } options.setMburst((double) protoOptions.getMburst()); options.setMer((double) protoOptions.getMer()); options.setOpaque(protoOptions.getOpaque()); options.setUnidirectional(protoOptions.getUnidirectional() ? 1 : 0); link.setOptions(options); + session.getLinks().add(link); } session.setState(response.getSession().getStateValue()); return session; @@ -454,7 +466,11 @@ public class CoreGrpcClient implements ICoreClient { @Override public List getEmaneModels() throws IOException { - return null; + CoreProto.GetEmaneModelsRequest request = CoreProto.GetEmaneModelsRequest.newBuilder() + .setSession(sessionId) + .build(); + CoreProto.GetEmaneModelsResponse response = blockingStub.getEmaneModels(request); + return response.getModelsList(); } @Override @@ -479,12 +495,25 @@ public class CoreGrpcClient implements ICoreClient { @Override public void saveSession(File file) throws IOException { - + CoreProto.SaveXmlRequest request = CoreProto.SaveXmlRequest.newBuilder() + .setSession(sessionId) + .build(); + CoreProto.SaveXmlResponse response = blockingStub.saveXml(request); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print(response.getData().toStringUtf8()); + } } @Override public SessionOverview openSession(File file) throws IOException { - return null; + ByteString data = ByteString.readFrom(new FileInputStream(file)); + CoreProto.OpenXmlRequest request = CoreProto.OpenXmlRequest.newBuilder() + .setData(data) + .build(); + CoreProto.OpenXmlResponse response = blockingStub.openXml(request); + SessionOverview sessionOverview = new SessionOverview(); + sessionOverview.setId(response.getSession()); + return sessionOverview; } @Override From 636a5053657669f7e65a0fd605e0edac02b955f2 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 26 May 2019 10:20:56 -0700 Subject: [PATCH 0022/1992] converted more client calls to grpc --- corefx/src/main/java/com/core/Controller.java | 1 + .../com/core/client/grpc/CoreGrpcClient.java | 98 +++++++++++++++++-- 2 files changed, 91 insertions(+), 8 deletions(-) diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java index bfff6bf2..0dc3ffdf 100644 --- a/corefx/src/main/java/com/core/Controller.java +++ b/corefx/src/main/java/com/core/Controller.java @@ -132,6 +132,7 @@ public class Controller implements Initializable { // set emane models List emaneModels = coreClient.getEmaneModels(); + logger.info("emane models: {}", emaneModels); nodeEmaneDialog.setModels(emaneModels); } diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 8572711c..af57b9f5 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -53,6 +53,25 @@ public class CoreGrpcClient implements ICoreClient { return builder.build(); } + private List protoToConfigGroups(List protoConfigs) { + List configs = new ArrayList<>(); + for (CoreProto.ConfigGroup protoConfig : protoConfigs) { + ConfigGroup config = new ConfigGroup(); + config.setName(protoConfig.getName()); + for (CoreProto.ConfigOption protoOption : protoConfig.getOptionsList()) { + ConfigOption option = new ConfigOption(); + option.setType(protoOption.getType()); + option.setLabel(protoOption.getLabel()); + option.setName(protoOption.getName()); + option.setValue(protoOption.getValue()); + option.setSelect(protoOption.getSelectList()); + config.getOptions().add(option); + } + configs.add(config); + } + return configs; + } + private CoreProto.LinkOptions linkOptionsToProto(CoreLinkOptions options) { CoreProto.LinkOptions.Builder builder = CoreProto.LinkOptions.newBuilder(); boolean unidirectional = false; @@ -116,6 +135,14 @@ public class CoreGrpcClient implements ICoreClient { return builder.build(); } + private Map configOptionListToMap(List options) { + Map config = new HashMap<>(); + for (ConfigOption option : options) { + config.put(option.getName(), option.getValue()); + } + return config; + } + @Override public void setConnection(String address, int port) { this.address = address; @@ -139,11 +166,13 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean startThroughput() throws IOException { + // TODO: convert return false; } @Override public boolean stopThroughput() throws IOException { + // TODO: convert return false; } @@ -461,7 +490,11 @@ public class CoreGrpcClient implements ICoreClient { @Override public List getEmaneConfig(CoreNode node) throws IOException { - return null; + CoreProto.GetEmaneConfigRequest request = CoreProto.GetEmaneConfigRequest.newBuilder() + .setSession(sessionId) + .build(); + CoreProto.GetEmaneConfigResponse response = blockingStub.getEmaneConfig(request); + return protoToConfigGroups(response.getGroupsList()); } @Override @@ -470,22 +503,42 @@ public class CoreGrpcClient implements ICoreClient { .setSession(sessionId) .build(); CoreProto.GetEmaneModelsResponse response = blockingStub.getEmaneModels(request); - return response.getModelsList(); + return new ArrayList<>(response.getModelsList()); } @Override public boolean setEmaneConfig(CoreNode node, List options) throws IOException { - return false; + Map config = configOptionListToMap(options); + CoreProto.SetEmaneConfigRequest request = CoreProto.SetEmaneConfigRequest.newBuilder() + .setSession(sessionId) + .putAllConfig(config) + .build(); + CoreProto.SetEmaneConfigResponse response = blockingStub.setEmaneConfig(request); + return response.getResult(); } @Override public List getEmaneModelConfig(Integer id, String model) throws IOException { - return null; + CoreProto.GetEmaneModelConfigRequest request = CoreProto.GetEmaneModelConfigRequest.newBuilder() + .setSession(sessionId) + .setId(id) + .setModel(model) + .build(); + CoreProto.GetEmaneModelConfigResponse response = blockingStub.getEmaneModelConfig(request); + return protoToConfigGroups(response.getGroupsList()); } @Override public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException { - return false; + Map config = configOptionListToMap(options); + CoreProto.SetEmaneModelConfigRequest request = CoreProto.SetEmaneModelConfigRequest.newBuilder() + .setSession(sessionId) + .setId(id) + .setModel(model) + .putAllConfig(config) + .build(); + CoreProto.SetEmaneModelConfigResponse response = blockingStub.setEmaneModelConfig(request); + return response.getResult(); } @Override @@ -518,12 +571,22 @@ public class CoreGrpcClient implements ICoreClient { @Override public List getSessionConfig() throws IOException { - return null; + CoreProto.GetSessionOptionsRequest request = CoreProto.GetSessionOptionsRequest.newBuilder() + .setId(sessionId) + .build(); + CoreProto.GetSessionOptionsResponse response = blockingStub.getSessionOptions(request); + return protoToConfigGroups(response.getGroupsList()); } @Override public boolean setSessionConfig(List configOptions) throws IOException { - return false; + Map config = configOptionListToMap(configOptions); + CoreProto.SetSessionOptionsRequest request = CoreProto.SetSessionOptionsRequest.newBuilder() + .setId(sessionId) + .putAllConfig(config) + .build(); + CoreProto.SetSessionOptionsResponse response = blockingStub.setSessionOptions(request); + return response.getResult(); } @Override @@ -539,6 +602,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public String nodeCommand(CoreNode node, String command) throws IOException { + // TODO: convert return null; } @@ -649,16 +713,19 @@ public class CoreGrpcClient implements ICoreClient { @Override public WlanConfig getWlanConfig(CoreNode node) throws IOException { + // TODO: convert return null; } @Override public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { + // TODO: convert return false; } @Override public String getTerminalCommand(CoreNode node) throws IOException { + // TODO: convert return null; } @@ -681,26 +748,41 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException { + // TODO: convert return false; } @Override public MobilityConfig getMobilityConfig(CoreNode node) throws IOException { + CoreProto.GetMobilityConfigRequest request = CoreProto.GetMobilityConfigRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .build(); + CoreProto.GetMobilityConfigResponse response = blockingStub.getMobilityConfig(request); + // TODO: convert return null; } @Override public boolean mobilityAction(CoreNode node, String action) throws IOException { - return false; + CoreProto.MobilityActionRequest request = CoreProto.MobilityActionRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .setAction(CoreProto.MobilityAction.valueOf(action)) + .build(); + CoreProto.MobilityActionResponse response = blockingStub.mobilityAction(request); + return response.getResult(); } @Override public LocationConfig getLocationConfig() throws IOException { + // TODO: convert return null; } @Override public boolean setLocationConfig(LocationConfig config) throws IOException { + // TODO: convert return false; } } From e9d7ed5049e54931082af21f15fb7b0aa49aa94c Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 26 May 2019 10:58:04 -0700 Subject: [PATCH 0023/1992] converted session location calls --- .../com/core/client/grpc/CoreGrpcClient.java | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index af57b9f5..04e10ba5 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -776,13 +776,48 @@ public class CoreGrpcClient implements ICoreClient { @Override public LocationConfig getLocationConfig() throws IOException { - // TODO: convert - return null; + CoreProto.GetSessionLocationRequest request = CoreProto.GetSessionLocationRequest.newBuilder() + .setId(sessionId) + .build(); + CoreProto.GetSessionLocationResponse response = blockingStub.getSessionLocation(request); + LocationConfig config = new LocationConfig(); + config.setScale((double) response.getScale()); + config.getPosition().setX((double) response.getPosition().getX()); + config.getPosition().setY((double) response.getPosition().getY()); + config.getPosition().setZ((double) response.getPosition().getZ()); + config.getLocation().setLatitude((double) response.getPosition().getLat()); + config.getLocation().setLongitude((double) response.getPosition().getLon()); + config.getLocation().setAltitude((double) response.getPosition().getAlt()); + return config; } @Override public boolean setLocationConfig(LocationConfig config) throws IOException { - // TODO: convert - return false; + CoreProto.SetSessionLocationRequest.Builder builder = CoreProto.SetSessionLocationRequest.newBuilder() + .setId(sessionId); + if (config.getScale() != null) { + builder.setScale(config.getScale().floatValue()); + } + CoreProto.Position.Builder positionBuilder = CoreProto.Position.newBuilder(); + if (config.getPosition().getX() != null) { + positionBuilder.setX(config.getPosition().getX().floatValue()); + } + if (config.getPosition().getY() != null) { + positionBuilder.setY(config.getPosition().getY().floatValue()); + } + if (config.getPosition().getZ() != null) { + positionBuilder.setZ(config.getPosition().getZ().floatValue()); + } + if (config.getLocation().getLongitude() != null) { + positionBuilder.setLon(config.getLocation().getLongitude().floatValue()); + } + if (config.getLocation().getLatitude() != null) { + positionBuilder.setLat(config.getLocation().getLatitude().floatValue()); + } + if (config.getLocation().getAltitude() != null) { + positionBuilder.setAlt(config.getLocation().getAltitude().floatValue()); + } + CoreProto.SetSessionLocationResponse response = blockingStub.setSessionLocation(builder.build()); + return response.getResult(); } } From 2b419ac67ce41155f20448a534f847135df74be0 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 26 May 2019 12:36:48 -0700 Subject: [PATCH 0024/1992] finished all current grpc commands, potentially should add remaining --- .../com/core/client/grpc/CoreGrpcClient.java | 94 ++++++++++++++++--- 1 file changed, 82 insertions(+), 12 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 04e10ba5..c58e61f1 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -166,13 +166,13 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean startThroughput() throws IOException { - // TODO: convert + // TODO: convert throughput return false; } @Override public boolean stopThroughput() throws IOException { - // TODO: convert + // TODO: convert throughput return false; } @@ -602,7 +602,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public String nodeCommand(CoreNode node, String command) throws IOException { - // TODO: convert + // TODO: convert node command return null; } @@ -713,14 +713,41 @@ public class CoreGrpcClient implements ICoreClient { @Override public WlanConfig getWlanConfig(CoreNode node) throws IOException { - // TODO: convert - return null; + CoreProto.GetWlanConfigRequest request = CoreProto.GetWlanConfigRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .build(); + CoreProto.GetWlanConfigResponse response = blockingStub.getWlanConfig(request); + Map protoConfig = new HashMap<>(); + for (CoreProto.ConfigGroup group : response.getGroupsList()) { + for (CoreProto.ConfigOption option : group.getOptionsList()) { + protoConfig.put(option.getName(), option.getValue()); + } + } + WlanConfig config = new WlanConfig(); + config.setBandwidth(protoConfig.get("bandwidth")); + config.setDelay(protoConfig.get("delay")); + config.setError(protoConfig.get("error")); + config.setJitter(protoConfig.get("jitter")); + config.setRange(protoConfig.get("range")); + return config; } @Override public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { - // TODO: convert - return false; + Map protoConfig = new HashMap<>(); + protoConfig.put("bandwidth", config.getBandwidth()); + protoConfig.put("delay", config.getDelay()); + protoConfig.put("error", config.getError()); + protoConfig.put("jitter", config.getJitter()); + protoConfig.put("range", config.getRange()); + CoreProto.SetWlanConfigRequest request = CoreProto.SetWlanConfigRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .putAllConfig(protoConfig) + .build(); + CoreProto.SetWlanConfigResponse response = blockingStub.setWlanConfig(request); + return response.getResult(); } @Override @@ -740,7 +767,20 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.GetMobilityConfigsResponse.MobilityConfig protoMobilityConfig = response.getConfigsMap() .get(nodeId); MobilityConfig mobilityConfig = new MobilityConfig(); - CoreProto.ConfigGroup configGroup = protoMobilityConfig.getGroups(0); + Map protoConfig = new HashMap<>(); + for (CoreProto.ConfigGroup group : protoMobilityConfig.getGroupsList()) { + for (CoreProto.ConfigOption option : group.getOptionsList()) { + protoConfig.put(option.getName(), option.getValue()); + } + } + mobilityConfig.setFile(protoConfig.get("file")); + mobilityConfig.setRefresh(Integer.parseInt(protoConfig.get("refresh_ms"))); + mobilityConfig.setAutostart(protoConfig.get("autostart")); + mobilityConfig.setLoop(protoConfig.get("loop")); + mobilityConfig.setPauseScript(protoConfig.get("script_pause")); + mobilityConfig.setStartScript(protoConfig.get("script_start")); + mobilityConfig.setStopScript(protoConfig.get("script_stop")); + mobilityConfig.setMap(protoConfig.get("map")); mobilityConfigs.put(nodeId, mobilityConfig); } return mobilityConfigs; @@ -748,8 +788,24 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException { - // TODO: convert - return false; + Map protoConfig = new HashMap<>(); + protoConfig.put("file", config.getFile()); + if (config.getRefresh() != null) { + protoConfig.put("refresh_ms", config.getRefresh().toString()); + } + protoConfig.put("autostart", config.getAutostart()); + protoConfig.put("loop", config.getLoop()); + protoConfig.put("map", config.getMap()); + protoConfig.put("script_pause", config.getPauseScript()); + protoConfig.put("script_start", config.getStartScript()); + protoConfig.put("script_stop", config.getStopScript()); + CoreProto.SetMobilityConfigRequest request = CoreProto.SetMobilityConfigRequest.newBuilder() + .setSession(sessionId) + .setId(node.getId()) + .putAllConfig(protoConfig) + .build(); + CoreProto.SetMobilityConfigResponse response = blockingStub.setMobilityConfig(request); + return response.getResult(); } @Override @@ -759,8 +815,22 @@ public class CoreGrpcClient implements ICoreClient { .setId(node.getId()) .build(); CoreProto.GetMobilityConfigResponse response = blockingStub.getMobilityConfig(request); - // TODO: convert - return null; + Map protoConfig = new HashMap<>(); + for (CoreProto.ConfigGroup group : response.getGroupsList()) { + for (CoreProto.ConfigOption option : group.getOptionsList()) { + protoConfig.put(option.getName(), option.getValue()); + } + } + MobilityConfig config = new MobilityConfig(); + config.setFile(protoConfig.get("file")); + config.setRefresh(Integer.parseInt(protoConfig.get("refresh_ms"))); + config.setAutostart(protoConfig.get("autostart")); + config.setLoop(protoConfig.get("loop")); + config.setPauseScript(protoConfig.get("script_pause")); + config.setStartScript(protoConfig.get("script_start")); + config.setStopScript(protoConfig.get("script_stop")); + config.setMap(protoConfig.get("map")); + return config; } @Override From 5acc93207ac6bf4517cce152dd37e5592f051912 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 28 May 2019 09:26:40 -0700 Subject: [PATCH 0025/1992] updated ignore to not exclude desired corefx files --- .gitignore | 2 +- config/compile | 347 ++++++++++++++++++++ config/depcomp | 791 ++++++++++++++++++++++++++++++++++++++++++++++ config/install-sh | 508 +++++++++++++++++++++++++++++ config/missing | 215 +++++++++++++ 5 files changed, 1862 insertions(+), 1 deletion(-) create mode 100755 config/compile create mode 100755 config/depcomp create mode 100755 config/install-sh create mode 100755 config/missing diff --git a/.gitignore b/.gitignore index 56f130ee..af590262 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ Makefile Makefile.in aclocal.m4 autom4te.cache -config +./config config.h config.h.in config.log diff --git a/config/compile b/config/compile new file mode 100755 index 00000000..a85b723c --- /dev/null +++ b/config/compile @@ -0,0 +1,347 @@ +#! /bin/sh +# Wrapper for compilers which do not understand '-c -o'. + +scriptversion=2012-10-14.11; # UTC + +# Copyright (C) 1999-2014 Free Software Foundation, Inc. +# Written by Tom Tromey . +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# This file is maintained in Automake, please report +# bugs to or send patches to +# . + +nl=' +' + +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent tools from complaining about whitespace usage. +IFS=" "" $nl" + +file_conv= + +# func_file_conv build_file lazy +# Convert a $build file to $host form and store it in $file +# Currently only supports Windows hosts. If the determined conversion +# type is listed in (the comma separated) LAZY, no conversion will +# take place. +func_file_conv () +{ + file=$1 + case $file in + / | /[!/]*) # absolute file, and not a UNC file + if test -z "$file_conv"; then + # lazily determine how to convert abs files + case `uname -s` in + MINGW*) + file_conv=mingw + ;; + CYGWIN*) + file_conv=cygwin + ;; + *) + file_conv=wine + ;; + esac + fi + case $file_conv/,$2, in + *,$file_conv,*) + ;; + mingw/*) + file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` + ;; + cygwin/*) + file=`cygpath -m "$file" || echo "$file"` + ;; + wine/*) + file=`winepath -w "$file" || echo "$file"` + ;; + esac + ;; + esac +} + +# func_cl_dashL linkdir +# Make cl look for libraries in LINKDIR +func_cl_dashL () +{ + func_file_conv "$1" + if test -z "$lib_path"; then + lib_path=$file + else + lib_path="$lib_path;$file" + fi + linker_opts="$linker_opts -LIBPATH:$file" +} + +# func_cl_dashl library +# Do a library search-path lookup for cl +func_cl_dashl () +{ + lib=$1 + found=no + save_IFS=$IFS + IFS=';' + for dir in $lib_path $LIB + do + IFS=$save_IFS + if $shared && test -f "$dir/$lib.dll.lib"; then + found=yes + lib=$dir/$lib.dll.lib + break + fi + if test -f "$dir/$lib.lib"; then + found=yes + lib=$dir/$lib.lib + break + fi + if test -f "$dir/lib$lib.a"; then + found=yes + lib=$dir/lib$lib.a + break + fi + done + IFS=$save_IFS + + if test "$found" != yes; then + lib=$lib.lib + fi +} + +# func_cl_wrapper cl arg... +# Adjust compile command to suit cl +func_cl_wrapper () +{ + # Assume a capable shell + lib_path= + shared=: + linker_opts= + for arg + do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + eat=1 + case $2 in + *.o | *.[oO][bB][jJ]) + func_file_conv "$2" + set x "$@" -Fo"$file" + shift + ;; + *) + func_file_conv "$2" + set x "$@" -Fe"$file" + shift + ;; + esac + ;; + -I) + eat=1 + func_file_conv "$2" mingw + set x "$@" -I"$file" + shift + ;; + -I*) + func_file_conv "${1#-I}" mingw + set x "$@" -I"$file" + shift + ;; + -l) + eat=1 + func_cl_dashl "$2" + set x "$@" "$lib" + shift + ;; + -l*) + func_cl_dashl "${1#-l}" + set x "$@" "$lib" + shift + ;; + -L) + eat=1 + func_cl_dashL "$2" + ;; + -L*) + func_cl_dashL "${1#-L}" + ;; + -static) + shared=false + ;; + -Wl,*) + arg=${1#-Wl,} + save_ifs="$IFS"; IFS=',' + for flag in $arg; do + IFS="$save_ifs" + linker_opts="$linker_opts $flag" + done + IFS="$save_ifs" + ;; + -Xlinker) + eat=1 + linker_opts="$linker_opts $2" + ;; + -*) + set x "$@" "$1" + shift + ;; + *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) + func_file_conv "$1" + set x "$@" -Tp"$file" + shift + ;; + *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) + func_file_conv "$1" mingw + set x "$@" "$file" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift + done + if test -n "$linker_opts"; then + linker_opts="-link$linker_opts" + fi + exec "$@" $linker_opts + exit 1 +} + +eat= + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: compile [--help] [--version] PROGRAM [ARGS] + +Wrapper for compilers which do not understand '-c -o'. +Remove '-o dest.o' from ARGS, run PROGRAM with the remaining +arguments, and rename the output as expected. + +If you are trying to build a whole package this is not the +right script to run: please start by reading the file 'INSTALL'. + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "compile $scriptversion" + exit $? + ;; + cl | *[/\\]cl | cl.exe | *[/\\]cl.exe ) + func_cl_wrapper "$@" # Doesn't return... + ;; +esac + +ofile= +cfile= + +for arg +do + if test -n "$eat"; then + eat= + else + case $1 in + -o) + # configure might choose to run compile as 'compile cc -o foo foo.c'. + # So we strip '-o arg' only if arg is an object. + eat=1 + case $2 in + *.o | *.obj) + ofile=$2 + ;; + *) + set x "$@" -o "$2" + shift + ;; + esac + ;; + *.c) + cfile=$1 + set x "$@" "$1" + shift + ;; + *) + set x "$@" "$1" + shift + ;; + esac + fi + shift +done + +if test -z "$ofile" || test -z "$cfile"; then + # If no '-o' option was seen then we might have been invoked from a + # pattern rule where we don't need one. That is ok -- this is a + # normal compilation that the losing compiler can handle. If no + # '.c' file was seen then we are probably linking. That is also + # ok. + exec "$@" +fi + +# Name of file we expect compiler to create. +cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` + +# Create the lock directory. +# Note: use '[/\\:.-]' here to ensure that we don't use the same name +# that we are using for the .o file. Also, base the name on the expected +# object file name, since that is what matters with a parallel build. +lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d +while true; do + if mkdir "$lockdir" >/dev/null 2>&1; then + break + fi + sleep 1 +done +# FIXME: race condition here if user kills between mkdir and trap. +trap "rmdir '$lockdir'; exit 1" 1 2 15 + +# Run the compile. +"$@" +ret=$? + +if test -f "$cofile"; then + test "$cofile" = "$ofile" || mv "$cofile" "$ofile" +elif test -f "${cofile}bj"; then + test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" +fi + +rmdir "$lockdir" +exit $ret + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/config/depcomp b/config/depcomp new file mode 100755 index 00000000..b39f98f9 --- /dev/null +++ b/config/depcomp @@ -0,0 +1,791 @@ +#! /bin/sh +# depcomp - compile a program generating dependencies as side-effects + +scriptversion=2016-01-11.22; # UTC + +# Copyright (C) 1999-2017 Free Software Foundation, Inc. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +# Originally written by Alexandre Oliva . + +case $1 in + '') + echo "$0: No command. Try '$0 --help' for more information." 1>&2 + exit 1; + ;; + -h | --h*) + cat <<\EOF +Usage: depcomp [--help] [--version] PROGRAM [ARGS] + +Run PROGRAMS ARGS to compile a file, generating dependencies +as side-effects. + +Environment variables: + depmode Dependency tracking mode. + source Source file read by 'PROGRAMS ARGS'. + object Object file output by 'PROGRAMS ARGS'. + DEPDIR directory where to store dependencies. + depfile Dependency file to output. + tmpdepfile Temporary file to use when outputting dependencies. + libtool Whether libtool is used (yes/no). + +Report bugs to . +EOF + exit $? + ;; + -v | --v*) + echo "depcomp $scriptversion" + exit $? + ;; +esac + +# Get the directory component of the given path, and save it in the +# global variables '$dir'. Note that this directory component will +# be either empty or ending with a '/' character. This is deliberate. +set_dir_from () +{ + case $1 in + */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; + *) dir=;; + esac +} + +# Get the suffix-stripped basename of the given path, and save it the +# global variable '$base'. +set_base_from () +{ + base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` +} + +# If no dependency file was actually created by the compiler invocation, +# we still have to create a dummy depfile, to avoid errors with the +# Makefile "include basename.Plo" scheme. +make_dummy_depfile () +{ + echo "#dummy" > "$depfile" +} + +# Factor out some common post-processing of the generated depfile. +# Requires the auxiliary global variable '$tmpdepfile' to be set. +aix_post_process_depfile () +{ + # If the compiler actually managed to produce a dependency file, + # post-process it. + if test -f "$tmpdepfile"; then + # Each line is of the form 'foo.o: dependency.h'. + # Do two passes, one to just change these to + # $object: dependency.h + # and one to simply output + # dependency.h: + # which is needed to avoid the deleted-header problem. + { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" + sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" + } > "$depfile" + rm -f "$tmpdepfile" + else + make_dummy_depfile + fi +} + +# A tabulation character. +tab=' ' +# A newline character. +nl=' +' +# Character ranges might be problematic outside the C locale. +# These definitions help. +upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ +lower=abcdefghijklmnopqrstuvwxyz +digits=0123456789 +alpha=${upper}${lower} + +if test -z "$depmode" || test -z "$source" || test -z "$object"; then + echo "depcomp: Variables source, object and depmode must be set" 1>&2 + exit 1 +fi + +# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. +depfile=${depfile-`echo "$object" | + sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} +tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} + +rm -f "$tmpdepfile" + +# Avoid interferences from the environment. +gccflag= dashmflag= + +# Some modes work just like other modes, but use different flags. We +# parameterize here, but still list the modes in the big case below, +# to make depend.m4 easier to write. Note that we *cannot* use a case +# here, because this file can only contain one case statement. +if test "$depmode" = hp; then + # HP compiler uses -M and no extra arg. + gccflag=-M + depmode=gcc +fi + +if test "$depmode" = dashXmstdout; then + # This is just like dashmstdout with a different argument. + dashmflag=-xM + depmode=dashmstdout +fi + +cygpath_u="cygpath -u -f -" +if test "$depmode" = msvcmsys; then + # This is just like msvisualcpp but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvisualcpp +fi + +if test "$depmode" = msvc7msys; then + # This is just like msvc7 but w/o cygpath translation. + # Just convert the backslash-escaped backslashes to single forward + # slashes to satisfy depend.m4 + cygpath_u='sed s,\\\\,/,g' + depmode=msvc7 +fi + +if test "$depmode" = xlc; then + # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. + gccflag=-qmakedep=gcc,-MF + depmode=gcc +fi + +case "$depmode" in +gcc3) +## gcc 3 implements dependency tracking that does exactly what +## we want. Yay! Note: for some reason libtool 1.4 doesn't like +## it if -MD -MP comes after the -MF stuff. Hmm. +## Unfortunately, FreeBSD c89 acceptance of flags depends upon +## the command line argument order; so add the flags where they +## appear in depend2.am. Note that the slowdown incurred here +## affects only configure: in makefiles, %FASTDEP% shortcuts this. + for arg + do + case $arg in + -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; + *) set fnord "$@" "$arg" ;; + esac + shift # fnord + shift # $arg + done + "$@" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + mv "$tmpdepfile" "$depfile" + ;; + +gcc) +## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. +## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. +## (see the conditional assignment to $gccflag above). +## There are various ways to get dependency output from gcc. Here's +## why we pick this rather obscure method: +## - Don't want to use -MD because we'd like the dependencies to end +## up in a subdir. Having to rename by hand is ugly. +## (We might end up doing this anyway to support other compilers.) +## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like +## -MM, not -M (despite what the docs say). Also, it might not be +## supported by the other compilers which use the 'gcc' depmode. +## - Using -M directly means running the compiler twice (even worse +## than renaming). + if test -z "$gccflag"; then + gccflag=-MD, + fi + "$@" -Wp,"$gccflag$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The second -e expression handles DOS-style file names with drive + # letters. + sed -e 's/^[^:]*: / /' \ + -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" +## This next piece of magic avoids the "deleted header file" problem. +## The problem is that when a header file which appears in a .P file +## is deleted, the dependency causes make to die (because there is +## typically no way to rebuild the header). We avoid this by adding +## dummy dependencies for each header file. Too bad gcc doesn't do +## this for us directly. +## Some versions of gcc put a space before the ':'. On the theory +## that the space means something, we add a space to the output as +## well. hp depmode also adds that space, but also prefixes the VPATH +## to the object. Take care to not repeat it in the output. +## Some versions of the HPUX 10.20 sed can't process this invocation +## correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +sgi) + if test "$libtool" = yes; then + "$@" "-Wp,-MDupdate,$tmpdepfile" + else + "$@" -MDupdate "$tmpdepfile" + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + + if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files + echo "$object : \\" > "$depfile" + # Clip off the initial element (the dependent). Don't try to be + # clever and replace this with sed code, as IRIX sed won't handle + # lines with more than a fixed number of characters (4096 in + # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; + # the IRIX cc adds comments like '#:fec' to the end of the + # dependency line. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ + | tr "$nl" ' ' >> "$depfile" + echo >> "$depfile" + # The second pass generates a dummy entry for each header file. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ + >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" + ;; + +xlc) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +aix) + # The C for AIX Compiler uses -M and outputs the dependencies + # in a .u file. In older versions, this file always lives in the + # current directory. Also, the AIX compiler puts '$object:' at the + # start of each line; $object doesn't have directory information. + # Version 6 uses the directory in both cases. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.u + tmpdepfile2=$base.u + tmpdepfile3=$dir.libs/$base.u + "$@" -Wc,-M + else + tmpdepfile1=$dir$base.u + tmpdepfile2=$dir$base.u + tmpdepfile3=$dir$base.u + "$@" -M + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + aix_post_process_depfile + ;; + +tcc) + # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 + # FIXME: That version still under development at the moment of writing. + # Make that this statement remains true also for stable, released + # versions. + # It will wrap lines (doesn't matter whether long or short) with a + # trailing '\', as in: + # + # foo.o : \ + # foo.c \ + # foo.h \ + # + # It will put a trailing '\' even on the last line, and will use leading + # spaces rather than leading tabs (at least since its commit 0394caf7 + # "Emit spaces for -MD"). + "$@" -MD -MF "$tmpdepfile" + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. + # We have to change lines of the first kind to '$object: \'. + sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" + # And for each line of the second kind, we have to emit a 'dep.h:' + # dummy dependency, to avoid the deleted-header problem. + sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" + rm -f "$tmpdepfile" + ;; + +## The order of this option in the case statement is important, since the +## shell code in configure will try each of these formats in the order +## listed in this file. A plain '-MD' option would be understood by many +## compilers, so we must ensure this comes after the gcc and icc options. +pgcc) + # Portland's C compiler understands '-MD'. + # Will always output deps to 'file.d' where file is the root name of the + # source file under compilation, even if file resides in a subdirectory. + # The object file name does not affect the name of the '.d' file. + # pgcc 10.2 will output + # foo.o: sub/foo.c sub/foo.h + # and will wrap long lines using '\' : + # foo.o: sub/foo.c ... \ + # sub/foo.h ... \ + # ... + set_dir_from "$object" + # Use the source, not the object, to determine the base name, since + # that's sadly what pgcc will do too. + set_base_from "$source" + tmpdepfile=$base.d + + # For projects that build the same source file twice into different object + # files, the pgcc approach of using the *source* file root name can cause + # problems in parallel builds. Use a locking strategy to avoid stomping on + # the same $tmpdepfile. + lockdir=$base.d-lock + trap " + echo '$0: caught signal, cleaning up...' >&2 + rmdir '$lockdir' + exit 1 + " 1 2 13 15 + numtries=100 + i=$numtries + while test $i -gt 0; do + # mkdir is a portable test-and-set. + if mkdir "$lockdir" 2>/dev/null; then + # This process acquired the lock. + "$@" -MD + stat=$? + # Release the lock. + rmdir "$lockdir" + break + else + # If the lock is being held by a different process, wait + # until the winning process is done or we timeout. + while test -d "$lockdir" && test $i -gt 0; do + sleep 1 + i=`expr $i - 1` + done + fi + i=`expr $i - 1` + done + trap - 1 2 13 15 + if test $i -le 0; then + echo "$0: failed to acquire lock after $numtries attempts" >&2 + echo "$0: check lockdir '$lockdir'" >&2 + exit 1 + fi + + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + # Each line is of the form `foo.o: dependent.h', + # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. + # Do two passes, one to just change these to + # `$object: dependent.h' and one to simply `dependent.h:'. + sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +hp2) + # The "hp" stanza above does not work with aCC (C++) and HP's ia64 + # compilers, which have integrated preprocessors. The correct option + # to use with these is +Maked; it writes dependencies to a file named + # 'foo.d', which lands next to the object file, wherever that + # happens to be. + # Much of this is similar to the tru64 case; see comments there. + set_dir_from "$object" + set_base_from "$object" + if test "$libtool" = yes; then + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir.libs/$base.d + "$@" -Wc,+Maked + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + "$@" +Maked + fi + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" + do + test -f "$tmpdepfile" && break + done + if test -f "$tmpdepfile"; then + sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" + # Add 'dependent.h:' lines. + sed -ne '2,${ + s/^ *// + s/ \\*$// + s/$/:/ + p + }' "$tmpdepfile" >> "$depfile" + else + make_dummy_depfile + fi + rm -f "$tmpdepfile" "$tmpdepfile2" + ;; + +tru64) + # The Tru64 compiler uses -MD to generate dependencies as a side + # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. + # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put + # dependencies in 'foo.d' instead, so we check for that too. + # Subdirectories are respected. + set_dir_from "$object" + set_base_from "$object" + + if test "$libtool" = yes; then + # Libtool generates 2 separate objects for the 2 libraries. These + # two compilations output dependencies in $dir.libs/$base.o.d and + # in $dir$base.o.d. We have to check for both files, because + # one of the two compilations can be disabled. We should prefer + # $dir$base.o.d over $dir.libs/$base.o.d because the latter is + # automatically cleaned when .libs/ is deleted, while ignoring + # the former would cause a distcleancheck panic. + tmpdepfile1=$dir$base.o.d # libtool 1.5 + tmpdepfile2=$dir.libs/$base.o.d # Likewise. + tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 + "$@" -Wc,-MD + else + tmpdepfile1=$dir$base.d + tmpdepfile2=$dir$base.d + tmpdepfile3=$dir$base.d + "$@" -MD + fi + + stat=$? + if test $stat -ne 0; then + rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + exit $stat + fi + + for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" + do + test -f "$tmpdepfile" && break + done + # Same post-processing that is required for AIX mode. + aix_post_process_depfile + ;; + +msvc7) + if test "$libtool" = yes; then + showIncludes=-Wc,-showIncludes + else + showIncludes=-showIncludes + fi + "$@" $showIncludes > "$tmpdepfile" + stat=$? + grep -v '^Note: including file: ' "$tmpdepfile" + if test $stat -ne 0; then + rm -f "$tmpdepfile" + exit $stat + fi + rm -f "$depfile" + echo "$object : \\" > "$depfile" + # The first sed program below extracts the file names and escapes + # backslashes for cygpath. The second sed program outputs the file + # name when reading, but also accumulates all include files in the + # hold buffer in order to output them again at the end. This only + # works with sed implementations that can handle large buffers. + sed < "$tmpdepfile" -n ' +/^Note: including file: *\(.*\)/ { + s//\1/ + s/\\/\\\\/g + p +}' | $cygpath_u | sort -u | sed -n ' +s/ /\\ /g +s/\(.*\)/'"$tab"'\1 \\/p +s/.\(.*\) \\/\1:/ +H +$ { + s/.*/'"$tab"'/ + G + p +}' >> "$depfile" + echo >> "$depfile" # make sure the fragment doesn't end with a backslash + rm -f "$tmpdepfile" + ;; + +msvc7msys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +#nosideeffect) + # This comment above is used by automake to tell side-effect + # dependency tracking mechanisms from slower ones. + +dashmstdout) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout, regardless of -o. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + test -z "$dashmflag" && dashmflag=-M + # Require at least two characters before searching for ':' + # in the target name. This is to cope with DOS-style filenames: + # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. + "$@" $dashmflag | + sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" + rm -f "$depfile" + cat < "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process this sed invocation + # correctly. Breaking it into two sed invocations is a workaround. + tr ' ' "$nl" < "$tmpdepfile" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +dashXmstdout) + # This case only exists to satisfy depend.m4. It is never actually + # run, as this mode is specially recognized in the preamble. + exit 1 + ;; + +makedepend) + "$@" || exit $? + # Remove any Libtool call + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + # X makedepend + shift + cleared=no eat=no + for arg + do + case $cleared in + no) + set ""; shift + cleared=yes ;; + esac + if test $eat = yes; then + eat=no + continue + fi + case "$arg" in + -D*|-I*) + set fnord "$@" "$arg"; shift ;; + # Strip any option that makedepend may not understand. Remove + # the object too, otherwise makedepend will parse it as a source file. + -arch) + eat=yes ;; + -*|$object) + ;; + *) + set fnord "$@" "$arg"; shift ;; + esac + done + obj_suffix=`echo "$object" | sed 's/^.*\././'` + touch "$tmpdepfile" + ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" + rm -f "$depfile" + # makedepend may prepend the VPATH from the source file name to the object. + # No need to regex-escape $object, excess matching of '.' is harmless. + sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" + # Some versions of the HPUX 10.20 sed can't process the last invocation + # correctly. Breaking it into two sed invocations is a workaround. + sed '1,2d' "$tmpdepfile" \ + | tr ' ' "$nl" \ + | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ + | sed -e 's/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" "$tmpdepfile".bak + ;; + +cpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + # Remove '-o $object'. + IFS=" " + for arg + do + case $arg in + -o) + shift + ;; + $object) + shift + ;; + *) + set fnord "$@" "$arg" + shift # fnord + shift # $arg + ;; + esac + done + + "$@" -E \ + | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ + | sed '$ s: \\$::' > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + cat < "$tmpdepfile" >> "$depfile" + sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvisualcpp) + # Important note: in order to support this mode, a compiler *must* + # always write the preprocessed file to stdout. + "$@" || exit $? + + # Remove the call to Libtool. + if test "$libtool" = yes; then + while test "X$1" != 'X--mode=compile'; do + shift + done + shift + fi + + IFS=" " + for arg + do + case "$arg" in + -o) + shift + ;; + $object) + shift + ;; + "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") + set fnord "$@" + shift + shift + ;; + *) + set fnord "$@" "$arg" + shift + shift + ;; + esac + done + "$@" -E 2>/dev/null | + sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" + rm -f "$depfile" + echo "$object : \\" > "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" + echo "$tab" >> "$depfile" + sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" + rm -f "$tmpdepfile" + ;; + +msvcmsys) + # This case exists only to let depend.m4 do its work. It works by + # looking at the text of this script. This case will never be run, + # since it is checked for above. + exit 1 + ;; + +none) + exec "$@" + ;; + +*) + echo "Unknown depmode $depmode" 1>&2 + exit 1 + ;; +esac + +exit 0 + +# Local Variables: +# mode: shell-script +# sh-indentation: 2 +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC0" +# time-stamp-end: "; # UTC" +# End: diff --git a/config/install-sh b/config/install-sh new file mode 100755 index 00000000..59990a10 --- /dev/null +++ b/config/install-sh @@ -0,0 +1,508 @@ +#!/bin/sh +# install - install a program, script, or datafile + +scriptversion=2014-09-12.12; # UTC + +# This originates from X11R5 (mit/util/scripts/install.sh), which was +# later released in X11R6 (xc/config/util/install.sh) with the +# following copyright and license. +# +# Copyright (C) 1994 X Consortium +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- +# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the name of the X Consortium shall not +# be used in advertising or otherwise to promote the sale, use or other deal- +# ings in this Software without prior written authorization from the X Consor- +# tium. +# +# +# FSF changes to this file are in the public domain. +# +# Calling this script install-sh is preferred over install.sh, to prevent +# 'make' implicit rules from creating a file called install from it +# when there is no Makefile. +# +# This script is compatible with the BSD install script, but was written +# from scratch. + +tab=' ' +nl=' +' +IFS=" $tab$nl" + +# Set DOITPROG to "echo" to test this script. + +doit=${DOITPROG-} +doit_exec=${doit:-exec} + +# Put in absolute file names if you don't have them in your path; +# or use environment vars. + +chgrpprog=${CHGRPPROG-chgrp} +chmodprog=${CHMODPROG-chmod} +chownprog=${CHOWNPROG-chown} +cmpprog=${CMPPROG-cmp} +cpprog=${CPPROG-cp} +mkdirprog=${MKDIRPROG-mkdir} +mvprog=${MVPROG-mv} +rmprog=${RMPROG-rm} +stripprog=${STRIPPROG-strip} + +posix_mkdir= + +# Desired mode of installed file. +mode=0755 + +chgrpcmd= +chmodcmd=$chmodprog +chowncmd= +mvcmd=$mvprog +rmcmd="$rmprog -f" +stripcmd= + +src= +dst= +dir_arg= +dst_arg= + +copy_on_change=false +is_target_a_directory=possibly + +usage="\ +Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE + or: $0 [OPTION]... SRCFILES... DIRECTORY + or: $0 [OPTION]... -t DIRECTORY SRCFILES... + or: $0 [OPTION]... -d DIRECTORIES... + +In the 1st form, copy SRCFILE to DSTFILE. +In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. +In the 4th, create DIRECTORIES. + +Options: + --help display this help and exit. + --version display version info and exit. + + -c (ignored) + -C install only if different (preserve the last data modification time) + -d create directories instead of installing files. + -g GROUP $chgrpprog installed files to GROUP. + -m MODE $chmodprog installed files to MODE. + -o USER $chownprog installed files to USER. + -s $stripprog installed files. + -t DIRECTORY install into DIRECTORY. + -T report an error if DSTFILE is a directory. + +Environment variables override the default commands: + CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG + RMPROG STRIPPROG +" + +while test $# -ne 0; do + case $1 in + -c) ;; + + -C) copy_on_change=true;; + + -d) dir_arg=true;; + + -g) chgrpcmd="$chgrpprog $2" + shift;; + + --help) echo "$usage"; exit $?;; + + -m) mode=$2 + case $mode in + *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) + echo "$0: invalid mode: $mode" >&2 + exit 1;; + esac + shift;; + + -o) chowncmd="$chownprog $2" + shift;; + + -s) stripcmd=$stripprog;; + + -t) + is_target_a_directory=always + dst_arg=$2 + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + shift;; + + -T) is_target_a_directory=never;; + + --version) echo "$0 $scriptversion"; exit $?;; + + --) shift + break;; + + -*) echo "$0: invalid option: $1" >&2 + exit 1;; + + *) break;; + esac + shift +done + +# We allow the use of options -d and -T together, by making -d +# take the precedence; this is for compatibility with GNU install. + +if test -n "$dir_arg"; then + if test -n "$dst_arg"; then + echo "$0: target directory not allowed when installing a directory." >&2 + exit 1 + fi +fi + +if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then + # When -d is used, all remaining arguments are directories to create. + # When -t is used, the destination is already specified. + # Otherwise, the last argument is the destination. Remove it from $@. + for arg + do + if test -n "$dst_arg"; then + # $@ is not empty: it contains at least $arg. + set fnord "$@" "$dst_arg" + shift # fnord + fi + shift # arg + dst_arg=$arg + # Protect names problematic for 'test' and other utilities. + case $dst_arg in + -* | [=\(\)!]) dst_arg=./$dst_arg;; + esac + done +fi + +if test $# -eq 0; then + if test -z "$dir_arg"; then + echo "$0: no input file specified." >&2 + exit 1 + fi + # It's OK to call 'install-sh -d' without argument. + # This can happen when creating conditional directories. + exit 0 +fi + +if test -z "$dir_arg"; then + if test $# -gt 1 || test "$is_target_a_directory" = always; then + if test ! -d "$dst_arg"; then + echo "$0: $dst_arg: Is not a directory." >&2 + exit 1 + fi + fi +fi + +if test -z "$dir_arg"; then + do_exit='(exit $ret); exit $ret' + trap "ret=129; $do_exit" 1 + trap "ret=130; $do_exit" 2 + trap "ret=141; $do_exit" 13 + trap "ret=143; $do_exit" 15 + + # Set umask so as not to create temps with too-generous modes. + # However, 'strip' requires both read and write access to temps. + case $mode in + # Optimize common cases. + *644) cp_umask=133;; + *755) cp_umask=22;; + + *[0-7]) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw='% 200' + fi + cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; + *) + if test -z "$stripcmd"; then + u_plus_rw= + else + u_plus_rw=,u+rw + fi + cp_umask=$mode$u_plus_rw;; + esac +fi + +for src +do + # Protect names problematic for 'test' and other utilities. + case $src in + -* | [=\(\)!]) src=./$src;; + esac + + if test -n "$dir_arg"; then + dst=$src + dstdir=$dst + test -d "$dstdir" + dstdir_status=$? + else + + # Waiting for this to be detected by the "$cpprog $src $dsttmp" command + # might cause directories to be created, which would be especially bad + # if $src (and thus $dsttmp) contains '*'. + if test ! -f "$src" && test ! -d "$src"; then + echo "$0: $src does not exist." >&2 + exit 1 + fi + + if test -z "$dst_arg"; then + echo "$0: no destination specified." >&2 + exit 1 + fi + dst=$dst_arg + + # If destination is a directory, append the input filename; won't work + # if double slashes aren't ignored. + if test -d "$dst"; then + if test "$is_target_a_directory" = never; then + echo "$0: $dst_arg: Is a directory" >&2 + exit 1 + fi + dstdir=$dst + dst=$dstdir/`basename "$src"` + dstdir_status=0 + else + dstdir=`dirname "$dst"` + test -d "$dstdir" + dstdir_status=$? + fi + fi + + obsolete_mkdir_used=false + + if test $dstdir_status != 0; then + case $posix_mkdir in + '') + # Create intermediate dirs using mode 755 as modified by the umask. + # This is like FreeBSD 'install' as of 1997-10-28. + umask=`umask` + case $stripcmd.$umask in + # Optimize common cases. + *[2367][2367]) mkdir_umask=$umask;; + .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; + + *[0-7]) + mkdir_umask=`expr $umask + 22 \ + - $umask % 100 % 40 + $umask % 20 \ + - $umask % 10 % 4 + $umask % 2 + `;; + *) mkdir_umask=$umask,go-w;; + esac + + # With -d, create the new directory with the user-specified mode. + # Otherwise, rely on $mkdir_umask. + if test -n "$dir_arg"; then + mkdir_mode=-m$mode + else + mkdir_mode= + fi + + posix_mkdir=false + case $umask in + *[123567][0-7][0-7]) + # POSIX mkdir -p sets u+wx bits regardless of umask, which + # is incompatible with FreeBSD 'install' when (umask & 300) != 0. + ;; + *) + # $RANDOM is not portable (e.g. dash); use it when possible to + # lower collision chance + tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ + trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 + + # As "mkdir -p" follows symlinks and we work in /tmp possibly; so + # create the $tmpdir first (and fail if unsuccessful) to make sure + # that nobody tries to guess the $tmpdir name. + if (umask $mkdir_umask && + $mkdirprog $mkdir_mode "$tmpdir" && + exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 + then + if test -z "$dir_arg" || { + # Check for POSIX incompatibilities with -m. + # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or + # other-writable bit of parent directory when it shouldn't. + # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. + test_tmpdir="$tmpdir/a" + ls_ld_tmpdir=`ls -ld "$test_tmpdir"` + case $ls_ld_tmpdir in + d????-?r-*) different_mode=700;; + d????-?--*) different_mode=755;; + *) false;; + esac && + $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { + ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` + test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" + } + } + then posix_mkdir=: + fi + rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" + else + # Remove any dirs left behind by ancient mkdir implementations. + rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null + fi + trap '' 0;; + esac;; + esac + + if + $posix_mkdir && ( + umask $mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" + ) + then : + else + + # The umask is ridiculous, or mkdir does not conform to POSIX, + # or it failed possibly due to a race condition. Create the + # directory the slow way, step by step, checking for races as we go. + + case $dstdir in + /*) prefix='/';; + [-=\(\)!]*) prefix='./';; + *) prefix='';; + esac + + oIFS=$IFS + IFS=/ + set -f + set fnord $dstdir + shift + set +f + IFS=$oIFS + + prefixes= + + for d + do + test X"$d" = X && continue + + prefix=$prefix$d + if test -d "$prefix"; then + prefixes= + else + if $posix_mkdir; then + (umask=$mkdir_umask && + $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break + # Don't fail if two instances are running concurrently. + test -d "$prefix" || exit 1 + else + case $prefix in + *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; + *) qprefix=$prefix;; + esac + prefixes="$prefixes '$qprefix'" + fi + fi + prefix=$prefix/ + done + + if test -n "$prefixes"; then + # Don't fail if two instances are running concurrently. + (umask $mkdir_umask && + eval "\$doit_exec \$mkdirprog $prefixes") || + test -d "$dstdir" || exit 1 + obsolete_mkdir_used=true + fi + fi + fi + + if test -n "$dir_arg"; then + { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && + { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || + test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 + else + + # Make a couple of temp file names in the proper directory. + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ + + # Trap to clean up those temp files at exit. + trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 + + # Copy the file name to the temp name. + (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && + + # and set any options; do chmod last to preserve setuid bits. + # + # If any of these fail, we abort the whole thing. If we want to + # ignore errors from any of these, just make sure not to ignore + # errors from the above "$doit $cpprog $src $dsttmp" command. + # + { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && + { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && + { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && + { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && + + # If -C, don't bother to copy if it wouldn't change the file. + if $copy_on_change && + old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && + new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && + set -f && + set X $old && old=:$2:$4:$5:$6 && + set X $new && new=:$2:$4:$5:$6 && + set +f && + test "$old" = "$new" && + $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 + then + rm -f "$dsttmp" + else + # Rename the file to the real destination. + $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || + + # The rename failed, perhaps because mv can't rename something else + # to itself, or perhaps because mv is so ancient that it does not + # support -f. + { + # Now remove or move aside any old file at destination location. + # We try this two ways since rm can't unlink itself on some + # systems and the destination file might be busy for other + # reasons. In this case, the final cleanup might fail but the new + # file should still install successfully. + { + test ! -f "$dst" || + $doit $rmcmd -f "$dst" 2>/dev/null || + { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && + { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } + } || + { echo "$0: cannot unlink or rename $dst" >&2 + (exit 1); exit 1 + } + } && + + # Now rename the file to the real destination. + $doit $mvcmd "$dsttmp" "$dst" + } + fi || exit 1 + + trap '' 0 + fi +done + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: diff --git a/config/missing b/config/missing new file mode 100755 index 00000000..f62bbae3 --- /dev/null +++ b/config/missing @@ -0,0 +1,215 @@ +#! /bin/sh +# Common wrapper for a few potentially missing GNU programs. + +scriptversion=2013-10-28.13; # UTC + +# Copyright (C) 1996-2014 Free Software Foundation, Inc. +# Originally written by Fran,cois Pinard , 1996. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2, or (at your option) +# any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + +if test $# -eq 0; then + echo 1>&2 "Try '$0 --help' for more information" + exit 1 +fi + +case $1 in + + --is-lightweight) + # Used by our autoconf macros to check whether the available missing + # script is modern enough. + exit 0 + ;; + + --run) + # Back-compat with the calling convention used by older automake. + shift + ;; + + -h|--h|--he|--hel|--help) + echo "\ +$0 [OPTION]... PROGRAM [ARGUMENT]... + +Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due +to PROGRAM being missing or too old. + +Options: + -h, --help display this help and exit + -v, --version output version information and exit + +Supported PROGRAM values: + aclocal autoconf autoheader autom4te automake makeinfo + bison yacc flex lex help2man + +Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and +'g' are ignored when checking the name. + +Send bug reports to ." + exit $? + ;; + + -v|--v|--ve|--ver|--vers|--versi|--versio|--version) + echo "missing $scriptversion (GNU Automake)" + exit $? + ;; + + -*) + echo 1>&2 "$0: unknown '$1' option" + echo 1>&2 "Try '$0 --help' for more information" + exit 1 + ;; + +esac + +# Run the given program, remember its exit status. +"$@"; st=$? + +# If it succeeded, we are done. +test $st -eq 0 && exit 0 + +# Also exit now if we it failed (or wasn't found), and '--version' was +# passed; such an option is passed most likely to detect whether the +# program is present and works. +case $2 in --version|--help) exit $st;; esac + +# Exit code 63 means version mismatch. This often happens when the user +# tries to use an ancient version of a tool on a file that requires a +# minimum version. +if test $st -eq 63; then + msg="probably too old" +elif test $st -eq 127; then + # Program was missing. + msg="missing on your system" +else + # Program was found and executed, but failed. Give up. + exit $st +fi + +perl_URL=http://www.perl.org/ +flex_URL=http://flex.sourceforge.net/ +gnu_software_URL=http://www.gnu.org/software + +program_details () +{ + case $1 in + aclocal|automake) + echo "The '$1' program is part of the GNU Automake package:" + echo "<$gnu_software_URL/automake>" + echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/autoconf>" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + autoconf|autom4te|autoheader) + echo "The '$1' program is part of the GNU Autoconf package:" + echo "<$gnu_software_URL/autoconf/>" + echo "It also requires GNU m4 and Perl in order to run:" + echo "<$gnu_software_URL/m4/>" + echo "<$perl_URL>" + ;; + esac +} + +give_advice () +{ + # Normalize program name to check for. + normalized_program=`echo "$1" | sed ' + s/^gnu-//; t + s/^gnu//; t + s/^g//; t'` + + printf '%s\n' "'$1' is $msg." + + configure_deps="'configure.ac' or m4 files included by 'configure.ac'" + case $normalized_program in + autoconf*) + echo "You should only need it if you modified 'configure.ac'," + echo "or m4 files included by it." + program_details 'autoconf' + ;; + autoheader*) + echo "You should only need it if you modified 'acconfig.h' or" + echo "$configure_deps." + program_details 'autoheader' + ;; + automake*) + echo "You should only need it if you modified 'Makefile.am' or" + echo "$configure_deps." + program_details 'automake' + ;; + aclocal*) + echo "You should only need it if you modified 'acinclude.m4' or" + echo "$configure_deps." + program_details 'aclocal' + ;; + autom4te*) + echo "You might have modified some maintainer files that require" + echo "the 'autom4te' program to be rebuilt." + program_details 'autom4te' + ;; + bison*|yacc*) + echo "You should only need it if you modified a '.y' file." + echo "You may want to install the GNU Bison package:" + echo "<$gnu_software_URL/bison/>" + ;; + lex*|flex*) + echo "You should only need it if you modified a '.l' file." + echo "You may want to install the Fast Lexical Analyzer package:" + echo "<$flex_URL>" + ;; + help2man*) + echo "You should only need it if you modified a dependency" \ + "of a man page." + echo "You may want to install the GNU Help2man package:" + echo "<$gnu_software_URL/help2man/>" + ;; + makeinfo*) + echo "You should only need it if you modified a '.texi' file, or" + echo "any other file indirectly affecting the aspect of the manual." + echo "You might want to install the Texinfo package:" + echo "<$gnu_software_URL/texinfo/>" + echo "The spurious makeinfo call might also be the consequence of" + echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" + echo "want to install GNU make:" + echo "<$gnu_software_URL/make/>" + ;; + *) + echo "You might have modified some files without having the proper" + echo "tools for further handling them. Check the 'README' file, it" + echo "often tells you about the needed prerequisites for installing" + echo "this package. You may also peek at any GNU archive site, in" + echo "case some other package contains this missing '$1' program." + ;; + esac +} + +give_advice "$1" | sed -e '1s/^/WARNING: /' \ + -e '2,$s/^/ /' >&2 + +# Propagate the correct exit status (expected to be 127 for a program +# not found, 63 for a program that failed due to version mismatch). +exit $st + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "scriptversion=" +# time-stamp-format: "%:y-%02m-%02d.%02H" +# time-stamp-time-zone: "UTC" +# time-stamp-end: "; # UTC" +# End: From a294ff3c4092e442f96b9ea078c9e78e37a85c45 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 28 May 2019 09:33:14 -0700 Subject: [PATCH 0026/1992] removed generated config files --- config/compile | 347 -------------------- config/depcomp | 791 ---------------------------------------------- config/install-sh | 508 ----------------------------- config/missing | 215 ------------- 4 files changed, 1861 deletions(-) delete mode 100755 config/compile delete mode 100755 config/depcomp delete mode 100755 config/install-sh delete mode 100755 config/missing diff --git a/config/compile b/config/compile deleted file mode 100755 index a85b723c..00000000 --- a/config/compile +++ /dev/null @@ -1,347 +0,0 @@ -#! /bin/sh -# Wrapper for compilers which do not understand '-c -o'. - -scriptversion=2012-10-14.11; # UTC - -# Copyright (C) 1999-2014 Free Software Foundation, Inc. -# Written by Tom Tromey . -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# This file is maintained in Automake, please report -# bugs to or send patches to -# . - -nl=' -' - -# We need space, tab and new line, in precisely that order. Quoting is -# there to prevent tools from complaining about whitespace usage. -IFS=" "" $nl" - -file_conv= - -# func_file_conv build_file lazy -# Convert a $build file to $host form and store it in $file -# Currently only supports Windows hosts. If the determined conversion -# type is listed in (the comma separated) LAZY, no conversion will -# take place. -func_file_conv () -{ - file=$1 - case $file in - / | /[!/]*) # absolute file, and not a UNC file - if test -z "$file_conv"; then - # lazily determine how to convert abs files - case `uname -s` in - MINGW*) - file_conv=mingw - ;; - CYGWIN*) - file_conv=cygwin - ;; - *) - file_conv=wine - ;; - esac - fi - case $file_conv/,$2, in - *,$file_conv,*) - ;; - mingw/*) - file=`cmd //C echo "$file " | sed -e 's/"\(.*\) " *$/\1/'` - ;; - cygwin/*) - file=`cygpath -m "$file" || echo "$file"` - ;; - wine/*) - file=`winepath -w "$file" || echo "$file"` - ;; - esac - ;; - esac -} - -# func_cl_dashL linkdir -# Make cl look for libraries in LINKDIR -func_cl_dashL () -{ - func_file_conv "$1" - if test -z "$lib_path"; then - lib_path=$file - else - lib_path="$lib_path;$file" - fi - linker_opts="$linker_opts -LIBPATH:$file" -} - -# func_cl_dashl library -# Do a library search-path lookup for cl -func_cl_dashl () -{ - lib=$1 - found=no - save_IFS=$IFS - IFS=';' - for dir in $lib_path $LIB - do - IFS=$save_IFS - if $shared && test -f "$dir/$lib.dll.lib"; then - found=yes - lib=$dir/$lib.dll.lib - break - fi - if test -f "$dir/$lib.lib"; then - found=yes - lib=$dir/$lib.lib - break - fi - if test -f "$dir/lib$lib.a"; then - found=yes - lib=$dir/lib$lib.a - break - fi - done - IFS=$save_IFS - - if test "$found" != yes; then - lib=$lib.lib - fi -} - -# func_cl_wrapper cl arg... -# Adjust compile command to suit cl -func_cl_wrapper () -{ - # Assume a capable shell - lib_path= - shared=: - linker_opts= - for arg - do - if test -n "$eat"; then - eat= - else - case $1 in - -o) - # configure might choose to run compile as 'compile cc -o foo foo.c'. - eat=1 - case $2 in - *.o | *.[oO][bB][jJ]) - func_file_conv "$2" - set x "$@" -Fo"$file" - shift - ;; - *) - func_file_conv "$2" - set x "$@" -Fe"$file" - shift - ;; - esac - ;; - -I) - eat=1 - func_file_conv "$2" mingw - set x "$@" -I"$file" - shift - ;; - -I*) - func_file_conv "${1#-I}" mingw - set x "$@" -I"$file" - shift - ;; - -l) - eat=1 - func_cl_dashl "$2" - set x "$@" "$lib" - shift - ;; - -l*) - func_cl_dashl "${1#-l}" - set x "$@" "$lib" - shift - ;; - -L) - eat=1 - func_cl_dashL "$2" - ;; - -L*) - func_cl_dashL "${1#-L}" - ;; - -static) - shared=false - ;; - -Wl,*) - arg=${1#-Wl,} - save_ifs="$IFS"; IFS=',' - for flag in $arg; do - IFS="$save_ifs" - linker_opts="$linker_opts $flag" - done - IFS="$save_ifs" - ;; - -Xlinker) - eat=1 - linker_opts="$linker_opts $2" - ;; - -*) - set x "$@" "$1" - shift - ;; - *.cc | *.CC | *.cxx | *.CXX | *.[cC]++) - func_file_conv "$1" - set x "$@" -Tp"$file" - shift - ;; - *.c | *.cpp | *.CPP | *.lib | *.LIB | *.Lib | *.OBJ | *.obj | *.[oO]) - func_file_conv "$1" mingw - set x "$@" "$file" - shift - ;; - *) - set x "$@" "$1" - shift - ;; - esac - fi - shift - done - if test -n "$linker_opts"; then - linker_opts="-link$linker_opts" - fi - exec "$@" $linker_opts - exit 1 -} - -eat= - -case $1 in - '') - echo "$0: No command. Try '$0 --help' for more information." 1>&2 - exit 1; - ;; - -h | --h*) - cat <<\EOF -Usage: compile [--help] [--version] PROGRAM [ARGS] - -Wrapper for compilers which do not understand '-c -o'. -Remove '-o dest.o' from ARGS, run PROGRAM with the remaining -arguments, and rename the output as expected. - -If you are trying to build a whole package this is not the -right script to run: please start by reading the file 'INSTALL'. - -Report bugs to . -EOF - exit $? - ;; - -v | --v*) - echo "compile $scriptversion" - exit $? - ;; - cl | *[/\\]cl | cl.exe | *[/\\]cl.exe ) - func_cl_wrapper "$@" # Doesn't return... - ;; -esac - -ofile= -cfile= - -for arg -do - if test -n "$eat"; then - eat= - else - case $1 in - -o) - # configure might choose to run compile as 'compile cc -o foo foo.c'. - # So we strip '-o arg' only if arg is an object. - eat=1 - case $2 in - *.o | *.obj) - ofile=$2 - ;; - *) - set x "$@" -o "$2" - shift - ;; - esac - ;; - *.c) - cfile=$1 - set x "$@" "$1" - shift - ;; - *) - set x "$@" "$1" - shift - ;; - esac - fi - shift -done - -if test -z "$ofile" || test -z "$cfile"; then - # If no '-o' option was seen then we might have been invoked from a - # pattern rule where we don't need one. That is ok -- this is a - # normal compilation that the losing compiler can handle. If no - # '.c' file was seen then we are probably linking. That is also - # ok. - exec "$@" -fi - -# Name of file we expect compiler to create. -cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` - -# Create the lock directory. -# Note: use '[/\\:.-]' here to ensure that we don't use the same name -# that we are using for the .o file. Also, base the name on the expected -# object file name, since that is what matters with a parallel build. -lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d -while true; do - if mkdir "$lockdir" >/dev/null 2>&1; then - break - fi - sleep 1 -done -# FIXME: race condition here if user kills between mkdir and trap. -trap "rmdir '$lockdir'; exit 1" 1 2 15 - -# Run the compile. -"$@" -ret=$? - -if test -f "$cofile"; then - test "$cofile" = "$ofile" || mv "$cofile" "$ofile" -elif test -f "${cofile}bj"; then - test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" -fi - -rmdir "$lockdir" -exit $ret - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" -# time-stamp-end: "; # UTC" -# End: diff --git a/config/depcomp b/config/depcomp deleted file mode 100755 index b39f98f9..00000000 --- a/config/depcomp +++ /dev/null @@ -1,791 +0,0 @@ -#! /bin/sh -# depcomp - compile a program generating dependencies as side-effects - -scriptversion=2016-01-11.22; # UTC - -# Copyright (C) 1999-2017 Free Software Foundation, Inc. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# Originally written by Alexandre Oliva . - -case $1 in - '') - echo "$0: No command. Try '$0 --help' for more information." 1>&2 - exit 1; - ;; - -h | --h*) - cat <<\EOF -Usage: depcomp [--help] [--version] PROGRAM [ARGS] - -Run PROGRAMS ARGS to compile a file, generating dependencies -as side-effects. - -Environment variables: - depmode Dependency tracking mode. - source Source file read by 'PROGRAMS ARGS'. - object Object file output by 'PROGRAMS ARGS'. - DEPDIR directory where to store dependencies. - depfile Dependency file to output. - tmpdepfile Temporary file to use when outputting dependencies. - libtool Whether libtool is used (yes/no). - -Report bugs to . -EOF - exit $? - ;; - -v | --v*) - echo "depcomp $scriptversion" - exit $? - ;; -esac - -# Get the directory component of the given path, and save it in the -# global variables '$dir'. Note that this directory component will -# be either empty or ending with a '/' character. This is deliberate. -set_dir_from () -{ - case $1 in - */*) dir=`echo "$1" | sed -e 's|/[^/]*$|/|'`;; - *) dir=;; - esac -} - -# Get the suffix-stripped basename of the given path, and save it the -# global variable '$base'. -set_base_from () -{ - base=`echo "$1" | sed -e 's|^.*/||' -e 's/\.[^.]*$//'` -} - -# If no dependency file was actually created by the compiler invocation, -# we still have to create a dummy depfile, to avoid errors with the -# Makefile "include basename.Plo" scheme. -make_dummy_depfile () -{ - echo "#dummy" > "$depfile" -} - -# Factor out some common post-processing of the generated depfile. -# Requires the auxiliary global variable '$tmpdepfile' to be set. -aix_post_process_depfile () -{ - # If the compiler actually managed to produce a dependency file, - # post-process it. - if test -f "$tmpdepfile"; then - # Each line is of the form 'foo.o: dependency.h'. - # Do two passes, one to just change these to - # $object: dependency.h - # and one to simply output - # dependency.h: - # which is needed to avoid the deleted-header problem. - { sed -e "s,^.*\.[$lower]*:,$object:," < "$tmpdepfile" - sed -e "s,^.*\.[$lower]*:[$tab ]*,," -e 's,$,:,' < "$tmpdepfile" - } > "$depfile" - rm -f "$tmpdepfile" - else - make_dummy_depfile - fi -} - -# A tabulation character. -tab=' ' -# A newline character. -nl=' -' -# Character ranges might be problematic outside the C locale. -# These definitions help. -upper=ABCDEFGHIJKLMNOPQRSTUVWXYZ -lower=abcdefghijklmnopqrstuvwxyz -digits=0123456789 -alpha=${upper}${lower} - -if test -z "$depmode" || test -z "$source" || test -z "$object"; then - echo "depcomp: Variables source, object and depmode must be set" 1>&2 - exit 1 -fi - -# Dependencies for sub/bar.o or sub/bar.obj go into sub/.deps/bar.Po. -depfile=${depfile-`echo "$object" | - sed 's|[^\\/]*$|'${DEPDIR-.deps}'/&|;s|\.\([^.]*\)$|.P\1|;s|Pobj$|Po|'`} -tmpdepfile=${tmpdepfile-`echo "$depfile" | sed 's/\.\([^.]*\)$/.T\1/'`} - -rm -f "$tmpdepfile" - -# Avoid interferences from the environment. -gccflag= dashmflag= - -# Some modes work just like other modes, but use different flags. We -# parameterize here, but still list the modes in the big case below, -# to make depend.m4 easier to write. Note that we *cannot* use a case -# here, because this file can only contain one case statement. -if test "$depmode" = hp; then - # HP compiler uses -M and no extra arg. - gccflag=-M - depmode=gcc -fi - -if test "$depmode" = dashXmstdout; then - # This is just like dashmstdout with a different argument. - dashmflag=-xM - depmode=dashmstdout -fi - -cygpath_u="cygpath -u -f -" -if test "$depmode" = msvcmsys; then - # This is just like msvisualcpp but w/o cygpath translation. - # Just convert the backslash-escaped backslashes to single forward - # slashes to satisfy depend.m4 - cygpath_u='sed s,\\\\,/,g' - depmode=msvisualcpp -fi - -if test "$depmode" = msvc7msys; then - # This is just like msvc7 but w/o cygpath translation. - # Just convert the backslash-escaped backslashes to single forward - # slashes to satisfy depend.m4 - cygpath_u='sed s,\\\\,/,g' - depmode=msvc7 -fi - -if test "$depmode" = xlc; then - # IBM C/C++ Compilers xlc/xlC can output gcc-like dependency information. - gccflag=-qmakedep=gcc,-MF - depmode=gcc -fi - -case "$depmode" in -gcc3) -## gcc 3 implements dependency tracking that does exactly what -## we want. Yay! Note: for some reason libtool 1.4 doesn't like -## it if -MD -MP comes after the -MF stuff. Hmm. -## Unfortunately, FreeBSD c89 acceptance of flags depends upon -## the command line argument order; so add the flags where they -## appear in depend2.am. Note that the slowdown incurred here -## affects only configure: in makefiles, %FASTDEP% shortcuts this. - for arg - do - case $arg in - -c) set fnord "$@" -MT "$object" -MD -MP -MF "$tmpdepfile" "$arg" ;; - *) set fnord "$@" "$arg" ;; - esac - shift # fnord - shift # $arg - done - "$@" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - mv "$tmpdepfile" "$depfile" - ;; - -gcc) -## Note that this doesn't just cater to obsosete pre-3.x GCC compilers. -## but also to in-use compilers like IMB xlc/xlC and the HP C compiler. -## (see the conditional assignment to $gccflag above). -## There are various ways to get dependency output from gcc. Here's -## why we pick this rather obscure method: -## - Don't want to use -MD because we'd like the dependencies to end -## up in a subdir. Having to rename by hand is ugly. -## (We might end up doing this anyway to support other compilers.) -## - The DEPENDENCIES_OUTPUT environment variable makes gcc act like -## -MM, not -M (despite what the docs say). Also, it might not be -## supported by the other compilers which use the 'gcc' depmode. -## - Using -M directly means running the compiler twice (even worse -## than renaming). - if test -z "$gccflag"; then - gccflag=-MD, - fi - "$@" -Wp,"$gccflag$tmpdepfile" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - echo "$object : \\" > "$depfile" - # The second -e expression handles DOS-style file names with drive - # letters. - sed -e 's/^[^:]*: / /' \ - -e 's/^['$alpha']:\/[^:]*: / /' < "$tmpdepfile" >> "$depfile" -## This next piece of magic avoids the "deleted header file" problem. -## The problem is that when a header file which appears in a .P file -## is deleted, the dependency causes make to die (because there is -## typically no way to rebuild the header). We avoid this by adding -## dummy dependencies for each header file. Too bad gcc doesn't do -## this for us directly. -## Some versions of gcc put a space before the ':'. On the theory -## that the space means something, we add a space to the output as -## well. hp depmode also adds that space, but also prefixes the VPATH -## to the object. Take care to not repeat it in the output. -## Some versions of the HPUX 10.20 sed can't process this invocation -## correctly. Breaking it into two sed invocations is a workaround. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^\\$//' -e '/^$/d' -e "s|.*$object$||" -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -hp) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -sgi) - if test "$libtool" = yes; then - "$@" "-Wp,-MDupdate,$tmpdepfile" - else - "$@" -MDupdate "$tmpdepfile" - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - - if test -f "$tmpdepfile"; then # yes, the sourcefile depend on other files - echo "$object : \\" > "$depfile" - # Clip off the initial element (the dependent). Don't try to be - # clever and replace this with sed code, as IRIX sed won't handle - # lines with more than a fixed number of characters (4096 in - # IRIX 6.2 sed, 8192 in IRIX 6.5). We also remove comment lines; - # the IRIX cc adds comments like '#:fec' to the end of the - # dependency line. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' \ - | tr "$nl" ' ' >> "$depfile" - echo >> "$depfile" - # The second pass generates a dummy entry for each header file. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^.*\.o://' -e 's/#.*$//' -e '/^$/ d' -e 's/$/:/' \ - >> "$depfile" - else - make_dummy_depfile - fi - rm -f "$tmpdepfile" - ;; - -xlc) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -aix) - # The C for AIX Compiler uses -M and outputs the dependencies - # in a .u file. In older versions, this file always lives in the - # current directory. Also, the AIX compiler puts '$object:' at the - # start of each line; $object doesn't have directory information. - # Version 6 uses the directory in both cases. - set_dir_from "$object" - set_base_from "$object" - if test "$libtool" = yes; then - tmpdepfile1=$dir$base.u - tmpdepfile2=$base.u - tmpdepfile3=$dir.libs/$base.u - "$@" -Wc,-M - else - tmpdepfile1=$dir$base.u - tmpdepfile2=$dir$base.u - tmpdepfile3=$dir$base.u - "$@" -M - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - do - test -f "$tmpdepfile" && break - done - aix_post_process_depfile - ;; - -tcc) - # tcc (Tiny C Compiler) understand '-MD -MF file' since version 0.9.26 - # FIXME: That version still under development at the moment of writing. - # Make that this statement remains true also for stable, released - # versions. - # It will wrap lines (doesn't matter whether long or short) with a - # trailing '\', as in: - # - # foo.o : \ - # foo.c \ - # foo.h \ - # - # It will put a trailing '\' even on the last line, and will use leading - # spaces rather than leading tabs (at least since its commit 0394caf7 - # "Emit spaces for -MD"). - "$@" -MD -MF "$tmpdepfile" - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - # Each non-empty line is of the form 'foo.o : \' or ' dep.h \'. - # We have to change lines of the first kind to '$object: \'. - sed -e "s|.*:|$object :|" < "$tmpdepfile" > "$depfile" - # And for each line of the second kind, we have to emit a 'dep.h:' - # dummy dependency, to avoid the deleted-header problem. - sed -n -e 's|^ *\(.*\) *\\$|\1:|p' < "$tmpdepfile" >> "$depfile" - rm -f "$tmpdepfile" - ;; - -## The order of this option in the case statement is important, since the -## shell code in configure will try each of these formats in the order -## listed in this file. A plain '-MD' option would be understood by many -## compilers, so we must ensure this comes after the gcc and icc options. -pgcc) - # Portland's C compiler understands '-MD'. - # Will always output deps to 'file.d' where file is the root name of the - # source file under compilation, even if file resides in a subdirectory. - # The object file name does not affect the name of the '.d' file. - # pgcc 10.2 will output - # foo.o: sub/foo.c sub/foo.h - # and will wrap long lines using '\' : - # foo.o: sub/foo.c ... \ - # sub/foo.h ... \ - # ... - set_dir_from "$object" - # Use the source, not the object, to determine the base name, since - # that's sadly what pgcc will do too. - set_base_from "$source" - tmpdepfile=$base.d - - # For projects that build the same source file twice into different object - # files, the pgcc approach of using the *source* file root name can cause - # problems in parallel builds. Use a locking strategy to avoid stomping on - # the same $tmpdepfile. - lockdir=$base.d-lock - trap " - echo '$0: caught signal, cleaning up...' >&2 - rmdir '$lockdir' - exit 1 - " 1 2 13 15 - numtries=100 - i=$numtries - while test $i -gt 0; do - # mkdir is a portable test-and-set. - if mkdir "$lockdir" 2>/dev/null; then - # This process acquired the lock. - "$@" -MD - stat=$? - # Release the lock. - rmdir "$lockdir" - break - else - # If the lock is being held by a different process, wait - # until the winning process is done or we timeout. - while test -d "$lockdir" && test $i -gt 0; do - sleep 1 - i=`expr $i - 1` - done - fi - i=`expr $i - 1` - done - trap - 1 2 13 15 - if test $i -le 0; then - echo "$0: failed to acquire lock after $numtries attempts" >&2 - echo "$0: check lockdir '$lockdir'" >&2 - exit 1 - fi - - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - # Each line is of the form `foo.o: dependent.h', - # or `foo.o: dep1.h dep2.h \', or ` dep3.h dep4.h \'. - # Do two passes, one to just change these to - # `$object: dependent.h' and one to simply `dependent.h:'. - sed "s,^[^:]*:,$object :," < "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process this invocation - # correctly. Breaking it into two sed invocations is a workaround. - sed 's,^[^:]*: \(.*\)$,\1,;s/^\\$//;/^$/d;/:$/d' < "$tmpdepfile" \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -hp2) - # The "hp" stanza above does not work with aCC (C++) and HP's ia64 - # compilers, which have integrated preprocessors. The correct option - # to use with these is +Maked; it writes dependencies to a file named - # 'foo.d', which lands next to the object file, wherever that - # happens to be. - # Much of this is similar to the tru64 case; see comments there. - set_dir_from "$object" - set_base_from "$object" - if test "$libtool" = yes; then - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir.libs/$base.d - "$@" -Wc,+Maked - else - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir$base.d - "$@" +Maked - fi - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" - do - test -f "$tmpdepfile" && break - done - if test -f "$tmpdepfile"; then - sed -e "s,^.*\.[$lower]*:,$object:," "$tmpdepfile" > "$depfile" - # Add 'dependent.h:' lines. - sed -ne '2,${ - s/^ *// - s/ \\*$// - s/$/:/ - p - }' "$tmpdepfile" >> "$depfile" - else - make_dummy_depfile - fi - rm -f "$tmpdepfile" "$tmpdepfile2" - ;; - -tru64) - # The Tru64 compiler uses -MD to generate dependencies as a side - # effect. 'cc -MD -o foo.o ...' puts the dependencies into 'foo.o.d'. - # At least on Alpha/Redhat 6.1, Compaq CCC V6.2-504 seems to put - # dependencies in 'foo.d' instead, so we check for that too. - # Subdirectories are respected. - set_dir_from "$object" - set_base_from "$object" - - if test "$libtool" = yes; then - # Libtool generates 2 separate objects for the 2 libraries. These - # two compilations output dependencies in $dir.libs/$base.o.d and - # in $dir$base.o.d. We have to check for both files, because - # one of the two compilations can be disabled. We should prefer - # $dir$base.o.d over $dir.libs/$base.o.d because the latter is - # automatically cleaned when .libs/ is deleted, while ignoring - # the former would cause a distcleancheck panic. - tmpdepfile1=$dir$base.o.d # libtool 1.5 - tmpdepfile2=$dir.libs/$base.o.d # Likewise. - tmpdepfile3=$dir.libs/$base.d # Compaq CCC V6.2-504 - "$@" -Wc,-MD - else - tmpdepfile1=$dir$base.d - tmpdepfile2=$dir$base.d - tmpdepfile3=$dir$base.d - "$@" -MD - fi - - stat=$? - if test $stat -ne 0; then - rm -f "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - exit $stat - fi - - for tmpdepfile in "$tmpdepfile1" "$tmpdepfile2" "$tmpdepfile3" - do - test -f "$tmpdepfile" && break - done - # Same post-processing that is required for AIX mode. - aix_post_process_depfile - ;; - -msvc7) - if test "$libtool" = yes; then - showIncludes=-Wc,-showIncludes - else - showIncludes=-showIncludes - fi - "$@" $showIncludes > "$tmpdepfile" - stat=$? - grep -v '^Note: including file: ' "$tmpdepfile" - if test $stat -ne 0; then - rm -f "$tmpdepfile" - exit $stat - fi - rm -f "$depfile" - echo "$object : \\" > "$depfile" - # The first sed program below extracts the file names and escapes - # backslashes for cygpath. The second sed program outputs the file - # name when reading, but also accumulates all include files in the - # hold buffer in order to output them again at the end. This only - # works with sed implementations that can handle large buffers. - sed < "$tmpdepfile" -n ' -/^Note: including file: *\(.*\)/ { - s//\1/ - s/\\/\\\\/g - p -}' | $cygpath_u | sort -u | sed -n ' -s/ /\\ /g -s/\(.*\)/'"$tab"'\1 \\/p -s/.\(.*\) \\/\1:/ -H -$ { - s/.*/'"$tab"'/ - G - p -}' >> "$depfile" - echo >> "$depfile" # make sure the fragment doesn't end with a backslash - rm -f "$tmpdepfile" - ;; - -msvc7msys) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -#nosideeffect) - # This comment above is used by automake to tell side-effect - # dependency tracking mechanisms from slower ones. - -dashmstdout) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout, regardless of -o. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - # Remove '-o $object'. - IFS=" " - for arg - do - case $arg in - -o) - shift - ;; - $object) - shift - ;; - *) - set fnord "$@" "$arg" - shift # fnord - shift # $arg - ;; - esac - done - - test -z "$dashmflag" && dashmflag=-M - # Require at least two characters before searching for ':' - # in the target name. This is to cope with DOS-style filenames: - # a dependency such as 'c:/foo/bar' could be seen as target 'c' otherwise. - "$@" $dashmflag | - sed "s|^[$tab ]*[^:$tab ][^:][^:]*:[$tab ]*|$object: |" > "$tmpdepfile" - rm -f "$depfile" - cat < "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process this sed invocation - # correctly. Breaking it into two sed invocations is a workaround. - tr ' ' "$nl" < "$tmpdepfile" \ - | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -dashXmstdout) - # This case only exists to satisfy depend.m4. It is never actually - # run, as this mode is specially recognized in the preamble. - exit 1 - ;; - -makedepend) - "$@" || exit $? - # Remove any Libtool call - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - # X makedepend - shift - cleared=no eat=no - for arg - do - case $cleared in - no) - set ""; shift - cleared=yes ;; - esac - if test $eat = yes; then - eat=no - continue - fi - case "$arg" in - -D*|-I*) - set fnord "$@" "$arg"; shift ;; - # Strip any option that makedepend may not understand. Remove - # the object too, otherwise makedepend will parse it as a source file. - -arch) - eat=yes ;; - -*|$object) - ;; - *) - set fnord "$@" "$arg"; shift ;; - esac - done - obj_suffix=`echo "$object" | sed 's/^.*\././'` - touch "$tmpdepfile" - ${MAKEDEPEND-makedepend} -o"$obj_suffix" -f"$tmpdepfile" "$@" - rm -f "$depfile" - # makedepend may prepend the VPATH from the source file name to the object. - # No need to regex-escape $object, excess matching of '.' is harmless. - sed "s|^.*\($object *:\)|\1|" "$tmpdepfile" > "$depfile" - # Some versions of the HPUX 10.20 sed can't process the last invocation - # correctly. Breaking it into two sed invocations is a workaround. - sed '1,2d' "$tmpdepfile" \ - | tr ' ' "$nl" \ - | sed -e 's/^\\$//' -e '/^$/d' -e '/:$/d' \ - | sed -e 's/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" "$tmpdepfile".bak - ;; - -cpp) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - # Remove '-o $object'. - IFS=" " - for arg - do - case $arg in - -o) - shift - ;; - $object) - shift - ;; - *) - set fnord "$@" "$arg" - shift # fnord - shift # $arg - ;; - esac - done - - "$@" -E \ - | sed -n -e '/^# [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ - -e '/^#line [0-9][0-9]* "\([^"]*\)".*/ s:: \1 \\:p' \ - | sed '$ s: \\$::' > "$tmpdepfile" - rm -f "$depfile" - echo "$object : \\" > "$depfile" - cat < "$tmpdepfile" >> "$depfile" - sed < "$tmpdepfile" '/^$/d;s/^ //;s/ \\$//;s/$/ :/' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -msvisualcpp) - # Important note: in order to support this mode, a compiler *must* - # always write the preprocessed file to stdout. - "$@" || exit $? - - # Remove the call to Libtool. - if test "$libtool" = yes; then - while test "X$1" != 'X--mode=compile'; do - shift - done - shift - fi - - IFS=" " - for arg - do - case "$arg" in - -o) - shift - ;; - $object) - shift - ;; - "-Gm"|"/Gm"|"-Gi"|"/Gi"|"-ZI"|"/ZI") - set fnord "$@" - shift - shift - ;; - *) - set fnord "$@" "$arg" - shift - shift - ;; - esac - done - "$@" -E 2>/dev/null | - sed -n '/^#line [0-9][0-9]* "\([^"]*\)"/ s::\1:p' | $cygpath_u | sort -u > "$tmpdepfile" - rm -f "$depfile" - echo "$object : \\" > "$depfile" - sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::'"$tab"'\1 \\:p' >> "$depfile" - echo "$tab" >> "$depfile" - sed < "$tmpdepfile" -n -e 's% %\\ %g' -e '/^\(.*\)$/ s::\1\::p' >> "$depfile" - rm -f "$tmpdepfile" - ;; - -msvcmsys) - # This case exists only to let depend.m4 do its work. It works by - # looking at the text of this script. This case will never be run, - # since it is checked for above. - exit 1 - ;; - -none) - exec "$@" - ;; - -*) - echo "Unknown depmode $depmode" 1>&2 - exit 1 - ;; -esac - -exit 0 - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC0" -# time-stamp-end: "; # UTC" -# End: diff --git a/config/install-sh b/config/install-sh deleted file mode 100755 index 59990a10..00000000 --- a/config/install-sh +++ /dev/null @@ -1,508 +0,0 @@ -#!/bin/sh -# install - install a program, script, or datafile - -scriptversion=2014-09-12.12; # UTC - -# This originates from X11R5 (mit/util/scripts/install.sh), which was -# later released in X11R6 (xc/config/util/install.sh) with the -# following copyright and license. -# -# Copyright (C) 1994 X Consortium -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to -# deal in the Software without restriction, including without limitation the -# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -# sell copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN -# AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNEC- -# TION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the name of the X Consortium shall not -# be used in advertising or otherwise to promote the sale, use or other deal- -# ings in this Software without prior written authorization from the X Consor- -# tium. -# -# -# FSF changes to this file are in the public domain. -# -# Calling this script install-sh is preferred over install.sh, to prevent -# 'make' implicit rules from creating a file called install from it -# when there is no Makefile. -# -# This script is compatible with the BSD install script, but was written -# from scratch. - -tab=' ' -nl=' -' -IFS=" $tab$nl" - -# Set DOITPROG to "echo" to test this script. - -doit=${DOITPROG-} -doit_exec=${doit:-exec} - -# Put in absolute file names if you don't have them in your path; -# or use environment vars. - -chgrpprog=${CHGRPPROG-chgrp} -chmodprog=${CHMODPROG-chmod} -chownprog=${CHOWNPROG-chown} -cmpprog=${CMPPROG-cmp} -cpprog=${CPPROG-cp} -mkdirprog=${MKDIRPROG-mkdir} -mvprog=${MVPROG-mv} -rmprog=${RMPROG-rm} -stripprog=${STRIPPROG-strip} - -posix_mkdir= - -# Desired mode of installed file. -mode=0755 - -chgrpcmd= -chmodcmd=$chmodprog -chowncmd= -mvcmd=$mvprog -rmcmd="$rmprog -f" -stripcmd= - -src= -dst= -dir_arg= -dst_arg= - -copy_on_change=false -is_target_a_directory=possibly - -usage="\ -Usage: $0 [OPTION]... [-T] SRCFILE DSTFILE - or: $0 [OPTION]... SRCFILES... DIRECTORY - or: $0 [OPTION]... -t DIRECTORY SRCFILES... - or: $0 [OPTION]... -d DIRECTORIES... - -In the 1st form, copy SRCFILE to DSTFILE. -In the 2nd and 3rd, copy all SRCFILES to DIRECTORY. -In the 4th, create DIRECTORIES. - -Options: - --help display this help and exit. - --version display version info and exit. - - -c (ignored) - -C install only if different (preserve the last data modification time) - -d create directories instead of installing files. - -g GROUP $chgrpprog installed files to GROUP. - -m MODE $chmodprog installed files to MODE. - -o USER $chownprog installed files to USER. - -s $stripprog installed files. - -t DIRECTORY install into DIRECTORY. - -T report an error if DSTFILE is a directory. - -Environment variables override the default commands: - CHGRPPROG CHMODPROG CHOWNPROG CMPPROG CPPROG MKDIRPROG MVPROG - RMPROG STRIPPROG -" - -while test $# -ne 0; do - case $1 in - -c) ;; - - -C) copy_on_change=true;; - - -d) dir_arg=true;; - - -g) chgrpcmd="$chgrpprog $2" - shift;; - - --help) echo "$usage"; exit $?;; - - -m) mode=$2 - case $mode in - *' '* | *"$tab"* | *"$nl"* | *'*'* | *'?'* | *'['*) - echo "$0: invalid mode: $mode" >&2 - exit 1;; - esac - shift;; - - -o) chowncmd="$chownprog $2" - shift;; - - -s) stripcmd=$stripprog;; - - -t) - is_target_a_directory=always - dst_arg=$2 - # Protect names problematic for 'test' and other utilities. - case $dst_arg in - -* | [=\(\)!]) dst_arg=./$dst_arg;; - esac - shift;; - - -T) is_target_a_directory=never;; - - --version) echo "$0 $scriptversion"; exit $?;; - - --) shift - break;; - - -*) echo "$0: invalid option: $1" >&2 - exit 1;; - - *) break;; - esac - shift -done - -# We allow the use of options -d and -T together, by making -d -# take the precedence; this is for compatibility with GNU install. - -if test -n "$dir_arg"; then - if test -n "$dst_arg"; then - echo "$0: target directory not allowed when installing a directory." >&2 - exit 1 - fi -fi - -if test $# -ne 0 && test -z "$dir_arg$dst_arg"; then - # When -d is used, all remaining arguments are directories to create. - # When -t is used, the destination is already specified. - # Otherwise, the last argument is the destination. Remove it from $@. - for arg - do - if test -n "$dst_arg"; then - # $@ is not empty: it contains at least $arg. - set fnord "$@" "$dst_arg" - shift # fnord - fi - shift # arg - dst_arg=$arg - # Protect names problematic for 'test' and other utilities. - case $dst_arg in - -* | [=\(\)!]) dst_arg=./$dst_arg;; - esac - done -fi - -if test $# -eq 0; then - if test -z "$dir_arg"; then - echo "$0: no input file specified." >&2 - exit 1 - fi - # It's OK to call 'install-sh -d' without argument. - # This can happen when creating conditional directories. - exit 0 -fi - -if test -z "$dir_arg"; then - if test $# -gt 1 || test "$is_target_a_directory" = always; then - if test ! -d "$dst_arg"; then - echo "$0: $dst_arg: Is not a directory." >&2 - exit 1 - fi - fi -fi - -if test -z "$dir_arg"; then - do_exit='(exit $ret); exit $ret' - trap "ret=129; $do_exit" 1 - trap "ret=130; $do_exit" 2 - trap "ret=141; $do_exit" 13 - trap "ret=143; $do_exit" 15 - - # Set umask so as not to create temps with too-generous modes. - # However, 'strip' requires both read and write access to temps. - case $mode in - # Optimize common cases. - *644) cp_umask=133;; - *755) cp_umask=22;; - - *[0-7]) - if test -z "$stripcmd"; then - u_plus_rw= - else - u_plus_rw='% 200' - fi - cp_umask=`expr '(' 777 - $mode % 1000 ')' $u_plus_rw`;; - *) - if test -z "$stripcmd"; then - u_plus_rw= - else - u_plus_rw=,u+rw - fi - cp_umask=$mode$u_plus_rw;; - esac -fi - -for src -do - # Protect names problematic for 'test' and other utilities. - case $src in - -* | [=\(\)!]) src=./$src;; - esac - - if test -n "$dir_arg"; then - dst=$src - dstdir=$dst - test -d "$dstdir" - dstdir_status=$? - else - - # Waiting for this to be detected by the "$cpprog $src $dsttmp" command - # might cause directories to be created, which would be especially bad - # if $src (and thus $dsttmp) contains '*'. - if test ! -f "$src" && test ! -d "$src"; then - echo "$0: $src does not exist." >&2 - exit 1 - fi - - if test -z "$dst_arg"; then - echo "$0: no destination specified." >&2 - exit 1 - fi - dst=$dst_arg - - # If destination is a directory, append the input filename; won't work - # if double slashes aren't ignored. - if test -d "$dst"; then - if test "$is_target_a_directory" = never; then - echo "$0: $dst_arg: Is a directory" >&2 - exit 1 - fi - dstdir=$dst - dst=$dstdir/`basename "$src"` - dstdir_status=0 - else - dstdir=`dirname "$dst"` - test -d "$dstdir" - dstdir_status=$? - fi - fi - - obsolete_mkdir_used=false - - if test $dstdir_status != 0; then - case $posix_mkdir in - '') - # Create intermediate dirs using mode 755 as modified by the umask. - # This is like FreeBSD 'install' as of 1997-10-28. - umask=`umask` - case $stripcmd.$umask in - # Optimize common cases. - *[2367][2367]) mkdir_umask=$umask;; - .*0[02][02] | .[02][02] | .[02]) mkdir_umask=22;; - - *[0-7]) - mkdir_umask=`expr $umask + 22 \ - - $umask % 100 % 40 + $umask % 20 \ - - $umask % 10 % 4 + $umask % 2 - `;; - *) mkdir_umask=$umask,go-w;; - esac - - # With -d, create the new directory with the user-specified mode. - # Otherwise, rely on $mkdir_umask. - if test -n "$dir_arg"; then - mkdir_mode=-m$mode - else - mkdir_mode= - fi - - posix_mkdir=false - case $umask in - *[123567][0-7][0-7]) - # POSIX mkdir -p sets u+wx bits regardless of umask, which - # is incompatible with FreeBSD 'install' when (umask & 300) != 0. - ;; - *) - # $RANDOM is not portable (e.g. dash); use it when possible to - # lower collision chance - tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ - trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 - - # As "mkdir -p" follows symlinks and we work in /tmp possibly; so - # create the $tmpdir first (and fail if unsuccessful) to make sure - # that nobody tries to guess the $tmpdir name. - if (umask $mkdir_umask && - $mkdirprog $mkdir_mode "$tmpdir" && - exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 - then - if test -z "$dir_arg" || { - # Check for POSIX incompatibilities with -m. - # HP-UX 11.23 and IRIX 6.5 mkdir -m -p sets group- or - # other-writable bit of parent directory when it shouldn't. - # FreeBSD 6.1 mkdir -m -p sets mode of existing directory. - test_tmpdir="$tmpdir/a" - ls_ld_tmpdir=`ls -ld "$test_tmpdir"` - case $ls_ld_tmpdir in - d????-?r-*) different_mode=700;; - d????-?--*) different_mode=755;; - *) false;; - esac && - $mkdirprog -m$different_mode -p -- "$test_tmpdir" && { - ls_ld_tmpdir_1=`ls -ld "$test_tmpdir"` - test "$ls_ld_tmpdir" = "$ls_ld_tmpdir_1" - } - } - then posix_mkdir=: - fi - rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" - else - # Remove any dirs left behind by ancient mkdir implementations. - rmdir ./$mkdir_mode ./-p ./-- "$tmpdir" 2>/dev/null - fi - trap '' 0;; - esac;; - esac - - if - $posix_mkdir && ( - umask $mkdir_umask && - $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir" - ) - then : - else - - # The umask is ridiculous, or mkdir does not conform to POSIX, - # or it failed possibly due to a race condition. Create the - # directory the slow way, step by step, checking for races as we go. - - case $dstdir in - /*) prefix='/';; - [-=\(\)!]*) prefix='./';; - *) prefix='';; - esac - - oIFS=$IFS - IFS=/ - set -f - set fnord $dstdir - shift - set +f - IFS=$oIFS - - prefixes= - - for d - do - test X"$d" = X && continue - - prefix=$prefix$d - if test -d "$prefix"; then - prefixes= - else - if $posix_mkdir; then - (umask=$mkdir_umask && - $doit_exec $mkdirprog $mkdir_mode -p -- "$dstdir") && break - # Don't fail if two instances are running concurrently. - test -d "$prefix" || exit 1 - else - case $prefix in - *\'*) qprefix=`echo "$prefix" | sed "s/'/'\\\\\\\\''/g"`;; - *) qprefix=$prefix;; - esac - prefixes="$prefixes '$qprefix'" - fi - fi - prefix=$prefix/ - done - - if test -n "$prefixes"; then - # Don't fail if two instances are running concurrently. - (umask $mkdir_umask && - eval "\$doit_exec \$mkdirprog $prefixes") || - test -d "$dstdir" || exit 1 - obsolete_mkdir_used=true - fi - fi - fi - - if test -n "$dir_arg"; then - { test -z "$chowncmd" || $doit $chowncmd "$dst"; } && - { test -z "$chgrpcmd" || $doit $chgrpcmd "$dst"; } && - { test "$obsolete_mkdir_used$chowncmd$chgrpcmd" = false || - test -z "$chmodcmd" || $doit $chmodcmd $mode "$dst"; } || exit 1 - else - - # Make a couple of temp file names in the proper directory. - dsttmp=$dstdir/_inst.$$_ - rmtmp=$dstdir/_rm.$$_ - - # Trap to clean up those temp files at exit. - trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 - - # Copy the file name to the temp name. - (umask $cp_umask && $doit_exec $cpprog "$src" "$dsttmp") && - - # and set any options; do chmod last to preserve setuid bits. - # - # If any of these fail, we abort the whole thing. If we want to - # ignore errors from any of these, just make sure not to ignore - # errors from the above "$doit $cpprog $src $dsttmp" command. - # - { test -z "$chowncmd" || $doit $chowncmd "$dsttmp"; } && - { test -z "$chgrpcmd" || $doit $chgrpcmd "$dsttmp"; } && - { test -z "$stripcmd" || $doit $stripcmd "$dsttmp"; } && - { test -z "$chmodcmd" || $doit $chmodcmd $mode "$dsttmp"; } && - - # If -C, don't bother to copy if it wouldn't change the file. - if $copy_on_change && - old=`LC_ALL=C ls -dlL "$dst" 2>/dev/null` && - new=`LC_ALL=C ls -dlL "$dsttmp" 2>/dev/null` && - set -f && - set X $old && old=:$2:$4:$5:$6 && - set X $new && new=:$2:$4:$5:$6 && - set +f && - test "$old" = "$new" && - $cmpprog "$dst" "$dsttmp" >/dev/null 2>&1 - then - rm -f "$dsttmp" - else - # Rename the file to the real destination. - $doit $mvcmd -f "$dsttmp" "$dst" 2>/dev/null || - - # The rename failed, perhaps because mv can't rename something else - # to itself, or perhaps because mv is so ancient that it does not - # support -f. - { - # Now remove or move aside any old file at destination location. - # We try this two ways since rm can't unlink itself on some - # systems and the destination file might be busy for other - # reasons. In this case, the final cleanup might fail but the new - # file should still install successfully. - { - test ! -f "$dst" || - $doit $rmcmd -f "$dst" 2>/dev/null || - { $doit $mvcmd -f "$dst" "$rmtmp" 2>/dev/null && - { $doit $rmcmd -f "$rmtmp" 2>/dev/null; :; } - } || - { echo "$0: cannot unlink or rename $dst" >&2 - (exit 1); exit 1 - } - } && - - # Now rename the file to the real destination. - $doit $mvcmd "$dsttmp" "$dst" - } - fi || exit 1 - - trap '' 0 - fi -done - -# Local variables: -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" -# time-stamp-end: "; # UTC" -# End: diff --git a/config/missing b/config/missing deleted file mode 100755 index f62bbae3..00000000 --- a/config/missing +++ /dev/null @@ -1,215 +0,0 @@ -#! /bin/sh -# Common wrapper for a few potentially missing GNU programs. - -scriptversion=2013-10-28.13; # UTC - -# Copyright (C) 1996-2014 Free Software Foundation, Inc. -# Originally written by Fran,cois Pinard , 1996. - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2, or (at your option) -# any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -if test $# -eq 0; then - echo 1>&2 "Try '$0 --help' for more information" - exit 1 -fi - -case $1 in - - --is-lightweight) - # Used by our autoconf macros to check whether the available missing - # script is modern enough. - exit 0 - ;; - - --run) - # Back-compat with the calling convention used by older automake. - shift - ;; - - -h|--h|--he|--hel|--help) - echo "\ -$0 [OPTION]... PROGRAM [ARGUMENT]... - -Run 'PROGRAM [ARGUMENT]...', returning a proper advice when this fails due -to PROGRAM being missing or too old. - -Options: - -h, --help display this help and exit - -v, --version output version information and exit - -Supported PROGRAM values: - aclocal autoconf autoheader autom4te automake makeinfo - bison yacc flex lex help2man - -Version suffixes to PROGRAM as well as the prefixes 'gnu-', 'gnu', and -'g' are ignored when checking the name. - -Send bug reports to ." - exit $? - ;; - - -v|--v|--ve|--ver|--vers|--versi|--versio|--version) - echo "missing $scriptversion (GNU Automake)" - exit $? - ;; - - -*) - echo 1>&2 "$0: unknown '$1' option" - echo 1>&2 "Try '$0 --help' for more information" - exit 1 - ;; - -esac - -# Run the given program, remember its exit status. -"$@"; st=$? - -# If it succeeded, we are done. -test $st -eq 0 && exit 0 - -# Also exit now if we it failed (or wasn't found), and '--version' was -# passed; such an option is passed most likely to detect whether the -# program is present and works. -case $2 in --version|--help) exit $st;; esac - -# Exit code 63 means version mismatch. This often happens when the user -# tries to use an ancient version of a tool on a file that requires a -# minimum version. -if test $st -eq 63; then - msg="probably too old" -elif test $st -eq 127; then - # Program was missing. - msg="missing on your system" -else - # Program was found and executed, but failed. Give up. - exit $st -fi - -perl_URL=http://www.perl.org/ -flex_URL=http://flex.sourceforge.net/ -gnu_software_URL=http://www.gnu.org/software - -program_details () -{ - case $1 in - aclocal|automake) - echo "The '$1' program is part of the GNU Automake package:" - echo "<$gnu_software_URL/automake>" - echo "It also requires GNU Autoconf, GNU m4 and Perl in order to run:" - echo "<$gnu_software_URL/autoconf>" - echo "<$gnu_software_URL/m4/>" - echo "<$perl_URL>" - ;; - autoconf|autom4te|autoheader) - echo "The '$1' program is part of the GNU Autoconf package:" - echo "<$gnu_software_URL/autoconf/>" - echo "It also requires GNU m4 and Perl in order to run:" - echo "<$gnu_software_URL/m4/>" - echo "<$perl_URL>" - ;; - esac -} - -give_advice () -{ - # Normalize program name to check for. - normalized_program=`echo "$1" | sed ' - s/^gnu-//; t - s/^gnu//; t - s/^g//; t'` - - printf '%s\n' "'$1' is $msg." - - configure_deps="'configure.ac' or m4 files included by 'configure.ac'" - case $normalized_program in - autoconf*) - echo "You should only need it if you modified 'configure.ac'," - echo "or m4 files included by it." - program_details 'autoconf' - ;; - autoheader*) - echo "You should only need it if you modified 'acconfig.h' or" - echo "$configure_deps." - program_details 'autoheader' - ;; - automake*) - echo "You should only need it if you modified 'Makefile.am' or" - echo "$configure_deps." - program_details 'automake' - ;; - aclocal*) - echo "You should only need it if you modified 'acinclude.m4' or" - echo "$configure_deps." - program_details 'aclocal' - ;; - autom4te*) - echo "You might have modified some maintainer files that require" - echo "the 'autom4te' program to be rebuilt." - program_details 'autom4te' - ;; - bison*|yacc*) - echo "You should only need it if you modified a '.y' file." - echo "You may want to install the GNU Bison package:" - echo "<$gnu_software_URL/bison/>" - ;; - lex*|flex*) - echo "You should only need it if you modified a '.l' file." - echo "You may want to install the Fast Lexical Analyzer package:" - echo "<$flex_URL>" - ;; - help2man*) - echo "You should only need it if you modified a dependency" \ - "of a man page." - echo "You may want to install the GNU Help2man package:" - echo "<$gnu_software_URL/help2man/>" - ;; - makeinfo*) - echo "You should only need it if you modified a '.texi' file, or" - echo "any other file indirectly affecting the aspect of the manual." - echo "You might want to install the Texinfo package:" - echo "<$gnu_software_URL/texinfo/>" - echo "The spurious makeinfo call might also be the consequence of" - echo "using a buggy 'make' (AIX, DU, IRIX), in which case you might" - echo "want to install GNU make:" - echo "<$gnu_software_URL/make/>" - ;; - *) - echo "You might have modified some files without having the proper" - echo "tools for further handling them. Check the 'README' file, it" - echo "often tells you about the needed prerequisites for installing" - echo "this package. You may also peek at any GNU archive site, in" - echo "case some other package contains this missing '$1' program." - ;; - esac -} - -give_advice "$1" | sed -e '1s/^/WARNING: /' \ - -e '2,$s/^/ /' >&2 - -# Propagate the correct exit status (expected to be 127 for a program -# not found, 63 for a program that failed due to version mismatch). -exit $st - -# Local variables: -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" -# time-stamp-end: "; # UTC" -# End: From 01fc5be82f73c051b594edf75f969bd52add35a9 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 28 May 2019 09:34:11 -0700 Subject: [PATCH 0027/1992] properly ignore root level config directory --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index af590262..530a179f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ Makefile Makefile.in aclocal.m4 autom4te.cache -./config +/config config.h config.h.in config.log From 7da09a0dd9486753015d0b935928b8b88a3dc396 Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Tue, 28 May 2019 09:36:18 -0700 Subject: [PATCH 0028/1992] added missing corefx config files due to gitignore --- .../com/core/ui/config/BaseConfigItem.java | 19 +++++++ .../com/core/ui/config/BooleanConfigItem.java | 32 +++++++++++ .../com/core/ui/config/ConfigItemUtils.java | 29 ++++++++++ .../com/core/ui/config/DefaultConfigItem.java | 24 ++++++++ .../com/core/ui/config/FileConfigItem.java | 56 +++++++++++++++++++ .../java/com/core/ui/config/IConfigItem.java | 13 +++++ .../com/core/ui/config/SelectConfigItem.java | 29 ++++++++++ 7 files changed, 202 insertions(+) create mode 100644 corefx/src/main/java/com/core/ui/config/BaseConfigItem.java create mode 100644 corefx/src/main/java/com/core/ui/config/BooleanConfigItem.java create mode 100644 corefx/src/main/java/com/core/ui/config/ConfigItemUtils.java create mode 100644 corefx/src/main/java/com/core/ui/config/DefaultConfigItem.java create mode 100644 corefx/src/main/java/com/core/ui/config/FileConfigItem.java create mode 100644 corefx/src/main/java/com/core/ui/config/IConfigItem.java create mode 100644 corefx/src/main/java/com/core/ui/config/SelectConfigItem.java diff --git a/corefx/src/main/java/com/core/ui/config/BaseConfigItem.java b/corefx/src/main/java/com/core/ui/config/BaseConfigItem.java new file mode 100644 index 00000000..99ff43ed --- /dev/null +++ b/corefx/src/main/java/com/core/ui/config/BaseConfigItem.java @@ -0,0 +1,19 @@ +package com.core.ui.config; + +import com.core.data.ConfigOption; +import javafx.scene.control.Label; +import javafx.stage.Stage; +import lombok.Data; + +@Data +public abstract class BaseConfigItem implements IConfigItem { + private final Stage stage; + private final Label label; + private final ConfigOption option; + + public BaseConfigItem(Stage stage, ConfigOption option) { + this.stage = stage; + this.option = option; + this.label = new Label(option.getLabel()); + } +} diff --git a/corefx/src/main/java/com/core/ui/config/BooleanConfigItem.java b/corefx/src/main/java/com/core/ui/config/BooleanConfigItem.java new file mode 100644 index 00000000..482d9b50 --- /dev/null +++ b/corefx/src/main/java/com/core/ui/config/BooleanConfigItem.java @@ -0,0 +1,32 @@ +package com.core.ui.config; + +import com.core.data.ConfigOption; +import com.jfoenix.controls.JFXToggleButton; +import javafx.scene.Node; +import javafx.stage.Stage; + +public class BooleanConfigItem extends BaseConfigItem { + private JFXToggleButton button = new JFXToggleButton(); + + public BooleanConfigItem(Stage stage, ConfigOption option) { + super(stage, option); + button.setMaxWidth(Double.MAX_VALUE); + if ("1".equals(option.getValue())) { + button.setSelected(true); + } + button.selectedProperty().addListener(((observable, oldValue, newValue) -> { + String value; + if (newValue) { + value = "1"; + } else { + value = "0"; + } + getOption().setValue(value); + })); + } + + @Override + public Node getNode() { + return button; + } +} diff --git a/corefx/src/main/java/com/core/ui/config/ConfigItemUtils.java b/corefx/src/main/java/com/core/ui/config/ConfigItemUtils.java new file mode 100644 index 00000000..37a5dd27 --- /dev/null +++ b/corefx/src/main/java/com/core/ui/config/ConfigItemUtils.java @@ -0,0 +1,29 @@ +package com.core.ui.config; + +import com.core.data.ConfigOption; +import com.core.data.ConfigDataType; +import javafx.stage.Stage; + +public final class ConfigItemUtils { + private ConfigItemUtils() { + + } + + public static IConfigItem get(Stage stage, ConfigOption option) { + IConfigItem configItem; + ConfigDataType dataType = ConfigDataType.get(option.getType()); + if (dataType == ConfigDataType.BOOL) { + configItem = new BooleanConfigItem(stage, option); + } else { + if (!option.getSelect().isEmpty()) { + configItem = new SelectConfigItem(stage, option); + } else if (option.getLabel().endsWith(" file")) { + configItem = new FileConfigItem(stage, option); + } else { + configItem = new DefaultConfigItem(stage, option); + } + } + + return configItem; + } +} diff --git a/corefx/src/main/java/com/core/ui/config/DefaultConfigItem.java b/corefx/src/main/java/com/core/ui/config/DefaultConfigItem.java new file mode 100644 index 00000000..674041c8 --- /dev/null +++ b/corefx/src/main/java/com/core/ui/config/DefaultConfigItem.java @@ -0,0 +1,24 @@ +package com.core.ui.config; + +import com.core.data.ConfigOption; +import com.jfoenix.controls.JFXTextField; +import javafx.scene.Node; +import javafx.stage.Stage; + +public class DefaultConfigItem extends BaseConfigItem { + private JFXTextField textField; + + public DefaultConfigItem(Stage stage, ConfigOption option) { + super(stage, option); + textField = new JFXTextField(option.getValue()); + textField.setMaxWidth(Double.MAX_VALUE); + textField.textProperty().addListener(((observable, oldValue, newValue) -> { + getOption().setValue(newValue); + })); + } + + @Override + public Node getNode() { + return textField; + } +} diff --git a/corefx/src/main/java/com/core/ui/config/FileConfigItem.java b/corefx/src/main/java/com/core/ui/config/FileConfigItem.java new file mode 100644 index 00000000..9648c38c --- /dev/null +++ b/corefx/src/main/java/com/core/ui/config/FileConfigItem.java @@ -0,0 +1,56 @@ +package com.core.ui.config; + +import com.core.data.ConfigOption; +import com.jfoenix.controls.JFXButton; +import com.jfoenix.controls.JFXTextField; +import javafx.scene.Node; +import javafx.scene.layout.ColumnConstraints; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.Priority; +import javafx.scene.layout.RowConstraints; +import javafx.stage.FileChooser; +import javafx.stage.Stage; + +import java.io.File; + +public class FileConfigItem extends BaseConfigItem { + private GridPane gridPane; + + public FileConfigItem(Stage stage, ConfigOption option) { + super(stage, option); + gridPane = new GridPane(); + gridPane.setHgap(5); + gridPane.setMaxWidth(Double.MAX_VALUE); + RowConstraints rowConstraints = new RowConstraints(); + rowConstraints.setVgrow(Priority.SOMETIMES); + ColumnConstraints textFieldConstraints = new ColumnConstraints(); + textFieldConstraints.setHgrow(Priority.SOMETIMES); + textFieldConstraints.setPercentWidth(60); + ColumnConstraints buttonConstraints = new ColumnConstraints(); + buttonConstraints.setHgrow(Priority.SOMETIMES); + buttonConstraints.setPercentWidth(40); + gridPane.getColumnConstraints().addAll(textFieldConstraints, buttonConstraints); + + JFXTextField textField = new JFXTextField(); + textField.setMaxWidth(Double.MAX_VALUE); + textField.textProperty().addListener(((observable, oldValue, newValue) -> getOption().setValue(newValue))); + JFXButton button = new JFXButton("Select File"); + button.getStyleClass().add("core-button"); + button.setMaxWidth(Double.MAX_VALUE); + button.setOnAction(event -> { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("Select File"); + fileChooser.setInitialDirectory(new File(System.getProperty("user.home"))); + File file = fileChooser.showOpenDialog(stage); + if (file != null) { + textField.setText(file.getPath()); + } + }); + gridPane.addRow(0, textField, button); + } + + @Override + public Node getNode() { + return gridPane; + } +} diff --git a/corefx/src/main/java/com/core/ui/config/IConfigItem.java b/corefx/src/main/java/com/core/ui/config/IConfigItem.java new file mode 100644 index 00000000..057693ae --- /dev/null +++ b/corefx/src/main/java/com/core/ui/config/IConfigItem.java @@ -0,0 +1,13 @@ +package com.core.ui.config; + +import com.core.data.ConfigOption; +import javafx.scene.Node; +import javafx.scene.control.Label; + +public interface IConfigItem { + Label getLabel(); + + Node getNode(); + + ConfigOption getOption(); +} diff --git a/corefx/src/main/java/com/core/ui/config/SelectConfigItem.java b/corefx/src/main/java/com/core/ui/config/SelectConfigItem.java new file mode 100644 index 00000000..f2dbea22 --- /dev/null +++ b/corefx/src/main/java/com/core/ui/config/SelectConfigItem.java @@ -0,0 +1,29 @@ +package com.core.ui.config; + +import com.core.data.ConfigOption; +import com.jfoenix.controls.JFXComboBox; +import javafx.scene.Node; +import javafx.stage.Stage; + +public class SelectConfigItem extends BaseConfigItem { + private JFXComboBox comboBox = new JFXComboBox<>(); + + public SelectConfigItem(Stage stage, ConfigOption option) { + super(stage, option); + comboBox.setMaxWidth(Double.MAX_VALUE); + comboBox.getItems().addAll(option.getSelect()); + comboBox.getSelectionModel().select(option.getValue()); + comboBox.getSelectionModel().selectedItemProperty().addListener(((observable, oldValue, newValue) -> { + if (newValue == null) { + return; + } + + getOption().setValue(newValue); + })); + } + + @Override + public Node getNode() { + return comboBox; + } +} From 83e883ee130a249c59baf0af1993e0b9ebfbd6b6 Mon Sep 17 00:00:00 2001 From: Kevin Larson Date: Tue, 28 May 2019 10:59:34 -0700 Subject: [PATCH 0029/1992] Removed instances of old nrl mailing list --- configure.ac | 2 +- daemon/setup.py.in | 1 - gui/initgui.tcl | 2 -- man/core-cleanup.1 | 4 ++-- man/core-daemon.1 | 2 +- man/core-gui.1 | 2 +- man/core-xen-cleanup.1 | 4 ++-- man/coresendmsg.1 | 2 +- man/netns.1 | 3 ++- man/vcmd.1 | 2 +- man/vnoded.1 | 2 +- netns/setup.py.in | 1 - ns3/corens3/__init__.py | 4 ++-- ns3/setup.py.in | 1 - 14 files changed, 14 insertions(+), 18 deletions(-) diff --git a/configure.ac b/configure.ac index 79c5ecc0..b84d2be2 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 5.2.1, core-dev@nrl.navy.mil) +AC_INIT(core, 5.2.1) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) diff --git a/daemon/setup.py.in b/daemon/setup.py.in index 89be649e..f61ef09c 100644 --- a/daemon/setup.py.in +++ b/daemon/setup.py.in @@ -56,7 +56,6 @@ setup( description="Python components of CORE", url="https://github.com/coreemu/core", author="Boeing Research & Technology", - author_email="core-dev@nrl.navy.mil", license="BSD", long_description="Python scripts and modules for building virtual emulated networks.", ) diff --git a/gui/initgui.tcl b/gui/initgui.tcl index 4d64cdb2..fd92e443 100644 --- a/gui/initgui.tcl +++ b/gui/initgui.tcl @@ -598,8 +598,6 @@ menu .menubar.help -tearoff 0 "_launchBrowser https://github.com/coreemu/core" .menubar.help add command -label "CORE Documentation (www)" -command \ "_launchBrowser https://coreemu.github.io/core/" -.menubar.help add command -label "Mailing list (www)" -command \ - "_launchBrowser https://publists.nrl.navy.mil/mailman/listinfo/core-users" .menubar.help add command -label "About" -command popupAbout # diff --git a/man/core-cleanup.1 b/man/core-cleanup.1 index 649b5c17..2ef10474 100644 --- a/man/core-cleanup.1 +++ b/man/core-cleanup.1 @@ -23,7 +23,7 @@ remove the core-daemon.log file .BR vcmd(1), .BR vnoded(1) .SH BUGS -Report bugs to -.BI core-dev@nrl.navy.mil. +Report bugs to +.BI https://github.com/coreemu/core/issues diff --git a/man/core-daemon.1 b/man/core-daemon.1 index 75f40def..f6f8a61e 100644 --- a/man/core-daemon.1 +++ b/man/core-daemon.1 @@ -48,5 +48,5 @@ enable debug logging; default = False .BR vnoded(1) .SH BUGS Report bugs to -.BI core-dev@nrl.navy.mil. +.BI https://github.com/coreemu/core/issues diff --git a/man/core-gui.1 b/man/core-gui.1 index 84202e6e..f646bb5f 100644 --- a/man/core-gui.1 +++ b/man/core-gui.1 @@ -40,5 +40,5 @@ With no parameters, starts the GUI in edit mode with a blank canvas. .BR vnoded(1) .SH BUGS Report bugs to -.BI core-dev@nrl.navy.mil. +.BI https://github.com/coreemu/core/issues diff --git a/man/core-xen-cleanup.1 b/man/core-xen-cleanup.1 index 44bf8a18..92e1f3be 100644 --- a/man/core-xen-cleanup.1 +++ b/man/core-xen-cleanup.1 @@ -22,7 +22,7 @@ also kill the Python daemon .BR vnoded(1) .SH BUGS Warning! This script will remove logical volumes that match the name "/dev/vg*/c*-n*-" on all volume groups. Use with care. -Report bugs to -.BI core-dev@nrl.navy.mil. +Report bugs to +.BI https://github.com/coreemu/core/issues diff --git a/man/coresendmsg.1 b/man/coresendmsg.1 index 4c69fcd4..772e3cda 100644 --- a/man/coresendmsg.1 +++ b/man/coresendmsg.1 @@ -82,4 +82,4 @@ coresendmsg \-H .BR vnoded(1) .SH BUGS Report bugs to -.BI core-dev@nrl.navy.mil. +.BI https://github.com/coreemu/core/issues diff --git a/man/netns.1 b/man/netns.1 index ad509bab..b5308b22 100644 --- a/man/netns.1 +++ b/man/netns.1 @@ -26,5 +26,6 @@ wait for command to complete (useful for interactive commands) .BR vnoded(1) .SH BUGS Report bugs to -.BI core-dev@nrl.navy.mil. +.BI https://github.com/coreemu/core/issues + diff --git a/man/vcmd.1 b/man/vcmd.1 index 1909264b..5544f4f9 100644 --- a/man/vcmd.1 +++ b/man/vcmd.1 @@ -38,5 +38,5 @@ control channel name (e.g. '/tmp/pycore.45647/n3') .BR vnoded(1), .SH BUGS Report bugs to -.BI core-dev@nrl.navy.mil. +.BI https://github.com/coreemu/core/issues diff --git a/man/vnoded.1 b/man/vnoded.1 index b452bcc8..34b39e37 100644 --- a/man/vnoded.1 +++ b/man/vnoded.1 @@ -40,5 +40,5 @@ establish the specified for receiving control commands .BR vcmd(1), .SH BUGS Report bugs to -.BI core-dev@nrl.navy.mil. +.BI https://github.com/coreemu/core/issues diff --git a/netns/setup.py.in b/netns/setup.py.in index 0866fe5c..c8ee1386 100644 --- a/netns/setup.py.in +++ b/netns/setup.py.in @@ -38,7 +38,6 @@ setup( ], url="https://github.com/coreemu/core", author="Boeing Research & Technology", - author_email="core-dev@nrl.navy.mil", license="BSD", long_description="Extension modules and utilities to support virtual nodes using Linux network namespaces", ) diff --git a/ns3/corens3/__init__.py b/ns3/corens3/__init__.py index dc998d05..9bc2e7eb 100644 --- a/ns3/corens3/__init__.py +++ b/ns3/corens3/__init__.py @@ -4,6 +4,6 @@ corens3 Python package containing CORE components for use with the ns-3 simulator. -See http://www.nrl.navy.mil/itd/ncs/products/core and -http://code.google.com/p/coreemu/ for more information on CORE. +See http://code.google.com/p/coreemu/ +for more information on CORE. """ diff --git a/ns3/setup.py.in b/ns3/setup.py.in index 8335d43f..c83c3127 100644 --- a/ns3/setup.py.in +++ b/ns3/setup.py.in @@ -14,7 +14,6 @@ setup( description="Python ns-3 components of CORE", url="https://github.com/coreemu/core", author="Boeing Research & Technology", - author_email="core-dev@nrl.navy.mil", license="GPLv2", long_description="Python scripts and modules for building virtual simulated networks." ) From e74b833db2a51eb3f5d25c087888d66c1d143d40 Mon Sep 17 00:00:00 2001 From: Kevin Larson Date: Tue, 28 May 2019 11:16:34 -0700 Subject: [PATCH 0030/1992] Updated print in configure.ac to be python2/3 compatible --- configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index b84d2be2..17637088 100644 --- a/configure.ac +++ b/configure.ac @@ -146,7 +146,7 @@ if test "x$enable_daemon" = "xyes"; then CFLAGS_save=$CFLAGS CPPFLAGS_save=$CPPFLAGS if test "x$PYTHON_INCLUDE_DIR" = "x"; then - PYTHON_INCLUDE_DIR=`$PYTHON -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()"` + PYTHON_INCLUDE_DIR=`$PYTHON -c "import distutils.sysconfig; print(distutils.sysconfig.get_python_inc())"` fi CFLAGS="-I$PYTHON_INCLUDE_DIR" CPPFLAGS="-I$PYTHON_INCLUDE_DIR" From 09a681dab610867b21fef870fabc52e9aea02768 Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Tue, 28 May 2019 12:10:24 -0700 Subject: [PATCH 0031/1992] catch grpc runtime exceptions and throw as expected ioexceptions --- .../com/core/client/grpc/CoreGrpcClient.java | 606 +++++++++++------- 1 file changed, 383 insertions(+), 223 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index c58e61f1..3136a8d6 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -7,6 +7,7 @@ import com.core.data.*; import com.google.protobuf.ByteString; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; +import io.grpc.StatusRuntimeException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -189,105 +190,121 @@ public class CoreGrpcClient implements ICoreClient { @Override public SessionOverview createSession() throws IOException { CoreProto.CreateSessionRequest request = CoreProto.CreateSessionRequest.newBuilder().build(); - CoreProto.CreateSessionResponse response = blockingStub.createSession(request); - SessionOverview overview = new SessionOverview(); - overview.setId(response.getId()); - overview.setState(response.getStateValue()); - overview.setNodes(0); - return overview; + try { + CoreProto.CreateSessionResponse response = blockingStub.createSession(request); + SessionOverview overview = new SessionOverview(); + overview.setId(response.getId()); + overview.setState(response.getStateValue()); + overview.setNodes(0); + return overview; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override public boolean deleteSession(Integer sessionId) throws IOException { CoreProto.DeleteSessionRequest request = CoreProto.DeleteSessionRequest.newBuilder().setId(sessionId).build(); - return blockingStub.deleteSession(request).getResult(); + try { + return blockingStub.deleteSession(request).getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override public List getSessions() throws IOException { CoreProto.GetSessionsRequest request = CoreProto.GetSessionsRequest.newBuilder().build(); - CoreProto.GetSessionsResponse response = blockingStub.getSessions(request); - List sessions = new ArrayList<>(); - for (CoreProto.SessionSummary summary : response.getSessionsList()) { - SessionOverview overview = new SessionOverview(); - overview.setId(summary.getId()); - overview.setNodes(summary.getNodes()); - overview.setState(summary.getStateValue()); - sessions.add(overview); + try { + CoreProto.GetSessionsResponse response = blockingStub.getSessions(request); + List sessions = new ArrayList<>(); + for (CoreProto.SessionSummary summary : response.getSessionsList()) { + SessionOverview overview = new SessionOverview(); + overview.setId(summary.getId()); + overview.setNodes(summary.getNodes()); + overview.setState(summary.getStateValue()); + sessions.add(overview); + } + return sessions; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } - return sessions; } @Override public Session getSession(Integer sessionId) throws IOException { logger.info("getting session: {}", sessionId); CoreProto.GetSessionRequest request = CoreProto.GetSessionRequest.newBuilder().setId(sessionId).build(); - CoreProto.GetSessionResponse response = blockingStub.getSession(request); - Session session = new Session(); - for (CoreProto.Node protoNode : response.getSession().getNodesList()) { - if (CoreProto.NodeType.NODE_PEER_TO_PEER == protoNode.getType()) { - continue; + try { + CoreProto.GetSessionResponse response = blockingStub.getSession(request); + Session session = new Session(); + for (CoreProto.Node protoNode : response.getSession().getNodesList()) { + if (CoreProto.NodeType.NODE_PEER_TO_PEER == protoNode.getType()) { + continue; + } + + logger.info("adding node: {}", protoNode); + CoreNode node = new CoreNode(protoNode.getId()); + node.setName(protoNode.getName()); + node.setEmane(protoNode.getEmane()); + node.setIcon(protoNode.getIcon()); + node.setModel(protoNode.getModel()); + node.setServices(new HashSet<>(protoNode.getServicesList())); + node.getPosition().setX((double) protoNode.getPosition().getX()); + node.getPosition().setY((double) protoNode.getPosition().getY()); + node.setType(protoNode.getTypeValue()); + session.getNodes().add(node); } + for (CoreProto.Link linkProto : response.getSession().getLinksList()) { + logger.info("adding link: {} - {}", linkProto.getNodeOne(), linkProto.getNodeTwo()); + CoreLink link = new CoreLink(); + link.setNodeOne(linkProto.getNodeOne()); + link.setNodeTwo(linkProto.getNodeTwo()); + CoreProto.Interface interfaceOneProto = linkProto.getInterfaceOne(); + CoreInterface interfaceOne = new CoreInterface(); + interfaceOne.setId(interfaceOneProto.getId()); + interfaceOne.setName(interfaceOneProto.getName()); + interfaceOne.setMac(interfaceOneProto.getMac()); + interfaceOne.setIp4(interfaceOneProto.getIp4()); + interfaceOne.setIp4Mask(interfaceOneProto.getIp4Mask()); + interfaceOne.setIp6(interfaceOneProto.getIp6()); + interfaceOne.setIp6Mask(Integer.toString(interfaceOneProto.getIp6Mask())); + link.setInterfaceOne(interfaceOne); - logger.info("adding node: {}", protoNode); - CoreNode node = new CoreNode(protoNode.getId()); - node.setName(protoNode.getName()); - node.setEmane(protoNode.getEmane()); - node.setIcon(protoNode.getIcon()); - node.setModel(protoNode.getModel()); - node.setServices(new HashSet<>(protoNode.getServicesList())); - node.getPosition().setX((double) protoNode.getPosition().getX()); - node.getPosition().setY((double) protoNode.getPosition().getY()); - node.setType(protoNode.getTypeValue()); - session.getNodes().add(node); - } - for (CoreProto.Link linkProto : response.getSession().getLinksList()) { - logger.info("adding link: {} - {}", linkProto.getNodeOne(), linkProto.getNodeTwo()); - CoreLink link = new CoreLink(); - link.setNodeOne(linkProto.getNodeOne()); - link.setNodeTwo(linkProto.getNodeTwo()); - CoreProto.Interface interfaceOneProto = linkProto.getInterfaceOne(); - CoreInterface interfaceOne = new CoreInterface(); - interfaceOne.setId(interfaceOneProto.getId()); - interfaceOne.setName(interfaceOneProto.getName()); - interfaceOne.setMac(interfaceOneProto.getMac()); - interfaceOne.setIp4(interfaceOneProto.getIp4()); - interfaceOne.setIp4Mask(interfaceOneProto.getIp4Mask()); - interfaceOne.setIp6(interfaceOneProto.getIp6()); - interfaceOne.setIp6Mask(Integer.toString(interfaceOneProto.getIp6Mask())); - link.setInterfaceOne(interfaceOne); + CoreProto.Interface interfaceTwoProto = linkProto.getInterfaceTwo(); + CoreInterface interfaceTwo = new CoreInterface(); + interfaceTwo.setId(interfaceTwoProto.getId()); + interfaceTwo.setName(interfaceTwoProto.getName()); + interfaceTwo.setMac(interfaceTwoProto.getMac()); + interfaceTwo.setIp4(interfaceTwoProto.getIp4()); + interfaceTwo.setIp4Mask(interfaceTwoProto.getIp4Mask()); + interfaceTwo.setIp6(interfaceTwoProto.getIp6()); + interfaceTwo.setIp6Mask(Integer.toString(interfaceTwoProto.getIp6Mask())); + link.setInterfaceTwo(interfaceTwo); - CoreProto.Interface interfaceTwoProto = linkProto.getInterfaceTwo(); - CoreInterface interfaceTwo = new CoreInterface(); - interfaceTwo.setId(interfaceTwoProto.getId()); - interfaceTwo.setName(interfaceTwoProto.getName()); - interfaceTwo.setMac(interfaceTwoProto.getMac()); - interfaceTwo.setIp4(interfaceTwoProto.getIp4()); - interfaceTwo.setIp4Mask(interfaceTwoProto.getIp4Mask()); - interfaceTwo.setIp6(interfaceTwoProto.getIp6()); - interfaceTwo.setIp6Mask(Integer.toString(interfaceTwoProto.getIp6Mask())); - link.setInterfaceTwo(interfaceTwo); - - CoreLinkOptions options = new CoreLinkOptions(); - CoreProto.LinkOptions protoOptions = linkProto.getOptions(); - options.setBandwidth((double) protoOptions.getBandwidth()); - options.setDelay((double) protoOptions.getDelay()); - options.setDup((double) protoOptions.getDup()); - options.setJitter((double) protoOptions.getJitter()); - options.setPer((double) protoOptions.getPer()); - options.setBurst((double) protoOptions.getBurst()); - if (!protoOptions.getKey().isEmpty()) { - options.setKey(Integer.parseInt(protoOptions.getKey())); + CoreLinkOptions options = new CoreLinkOptions(); + CoreProto.LinkOptions protoOptions = linkProto.getOptions(); + options.setBandwidth((double) protoOptions.getBandwidth()); + options.setDelay((double) protoOptions.getDelay()); + options.setDup((double) protoOptions.getDup()); + options.setJitter((double) protoOptions.getJitter()); + options.setPer((double) protoOptions.getPer()); + options.setBurst((double) protoOptions.getBurst()); + if (!protoOptions.getKey().isEmpty()) { + options.setKey(Integer.parseInt(protoOptions.getKey())); + } + options.setMburst((double) protoOptions.getMburst()); + options.setMer((double) protoOptions.getMer()); + options.setOpaque(protoOptions.getOpaque()); + options.setUnidirectional(protoOptions.getUnidirectional() ? 1 : 0); + link.setOptions(options); + session.getLinks().add(link); } - options.setMburst((double) protoOptions.getMburst()); - options.setMer((double) protoOptions.getMer()); - options.setOpaque(protoOptions.getOpaque()); - options.setUnidirectional(protoOptions.getUnidirectional() ? 1 : 0); - link.setOptions(options); - session.getLinks().add(link); + session.setState(response.getSession().getStateValue()); + return session; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } - session.setState(response.getSession().getStateValue()); - return session; } @Override @@ -340,31 +357,43 @@ public class CoreGrpcClient implements ICoreClient { .setId(sessionId) .setStateValue(state.getValue()) .build(); - CoreProto.SetSessionStateResponse response = blockingStub.setSessionState(request); - return response.getResult(); + try { + CoreProto.SetSessionStateResponse response = blockingStub.setSessionState(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override public Map> getServices() throws IOException { CoreProto.GetServicesRequest request = CoreProto.GetServicesRequest.newBuilder().build(); - CoreProto.GetServicesResponse response = blockingStub.getServices(request); - Map> servicesMap = new HashMap<>(); - for (CoreProto.Service protoService : response.getServicesList()) { - List services = servicesMap.computeIfAbsent(protoService.getGroup(), x -> new ArrayList<>()); - services.add(protoService.getName()); + try { + CoreProto.GetServicesResponse response = blockingStub.getServices(request); + Map> servicesMap = new HashMap<>(); + for (CoreProto.Service protoService : response.getServicesList()) { + List services = servicesMap.computeIfAbsent(protoService.getGroup(), x -> new ArrayList<>()); + services.add(protoService.getName()); + } + return servicesMap; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } - return servicesMap; } @Override public Map> getDefaultServices() throws IOException { CoreProto.GetServiceDefaultsRequest request = CoreProto.GetServiceDefaultsRequest.newBuilder().build(); - CoreProto.GetServiceDefaultsResponse response = blockingStub.getServiceDefaults(request); - Map> servicesMap = new HashMap<>(); - for (CoreProto.ServiceDefaults serviceDefaults : response.getDefaultsList()) { - servicesMap.put(serviceDefaults.getNodeType(), serviceDefaults.getServicesList()); + try { + CoreProto.GetServiceDefaultsResponse response = blockingStub.getServiceDefaults(request); + Map> servicesMap = new HashMap<>(); + for (CoreProto.ServiceDefaults serviceDefaults : response.getDefaultsList()) { + servicesMap.put(serviceDefaults.getNodeType(), serviceDefaults.getServicesList()); + } + return servicesMap; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } - return servicesMap; } @Override @@ -379,32 +408,39 @@ public class CoreGrpcClient implements ICoreClient { .build(); allDefaults.add(serviceDefaults); } - CoreProto.SetServiceDefaultsRequest request = CoreProto.SetServiceDefaultsRequest.newBuilder() .setSession(sessionId) .addAllDefaults(allDefaults) .build(); - CoreProto.SetServiceDefaultsResponse response = blockingStub.setServiceDefaults(request); - return response.getResult(); + try { + CoreProto.SetServiceDefaultsResponse response = blockingStub.setServiceDefaults(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override public CoreService getService(CoreNode node, String serviceName) throws IOException { CoreProto.GetNodeServiceRequest request = CoreProto.GetNodeServiceRequest.newBuilder().build(); - CoreProto.GetNodeServiceResponse response = blockingStub.getNodeService(request); - CoreProto.NodeServiceData nodeServiceData = response.getService(); - CoreService service = new CoreService(); - service.setShutdown(nodeServiceData.getShutdownList()); - service.setStartup(nodeServiceData.getStartupList()); - service.setValidate(nodeServiceData.getValidateList()); - service.setConfigs(nodeServiceData.getConfigsList()); - service.setDependencies(nodeServiceData.getDependenciesList()); - service.setDirs(nodeServiceData.getDirsList()); - service.setExecutables(nodeServiceData.getExecutablesList()); - service.setMeta(nodeServiceData.getMeta()); - service.setValidationMode(nodeServiceData.getValidationMode().name()); - service.setValidationTimer(Integer.toString(nodeServiceData.getValidationTimer())); - return service; + try { + CoreProto.GetNodeServiceResponse response = blockingStub.getNodeService(request); + CoreProto.NodeServiceData nodeServiceData = response.getService(); + CoreService service = new CoreService(); + service.setShutdown(nodeServiceData.getShutdownList()); + service.setStartup(nodeServiceData.getStartupList()); + service.setValidate(nodeServiceData.getValidateList()); + service.setConfigs(nodeServiceData.getConfigsList()); + service.setDependencies(nodeServiceData.getDependenciesList()); + service.setDirs(nodeServiceData.getDirsList()); + service.setExecutables(nodeServiceData.getExecutablesList()); + service.setMeta(nodeServiceData.getMeta()); + service.setValidationMode(nodeServiceData.getValidationMode().name()); + service.setValidationTimer(Integer.toString(nodeServiceData.getValidationTimer())); + return service; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -417,7 +453,11 @@ public class CoreGrpcClient implements ICoreClient { request.getShutdownList().addAll(service.getShutdown()); request.getValidateList().addAll(service.getValidate()); request.getStartupList().addAll(service.getStartup()); - return blockingStub.setNodeService(request).getResult(); + try { + return blockingStub.setNodeService(request).getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -427,8 +467,12 @@ public class CoreGrpcClient implements ICoreClient { .setId(node.getId()) .setService(serviceName) .build(); - CoreProto.GetNodeServiceFileResponse response = blockingStub.getNodeServiceFile(request); - return response.getData().toStringUtf8(); + try { + CoreProto.GetNodeServiceFileResponse response = blockingStub.getNodeServiceFile(request); + return response.getData().toStringUtf8(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -439,7 +483,11 @@ public class CoreGrpcClient implements ICoreClient { .setService(serviceName) .setAction(CoreProto.ServiceAction.SERVICE_START) .build(); - return blockingStub.serviceAction(request).getResult(); + try { + return blockingStub.serviceAction(request).getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -450,7 +498,11 @@ public class CoreGrpcClient implements ICoreClient { .setService(serviceName) .setAction(CoreProto.ServiceAction.SERVICE_STOP) .build(); - return blockingStub.serviceAction(request).getResult(); + try { + return blockingStub.serviceAction(request).getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -461,7 +513,11 @@ public class CoreGrpcClient implements ICoreClient { .setService(serviceName) .setAction(CoreProto.ServiceAction.SERVICE_RESTART) .build(); - return blockingStub.serviceAction(request).getResult(); + try { + return blockingStub.serviceAction(request).getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -472,7 +528,11 @@ public class CoreGrpcClient implements ICoreClient { .setService(serviceName) .setAction(CoreProto.ServiceAction.SERVICE_VALIDATE) .build(); - return blockingStub.serviceAction(request).getResult(); + try { + return blockingStub.serviceAction(request).getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -484,8 +544,12 @@ public class CoreGrpcClient implements ICoreClient { .setFile(serviceFile.getName()) .setData(ByteString.copyFromUtf8(serviceFile.getData())) .build(); - CoreProto.SetNodeServiceFileResponse response = blockingStub.setNodeServiceFile(request); - return response.getResult(); + try { + CoreProto.SetNodeServiceFileResponse response = blockingStub.setNodeServiceFile(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -493,8 +557,12 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.GetEmaneConfigRequest request = CoreProto.GetEmaneConfigRequest.newBuilder() .setSession(sessionId) .build(); - CoreProto.GetEmaneConfigResponse response = blockingStub.getEmaneConfig(request); - return protoToConfigGroups(response.getGroupsList()); + try { + CoreProto.GetEmaneConfigResponse response = blockingStub.getEmaneConfig(request); + return protoToConfigGroups(response.getGroupsList()); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -502,8 +570,12 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.GetEmaneModelsRequest request = CoreProto.GetEmaneModelsRequest.newBuilder() .setSession(sessionId) .build(); - CoreProto.GetEmaneModelsResponse response = blockingStub.getEmaneModels(request); - return new ArrayList<>(response.getModelsList()); + try { + CoreProto.GetEmaneModelsResponse response = blockingStub.getEmaneModels(request); + return new ArrayList<>(response.getModelsList()); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -513,8 +585,12 @@ public class CoreGrpcClient implements ICoreClient { .setSession(sessionId) .putAllConfig(config) .build(); - CoreProto.SetEmaneConfigResponse response = blockingStub.setEmaneConfig(request); - return response.getResult(); + try { + CoreProto.SetEmaneConfigResponse response = blockingStub.setEmaneConfig(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -524,8 +600,12 @@ public class CoreGrpcClient implements ICoreClient { .setId(id) .setModel(model) .build(); - CoreProto.GetEmaneModelConfigResponse response = blockingStub.getEmaneModelConfig(request); - return protoToConfigGroups(response.getGroupsList()); + try { + CoreProto.GetEmaneModelConfigResponse response = blockingStub.getEmaneModelConfig(request); + return protoToConfigGroups(response.getGroupsList()); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -537,8 +617,12 @@ public class CoreGrpcClient implements ICoreClient { .setModel(model) .putAllConfig(config) .build(); - CoreProto.SetEmaneModelConfigResponse response = blockingStub.setEmaneModelConfig(request); - return response.getResult(); + try { + CoreProto.SetEmaneModelConfigResponse response = blockingStub.setEmaneModelConfig(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -551,9 +635,13 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.SaveXmlRequest request = CoreProto.SaveXmlRequest.newBuilder() .setSession(sessionId) .build(); - CoreProto.SaveXmlResponse response = blockingStub.saveXml(request); - try (PrintWriter writer = new PrintWriter(file)) { - writer.print(response.getData().toStringUtf8()); + try { + CoreProto.SaveXmlResponse response = blockingStub.saveXml(request); + try (PrintWriter writer = new PrintWriter(file)) { + writer.print(response.getData().toStringUtf8()); + } + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } } @@ -563,10 +651,14 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.OpenXmlRequest request = CoreProto.OpenXmlRequest.newBuilder() .setData(data) .build(); - CoreProto.OpenXmlResponse response = blockingStub.openXml(request); - SessionOverview sessionOverview = new SessionOverview(); - sessionOverview.setId(response.getSession()); - return sessionOverview; + try { + CoreProto.OpenXmlResponse response = blockingStub.openXml(request); + SessionOverview sessionOverview = new SessionOverview(); + sessionOverview.setId(response.getSession()); + return sessionOverview; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -574,8 +666,12 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.GetSessionOptionsRequest request = CoreProto.GetSessionOptionsRequest.newBuilder() .setId(sessionId) .build(); - CoreProto.GetSessionOptionsResponse response = blockingStub.getSessionOptions(request); - return protoToConfigGroups(response.getGroupsList()); + try { + CoreProto.GetSessionOptionsResponse response = blockingStub.getSessionOptions(request); + return protoToConfigGroups(response.getGroupsList()); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -585,8 +681,12 @@ public class CoreGrpcClient implements ICoreClient { .setId(sessionId) .putAllConfig(config) .build(); - CoreProto.SetSessionOptionsResponse response = blockingStub.setSessionOptions(request); - return response.getResult(); + try { + CoreProto.SetSessionOptionsResponse response = blockingStub.setSessionOptions(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -596,8 +696,12 @@ public class CoreGrpcClient implements ICoreClient { .setSession(sessionId) .setNode(protoNode) .build(); - blockingStub.addNode(request); - return true; + try { + blockingStub.addNode(request); + return true; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -617,16 +721,24 @@ public class CoreGrpcClient implements ICoreClient { .setId(node.getId()) .setPosition(position) .build(); - CoreProto.EditNodeResponse response = blockingStub.editNode(request); - return response.getResult(); + try { + CoreProto.EditNodeResponse response = blockingStub.editNode(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override public boolean deleteNode(CoreNode node) throws IOException { CoreProto.DeleteNodeRequest request = CoreProto.DeleteNodeRequest.newBuilder() .build(); - CoreProto.DeleteNodeResponse response = blockingStub.deleteNode(request); - return response.getResult(); + try { + CoreProto.DeleteNodeResponse response = blockingStub.deleteNode(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -653,8 +765,12 @@ public class CoreGrpcClient implements ICoreClient { .setSession(sessionId) .setLink(protoLink) .build(); - CoreProto.AddLinkResponse response = blockingStub.addLink(request); - return response.getResult(); + try { + CoreProto.AddLinkResponse response = blockingStub.addLink(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -678,8 +794,12 @@ public class CoreGrpcClient implements ICoreClient { builder.setOptions(protoOptions); } CoreProto.EditLinkRequest request = builder.build(); - CoreProto.EditLinkResponse response = blockingStub.editLink(request); - return response.getResult(); + try { + CoreProto.EditLinkResponse response = blockingStub.editLink(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -692,23 +812,31 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.AddHookRequest request = CoreProto.AddHookRequest.newBuilder() .setHook(hookProto) .build(); - CoreProto.AddHookResponse response = blockingStub.addHook(request); - return response.getResult(); + try { + CoreProto.AddHookResponse response = blockingStub.addHook(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override public List getHooks() throws IOException { CoreProto.GetHooksRequest request = CoreProto.GetHooksRequest.newBuilder().setSession(sessionId).build(); - CoreProto.GetHooksResponse response = blockingStub.getHooks(request); - List hooks = new ArrayList<>(); - for (CoreProto.Hook protoHook : response.getHooksList()) { - Hook hook = new Hook(); - hook.setFile(protoHook.getFile()); - hook.setData(protoHook.getData().toStringUtf8()); - hook.setState(protoHook.getStateValue()); - hooks.add(hook); + try { + CoreProto.GetHooksResponse response = blockingStub.getHooks(request); + List hooks = new ArrayList<>(); + for (CoreProto.Hook protoHook : response.getHooksList()) { + Hook hook = new Hook(); + hook.setFile(protoHook.getFile()); + hook.setData(protoHook.getData().toStringUtf8()); + hook.setState(protoHook.getStateValue()); + hooks.add(hook); + } + return hooks; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } - return hooks; } @Override @@ -717,20 +845,24 @@ public class CoreGrpcClient implements ICoreClient { .setSession(sessionId) .setId(node.getId()) .build(); - CoreProto.GetWlanConfigResponse response = blockingStub.getWlanConfig(request); - Map protoConfig = new HashMap<>(); - for (CoreProto.ConfigGroup group : response.getGroupsList()) { - for (CoreProto.ConfigOption option : group.getOptionsList()) { - protoConfig.put(option.getName(), option.getValue()); + try { + CoreProto.GetWlanConfigResponse response = blockingStub.getWlanConfig(request); + Map protoConfig = new HashMap<>(); + for (CoreProto.ConfigGroup group : response.getGroupsList()) { + for (CoreProto.ConfigOption option : group.getOptionsList()) { + protoConfig.put(option.getName(), option.getValue()); + } } + WlanConfig config = new WlanConfig(); + config.setBandwidth(protoConfig.get("bandwidth")); + config.setDelay(protoConfig.get("delay")); + config.setError(protoConfig.get("error")); + config.setJitter(protoConfig.get("jitter")); + config.setRange(protoConfig.get("range")); + return config; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } - WlanConfig config = new WlanConfig(); - config.setBandwidth(protoConfig.get("bandwidth")); - config.setDelay(protoConfig.get("delay")); - config.setError(protoConfig.get("error")); - config.setJitter(protoConfig.get("jitter")); - config.setRange(protoConfig.get("range")); - return config; } @Override @@ -746,8 +878,12 @@ public class CoreGrpcClient implements ICoreClient { .setId(node.getId()) .putAllConfig(protoConfig) .build(); - CoreProto.SetWlanConfigResponse response = blockingStub.setWlanConfig(request); - return response.getResult(); + try { + CoreProto.SetWlanConfigResponse response = blockingStub.setWlanConfig(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -760,30 +896,34 @@ public class CoreGrpcClient implements ICoreClient { public Map getMobilityConfigs() throws IOException { CoreProto.GetMobilityConfigsRequest request = CoreProto.GetMobilityConfigsRequest.newBuilder() .setSession(sessionId).build(); - CoreProto.GetMobilityConfigsResponse response = blockingStub.getMobilityConfigs(request); + try { + CoreProto.GetMobilityConfigsResponse response = blockingStub.getMobilityConfigs(request); - Map mobilityConfigs = new HashMap<>(); - for (Integer nodeId : response.getConfigsMap().keySet()) { - CoreProto.GetMobilityConfigsResponse.MobilityConfig protoMobilityConfig = response.getConfigsMap() - .get(nodeId); - MobilityConfig mobilityConfig = new MobilityConfig(); - Map protoConfig = new HashMap<>(); - for (CoreProto.ConfigGroup group : protoMobilityConfig.getGroupsList()) { - for (CoreProto.ConfigOption option : group.getOptionsList()) { - protoConfig.put(option.getName(), option.getValue()); + Map mobilityConfigs = new HashMap<>(); + for (Integer nodeId : response.getConfigsMap().keySet()) { + CoreProto.GetMobilityConfigsResponse.MobilityConfig protoMobilityConfig = response.getConfigsMap() + .get(nodeId); + MobilityConfig mobilityConfig = new MobilityConfig(); + Map protoConfig = new HashMap<>(); + for (CoreProto.ConfigGroup group : protoMobilityConfig.getGroupsList()) { + for (CoreProto.ConfigOption option : group.getOptionsList()) { + protoConfig.put(option.getName(), option.getValue()); + } } + mobilityConfig.setFile(protoConfig.get("file")); + mobilityConfig.setRefresh(Integer.parseInt(protoConfig.get("refresh_ms"))); + mobilityConfig.setAutostart(protoConfig.get("autostart")); + mobilityConfig.setLoop(protoConfig.get("loop")); + mobilityConfig.setPauseScript(protoConfig.get("script_pause")); + mobilityConfig.setStartScript(protoConfig.get("script_start")); + mobilityConfig.setStopScript(protoConfig.get("script_stop")); + mobilityConfig.setMap(protoConfig.get("map")); + mobilityConfigs.put(nodeId, mobilityConfig); } - mobilityConfig.setFile(protoConfig.get("file")); - mobilityConfig.setRefresh(Integer.parseInt(protoConfig.get("refresh_ms"))); - mobilityConfig.setAutostart(protoConfig.get("autostart")); - mobilityConfig.setLoop(protoConfig.get("loop")); - mobilityConfig.setPauseScript(protoConfig.get("script_pause")); - mobilityConfig.setStartScript(protoConfig.get("script_start")); - mobilityConfig.setStopScript(protoConfig.get("script_stop")); - mobilityConfig.setMap(protoConfig.get("map")); - mobilityConfigs.put(nodeId, mobilityConfig); + return mobilityConfigs; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } - return mobilityConfigs; } @Override @@ -804,8 +944,12 @@ public class CoreGrpcClient implements ICoreClient { .setId(node.getId()) .putAllConfig(protoConfig) .build(); - CoreProto.SetMobilityConfigResponse response = blockingStub.setMobilityConfig(request); - return response.getResult(); + try { + CoreProto.SetMobilityConfigResponse response = blockingStub.setMobilityConfig(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -814,23 +958,27 @@ public class CoreGrpcClient implements ICoreClient { .setSession(sessionId) .setId(node.getId()) .build(); - CoreProto.GetMobilityConfigResponse response = blockingStub.getMobilityConfig(request); - Map protoConfig = new HashMap<>(); - for (CoreProto.ConfigGroup group : response.getGroupsList()) { - for (CoreProto.ConfigOption option : group.getOptionsList()) { - protoConfig.put(option.getName(), option.getValue()); + try { + CoreProto.GetMobilityConfigResponse response = blockingStub.getMobilityConfig(request); + Map protoConfig = new HashMap<>(); + for (CoreProto.ConfigGroup group : response.getGroupsList()) { + for (CoreProto.ConfigOption option : group.getOptionsList()) { + protoConfig.put(option.getName(), option.getValue()); + } } + MobilityConfig config = new MobilityConfig(); + config.setFile(protoConfig.get("file")); + config.setRefresh(Integer.parseInt(protoConfig.get("refresh_ms"))); + config.setAutostart(protoConfig.get("autostart")); + config.setLoop(protoConfig.get("loop")); + config.setPauseScript(protoConfig.get("script_pause")); + config.setStartScript(protoConfig.get("script_start")); + config.setStopScript(protoConfig.get("script_stop")); + config.setMap(protoConfig.get("map")); + return config; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); } - MobilityConfig config = new MobilityConfig(); - config.setFile(protoConfig.get("file")); - config.setRefresh(Integer.parseInt(protoConfig.get("refresh_ms"))); - config.setAutostart(protoConfig.get("autostart")); - config.setLoop(protoConfig.get("loop")); - config.setPauseScript(protoConfig.get("script_pause")); - config.setStartScript(protoConfig.get("script_start")); - config.setStopScript(protoConfig.get("script_stop")); - config.setMap(protoConfig.get("map")); - return config; } @Override @@ -840,8 +988,12 @@ public class CoreGrpcClient implements ICoreClient { .setId(node.getId()) .setAction(CoreProto.MobilityAction.valueOf(action)) .build(); - CoreProto.MobilityActionResponse response = blockingStub.mobilityAction(request); - return response.getResult(); + try { + CoreProto.MobilityActionResponse response = blockingStub.mobilityAction(request); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -849,16 +1001,20 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.GetSessionLocationRequest request = CoreProto.GetSessionLocationRequest.newBuilder() .setId(sessionId) .build(); - CoreProto.GetSessionLocationResponse response = blockingStub.getSessionLocation(request); - LocationConfig config = new LocationConfig(); - config.setScale((double) response.getScale()); - config.getPosition().setX((double) response.getPosition().getX()); - config.getPosition().setY((double) response.getPosition().getY()); - config.getPosition().setZ((double) response.getPosition().getZ()); - config.getLocation().setLatitude((double) response.getPosition().getLat()); - config.getLocation().setLongitude((double) response.getPosition().getLon()); - config.getLocation().setAltitude((double) response.getPosition().getAlt()); - return config; + try { + CoreProto.GetSessionLocationResponse response = blockingStub.getSessionLocation(request); + LocationConfig config = new LocationConfig(); + config.setScale((double) response.getScale()); + config.getPosition().setX((double) response.getPosition().getX()); + config.getPosition().setY((double) response.getPosition().getY()); + config.getPosition().setZ((double) response.getPosition().getZ()); + config.getLocation().setLatitude((double) response.getPosition().getLat()); + config.getLocation().setLongitude((double) response.getPosition().getLon()); + config.getLocation().setAltitude((double) response.getPosition().getAlt()); + return config; + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override @@ -887,7 +1043,11 @@ public class CoreGrpcClient implements ICoreClient { if (config.getLocation().getAltitude() != null) { positionBuilder.setAlt(config.getLocation().getAltitude().floatValue()); } - CoreProto.SetSessionLocationResponse response = blockingStub.setSessionLocation(builder.build()); - return response.getResult(); + try { + CoreProto.SetSessionLocationResponse response = blockingStub.setSessionLocation(builder.build()); + return response.getResult(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } } From 3e194af5eed71629279739f2f2861082ab98e3f5 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 28 May 2019 14:31:34 -0700 Subject: [PATCH 0032/1992] updated all grpc variables to use more explicit naming for session/node/interface ids --- daemon/core/grpc/client.py | 325 +++++++++++++++++---------------- daemon/core/grpc/server.py | 201 ++++++++++---------- daemon/examples/grpc/switch.py | 31 ++-- daemon/proto/core.proto | 164 ++++++++--------- daemon/tests/test_grpc.py | 12 +- 5 files changed, 370 insertions(+), 363 deletions(-) diff --git a/daemon/core/grpc/client.py b/daemon/core/grpc/client.py index a6863e37..7764c12e 100644 --- a/daemon/core/grpc/client.py +++ b/daemon/core/grpc/client.py @@ -149,27 +149,27 @@ class CoreGrpcClient(object): self.stub = None self.channel = None - def create_session(self, _id=None): + def create_session(self, session_id=None): """ Create a session. - :param int _id: id for session, default is None and one will be created for you + :param int session_id: id for session, default is None and one will be created for you :return: response with created session id :rtype: core_pb2.CreateSessionResponse """ - request = core_pb2.CreateSessionRequest(id=_id) + request = core_pb2.CreateSessionRequest(session_id=session_id) return self.stub.CreateSession(request) - def delete_session(self, _id): + def delete_session(self, session_id): """ Delete a session. - :param int _id: id of session + :param int session_id: id of session :return: response with result of deletion success or failure :rtype: core_pb2.DeleteSessionResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.DeleteSessionRequest(id=_id) + request = core_pb2.DeleteSessionRequest(session_id=session_id) return self.stub.DeleteSession(request) def get_sessions(self): @@ -181,60 +181,60 @@ class CoreGrpcClient(object): """ return self.stub.GetSessions(core_pb2.GetSessionsRequest()) - def get_session(self, _id): + def get_session(self, session_id): """ Retrieve a session. - :param int _id: id of session + :param int session_id: id of session :return: response with sessions state, nodes, and links :rtype: core_pb2.GetSessionResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetSessionRequest(id=_id) + request = core_pb2.GetSessionRequest(session_id=session_id) return self.stub.GetSession(request) - def get_session_options(self, _id): + def get_session_options(self, session_id): """ Retrieve session options. - :param int _id: id of session + :param int session_id: id of session :return: response with a list of configuration groups :rtype: core_pb2.GetSessionOptionsResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetSessionOptionsRequest(id=_id) + request = core_pb2.GetSessionOptionsRequest(session_id=session_id) return self.stub.GetSessionOptions(request) - def set_session_options(self, _id, config): + def set_session_options(self, session_id, config): """ Set options for a session. - :param int _id: id of session + :param int session_id: id of session :param dict[str, str] config: configuration values to set :return: response with result of success or failure :rtype: core_pb2.SetSessionOptionsResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.SetSessionOptionsRequest(id=_id, config=config) + request = core_pb2.SetSessionOptionsRequest(session_id=session_id, config=config) return self.stub.SetSessionOptions(request) - def get_session_location(self, _id): + def get_session_location(self, session_id): """ Get session location. - :param int _id: id of session + :param int session_id: id of session :return: response with session position reference and scale :rtype: core_pb2.GetSessionLocationResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetSessionLocationRequest(id=_id) + request = core_pb2.GetSessionLocationRequest(session_id=session_id) return self.stub.GetSessionLocation(request) - def set_session_location(self, _id, x=None, y=None, z=None, lat=None, lon=None, alt=None, scale=None): + def set_session_location(self, session_id, x=None, y=None, z=None, lat=None, lon=None, alt=None, scale=None): """ Set session location. - :param int _id: id of session + :param int session_id: id of session :param float x: x position :param float y: y position :param float z: z position @@ -247,173 +247,173 @@ class CoreGrpcClient(object): :raises grpc.RpcError: when session doesn't exist """ position = core_pb2.Position(x=x, y=y, z=z, lat=lat, lon=lon, alt=alt) - request = core_pb2.SetSessionLocationRequest(id=_id, position=position, scale=scale) + request = core_pb2.SetSessionLocationRequest(session_id=session_id, position=position, scale=scale) return self.stub.SetSessionLocation(request) - def set_session_state(self, _id, state): + def set_session_state(self, session_id, state): """ Set session state. - :param int _id: id of session + :param int session_id: id of session :param core_pb2.SessionState state: session state to transition to :return: response with result of success or failure :rtype: core_pb2.SetSessionStateResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.SetSessionStateRequest(id=_id, state=state) + request = core_pb2.SetSessionStateRequest(session_id=session_id, state=state) return self.stub.SetSessionState(request) - def node_events(self, _id, handler): + def node_events(self, session_id, handler): """ Listen for session node events. - :param int _id: id of session + :param int session_id: id of session :param handler: handler for every event :return: nothing :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.NodeEventsRequest(id=_id) + request = core_pb2.NodeEventsRequest(session_id=session_id) stream = self.stub.NodeEvents(request) start_streamer(stream, handler) - def link_events(self, _id, handler): + def link_events(self, session_id, handler): """ Listen for session link events. - :param int _id: id of session + :param int session_id: id of session :param handler: handler for every event :return: nothing :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.LinkEventsRequest(id=_id) + request = core_pb2.LinkEventsRequest(session_id=session_id) stream = self.stub.LinkEvents(request) start_streamer(stream, handler) - def session_events(self, _id, handler): + def session_events(self, session_id, handler): """ Listen for session events. - :param int _id: id of session + :param int session_id: id of session :param handler: handler for every event :return: nothing :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.SessionEventsRequest(id=_id) + request = core_pb2.SessionEventsRequest(session_id=session_id) stream = self.stub.SessionEvents(request) start_streamer(stream, handler) - def config_events(self, _id, handler): + def config_events(self, session_id, handler): """ Listen for session config events. - :param int _id: id of session + :param int session_id: id of session :param handler: handler for every event :return: nothing :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.ConfigEventsRequest(id=_id) + request = core_pb2.ConfigEventsRequest(session_id=session_id) stream = self.stub.ConfigEvents(request) start_streamer(stream, handler) - def exception_events(self, _id, handler): + def exception_events(self, session_id, handler): """ Listen for session exception events. - :param int _id: id of session + :param int session_id: id of session :param handler: handler for every event :return: nothing :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.ExceptionEventsRequest(id=_id) + request = core_pb2.ExceptionEventsRequest(session_id=session_id) stream = self.stub.ExceptionEvents(request) start_streamer(stream, handler) - def file_events(self, _id, handler): + def file_events(self, session_id, handler): """ Listen for session file events. - :param int _id: id of session + :param int session_id: id of session :param handler: handler for every event :return: nothing :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.FileEventsRequest(id=_id) + request = core_pb2.FileEventsRequest(session_id=session_id) stream = self.stub.FileEvents(request) start_streamer(stream, handler) - def add_node(self, session, node): + def add_node(self, session_id, node): """ Add node to session. - :param int session: session id + :param int session_id: session id :param core_pb2.Node node: node to add :return: response with node id :rtype: core_pb2.AddNodeResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.AddNodeRequest(session=session, node=node) + request = core_pb2.AddNodeRequest(session_id=session_id, node=node) return self.stub.AddNode(request) - def get_node(self, session, _id): + def get_node(self, session_id, node_id): """ Get node details. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :return: response with node details :rtype: core_pb2.GetNodeResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.GetNodeRequest(session=session, id=_id) + request = core_pb2.GetNodeRequest(session_id=session_id, node_id=node_id) return self.stub.GetNode(request) - def edit_node(self, session, _id, position): + def edit_node(self, session_id, node_id, position): """ Edit a node, currently only changes position. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param core_pb2.Position position: position to set node to :return: response with result of success or failure :rtype: core_pb2.EditNodeResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.EditNodeRequest(session=session, id=_id, position=position) + request = core_pb2.EditNodeRequest(session_id=session_id, node_id=node_id, position=position) return self.stub.EditNode(request) - def delete_node(self, session, _id): + def delete_node(self, session_id, node_id): """ Delete node from session. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :return: response with result of success or failure :rtype: core_pb2.DeleteNodeResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.DeleteNodeRequest(session=session, id=_id) + request = core_pb2.DeleteNodeRequest(session_id=session_id, node_id=node_id) return self.stub.DeleteNode(request) - def get_node_links(self, session, _id): + def get_node_links(self, session_id, node_id): """ Get current links for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :return: response with a list of links :rtype: core_pb2.GetNodeLinksResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.GetNodeLinksRequest(session=session, id=_id) + request = core_pb2.GetNodeLinksRequest(session_id=session_id, node_id=node_id) return self.stub.GetNodeLinks(request) - def add_link(self, session, node_one, node_two, interface_one=None, interface_two=None, options=None): + def add_link(self, session_id, node_one_id, node_two_id, interface_one=None, interface_two=None, options=None): """ Add a link between nodes. - :param int session: session id - :param int node_one: node one id - :param int node_two: node two id + :param int session_id: session id + :param int node_one_id: node one id + :param int node_two_id: node two id :param core_pb2.Interface interface_one: node one interface data :param core_pb2.Interface interface_two: node two interface data :param core_pb2.LinkOptions options: options for link (jitter, bandwidth, etc) @@ -422,65 +422,65 @@ class CoreGrpcClient(object): :raises grpc.RpcError: when session or one of the nodes don't exist """ link = core_pb2.Link( - node_one=node_one, node_two=node_two, type=core_pb2.LINK_WIRED, + node_one_id=node_one_id, node_two_id=node_two_id, type=core_pb2.LINK_WIRED, interface_one=interface_one, interface_two=interface_two, options=options) - request = core_pb2.AddLinkRequest(session=session, link=link) + request = core_pb2.AddLinkRequest(session_id=session_id, link=link) return self.stub.AddLink(request) - def edit_link(self, session, node_one, node_two, options, interface_one=None, interface_two=None): + def edit_link(self, session_id, node_one_id, node_two_id, options, interface_one_id=None, interface_two_id=None): """ Edit a link between nodes. - :param int session: session id - :param int node_one: node one id - :param int node_two: node two id + :param int session_id: session id + :param int node_one_id: node one id + :param int node_two_id: node two id :param core_pb2.LinkOptions options: options for link (jitter, bandwidth, etc) - :param int interface_one: node one interface id - :param int interface_two: node two interface id + :param int interface_one_id: node one interface id + :param int interface_two_id: node two interface id :return: response with result of success or failure :rtype: core_pb2.EditLinkResponse :raises grpc.RpcError: when session or one of the nodes don't exist """ request = core_pb2.EditLinkRequest( - session=session, node_one=node_one, node_two=node_two, options=options, - interface_one=interface_one, interface_two=interface_two) + session_id=session_id, node_one_id=node_one_id, node_two_id=node_two_id, options=options, + interface_one_id=interface_one_id, interface_two_id=interface_two_id) return self.stub.EditLink(request) - def delete_link(self, session, node_one, node_two, interface_one=None, interface_two=None): + def delete_link(self, session_id, node_one_id, node_two_id, interface_one_id=None, interface_two_id=None): """ Delete a link between nodes. - :param int session: session id - :param int node_one: node one id - :param int node_two: node two id - :param int interface_one: node one interface id - :param int interface_two: node two interface id + :param int session_id: session id + :param int node_one_id: node one id + :param int node_two_id: node two id + :param int interface_one_id: node one interface id + :param int interface_two_id: node two interface id :return: response with result of success or failure :rtype: core_pb2.DeleteLinkResponse :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.DeleteLinkRequest( - session=session, node_one=node_one, node_two=node_two, - interface_one=interface_one, interface_two=interface_two) + session_id=session_id, node_one_id=node_one_id, node_two_id=node_two_id, + interface_one_id=interface_one_id, interface_two_id=interface_two_id) return self.stub.DeleteLink(request) - def get_hooks(self, session): + def get_hooks(self, session_id): """ Get all hook scripts. - :param int session: session id + :param int session_id: session id :return: response with a list of hooks :rtype: core_pb2.GetHooksResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetHooksRequest(session=session) + request = core_pb2.GetHooksRequest(session_id=session_id) return self.stub.GetHooks(request) - def add_hook(self, session, state, file_name, file_data): + def add_hook(self, session_id, state, file_name, file_data): """ Add hook scripts. - :param int session: session id + :param int session_id: session id :param core_pb2.SessionState state: state to trigger hook :param str file_name: name of file for hook script :param bytes file_data: hook script contents @@ -489,60 +489,60 @@ class CoreGrpcClient(object): :raises grpc.RpcError: when session doesn't exist """ hook = core_pb2.Hook(state=state, file=file_name, data=file_data) - request = core_pb2.AddHookRequest(session=session, hook=hook) + request = core_pb2.AddHookRequest(session_id=session_id, hook=hook) return self.stub.AddHook(request) - def get_mobility_configs(self, session): + def get_mobility_configs(self, session_id): """ Get all mobility configurations. - :param int session: session id + :param int session_id: session id :return: response with a dict of node ids to mobility configurations :rtype: core_pb2.GetMobilityConfigsResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetMobilityConfigsRequest(session=session) + request = core_pb2.GetMobilityConfigsRequest(session_id=session_id) return self.stub.GetMobilityConfigs(request) - def get_mobility_config(self, session, _id): + def get_mobility_config(self, session_id, node_id): """ Get mobility configuration for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :return: response with a list of configuration groups :rtype: core_pb2.GetMobilityConfigResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.GetMobilityConfigRequest(session=session, id=_id) + request = core_pb2.GetMobilityConfigRequest(session_id=session_id, node_id=node_id) return self.stub.GetMobilityConfig(request) - def set_mobility_config(self, session, _id, config): + def set_mobility_config(self, session_id, node_id, config): """ Set mobility configuration for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param dict[str, str] config: mobility configuration :return: response with result of success or failure :rtype: core_pb2.SetMobilityConfigResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.SetMobilityConfigRequest(session=session, id=_id, config=config) + request = core_pb2.SetMobilityConfigRequest(session_id=session_id, node_id=node_id, config=config) return self.stub.SetMobilityConfig(request) - def mobility_action(self, session, _id, action): + def mobility_action(self, session_id, node_id, action): """ Send a mobility action for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param core_pb2.ServiceAction action: action to take :return: response with result of success or failure :rtype: core_pb2.MobilityActionResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.MobilityActionRequest(session=session, id=_id, action=action) + request = core_pb2.MobilityActionRequest(session_id=session_id, node_id=node_id, action=action) return self.stub.MobilityAction(request) def get_services(self): @@ -555,23 +555,23 @@ class CoreGrpcClient(object): request = core_pb2.GetServicesRequest() return self.stub.GetServices(request) - def get_service_defaults(self, session): + def get_service_defaults(self, session_id): """ Get default services for different default node models. - :param int session: session id + :param int session_id: session id :return: response with a dict of node model to a list of services :rtype: core_pb2.GetServiceDefaultsResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetServiceDefaultsRequest(session=session) + request = core_pb2.GetServiceDefaultsRequest(session_id=session_id) return self.stub.GetServiceDefaults(request) - def set_service_defaults(self, session, service_defaults): + def set_service_defaults(self, session_id, service_defaults): """ Set default services for node models. - :param int session: session id + :param int session_id: session id :param dict service_defaults: node models to lists of services :return: response with result of success or failure :rtype: core_pb2.SetServiceDefaultsResponse @@ -582,44 +582,45 @@ class CoreGrpcClient(object): services = service_defaults[node_type] default = core_pb2.ServiceDefaults(node_type=node_type, services=services) defaults.append(default) - request = core_pb2.SetServiceDefaultsRequest(session=session, defaults=defaults) + request = core_pb2.SetServiceDefaultsRequest(session_id=session_id, defaults=defaults) return self.stub.SetServiceDefaults(request) - def get_node_service(self, session, _id, service): + def get_node_service(self, session_id, node_id, service): """ Get service data for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param str service: service name :return: response with node service data :rtype: core_pb2.GetNodeServiceResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.GetNodeServiceRequest(session=session, id=_id, service=service) + request = core_pb2.GetNodeServiceRequest(session_id=session_id, node_id=node_id, service=service) return self.stub.GetNodeService(request) - def get_node_service_file(self, session, _id, service, file_name): + def get_node_service_file(self, session_id, node_id, service, file_name): """ Get a service file for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param str service: service name :param str file_name: file name to get data for :return: response with file data :rtype: core_pb2.GetNodeServiceFileResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.GetNodeServiceFileRequest(session=session, id=_id, service=service, file=file_name) + request = core_pb2.GetNodeServiceFileRequest( + session_id=session_id, node_id=node_id, service=service, file=file_name) return self.stub.GetNodeServiceFile(request) - def set_node_service(self, session, _id, service, startup, validate, shutdown): + def set_node_service(self, session_id, node_id, service, startup, validate, shutdown): """ Set service data for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param str service: service name :param list startup: startup commands :param list validate: validation commands @@ -629,15 +630,16 @@ class CoreGrpcClient(object): :raises grpc.RpcError: when session or node doesn't exist """ request = core_pb2.SetNodeServiceRequest( - session=session, id=_id, service=service, startup=startup, validate=validate, shutdown=shutdown) + session_id=session_id, node_id=node_id, service=service, startup=startup, validate=validate, + shutdown=shutdown) return self.stub.SetNodeService(request) - def set_node_service_file(self, session, _id, service, file_name, data): + def set_node_service_file(self, session_id, node_id, service, file_name, data): """ Set a service file for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param str service: service name :param str file_name: file name to save :param bytes data: data to save for file @@ -646,109 +648,110 @@ class CoreGrpcClient(object): :raises grpc.RpcError: when session or node doesn't exist """ request = core_pb2.SetNodeServiceFileRequest( - session=session, id=_id, service=service, file=file_name, data=data) + session_id=session_id, node_id=node_id, service=service, file=file_name, data=data) return self.stub.SetNodeServiceFile(request) - def service_action(self, session, _id, service, action): + def service_action(self, session_id, node_id, service, action): """ Send an action to a service for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param str service: service name :param core_pb2.ServiceAction action: action for service (start, stop, restart, validate) :return: response with result of success or failure :rtype: core_pb2.ServiceActionResponse :raises grpc.RpcError: when session or node doesn't exist """ - request = core_pb2.ServiceActionRequest(session=session, id=_id, service=service, action=action) + request = core_pb2.ServiceActionRequest(session_id=session_id, node_id=node_id, service=service, action=action) return self.stub.ServiceAction(request) - def get_wlan_config(self, session, _id): + def get_wlan_config(self, session_id, node_id): """ Get wlan configuration for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :return: response with a list of configuration groups :rtype: core_pb2.GetWlanConfigResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetWlanConfigRequest(session=session, id=_id) + request = core_pb2.GetWlanConfigRequest(session_id=session_id, node_id=node_id) return self.stub.GetWlanConfig(request) - def set_wlan_config(self, session, _id, config): + def set_wlan_config(self, session_id, node_id, config): """ Set wlan configuration for a node. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param dict[str, str] config: wlan configuration :return: response with result of success or failure :rtype: core_pb2.SetWlanConfigResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.SetWlanConfigRequest(session=session, id=_id, config=config) + request = core_pb2.SetWlanConfigRequest(session_id=session_id, node_id=node_id, config=config) return self.stub.SetWlanConfig(request) - def get_emane_config(self, session): + def get_emane_config(self, session_id): """ Get session emane configuration. - :param int session: session id + :param int session_id: session id :return: response with a list of configuration groups :rtype: core_pb2.GetEmaneConfigResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetEmaneConfigRequest(session=session) + request = core_pb2.GetEmaneConfigRequest(session_id=session_id) return self.stub.GetEmaneConfig(request) - def set_emane_config(self, session, config): + def set_emane_config(self, session_id, config): """ Set session emane configuration. - :param int session: session id + :param int session_id: session id :param dict[str, str] config: emane configuration :return: response with result of success or failure :rtype: core_pb2.SetEmaneConfigResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.SetEmaneConfigRequest(session=session, config=config) + request = core_pb2.SetEmaneConfigRequest(session_id=session_id, config=config) return self.stub.SetEmaneConfig(request) - def get_emane_models(self, session): + def get_emane_models(self, session_id): """ Get session emane models. - :param int session: session id + :param int session_id: session id :return: response with a list of emane models :rtype: core_pb2.GetEmaneModelsResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetEmaneModelsRequest(session=session) + request = core_pb2.GetEmaneModelsRequest(session_id=session_id) return self.stub.GetEmaneModels(request) - def get_emane_model_config(self, session, _id, model, interface_id=-1): + def get_emane_model_config(self, session_id, node_id, model, interface_id=-1): """ Get emane model configuration for a node or a node's interface. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param str model: emane model name :param int interface_id: node interface id :return: response with a list of configuration groups :rtype: core_pb2.GetEmaneModelConfigResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetEmaneModelConfigRequest(session=session, id=_id, model=model, interface=interface_id) + request = core_pb2.GetEmaneModelConfigRequest( + session_id=session_id, node_id=node_id, model=model, interface=interface_id) return self.stub.GetEmaneModelConfig(request) - def set_emane_model_config(self, session, _id, model, config, interface_id=-1): + def set_emane_model_config(self, session_id, node_id, model, config, interface_id=-1): """ Set emane model configuration for a node or a node's interface. - :param int session: session id - :param int _id: node id + :param int session_id: session id + :param int node_id: node id :param str model: emane model name :param dict[str, str] config: emane model configuration :param int interface_id: node interface id @@ -757,30 +760,30 @@ class CoreGrpcClient(object): :raises grpc.RpcError: when session doesn't exist """ request = core_pb2.SetEmaneModelConfigRequest( - session=session, id=_id, model=model, config=config, interface=interface_id) + session_id=session_id, node_id=node_id, model=model, config=config, interface_id=interface_id) return self.stub.SetEmaneModelConfig(request) - def get_emane_model_configs(self, session): + def get_emane_model_configs(self, session_id): """ Get all emane model configurations for a session. - :param int session: session id + :param int session_id: session id :return: response with a dictionary of node/interface ids to configurations :rtype: core_pb2.GetEmaneModelConfigsResponse :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.GetEmaneModelConfigsRequest(session=session) + request = core_pb2.GetEmaneModelConfigsRequest(session_id=session_id) return self.stub.GetEmaneModelConfigs(request) - def save_xml(self, session, file_path): + def save_xml(self, session_id, file_path): """ Save the current scenario to an XML file. - :param int session: session id + :param int session_id: session id :param str file_path: local path to save scenario XML file to :return: nothing """ - request = core_pb2.SaveXmlRequest(session=session) + request = core_pb2.SaveXmlRequest(session_id=session_id) response = self.stub.SaveXml(request) with open(file_path, "wb") as xml_file: xml_file.write(response.data) diff --git a/daemon/core/grpc/server.py b/daemon/core/grpc/server.py index 45c35fc8..caa3d276 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/grpc/server.py @@ -58,11 +58,11 @@ def get_links(session, node): return links -def get_emane_model_id(_id, interface): - if interface >= 0: - return _id * 1000 + interface +def get_emane_model_id(node_id, interface_id): + if interface_id >= 0: + return node_id * 1000 + interface_id else: - return _id + return node_id def convert_link(session, link_data): @@ -99,7 +99,7 @@ def convert_link(session, link_data): ) return core_pb2.Link( - type=link_data.link_type, node_one=link_data.node1_id, node_two=link_data.node2_id, + type=link_data.link_type, node_one_id=link_data.node1_id, node_two_id=link_data.node2_id, interface_one=interface_one, interface_two=interface_two, options=options ) @@ -135,29 +135,29 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): except KeyboardInterrupt: self.server.stop(None) - def get_session(self, _id, context): - session = self.coreemu.sessions.get(_id) + def get_session(self, session_id, context): + session = self.coreemu.sessions.get(session_id) if not session: - context.abort(grpc.StatusCode.NOT_FOUND, "session {} not found".format(_id)) + context.abort(grpc.StatusCode.NOT_FOUND, "session {} not found".format(session_id)) return session - def get_node(self, session, _id, context): + def get_node(self, session, node_id, context): try: - return session.get_object(_id) + return session.get_object(node_id) except KeyError: - context.abort(grpc.StatusCode.NOT_FOUND, "node {} not found".format(_id)) + context.abort(grpc.StatusCode.NOT_FOUND, "node {} not found".format(node_id)) def CreateSession(self, request, context): logging.debug("create session: %s", request) - session = self.coreemu.create_session(request.id) + session = self.coreemu.create_session(request.session_id) session.set_state(EventTypes.DEFINITION_STATE) session.location.setrefgeo(47.57917, -122.13232, 2.0) session.location.refscale = 150000.0 - return core_pb2.CreateSessionResponse(id=session.id, state=session.state) + return core_pb2.CreateSessionResponse(session_id=session.id, state=session.state) def DeleteSession(self, request, context): logging.debug("delete session: %s", request) - result = self.coreemu.delete_session(request.id) + result = self.coreemu.delete_session(request.session_id) return core_pb2.DeleteSessionResponse(result=result) def GetSessions(self, request, context): @@ -172,7 +172,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetSessionLocation(self, request, context): logging.debug("get session location: %s", request) - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) x, y, z = session.location.refxyz lat, lon, alt = session.location.refgeo position = core_pb2.Position(x=x, y=y, z=z, lat=lat, lon=lon, alt=alt) @@ -180,7 +180,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SetSessionLocation(self, request, context): logging.debug("set session location: %s", request) - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) session.location.refxyz = (request.position.x, request.position.y, request.position.z) session.location.setrefgeo(request.position.lat, request.position.lon, request.position.alt) session.location.refscale = request.scale @@ -188,7 +188,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SetSessionState(self, request, context): logging.debug("set session state: %s", request) - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) try: state = EventTypes(request.state) @@ -213,7 +213,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetSessionOptions(self, request, context): logging.debug("get session options: %s", request) - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) config = session.options.get_configs() defaults = session.options.default_values() defaults.update(config) @@ -222,14 +222,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SetSessionOptions(self, request, context): logging.debug("set session options: %s", request) - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) config = session.options.get_configs() config.update(request.config) return core_pb2.SetSessionOptionsResponse(result=True) def GetSession(self, request, context): logging.debug("get session: %s", request) - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) links = [] nodes = [] @@ -263,7 +263,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): return core_pb2.GetSessionResponse(session=session_proto) def NodeEvents(self, request, context): - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) queue = Queue() session.node_handlers.append(queue.put) @@ -283,7 +283,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): self._cancel_stream(context) def LinkEvents(self, request, context): - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) queue = Queue() session.link_handlers.append(queue.put) @@ -318,7 +318,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): unidirectional=event.unidirectional ) link = core_pb2.Link( - type=event.link_type, node_one=event.node1_id, node_two=event.node2_id, + type=event.link_type, node_one_id=event.node1_id, node_two_id=event.node2_id, interface_one=interface_one, interface_two=interface_two, options=options) link_event = core_pb2.LinkEvent(message_type=event.message_type, link=link) yield link_event @@ -328,7 +328,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): self._cancel_stream(context) def SessionEvents(self, request, context): - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) queue = Queue() session.event_handlers.append(queue.put) @@ -339,12 +339,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): if event_time is not None: event_time = float(event_time) session_event = core_pb2.SessionEvent( - node=event.node, + node_id=event.node, event=event.event_type, name=event.name, data=event.data, time=event_time, - session=session.id + session_id=session.id ) yield session_event except Empty: @@ -353,16 +353,19 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): self._cancel_stream(context) def ConfigEvents(self, request, context): - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) queue = Queue() session.config_handlers.append(queue.put) while self._is_running(context): try: event = queue.get(timeout=1) + session_id = None + if event.session is not None: + session_id = int(event.session) config_event = core_pb2.ConfigEvent( message_type=event.message_type, - node=event.node, + node_id=event.node, object=event.object, type=event.type, captions=event.captions, @@ -370,7 +373,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): data_values=event.data_values, possible_values=event.possible_values, groups=event.groups, - session=event.session, + session_id=session_id, interface=event.interface_number, network_id=event.network_id, opaque=event.opaque, @@ -383,7 +386,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): self._cancel_stream(context) def ExceptionEvents(self, request, context): - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) queue = Queue() session.exception_handlers.append(queue.put) @@ -391,8 +394,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): try: event = queue.get(timeout=1) exception_event = core_pb2.ExceptionEvent( - node=event.node, - session=int(event.session), + node_id=event.node, + session_id=int(event.session), level=event.level.value, source=event.source, date=event.date, @@ -406,7 +409,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): self._cancel_stream(context) def FileEvents(self, request, context): - session = self.get_session(request.id, context) + session = self.get_session(request.session_id, context) queue = Queue() session.file_handlers.append(queue.put) @@ -415,13 +418,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): event = queue.get(timeout=1) file_event = core_pb2.FileEvent( message_type=event.message_type, - node=event.node, + node_id=event.node, name=event.name, mode=event.mode, number=event.number, type=event.type, source=event.source, - session=event.session, + session_id=event.session, data=event.data, compressed_data=event.compressed_data ) @@ -433,7 +436,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def AddNode(self, request, context): logging.debug("add node: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) node_proto = request.node node_id = node_proto.id @@ -457,12 +460,12 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): if emane_model: session.emane.set_model_config(node_id, emane_model) - return core_pb2.AddNodeResponse(id=node.objid) + return core_pb2.AddNodeResponse(node_id=node.objid) def GetNode(self, request, context): logging.debug("get node: %s", request) - session = self.get_session(request.session, context) - node = self.get_node(session, request.id, context) + session = self.get_session(request.session_id, context) + node = self.get_node(session, request.node_id, context) interfaces = [] for interface_id, interface in node._netif.iteritems(): @@ -489,8 +492,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def EditNode(self, request, context): logging.debug("edit node: %s", request) - session = self.get_session(request.session, context) - node_id = request.id + session = self.get_session(request.session_id, context) + node_id = request.node_id node_options = NodeOptions() x = request.position.x y = request.position.y @@ -504,26 +507,26 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def DeleteNode(self, request, context): logging.debug("delete node: %s", request) - session = self.get_session(request.session, context) - result = session.delete_node(request.id) + session = self.get_session(request.session_id, context) + result = session.delete_node(request.node_id) return core_pb2.DeleteNodeResponse(result=result) def GetNodeLinks(self, request, context): logging.debug("get node links: %s", request) - session = self.get_session(request.session, context) - node = self.get_node(session, request.id, context) + session = self.get_session(request.session_id, context) + node = self.get_node(session, request.node_id, context) links = get_links(session, node) return core_pb2.GetNodeLinksResponse(links=links) def AddLink(self, request, context): logging.debug("add link: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) # validate node exist - self.get_node(session, request.link.node_one, context) - self.get_node(session, request.link.node_two, context) - node_one = request.link.node_one - node_two = request.link.node_two + self.get_node(session, request.link.node_one_id, context) + self.get_node(session, request.link.node_two_id, context) + node_one_id = request.link.node_one_id + node_two_id = request.link.node_two_id interface_one = None interface_one_data = request.link.interface_one @@ -587,16 +590,16 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): link_options.key = options_data.key link_options.opaque = options_data.opaque - session.add_link(node_one, node_two, interface_one, interface_two, link_options=link_options) + session.add_link(node_one_id, node_two_id, interface_one, interface_two, link_options=link_options) return core_pb2.AddLinkResponse(result=True) def EditLink(self, request, context): logging.debug("edit link: %s", request) - session = self.get_session(request.session, context) - node_one = request.node_one - node_two = request.node_two - interface_one_id = request.interface_one - interface_two_id = request.interface_two + session = self.get_session(request.session_id, context) + node_one_id = request.node_one_id + node_two_id = request.node_two_id + interface_one_id = request.interface_one_id + interface_two_id = request.interface_two_id options_data = request.options link_options = LinkOptions() link_options.delay = options_data.delay @@ -610,22 +613,22 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): link_options.unidirectional = options_data.unidirectional link_options.key = options_data.key link_options.opaque = options_data.opaque - session.update_link(node_one, node_two, interface_one_id, interface_two_id, link_options) + session.update_link(node_one_id, node_two_id, interface_one_id, interface_two_id, link_options) return core_pb2.EditLinkResponse(result=True) def DeleteLink(self, request, context): logging.debug("delete link: %s", request) - session = self.get_session(request.session, context) - node_one = request.node_one - node_two = request.node_two - interface_one = request.interface_one - interface_two = request.interface_two - session.delete_link(node_one, node_two, interface_one, interface_two) + session = self.get_session(request.session_id, context) + node_one_id = request.node_one_id + node_two_id = request.node_two_id + interface_one_id = request.interface_one_id + interface_two_id = request.interface_two_id + session.delete_link(node_one_id, node_two_id, interface_one_id, interface_two_id) return core_pb2.DeleteLinkResponse(result=True) def GetHooks(self, request, context): logging.debug("get hooks: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) hooks = [] for state, state_hooks in session._hooks.iteritems(): for file_name, file_data in state_hooks: @@ -635,14 +638,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def AddHook(self, request, context): logging.debug("add hook: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) hook = request.hook session.add_hook(hook.state, hook.file, None, hook.data) return core_pb2.AddHookResponse(result=True) def GetMobilityConfigs(self, request, context): logging.debug("get mobility configs: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) response = core_pb2.GetMobilityConfigsResponse() for node_id, model_config in session.mobility.node_configurations.iteritems(): if node_id == -1: @@ -657,21 +660,21 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetMobilityConfig(self, request, context): logging.debug("get mobility config: %s", request) - session = self.get_session(request.session, context) - config = session.mobility.get_model_config(request.id, Ns2ScriptedMobility.name) + session = self.get_session(request.session_id, context) + config = session.mobility.get_model_config(request.node_id, Ns2ScriptedMobility.name) groups = get_config_groups(config, Ns2ScriptedMobility) return core_pb2.GetMobilityConfigResponse(groups=groups) def SetMobilityConfig(self, request, context): logging.debug("set mobility config: %s", request) - session = self.get_session(request.session, context) - session.mobility.set_model_config(request.id, Ns2ScriptedMobility.name, request.config) + session = self.get_session(request.session_id, context) + session.mobility.set_model_config(request.node_id, Ns2ScriptedMobility.name, request.config) return core_pb2.SetMobilityConfigResponse(result=True) def MobilityAction(self, request, context): logging.debug("mobility action: %s", request) - session = self.get_session(request.session, context) - node = self.get_node(session, request.id, context) + session = self.get_session(request.session_id, context) + node = self.get_node(session, request.node_id, context) result = True if request.action == core_pb2.MOBILITY_START: node.mobility.start() @@ -693,7 +696,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetServiceDefaults(self, request, context): logging.debug("get service defaults: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) all_service_defaults = [] for node_type in session.services.default_services: services = session.services.default_services[node_type] @@ -703,7 +706,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SetServiceDefaults(self, request, context): logging.debug("set service defaults: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) session.services.default_services.clear() for service_defaults in request.defaults: session.services.default_services[service_defaults.node_type] = service_defaults.services @@ -711,8 +714,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetNodeService(self, request, context): logging.debug("get node service: %s", request) - session = self.get_session(request.session, context) - service = session.services.get_service(request.id, request.service, default_service=True) + session = self.get_session(request.session_id, context) + service = session.services.get_service(request.node_id, request.service, default_service=True) service_proto = core_pb2.NodeServiceData( executables=service.executables, dependencies=service.dependencies, @@ -729,8 +732,8 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetNodeServiceFile(self, request, context): logging.debug("get node service file: %s", request) - session = self.get_session(request.session, context) - node = self.get_node(session, request.id, context) + session = self.get_session(request.session_id, context) + node = self.get_node(session, request.node_id, context) service = None for current_service in node.services: if current_service.name == request.service: @@ -743,9 +746,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SetNodeService(self, request, context): logging.debug("set node service: %s", request) - session = self.get_session(request.session, context) - session.services.set_service(request.id, request.service) - service = session.services.get_service(request.id, request.service) + session = self.get_session(request.session_id, context) + session.services.set_service(request.node_id, request.service) + service = session.services.get_service(request.node_id, request.service) service.startup = tuple(request.startup) service.validate = tuple(request.validate) service.shutdown = tuple(request.shutdown) @@ -753,14 +756,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SetNodeServiceFile(self, request, context): logging.debug("set node service file: %s", request) - session = self.get_session(request.session, context) - session.services.set_service_file(request.id, request.service, request.file, request.data) + session = self.get_session(request.session_id, context) + session.services.set_service_file(request.node_id, request.service, request.file, request.data) return core_pb2.SetNodeServiceFileResponse(result=True) def ServiceAction(self, request, context): logging.debug("service action: %s", request) - session = self.get_session(request.session, context) - node = self.get_node(session, request.id, context) + session = self.get_session(request.session_id, context) + node = self.get_node(session, request.node_id, context) service = None for current_service in node.services: if current_service.name == request.service: @@ -790,34 +793,34 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetWlanConfig(self, request, context): logging.debug("get wlan config: %s", request) - session = self.get_session(request.session, context) - config = session.mobility.get_model_config(request.id, BasicRangeModel.name) + session = self.get_session(request.session_id, context) + config = session.mobility.get_model_config(request.node_id, BasicRangeModel.name) groups = get_config_groups(config, BasicRangeModel) return core_pb2.GetWlanConfigResponse(groups=groups) def SetWlanConfig(self, request, context): logging.debug("set wlan config: %s", request) - session = self.get_session(request.session, context) - session.mobility.set_model_config(request.id, BasicRangeModel.name, request.config) + session = self.get_session(request.session_id, context) + session.mobility.set_model_config(request.node_id, BasicRangeModel.name, request.config) return core_pb2.SetWlanConfigResponse(result=True) def GetEmaneConfig(self, request, context): logging.debug("get emane config: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) config = session.emane.get_configs() groups = get_config_groups(config, session.emane.emane_config) return core_pb2.GetEmaneConfigResponse(groups=groups) def SetEmaneConfig(self, request, context): logging.debug("set emane config: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) config = session.emane.get_configs() config.update(request.config) return core_pb2.SetEmaneConfigResponse(result=True) def GetEmaneModels(self, request, context): logging.debug("get emane models: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) models = [] for model in session.emane.models.keys(): if len(model.split("_")) != 2: @@ -827,23 +830,23 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def GetEmaneModelConfig(self, request, context): logging.debug("get emane model config: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) model = session.emane.models[request.model] - _id = get_emane_model_id(request.id, request.interface) + _id = get_emane_model_id(request.node_id, request.interface) config = session.emane.get_model_config(_id, request.model) groups = get_config_groups(config, model) return core_pb2.GetEmaneModelConfigResponse(groups=groups) def SetEmaneModelConfig(self, request, context): logging.debug("set emane model config: %s", request) - session = self.get_session(request.session, context) - _id = get_emane_model_id(request.id, request.interface) + session = self.get_session(request.session_id, context) + _id = get_emane_model_id(request.node_id, request.interface_id) session.emane.set_model_config(_id, request.model, request.config) return core_pb2.SetEmaneModelConfigResponse(result=True) def GetEmaneModelConfigs(self, request, context): logging.debug("get emane model configs: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) response = core_pb2.GetEmaneModelConfigsResponse() for node_id, model_config in session.emane.node_configurations.iteritems(): if node_id == -1: @@ -860,7 +863,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SaveXml(self, request, context): logging.debug("save xml: %s", request) - session = self.get_session(request.session, context) + session = self.get_session(request.session_id, context) _, temp_path = tempfile.mkstemp() session.save_xml(temp_path) @@ -881,7 +884,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): try: session.open_xml(temp_path, start=True) - return core_pb2.OpenXmlResponse(session=session.id, result=True) + return core_pb2.OpenXmlResponse(session_id=session.id, result=True) except IOError: logging.exception("error opening session file") self.coreemu.delete_session(session.id) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 354e7b66..52c3131e 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -13,26 +13,27 @@ def main(): with core.context_connect(): # create session - session = core.create_session() - logging.info("created session: %s", session) + response = core.create_session() + logging.info("created session: %s", response) # handle events session may broadcast - core.exception_events(session.id, log_event) - core.node_events(session.id, log_event) - core.session_events(session.id, log_event) - core.link_events(session.id, log_event) - core.file_events(session.id, log_event) - core.config_events(session.id, log_event) + session_id = response.session_id + core.exception_events(session_id, log_event) + core.node_events(session_id, log_event) + core.session_events(session_id, log_event) + core.link_events(session_id, log_event) + core.file_events(session_id, log_event) + core.config_events(session_id, log_event) # change session state - response = core.set_session_state(session.id, core_pb2.STATE_CONFIGURATION) + response = core.set_session_state(session_id, core_pb2.STATE_CONFIGURATION) logging.info("set session state: %s", response) # create switch node switch = core_pb2.Node(type=core_pb2.NODE_SWITCH) - response = core.add_node(session.id, switch) + response = core.add_node(session_id, switch) logging.info("created switch: %s", response) - switch_id = response.id + switch_id = response.node_id # helper to create interfaces interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") @@ -41,17 +42,17 @@ def main(): # create node position = core_pb2.Position(x=50 + 50 * i, y=50) node = core_pb2.Node(position=position) - response = core.add_node(session.id, node) + response = core.add_node(session_id, node) logging.info("created node: %s", response) - node_id = response.id + node_id = response.node_id # create link interface_one = interface_helper.create_interface(node_id, 0) - response = core.add_link(session.id, node_id, switch_id, interface_one) + response = core.add_link(session_id, node_id, switch_id, interface_one) logging.info("created link: %s", response) # change session state - response = core.set_session_state(session.id, core_pb2.STATE_INSTANTIATION) + response = core.set_session_state(session_id, core_pb2.STATE_INSTANTIATION) logging.info("set session state: %s", response) diff --git a/daemon/proto/core.proto b/daemon/proto/core.proto index 45fd597e..55ec3ede 100644 --- a/daemon/proto/core.proto +++ b/daemon/proto/core.proto @@ -120,16 +120,16 @@ service CoreApi { // rpc request/response messages message CreateSessionRequest { - int32 id = 1; + int32 session_id = 1; } message CreateSessionResponse { - int32 id = 1; + int32 session_id = 1; SessionState state = 2; } message DeleteSessionRequest { - int32 id = 1; + int32 session_id = 1; } message DeleteSessionResponse { @@ -144,7 +144,7 @@ message GetSessionsResponse { } message GetSessionRequest { - int32 id = 1; + int32 session_id = 1; } message GetSessionResponse { @@ -152,7 +152,7 @@ message GetSessionResponse { } message GetSessionOptionsRequest { - int32 id = 1; + int32 session_id = 1; } message GetSessionOptionsResponse { @@ -160,7 +160,7 @@ message GetSessionOptionsResponse { } message SetSessionOptionsRequest { - int32 id = 1; + int32 session_id = 1; map config = 2; } @@ -169,7 +169,7 @@ message SetSessionOptionsResponse { } message GetSessionLocationRequest { - int32 id = 1; + int32 session_id = 1; } message GetSessionLocationResponse { @@ -178,7 +178,7 @@ message GetSessionLocationResponse { } message SetSessionLocationRequest { - int32 id = 1; + int32 session_id = 1; Position position = 2; float scale = 3; } @@ -188,7 +188,7 @@ message SetSessionLocationResponse { } message SetSessionStateRequest { - int32 id = 1; + int32 session_id = 1; SessionState state = 2; } @@ -197,7 +197,7 @@ message SetSessionStateResponse { } message NodeEventsRequest { - int32 id = 1; + int32 session_id = 1; } message NodeEvent { @@ -205,7 +205,7 @@ message NodeEvent { } message LinkEventsRequest { - int32 id = 1; + int32 session_id = 1; } message LinkEvent { @@ -214,25 +214,25 @@ message LinkEvent { } message SessionEventsRequest { - int32 id = 1; + int32 session_id = 1; } message SessionEvent { - int32 node = 1; + int32 node_id = 1; int32 event = 2; string name = 3; bytes data = 4; float time = 5; - int32 session = 6; + int32 session_id = 6; } message ConfigEventsRequest { - int32 id = 1; + int32 session_id = 1; } message ConfigEvent { MessageType message_type = 1; - int32 node = 2; + int32 node_id = 2; string object = 3; int32 type = 4; repeated int32 data_types = 5; @@ -241,19 +241,19 @@ message ConfigEvent { string bitmap = 8; string possible_values = 9; string groups = 10; - string session = 11; + int32 session_id = 11; int32 interface = 12; int32 network_id = 13; string opaque = 14; } message ExceptionEventsRequest { - int32 id = 1; + int32 session_id = 1; } message ExceptionEvent { - int32 node = 1; - int32 session = 2; + int32 node_id = 1; + int32 session_id = 2; ExceptionLevel level = 3; string source = 4; string date = 5; @@ -262,34 +262,34 @@ message ExceptionEvent { } message FileEventsRequest { - int32 id = 1; + int32 session_id = 1; } message FileEvent { MessageType message_type = 1; - int32 node = 2; + int32 node_id = 2; string name = 3; string mode = 4; int32 number = 5; string type = 6; string source = 7; - int32 session = 8; + int32 session_id = 8; bytes data = 9; bytes compressed_data = 10; } message AddNodeRequest { - int32 session = 1; + int32 session_id = 1; Node node = 2; } message AddNodeResponse { - int32 id = 1; + int32 node_id = 1; } message GetNodeRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; } message GetNodeResponse { @@ -298,8 +298,8 @@ message GetNodeResponse { } message EditNodeRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; Position position = 3; } @@ -308,8 +308,8 @@ message EditNodeResponse { } message DeleteNodeRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; } message DeleteNodeResponse { @@ -317,8 +317,8 @@ message DeleteNodeResponse { } message GetNodeLinksRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; } message GetNodeLinksResponse { @@ -326,7 +326,7 @@ message GetNodeLinksResponse { } message AddLinkRequest { - int32 session = 1; + int32 session_id = 1; Link link = 2; } @@ -335,11 +335,11 @@ message AddLinkResponse { } message EditLinkRequest { - int32 session = 1; - int32 node_one = 2; - int32 node_two = 3; - int32 interface_one = 4; - int32 interface_two = 5; + int32 session_id = 1; + int32 node_one_id = 2; + int32 node_two_id = 3; + int32 interface_one_id = 4; + int32 interface_two_id = 5; LinkOptions options = 6; } @@ -348,11 +348,11 @@ message EditLinkResponse { } message DeleteLinkRequest { - int32 session = 1; - int32 node_one = 2; - int32 node_two = 3; - int32 interface_one = 4; - int32 interface_two = 5; + int32 session_id = 1; + int32 node_one_id = 2; + int32 node_two_id = 3; + int32 interface_one_id = 4; + int32 interface_two_id = 5; } message DeleteLinkResponse { @@ -360,7 +360,7 @@ message DeleteLinkResponse { } message GetHooksRequest { - int32 session = 1; + int32 session_id = 1; } message GetHooksResponse { @@ -368,7 +368,7 @@ message GetHooksResponse { } message AddHookRequest { - int32 session = 1; + int32 session_id = 1; Hook hook = 2; } @@ -377,7 +377,7 @@ message AddHookResponse { } message GetMobilityConfigsRequest { - int32 session = 1; + int32 session_id = 1; } message GetMobilityConfigsResponse { @@ -388,8 +388,8 @@ message GetMobilityConfigsResponse { } message GetMobilityConfigRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; } message GetMobilityConfigResponse { @@ -397,8 +397,8 @@ message GetMobilityConfigResponse { } message SetMobilityConfigRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; map config = 3; } @@ -407,8 +407,8 @@ message SetMobilityConfigResponse { } message MobilityActionRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; MobilityAction action = 3; } @@ -425,7 +425,7 @@ message GetServicesResponse { } message GetServiceDefaultsRequest { - int32 session = 1; + int32 session_id = 1; } message GetServiceDefaultsResponse { @@ -433,7 +433,7 @@ message GetServiceDefaultsResponse { } message SetServiceDefaultsRequest { - int32 session = 1; + int32 session_id = 1; repeated ServiceDefaults defaults = 2; } @@ -442,8 +442,8 @@ message SetServiceDefaultsResponse { } message GetNodeServiceRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; string service = 3; } @@ -452,8 +452,8 @@ message GetNodeServiceResponse { } message GetNodeServiceFileRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; string service = 3; string file = 4; } @@ -463,8 +463,8 @@ message GetNodeServiceFileResponse { } message SetNodeServiceRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; string service = 3; repeated string startup = 4; repeated string validate = 5; @@ -476,8 +476,8 @@ message SetNodeServiceResponse { } message SetNodeServiceFileRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; string service = 3; string file = 4; bytes data = 5; @@ -488,8 +488,8 @@ message SetNodeServiceFileResponse { } message ServiceActionRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; string service = 3; ServiceAction action = 4; } @@ -499,8 +499,8 @@ message ServiceActionResponse { } message GetWlanConfigRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; } message GetWlanConfigResponse { @@ -508,8 +508,8 @@ message GetWlanConfigResponse { } message SetWlanConfigRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; map config = 3; } @@ -518,7 +518,7 @@ message SetWlanConfigResponse { } message GetEmaneConfigRequest { - int32 session = 1; + int32 session_id = 1; } message GetEmaneConfigResponse { @@ -526,7 +526,7 @@ message GetEmaneConfigResponse { } message SetEmaneConfigRequest { - int32 session = 1; + int32 session_id = 1; map config = 2; } @@ -535,7 +535,7 @@ message SetEmaneConfigResponse { } message GetEmaneModelsRequest { - int32 session = 1; + int32 session_id = 1; } message GetEmaneModelsResponse { @@ -543,8 +543,8 @@ message GetEmaneModelsResponse { } message GetEmaneModelConfigRequest { - int32 session = 1; - int32 id = 2; + int32 session_id = 1; + int32 node_id = 2; int32 interface = 3; string model = 4; } @@ -554,9 +554,9 @@ message GetEmaneModelConfigResponse { } message SetEmaneModelConfigRequest { - int32 session = 1; - int32 id = 2; - int32 interface = 3; + int32 session_id = 1; + int32 node_id = 2; + int32 interface_id = 3; string model = 4; map config = 5; } @@ -566,7 +566,7 @@ message SetEmaneModelConfigResponse { } message GetEmaneModelConfigsRequest { - int32 session = 1; + int32 session_id = 1; } message GetEmaneModelConfigsResponse { @@ -578,7 +578,7 @@ message GetEmaneModelConfigsResponse { } message SaveXmlRequest { - int32 session = 1; + int32 session_id = 1; } message SaveXmlResponse { @@ -591,7 +591,7 @@ message OpenXmlRequest { message OpenXmlResponse { bool result = 1; - int32 session = 2; + int32 session_id = 2; } // data structures for messages below @@ -733,8 +733,8 @@ message Node { } message Link { - int32 node_one = 1; - int32 node_two = 2; + int32 node_one_id = 1; + int32 node_two_id = 2; LinkType type = 3; Interface interface_one = 4; Interface interface_two = 5; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 85a7910d..1a191a61 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -25,13 +25,13 @@ class TestGrpc: response = client.create_session(session_id) # then - assert isinstance(response.id, int) + assert isinstance(response.session_id, int) assert isinstance(response.state, int) - session = grpc_server.coreemu.sessions.get(response.id) + session = grpc_server.coreemu.sessions.get(response.session_id) assert session is not None assert session.state == response.state if session_id is not None: - assert response.id == session_id + assert response.session_id == session_id assert session.id == session_id @pytest.mark.parametrize("session_id, expected", [ @@ -181,8 +181,8 @@ class TestGrpc: response = client.add_node(session.id, node) # then - assert response.id is not None - assert session.get_object(response.id) is not None + assert response.node_id is not None + assert session.get_object(response.node_id) is not None def test_get_node(self, grpc_server): # given @@ -298,7 +298,7 @@ class TestGrpc: # then assert response.result is True - assert response.session is not None + assert response.session_id is not None def test_get_node_links(self, grpc_server, ip_prefixes): # given From 6b93f60056913969238551e37412e46c572cf95f Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Tue, 28 May 2019 14:49:25 -0700 Subject: [PATCH 0033/1992] updated corefx to use explicit id names for grpc --- .../com/core/client/grpc/CoreGrpcClient.java | 120 +++++++++--------- 1 file changed, 62 insertions(+), 58 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 3136a8d6..2053cc98 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -193,7 +193,7 @@ public class CoreGrpcClient implements ICoreClient { try { CoreProto.CreateSessionResponse response = blockingStub.createSession(request); SessionOverview overview = new SessionOverview(); - overview.setId(response.getId()); + overview.setId(response.getSessionId()); overview.setState(response.getStateValue()); overview.setNodes(0); return overview; @@ -204,7 +204,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean deleteSession(Integer sessionId) throws IOException { - CoreProto.DeleteSessionRequest request = CoreProto.DeleteSessionRequest.newBuilder().setId(sessionId).build(); + CoreProto.DeleteSessionRequest request = CoreProto.DeleteSessionRequest.newBuilder() + .setSessionId(sessionId).build(); try { return blockingStub.deleteSession(request).getResult(); } catch (StatusRuntimeException ex) { @@ -234,7 +235,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public Session getSession(Integer sessionId) throws IOException { logger.info("getting session: {}", sessionId); - CoreProto.GetSessionRequest request = CoreProto.GetSessionRequest.newBuilder().setId(sessionId).build(); + CoreProto.GetSessionRequest request = CoreProto.GetSessionRequest.newBuilder().setSessionId(sessionId).build(); try { CoreProto.GetSessionResponse response = blockingStub.getSession(request); Session session = new Session(); @@ -256,10 +257,10 @@ public class CoreGrpcClient implements ICoreClient { session.getNodes().add(node); } for (CoreProto.Link linkProto : response.getSession().getLinksList()) { - logger.info("adding link: {} - {}", linkProto.getNodeOne(), linkProto.getNodeTwo()); + logger.info("adding link: {} - {}", linkProto.getNodeOneId(), linkProto.getNodeTwoId()); CoreLink link = new CoreLink(); - link.setNodeOne(linkProto.getNodeOne()); - link.setNodeTwo(linkProto.getNodeTwo()); + link.setNodeOne(linkProto.getNodeOneId()); + link.setNodeTwo(linkProto.getNodeTwoId()); CoreProto.Interface interfaceOneProto = linkProto.getInterfaceOne(); CoreInterface interfaceOne = new CoreInterface(); interfaceOne.setId(interfaceOneProto.getId()); @@ -354,11 +355,14 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean setState(SessionState state) throws IOException { CoreProto.SetSessionStateRequest request = CoreProto.SetSessionStateRequest.newBuilder() - .setId(sessionId) + .setSessionId(sessionId) .setStateValue(state.getValue()) .build(); try { CoreProto.SetSessionStateResponse response = blockingStub.setSessionState(request); + if (response.getResult()) { + sessionState = state; + } return response.getResult(); } catch (StatusRuntimeException ex) { throw new IOException(ex); @@ -409,7 +413,7 @@ public class CoreGrpcClient implements ICoreClient { allDefaults.add(serviceDefaults); } CoreProto.SetServiceDefaultsRequest request = CoreProto.SetServiceDefaultsRequest.newBuilder() - .setSession(sessionId) + .setSessionId(sessionId) .addAllDefaults(allDefaults) .build(); try { @@ -446,8 +450,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException { CoreProto.SetNodeServiceRequest request = CoreProto.SetNodeServiceRequest.newBuilder() - .setId(node.getId()) - .setSession(sessionId) + .setNodeId(node.getId()) + .setSessionId(sessionId) .setService(serviceName) .build(); request.getShutdownList().addAll(service.getShutdown()); @@ -463,8 +467,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException { CoreProto.GetNodeServiceFileRequest request = CoreProto.GetNodeServiceFileRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .setService(serviceName) .build(); try { @@ -478,8 +482,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean startService(CoreNode node, String serviceName) throws IOException { CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .setService(serviceName) .setAction(CoreProto.ServiceAction.SERVICE_START) .build(); @@ -493,8 +497,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean stopService(CoreNode node, String serviceName) throws IOException { CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .setService(serviceName) .setAction(CoreProto.ServiceAction.SERVICE_STOP) .build(); @@ -508,8 +512,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean restartService(CoreNode node, String serviceName) throws IOException { CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .setService(serviceName) .setAction(CoreProto.ServiceAction.SERVICE_RESTART) .build(); @@ -523,8 +527,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean validateService(CoreNode node, String serviceName) throws IOException { CoreProto.ServiceActionRequest request = CoreProto.ServiceActionRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .setService(serviceName) .setAction(CoreProto.ServiceAction.SERVICE_VALIDATE) .build(); @@ -538,8 +542,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException { CoreProto.SetNodeServiceFileRequest request = CoreProto.SetNodeServiceFileRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .setService(serviceName) .setFile(serviceFile.getName()) .setData(ByteString.copyFromUtf8(serviceFile.getData())) @@ -555,7 +559,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public List getEmaneConfig(CoreNode node) throws IOException { CoreProto.GetEmaneConfigRequest request = CoreProto.GetEmaneConfigRequest.newBuilder() - .setSession(sessionId) + .setSessionId(sessionId) .build(); try { CoreProto.GetEmaneConfigResponse response = blockingStub.getEmaneConfig(request); @@ -568,7 +572,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public List getEmaneModels() throws IOException { CoreProto.GetEmaneModelsRequest request = CoreProto.GetEmaneModelsRequest.newBuilder() - .setSession(sessionId) + .setSessionId(sessionId) .build(); try { CoreProto.GetEmaneModelsResponse response = blockingStub.getEmaneModels(request); @@ -582,7 +586,7 @@ public class CoreGrpcClient implements ICoreClient { public boolean setEmaneConfig(CoreNode node, List options) throws IOException { Map config = configOptionListToMap(options); CoreProto.SetEmaneConfigRequest request = CoreProto.SetEmaneConfigRequest.newBuilder() - .setSession(sessionId) + .setSessionId(sessionId) .putAllConfig(config) .build(); try { @@ -596,8 +600,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public List getEmaneModelConfig(Integer id, String model) throws IOException { CoreProto.GetEmaneModelConfigRequest request = CoreProto.GetEmaneModelConfigRequest.newBuilder() - .setSession(sessionId) - .setId(id) + .setSessionId(sessionId) + .setNodeId(id) .setModel(model) .build(); try { @@ -612,8 +616,8 @@ public class CoreGrpcClient implements ICoreClient { public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException { Map config = configOptionListToMap(options); CoreProto.SetEmaneModelConfigRequest request = CoreProto.SetEmaneModelConfigRequest.newBuilder() - .setSession(sessionId) - .setId(id) + .setSessionId(sessionId) + .setNodeId(id) .setModel(model) .putAllConfig(config) .build(); @@ -633,7 +637,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public void saveSession(File file) throws IOException { CoreProto.SaveXmlRequest request = CoreProto.SaveXmlRequest.newBuilder() - .setSession(sessionId) + .setSessionId(sessionId) .build(); try { CoreProto.SaveXmlResponse response = blockingStub.saveXml(request); @@ -654,7 +658,7 @@ public class CoreGrpcClient implements ICoreClient { try { CoreProto.OpenXmlResponse response = blockingStub.openXml(request); SessionOverview sessionOverview = new SessionOverview(); - sessionOverview.setId(response.getSession()); + sessionOverview.setId(response.getSessionId()); return sessionOverview; } catch (StatusRuntimeException ex) { throw new IOException(ex); @@ -664,7 +668,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public List getSessionConfig() throws IOException { CoreProto.GetSessionOptionsRequest request = CoreProto.GetSessionOptionsRequest.newBuilder() - .setId(sessionId) + .setSessionId(sessionId) .build(); try { CoreProto.GetSessionOptionsResponse response = blockingStub.getSessionOptions(request); @@ -678,7 +682,7 @@ public class CoreGrpcClient implements ICoreClient { public boolean setSessionConfig(List configOptions) throws IOException { Map config = configOptionListToMap(configOptions); CoreProto.SetSessionOptionsRequest request = CoreProto.SetSessionOptionsRequest.newBuilder() - .setId(sessionId) + .setSessionId(sessionId) .putAllConfig(config) .build(); try { @@ -693,7 +697,7 @@ public class CoreGrpcClient implements ICoreClient { public boolean createNode(CoreNode node) throws IOException { CoreProto.Node protoNode = nodeToProto(node); CoreProto.AddNodeRequest request = CoreProto.AddNodeRequest.newBuilder() - .setSession(sessionId) + .setSessionId(sessionId) .setNode(protoNode) .build(); try { @@ -717,8 +721,8 @@ public class CoreGrpcClient implements ICoreClient { .setY(node.getPosition().getY().floatValue()) .build(); CoreProto.EditNodeRequest request = CoreProto.EditNodeRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .setPosition(position) .build(); try { @@ -746,10 +750,10 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.Link.Builder builder = CoreProto.Link.newBuilder() .setTypeValue(link.getType()); if (link.getNodeOne() != null) { - builder.setNodeOne(link.getNodeOne()); + builder.setNodeOneId(link.getNodeOne()); } if (link.getNodeTwo() != null) { - builder.setNodeTwo(link.getNodeTwo()); + builder.setNodeTwoId(link.getNodeTwo()); } if (link.getInterfaceOne() != null) { builder.setInterfaceOne(interfaceToProto(link.getInterfaceOne())); @@ -762,7 +766,7 @@ public class CoreGrpcClient implements ICoreClient { } CoreProto.Link protoLink = builder.build(); CoreProto.AddLinkRequest request = CoreProto.AddLinkRequest.newBuilder() - .setSession(sessionId) + .setSessionId(sessionId) .setLink(protoLink) .build(); try { @@ -776,18 +780,18 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean editLink(CoreLink link) throws IOException { CoreProto.EditLinkRequest.Builder builder = CoreProto.EditLinkRequest.newBuilder() - .setSession(sessionId); + .setSessionId(sessionId); if (link.getNodeOne() != null) { - builder.setNodeOne(link.getNodeOne()); + builder.setNodeOneId(link.getNodeOne()); } if (link.getNodeTwo() != null) { - builder.setNodeTwo(link.getNodeTwo()); + builder.setNodeTwoId(link.getNodeTwo()); } if (link.getInterfaceOne() != null) { - builder.setInterfaceOne(link.getInterfaceOne().getId()); + builder.setInterfaceOneId(link.getInterfaceOne().getId()); } if (link.getInterfaceTwo() != null) { - builder.setInterfaceTwo(link.getInterfaceTwo().getId()); + builder.setInterfaceTwoId(link.getInterfaceTwo().getId()); } if (link.getOptions() != null) { CoreProto.LinkOptions protoOptions = linkOptionsToProto(link.getOptions()); @@ -822,7 +826,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public List getHooks() throws IOException { - CoreProto.GetHooksRequest request = CoreProto.GetHooksRequest.newBuilder().setSession(sessionId).build(); + CoreProto.GetHooksRequest request = CoreProto.GetHooksRequest.newBuilder().setSessionId(sessionId).build(); try { CoreProto.GetHooksResponse response = blockingStub.getHooks(request); List hooks = new ArrayList<>(); @@ -842,8 +846,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public WlanConfig getWlanConfig(CoreNode node) throws IOException { CoreProto.GetWlanConfigRequest request = CoreProto.GetWlanConfigRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .build(); try { CoreProto.GetWlanConfigResponse response = blockingStub.getWlanConfig(request); @@ -874,8 +878,8 @@ public class CoreGrpcClient implements ICoreClient { protoConfig.put("jitter", config.getJitter()); protoConfig.put("range", config.getRange()); CoreProto.SetWlanConfigRequest request = CoreProto.SetWlanConfigRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .putAllConfig(protoConfig) .build(); try { @@ -895,7 +899,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public Map getMobilityConfigs() throws IOException { CoreProto.GetMobilityConfigsRequest request = CoreProto.GetMobilityConfigsRequest.newBuilder() - .setSession(sessionId).build(); + .setSessionId(sessionId).build(); try { CoreProto.GetMobilityConfigsResponse response = blockingStub.getMobilityConfigs(request); @@ -940,8 +944,8 @@ public class CoreGrpcClient implements ICoreClient { protoConfig.put("script_start", config.getStartScript()); protoConfig.put("script_stop", config.getStopScript()); CoreProto.SetMobilityConfigRequest request = CoreProto.SetMobilityConfigRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .putAllConfig(protoConfig) .build(); try { @@ -955,8 +959,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public MobilityConfig getMobilityConfig(CoreNode node) throws IOException { CoreProto.GetMobilityConfigRequest request = CoreProto.GetMobilityConfigRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .build(); try { CoreProto.GetMobilityConfigResponse response = blockingStub.getMobilityConfig(request); @@ -984,8 +988,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean mobilityAction(CoreNode node, String action) throws IOException { CoreProto.MobilityActionRequest request = CoreProto.MobilityActionRequest.newBuilder() - .setSession(sessionId) - .setId(node.getId()) + .setSessionId(sessionId) + .setNodeId(node.getId()) .setAction(CoreProto.MobilityAction.valueOf(action)) .build(); try { @@ -999,7 +1003,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public LocationConfig getLocationConfig() throws IOException { CoreProto.GetSessionLocationRequest request = CoreProto.GetSessionLocationRequest.newBuilder() - .setId(sessionId) + .setSessionId(sessionId) .build(); try { CoreProto.GetSessionLocationResponse response = blockingStub.getSessionLocation(request); @@ -1020,7 +1024,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean setLocationConfig(LocationConfig config) throws IOException { CoreProto.SetSessionLocationRequest.Builder builder = CoreProto.SetSessionLocationRequest.newBuilder() - .setId(sessionId); + .setSessionId(sessionId); if (config.getScale() != null) { builder.setScale(config.getScale().floatValue()); } From ec672d209f8fc9eee7087d65cc26aa57120d26ee Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Wed, 29 May 2019 12:25:33 -0700 Subject: [PATCH 0034/1992] corefx updates to support handling streamed events from the grpc server --- corefx/src/main/java/com/core/Controller.java | 3 + .../java/com/core/client/ICoreClient.java | 3 + .../com/core/client/grpc/CoreGrpcClient.java | 264 ++++++++++++++---- .../com/core/client/rest/CoreRestClient.java | 5 + 4 files changed, 225 insertions(+), 50 deletions(-) diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java index 0dc3ffdf..e54c535b 100644 --- a/corefx/src/main/java/com/core/Controller.java +++ b/corefx/src/main/java/com/core/Controller.java @@ -154,6 +154,9 @@ public class Controller implements Initializable { coreClient.updateSession(sessionId); coreClient.updateState(sessionState); + // setup event handlers + coreClient.setupEventHandlers(this); + // display all nodes logger.info("joining core session({}) state({}): {}", sessionId, sessionState, session); for (CoreNode node : session.getNodes()) { diff --git a/corefx/src/main/java/com/core/client/ICoreClient.java b/corefx/src/main/java/com/core/client/ICoreClient.java index fa06395d..531f43dd 100644 --- a/corefx/src/main/java/com/core/client/ICoreClient.java +++ b/corefx/src/main/java/com/core/client/ICoreClient.java @@ -1,5 +1,6 @@ package com.core.client; +import com.core.Controller; import com.core.client.rest.ServiceFile; import com.core.client.rest.WlanConfig; import com.core.data.*; @@ -115,4 +116,6 @@ public interface ICoreClient { LocationConfig getLocationConfig() throws IOException; boolean setLocationConfig(LocationConfig config) throws IOException; + + void setupEventHandlers(Controller controller) throws IOException; } diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 2053cc98..b2a7fdfa 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -1,9 +1,11 @@ package com.core.client.grpc; +import com.core.Controller; import com.core.client.ICoreClient; import com.core.client.rest.ServiceFile; import com.core.client.rest.WlanConfig; import com.core.data.*; +import com.core.ui.dialogs.MobilityPlayerDialog; import com.google.protobuf.ByteString; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -16,6 +18,8 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class CoreGrpcClient implements ICoreClient { private static final Logger logger = LogManager.getLogger(); @@ -25,6 +29,8 @@ public class CoreGrpcClient implements ICoreClient { private SessionState sessionState; private CoreApiGrpc.CoreApiBlockingStub blockingStub; private ManagedChannel channel; + private final ExecutorService executorService = Executors.newFixedThreadPool(6); + private boolean handlingEvents = false; private CoreProto.Node nodeToProto(CoreNode node) { CoreProto.Position position = CoreProto.Position.newBuilder() @@ -144,6 +150,60 @@ public class CoreGrpcClient implements ICoreClient { return config; } + private CoreNode protoToNode(CoreProto.Node protoNode) { + CoreNode node = new CoreNode(protoNode.getId()); + node.setName(protoNode.getName()); + node.setEmane(protoNode.getEmane()); + node.setIcon(protoNode.getIcon()); + node.setModel(protoNode.getModel()); + node.setServices(new HashSet<>(protoNode.getServicesList())); + node.getPosition().setX((double) protoNode.getPosition().getX()); + node.getPosition().setY((double) protoNode.getPosition().getY()); + node.setType(protoNode.getTypeValue()); + return node; + } + + private CoreInterface protoToInterface(CoreProto.Interface protoInterface) { + CoreInterface coreInterface = new CoreInterface(); + coreInterface.setId(protoInterface.getId()); + coreInterface.setName(protoInterface.getName()); + coreInterface.setMac(protoInterface.getMac()); + coreInterface.setIp4(protoInterface.getIp4()); + coreInterface.setIp4Mask(protoInterface.getIp4Mask()); + coreInterface.setIp6(protoInterface.getIp6()); + coreInterface.setIp6Mask(Integer.toString(protoInterface.getIp6Mask())); + return coreInterface; + } + + private CoreLink protoToLink(CoreProto.Link linkProto) { + CoreLink link = new CoreLink(); + link.setNodeOne(linkProto.getNodeOneId()); + link.setNodeTwo(linkProto.getNodeTwoId()); + CoreInterface interfaceOne = protoToInterface(linkProto.getInterfaceOne()); + link.setInterfaceOne(interfaceOne); + CoreInterface interfaceTwo = protoToInterface(linkProto.getInterfaceTwo()); + link.setInterfaceTwo(interfaceTwo); + + CoreLinkOptions options = new CoreLinkOptions(); + CoreProto.LinkOptions protoOptions = linkProto.getOptions(); + options.setBandwidth((double) protoOptions.getBandwidth()); + options.setDelay((double) protoOptions.getDelay()); + options.setDup((double) protoOptions.getDup()); + options.setJitter((double) protoOptions.getJitter()); + options.setPer((double) protoOptions.getPer()); + options.setBurst((double) protoOptions.getBurst()); + if (!protoOptions.getKey().isEmpty()) { + options.setKey(Integer.parseInt(protoOptions.getKey())); + } + options.setMburst((double) protoOptions.getMburst()); + options.setMer((double) protoOptions.getMer()); + options.setOpaque(protoOptions.getOpaque()); + options.setUnidirectional(protoOptions.getUnidirectional() ? 1 : 0); + link.setOptions(options); + + return link; + } + @Override public void setConnection(String address, int port) { this.address = address; @@ -245,60 +305,12 @@ public class CoreGrpcClient implements ICoreClient { } logger.info("adding node: {}", protoNode); - CoreNode node = new CoreNode(protoNode.getId()); - node.setName(protoNode.getName()); - node.setEmane(protoNode.getEmane()); - node.setIcon(protoNode.getIcon()); - node.setModel(protoNode.getModel()); - node.setServices(new HashSet<>(protoNode.getServicesList())); - node.getPosition().setX((double) protoNode.getPosition().getX()); - node.getPosition().setY((double) protoNode.getPosition().getY()); - node.setType(protoNode.getTypeValue()); + CoreNode node = protoToNode(protoNode); session.getNodes().add(node); } for (CoreProto.Link linkProto : response.getSession().getLinksList()) { logger.info("adding link: {} - {}", linkProto.getNodeOneId(), linkProto.getNodeTwoId()); - CoreLink link = new CoreLink(); - link.setNodeOne(linkProto.getNodeOneId()); - link.setNodeTwo(linkProto.getNodeTwoId()); - CoreProto.Interface interfaceOneProto = linkProto.getInterfaceOne(); - CoreInterface interfaceOne = new CoreInterface(); - interfaceOne.setId(interfaceOneProto.getId()); - interfaceOne.setName(interfaceOneProto.getName()); - interfaceOne.setMac(interfaceOneProto.getMac()); - interfaceOne.setIp4(interfaceOneProto.getIp4()); - interfaceOne.setIp4Mask(interfaceOneProto.getIp4Mask()); - interfaceOne.setIp6(interfaceOneProto.getIp6()); - interfaceOne.setIp6Mask(Integer.toString(interfaceOneProto.getIp6Mask())); - link.setInterfaceOne(interfaceOne); - - CoreProto.Interface interfaceTwoProto = linkProto.getInterfaceTwo(); - CoreInterface interfaceTwo = new CoreInterface(); - interfaceTwo.setId(interfaceTwoProto.getId()); - interfaceTwo.setName(interfaceTwoProto.getName()); - interfaceTwo.setMac(interfaceTwoProto.getMac()); - interfaceTwo.setIp4(interfaceTwoProto.getIp4()); - interfaceTwo.setIp4Mask(interfaceTwoProto.getIp4Mask()); - interfaceTwo.setIp6(interfaceTwoProto.getIp6()); - interfaceTwo.setIp6Mask(Integer.toString(interfaceTwoProto.getIp6Mask())); - link.setInterfaceTwo(interfaceTwo); - - CoreLinkOptions options = new CoreLinkOptions(); - CoreProto.LinkOptions protoOptions = linkProto.getOptions(); - options.setBandwidth((double) protoOptions.getBandwidth()); - options.setDelay((double) protoOptions.getDelay()); - options.setDup((double) protoOptions.getDup()); - options.setJitter((double) protoOptions.getJitter()); - options.setPer((double) protoOptions.getPer()); - options.setBurst((double) protoOptions.getBurst()); - if (!protoOptions.getKey().isEmpty()) { - options.setKey(Integer.parseInt(protoOptions.getKey())); - } - options.setMburst((double) protoOptions.getMburst()); - options.setMer((double) protoOptions.getMer()); - options.setOpaque(protoOptions.getOpaque()); - options.setUnidirectional(protoOptions.getUnidirectional() ? 1 : 0); - link.setOptions(options); + CoreLink link = protoToLink(linkProto); session.getLinks().add(link); } session.setState(response.getSession().getStateValue()); @@ -349,6 +361,7 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean stop() throws IOException { + handlingEvents = false; return setState(SessionState.SHUTDOWN); } @@ -1054,4 +1067,155 @@ public class CoreGrpcClient implements ICoreClient { throw new IOException(ex); } } + + @Override + public void setupEventHandlers(Controller controller) throws IOException { + logger.info("setting up event handlers"); + handlingEvents = true; + try { + handleSessionEvents(controller); + handleNodeEvents(controller); + handleExceptionEvents(controller); + handleConfigEvents(controller); + handleLinkEvents(controller); + handleFileEvents(controller); + } catch (StatusRuntimeException ex) { + throw new IOException("setup event handlers error", ex); + } + } + + private void handleSessionEvents(Controller controller) { + CoreProto.SessionEventsRequest request = CoreProto.SessionEventsRequest.newBuilder() + .setSessionId(sessionId) + .build(); + Iterator events = blockingStub.sessionEvents(request); + executorService.submit(() -> { + try { + while (handlingEvents) { + CoreProto.SessionEvent event = events.next(); + logger.info("session event: {}", event); + SessionState state = SessionState.get(event.getEvent()); + if (state == null) { + logger.warn("unknown event type: {}", event.getEvent()); + continue; + } + + // session state event + if (state.getValue() <= 6) { + logger.info("event updating session state: {}", state); + updateState(state); + // mobility script event + } else if (state.getValue() <= 9) { + Integer nodeId = event.getNodeId(); + String[] values = event.getData().toStringUtf8().split("\\s+"); + Integer start = Integer.parseInt(values[0].split("=")[1]); + Integer end = Integer.parseInt(values[1].split("=")[1]); + logger.info(String.format("node(%s) mobility event (%s) - start(%s) stop(%s)", + nodeId, state, start, end)); + logger.info("all dialogs: {}", controller.getMobilityPlayerDialogs().keySet()); + MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(nodeId); + mobilityPlayerDialog.event(state, start, end); + } + } + } catch (StatusRuntimeException ex) { + logger.error("error handling session events", ex); + } + }); + } + + private void handleNodeEvents(Controller controller) { + CoreProto.NodeEventsRequest request = CoreProto.NodeEventsRequest.newBuilder().setSessionId(sessionId) + .build(); + Iterator events = blockingStub.nodeEvents(request); + executorService.submit(() -> { + try { + while (handlingEvents) { + CoreProto.NodeEvent event = events.next(); + logger.info("node event: {}", event); + CoreNode node = protoToNode(event.getNode()); + controller.getNetworkGraph().setNodeLocation(node); + } + } catch (StatusRuntimeException ex) { + logger.error("error handling node events", ex); + } + }); + } + + private void handleExceptionEvents(Controller controller) { + CoreProto.ExceptionEventsRequest request = CoreProto.ExceptionEventsRequest.newBuilder() + .setSessionId(sessionId) + .build(); + Iterator events = blockingStub.exceptionEvents(request); + executorService.submit(() -> { + try { + while (handlingEvents) { + CoreProto.ExceptionEvent event = events.next(); + logger.info("exception event: {}", event); + } + } catch (StatusRuntimeException ex) { + logger.error("error handling exception events", ex); + } + }); + } + + private void handleConfigEvents(Controller controller) { + CoreProto.ConfigEventsRequest request = CoreProto.ConfigEventsRequest.newBuilder() + .setSessionId(sessionId) + .build(); + Iterator events = blockingStub.configEvents(request); + executorService.submit(() -> { + try { + while (handlingEvents) { + CoreProto.ConfigEvent event = events.next(); + logger.info("config event: {}", event); + } + } catch (StatusRuntimeException ex) { + logger.error("error handling config events", ex); + } + }); + } + + private void handleLinkEvents(Controller controller) { + CoreProto.LinkEventsRequest request = CoreProto.LinkEventsRequest.newBuilder() + .setSessionId(sessionId) + .build(); + Iterator events = blockingStub.linkEvents(request); + executorService.submit(() -> { + try { + while (handlingEvents) { + CoreProto.LinkEvent event = events.next(); + logger.info("link event: {}", event); + CoreLink link = protoToLink(event.getLink()); + MessageFlags flag = MessageFlags.get(event.getMessageTypeValue()); + if (MessageFlags.DELETE == flag) { + logger.info("delete"); + controller.getNetworkGraph().removeWirelessLink(link); + } else if (MessageFlags.ADD == flag) { + link.setLoaded(true); + controller.getNetworkGraph().addLink(link); + } + controller.getNetworkGraph().getGraphViewer().repaint(); + } + } catch (StatusRuntimeException ex) { + logger.error("error handling link events", ex); + } + }); + } + + private void handleFileEvents(Controller controller) { + CoreProto.FileEventsRequest request = CoreProto.FileEventsRequest.newBuilder() + .setSessionId(sessionId) + .build(); + Iterator events = blockingStub.fileEvents(request); + executorService.submit(() -> { + try { + while (handlingEvents) { + CoreProto.FileEvent event = events.next(); + logger.info("file event: {}", event); + } + } catch (StatusRuntimeException ex) { + logger.error("error handling file events", ex); + } + }); + } } diff --git a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java index 3e963583..3624cdb5 100644 --- a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java +++ b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java @@ -1,5 +1,6 @@ package com.core.client.rest; +import com.core.Controller; import com.core.client.ICoreClient; import com.core.data.*; import com.core.utils.WebUtils; @@ -412,4 +413,8 @@ public class CoreRestClient implements ICoreClient { String url = getUrl(String.format("sessions/%s/nodes/%s/mobility/%s", sessionId, node.getId(), action)); return WebUtils.putJson(url); } + + @Override + public void setupEventHandlers(Controller controller) throws IOException { + } } From 2ed2b4a8794f4caef02c3d7ea0dced1f04f21a9e Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Thu, 30 May 2019 08:57:24 -0700 Subject: [PATCH 0035/1992] encapsulated grpc enums within messages to help provide better namespace separation due to python/c code generation --- daemon/core/grpc/client.py | 2 +- daemon/core/grpc/server.py | 14 ++-- daemon/proto/core.proto | 152 ++++++++++++++++++++----------------- daemon/tests/test_grpc.py | 20 ++--- 4 files changed, 102 insertions(+), 86 deletions(-) diff --git a/daemon/core/grpc/client.py b/daemon/core/grpc/client.py index 7764c12e..15c0948b 100644 --- a/daemon/core/grpc/client.py +++ b/daemon/core/grpc/client.py @@ -422,7 +422,7 @@ class CoreGrpcClient(object): :raises grpc.RpcError: when session or one of the nodes don't exist """ link = core_pb2.Link( - node_one_id=node_one_id, node_two_id=node_two_id, type=core_pb2.LINK_WIRED, + node_one_id=node_one_id, node_two_id=node_two_id, type=core_pb2.LinkType.WIRED, interface_one=interface_one, interface_two=interface_two, options=options) request = core_pb2.AddLinkRequest(session_id=session_id, link=link) return self.stub.AddLink(request) diff --git a/daemon/core/grpc/server.py b/daemon/core/grpc/server.py index caa3d276..eaba4419 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/grpc/server.py @@ -676,11 +676,11 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session = self.get_session(request.session_id, context) node = self.get_node(session, request.node_id, context) result = True - if request.action == core_pb2.MOBILITY_START: + if request.action == core_pb2.MobilityAction.START: node.mobility.start() - elif request.action == core_pb2.MOBILITY_PAUSE: + elif request.action == core_pb2.MobilityAction.PAUSE: node.mobility.pause() - elif request.action == core_pb2.MOBILITY_STOP: + elif request.action == core_pb2.MobilityAction.STOP: node.mobility.stop(move_initial=True) else: result = False @@ -774,15 +774,15 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): context.abort(grpc.StatusCode.NOT_FOUND, "service not found") status = -1 - if request.action == core_pb2.SERVICE_START: + if request.action == core_pb2.ServiceAction.START: status = session.services.startup_service(node, service, wait=True) - elif request.action == core_pb2.SERVICE_STOP: + elif request.action == core_pb2.ServiceAction.STOP: status = session.services.stop_service(node, service) - elif request.action == core_pb2.SERVICE_RESTART: + elif request.action == core_pb2.ServiceAction.RESTART: status = session.services.stop_service(node, service) if not status: status = session.services.startup_service(node, service, wait=True) - elif request.action == core_pb2.SERVICE_VALIDATE: + elif request.action == core_pb2.ServiceAction.VALIDATE: status = session.services.validate_service(node, service) result = False diff --git a/daemon/proto/core.proto b/daemon/proto/core.proto index fc716444..f159c8af 100644 --- a/daemon/proto/core.proto +++ b/daemon/proto/core.proto @@ -128,7 +128,7 @@ message CreateSessionRequest { message CreateSessionResponse { int32 session_id = 1; - SessionState state = 2; + SessionState.Enum state = 2; } message DeleteSessionRequest { @@ -192,7 +192,7 @@ message SetSessionLocationResponse { message SetSessionStateRequest { int32 session_id = 1; - SessionState state = 2; + SessionState.Enum state = 2; } message SetSessionStateResponse { @@ -212,7 +212,7 @@ message LinkEventsRequest { } message LinkEvent { - MessageType message_type = 1; + MessageType.Enum message_type = 1; Link link = 2; } @@ -234,7 +234,7 @@ message ConfigEventsRequest { } message ConfigEvent { - MessageType message_type = 1; + MessageType.Enum message_type = 1; int32 node_id = 2; string object = 3; int32 type = 4; @@ -257,7 +257,7 @@ message ExceptionEventsRequest { message ExceptionEvent { int32 node_id = 1; int32 session_id = 2; - ExceptionLevel level = 3; + ExceptionLevel.Enum level = 3; string source = 4; string date = 5; string text = 6; @@ -269,7 +269,7 @@ message FileEventsRequest { } message FileEvent { - MessageType message_type = 1; + MessageType.Enum message_type = 1; int32 node_id = 2; string name = 3; string mode = 4; @@ -412,7 +412,7 @@ message SetMobilityConfigResponse { message MobilityActionRequest { int32 session_id = 1; int32 node_id = 2; - MobilityAction action = 3; + MobilityAction.Enum action = 3; } message MobilityActionResponse { @@ -494,7 +494,7 @@ message ServiceActionRequest { int32 session_id = 1; int32 node_id = 2; string service = 3; - ServiceAction action = 4; + ServiceAction.Enum action = 4; } message ServiceActionResponse { @@ -598,78 +598,94 @@ message OpenXmlResponse { } // data structures for messages below -enum MessageType { - MESSAGE_NONE = 0; - MESSAGE_ADD = 1; - MESSAGE_DELETE = 2; - MESSAGE_CRI = 4; - MESSAGE_LOCAL = 8; - MESSAGE_STRING = 16; - MESSAGE_TEXT = 32; - MESSAGE_TTY = 64; +message MessageType { + enum Enum { + NONE = 0; + ADD = 1; + DELETE = 2; + CRI = 4; + LOCAL = 8; + STRING = 16; + TEXT = 32; + TTY = 64; + } } -enum LinkType { - LINK_WIRELESS = 0; - LINK_WIRED = 1; +message LinkType { + enum Enum { + WIRELESS = 0; + WIRED = 1; + } } -enum SessionState { - STATE_NONE = 0; - STATE_DEFINITION = 1; - STATE_CONFIGURATION = 2; - STATE_INSTANTIATION = 3; - STATE_RUNTIME = 4; - STATE_DATACOLLECT = 5; - STATE_SHUTDOWN = 6; +message SessionState { + enum Enum { + NONE = 0; + DEFINITION = 1; + CONFIGURATION = 2; + INSTANTIATION = 3; + RUNTIME = 4; + DATACOLLECT = 5; + SHUTDOWN = 6; + } } -enum NodeType { - NODE_DEFAULT = 0; - NODE_PHYSICAL = 1; - NODE_TBD = 3; - NODE_SWITCH = 4; - NODE_HUB = 5; - NODE_WIRELESS_LAN = 6; - NODE_RJ45 = 7; - NODE_TUNNEL = 8; - NODE_KTUNNEL = 9; - NODE_EMANE = 10; - NODE_TAP_BRIDGE = 11; - NODE_PEER_TO_PEER = 12; - NODE_CONTROL_NET = 13; - NODE_EMANE_NET = 14; +message NodeType { + enum Enum { + DEFAULT = 0; + PHYSICAL = 1; + TBD = 3; + SWITCH = 4; + HUB = 5; + WIRELESS_LAN = 6; + RJ45 = 7; + TUNNEL = 8; + KTUNNEL = 9; + EMANE = 10; + TAP_BRIDGE = 11; + PEER_TO_PEER = 12; + CONTROL_NET = 13; + EMANE_NET = 14; + } } -enum ServiceValidationMode { - VALIDATION_BLOCKING = 0; - VALIDATION_NON_BLOCKING = 1; - VALIDATION_TIMER = 2; +message ServiceValidationMode { + enum Enum { + BLOCKING = 0; + NON_BLOCKING = 1; + TIMER = 2; + } } -enum ServiceAction { - SERVICE_START = 0; - SERVICE_STOP = 1; - SERVICE_RESTART = 2; - SERVICE_VALIDATE = 3; +message ServiceAction { + enum Enum { + START = 0; + STOP = 1; + RESTART = 2; + VALIDATE = 3; + } } -enum MobilityAction { - MOBILITY_START = 0; - MOBILITY_PAUSE = 1; - MOBILITY_STOP = 2; +message MobilityAction { + enum Enum { + START = 0; + PAUSE = 1; + STOP = 2; + } } -enum ExceptionLevel { - EXCEPTION_DEFAULT = 0; - EXCEPTION_FATAL = 1; - EXCEPTION_ERROR = 2; - EXCEPTION_WARNING = 3; - EXCEPTION_NOTICE = 4; +message ExceptionLevel { + enum Enum { + DEFAULT = 0; + FATAL = 1; + ERROR = 2; + WARNING = 3; + NOTICE = 4; + } } message Hook { - SessionState state = 1; + SessionState.Enum state = 1; string file = 2; bytes data = 3; } @@ -691,7 +707,7 @@ message NodeServiceData { repeated string configs = 4; repeated string startup = 5; repeated string validate = 6; - ServiceValidationMode validation_mode = 7; + ServiceValidationMode.Enum validation_mode = 7; int32 validation_timer = 8; repeated string shutdown = 9; string meta = 10; @@ -712,21 +728,21 @@ message ConfigOption { message Session { int32 id = 1; - SessionState state = 2; + SessionState.Enum state = 2; repeated Node nodes = 3; repeated Link links = 4; } message SessionSummary { int32 id = 1; - SessionState state = 2; + SessionState.Enum state = 2; int32 nodes = 3; } message Node { int32 id = 1; string name = 2; - NodeType type = 3; + NodeType.Enum type = 3; string model = 4; Position position = 5; repeated string services = 6; @@ -738,7 +754,7 @@ message Node { message Link { int32 node_one_id = 1; int32 node_two_id = 2; - LinkType type = 3; + LinkType.Enum type = 3; Interface interface_one = 4; Interface interface_two = 5; LinkOptions options = 6; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 1a191a61..7ef096a5 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -65,7 +65,7 @@ class TestGrpc: response = client.get_session(session.id) # then - assert response.session.state == core_pb2.STATE_DEFINITION + assert response.session.state == core_pb2.SessionState.DEFINITION assert len(response.session.nodes) == 1 assert len(response.session.links) == 0 @@ -164,11 +164,11 @@ class TestGrpc: # then with client.context_connect(): - response = client.set_session_state(session.id, core_pb2.STATE_DEFINITION) + response = client.set_session_state(session.id, core_pb2.SessionState.DEFINITION) # then assert response.result is True - assert session.state == core_pb2.STATE_DEFINITION + assert session.state == core_pb2.SessionState.DEFINITION def test_add_node(self, grpc_server): # given @@ -254,7 +254,7 @@ class TestGrpc: # then assert len(response.hooks) == 1 hook = response.hooks[0] - assert hook.state == EventTypes.RUNTIME_STATE.value + assert hook.state == core_pb2.SessionState.RUNTIME assert hook.file == file_name assert hook.data == file_data @@ -267,7 +267,7 @@ class TestGrpc: file_name = "test" file_data = "echo hello" with client.context_connect(): - response = client.add_hook(session.id, core_pb2.STATE_RUNTIME, file_name, file_data) + response = client.add_hook(session.id, core_pb2.SessionState.RUNTIME, file_name, file_data) # then assert response.result is True @@ -591,7 +591,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.mobility_action(session.id, wlan.objid, core_pb2.MOBILITY_STOP) + response = client.mobility_action(session.id, wlan.objid, core_pb2.MobilityAction.STOP) # then assert response.result is True @@ -666,16 +666,16 @@ class TestGrpc: session = grpc_server.coreemu.create_session() node = session.add_node() service_name = "IPForward" - validate = ("echo hello",) + validate = ["echo hello"] # then with client.context_connect(): - response = client.set_node_service(session.id, node.objid, service_name, (), validate, ()) + response = client.set_node_service(session.id, node.objid, service_name, [], validate, []) # then assert response.result is True service = session.services.get_service(node.objid, service_name, default_service=True) - assert service.validate == validate + assert service.validate == tuple(validate) def test_set_node_service_file(self, grpc_server): # given @@ -704,7 +704,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.service_action(session.id, node.objid, service_name, core_pb2.SERVICE_STOP) + response = client.service_action(session.id, node.objid, service_name, core_pb2.ServiceAction.STOP) # then assert response.result is True From 2ba8669c5c8d236c89001de897fa666a2b2b30ee Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Thu, 30 May 2019 10:15:00 -0700 Subject: [PATCH 0036/1992] corefx updated to use latest proto file, moved websocket into rest client --- corefx/src/main/java/com/core/Controller.java | 11 ++--------- .../java/com/core/client/grpc/CoreGrpcClient.java | 14 +++++++------- .../java/com/core/client/rest/CoreRestClient.java | 10 ++++++++++ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java index e54c535b..a24cb4eb 100644 --- a/corefx/src/main/java/com/core/Controller.java +++ b/corefx/src/main/java/com/core/Controller.java @@ -9,7 +9,6 @@ import com.core.ui.dialogs.*; import com.core.utils.ConfigUtils; import com.core.utils.Configuration; import com.core.utils.NodeTypeConfig; -import com.core.websocket.CoreWebSocket; import com.jfoenix.controls.JFXDecorator; import com.jfoenix.controls.JFXProgressBar; import javafx.application.Application; @@ -33,7 +32,6 @@ import org.apache.logging.log4j.Logger; import java.awt.event.ItemEvent; import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; import java.net.URL; import java.util.*; import java.util.concurrent.ExecutorService; @@ -63,7 +61,6 @@ public class Controller implements Initializable { // core client utilities private ICoreClient coreClient = new CoreGrpcClient(); - private CoreWebSocket coreWebSocket; // ui elements private NetworkGraph networkGraph = new NetworkGraph(this); @@ -91,15 +88,12 @@ public class Controller implements Initializable { private NodeTypeCreateDialog nodeTypeCreateDialog = new NodeTypeCreateDialog(this); public void connectToCore(String address, int port) { - coreWebSocket.stop(); - - ExecutorService executorService = Executors.newSingleThreadExecutor(); +// ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(() -> { try { - coreWebSocket.start(address, port); coreClient.setConnection(address, port); initialJoin(); - } catch (IOException | URISyntaxException ex) { + } catch (IOException ex) { Toast.error(String.format("Connection failure: %s", ex.getMessage()), ex); Platform.runLater(() -> connectDialog.showDialog()); } @@ -456,7 +450,6 @@ public class Controller implements Initializable { @Override public void initialize(URL location, ResourceBundle resources) { - coreWebSocket = new CoreWebSocket(this); configuration = ConfigUtils.load(); String address = configuration.getCoreAddress(); int port = configuration.getCorePort(); diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index b2a7fdfa..e4033c2f 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -39,7 +39,7 @@ public class CoreGrpcClient implements ICoreClient { .build(); CoreProto.Node.Builder builder = CoreProto.Node.newBuilder() .addAllServices(node.getServices()) - .setType(CoreProto.NodeType.forNumber(node.getType())) + .setType(CoreProto.NodeType.Enum.forNumber(node.getType())) .setPosition(position); if (node.getId() != null) { builder.setId(node.getId()); @@ -300,7 +300,7 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.GetSessionResponse response = blockingStub.getSession(request); Session session = new Session(); for (CoreProto.Node protoNode : response.getSession().getNodesList()) { - if (CoreProto.NodeType.NODE_PEER_TO_PEER == protoNode.getType()) { + if (CoreProto.NodeType.Enum.PEER_TO_PEER == protoNode.getType()) { continue; } @@ -498,7 +498,7 @@ public class CoreGrpcClient implements ICoreClient { .setSessionId(sessionId) .setNodeId(node.getId()) .setService(serviceName) - .setAction(CoreProto.ServiceAction.SERVICE_START) + .setAction(CoreProto.ServiceAction.Enum.START) .build(); try { return blockingStub.serviceAction(request).getResult(); @@ -513,7 +513,7 @@ public class CoreGrpcClient implements ICoreClient { .setSessionId(sessionId) .setNodeId(node.getId()) .setService(serviceName) - .setAction(CoreProto.ServiceAction.SERVICE_STOP) + .setAction(CoreProto.ServiceAction.Enum.STOP) .build(); try { return blockingStub.serviceAction(request).getResult(); @@ -528,7 +528,7 @@ public class CoreGrpcClient implements ICoreClient { .setSessionId(sessionId) .setNodeId(node.getId()) .setService(serviceName) - .setAction(CoreProto.ServiceAction.SERVICE_RESTART) + .setAction(CoreProto.ServiceAction.Enum.RESTART) .build(); try { return blockingStub.serviceAction(request).getResult(); @@ -543,7 +543,7 @@ public class CoreGrpcClient implements ICoreClient { .setSessionId(sessionId) .setNodeId(node.getId()) .setService(serviceName) - .setAction(CoreProto.ServiceAction.SERVICE_VALIDATE) + .setAction(CoreProto.ServiceAction.Enum.VALIDATE) .build(); try { return blockingStub.serviceAction(request).getResult(); @@ -1003,7 +1003,7 @@ public class CoreGrpcClient implements ICoreClient { CoreProto.MobilityActionRequest request = CoreProto.MobilityActionRequest.newBuilder() .setSessionId(sessionId) .setNodeId(node.getId()) - .setAction(CoreProto.MobilityAction.valueOf(action)) + .setAction(CoreProto.MobilityAction.Enum.valueOf(action)) .build(); try { CoreProto.MobilityActionResponse response = blockingStub.mobilityAction(request); diff --git a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java index 3624cdb5..886f7679 100644 --- a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java +++ b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java @@ -4,12 +4,14 @@ import com.core.Controller; import com.core.client.ICoreClient; import com.core.data.*; import com.core.utils.WebUtils; +import com.core.websocket.CoreWebSocket; import lombok.Data; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; import java.util.*; @Data @@ -19,6 +21,7 @@ public class CoreRestClient implements ICoreClient { private int port; private Integer sessionId; private SessionState sessionState; + private CoreWebSocket coreWebSocket; @Override public void setConnection(String address, int port) { @@ -416,5 +419,12 @@ public class CoreRestClient implements ICoreClient { @Override public void setupEventHandlers(Controller controller) throws IOException { + coreWebSocket.stop(); + coreWebSocket = new CoreWebSocket(controller); + try { + coreWebSocket.start(address, port); + } catch (URISyntaxException ex) { + throw new IOException("error starting web socket", ex); + } } } From df3a8980ed610be1169e60c97ec0f607bce28364 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Thu, 30 May 2019 13:07:45 -0700 Subject: [PATCH 0037/1992] grpc merged multiple event request/handlers into a singular event request for a session, which will return all events --- daemon/core/grpc/client.py | 71 +--------- daemon/core/grpc/server.py | 275 +++++++++++++++++-------------------- daemon/proto/core.proto | 45 ++---- daemon/tests/test_grpc.py | 18 ++- 4 files changed, 152 insertions(+), 257 deletions(-) diff --git a/daemon/core/grpc/client.py b/daemon/core/grpc/client.py index 15c0948b..3120aed9 100644 --- a/daemon/core/grpc/client.py +++ b/daemon/core/grpc/client.py @@ -263,33 +263,7 @@ class CoreGrpcClient(object): request = core_pb2.SetSessionStateRequest(session_id=session_id, state=state) return self.stub.SetSessionState(request) - def node_events(self, session_id, handler): - """ - Listen for session node events. - - :param int session_id: id of session - :param handler: handler for every event - :return: nothing - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.NodeEventsRequest(session_id=session_id) - stream = self.stub.NodeEvents(request) - start_streamer(stream, handler) - - def link_events(self, session_id, handler): - """ - Listen for session link events. - - :param int session_id: id of session - :param handler: handler for every event - :return: nothing - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.LinkEventsRequest(session_id=session_id) - stream = self.stub.LinkEvents(request) - start_streamer(stream, handler) - - def session_events(self, session_id, handler): + def events(self, session_id, handler): """ Listen for session events. @@ -298,47 +272,8 @@ class CoreGrpcClient(object): :return: nothing :raises grpc.RpcError: when session doesn't exist """ - request = core_pb2.SessionEventsRequest(session_id=session_id) - stream = self.stub.SessionEvents(request) - start_streamer(stream, handler) - - def config_events(self, session_id, handler): - """ - Listen for session config events. - - :param int session_id: id of session - :param handler: handler for every event - :return: nothing - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.ConfigEventsRequest(session_id=session_id) - stream = self.stub.ConfigEvents(request) - start_streamer(stream, handler) - - def exception_events(self, session_id, handler): - """ - Listen for session exception events. - - :param int session_id: id of session - :param handler: handler for every event - :return: nothing - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.ExceptionEventsRequest(session_id=session_id) - stream = self.stub.ExceptionEvents(request) - start_streamer(stream, handler) - - def file_events(self, session_id, handler): - """ - Listen for session file events. - - :param int session_id: id of session - :param handler: handler for every event - :return: nothing - :raises grpc.RpcError: when session doesn't exist - """ - request = core_pb2.FileEventsRequest(session_id=session_id) - stream = self.stub.FileEvents(request) + request = core_pb2.EventsRequest(session_id=session_id) + stream = self.stub.Events(request) start_streamer(stream, handler) def add_node(self, session_id, node): diff --git a/daemon/core/grpc/server.py b/daemon/core/grpc/server.py index eaba4419..07949ca4 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/grpc/server.py @@ -4,6 +4,7 @@ import os import tempfile import time from Queue import Queue, Empty +from core.data import NodeData, LinkData, EventData, ConfigData, ExceptionData, FileData import grpc from concurrent import futures @@ -262,177 +263,149 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session_proto = core_pb2.Session(state=session.state, nodes=nodes, links=links) return core_pb2.GetSessionResponse(session=session_proto) - def NodeEvents(self, request, context): + def Events(self, request, context): session = self.get_session(request.session_id, context) queue = Queue() session.node_handlers.append(queue.put) - - while self._is_running(context): - try: - node = queue.get(timeout=1) - position = core_pb2.Position(x=node.x_position, y=node.y_position) - services = node.services or "" - services = services.split("|") - node_proto = core_pb2.Node( - id=node.id, name=node.name, model=node.model, position=position, services=services) - node_event = core_pb2.NodeEvent(node=node_proto) - yield node_event - except Empty: - continue - - self._cancel_stream(context) - - def LinkEvents(self, request, context): - session = self.get_session(request.session_id, context) - queue = Queue() session.link_handlers.append(queue.put) - - while self._is_running(context): - try: - event = queue.get(timeout=1) - interface_one = None - if event.interface1_id is not None: - interface_one = core_pb2.Interface( - id=event.interface1_id, name=event.interface1_name, mac=convert_value(event.interface1_mac), - ip4=convert_value(event.interface1_ip4), ip4mask=event.interface1_ip4_mask, - ip6=convert_value(event.interface1_ip6), ip6mask=event.interface1_ip6_mask) - - interface_two = None - if event.interface2_id is not None: - interface_two = core_pb2.Interface( - id=event.interface2_id, name=event.interface2_name, mac=convert_value(event.interface2_mac), - ip4=convert_value(event.interface2_ip4), ip4mask=event.interface2_ip4_mask, - ip6=convert_value(event.interface2_ip6), ip6mask=event.interface2_ip6_mask) - - options = core_pb2.LinkOptions( - opaque=event.opaque, - jitter=event.jitter, - key=event.key, - mburst=event.mburst, - mer=event.mer, - per=event.per, - bandwidth=event.bandwidth, - burst=event.burst, - delay=event.delay, - dup=event.dup, - unidirectional=event.unidirectional - ) - link = core_pb2.Link( - type=event.link_type, node_one_id=event.node1_id, node_two_id=event.node2_id, - interface_one=interface_one, interface_two=interface_two, options=options) - link_event = core_pb2.LinkEvent(message_type=event.message_type, link=link) - yield link_event - except Empty: - continue - - self._cancel_stream(context) - - def SessionEvents(self, request, context): - session = self.get_session(request.session_id, context) - queue = Queue() + session.config_handlers.append(queue.put) + session.file_handlers.append(queue.put) + session.exception_handlers.append(queue.put) session.event_handlers.append(queue.put) while self._is_running(context): + event = core_pb2.Event() try: - event = queue.get(timeout=1) - event_time = event.time - if event_time is not None: - event_time = float(event_time) - session_event = core_pb2.SessionEvent( - node_id=event.node, - event=event.event_type, - name=event.name, - data=event.data, - time=event_time, - session_id=session.id - ) - yield session_event + data = queue.get(timeout=1) + if isinstance(data, NodeData): + event.node_event.CopyFrom(self._handle_node_event(data)) + elif isinstance(data, LinkData): + event.link_event.CopyFrom(self._handle_link_event(data)) + elif isinstance(data, EventData): + event.session_event.CopyFrom(self._handle_session_event(data)) + elif isinstance(data, ConfigData): + event.config_event.CopyFrom(self._handle_config_event(data)) + # TODO: remove when config events are fixed + event.config_event.session_id = session.id + elif isinstance(data, ExceptionData): + event.exception_event.CopyFrom(self._handle_exception_event(data)) + elif isinstance(data, FileData): + event.file_event.CopyFrom(self._handle_file_event(data)) + else: + logging.error("unknown event: %s", data) + continue + + yield event except Empty: continue + session.node_handlers.remove(queue.put) + session.link_handlers.remove(queue.put) + session.config_handlers.remove(queue.put) + session.file_handlers.remove(queue.put) + session.exception_handlers.remove(queue.put) + session.event_handlers.remove(queue.put) self._cancel_stream(context) - def ConfigEvents(self, request, context): - session = self.get_session(request.session_id, context) - queue = Queue() - session.config_handlers.append(queue.put) + def _handle_node_event(self, event): + position = core_pb2.Position(x=event.x_position, y=event.y_position) + services = event.services or "" + services = services.split("|") + node_proto = core_pb2.Node( + id=event.id, name=event.name, model=event.model, position=position, services=services) + return core_pb2.NodeEvent(node=node_proto) - while self._is_running(context): - try: - event = queue.get(timeout=1) - session_id = None - if event.session is not None: - session_id = int(event.session) - config_event = core_pb2.ConfigEvent( - message_type=event.message_type, - node_id=event.node, - object=event.object, - type=event.type, - captions=event.captions, - bitmap=event.bitmap, - data_values=event.data_values, - possible_values=event.possible_values, - groups=event.groups, - session_id=session_id, - interface=event.interface_number, - network_id=event.network_id, - opaque=event.opaque, - data_types=event.data_types - ) - yield config_event - except Empty: - continue + def _handle_link_event(self, event): + interface_one = None + if event.interface1_id is not None: + interface_one = core_pb2.Interface( + id=event.interface1_id, name=event.interface1_name, mac=convert_value(event.interface1_mac), + ip4=convert_value(event.interface1_ip4), ip4mask=event.interface1_ip4_mask, + ip6=convert_value(event.interface1_ip6), ip6mask=event.interface1_ip6_mask) - self._cancel_stream(context) + interface_two = None + if event.interface2_id is not None: + interface_two = core_pb2.Interface( + id=event.interface2_id, name=event.interface2_name, mac=convert_value(event.interface2_mac), + ip4=convert_value(event.interface2_ip4), ip4mask=event.interface2_ip4_mask, + ip6=convert_value(event.interface2_ip6), ip6mask=event.interface2_ip6_mask) - def ExceptionEvents(self, request, context): - session = self.get_session(request.session_id, context) - queue = Queue() - session.exception_handlers.append(queue.put) + options = core_pb2.LinkOptions( + opaque=event.opaque, + jitter=event.jitter, + key=event.key, + mburst=event.mburst, + mer=event.mer, + per=event.per, + bandwidth=event.bandwidth, + burst=event.burst, + delay=event.delay, + dup=event.dup, + unidirectional=event.unidirectional + ) + link = core_pb2.Link( + type=event.link_type, node_one_id=event.node1_id, node_two_id=event.node2_id, + interface_one=interface_one, interface_two=interface_two, options=options) + return core_pb2.LinkEvent(message_type=event.message_type, link=link) - while self._is_running(context): - try: - event = queue.get(timeout=1) - exception_event = core_pb2.ExceptionEvent( - node_id=event.node, - session_id=int(event.session), - level=event.level.value, - source=event.source, - date=event.date, - text=event.text, - opaque=event.opaque - ) - yield exception_event - except Empty: - continue + def _handle_session_event(self, event): + event_time = event.time + if event_time is not None: + event_time = float(event_time) + return core_pb2.SessionEvent( + node_id=event.node, + event=event.event_type, + name=event.name, + data=event.data, + time=event_time, + session_id=event.session + ) - self._cancel_stream(context) + def _handle_config_event(self, event): + session_id = None + if event.session is not None: + session_id = int(event.session) + return core_pb2.ConfigEvent( + message_type=event.message_type, + node_id=event.node, + object=event.object, + type=event.type, + captions=event.captions, + bitmap=event.bitmap, + data_values=event.data_values, + possible_values=event.possible_values, + groups=event.groups, + session_id=session_id, + interface=event.interface_number, + network_id=event.network_id, + opaque=event.opaque, + data_types=event.data_types + ) - def FileEvents(self, request, context): - session = self.get_session(request.session_id, context) - queue = Queue() - session.file_handlers.append(queue.put) + def _handle_exception_event(self, event): + return core_pb2.ExceptionEvent( + node_id=event.node, + session_id=int(event.session), + level=event.level.value, + source=event.source, + date=event.date, + text=event.text, + opaque=event.opaque + ) - while self._is_running(context): - try: - event = queue.get(timeout=1) - file_event = core_pb2.FileEvent( - message_type=event.message_type, - node_id=event.node, - name=event.name, - mode=event.mode, - number=event.number, - type=event.type, - source=event.source, - session_id=event.session, - data=event.data, - compressed_data=event.compressed_data - ) - yield file_event - except Empty: - continue - - self._cancel_stream(context) + def _handle_file_event(self, event): + return core_pb2.FileEvent( + message_type=event.message_type, + node_id=event.node, + name=event.name, + mode=event.mode, + number=event.number, + type=event.type, + source=event.source, + session_id=event.session, + data=event.data, + compressed_data=event.compressed_data + ) def AddNode(self, request, context): logging.debug("add node: %s", request) diff --git a/daemon/proto/core.proto b/daemon/proto/core.proto index f159c8af..012a9700 100644 --- a/daemon/proto/core.proto +++ b/daemon/proto/core.proto @@ -27,17 +27,7 @@ service CoreApi { } // event streams - rpc NodeEvents (NodeEventsRequest) returns (stream NodeEvent) { - } - rpc LinkEvents (LinkEventsRequest) returns (stream LinkEvent) { - } - rpc SessionEvents (SessionEventsRequest) returns (stream SessionEvent) { - } - rpc ConfigEvents (ConfigEventsRequest) returns (stream ConfigEvent) { - } - rpc ExceptionEvents (ExceptionEventsRequest) returns (stream ExceptionEvent) { - } - rpc FileEvents (FileEventsRequest) returns (stream FileEvent) { + rpc Events (EventsRequest) returns (stream Event) { } // node rpc @@ -199,27 +189,30 @@ message SetSessionStateResponse { bool result = 1; } -message NodeEventsRequest { +message EventsRequest { int32 session_id = 1; } +message Event { + oneof event_type { + SessionEvent session_event = 1; + NodeEvent node_event = 2; + LinkEvent link_event = 3; + ConfigEvent config_event = 4; + ExceptionEvent exception_event = 5; + FileEvent file_event = 6; + } +} + message NodeEvent { Node node = 1; } -message LinkEventsRequest { - int32 session_id = 1; -} - message LinkEvent { MessageType.Enum message_type = 1; Link link = 2; } -message SessionEventsRequest { - int32 session_id = 1; -} - message SessionEvent { int32 node_id = 1; int32 event = 2; @@ -229,10 +222,6 @@ message SessionEvent { int32 session_id = 6; } -message ConfigEventsRequest { - int32 session_id = 1; -} - message ConfigEvent { MessageType.Enum message_type = 1; int32 node_id = 2; @@ -250,10 +239,6 @@ message ConfigEvent { string opaque = 14; } -message ExceptionEventsRequest { - int32 session_id = 1; -} - message ExceptionEvent { int32 node_id = 1; int32 session_id = 2; @@ -264,10 +249,6 @@ message ExceptionEvent { string opaque = 7; } -message FileEventsRequest { - int32 session_id = 1; -} - message FileEvent { MessageType.Enum message_type = 1; int32 node_id = 2; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 7ef096a5..f9e03bc0 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -718,11 +718,12 @@ class TestGrpc: queue = Queue() def handle_event(event_data): + assert event_data.HasField("node_event") queue.put(event_data) # then with client.context_connect(): - client.node_events(session.id, handle_event) + client.events(session.id, handle_event) time.sleep(0.1) session.broadcast_node(node_data) @@ -741,11 +742,12 @@ class TestGrpc: queue = Queue() def handle_event(event_data): + assert event_data.HasField("link_event") queue.put(event_data) # then with client.context_connect(): - client.link_events(session.id, handle_event) + client.events(session.id, handle_event) time.sleep(0.1) session.broadcast_link(link_data) @@ -759,11 +761,12 @@ class TestGrpc: queue = Queue() def handle_event(event_data): + assert event_data.HasField("session_event") queue.put(event_data) # then with client.context_connect(): - client.session_events(session.id, handle_event) + client.events(session.id, handle_event) time.sleep(0.1) event = EventData(event_type=EventTypes.RUNTIME_STATE.value, time="%s" % time.time()) session.broadcast_event(event) @@ -778,11 +781,12 @@ class TestGrpc: queue = Queue() def handle_event(event_data): + assert event_data.HasField("config_event") queue.put(event_data) # then with client.context_connect(): - client.config_events(session.id, handle_event) + client.events(session.id, handle_event) time.sleep(0.1) session_config = session.options.get_configs() config_data = ConfigShim.config_data(0, None, ConfigFlags.UPDATE.value, session.options, session_config) @@ -798,11 +802,12 @@ class TestGrpc: queue = Queue() def handle_event(event_data): + assert event_data.HasField("exception_event") queue.put(event_data) # then with client.context_connect(): - client.exception_events(session.id, handle_event) + client.events(session.id, handle_event) time.sleep(0.1) session.exception(ExceptionLevels.FATAL, "test", None, "exception message") @@ -817,11 +822,12 @@ class TestGrpc: queue = Queue() def handle_event(event_data): + assert event_data.HasField("file_event") queue.put(event_data) # then with client.context_connect(): - client.file_events(session.id, handle_event) + client.events(session.id, handle_event) time.sleep(0.1) file_data = session.services.get_service_file(node, "IPForward", "ipforward.sh") session.broadcast_file(file_data) From 4dd6dc1837e13ce4910a6a95747617e552474dbc Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Thu, 30 May 2019 13:27:54 -0700 Subject: [PATCH 0038/1992] updated corefx to use consolidated event streaming --- .../com/core/client/grpc/CoreGrpcClient.java | 210 +++++++----------- 1 file changed, 82 insertions(+), 128 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index e4033c2f..241360f1 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -1073,149 +1073,103 @@ public class CoreGrpcClient implements ICoreClient { logger.info("setting up event handlers"); handlingEvents = true; try { - handleSessionEvents(controller); - handleNodeEvents(controller); - handleExceptionEvents(controller); - handleConfigEvents(controller); - handleLinkEvents(controller); - handleFileEvents(controller); + CoreProto.EventsRequest request = CoreProto.EventsRequest.newBuilder() + .setSessionId(sessionId) + .build(); + + Iterator events = blockingStub.events(request); + executorService.submit(() -> { + try { + while (handlingEvents) { + CoreProto.Event event = events.next(); + logger.info("handling event: {}", event); + switch (event.getEventTypeCase()) { + case SESSION_EVENT: + handleSessionEvents(controller, event.getSessionEvent()); + break; + case NODE_EVENT: + handleNodeEvents(controller, event.getNodeEvent()); + break; + case LINK_EVENT: + handleLinkEvents(controller, event.getLinkEvent()); + break; + case CONFIG_EVENT: + handleConfigEvents(controller, event.getConfigEvent()); + break; + case EXCEPTION_EVENT: + handleExceptionEvents(controller, event.getExceptionEvent()); + break; + case FILE_EVENT: + handleFileEvents(controller, event.getFileEvent()); + break; + default: + logger.error("unknown event type: {}", event.getEventTypeCase()); + } + } + } catch (StatusRuntimeException ex) { + logger.error("error handling session events", ex); + } + }); } catch (StatusRuntimeException ex) { throw new IOException("setup event handlers error", ex); } } - private void handleSessionEvents(Controller controller) { - CoreProto.SessionEventsRequest request = CoreProto.SessionEventsRequest.newBuilder() - .setSessionId(sessionId) - .build(); - Iterator events = blockingStub.sessionEvents(request); - executorService.submit(() -> { - try { - while (handlingEvents) { - CoreProto.SessionEvent event = events.next(); - logger.info("session event: {}", event); - SessionState state = SessionState.get(event.getEvent()); - if (state == null) { - logger.warn("unknown event type: {}", event.getEvent()); - continue; - } + private void handleSessionEvents(Controller controller, CoreProto.SessionEvent event) { + logger.info("session event: {}", event); + SessionState state = SessionState.get(event.getEvent()); + if (state == null) { + logger.warn("unknown session event: {}", event.getEvent()); + return; + } - // session state event - if (state.getValue() <= 6) { - logger.info("event updating session state: {}", state); - updateState(state); - // mobility script event - } else if (state.getValue() <= 9) { - Integer nodeId = event.getNodeId(); - String[] values = event.getData().toStringUtf8().split("\\s+"); - Integer start = Integer.parseInt(values[0].split("=")[1]); - Integer end = Integer.parseInt(values[1].split("=")[1]); - logger.info(String.format("node(%s) mobility event (%s) - start(%s) stop(%s)", - nodeId, state, start, end)); - logger.info("all dialogs: {}", controller.getMobilityPlayerDialogs().keySet()); - MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(nodeId); - mobilityPlayerDialog.event(state, start, end); - } - } - } catch (StatusRuntimeException ex) { - logger.error("error handling session events", ex); - } - }); + // session state event + if (state.getValue() <= 6) { + logger.info("event updating session state: {}", state); + updateState(state); + // mobility script event + } else if (state.getValue() <= 9) { + Integer nodeId = event.getNodeId(); + String[] values = event.getData().toStringUtf8().split("\\s+"); + Integer start = Integer.parseInt(values[0].split("=")[1]); + Integer end = Integer.parseInt(values[1].split("=")[1]); + logger.info(String.format("node(%s) mobility event (%s) - start(%s) stop(%s)", + nodeId, state, start, end)); + logger.info("all dialogs: {}", controller.getMobilityPlayerDialogs().keySet()); + MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(nodeId); + mobilityPlayerDialog.event(state, start, end); + } } - private void handleNodeEvents(Controller controller) { - CoreProto.NodeEventsRequest request = CoreProto.NodeEventsRequest.newBuilder().setSessionId(sessionId) - .build(); - Iterator events = blockingStub.nodeEvents(request); - executorService.submit(() -> { - try { - while (handlingEvents) { - CoreProto.NodeEvent event = events.next(); - logger.info("node event: {}", event); - CoreNode node = protoToNode(event.getNode()); - controller.getNetworkGraph().setNodeLocation(node); - } - } catch (StatusRuntimeException ex) { - logger.error("error handling node events", ex); - } - }); + private void handleNodeEvents(Controller controller, CoreProto.NodeEvent event) { + logger.info("node event: {}", event); + CoreNode node = protoToNode(event.getNode()); + controller.getNetworkGraph().setNodeLocation(node); } - private void handleExceptionEvents(Controller controller) { - CoreProto.ExceptionEventsRequest request = CoreProto.ExceptionEventsRequest.newBuilder() - .setSessionId(sessionId) - .build(); - Iterator events = blockingStub.exceptionEvents(request); - executorService.submit(() -> { - try { - while (handlingEvents) { - CoreProto.ExceptionEvent event = events.next(); - logger.info("exception event: {}", event); - } - } catch (StatusRuntimeException ex) { - logger.error("error handling exception events", ex); - } - }); + private void handleExceptionEvents(Controller controller, CoreProto.ExceptionEvent event) { + logger.info("exception event: {}", event); } - private void handleConfigEvents(Controller controller) { - CoreProto.ConfigEventsRequest request = CoreProto.ConfigEventsRequest.newBuilder() - .setSessionId(sessionId) - .build(); - Iterator events = blockingStub.configEvents(request); - executorService.submit(() -> { - try { - while (handlingEvents) { - CoreProto.ConfigEvent event = events.next(); - logger.info("config event: {}", event); - } - } catch (StatusRuntimeException ex) { - logger.error("error handling config events", ex); - } - }); + private void handleConfigEvents(Controller controller, CoreProto.ConfigEvent event) { + logger.info("config event: {}", event); } - private void handleLinkEvents(Controller controller) { - CoreProto.LinkEventsRequest request = CoreProto.LinkEventsRequest.newBuilder() - .setSessionId(sessionId) - .build(); - Iterator events = blockingStub.linkEvents(request); - executorService.submit(() -> { - try { - while (handlingEvents) { - CoreProto.LinkEvent event = events.next(); - logger.info("link event: {}", event); - CoreLink link = protoToLink(event.getLink()); - MessageFlags flag = MessageFlags.get(event.getMessageTypeValue()); - if (MessageFlags.DELETE == flag) { - logger.info("delete"); - controller.getNetworkGraph().removeWirelessLink(link); - } else if (MessageFlags.ADD == flag) { - link.setLoaded(true); - controller.getNetworkGraph().addLink(link); - } - controller.getNetworkGraph().getGraphViewer().repaint(); - } - } catch (StatusRuntimeException ex) { - logger.error("error handling link events", ex); - } - }); + private void handleLinkEvents(Controller controller, CoreProto.LinkEvent event) { + logger.info("link event: {}", event); + CoreLink link = protoToLink(event.getLink()); + MessageFlags flag = MessageFlags.get(event.getMessageTypeValue()); + if (MessageFlags.DELETE == flag) { + logger.info("delete"); + controller.getNetworkGraph().removeWirelessLink(link); + } else if (MessageFlags.ADD == flag) { + link.setLoaded(true); + controller.getNetworkGraph().addLink(link); + } + controller.getNetworkGraph().getGraphViewer().repaint(); } - private void handleFileEvents(Controller controller) { - CoreProto.FileEventsRequest request = CoreProto.FileEventsRequest.newBuilder() - .setSessionId(sessionId) - .build(); - Iterator events = blockingStub.fileEvents(request); - executorService.submit(() -> { - try { - while (handlingEvents) { - CoreProto.FileEvent event = events.next(); - logger.info("file event: {}", event); - } - } catch (StatusRuntimeException ex) { - logger.error("error handling file events", ex); - } - }); + private void handleFileEvents(Controller controller, CoreProto.FileEvent event) { + logger.info("file event: {}", event); } } From e063fcd4fe5015920327e8b74d6e950ef937a345 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Thu, 30 May 2019 16:31:48 -0700 Subject: [PATCH 0039/1992] core-daemon now allows configuration for grpc address and port, defaults to localhost, also fixed grpc example due to enum and event changes --- daemon/core/grpc/server.py | 2 +- daemon/examples/grpc/switch.py | 13 ++++--------- daemon/scripts/core-daemon | 9 ++++++++- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/daemon/core/grpc/server.py b/daemon/core/grpc/server.py index 07949ca4..daa55cc9 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/grpc/server.py @@ -123,7 +123,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def _cancel_stream(self, context): context.abort(grpc.StatusCode.CANCELLED, "server stopping") - def listen(self, address="[::]:50051"): + def listen(self, address): logging.info("starting grpc api: %s", address) self.server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) core_pb2_grpc.add_CoreApiServicer_to_server(self, self.server) diff --git a/daemon/examples/grpc/switch.py b/daemon/examples/grpc/switch.py index 52c3131e..42143184 100644 --- a/daemon/examples/grpc/switch.py +++ b/daemon/examples/grpc/switch.py @@ -18,19 +18,14 @@ def main(): # handle events session may broadcast session_id = response.session_id - core.exception_events(session_id, log_event) - core.node_events(session_id, log_event) - core.session_events(session_id, log_event) - core.link_events(session_id, log_event) - core.file_events(session_id, log_event) - core.config_events(session_id, log_event) + core.events(session_id, log_event) # change session state - response = core.set_session_state(session_id, core_pb2.STATE_CONFIGURATION) + response = core.set_session_state(session_id, core_pb2.SessionState.CONFIGURATION) logging.info("set session state: %s", response) # create switch node - switch = core_pb2.Node(type=core_pb2.NODE_SWITCH) + switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH) response = core.add_node(session_id, switch) logging.info("created switch: %s", response) switch_id = response.node_id @@ -52,7 +47,7 @@ def main(): logging.info("created link: %s", response) # change session state - response = core.set_session_state(session_id, core_pb2.STATE_INSTANTIATION) + response = core.set_session_state(session_id, core_pb2.SessionState.INSTANTIATION) logging.info("set session state: %s", response) diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index a1c14cec..9bffdc28 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -57,7 +57,8 @@ def cored(cfg): # initialize grpc api if cfg["grpc"] == "True": grpc_server = CoreGrpcServer(server.coreemu) - grpc_thread = threading.Thread(target=grpc_server.listen) + grpc_address = "%s:%s" % (cfg["grpcaddress"], cfg["grpcport"]) + grpc_thread = threading.Thread(target=grpc_server.listen, args=(grpc_address,)) grpc_thread.daemon = True grpc_thread.start() @@ -80,6 +81,8 @@ def get_merged_config(filename): "listenaddr": "localhost", "xmlfilever": "1.0", "numthreads": "1", + "grpcport": "50051", + "grpcaddress": "localhost" } parser = argparse.ArgumentParser( @@ -92,6 +95,10 @@ def get_merged_config(filename): help="number of server threads; default = %s" % defaults["numthreads"]) parser.add_argument("--ovs", action="store_true", help="enable experimental ovs mode, default is false") parser.add_argument("--grpc", action="store_true", help="enable grpc api, default is false") + parser.add_argument("--grpc-port", dest="grpcport", + help="grpc port to listen on; default %s" % defaults["grpcport"]) + parser.add_argument("--grpc-address", dest="grpcaddress", + help="grpc address to listen on; default %s" % defaults["grpcaddress"]) # parse command line options args = parser.parse_args() From 1890db599123ba69f5b904202d04dd7eaf18d387 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Fri, 31 May 2019 11:46:47 -0700 Subject: [PATCH 0040/1992] added grpc for a node command and fixed grpc unit tests --- daemon/core/grpc/client.py | 13 +++++++++++++ daemon/core/grpc/server.py | 7 +++++++ daemon/proto/core.proto | 12 ++++++++++++ daemon/tests/conftest.py | 2 +- daemon/tests/test_grpc.py | 20 +++++++++++++++++++- 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/daemon/core/grpc/client.py b/daemon/core/grpc/client.py index 3120aed9..777536f3 100644 --- a/daemon/core/grpc/client.py +++ b/daemon/core/grpc/client.py @@ -329,6 +329,19 @@ class CoreGrpcClient(object): request = core_pb2.DeleteNodeRequest(session_id=session_id, node_id=node_id) return self.stub.DeleteNode(request) + def node_command(self, session_id, node_id, command): + """ + Send command to a node and get the output. + + :param int session_id: session id + :param int node_id: node id + :return: response with command combined stdout/stderr + :rtype: core_pb2.NodeCommandResponse + :raises grpc.RpcError: when session or node doesn't exist + """ + request = core_pb2.NodeCommandRequest(session_id=session_id, node_id=node_id, command=command) + return self.stub.NodeCommand(request) + def get_node_links(self, session_id, node_id): """ Get current links for a node. diff --git a/daemon/core/grpc/server.py b/daemon/core/grpc/server.py index daa55cc9..9ae00dff 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/grpc/server.py @@ -484,6 +484,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): result = session.delete_node(request.node_id) return core_pb2.DeleteNodeResponse(result=result) + def NodeCommand(self, request, context): + logging.debug("sending node command: %s", request) + session = self.get_session(request.session_id, context) + node = self.get_node(session, request.node_id, context) + _, output = node.cmd_output(request.command) + return core_pb2.NodeCommandResponse(output=output) + def GetNodeLinks(self, request, context): logging.debug("get node links: %s", request) session = self.get_session(request.session_id, context) diff --git a/daemon/proto/core.proto b/daemon/proto/core.proto index 012a9700..d6c1a646 100644 --- a/daemon/proto/core.proto +++ b/daemon/proto/core.proto @@ -39,6 +39,8 @@ service CoreApi { } rpc DeleteNode (DeleteNodeRequest) returns (DeleteNodeResponse) { } + rpc NodeCommand (NodeCommandRequest) returns (NodeCommandResponse) { + } // link rpc rpc GetNodeLinks (GetNodeLinksRequest) returns (GetNodeLinksResponse) { @@ -300,6 +302,16 @@ message DeleteNodeResponse { bool result = 1; } +message NodeCommandRequest { + int32 session_id = 1; + int32 node_id = 2; + string command = 3; +} + +message NodeCommandResponse { + string output = 1; +} + message GetNodeLinksRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/tests/conftest.py b/daemon/tests/conftest.py index 3bd32121..9f7f07f1 100644 --- a/daemon/tests/conftest.py +++ b/daemon/tests/conftest.py @@ -212,7 +212,7 @@ class CoreServerTest(object): def grpc_server(): coremu = CoreEmu() grpc_server = CoreGrpcServer(coremu) - thread = threading.Thread(target=grpc_server.listen) + thread = threading.Thread(target=grpc_server.listen, args=("localhost:50051",)) thread.daemon = True thread.start() time.sleep(0.1) diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index f9e03bc0..905eb006 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -1,5 +1,4 @@ import time - from Queue import Queue import grpc @@ -8,6 +7,7 @@ import pytest from core.conf import ConfigShim from core.data import EventData from core.emane.ieee80211abg import EmaneIeee80211abgModel +from core.emulator.emudata import NodeOptions from core.enumerations import NodeTypes, EventTypes, ConfigFlags, ExceptionLevels from core.grpc import core_pb2 from core.grpc.client import CoreGrpcClient @@ -239,6 +239,24 @@ class TestGrpc: with pytest.raises(KeyError): assert session.get_object(node.objid) + def test_node_command(self, grpc_server): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + session.set_state(EventTypes.CONFIGURATION_STATE) + node_options = NodeOptions(model="Host") + node = session.add_node(node_options=node_options) + session.instantiate() + output = "hello world" + + # then + command = "echo %s" % output + with client.context_connect(): + response = client.node_command(session.id, node.objid, command) + + # then + assert response.output == output + def test_get_hooks(self, grpc_server): # given client = CoreGrpcClient() From 5542dbbce293d71632ce10972d188db6f7d4d9df Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Fri, 31 May 2019 12:38:35 -0700 Subject: [PATCH 0041/1992] corefx added node command usage from grpc and removed mailing list link --- corefx/src/main/java/com/core/Controller.java | 6 ------ .../java/com/core/client/grpc/CoreGrpcClient.java | 13 +++++++++++-- corefx/src/main/resources/fxml/main.fxml | 1 - 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java index a24cb4eb..ef04091e 100644 --- a/corefx/src/main/java/com/core/Controller.java +++ b/corefx/src/main/java/com/core/Controller.java @@ -88,7 +88,6 @@ public class Controller implements Initializable { private NodeTypeCreateDialog nodeTypeCreateDialog = new NodeTypeCreateDialog(this); public void connectToCore(String address, int port) { -// ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(() -> { try { coreClient.setConnection(address, port); @@ -345,11 +344,6 @@ public class Controller implements Initializable { application.getHostServices().showDocument("http://coreemu.github.io/core/"); } - @FXML - private void onHelpMenuMailingList(ActionEvent event) { - application.getHostServices().showDocument("https://publists.nrl.navy.mil/mailman/listinfo/core-users"); - } - @FXML private void onOpenXmlAction() { FileChooser fileChooser = new FileChooser(); diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 241360f1..f79cbec2 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -723,8 +723,17 @@ public class CoreGrpcClient implements ICoreClient { @Override public String nodeCommand(CoreNode node, String command) throws IOException { - // TODO: convert node command - return null; + CoreProto.NodeCommandRequest request = CoreProto.NodeCommandRequest.newBuilder() + .setSessionId(sessionId) + .setNodeId(node.getId()) + .setCommand(command) + .build(); + try { + CoreProto.NodeCommandResponse response = blockingStub.nodeCommand(request); + return response.getOutput(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override diff --git a/corefx/src/main/resources/fxml/main.fxml b/corefx/src/main/resources/fxml/main.fxml index 848b9c83..58641ed9 100644 --- a/corefx/src/main/resources/fxml/main.fxml +++ b/corefx/src/main/resources/fxml/main.fxml @@ -51,7 +51,6 @@ - From 4c1d7bfb22856a7c737283a9212383b4efd29fe8 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Fri, 31 May 2019 14:19:23 -0700 Subject: [PATCH 0042/1992] grpc added simple throughputs stream events --- daemon/core/grpc/client.py | 11 +++++++ daemon/core/grpc/server.py | 60 +++++++++++++++++++++++++++++++++++++- daemon/proto/core.proto | 23 ++++++++++++++- daemon/tests/test_grpc.py | 17 +++++++++++ 4 files changed, 109 insertions(+), 2 deletions(-) diff --git a/daemon/core/grpc/client.py b/daemon/core/grpc/client.py index 777536f3..53b0538e 100644 --- a/daemon/core/grpc/client.py +++ b/daemon/core/grpc/client.py @@ -276,6 +276,17 @@ class CoreGrpcClient(object): stream = self.stub.Events(request) start_streamer(stream, handler) + def throughputs(self, handler): + """ + Listen for throughput events with information for interfaces and bridges. + + :param handler: handler for every event + :return: nothing + """ + request = core_pb2.ThroughputsRequest() + stream = self.stub.Throughputs(request) + start_streamer(stream, handler) + def add_node(self, session_id, node): """ Add node to session. diff --git a/daemon/core/grpc/server.py b/daemon/core/grpc/server.py index 9ae00dff..aedac46d 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/grpc/server.py @@ -1,14 +1,15 @@ import atexit import logging import os +import re import tempfile import time from Queue import Queue, Empty -from core.data import NodeData, LinkData, EventData, ConfigData, ExceptionData, FileData import grpc from concurrent import futures +from core.data import NodeData, LinkData, EventData, ConfigData, ExceptionData, FileData from core.emulator.emudata import NodeOptions, InterfaceData, LinkOptions from core.enumerations import NodeTypes, EventTypes, LinkTypes from core.grpc import core_pb2 @@ -19,6 +20,7 @@ from core.mobility import BasicRangeModel, Ns2ScriptedMobility from core.service import ServiceManager _ONE_DAY_IN_SECONDS = 60 * 60 * 24 +_INTERFACE_REGEX = re.compile("\d+") def convert_value(value): @@ -105,6 +107,22 @@ def convert_link(session, link_data): ) +def get_net_stats(): + with open("/proc/net/dev", "r") as f: + data = f.readlines()[2:] + + stats = {} + for line in data: + line = line.strip() + if not line: + continue + line = line.split() + line[0] = line[0].strip(":") + stats[line[0]] = {"rx": float(line[1]), "tx": float(line[9])} + + return stats + + class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def __init__(self, coreemu): super(CoreGrpcServer, self).__init__() @@ -407,6 +425,46 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): compressed_data=event.compressed_data ) + def Throughputs(self, request, context): + delay = 3 + last_check = None + last_stats = None + while self._is_running(context): + now = time.time() + stats = get_net_stats() + + # calculate average + if last_check is not None: + interval = now - last_check + throughputs_event = core_pb2.ThroughputsEvent() + for key, current_rxtx in stats.iteritems(): + previous_rxtx = last_stats.get(key) + if not previous_rxtx: + continue + rx_kbps = (current_rxtx["rx"] - previous_rxtx["rx"]) * 8.0 / interval + tx_kbps = (current_rxtx["tx"] - previous_rxtx["tx"]) * 8.0 / interval + throughput = rx_kbps + tx_kbps + print "%s - %s" % (key, throughput) + if key.startswith("veth"): + key = key.split(".") + node_id = int(_INTERFACE_REGEX.search(key[0]).group()) + interface_id = int(key[1]) + interface_throughput = throughputs_event.interface_throughputs.add() + interface_throughput.node_id = node_id + interface_throughput.interface_id = interface_id + interface_throughput.throughput = throughput + elif key.startswith("b."): + node_id = int(key.split(".")[1]) + bridge_throughput = throughputs_event.bridge_throughputs.add() + bridge_throughput.node_id = node_id + bridge_throughput.throughput = throughput + + yield throughputs_event + + last_check = now + last_stats = stats + time.sleep(delay) + def AddNode(self, request, context): logging.debug("add node: %s", request) session = self.get_session(request.session_id, context) diff --git a/daemon/proto/core.proto b/daemon/proto/core.proto index d6c1a646..9b84f19a 100644 --- a/daemon/proto/core.proto +++ b/daemon/proto/core.proto @@ -26,9 +26,11 @@ service CoreApi { rpc SetSessionState (SetSessionStateRequest) returns (SetSessionStateResponse) { } - // event streams + // streams rpc Events (EventsRequest) returns (stream Event) { } + rpc Throughputs (ThroughputsRequest) returns (stream ThroughputsEvent) { + } // node rpc rpc AddNode (AddNodeRequest) returns (AddNodeResponse) { @@ -195,6 +197,25 @@ message EventsRequest { int32 session_id = 1; } +message ThroughputsRequest { +} + +message ThroughputsEvent { + repeated BridgeThroughput bridge_throughputs = 1; + repeated InterfaceThroughput interface_throughputs = 2; +} + +message InterfaceThroughput { + int32 node_id = 1; + int32 interface_id = 2; + double throughput = 3; +} + +message BridgeThroughput { + int32 node_id = 1; + double throughput = 2; +} + message Event { oneof event_type { SessionEvent session_event = 1; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 905eb006..13136931 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -772,6 +772,23 @@ class TestGrpc: # then queue.get(timeout=5) + def test_throughputs(self, grpc_server): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + queue = Queue() + + def handle_event(event_data): + queue.put(event_data) + + # then + with client.context_connect(): + client.throughputs(handle_event) + time.sleep(0.1) + + # then + queue.get(timeout=5) + def test_session_events(self, grpc_server): # given client = CoreGrpcClient() From b075181796ce35c9c581e8d7a1a37556acf455f3 Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Fri, 31 May 2019 16:06:24 -0700 Subject: [PATCH 0043/1992] corefx got throughput working using grpc, removed unused session nodes menu option --- corefx/src/main/java/com/core/Controller.java | 7 +-- .../java/com/core/client/ICoreClient.java | 2 +- .../com/core/client/grpc/CoreGrpcClient.java | 51 +++++++++++++++++-- .../com/core/client/rest/CoreRestClient.java | 2 +- corefx/src/main/resources/fxml/main.fxml | 1 - 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/corefx/src/main/java/com/core/Controller.java b/corefx/src/main/java/com/core/Controller.java index ef04091e..7a5d9be8 100644 --- a/corefx/src/main/java/com/core/Controller.java +++ b/corefx/src/main/java/com/core/Controller.java @@ -400,11 +400,6 @@ public class Controller implements Initializable { } } - @FXML - private void onSessionNodesMenu(ActionEvent event) { - - } - @FXML private void onSessionHooksMenu(ActionEvent event) { hooksDialog.showDialog(); @@ -505,7 +500,7 @@ public class Controller implements Initializable { @Override protected Boolean call() throws Exception { if (throughputMenuItem.isSelected()) { - return coreClient.startThroughput(); + return coreClient.startThroughput(Controller.this); } else { return coreClient.stopThroughput(); } diff --git a/corefx/src/main/java/com/core/client/ICoreClient.java b/corefx/src/main/java/com/core/client/ICoreClient.java index 531f43dd..7e0350aa 100644 --- a/corefx/src/main/java/com/core/client/ICoreClient.java +++ b/corefx/src/main/java/com/core/client/ICoreClient.java @@ -19,7 +19,7 @@ public interface ICoreClient { Integer currentSession(); - boolean startThroughput() throws IOException; + boolean startThroughput(Controller controller) throws IOException; boolean stopThroughput() throws IOException; diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index f79cbec2..6ad6b066 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -7,6 +7,7 @@ import com.core.client.rest.WlanConfig; import com.core.data.*; import com.core.ui.dialogs.MobilityPlayerDialog; import com.google.protobuf.ByteString; +import io.grpc.Context; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; import io.grpc.StatusRuntimeException; @@ -31,6 +32,7 @@ public class CoreGrpcClient implements ICoreClient { private ManagedChannel channel; private final ExecutorService executorService = Executors.newFixedThreadPool(6); private boolean handlingEvents = false; + private boolean handlingThroughputs = false; private CoreProto.Node nodeToProto(CoreNode node) { CoreProto.Position position = CoreProto.Position.newBuilder() @@ -226,15 +228,54 @@ public class CoreGrpcClient implements ICoreClient { } @Override - public boolean startThroughput() throws IOException { - // TODO: convert throughput - return false; + public boolean startThroughput(Controller controller) throws IOException { + CoreProto.ThroughputsRequest request = CoreProto.ThroughputsRequest.newBuilder().build(); + try { + handlingThroughputs = true; + executorService.submit(() -> { + Context.CancellableContext context = Context.current().withCancellation(); + context.run(() -> { + try { + Iterator iterator = blockingStub.throughputs(request); + while (handlingThroughputs) { + CoreProto.ThroughputsEvent event = iterator.next(); + logger.info("handling throughputs: {}", event); + Throughputs throughputs = new Throughputs(); + for (CoreProto.BridgeThroughput protoBridge : event.getBridgeThroughputsList()) { + BridgeThroughput bridge = new BridgeThroughput(); + bridge.setNode(protoBridge.getNodeId()); + bridge.setThroughput(protoBridge.getThroughput()); + throughputs.getBridges().add(bridge); + } + for (CoreProto.InterfaceThroughput protoInterface : event.getInterfaceThroughputsList()) { + InterfaceThroughput interfaceThroughput = new InterfaceThroughput(); + interfaceThroughput.setNode(protoInterface.getNodeId()); + interfaceThroughput.setNodeInterface(protoInterface.getInterfaceId()); + interfaceThroughput.setThroughput(protoInterface.getThroughput()); + throughputs.getInterfaces().add(interfaceThroughput); + } + controller.handleThroughputs(throughputs); + } + logger.info("exiting handling throughputs"); + } catch (StatusRuntimeException ex) { + logger.error("error handling session events", ex); + } finally { + context.cancel(null); + context.close(); + } + }); + }); + return true; + } catch (StatusRuntimeException ex) { + throw new IOException("setup event handlers error", ex); + } } @Override public boolean stopThroughput() throws IOException { - // TODO: convert throughput - return false; + logger.info("cancelling throughputs"); + handlingThroughputs = false; + return true; } @Override diff --git a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java index 886f7679..cecc5bf9 100644 --- a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java +++ b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java @@ -149,7 +149,7 @@ public class CoreRestClient implements ICoreClient { } @Override - public boolean startThroughput() throws IOException { + public boolean startThroughput(Controller controller) throws IOException { String url = getUrl("throughput/start"); return WebUtils.putJson(url); } diff --git a/corefx/src/main/resources/fxml/main.fxml b/corefx/src/main/resources/fxml/main.fxml index 58641ed9..e6b6d311 100644 --- a/corefx/src/main/resources/fxml/main.fxml +++ b/corefx/src/main/resources/fxml/main.fxml @@ -35,7 +35,6 @@ - From 43e18d820f60d6dfdd745c961bbd750863a48e8a Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Fri, 31 May 2019 16:53:16 -0700 Subject: [PATCH 0044/1992] corefx properly cancel server stream from client for session events --- .../com/core/client/grpc/CoreGrpcClient.java | 62 ++++++++++--------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 6ad6b066..4c062483 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -1129,36 +1129,42 @@ public class CoreGrpcClient implements ICoreClient { Iterator events = blockingStub.events(request); executorService.submit(() -> { - try { - while (handlingEvents) { - CoreProto.Event event = events.next(); - logger.info("handling event: {}", event); - switch (event.getEventTypeCase()) { - case SESSION_EVENT: - handleSessionEvents(controller, event.getSessionEvent()); - break; - case NODE_EVENT: - handleNodeEvents(controller, event.getNodeEvent()); - break; - case LINK_EVENT: - handleLinkEvents(controller, event.getLinkEvent()); - break; - case CONFIG_EVENT: - handleConfigEvents(controller, event.getConfigEvent()); - break; - case EXCEPTION_EVENT: - handleExceptionEvents(controller, event.getExceptionEvent()); - break; - case FILE_EVENT: - handleFileEvents(controller, event.getFileEvent()); - break; - default: - logger.error("unknown event type: {}", event.getEventTypeCase()); + Context.CancellableContext context = Context.current().withCancellation(); + context.run(() -> { + try { + while (handlingEvents) { + CoreProto.Event event = events.next(); + logger.info("handling event: {}", event); + switch (event.getEventTypeCase()) { + case SESSION_EVENT: + handleSessionEvents(controller, event.getSessionEvent()); + break; + case NODE_EVENT: + handleNodeEvents(controller, event.getNodeEvent()); + break; + case LINK_EVENT: + handleLinkEvents(controller, event.getLinkEvent()); + break; + case CONFIG_EVENT: + handleConfigEvents(controller, event.getConfigEvent()); + break; + case EXCEPTION_EVENT: + handleExceptionEvents(controller, event.getExceptionEvent()); + break; + case FILE_EVENT: + handleFileEvents(controller, event.getFileEvent()); + break; + default: + logger.error("unknown event type: {}", event.getEventTypeCase()); + } } + } catch (StatusRuntimeException ex) { + logger.error("error handling session events", ex); + } finally { + context.cancel(null); + context.close(); } - } catch (StatusRuntimeException ex) { - logger.error("error handling session events", ex); - } + }); }); } catch (StatusRuntimeException ex) { throw new IOException("setup event handlers error", ex); From a8061b811d7df19146f733056b3e182f271236d9 Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 31 May 2019 22:20:19 -0700 Subject: [PATCH 0045/1992] grpc added get node terminal, updated corefx to leverage get node terminal to launch local terminals on double click --- .../com/core/client/grpc/CoreGrpcClient.java | 11 +++++++++-- daemon/core/grpc/client.py | 13 +++++++++++++ daemon/core/grpc/server.py | 18 ++++++++++++++---- daemon/proto/core.proto | 11 +++++++++++ daemon/tests/test_grpc.py | 16 ++++++++++++++++ 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 4c062483..bc9098b3 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -955,8 +955,15 @@ public class CoreGrpcClient implements ICoreClient { @Override public String getTerminalCommand(CoreNode node) throws IOException { - // TODO: convert - return null; + CoreProto.GetNodeTerminalRequest request = CoreProto.GetNodeTerminalRequest.newBuilder() + .setSessionId(sessionId) + .setNodeId(node.getId()) + .build(); + try { + return blockingStub.getNodeTerminal(request).getTerminal(); + } catch (StatusRuntimeException ex) { + throw new IOException(ex); + } } @Override diff --git a/daemon/core/grpc/client.py b/daemon/core/grpc/client.py index 53b0538e..e61cc9a5 100644 --- a/daemon/core/grpc/client.py +++ b/daemon/core/grpc/client.py @@ -353,6 +353,19 @@ class CoreGrpcClient(object): request = core_pb2.NodeCommandRequest(session_id=session_id, node_id=node_id, command=command) return self.stub.NodeCommand(request) + def get_node_terminal(self, session_id, node_id): + """ + Retrieve terminal command string for launching a local terminal. + + :param int session_id: session id + :param int node_id: node id + :return: response with a node terminal command + :rtype: core_pb2.GetNodeTerminalResponse + :raises grpc.RpcError: when session or node doesn't exist + """ + request = core_pb2.GetNodeTerminalRequest(session_id=session_id, node_id=node_id) + return self.stub.GetNodeTerminal(request) + def get_node_links(self, session_id, node_id): """ Get current links for a node. diff --git a/daemon/core/grpc/server.py b/daemon/core/grpc/server.py index aedac46d..3e2215cf 100644 --- a/daemon/core/grpc/server.py +++ b/daemon/core/grpc/server.py @@ -454,10 +454,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): interface_throughput.interface_id = interface_id interface_throughput.throughput = throughput elif key.startswith("b."): - node_id = int(key.split(".")[1]) - bridge_throughput = throughputs_event.bridge_throughputs.add() - bridge_throughput.node_id = node_id - bridge_throughput.throughput = throughput + try: + node_id = int(key.split(".")[1]) + bridge_throughput = throughputs_event.bridge_throughputs.add() + bridge_throughput.node_id = node_id + bridge_throughput.throughput = throughput + except ValueError: + pass yield throughputs_event @@ -549,6 +552,13 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): _, output = node.cmd_output(request.command) return core_pb2.NodeCommandResponse(output=output) + def GetNodeTerminal(self, request, context): + logging.debug("getting node terminal: %s", request) + session = self.get_session(request.session_id, context) + node = self.get_node(session, request.node_id, context) + terminal = node.termcmdstring("/bin/bash") + return core_pb2.GetNodeTerminalResponse(terminal=terminal) + def GetNodeLinks(self, request, context): logging.debug("get node links: %s", request) session = self.get_session(request.session_id, context) diff --git a/daemon/proto/core.proto b/daemon/proto/core.proto index 9b84f19a..013135f2 100644 --- a/daemon/proto/core.proto +++ b/daemon/proto/core.proto @@ -43,6 +43,8 @@ service CoreApi { } rpc NodeCommand (NodeCommandRequest) returns (NodeCommandResponse) { } + rpc GetNodeTerminal (GetNodeTerminalRequest) returns (GetNodeTerminalResponse) { + } // link rpc rpc GetNodeLinks (GetNodeLinksRequest) returns (GetNodeLinksResponse) { @@ -323,6 +325,15 @@ message DeleteNodeResponse { bool result = 1; } +message GetNodeTerminalRequest { + int32 session_id = 1; + int32 node_id = 2; +} + +message GetNodeTerminalResponse { + string terminal = 1; +} + message NodeCommandRequest { int32 session_id = 1; int32 node_id = 2; diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 13136931..948cfd54 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -257,6 +257,22 @@ class TestGrpc: # then assert response.output == output + def test_get_node_terminal(self, grpc_server): + # given + client = CoreGrpcClient() + session = grpc_server.coreemu.create_session() + session.set_state(EventTypes.CONFIGURATION_STATE) + node_options = NodeOptions(model="Host") + node = session.add_node(node_options=node_options) + session.instantiate() + + # then + with client.context_connect(): + response = client.get_node_terminal(session.id, node.objid) + + # then + assert response.terminal is not None + def test_get_hooks(self, grpc_server): # given client = CoreGrpcClient() From ecc63f4abb27294299c5179a84a682d157ec7031 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 2 Jun 2019 19:06:25 -0700 Subject: [PATCH 0046/1992] updates to support 2/3 along with not using vcmd c extension --- configure.ac | 2 +- daemon/core/api/grpc/core_pb2.py | 738 ++++++++++---------- daemon/core/api/grpc/core_pb2_grpc.py | 378 +++++----- daemon/core/api/tlv/broker.py | 6 +- daemon/core/api/tlv/coreapi.py | 19 +- daemon/core/api/tlv/corehandlers.py | 16 +- daemon/core/api/tlv/structutils.py | 7 +- daemon/core/emane/emanemanager.py | 2 +- daemon/core/emulator/session.py | 1 + daemon/core/location/event.py | 1 + daemon/core/location/mobility.py | 1 + daemon/core/nodes/base.py | 5 +- daemon/core/nodes/client.py | 28 +- daemon/core/nodes/interface.py | 14 +- daemon/core/nodes/ipaddress.py | 9 +- daemon/core/nodes/network.py | 4 + daemon/core/plugins/sdt.py | 6 +- daemon/core/utils.py | 11 +- daemon/data/logging.conf | 2 +- daemon/proto/Makefile.am | 2 +- daemon/proto/{ => core/api/grpc}/core.proto | 0 netns/Makefile.am | 64 +- 22 files changed, 680 insertions(+), 636 deletions(-) rename daemon/proto/{ => core/api/grpc}/core.proto (100%) diff --git a/configure.ac b/configure.ac index 79c5ecc0..763c6c4a 100644 --- a/configure.ac +++ b/configure.ac @@ -146,7 +146,7 @@ if test "x$enable_daemon" = "xyes"; then CFLAGS_save=$CFLAGS CPPFLAGS_save=$CPPFLAGS if test "x$PYTHON_INCLUDE_DIR" = "x"; then - PYTHON_INCLUDE_DIR=`$PYTHON -c "import distutils.sysconfig; print distutils.sysconfig.get_python_inc()"` + PYTHON_INCLUDE_DIR=`$PYTHON -c "import distutils.sysconfig; print(distutils.sysconfig.get_python_inc())"` fi CFLAGS="-I$PYTHON_INCLUDE_DIR" CPPFLAGS="-I$PYTHON_INCLUDE_DIR" diff --git a/daemon/core/api/grpc/core_pb2.py b/daemon/core/api/grpc/core_pb2.py index 754f76c7..d1531404 100644 --- a/daemon/core/api/grpc/core_pb2.py +++ b/daemon/core/api/grpc/core_pb2.py @@ -1,5 +1,5 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! -# source: core.proto +# source: core/api/grpc/core.proto import sys _b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) @@ -16,11 +16,11 @@ _sym_db = _symbol_database.Default() DESCRIPTOR = _descriptor.FileDescriptor( - name='core.proto', + name='core/api/grpc/core.proto', package='core', syntax='proto3', serialized_options=None, - serialized_pb=_b('\n\ncore.proto\x12\x04\x63ore\"\"\n\x14\x43reateSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"F\n\x15\x43reateSessionResponse\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\"\"\n\x14\x44\x65leteSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\'\n\x15\x44\x65leteSessionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x14\n\x12GetSessionsRequest\"=\n\x13GetSessionsResponse\x12&\n\x08sessions\x18\x01 \x03(\x0b\x32\x14.core.SessionSummary\"\x1f\n\x11GetSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"4\n\x12GetSessionResponse\x12\x1e\n\x07session\x18\x01 \x01(\x0b\x32\r.core.Session\"&\n\x18GetSessionOptionsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\">\n\x19GetSessionOptionsResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x91\x01\n\x18SetSessionOptionsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12:\n\x06\x63onfig\x18\x02 \x03(\x0b\x32*.core.SetSessionOptionsRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x19SetSessionOptionsResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\'\n\x19GetSessionLocationRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"M\n\x1aGetSessionLocationResponse\x12 \n\x08position\x18\x01 \x01(\x0b\x32\x0e.core.Position\x12\r\n\x05scale\x18\x02 \x01(\x02\"X\n\x19SetSessionLocationRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12 \n\x08position\x18\x02 \x01(\x0b\x32\x0e.core.Position\x12\r\n\x05scale\x18\x03 \x01(\x02\",\n\x1aSetSessionLocationResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"G\n\x16SetSessionStateRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\")\n\x17SetSessionStateResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x1f\n\x11NodeEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"%\n\tNodeEvent\x12\x18\n\x04node\x18\x01 \x01(\x0b\x32\n.core.Node\"\x1f\n\x11LinkEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"N\n\tLinkEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x18\n\x04link\x18\x02 \x01(\x0b\x32\n.core.Link\"\"\n\x14SessionEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"f\n\x0cSessionEvent\x12\x0c\n\x04node\x18\x01 \x01(\x05\x12\r\n\x05\x65vent\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0c\n\x04time\x18\x05 \x01(\x02\x12\x0f\n\x07session\x18\x06 \x01(\x05\"!\n\x13\x43onfigEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\x9e\x02\n\x0b\x43onfigEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x0c\n\x04node\x18\x02 \x01(\x05\x12\x0e\n\x06object\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x12\n\ndata_types\x18\x05 \x03(\x05\x12\x13\n\x0b\x64\x61ta_values\x18\x06 \x01(\t\x12\x10\n\x08\x63\x61ptions\x18\x07 \x01(\t\x12\x0e\n\x06\x62itmap\x18\x08 \x01(\t\x12\x17\n\x0fpossible_values\x18\t \x01(\t\x12\x0e\n\x06groups\x18\n \x01(\t\x12\x0f\n\x07session\x18\x0b \x01(\t\x12\x11\n\tinterface\x18\x0c \x01(\x05\x12\x12\n\nnetwork_id\x18\r \x01(\x05\x12\x0e\n\x06opaque\x18\x0e \x01(\t\"$\n\x16\x45xceptionEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\x90\x01\n\x0e\x45xceptionEvent\x12\x0c\n\x04node\x18\x01 \x01(\x05\x12\x0f\n\x07session\x18\x02 \x01(\x05\x12#\n\x05level\x18\x03 \x01(\x0e\x32\x14.core.ExceptionLevel\x12\x0e\n\x06source\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61te\x18\x05 \x01(\t\x12\x0c\n\x04text\x18\x06 \x01(\t\x12\x0e\n\x06opaque\x18\x07 \x01(\t\"\x1f\n\x11\x46ileEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\xc4\x01\n\tFileEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x0c\n\x04node\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04mode\x18\x04 \x01(\t\x12\x0e\n\x06number\x18\x05 \x01(\x05\x12\x0c\n\x04type\x18\x06 \x01(\t\x12\x0e\n\x06source\x18\x07 \x01(\t\x12\x0f\n\x07session\x18\x08 \x01(\x05\x12\x0c\n\x04\x64\x61ta\x18\t \x01(\x0c\x12\x17\n\x0f\x63ompressed_data\x18\n \x01(\x0c\";\n\x0e\x41\x64\x64NodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04node\x18\x02 \x01(\x0b\x32\n.core.Node\"\x1d\n\x0f\x41\x64\x64NodeResponse\x12\n\n\x02id\x18\x01 \x01(\x05\"-\n\x0eGetNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"P\n\x0fGetNodeResponse\x12\x18\n\x04node\x18\x01 \x01(\x0b\x32\n.core.Node\x12#\n\ninterfaces\x18\x02 \x03(\x0b\x32\x0f.core.Interface\"P\n\x0f\x45\x64itNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12 \n\x08position\x18\x03 \x01(\x0b\x32\x0e.core.Position\"\"\n\x10\x45\x64itNodeResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"0\n\x11\x44\x65leteNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"$\n\x12\x44\x65leteNodeResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"2\n\x13GetNodeLinksRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"1\n\x14GetNodeLinksResponse\x12\x19\n\x05links\x18\x01 \x03(\x0b\x32\n.core.Link\";\n\x0e\x41\x64\x64LinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04link\x18\x02 \x01(\x0b\x32\n.core.Link\"!\n\x0f\x41\x64\x64LinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x98\x01\n\x0f\x45\x64itLinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x10\n\x08node_one\x18\x02 \x01(\x05\x12\x10\n\x08node_two\x18\x03 \x01(\x05\x12\x15\n\rinterface_one\x18\x04 \x01(\x05\x12\x15\n\rinterface_two\x18\x05 \x01(\x05\x12\"\n\x07options\x18\x06 \x01(\x0b\x32\x11.core.LinkOptions\"\"\n\x10\x45\x64itLinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"v\n\x11\x44\x65leteLinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x10\n\x08node_one\x18\x02 \x01(\x05\x12\x10\n\x08node_two\x18\x03 \x01(\x05\x12\x15\n\rinterface_one\x18\x04 \x01(\x05\x12\x15\n\rinterface_two\x18\x05 \x01(\x05\"$\n\x12\x44\x65leteLinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\"\n\x0fGetHooksRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"-\n\x10GetHooksResponse\x12\x19\n\x05hooks\x18\x01 \x03(\x0b\x32\n.core.Hook\";\n\x0e\x41\x64\x64HookRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04hook\x18\x02 \x01(\x0b\x32\n.core.Hook\"!\n\x0f\x41\x64\x64HookResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\",\n\x19GetMobilityConfigsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\xf2\x01\n\x1aGetMobilityConfigsResponse\x12>\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32-.core.GetMobilityConfigsResponse.ConfigsEntry\x1a\x33\n\x0eMobilityConfig\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\x1a_\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12>\n\x05value\x18\x02 \x01(\x0b\x32/.core.GetMobilityConfigsResponse.MobilityConfig:\x02\x38\x01\"7\n\x18GetMobilityConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\">\n\x19GetMobilityConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\xa2\x01\n\x18SetMobilityConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12:\n\x06\x63onfig\x18\x03 \x03(\x0b\x32*.core.SetMobilityConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x19SetMobilityConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"Z\n\x15MobilityActionRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12$\n\x06\x61\x63tion\x18\x03 \x01(\x0e\x32\x14.core.MobilityAction\"(\n\x16MobilityActionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x14\n\x12GetServicesRequest\"6\n\x13GetServicesResponse\x12\x1f\n\x08services\x18\x01 \x03(\x0b\x32\r.core.Service\",\n\x19GetServiceDefaultsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"E\n\x1aGetServiceDefaultsResponse\x12\'\n\x08\x64\x65\x66\x61ults\x18\x01 \x03(\x0b\x32\x15.core.ServiceDefaults\"U\n\x19SetServiceDefaultsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\'\n\x08\x64\x65\x66\x61ults\x18\x02 \x03(\x0b\x32\x15.core.ServiceDefaults\",\n\x1aSetServiceDefaultsResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"E\n\x15GetNodeServiceRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\"@\n\x16GetNodeServiceResponse\x12&\n\x07service\x18\x01 \x01(\x0b\x32\x15.core.NodeServiceData\"W\n\x19GetNodeServiceFileRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0c\n\x04\x66ile\x18\x04 \x01(\t\"*\n\x1aGetNodeServiceFileResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"z\n\x15SetNodeServiceRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0f\n\x07startup\x18\x04 \x03(\t\x12\x10\n\x08validate\x18\x05 \x03(\t\x12\x10\n\x08shutdown\x18\x06 \x03(\t\"(\n\x16SetNodeServiceResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"e\n\x19SetNodeServiceFileRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0c\n\x04\x66ile\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\",\n\x1aSetNodeServiceFileResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"i\n\x14ServiceActionRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12#\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32\x13.core.ServiceAction\"\'\n\x15ServiceActionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"3\n\x14GetWlanConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\":\n\x15GetWlanConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x9a\x01\n\x14SetWlanConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x36\n\x06\x63onfig\x18\x03 \x03(\x0b\x32&.core.SetWlanConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\'\n\x15SetWlanConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"(\n\x15GetEmaneConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\";\n\x16GetEmaneConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x90\x01\n\x15SetEmaneConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x37\n\x06\x63onfig\x18\x02 \x03(\x0b\x32\'.core.SetEmaneConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"(\n\x16SetEmaneConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"(\n\x15GetEmaneModelsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"(\n\x16GetEmaneModelsResponse\x12\x0e\n\x06models\x18\x01 \x03(\t\"[\n\x1aGetEmaneModelConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x11\n\tinterface\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\"@\n\x1bGetEmaneModelConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\xc8\x01\n\x1aSetEmaneModelConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x11\n\tinterface\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\x12<\n\x06\x63onfig\x18\x05 \x03(\x0b\x32,.core.SetEmaneModelConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"-\n\x1bSetEmaneModelConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\".\n\x1bGetEmaneModelConfigsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\x81\x02\n\x1cGetEmaneModelConfigsResponse\x12@\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32/.core.GetEmaneModelConfigsResponse.ConfigsEntry\x1a?\n\x0bModelConfig\x12\r\n\x05model\x18\x01 \x01(\t\x12!\n\x06groups\x18\x02 \x03(\x0b\x32\x11.core.ConfigGroup\x1a^\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12=\n\x05value\x18\x02 \x01(\x0b\x32..core.GetEmaneModelConfigsResponse.ModelConfig:\x02\x38\x01\"!\n\x0eSaveXmlRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\x1f\n\x0fSaveXmlResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x1e\n\x0eOpenXmlRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"2\n\x0fOpenXmlResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\x12\x0f\n\x07session\x18\x02 \x01(\x05\"E\n\x04Hook\x12!\n\x05state\x18\x01 \x01(\x0e\x32\x12.core.SessionState\x12\x0c\n\x04\x66ile\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"6\n\x0fServiceDefaults\x12\x11\n\tnode_type\x18\x01 \x01(\t\x12\x10\n\x08services\x18\x02 \x03(\t\"&\n\x07Service\x12\r\n\x05group\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xee\x01\n\x0fNodeServiceData\x12\x13\n\x0b\x65xecutables\x18\x01 \x03(\t\x12\x14\n\x0c\x64\x65pendencies\x18\x02 \x03(\t\x12\x0c\n\x04\x64irs\x18\x03 \x03(\t\x12\x0f\n\x07\x63onfigs\x18\x04 \x03(\t\x12\x0f\n\x07startup\x18\x05 \x03(\t\x12\x10\n\x08validate\x18\x06 \x03(\t\x12\x34\n\x0fvalidation_mode\x18\x07 \x01(\x0e\x32\x1b.core.ServiceValidationMode\x12\x18\n\x10validation_timer\x18\x08 \x01(\x05\x12\x10\n\x08shutdown\x18\t \x03(\t\x12\x0c\n\x04meta\x18\n \x01(\t\"@\n\x0b\x43onfigGroup\x12\x0c\n\x04name\x18\x01 \x01(\t\x12#\n\x07options\x18\x02 \x03(\x0b\x32\x12.core.ConfigOption\"X\n\x0c\x43onfigOption\x12\r\n\x05label\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x0e\n\x06select\x18\x05 \x03(\t\"n\n\x07Session\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\x12\x19\n\x05nodes\x18\x03 \x03(\x0b\x32\n.core.Node\x12\x19\n\x05links\x18\x04 \x03(\x0b\x32\n.core.Link\"N\n\x0eSessionSummary\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\x12\r\n\x05nodes\x18\x03 \x01(\x05\"\xae\x01\n\x04Node\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x1c\n\x04type\x18\x03 \x01(\x0e\x32\x0e.core.NodeType\x12\r\n\x05model\x18\x04 \x01(\t\x12 \n\x08position\x18\x05 \x01(\x0b\x32\x0e.core.Position\x12\x10\n\x08services\x18\x06 \x03(\t\x12\r\n\x05\x65mane\x18\x07 \x01(\t\x12\x0c\n\x04icon\x18\x08 \x01(\t\x12\x0e\n\x06opaque\x18\t \x01(\t\"\xbc\x01\n\x04Link\x12\x10\n\x08node_one\x18\x01 \x01(\x05\x12\x10\n\x08node_two\x18\x02 \x01(\x05\x12\x1c\n\x04type\x18\x03 \x01(\x0e\x32\x0e.core.LinkType\x12&\n\rinterface_one\x18\x04 \x01(\x0b\x32\x0f.core.Interface\x12&\n\rinterface_two\x18\x05 \x01(\x0b\x32\x0f.core.Interface\x12\"\n\x07options\x18\x06 \x01(\x0b\x32\x11.core.LinkOptions\"\xba\x01\n\x0bLinkOptions\x12\x0e\n\x06opaque\x18\x01 \x01(\t\x12\x0e\n\x06jitter\x18\x02 \x01(\x02\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0e\n\x06mburst\x18\x04 \x01(\x02\x12\x0b\n\x03mer\x18\x05 \x01(\x02\x12\x0b\n\x03per\x18\x06 \x01(\x02\x12\x11\n\tbandwidth\x18\x07 \x01(\x02\x12\r\n\x05\x62urst\x18\x08 \x01(\x02\x12\r\n\x05\x64\x65lay\x18\t \x01(\x02\x12\x0b\n\x03\x64up\x18\n \x01(\x02\x12\x16\n\x0eunidirectional\x18\x0b \x01(\x08\"\x9a\x01\n\tInterface\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0b\n\x03mac\x18\x03 \x01(\t\x12\x0b\n\x03ip4\x18\x04 \x01(\t\x12\x0f\n\x07ip4mask\x18\x05 \x01(\x05\x12\x0b\n\x03ip6\x18\x06 \x01(\t\x12\x0f\n\x07ip6mask\x18\x07 \x01(\x05\x12\r\n\x05netid\x18\x08 \x01(\x05\x12\x0e\n\x06\x66lowid\x18\t \x01(\x05\x12\x0b\n\x03mtu\x18\n \x01(\x05\"R\n\x08Position\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\x12\x0b\n\x03lat\x18\x04 \x01(\x02\x12\x0b\n\x03lon\x18\x05 \x01(\x02\x12\x0b\n\x03\x61lt\x18\x06 \x01(\x02*\x9f\x01\n\x0bMessageType\x12\x10\n\x0cMESSAGE_NONE\x10\x00\x12\x0f\n\x0bMESSAGE_ADD\x10\x01\x12\x12\n\x0eMESSAGE_DELETE\x10\x02\x12\x0f\n\x0bMESSAGE_CRI\x10\x04\x12\x11\n\rMESSAGE_LOCAL\x10\x08\x12\x12\n\x0eMESSAGE_STRING\x10\x10\x12\x10\n\x0cMESSAGE_TEXT\x10 \x12\x0f\n\x0bMESSAGE_TTY\x10@*-\n\x08LinkType\x12\x11\n\rLINK_WIRELESS\x10\x00\x12\x0e\n\nLINK_WIRED\x10\x01*\xa4\x01\n\x0cSessionState\x12\x0e\n\nSTATE_NONE\x10\x00\x12\x14\n\x10STATE_DEFINITION\x10\x01\x12\x17\n\x13STATE_CONFIGURATION\x10\x02\x12\x17\n\x13STATE_INSTANTIATION\x10\x03\x12\x11\n\rSTATE_RUNTIME\x10\x04\x12\x15\n\x11STATE_DATACOLLECT\x10\x05\x12\x12\n\x0eSTATE_SHUTDOWN\x10\x06*\x8b\x02\n\x08NodeType\x12\x10\n\x0cNODE_DEFAULT\x10\x00\x12\x11\n\rNODE_PHYSICAL\x10\x01\x12\x0c\n\x08NODE_TBD\x10\x03\x12\x0f\n\x0bNODE_SWITCH\x10\x04\x12\x0c\n\x08NODE_HUB\x10\x05\x12\x15\n\x11NODE_WIRELESS_LAN\x10\x06\x12\r\n\tNODE_RJ45\x10\x07\x12\x0f\n\x0bNODE_TUNNEL\x10\x08\x12\x10\n\x0cNODE_KTUNNEL\x10\t\x12\x0e\n\nNODE_EMANE\x10\n\x12\x13\n\x0fNODE_TAP_BRIDGE\x10\x0b\x12\x15\n\x11NODE_PEER_TO_PEER\x10\x0c\x12\x14\n\x10NODE_CONTROL_NET\x10\r\x12\x12\n\x0eNODE_EMANE_NET\x10\x0e*c\n\x15ServiceValidationMode\x12\x17\n\x13VALIDATION_BLOCKING\x10\x00\x12\x1b\n\x17VALIDATION_NON_BLOCKING\x10\x01\x12\x14\n\x10VALIDATION_TIMER\x10\x02*_\n\rServiceAction\x12\x11\n\rSERVICE_START\x10\x00\x12\x10\n\x0cSERVICE_STOP\x10\x01\x12\x13\n\x0fSERVICE_RESTART\x10\x02\x12\x14\n\x10SERVICE_VALIDATE\x10\x03*K\n\x0eMobilityAction\x12\x12\n\x0eMOBILITY_START\x10\x00\x12\x12\n\x0eMOBILITY_PAUSE\x10\x01\x12\x11\n\rMOBILITY_STOP\x10\x02*~\n\x0e\x45xceptionLevel\x12\x15\n\x11\x45XCEPTION_DEFAULT\x10\x00\x12\x13\n\x0f\x45XCEPTION_FATAL\x10\x01\x12\x13\n\x0f\x45XCEPTION_ERROR\x10\x02\x12\x15\n\x11\x45XCEPTION_WARNING\x10\x03\x12\x14\n\x10\x45XCEPTION_NOTICE\x10\x04\x32\xe2\x1b\n\x07\x43oreApi\x12J\n\rCreateSession\x12\x1a.core.CreateSessionRequest\x1a\x1b.core.CreateSessionResponse\"\x00\x12J\n\rDeleteSession\x12\x1a.core.DeleteSessionRequest\x1a\x1b.core.DeleteSessionResponse\"\x00\x12\x44\n\x0bGetSessions\x12\x18.core.GetSessionsRequest\x1a\x19.core.GetSessionsResponse\"\x00\x12\x41\n\nGetSession\x12\x17.core.GetSessionRequest\x1a\x18.core.GetSessionResponse\"\x00\x12V\n\x11GetSessionOptions\x12\x1e.core.GetSessionOptionsRequest\x1a\x1f.core.GetSessionOptionsResponse\"\x00\x12V\n\x11SetSessionOptions\x12\x1e.core.SetSessionOptionsRequest\x1a\x1f.core.SetSessionOptionsResponse\"\x00\x12Y\n\x12GetSessionLocation\x12\x1f.core.GetSessionLocationRequest\x1a .core.GetSessionLocationResponse\"\x00\x12Y\n\x12SetSessionLocation\x12\x1f.core.SetSessionLocationRequest\x1a .core.SetSessionLocationResponse\"\x00\x12P\n\x0fSetSessionState\x12\x1c.core.SetSessionStateRequest\x1a\x1d.core.SetSessionStateResponse\"\x00\x12:\n\nNodeEvents\x12\x17.core.NodeEventsRequest\x1a\x0f.core.NodeEvent\"\x00\x30\x01\x12:\n\nLinkEvents\x12\x17.core.LinkEventsRequest\x1a\x0f.core.LinkEvent\"\x00\x30\x01\x12\x43\n\rSessionEvents\x12\x1a.core.SessionEventsRequest\x1a\x12.core.SessionEvent\"\x00\x30\x01\x12@\n\x0c\x43onfigEvents\x12\x19.core.ConfigEventsRequest\x1a\x11.core.ConfigEvent\"\x00\x30\x01\x12I\n\x0f\x45xceptionEvents\x12\x1c.core.ExceptionEventsRequest\x1a\x14.core.ExceptionEvent\"\x00\x30\x01\x12:\n\nFileEvents\x12\x17.core.FileEventsRequest\x1a\x0f.core.FileEvent\"\x00\x30\x01\x12\x38\n\x07\x41\x64\x64Node\x12\x14.core.AddNodeRequest\x1a\x15.core.AddNodeResponse\"\x00\x12\x38\n\x07GetNode\x12\x14.core.GetNodeRequest\x1a\x15.core.GetNodeResponse\"\x00\x12;\n\x08\x45\x64itNode\x12\x15.core.EditNodeRequest\x1a\x16.core.EditNodeResponse\"\x00\x12\x41\n\nDeleteNode\x12\x17.core.DeleteNodeRequest\x1a\x18.core.DeleteNodeResponse\"\x00\x12G\n\x0cGetNodeLinks\x12\x19.core.GetNodeLinksRequest\x1a\x1a.core.GetNodeLinksResponse\"\x00\x12\x38\n\x07\x41\x64\x64Link\x12\x14.core.AddLinkRequest\x1a\x15.core.AddLinkResponse\"\x00\x12;\n\x08\x45\x64itLink\x12\x15.core.EditLinkRequest\x1a\x16.core.EditLinkResponse\"\x00\x12\x41\n\nDeleteLink\x12\x17.core.DeleteLinkRequest\x1a\x18.core.DeleteLinkResponse\"\x00\x12;\n\x08GetHooks\x12\x15.core.GetHooksRequest\x1a\x16.core.GetHooksResponse\"\x00\x12\x38\n\x07\x41\x64\x64Hook\x12\x14.core.AddHookRequest\x1a\x15.core.AddHookResponse\"\x00\x12Y\n\x12GetMobilityConfigs\x12\x1f.core.GetMobilityConfigsRequest\x1a .core.GetMobilityConfigsResponse\"\x00\x12V\n\x11GetMobilityConfig\x12\x1e.core.GetMobilityConfigRequest\x1a\x1f.core.GetMobilityConfigResponse\"\x00\x12V\n\x11SetMobilityConfig\x12\x1e.core.SetMobilityConfigRequest\x1a\x1f.core.SetMobilityConfigResponse\"\x00\x12M\n\x0eMobilityAction\x12\x1b.core.MobilityActionRequest\x1a\x1c.core.MobilityActionResponse\"\x00\x12\x44\n\x0bGetServices\x12\x18.core.GetServicesRequest\x1a\x19.core.GetServicesResponse\"\x00\x12Y\n\x12GetServiceDefaults\x12\x1f.core.GetServiceDefaultsRequest\x1a .core.GetServiceDefaultsResponse\"\x00\x12Y\n\x12SetServiceDefaults\x12\x1f.core.SetServiceDefaultsRequest\x1a .core.SetServiceDefaultsResponse\"\x00\x12M\n\x0eGetNodeService\x12\x1b.core.GetNodeServiceRequest\x1a\x1c.core.GetNodeServiceResponse\"\x00\x12Y\n\x12GetNodeServiceFile\x12\x1f.core.GetNodeServiceFileRequest\x1a .core.GetNodeServiceFileResponse\"\x00\x12M\n\x0eSetNodeService\x12\x1b.core.SetNodeServiceRequest\x1a\x1c.core.SetNodeServiceResponse\"\x00\x12Y\n\x12SetNodeServiceFile\x12\x1f.core.SetNodeServiceFileRequest\x1a .core.SetNodeServiceFileResponse\"\x00\x12J\n\rServiceAction\x12\x1a.core.ServiceActionRequest\x1a\x1b.core.ServiceActionResponse\"\x00\x12J\n\rGetWlanConfig\x12\x1a.core.GetWlanConfigRequest\x1a\x1b.core.GetWlanConfigResponse\"\x00\x12J\n\rSetWlanConfig\x12\x1a.core.SetWlanConfigRequest\x1a\x1b.core.SetWlanConfigResponse\"\x00\x12M\n\x0eGetEmaneConfig\x12\x1b.core.GetEmaneConfigRequest\x1a\x1c.core.GetEmaneConfigResponse\"\x00\x12M\n\x0eSetEmaneConfig\x12\x1b.core.SetEmaneConfigRequest\x1a\x1c.core.SetEmaneConfigResponse\"\x00\x12M\n\x0eGetEmaneModels\x12\x1b.core.GetEmaneModelsRequest\x1a\x1c.core.GetEmaneModelsResponse\"\x00\x12\\\n\x13GetEmaneModelConfig\x12 .core.GetEmaneModelConfigRequest\x1a!.core.GetEmaneModelConfigResponse\"\x00\x12\\\n\x13SetEmaneModelConfig\x12 .core.SetEmaneModelConfigRequest\x1a!.core.SetEmaneModelConfigResponse\"\x00\x12_\n\x14GetEmaneModelConfigs\x12!.core.GetEmaneModelConfigsRequest\x1a\".core.GetEmaneModelConfigsResponse\"\x00\x12\x38\n\x07SaveXml\x12\x14.core.SaveXmlRequest\x1a\x15.core.SaveXmlResponse\"\x00\x12\x38\n\x07OpenXml\x12\x14.core.OpenXmlRequest\x1a\x15.core.OpenXmlResponse\"\x00\x62\x06proto3') + serialized_pb=_b('\n\x18\x63ore/api/grpc/core.proto\x12\x04\x63ore\"\"\n\x14\x43reateSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"F\n\x15\x43reateSessionResponse\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\"\"\n\x14\x44\x65leteSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\'\n\x15\x44\x65leteSessionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x14\n\x12GetSessionsRequest\"=\n\x13GetSessionsResponse\x12&\n\x08sessions\x18\x01 \x03(\x0b\x32\x14.core.SessionSummary\"\x1f\n\x11GetSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"4\n\x12GetSessionResponse\x12\x1e\n\x07session\x18\x01 \x01(\x0b\x32\r.core.Session\"&\n\x18GetSessionOptionsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\">\n\x19GetSessionOptionsResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x91\x01\n\x18SetSessionOptionsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12:\n\x06\x63onfig\x18\x02 \x03(\x0b\x32*.core.SetSessionOptionsRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x19SetSessionOptionsResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\'\n\x19GetSessionLocationRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"M\n\x1aGetSessionLocationResponse\x12 \n\x08position\x18\x01 \x01(\x0b\x32\x0e.core.Position\x12\r\n\x05scale\x18\x02 \x01(\x02\"X\n\x19SetSessionLocationRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12 \n\x08position\x18\x02 \x01(\x0b\x32\x0e.core.Position\x12\r\n\x05scale\x18\x03 \x01(\x02\",\n\x1aSetSessionLocationResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"G\n\x16SetSessionStateRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\")\n\x17SetSessionStateResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x1f\n\x11NodeEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"%\n\tNodeEvent\x12\x18\n\x04node\x18\x01 \x01(\x0b\x32\n.core.Node\"\x1f\n\x11LinkEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"N\n\tLinkEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x18\n\x04link\x18\x02 \x01(\x0b\x32\n.core.Link\"\"\n\x14SessionEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"f\n\x0cSessionEvent\x12\x0c\n\x04node\x18\x01 \x01(\x05\x12\r\n\x05\x65vent\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0c\n\x04time\x18\x05 \x01(\x02\x12\x0f\n\x07session\x18\x06 \x01(\x05\"!\n\x13\x43onfigEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\x9e\x02\n\x0b\x43onfigEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x0c\n\x04node\x18\x02 \x01(\x05\x12\x0e\n\x06object\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x12\n\ndata_types\x18\x05 \x03(\x05\x12\x13\n\x0b\x64\x61ta_values\x18\x06 \x01(\t\x12\x10\n\x08\x63\x61ptions\x18\x07 \x01(\t\x12\x0e\n\x06\x62itmap\x18\x08 \x01(\t\x12\x17\n\x0fpossible_values\x18\t \x01(\t\x12\x0e\n\x06groups\x18\n \x01(\t\x12\x0f\n\x07session\x18\x0b \x01(\t\x12\x11\n\tinterface\x18\x0c \x01(\x05\x12\x12\n\nnetwork_id\x18\r \x01(\x05\x12\x0e\n\x06opaque\x18\x0e \x01(\t\"$\n\x16\x45xceptionEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\x90\x01\n\x0e\x45xceptionEvent\x12\x0c\n\x04node\x18\x01 \x01(\x05\x12\x0f\n\x07session\x18\x02 \x01(\x05\x12#\n\x05level\x18\x03 \x01(\x0e\x32\x14.core.ExceptionLevel\x12\x0e\n\x06source\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61te\x18\x05 \x01(\t\x12\x0c\n\x04text\x18\x06 \x01(\t\x12\x0e\n\x06opaque\x18\x07 \x01(\t\"\x1f\n\x11\x46ileEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\xc4\x01\n\tFileEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x0c\n\x04node\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04mode\x18\x04 \x01(\t\x12\x0e\n\x06number\x18\x05 \x01(\x05\x12\x0c\n\x04type\x18\x06 \x01(\t\x12\x0e\n\x06source\x18\x07 \x01(\t\x12\x0f\n\x07session\x18\x08 \x01(\x05\x12\x0c\n\x04\x64\x61ta\x18\t \x01(\x0c\x12\x17\n\x0f\x63ompressed_data\x18\n \x01(\x0c\";\n\x0e\x41\x64\x64NodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04node\x18\x02 \x01(\x0b\x32\n.core.Node\"\x1d\n\x0f\x41\x64\x64NodeResponse\x12\n\n\x02id\x18\x01 \x01(\x05\"-\n\x0eGetNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"P\n\x0fGetNodeResponse\x12\x18\n\x04node\x18\x01 \x01(\x0b\x32\n.core.Node\x12#\n\ninterfaces\x18\x02 \x03(\x0b\x32\x0f.core.Interface\"P\n\x0f\x45\x64itNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12 \n\x08position\x18\x03 \x01(\x0b\x32\x0e.core.Position\"\"\n\x10\x45\x64itNodeResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"0\n\x11\x44\x65leteNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"$\n\x12\x44\x65leteNodeResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"2\n\x13GetNodeLinksRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"1\n\x14GetNodeLinksResponse\x12\x19\n\x05links\x18\x01 \x03(\x0b\x32\n.core.Link\";\n\x0e\x41\x64\x64LinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04link\x18\x02 \x01(\x0b\x32\n.core.Link\"!\n\x0f\x41\x64\x64LinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x98\x01\n\x0f\x45\x64itLinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x10\n\x08node_one\x18\x02 \x01(\x05\x12\x10\n\x08node_two\x18\x03 \x01(\x05\x12\x15\n\rinterface_one\x18\x04 \x01(\x05\x12\x15\n\rinterface_two\x18\x05 \x01(\x05\x12\"\n\x07options\x18\x06 \x01(\x0b\x32\x11.core.LinkOptions\"\"\n\x10\x45\x64itLinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"v\n\x11\x44\x65leteLinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x10\n\x08node_one\x18\x02 \x01(\x05\x12\x10\n\x08node_two\x18\x03 \x01(\x05\x12\x15\n\rinterface_one\x18\x04 \x01(\x05\x12\x15\n\rinterface_two\x18\x05 \x01(\x05\"$\n\x12\x44\x65leteLinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\"\n\x0fGetHooksRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"-\n\x10GetHooksResponse\x12\x19\n\x05hooks\x18\x01 \x03(\x0b\x32\n.core.Hook\";\n\x0e\x41\x64\x64HookRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04hook\x18\x02 \x01(\x0b\x32\n.core.Hook\"!\n\x0f\x41\x64\x64HookResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\",\n\x19GetMobilityConfigsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\xf2\x01\n\x1aGetMobilityConfigsResponse\x12>\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32-.core.GetMobilityConfigsResponse.ConfigsEntry\x1a\x33\n\x0eMobilityConfig\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\x1a_\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12>\n\x05value\x18\x02 \x01(\x0b\x32/.core.GetMobilityConfigsResponse.MobilityConfig:\x02\x38\x01\"7\n\x18GetMobilityConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\">\n\x19GetMobilityConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\xa2\x01\n\x18SetMobilityConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12:\n\x06\x63onfig\x18\x03 \x03(\x0b\x32*.core.SetMobilityConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x19SetMobilityConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"Z\n\x15MobilityActionRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12$\n\x06\x61\x63tion\x18\x03 \x01(\x0e\x32\x14.core.MobilityAction\"(\n\x16MobilityActionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x14\n\x12GetServicesRequest\"6\n\x13GetServicesResponse\x12\x1f\n\x08services\x18\x01 \x03(\x0b\x32\r.core.Service\",\n\x19GetServiceDefaultsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"E\n\x1aGetServiceDefaultsResponse\x12\'\n\x08\x64\x65\x66\x61ults\x18\x01 \x03(\x0b\x32\x15.core.ServiceDefaults\"U\n\x19SetServiceDefaultsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\'\n\x08\x64\x65\x66\x61ults\x18\x02 \x03(\x0b\x32\x15.core.ServiceDefaults\",\n\x1aSetServiceDefaultsResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"E\n\x15GetNodeServiceRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\"@\n\x16GetNodeServiceResponse\x12&\n\x07service\x18\x01 \x01(\x0b\x32\x15.core.NodeServiceData\"W\n\x19GetNodeServiceFileRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0c\n\x04\x66ile\x18\x04 \x01(\t\"*\n\x1aGetNodeServiceFileResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"z\n\x15SetNodeServiceRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0f\n\x07startup\x18\x04 \x03(\t\x12\x10\n\x08validate\x18\x05 \x03(\t\x12\x10\n\x08shutdown\x18\x06 \x03(\t\"(\n\x16SetNodeServiceResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"e\n\x19SetNodeServiceFileRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0c\n\x04\x66ile\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\",\n\x1aSetNodeServiceFileResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"i\n\x14ServiceActionRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12#\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32\x13.core.ServiceAction\"\'\n\x15ServiceActionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"3\n\x14GetWlanConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\":\n\x15GetWlanConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x9a\x01\n\x14SetWlanConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x36\n\x06\x63onfig\x18\x03 \x03(\x0b\x32&.core.SetWlanConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\'\n\x15SetWlanConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"(\n\x15GetEmaneConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\";\n\x16GetEmaneConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x90\x01\n\x15SetEmaneConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x37\n\x06\x63onfig\x18\x02 \x03(\x0b\x32\'.core.SetEmaneConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"(\n\x16SetEmaneConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"(\n\x15GetEmaneModelsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"(\n\x16GetEmaneModelsResponse\x12\x0e\n\x06models\x18\x01 \x03(\t\"[\n\x1aGetEmaneModelConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x11\n\tinterface\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\"@\n\x1bGetEmaneModelConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\xc8\x01\n\x1aSetEmaneModelConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x11\n\tinterface\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\x12<\n\x06\x63onfig\x18\x05 \x03(\x0b\x32,.core.SetEmaneModelConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"-\n\x1bSetEmaneModelConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\".\n\x1bGetEmaneModelConfigsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\x81\x02\n\x1cGetEmaneModelConfigsResponse\x12@\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32/.core.GetEmaneModelConfigsResponse.ConfigsEntry\x1a?\n\x0bModelConfig\x12\r\n\x05model\x18\x01 \x01(\t\x12!\n\x06groups\x18\x02 \x03(\x0b\x32\x11.core.ConfigGroup\x1a^\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12=\n\x05value\x18\x02 \x01(\x0b\x32..core.GetEmaneModelConfigsResponse.ModelConfig:\x02\x38\x01\"!\n\x0eSaveXmlRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\x1f\n\x0fSaveXmlResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x1e\n\x0eOpenXmlRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"2\n\x0fOpenXmlResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\x12\x0f\n\x07session\x18\x02 \x01(\x05\"E\n\x04Hook\x12!\n\x05state\x18\x01 \x01(\x0e\x32\x12.core.SessionState\x12\x0c\n\x04\x66ile\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"6\n\x0fServiceDefaults\x12\x11\n\tnode_type\x18\x01 \x01(\t\x12\x10\n\x08services\x18\x02 \x03(\t\"&\n\x07Service\x12\r\n\x05group\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xee\x01\n\x0fNodeServiceData\x12\x13\n\x0b\x65xecutables\x18\x01 \x03(\t\x12\x14\n\x0c\x64\x65pendencies\x18\x02 \x03(\t\x12\x0c\n\x04\x64irs\x18\x03 \x03(\t\x12\x0f\n\x07\x63onfigs\x18\x04 \x03(\t\x12\x0f\n\x07startup\x18\x05 \x03(\t\x12\x10\n\x08validate\x18\x06 \x03(\t\x12\x34\n\x0fvalidation_mode\x18\x07 \x01(\x0e\x32\x1b.core.ServiceValidationMode\x12\x18\n\x10validation_timer\x18\x08 \x01(\x05\x12\x10\n\x08shutdown\x18\t \x03(\t\x12\x0c\n\x04meta\x18\n \x01(\t\"@\n\x0b\x43onfigGroup\x12\x0c\n\x04name\x18\x01 \x01(\t\x12#\n\x07options\x18\x02 \x03(\x0b\x32\x12.core.ConfigOption\"X\n\x0c\x43onfigOption\x12\r\n\x05label\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x0e\n\x06select\x18\x05 \x03(\t\"n\n\x07Session\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\x12\x19\n\x05nodes\x18\x03 \x03(\x0b\x32\n.core.Node\x12\x19\n\x05links\x18\x04 \x03(\x0b\x32\n.core.Link\"N\n\x0eSessionSummary\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\x12\r\n\x05nodes\x18\x03 \x01(\x05\"\xae\x01\n\x04Node\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x1c\n\x04type\x18\x03 \x01(\x0e\x32\x0e.core.NodeType\x12\r\n\x05model\x18\x04 \x01(\t\x12 \n\x08position\x18\x05 \x01(\x0b\x32\x0e.core.Position\x12\x10\n\x08services\x18\x06 \x03(\t\x12\r\n\x05\x65mane\x18\x07 \x01(\t\x12\x0c\n\x04icon\x18\x08 \x01(\t\x12\x0e\n\x06opaque\x18\t \x01(\t\"\xbc\x01\n\x04Link\x12\x10\n\x08node_one\x18\x01 \x01(\x05\x12\x10\n\x08node_two\x18\x02 \x01(\x05\x12\x1c\n\x04type\x18\x03 \x01(\x0e\x32\x0e.core.LinkType\x12&\n\rinterface_one\x18\x04 \x01(\x0b\x32\x0f.core.Interface\x12&\n\rinterface_two\x18\x05 \x01(\x0b\x32\x0f.core.Interface\x12\"\n\x07options\x18\x06 \x01(\x0b\x32\x11.core.LinkOptions\"\xba\x01\n\x0bLinkOptions\x12\x0e\n\x06opaque\x18\x01 \x01(\t\x12\x0e\n\x06jitter\x18\x02 \x01(\x02\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0e\n\x06mburst\x18\x04 \x01(\x02\x12\x0b\n\x03mer\x18\x05 \x01(\x02\x12\x0b\n\x03per\x18\x06 \x01(\x02\x12\x11\n\tbandwidth\x18\x07 \x01(\x02\x12\r\n\x05\x62urst\x18\x08 \x01(\x02\x12\r\n\x05\x64\x65lay\x18\t \x01(\x02\x12\x0b\n\x03\x64up\x18\n \x01(\x02\x12\x16\n\x0eunidirectional\x18\x0b \x01(\x08\"\x9a\x01\n\tInterface\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0b\n\x03mac\x18\x03 \x01(\t\x12\x0b\n\x03ip4\x18\x04 \x01(\t\x12\x0f\n\x07ip4mask\x18\x05 \x01(\x05\x12\x0b\n\x03ip6\x18\x06 \x01(\t\x12\x0f\n\x07ip6mask\x18\x07 \x01(\x05\x12\r\n\x05netid\x18\x08 \x01(\x05\x12\x0e\n\x06\x66lowid\x18\t \x01(\x05\x12\x0b\n\x03mtu\x18\n \x01(\x05\"R\n\x08Position\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\x12\x0b\n\x03lat\x18\x04 \x01(\x02\x12\x0b\n\x03lon\x18\x05 \x01(\x02\x12\x0b\n\x03\x61lt\x18\x06 \x01(\x02*\x9f\x01\n\x0bMessageType\x12\x10\n\x0cMESSAGE_NONE\x10\x00\x12\x0f\n\x0bMESSAGE_ADD\x10\x01\x12\x12\n\x0eMESSAGE_DELETE\x10\x02\x12\x0f\n\x0bMESSAGE_CRI\x10\x04\x12\x11\n\rMESSAGE_LOCAL\x10\x08\x12\x12\n\x0eMESSAGE_STRING\x10\x10\x12\x10\n\x0cMESSAGE_TEXT\x10 \x12\x0f\n\x0bMESSAGE_TTY\x10@*-\n\x08LinkType\x12\x11\n\rLINK_WIRELESS\x10\x00\x12\x0e\n\nLINK_WIRED\x10\x01*\xa4\x01\n\x0cSessionState\x12\x0e\n\nSTATE_NONE\x10\x00\x12\x14\n\x10STATE_DEFINITION\x10\x01\x12\x17\n\x13STATE_CONFIGURATION\x10\x02\x12\x17\n\x13STATE_INSTANTIATION\x10\x03\x12\x11\n\rSTATE_RUNTIME\x10\x04\x12\x15\n\x11STATE_DATACOLLECT\x10\x05\x12\x12\n\x0eSTATE_SHUTDOWN\x10\x06*\x8b\x02\n\x08NodeType\x12\x10\n\x0cNODE_DEFAULT\x10\x00\x12\x11\n\rNODE_PHYSICAL\x10\x01\x12\x0c\n\x08NODE_TBD\x10\x03\x12\x0f\n\x0bNODE_SWITCH\x10\x04\x12\x0c\n\x08NODE_HUB\x10\x05\x12\x15\n\x11NODE_WIRELESS_LAN\x10\x06\x12\r\n\tNODE_RJ45\x10\x07\x12\x0f\n\x0bNODE_TUNNEL\x10\x08\x12\x10\n\x0cNODE_KTUNNEL\x10\t\x12\x0e\n\nNODE_EMANE\x10\n\x12\x13\n\x0fNODE_TAP_BRIDGE\x10\x0b\x12\x15\n\x11NODE_PEER_TO_PEER\x10\x0c\x12\x14\n\x10NODE_CONTROL_NET\x10\r\x12\x12\n\x0eNODE_EMANE_NET\x10\x0e*c\n\x15ServiceValidationMode\x12\x17\n\x13VALIDATION_BLOCKING\x10\x00\x12\x1b\n\x17VALIDATION_NON_BLOCKING\x10\x01\x12\x14\n\x10VALIDATION_TIMER\x10\x02*_\n\rServiceAction\x12\x11\n\rSERVICE_START\x10\x00\x12\x10\n\x0cSERVICE_STOP\x10\x01\x12\x13\n\x0fSERVICE_RESTART\x10\x02\x12\x14\n\x10SERVICE_VALIDATE\x10\x03*K\n\x0eMobilityAction\x12\x12\n\x0eMOBILITY_START\x10\x00\x12\x12\n\x0eMOBILITY_PAUSE\x10\x01\x12\x11\n\rMOBILITY_STOP\x10\x02*~\n\x0e\x45xceptionLevel\x12\x15\n\x11\x45XCEPTION_DEFAULT\x10\x00\x12\x13\n\x0f\x45XCEPTION_FATAL\x10\x01\x12\x13\n\x0f\x45XCEPTION_ERROR\x10\x02\x12\x15\n\x11\x45XCEPTION_WARNING\x10\x03\x12\x14\n\x10\x45XCEPTION_NOTICE\x10\x04\x32\xe2\x1b\n\x07\x43oreApi\x12J\n\rCreateSession\x12\x1a.core.CreateSessionRequest\x1a\x1b.core.CreateSessionResponse\"\x00\x12J\n\rDeleteSession\x12\x1a.core.DeleteSessionRequest\x1a\x1b.core.DeleteSessionResponse\"\x00\x12\x44\n\x0bGetSessions\x12\x18.core.GetSessionsRequest\x1a\x19.core.GetSessionsResponse\"\x00\x12\x41\n\nGetSession\x12\x17.core.GetSessionRequest\x1a\x18.core.GetSessionResponse\"\x00\x12V\n\x11GetSessionOptions\x12\x1e.core.GetSessionOptionsRequest\x1a\x1f.core.GetSessionOptionsResponse\"\x00\x12V\n\x11SetSessionOptions\x12\x1e.core.SetSessionOptionsRequest\x1a\x1f.core.SetSessionOptionsResponse\"\x00\x12Y\n\x12GetSessionLocation\x12\x1f.core.GetSessionLocationRequest\x1a .core.GetSessionLocationResponse\"\x00\x12Y\n\x12SetSessionLocation\x12\x1f.core.SetSessionLocationRequest\x1a .core.SetSessionLocationResponse\"\x00\x12P\n\x0fSetSessionState\x12\x1c.core.SetSessionStateRequest\x1a\x1d.core.SetSessionStateResponse\"\x00\x12:\n\nNodeEvents\x12\x17.core.NodeEventsRequest\x1a\x0f.core.NodeEvent\"\x00\x30\x01\x12:\n\nLinkEvents\x12\x17.core.LinkEventsRequest\x1a\x0f.core.LinkEvent\"\x00\x30\x01\x12\x43\n\rSessionEvents\x12\x1a.core.SessionEventsRequest\x1a\x12.core.SessionEvent\"\x00\x30\x01\x12@\n\x0c\x43onfigEvents\x12\x19.core.ConfigEventsRequest\x1a\x11.core.ConfigEvent\"\x00\x30\x01\x12I\n\x0f\x45xceptionEvents\x12\x1c.core.ExceptionEventsRequest\x1a\x14.core.ExceptionEvent\"\x00\x30\x01\x12:\n\nFileEvents\x12\x17.core.FileEventsRequest\x1a\x0f.core.FileEvent\"\x00\x30\x01\x12\x38\n\x07\x41\x64\x64Node\x12\x14.core.AddNodeRequest\x1a\x15.core.AddNodeResponse\"\x00\x12\x38\n\x07GetNode\x12\x14.core.GetNodeRequest\x1a\x15.core.GetNodeResponse\"\x00\x12;\n\x08\x45\x64itNode\x12\x15.core.EditNodeRequest\x1a\x16.core.EditNodeResponse\"\x00\x12\x41\n\nDeleteNode\x12\x17.core.DeleteNodeRequest\x1a\x18.core.DeleteNodeResponse\"\x00\x12G\n\x0cGetNodeLinks\x12\x19.core.GetNodeLinksRequest\x1a\x1a.core.GetNodeLinksResponse\"\x00\x12\x38\n\x07\x41\x64\x64Link\x12\x14.core.AddLinkRequest\x1a\x15.core.AddLinkResponse\"\x00\x12;\n\x08\x45\x64itLink\x12\x15.core.EditLinkRequest\x1a\x16.core.EditLinkResponse\"\x00\x12\x41\n\nDeleteLink\x12\x17.core.DeleteLinkRequest\x1a\x18.core.DeleteLinkResponse\"\x00\x12;\n\x08GetHooks\x12\x15.core.GetHooksRequest\x1a\x16.core.GetHooksResponse\"\x00\x12\x38\n\x07\x41\x64\x64Hook\x12\x14.core.AddHookRequest\x1a\x15.core.AddHookResponse\"\x00\x12Y\n\x12GetMobilityConfigs\x12\x1f.core.GetMobilityConfigsRequest\x1a .core.GetMobilityConfigsResponse\"\x00\x12V\n\x11GetMobilityConfig\x12\x1e.core.GetMobilityConfigRequest\x1a\x1f.core.GetMobilityConfigResponse\"\x00\x12V\n\x11SetMobilityConfig\x12\x1e.core.SetMobilityConfigRequest\x1a\x1f.core.SetMobilityConfigResponse\"\x00\x12M\n\x0eMobilityAction\x12\x1b.core.MobilityActionRequest\x1a\x1c.core.MobilityActionResponse\"\x00\x12\x44\n\x0bGetServices\x12\x18.core.GetServicesRequest\x1a\x19.core.GetServicesResponse\"\x00\x12Y\n\x12GetServiceDefaults\x12\x1f.core.GetServiceDefaultsRequest\x1a .core.GetServiceDefaultsResponse\"\x00\x12Y\n\x12SetServiceDefaults\x12\x1f.core.SetServiceDefaultsRequest\x1a .core.SetServiceDefaultsResponse\"\x00\x12M\n\x0eGetNodeService\x12\x1b.core.GetNodeServiceRequest\x1a\x1c.core.GetNodeServiceResponse\"\x00\x12Y\n\x12GetNodeServiceFile\x12\x1f.core.GetNodeServiceFileRequest\x1a .core.GetNodeServiceFileResponse\"\x00\x12M\n\x0eSetNodeService\x12\x1b.core.SetNodeServiceRequest\x1a\x1c.core.SetNodeServiceResponse\"\x00\x12Y\n\x12SetNodeServiceFile\x12\x1f.core.SetNodeServiceFileRequest\x1a .core.SetNodeServiceFileResponse\"\x00\x12J\n\rServiceAction\x12\x1a.core.ServiceActionRequest\x1a\x1b.core.ServiceActionResponse\"\x00\x12J\n\rGetWlanConfig\x12\x1a.core.GetWlanConfigRequest\x1a\x1b.core.GetWlanConfigResponse\"\x00\x12J\n\rSetWlanConfig\x12\x1a.core.SetWlanConfigRequest\x1a\x1b.core.SetWlanConfigResponse\"\x00\x12M\n\x0eGetEmaneConfig\x12\x1b.core.GetEmaneConfigRequest\x1a\x1c.core.GetEmaneConfigResponse\"\x00\x12M\n\x0eSetEmaneConfig\x12\x1b.core.SetEmaneConfigRequest\x1a\x1c.core.SetEmaneConfigResponse\"\x00\x12M\n\x0eGetEmaneModels\x12\x1b.core.GetEmaneModelsRequest\x1a\x1c.core.GetEmaneModelsResponse\"\x00\x12\\\n\x13GetEmaneModelConfig\x12 .core.GetEmaneModelConfigRequest\x1a!.core.GetEmaneModelConfigResponse\"\x00\x12\\\n\x13SetEmaneModelConfig\x12 .core.SetEmaneModelConfigRequest\x1a!.core.SetEmaneModelConfigResponse\"\x00\x12_\n\x14GetEmaneModelConfigs\x12!.core.GetEmaneModelConfigsRequest\x1a\".core.GetEmaneModelConfigsResponse\"\x00\x12\x38\n\x07SaveXml\x12\x14.core.SaveXmlRequest\x1a\x15.core.SaveXmlResponse\"\x00\x12\x38\n\x07OpenXml\x12\x14.core.OpenXmlRequest\x1a\x15.core.OpenXmlResponse\"\x00\x62\x06proto3') ) _MESSAGETYPE = _descriptor.EnumDescriptor( @@ -64,8 +64,8 @@ _MESSAGETYPE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=8194, - serialized_end=8353, + serialized_start=8208, + serialized_end=8367, ) _sym_db.RegisterEnumDescriptor(_MESSAGETYPE) @@ -87,8 +87,8 @@ _LINKTYPE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=8355, - serialized_end=8400, + serialized_start=8369, + serialized_end=8414, ) _sym_db.RegisterEnumDescriptor(_LINKTYPE) @@ -130,8 +130,8 @@ _SESSIONSTATE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=8403, - serialized_end=8567, + serialized_start=8417, + serialized_end=8581, ) _sym_db.RegisterEnumDescriptor(_SESSIONSTATE) @@ -201,8 +201,8 @@ _NODETYPE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=8570, - serialized_end=8837, + serialized_start=8584, + serialized_end=8851, ) _sym_db.RegisterEnumDescriptor(_NODETYPE) @@ -228,8 +228,8 @@ _SERVICEVALIDATIONMODE = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=8839, - serialized_end=8938, + serialized_start=8853, + serialized_end=8952, ) _sym_db.RegisterEnumDescriptor(_SERVICEVALIDATIONMODE) @@ -259,8 +259,8 @@ _SERVICEACTION = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=8940, - serialized_end=9035, + serialized_start=8954, + serialized_end=9049, ) _sym_db.RegisterEnumDescriptor(_SERVICEACTION) @@ -286,8 +286,8 @@ _MOBILITYACTION = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=9037, - serialized_end=9112, + serialized_start=9051, + serialized_end=9126, ) _sym_db.RegisterEnumDescriptor(_MOBILITYACTION) @@ -321,8 +321,8 @@ _EXCEPTIONLEVEL = _descriptor.EnumDescriptor( ], containing_type=None, serialized_options=None, - serialized_start=9114, - serialized_end=9240, + serialized_start=9128, + serialized_end=9254, ) _sym_db.RegisterEnumDescriptor(_EXCEPTIONLEVEL) @@ -402,8 +402,8 @@ _CREATESESSIONREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=20, - serialized_end=54, + serialized_start=34, + serialized_end=68, ) @@ -440,8 +440,8 @@ _CREATESESSIONRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=56, - serialized_end=126, + serialized_start=70, + serialized_end=140, ) @@ -471,8 +471,8 @@ _DELETESESSIONREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=128, - serialized_end=162, + serialized_start=142, + serialized_end=176, ) @@ -502,8 +502,8 @@ _DELETESESSIONRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=164, - serialized_end=203, + serialized_start=178, + serialized_end=217, ) @@ -526,8 +526,8 @@ _GETSESSIONSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=205, - serialized_end=225, + serialized_start=219, + serialized_end=239, ) @@ -557,8 +557,8 @@ _GETSESSIONSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=227, - serialized_end=288, + serialized_start=241, + serialized_end=302, ) @@ -588,8 +588,8 @@ _GETSESSIONREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=290, - serialized_end=321, + serialized_start=304, + serialized_end=335, ) @@ -619,8 +619,8 @@ _GETSESSIONRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=323, - serialized_end=375, + serialized_start=337, + serialized_end=389, ) @@ -650,8 +650,8 @@ _GETSESSIONOPTIONSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=377, - serialized_end=415, + serialized_start=391, + serialized_end=429, ) @@ -681,8 +681,8 @@ _GETSESSIONOPTIONSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=417, - serialized_end=479, + serialized_start=431, + serialized_end=493, ) @@ -719,8 +719,8 @@ _SETSESSIONOPTIONSREQUEST_CONFIGENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=582, - serialized_end=627, + serialized_start=596, + serialized_end=641, ) _SETSESSIONOPTIONSREQUEST = _descriptor.Descriptor( @@ -756,8 +756,8 @@ _SETSESSIONOPTIONSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=482, - serialized_end=627, + serialized_start=496, + serialized_end=641, ) @@ -787,8 +787,8 @@ _SETSESSIONOPTIONSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=629, - serialized_end=672, + serialized_start=643, + serialized_end=686, ) @@ -818,8 +818,8 @@ _GETSESSIONLOCATIONREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=674, - serialized_end=713, + serialized_start=688, + serialized_end=727, ) @@ -856,8 +856,8 @@ _GETSESSIONLOCATIONRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=715, - serialized_end=792, + serialized_start=729, + serialized_end=806, ) @@ -901,8 +901,8 @@ _SETSESSIONLOCATIONREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=794, - serialized_end=882, + serialized_start=808, + serialized_end=896, ) @@ -932,8 +932,8 @@ _SETSESSIONLOCATIONRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=884, - serialized_end=928, + serialized_start=898, + serialized_end=942, ) @@ -970,8 +970,8 @@ _SETSESSIONSTATEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=930, - serialized_end=1001, + serialized_start=944, + serialized_end=1015, ) @@ -1001,8 +1001,8 @@ _SETSESSIONSTATERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1003, - serialized_end=1044, + serialized_start=1017, + serialized_end=1058, ) @@ -1032,8 +1032,8 @@ _NODEEVENTSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1046, - serialized_end=1077, + serialized_start=1060, + serialized_end=1091, ) @@ -1063,8 +1063,8 @@ _NODEEVENT = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1079, - serialized_end=1116, + serialized_start=1093, + serialized_end=1130, ) @@ -1094,8 +1094,8 @@ _LINKEVENTSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1118, - serialized_end=1149, + serialized_start=1132, + serialized_end=1163, ) @@ -1132,8 +1132,8 @@ _LINKEVENT = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1151, - serialized_end=1229, + serialized_start=1165, + serialized_end=1243, ) @@ -1163,8 +1163,8 @@ _SESSIONEVENTSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1231, - serialized_end=1265, + serialized_start=1245, + serialized_end=1279, ) @@ -1229,8 +1229,8 @@ _SESSIONEVENT = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1267, - serialized_end=1369, + serialized_start=1281, + serialized_end=1383, ) @@ -1260,8 +1260,8 @@ _CONFIGEVENTSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1371, - serialized_end=1404, + serialized_start=1385, + serialized_end=1418, ) @@ -1382,8 +1382,8 @@ _CONFIGEVENT = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1407, - serialized_end=1693, + serialized_start=1421, + serialized_end=1707, ) @@ -1413,8 +1413,8 @@ _EXCEPTIONEVENTSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1695, - serialized_end=1731, + serialized_start=1709, + serialized_end=1745, ) @@ -1486,8 +1486,8 @@ _EXCEPTIONEVENT = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1734, - serialized_end=1878, + serialized_start=1748, + serialized_end=1892, ) @@ -1517,8 +1517,8 @@ _FILEEVENTSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1880, - serialized_end=1911, + serialized_start=1894, + serialized_end=1925, ) @@ -1611,8 +1611,8 @@ _FILEEVENT = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=1914, - serialized_end=2110, + serialized_start=1928, + serialized_end=2124, ) @@ -1649,8 +1649,8 @@ _ADDNODEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2112, - serialized_end=2171, + serialized_start=2126, + serialized_end=2185, ) @@ -1680,8 +1680,8 @@ _ADDNODERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2173, - serialized_end=2202, + serialized_start=2187, + serialized_end=2216, ) @@ -1718,8 +1718,8 @@ _GETNODEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2204, - serialized_end=2249, + serialized_start=2218, + serialized_end=2263, ) @@ -1756,8 +1756,8 @@ _GETNODERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2251, - serialized_end=2331, + serialized_start=2265, + serialized_end=2345, ) @@ -1801,8 +1801,8 @@ _EDITNODEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2333, - serialized_end=2413, + serialized_start=2347, + serialized_end=2427, ) @@ -1832,8 +1832,8 @@ _EDITNODERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2415, - serialized_end=2449, + serialized_start=2429, + serialized_end=2463, ) @@ -1870,8 +1870,8 @@ _DELETENODEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2451, - serialized_end=2499, + serialized_start=2465, + serialized_end=2513, ) @@ -1901,8 +1901,8 @@ _DELETENODERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2501, - serialized_end=2537, + serialized_start=2515, + serialized_end=2551, ) @@ -1939,8 +1939,8 @@ _GETNODELINKSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2539, - serialized_end=2589, + serialized_start=2553, + serialized_end=2603, ) @@ -1970,8 +1970,8 @@ _GETNODELINKSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2591, - serialized_end=2640, + serialized_start=2605, + serialized_end=2654, ) @@ -2008,8 +2008,8 @@ _ADDLINKREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2642, - serialized_end=2701, + serialized_start=2656, + serialized_end=2715, ) @@ -2039,8 +2039,8 @@ _ADDLINKRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2703, - serialized_end=2736, + serialized_start=2717, + serialized_end=2750, ) @@ -2105,8 +2105,8 @@ _EDITLINKREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2739, - serialized_end=2891, + serialized_start=2753, + serialized_end=2905, ) @@ -2136,8 +2136,8 @@ _EDITLINKRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2893, - serialized_end=2927, + serialized_start=2907, + serialized_end=2941, ) @@ -2195,8 +2195,8 @@ _DELETELINKREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=2929, - serialized_end=3047, + serialized_start=2943, + serialized_end=3061, ) @@ -2226,8 +2226,8 @@ _DELETELINKRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3049, - serialized_end=3085, + serialized_start=3063, + serialized_end=3099, ) @@ -2257,8 +2257,8 @@ _GETHOOKSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3087, - serialized_end=3121, + serialized_start=3101, + serialized_end=3135, ) @@ -2288,8 +2288,8 @@ _GETHOOKSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3123, - serialized_end=3168, + serialized_start=3137, + serialized_end=3182, ) @@ -2326,8 +2326,8 @@ _ADDHOOKREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3170, - serialized_end=3229, + serialized_start=3184, + serialized_end=3243, ) @@ -2357,8 +2357,8 @@ _ADDHOOKRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3231, - serialized_end=3264, + serialized_start=3245, + serialized_end=3278, ) @@ -2388,8 +2388,8 @@ _GETMOBILITYCONFIGSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3266, - serialized_end=3310, + serialized_start=3280, + serialized_end=3324, ) @@ -2419,8 +2419,8 @@ _GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3407, - serialized_end=3458, + serialized_start=3421, + serialized_end=3472, ) _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY = _descriptor.Descriptor( @@ -2456,8 +2456,8 @@ _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3460, - serialized_end=3555, + serialized_start=3474, + serialized_end=3569, ) _GETMOBILITYCONFIGSRESPONSE = _descriptor.Descriptor( @@ -2486,8 +2486,8 @@ _GETMOBILITYCONFIGSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3313, - serialized_end=3555, + serialized_start=3327, + serialized_end=3569, ) @@ -2524,8 +2524,8 @@ _GETMOBILITYCONFIGREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3557, - serialized_end=3612, + serialized_start=3571, + serialized_end=3626, ) @@ -2555,8 +2555,8 @@ _GETMOBILITYCONFIGRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3614, - serialized_end=3676, + serialized_start=3628, + serialized_end=3690, ) @@ -2593,8 +2593,8 @@ _SETMOBILITYCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=582, - serialized_end=627, + serialized_start=596, + serialized_end=641, ) _SETMOBILITYCONFIGREQUEST = _descriptor.Descriptor( @@ -2637,8 +2637,8 @@ _SETMOBILITYCONFIGREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3679, - serialized_end=3841, + serialized_start=3693, + serialized_end=3855, ) @@ -2668,8 +2668,8 @@ _SETMOBILITYCONFIGRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3843, - serialized_end=3886, + serialized_start=3857, + serialized_end=3900, ) @@ -2713,8 +2713,8 @@ _MOBILITYACTIONREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3888, - serialized_end=3978, + serialized_start=3902, + serialized_end=3992, ) @@ -2744,8 +2744,8 @@ _MOBILITYACTIONRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=3980, - serialized_end=4020, + serialized_start=3994, + serialized_end=4034, ) @@ -2768,8 +2768,8 @@ _GETSERVICESREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4022, - serialized_end=4042, + serialized_start=4036, + serialized_end=4056, ) @@ -2799,8 +2799,8 @@ _GETSERVICESRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4044, - serialized_end=4098, + serialized_start=4058, + serialized_end=4112, ) @@ -2830,8 +2830,8 @@ _GETSERVICEDEFAULTSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4100, - serialized_end=4144, + serialized_start=4114, + serialized_end=4158, ) @@ -2861,8 +2861,8 @@ _GETSERVICEDEFAULTSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4146, - serialized_end=4215, + serialized_start=4160, + serialized_end=4229, ) @@ -2899,8 +2899,8 @@ _SETSERVICEDEFAULTSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4217, - serialized_end=4302, + serialized_start=4231, + serialized_end=4316, ) @@ -2930,8 +2930,8 @@ _SETSERVICEDEFAULTSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4304, - serialized_end=4348, + serialized_start=4318, + serialized_end=4362, ) @@ -2975,8 +2975,8 @@ _GETNODESERVICEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4350, - serialized_end=4419, + serialized_start=4364, + serialized_end=4433, ) @@ -3006,8 +3006,8 @@ _GETNODESERVICERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4421, - serialized_end=4485, + serialized_start=4435, + serialized_end=4499, ) @@ -3058,8 +3058,8 @@ _GETNODESERVICEFILEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4487, - serialized_end=4574, + serialized_start=4501, + serialized_end=4588, ) @@ -3089,8 +3089,8 @@ _GETNODESERVICEFILERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4576, - serialized_end=4618, + serialized_start=4590, + serialized_end=4632, ) @@ -3155,8 +3155,8 @@ _SETNODESERVICEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4620, - serialized_end=4742, + serialized_start=4634, + serialized_end=4756, ) @@ -3186,8 +3186,8 @@ _SETNODESERVICERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4744, - serialized_end=4784, + serialized_start=4758, + serialized_end=4798, ) @@ -3245,8 +3245,8 @@ _SETNODESERVICEFILEREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4786, - serialized_end=4887, + serialized_start=4800, + serialized_end=4901, ) @@ -3276,8 +3276,8 @@ _SETNODESERVICEFILERESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4889, - serialized_end=4933, + serialized_start=4903, + serialized_end=4947, ) @@ -3328,8 +3328,8 @@ _SERVICEACTIONREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=4935, - serialized_end=5040, + serialized_start=4949, + serialized_end=5054, ) @@ -3359,8 +3359,8 @@ _SERVICEACTIONRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5042, - serialized_end=5081, + serialized_start=5056, + serialized_end=5095, ) @@ -3397,8 +3397,8 @@ _GETWLANCONFIGREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5083, - serialized_end=5134, + serialized_start=5097, + serialized_end=5148, ) @@ -3428,8 +3428,8 @@ _GETWLANCONFIGRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5136, - serialized_end=5194, + serialized_start=5150, + serialized_end=5208, ) @@ -3466,8 +3466,8 @@ _SETWLANCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=582, - serialized_end=627, + serialized_start=596, + serialized_end=641, ) _SETWLANCONFIGREQUEST = _descriptor.Descriptor( @@ -3510,8 +3510,8 @@ _SETWLANCONFIGREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5197, - serialized_end=5351, + serialized_start=5211, + serialized_end=5365, ) @@ -3541,8 +3541,8 @@ _SETWLANCONFIGRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5353, - serialized_end=5392, + serialized_start=5367, + serialized_end=5406, ) @@ -3572,8 +3572,8 @@ _GETEMANECONFIGREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5394, - serialized_end=5434, + serialized_start=5408, + serialized_end=5448, ) @@ -3603,8 +3603,8 @@ _GETEMANECONFIGRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5436, - serialized_end=5495, + serialized_start=5450, + serialized_end=5509, ) @@ -3641,8 +3641,8 @@ _SETEMANECONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=582, - serialized_end=627, + serialized_start=596, + serialized_end=641, ) _SETEMANECONFIGREQUEST = _descriptor.Descriptor( @@ -3678,8 +3678,8 @@ _SETEMANECONFIGREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5498, - serialized_end=5642, + serialized_start=5512, + serialized_end=5656, ) @@ -3709,8 +3709,8 @@ _SETEMANECONFIGRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5644, - serialized_end=5684, + serialized_start=5658, + serialized_end=5698, ) @@ -3740,8 +3740,8 @@ _GETEMANEMODELSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5686, - serialized_end=5726, + serialized_start=5700, + serialized_end=5740, ) @@ -3771,8 +3771,8 @@ _GETEMANEMODELSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5728, - serialized_end=5768, + serialized_start=5742, + serialized_end=5782, ) @@ -3823,8 +3823,8 @@ _GETEMANEMODELCONFIGREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5770, - serialized_end=5861, + serialized_start=5784, + serialized_end=5875, ) @@ -3854,8 +3854,8 @@ _GETEMANEMODELCONFIGRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5863, - serialized_end=5927, + serialized_start=5877, + serialized_end=5941, ) @@ -3892,8 +3892,8 @@ _SETEMANEMODELCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=582, - serialized_end=627, + serialized_start=596, + serialized_end=641, ) _SETEMANEMODELCONFIGREQUEST = _descriptor.Descriptor( @@ -3950,8 +3950,8 @@ _SETEMANEMODELCONFIGREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=5930, - serialized_end=6130, + serialized_start=5944, + serialized_end=6144, ) @@ -3981,8 +3981,8 @@ _SETEMANEMODELCONFIGRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6132, - serialized_end=6177, + serialized_start=6146, + serialized_end=6191, ) @@ -4012,8 +4012,8 @@ _GETEMANEMODELCONFIGSREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6179, - serialized_end=6225, + serialized_start=6193, + serialized_end=6239, ) @@ -4050,8 +4050,8 @@ _GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6326, - serialized_end=6389, + serialized_start=6340, + serialized_end=6403, ) _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY = _descriptor.Descriptor( @@ -4087,8 +4087,8 @@ _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6391, - serialized_end=6485, + serialized_start=6405, + serialized_end=6499, ) _GETEMANEMODELCONFIGSRESPONSE = _descriptor.Descriptor( @@ -4117,8 +4117,8 @@ _GETEMANEMODELCONFIGSRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6228, - serialized_end=6485, + serialized_start=6242, + serialized_end=6499, ) @@ -4148,8 +4148,8 @@ _SAVEXMLREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6487, - serialized_end=6520, + serialized_start=6501, + serialized_end=6534, ) @@ -4179,8 +4179,8 @@ _SAVEXMLRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6522, - serialized_end=6553, + serialized_start=6536, + serialized_end=6567, ) @@ -4210,8 +4210,8 @@ _OPENXMLREQUEST = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6555, - serialized_end=6585, + serialized_start=6569, + serialized_end=6599, ) @@ -4248,8 +4248,8 @@ _OPENXMLRESPONSE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6587, - serialized_end=6637, + serialized_start=6601, + serialized_end=6651, ) @@ -4293,8 +4293,8 @@ _HOOK = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6639, - serialized_end=6708, + serialized_start=6653, + serialized_end=6722, ) @@ -4331,8 +4331,8 @@ _SERVICEDEFAULTS = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6710, - serialized_end=6764, + serialized_start=6724, + serialized_end=6778, ) @@ -4369,8 +4369,8 @@ _SERVICE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6766, - serialized_end=6804, + serialized_start=6780, + serialized_end=6818, ) @@ -4463,8 +4463,8 @@ _NODESERVICEDATA = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=6807, - serialized_end=7045, + serialized_start=6821, + serialized_end=7059, ) @@ -4501,8 +4501,8 @@ _CONFIGGROUP = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=7047, - serialized_end=7111, + serialized_start=7061, + serialized_end=7125, ) @@ -4560,8 +4560,8 @@ _CONFIGOPTION = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=7113, - serialized_end=7201, + serialized_start=7127, + serialized_end=7215, ) @@ -4612,8 +4612,8 @@ _SESSION = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=7203, - serialized_end=7313, + serialized_start=7217, + serialized_end=7327, ) @@ -4657,8 +4657,8 @@ _SESSIONSUMMARY = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=7315, - serialized_end=7393, + serialized_start=7329, + serialized_end=7407, ) @@ -4744,8 +4744,8 @@ _NODE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=7396, - serialized_end=7570, + serialized_start=7410, + serialized_end=7584, ) @@ -4810,8 +4810,8 @@ _LINK = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=7573, - serialized_end=7761, + serialized_start=7587, + serialized_end=7775, ) @@ -4911,8 +4911,8 @@ _LINKOPTIONS = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=7764, - serialized_end=7950, + serialized_start=7778, + serialized_end=7964, ) @@ -5005,8 +5005,8 @@ _INTERFACE = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=7953, - serialized_end=8107, + serialized_start=7967, + serialized_end=8121, ) @@ -5071,8 +5071,8 @@ _POSITION = _descriptor.Descriptor( extension_ranges=[], oneofs=[ ], - serialized_start=8109, - serialized_end=8191, + serialized_start=8123, + serialized_end=8205, ) _CREATESESSIONRESPONSE.fields_by_name['state'].enum_type = _SESSIONSTATE @@ -5259,70 +5259,70 @@ _sym_db.RegisterFileDescriptor(DESCRIPTOR) CreateSessionRequest = _reflection.GeneratedProtocolMessageType('CreateSessionRequest', (_message.Message,), dict( DESCRIPTOR = _CREATESESSIONREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.CreateSessionRequest) )) _sym_db.RegisterMessage(CreateSessionRequest) CreateSessionResponse = _reflection.GeneratedProtocolMessageType('CreateSessionResponse', (_message.Message,), dict( DESCRIPTOR = _CREATESESSIONRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.CreateSessionResponse) )) _sym_db.RegisterMessage(CreateSessionResponse) DeleteSessionRequest = _reflection.GeneratedProtocolMessageType('DeleteSessionRequest', (_message.Message,), dict( DESCRIPTOR = _DELETESESSIONREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.DeleteSessionRequest) )) _sym_db.RegisterMessage(DeleteSessionRequest) DeleteSessionResponse = _reflection.GeneratedProtocolMessageType('DeleteSessionResponse', (_message.Message,), dict( DESCRIPTOR = _DELETESESSIONRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.DeleteSessionResponse) )) _sym_db.RegisterMessage(DeleteSessionResponse) GetSessionsRequest = _reflection.GeneratedProtocolMessageType('GetSessionsRequest', (_message.Message,), dict( DESCRIPTOR = _GETSESSIONSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetSessionsRequest) )) _sym_db.RegisterMessage(GetSessionsRequest) GetSessionsResponse = _reflection.GeneratedProtocolMessageType('GetSessionsResponse', (_message.Message,), dict( DESCRIPTOR = _GETSESSIONSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetSessionsResponse) )) _sym_db.RegisterMessage(GetSessionsResponse) GetSessionRequest = _reflection.GeneratedProtocolMessageType('GetSessionRequest', (_message.Message,), dict( DESCRIPTOR = _GETSESSIONREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetSessionRequest) )) _sym_db.RegisterMessage(GetSessionRequest) GetSessionResponse = _reflection.GeneratedProtocolMessageType('GetSessionResponse', (_message.Message,), dict( DESCRIPTOR = _GETSESSIONRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetSessionResponse) )) _sym_db.RegisterMessage(GetSessionResponse) GetSessionOptionsRequest = _reflection.GeneratedProtocolMessageType('GetSessionOptionsRequest', (_message.Message,), dict( DESCRIPTOR = _GETSESSIONOPTIONSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetSessionOptionsRequest) )) _sym_db.RegisterMessage(GetSessionOptionsRequest) GetSessionOptionsResponse = _reflection.GeneratedProtocolMessageType('GetSessionOptionsResponse', (_message.Message,), dict( DESCRIPTOR = _GETSESSIONOPTIONSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetSessionOptionsResponse) )) _sym_db.RegisterMessage(GetSessionOptionsResponse) @@ -5331,12 +5331,12 @@ SetSessionOptionsRequest = _reflection.GeneratedProtocolMessageType('SetSessionO ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( DESCRIPTOR = _SETSESSIONOPTIONSREQUEST_CONFIGENTRY, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetSessionOptionsRequest.ConfigEntry) )) , DESCRIPTOR = _SETSESSIONOPTIONSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetSessionOptionsRequest) )) _sym_db.RegisterMessage(SetSessionOptionsRequest) @@ -5344,280 +5344,280 @@ _sym_db.RegisterMessage(SetSessionOptionsRequest.ConfigEntry) SetSessionOptionsResponse = _reflection.GeneratedProtocolMessageType('SetSessionOptionsResponse', (_message.Message,), dict( DESCRIPTOR = _SETSESSIONOPTIONSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetSessionOptionsResponse) )) _sym_db.RegisterMessage(SetSessionOptionsResponse) GetSessionLocationRequest = _reflection.GeneratedProtocolMessageType('GetSessionLocationRequest', (_message.Message,), dict( DESCRIPTOR = _GETSESSIONLOCATIONREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetSessionLocationRequest) )) _sym_db.RegisterMessage(GetSessionLocationRequest) GetSessionLocationResponse = _reflection.GeneratedProtocolMessageType('GetSessionLocationResponse', (_message.Message,), dict( DESCRIPTOR = _GETSESSIONLOCATIONRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetSessionLocationResponse) )) _sym_db.RegisterMessage(GetSessionLocationResponse) SetSessionLocationRequest = _reflection.GeneratedProtocolMessageType('SetSessionLocationRequest', (_message.Message,), dict( DESCRIPTOR = _SETSESSIONLOCATIONREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetSessionLocationRequest) )) _sym_db.RegisterMessage(SetSessionLocationRequest) SetSessionLocationResponse = _reflection.GeneratedProtocolMessageType('SetSessionLocationResponse', (_message.Message,), dict( DESCRIPTOR = _SETSESSIONLOCATIONRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetSessionLocationResponse) )) _sym_db.RegisterMessage(SetSessionLocationResponse) SetSessionStateRequest = _reflection.GeneratedProtocolMessageType('SetSessionStateRequest', (_message.Message,), dict( DESCRIPTOR = _SETSESSIONSTATEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetSessionStateRequest) )) _sym_db.RegisterMessage(SetSessionStateRequest) SetSessionStateResponse = _reflection.GeneratedProtocolMessageType('SetSessionStateResponse', (_message.Message,), dict( DESCRIPTOR = _SETSESSIONSTATERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetSessionStateResponse) )) _sym_db.RegisterMessage(SetSessionStateResponse) NodeEventsRequest = _reflection.GeneratedProtocolMessageType('NodeEventsRequest', (_message.Message,), dict( DESCRIPTOR = _NODEEVENTSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.NodeEventsRequest) )) _sym_db.RegisterMessage(NodeEventsRequest) NodeEvent = _reflection.GeneratedProtocolMessageType('NodeEvent', (_message.Message,), dict( DESCRIPTOR = _NODEEVENT, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.NodeEvent) )) _sym_db.RegisterMessage(NodeEvent) LinkEventsRequest = _reflection.GeneratedProtocolMessageType('LinkEventsRequest', (_message.Message,), dict( DESCRIPTOR = _LINKEVENTSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.LinkEventsRequest) )) _sym_db.RegisterMessage(LinkEventsRequest) LinkEvent = _reflection.GeneratedProtocolMessageType('LinkEvent', (_message.Message,), dict( DESCRIPTOR = _LINKEVENT, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.LinkEvent) )) _sym_db.RegisterMessage(LinkEvent) SessionEventsRequest = _reflection.GeneratedProtocolMessageType('SessionEventsRequest', (_message.Message,), dict( DESCRIPTOR = _SESSIONEVENTSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SessionEventsRequest) )) _sym_db.RegisterMessage(SessionEventsRequest) SessionEvent = _reflection.GeneratedProtocolMessageType('SessionEvent', (_message.Message,), dict( DESCRIPTOR = _SESSIONEVENT, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SessionEvent) )) _sym_db.RegisterMessage(SessionEvent) ConfigEventsRequest = _reflection.GeneratedProtocolMessageType('ConfigEventsRequest', (_message.Message,), dict( DESCRIPTOR = _CONFIGEVENTSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ConfigEventsRequest) )) _sym_db.RegisterMessage(ConfigEventsRequest) ConfigEvent = _reflection.GeneratedProtocolMessageType('ConfigEvent', (_message.Message,), dict( DESCRIPTOR = _CONFIGEVENT, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ConfigEvent) )) _sym_db.RegisterMessage(ConfigEvent) ExceptionEventsRequest = _reflection.GeneratedProtocolMessageType('ExceptionEventsRequest', (_message.Message,), dict( DESCRIPTOR = _EXCEPTIONEVENTSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ExceptionEventsRequest) )) _sym_db.RegisterMessage(ExceptionEventsRequest) ExceptionEvent = _reflection.GeneratedProtocolMessageType('ExceptionEvent', (_message.Message,), dict( DESCRIPTOR = _EXCEPTIONEVENT, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ExceptionEvent) )) _sym_db.RegisterMessage(ExceptionEvent) FileEventsRequest = _reflection.GeneratedProtocolMessageType('FileEventsRequest', (_message.Message,), dict( DESCRIPTOR = _FILEEVENTSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.FileEventsRequest) )) _sym_db.RegisterMessage(FileEventsRequest) FileEvent = _reflection.GeneratedProtocolMessageType('FileEvent', (_message.Message,), dict( DESCRIPTOR = _FILEEVENT, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.FileEvent) )) _sym_db.RegisterMessage(FileEvent) AddNodeRequest = _reflection.GeneratedProtocolMessageType('AddNodeRequest', (_message.Message,), dict( DESCRIPTOR = _ADDNODEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.AddNodeRequest) )) _sym_db.RegisterMessage(AddNodeRequest) AddNodeResponse = _reflection.GeneratedProtocolMessageType('AddNodeResponse', (_message.Message,), dict( DESCRIPTOR = _ADDNODERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.AddNodeResponse) )) _sym_db.RegisterMessage(AddNodeResponse) GetNodeRequest = _reflection.GeneratedProtocolMessageType('GetNodeRequest', (_message.Message,), dict( DESCRIPTOR = _GETNODEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetNodeRequest) )) _sym_db.RegisterMessage(GetNodeRequest) GetNodeResponse = _reflection.GeneratedProtocolMessageType('GetNodeResponse', (_message.Message,), dict( DESCRIPTOR = _GETNODERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetNodeResponse) )) _sym_db.RegisterMessage(GetNodeResponse) EditNodeRequest = _reflection.GeneratedProtocolMessageType('EditNodeRequest', (_message.Message,), dict( DESCRIPTOR = _EDITNODEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.EditNodeRequest) )) _sym_db.RegisterMessage(EditNodeRequest) EditNodeResponse = _reflection.GeneratedProtocolMessageType('EditNodeResponse', (_message.Message,), dict( DESCRIPTOR = _EDITNODERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.EditNodeResponse) )) _sym_db.RegisterMessage(EditNodeResponse) DeleteNodeRequest = _reflection.GeneratedProtocolMessageType('DeleteNodeRequest', (_message.Message,), dict( DESCRIPTOR = _DELETENODEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.DeleteNodeRequest) )) _sym_db.RegisterMessage(DeleteNodeRequest) DeleteNodeResponse = _reflection.GeneratedProtocolMessageType('DeleteNodeResponse', (_message.Message,), dict( DESCRIPTOR = _DELETENODERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.DeleteNodeResponse) )) _sym_db.RegisterMessage(DeleteNodeResponse) GetNodeLinksRequest = _reflection.GeneratedProtocolMessageType('GetNodeLinksRequest', (_message.Message,), dict( DESCRIPTOR = _GETNODELINKSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetNodeLinksRequest) )) _sym_db.RegisterMessage(GetNodeLinksRequest) GetNodeLinksResponse = _reflection.GeneratedProtocolMessageType('GetNodeLinksResponse', (_message.Message,), dict( DESCRIPTOR = _GETNODELINKSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetNodeLinksResponse) )) _sym_db.RegisterMessage(GetNodeLinksResponse) AddLinkRequest = _reflection.GeneratedProtocolMessageType('AddLinkRequest', (_message.Message,), dict( DESCRIPTOR = _ADDLINKREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.AddLinkRequest) )) _sym_db.RegisterMessage(AddLinkRequest) AddLinkResponse = _reflection.GeneratedProtocolMessageType('AddLinkResponse', (_message.Message,), dict( DESCRIPTOR = _ADDLINKRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.AddLinkResponse) )) _sym_db.RegisterMessage(AddLinkResponse) EditLinkRequest = _reflection.GeneratedProtocolMessageType('EditLinkRequest', (_message.Message,), dict( DESCRIPTOR = _EDITLINKREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.EditLinkRequest) )) _sym_db.RegisterMessage(EditLinkRequest) EditLinkResponse = _reflection.GeneratedProtocolMessageType('EditLinkResponse', (_message.Message,), dict( DESCRIPTOR = _EDITLINKRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.EditLinkResponse) )) _sym_db.RegisterMessage(EditLinkResponse) DeleteLinkRequest = _reflection.GeneratedProtocolMessageType('DeleteLinkRequest', (_message.Message,), dict( DESCRIPTOR = _DELETELINKREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.DeleteLinkRequest) )) _sym_db.RegisterMessage(DeleteLinkRequest) DeleteLinkResponse = _reflection.GeneratedProtocolMessageType('DeleteLinkResponse', (_message.Message,), dict( DESCRIPTOR = _DELETELINKRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.DeleteLinkResponse) )) _sym_db.RegisterMessage(DeleteLinkResponse) GetHooksRequest = _reflection.GeneratedProtocolMessageType('GetHooksRequest', (_message.Message,), dict( DESCRIPTOR = _GETHOOKSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetHooksRequest) )) _sym_db.RegisterMessage(GetHooksRequest) GetHooksResponse = _reflection.GeneratedProtocolMessageType('GetHooksResponse', (_message.Message,), dict( DESCRIPTOR = _GETHOOKSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetHooksResponse) )) _sym_db.RegisterMessage(GetHooksResponse) AddHookRequest = _reflection.GeneratedProtocolMessageType('AddHookRequest', (_message.Message,), dict( DESCRIPTOR = _ADDHOOKREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.AddHookRequest) )) _sym_db.RegisterMessage(AddHookRequest) AddHookResponse = _reflection.GeneratedProtocolMessageType('AddHookResponse', (_message.Message,), dict( DESCRIPTOR = _ADDHOOKRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.AddHookResponse) )) _sym_db.RegisterMessage(AddHookResponse) GetMobilityConfigsRequest = _reflection.GeneratedProtocolMessageType('GetMobilityConfigsRequest', (_message.Message,), dict( DESCRIPTOR = _GETMOBILITYCONFIGSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsRequest) )) _sym_db.RegisterMessage(GetMobilityConfigsRequest) @@ -5626,19 +5626,19 @@ GetMobilityConfigsResponse = _reflection.GeneratedProtocolMessageType('GetMobili MobilityConfig = _reflection.GeneratedProtocolMessageType('MobilityConfig', (_message.Message,), dict( DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse.MobilityConfig) )) , ConfigsEntry = _reflection.GeneratedProtocolMessageType('ConfigsEntry', (_message.Message,), dict( DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse.ConfigsEntry) )) , DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse) )) _sym_db.RegisterMessage(GetMobilityConfigsResponse) @@ -5647,14 +5647,14 @@ _sym_db.RegisterMessage(GetMobilityConfigsResponse.ConfigsEntry) GetMobilityConfigRequest = _reflection.GeneratedProtocolMessageType('GetMobilityConfigRequest', (_message.Message,), dict( DESCRIPTOR = _GETMOBILITYCONFIGREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetMobilityConfigRequest) )) _sym_db.RegisterMessage(GetMobilityConfigRequest) GetMobilityConfigResponse = _reflection.GeneratedProtocolMessageType('GetMobilityConfigResponse', (_message.Message,), dict( DESCRIPTOR = _GETMOBILITYCONFIGRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetMobilityConfigResponse) )) _sym_db.RegisterMessage(GetMobilityConfigResponse) @@ -5663,12 +5663,12 @@ SetMobilityConfigRequest = _reflection.GeneratedProtocolMessageType('SetMobility ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( DESCRIPTOR = _SETMOBILITYCONFIGREQUEST_CONFIGENTRY, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetMobilityConfigRequest.ConfigEntry) )) , DESCRIPTOR = _SETMOBILITYCONFIGREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetMobilityConfigRequest) )) _sym_db.RegisterMessage(SetMobilityConfigRequest) @@ -5676,147 +5676,147 @@ _sym_db.RegisterMessage(SetMobilityConfigRequest.ConfigEntry) SetMobilityConfigResponse = _reflection.GeneratedProtocolMessageType('SetMobilityConfigResponse', (_message.Message,), dict( DESCRIPTOR = _SETMOBILITYCONFIGRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetMobilityConfigResponse) )) _sym_db.RegisterMessage(SetMobilityConfigResponse) MobilityActionRequest = _reflection.GeneratedProtocolMessageType('MobilityActionRequest', (_message.Message,), dict( DESCRIPTOR = _MOBILITYACTIONREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.MobilityActionRequest) )) _sym_db.RegisterMessage(MobilityActionRequest) MobilityActionResponse = _reflection.GeneratedProtocolMessageType('MobilityActionResponse', (_message.Message,), dict( DESCRIPTOR = _MOBILITYACTIONRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.MobilityActionResponse) )) _sym_db.RegisterMessage(MobilityActionResponse) GetServicesRequest = _reflection.GeneratedProtocolMessageType('GetServicesRequest', (_message.Message,), dict( DESCRIPTOR = _GETSERVICESREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetServicesRequest) )) _sym_db.RegisterMessage(GetServicesRequest) GetServicesResponse = _reflection.GeneratedProtocolMessageType('GetServicesResponse', (_message.Message,), dict( DESCRIPTOR = _GETSERVICESRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetServicesResponse) )) _sym_db.RegisterMessage(GetServicesResponse) GetServiceDefaultsRequest = _reflection.GeneratedProtocolMessageType('GetServiceDefaultsRequest', (_message.Message,), dict( DESCRIPTOR = _GETSERVICEDEFAULTSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetServiceDefaultsRequest) )) _sym_db.RegisterMessage(GetServiceDefaultsRequest) GetServiceDefaultsResponse = _reflection.GeneratedProtocolMessageType('GetServiceDefaultsResponse', (_message.Message,), dict( DESCRIPTOR = _GETSERVICEDEFAULTSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetServiceDefaultsResponse) )) _sym_db.RegisterMessage(GetServiceDefaultsResponse) SetServiceDefaultsRequest = _reflection.GeneratedProtocolMessageType('SetServiceDefaultsRequest', (_message.Message,), dict( DESCRIPTOR = _SETSERVICEDEFAULTSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetServiceDefaultsRequest) )) _sym_db.RegisterMessage(SetServiceDefaultsRequest) SetServiceDefaultsResponse = _reflection.GeneratedProtocolMessageType('SetServiceDefaultsResponse', (_message.Message,), dict( DESCRIPTOR = _SETSERVICEDEFAULTSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetServiceDefaultsResponse) )) _sym_db.RegisterMessage(SetServiceDefaultsResponse) GetNodeServiceRequest = _reflection.GeneratedProtocolMessageType('GetNodeServiceRequest', (_message.Message,), dict( DESCRIPTOR = _GETNODESERVICEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetNodeServiceRequest) )) _sym_db.RegisterMessage(GetNodeServiceRequest) GetNodeServiceResponse = _reflection.GeneratedProtocolMessageType('GetNodeServiceResponse', (_message.Message,), dict( DESCRIPTOR = _GETNODESERVICERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetNodeServiceResponse) )) _sym_db.RegisterMessage(GetNodeServiceResponse) GetNodeServiceFileRequest = _reflection.GeneratedProtocolMessageType('GetNodeServiceFileRequest', (_message.Message,), dict( DESCRIPTOR = _GETNODESERVICEFILEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetNodeServiceFileRequest) )) _sym_db.RegisterMessage(GetNodeServiceFileRequest) GetNodeServiceFileResponse = _reflection.GeneratedProtocolMessageType('GetNodeServiceFileResponse', (_message.Message,), dict( DESCRIPTOR = _GETNODESERVICEFILERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetNodeServiceFileResponse) )) _sym_db.RegisterMessage(GetNodeServiceFileResponse) SetNodeServiceRequest = _reflection.GeneratedProtocolMessageType('SetNodeServiceRequest', (_message.Message,), dict( DESCRIPTOR = _SETNODESERVICEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetNodeServiceRequest) )) _sym_db.RegisterMessage(SetNodeServiceRequest) SetNodeServiceResponse = _reflection.GeneratedProtocolMessageType('SetNodeServiceResponse', (_message.Message,), dict( DESCRIPTOR = _SETNODESERVICERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetNodeServiceResponse) )) _sym_db.RegisterMessage(SetNodeServiceResponse) SetNodeServiceFileRequest = _reflection.GeneratedProtocolMessageType('SetNodeServiceFileRequest', (_message.Message,), dict( DESCRIPTOR = _SETNODESERVICEFILEREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetNodeServiceFileRequest) )) _sym_db.RegisterMessage(SetNodeServiceFileRequest) SetNodeServiceFileResponse = _reflection.GeneratedProtocolMessageType('SetNodeServiceFileResponse', (_message.Message,), dict( DESCRIPTOR = _SETNODESERVICEFILERESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetNodeServiceFileResponse) )) _sym_db.RegisterMessage(SetNodeServiceFileResponse) ServiceActionRequest = _reflection.GeneratedProtocolMessageType('ServiceActionRequest', (_message.Message,), dict( DESCRIPTOR = _SERVICEACTIONREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ServiceActionRequest) )) _sym_db.RegisterMessage(ServiceActionRequest) ServiceActionResponse = _reflection.GeneratedProtocolMessageType('ServiceActionResponse', (_message.Message,), dict( DESCRIPTOR = _SERVICEACTIONRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ServiceActionResponse) )) _sym_db.RegisterMessage(ServiceActionResponse) GetWlanConfigRequest = _reflection.GeneratedProtocolMessageType('GetWlanConfigRequest', (_message.Message,), dict( DESCRIPTOR = _GETWLANCONFIGREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetWlanConfigRequest) )) _sym_db.RegisterMessage(GetWlanConfigRequest) GetWlanConfigResponse = _reflection.GeneratedProtocolMessageType('GetWlanConfigResponse', (_message.Message,), dict( DESCRIPTOR = _GETWLANCONFIGRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetWlanConfigResponse) )) _sym_db.RegisterMessage(GetWlanConfigResponse) @@ -5825,12 +5825,12 @@ SetWlanConfigRequest = _reflection.GeneratedProtocolMessageType('SetWlanConfigRe ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( DESCRIPTOR = _SETWLANCONFIGREQUEST_CONFIGENTRY, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetWlanConfigRequest.ConfigEntry) )) , DESCRIPTOR = _SETWLANCONFIGREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetWlanConfigRequest) )) _sym_db.RegisterMessage(SetWlanConfigRequest) @@ -5838,21 +5838,21 @@ _sym_db.RegisterMessage(SetWlanConfigRequest.ConfigEntry) SetWlanConfigResponse = _reflection.GeneratedProtocolMessageType('SetWlanConfigResponse', (_message.Message,), dict( DESCRIPTOR = _SETWLANCONFIGRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetWlanConfigResponse) )) _sym_db.RegisterMessage(SetWlanConfigResponse) GetEmaneConfigRequest = _reflection.GeneratedProtocolMessageType('GetEmaneConfigRequest', (_message.Message,), dict( DESCRIPTOR = _GETEMANECONFIGREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneConfigRequest) )) _sym_db.RegisterMessage(GetEmaneConfigRequest) GetEmaneConfigResponse = _reflection.GeneratedProtocolMessageType('GetEmaneConfigResponse', (_message.Message,), dict( DESCRIPTOR = _GETEMANECONFIGRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneConfigResponse) )) _sym_db.RegisterMessage(GetEmaneConfigResponse) @@ -5861,12 +5861,12 @@ SetEmaneConfigRequest = _reflection.GeneratedProtocolMessageType('SetEmaneConfig ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( DESCRIPTOR = _SETEMANECONFIGREQUEST_CONFIGENTRY, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetEmaneConfigRequest.ConfigEntry) )) , DESCRIPTOR = _SETEMANECONFIGREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetEmaneConfigRequest) )) _sym_db.RegisterMessage(SetEmaneConfigRequest) @@ -5874,35 +5874,35 @@ _sym_db.RegisterMessage(SetEmaneConfigRequest.ConfigEntry) SetEmaneConfigResponse = _reflection.GeneratedProtocolMessageType('SetEmaneConfigResponse', (_message.Message,), dict( DESCRIPTOR = _SETEMANECONFIGRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetEmaneConfigResponse) )) _sym_db.RegisterMessage(SetEmaneConfigResponse) GetEmaneModelsRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelsRequest', (_message.Message,), dict( DESCRIPTOR = _GETEMANEMODELSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneModelsRequest) )) _sym_db.RegisterMessage(GetEmaneModelsRequest) GetEmaneModelsResponse = _reflection.GeneratedProtocolMessageType('GetEmaneModelsResponse', (_message.Message,), dict( DESCRIPTOR = _GETEMANEMODELSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneModelsResponse) )) _sym_db.RegisterMessage(GetEmaneModelsResponse) GetEmaneModelConfigRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigRequest', (_message.Message,), dict( DESCRIPTOR = _GETEMANEMODELCONFIGREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigRequest) )) _sym_db.RegisterMessage(GetEmaneModelConfigRequest) GetEmaneModelConfigResponse = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigResponse', (_message.Message,), dict( DESCRIPTOR = _GETEMANEMODELCONFIGRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigResponse) )) _sym_db.RegisterMessage(GetEmaneModelConfigResponse) @@ -5911,12 +5911,12 @@ SetEmaneModelConfigRequest = _reflection.GeneratedProtocolMessageType('SetEmaneM ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( DESCRIPTOR = _SETEMANEMODELCONFIGREQUEST_CONFIGENTRY, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigRequest.ConfigEntry) )) , DESCRIPTOR = _SETEMANEMODELCONFIGREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigRequest) )) _sym_db.RegisterMessage(SetEmaneModelConfigRequest) @@ -5924,14 +5924,14 @@ _sym_db.RegisterMessage(SetEmaneModelConfigRequest.ConfigEntry) SetEmaneModelConfigResponse = _reflection.GeneratedProtocolMessageType('SetEmaneModelConfigResponse', (_message.Message,), dict( DESCRIPTOR = _SETEMANEMODELCONFIGRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigResponse) )) _sym_db.RegisterMessage(SetEmaneModelConfigResponse) GetEmaneModelConfigsRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigsRequest', (_message.Message,), dict( DESCRIPTOR = _GETEMANEMODELCONFIGSREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsRequest) )) _sym_db.RegisterMessage(GetEmaneModelConfigsRequest) @@ -5940,19 +5940,19 @@ GetEmaneModelConfigsResponse = _reflection.GeneratedProtocolMessageType('GetEman ModelConfig = _reflection.GeneratedProtocolMessageType('ModelConfig', (_message.Message,), dict( DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse.ModelConfig) )) , ConfigsEntry = _reflection.GeneratedProtocolMessageType('ConfigsEntry', (_message.Message,), dict( DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse.ConfigsEntry) )) , DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse) )) _sym_db.RegisterMessage(GetEmaneModelConfigsResponse) @@ -5961,119 +5961,119 @@ _sym_db.RegisterMessage(GetEmaneModelConfigsResponse.ConfigsEntry) SaveXmlRequest = _reflection.GeneratedProtocolMessageType('SaveXmlRequest', (_message.Message,), dict( DESCRIPTOR = _SAVEXMLREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SaveXmlRequest) )) _sym_db.RegisterMessage(SaveXmlRequest) SaveXmlResponse = _reflection.GeneratedProtocolMessageType('SaveXmlResponse', (_message.Message,), dict( DESCRIPTOR = _SAVEXMLRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SaveXmlResponse) )) _sym_db.RegisterMessage(SaveXmlResponse) OpenXmlRequest = _reflection.GeneratedProtocolMessageType('OpenXmlRequest', (_message.Message,), dict( DESCRIPTOR = _OPENXMLREQUEST, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.OpenXmlRequest) )) _sym_db.RegisterMessage(OpenXmlRequest) OpenXmlResponse = _reflection.GeneratedProtocolMessageType('OpenXmlResponse', (_message.Message,), dict( DESCRIPTOR = _OPENXMLRESPONSE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.OpenXmlResponse) )) _sym_db.RegisterMessage(OpenXmlResponse) Hook = _reflection.GeneratedProtocolMessageType('Hook', (_message.Message,), dict( DESCRIPTOR = _HOOK, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.Hook) )) _sym_db.RegisterMessage(Hook) ServiceDefaults = _reflection.GeneratedProtocolMessageType('ServiceDefaults', (_message.Message,), dict( DESCRIPTOR = _SERVICEDEFAULTS, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ServiceDefaults) )) _sym_db.RegisterMessage(ServiceDefaults) Service = _reflection.GeneratedProtocolMessageType('Service', (_message.Message,), dict( DESCRIPTOR = _SERVICE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.Service) )) _sym_db.RegisterMessage(Service) NodeServiceData = _reflection.GeneratedProtocolMessageType('NodeServiceData', (_message.Message,), dict( DESCRIPTOR = _NODESERVICEDATA, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.NodeServiceData) )) _sym_db.RegisterMessage(NodeServiceData) ConfigGroup = _reflection.GeneratedProtocolMessageType('ConfigGroup', (_message.Message,), dict( DESCRIPTOR = _CONFIGGROUP, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ConfigGroup) )) _sym_db.RegisterMessage(ConfigGroup) ConfigOption = _reflection.GeneratedProtocolMessageType('ConfigOption', (_message.Message,), dict( DESCRIPTOR = _CONFIGOPTION, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.ConfigOption) )) _sym_db.RegisterMessage(ConfigOption) Session = _reflection.GeneratedProtocolMessageType('Session', (_message.Message,), dict( DESCRIPTOR = _SESSION, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.Session) )) _sym_db.RegisterMessage(Session) SessionSummary = _reflection.GeneratedProtocolMessageType('SessionSummary', (_message.Message,), dict( DESCRIPTOR = _SESSIONSUMMARY, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.SessionSummary) )) _sym_db.RegisterMessage(SessionSummary) Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( DESCRIPTOR = _NODE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.Node) )) _sym_db.RegisterMessage(Node) Link = _reflection.GeneratedProtocolMessageType('Link', (_message.Message,), dict( DESCRIPTOR = _LINK, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.Link) )) _sym_db.RegisterMessage(Link) LinkOptions = _reflection.GeneratedProtocolMessageType('LinkOptions', (_message.Message,), dict( DESCRIPTOR = _LINKOPTIONS, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.LinkOptions) )) _sym_db.RegisterMessage(LinkOptions) Interface = _reflection.GeneratedProtocolMessageType('Interface', (_message.Message,), dict( DESCRIPTOR = _INTERFACE, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.Interface) )) _sym_db.RegisterMessage(Interface) Position = _reflection.GeneratedProtocolMessageType('Position', (_message.Message,), dict( DESCRIPTOR = _POSITION, - __module__ = 'core_pb2' + __module__ = 'core.api.grpc.core_pb2' # @@protoc_insertion_point(class_scope:core.Position) )) _sym_db.RegisterMessage(Position) @@ -6093,8 +6093,8 @@ _COREAPI = _descriptor.ServiceDescriptor( file=DESCRIPTOR, index=0, serialized_options=None, - serialized_start=9243, - serialized_end=12797, + serialized_start=9257, + serialized_end=12811, methods=[ _descriptor.MethodDescriptor( name='CreateSession', diff --git a/daemon/core/api/grpc/core_pb2_grpc.py b/daemon/core/api/grpc/core_pb2_grpc.py index 5535b32b..fc3a622c 100644 --- a/daemon/core/api/grpc/core_pb2_grpc.py +++ b/daemon/core/api/grpc/core_pb2_grpc.py @@ -1,7 +1,7 @@ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! import grpc -import core_pb2 as core__pb2 +from core.api.grpc import core_pb2 as core_dot_api_dot_grpc_dot_core__pb2 class CoreApiStub(object): @@ -16,238 +16,238 @@ class CoreApiStub(object): """ self.CreateSession = channel.unary_unary( '/core.CoreApi/CreateSession', - request_serializer=core__pb2.CreateSessionRequest.SerializeToString, - response_deserializer=core__pb2.CreateSessionResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.CreateSessionRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.CreateSessionResponse.FromString, ) self.DeleteSession = channel.unary_unary( '/core.CoreApi/DeleteSession', - request_serializer=core__pb2.DeleteSessionRequest.SerializeToString, - response_deserializer=core__pb2.DeleteSessionResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteSessionRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteSessionResponse.FromString, ) self.GetSessions = channel.unary_unary( '/core.CoreApi/GetSessions', - request_serializer=core__pb2.GetSessionsRequest.SerializeToString, - response_deserializer=core__pb2.GetSessionsResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionsResponse.FromString, ) self.GetSession = channel.unary_unary( '/core.CoreApi/GetSession', - request_serializer=core__pb2.GetSessionRequest.SerializeToString, - response_deserializer=core__pb2.GetSessionResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionResponse.FromString, ) self.GetSessionOptions = channel.unary_unary( '/core.CoreApi/GetSessionOptions', - request_serializer=core__pb2.GetSessionOptionsRequest.SerializeToString, - response_deserializer=core__pb2.GetSessionOptionsResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionOptionsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionOptionsResponse.FromString, ) self.SetSessionOptions = channel.unary_unary( '/core.CoreApi/SetSessionOptions', - request_serializer=core__pb2.SetSessionOptionsRequest.SerializeToString, - response_deserializer=core__pb2.SetSessionOptionsResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionOptionsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionOptionsResponse.FromString, ) self.GetSessionLocation = channel.unary_unary( '/core.CoreApi/GetSessionLocation', - request_serializer=core__pb2.GetSessionLocationRequest.SerializeToString, - response_deserializer=core__pb2.GetSessionLocationResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionLocationRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionLocationResponse.FromString, ) self.SetSessionLocation = channel.unary_unary( '/core.CoreApi/SetSessionLocation', - request_serializer=core__pb2.SetSessionLocationRequest.SerializeToString, - response_deserializer=core__pb2.SetSessionLocationResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionLocationRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionLocationResponse.FromString, ) self.SetSessionState = channel.unary_unary( '/core.CoreApi/SetSessionState', - request_serializer=core__pb2.SetSessionStateRequest.SerializeToString, - response_deserializer=core__pb2.SetSessionStateResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionStateRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionStateResponse.FromString, ) self.NodeEvents = channel.unary_stream( '/core.CoreApi/NodeEvents', - request_serializer=core__pb2.NodeEventsRequest.SerializeToString, - response_deserializer=core__pb2.NodeEvent.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.NodeEventsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.NodeEvent.FromString, ) self.LinkEvents = channel.unary_stream( '/core.CoreApi/LinkEvents', - request_serializer=core__pb2.LinkEventsRequest.SerializeToString, - response_deserializer=core__pb2.LinkEvent.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.LinkEventsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.LinkEvent.FromString, ) self.SessionEvents = channel.unary_stream( '/core.CoreApi/SessionEvents', - request_serializer=core__pb2.SessionEventsRequest.SerializeToString, - response_deserializer=core__pb2.SessionEvent.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SessionEventsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SessionEvent.FromString, ) self.ConfigEvents = channel.unary_stream( '/core.CoreApi/ConfigEvents', - request_serializer=core__pb2.ConfigEventsRequest.SerializeToString, - response_deserializer=core__pb2.ConfigEvent.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.ConfigEventsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ConfigEvent.FromString, ) self.ExceptionEvents = channel.unary_stream( '/core.CoreApi/ExceptionEvents', - request_serializer=core__pb2.ExceptionEventsRequest.SerializeToString, - response_deserializer=core__pb2.ExceptionEvent.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.ExceptionEventsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ExceptionEvent.FromString, ) self.FileEvents = channel.unary_stream( '/core.CoreApi/FileEvents', - request_serializer=core__pb2.FileEventsRequest.SerializeToString, - response_deserializer=core__pb2.FileEvent.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.FileEventsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.FileEvent.FromString, ) self.AddNode = channel.unary_unary( '/core.CoreApi/AddNode', - request_serializer=core__pb2.AddNodeRequest.SerializeToString, - response_deserializer=core__pb2.AddNodeResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddNodeRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddNodeResponse.FromString, ) self.GetNode = channel.unary_unary( '/core.CoreApi/GetNode', - request_serializer=core__pb2.GetNodeRequest.SerializeToString, - response_deserializer=core__pb2.GetNodeResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeResponse.FromString, ) self.EditNode = channel.unary_unary( '/core.CoreApi/EditNode', - request_serializer=core__pb2.EditNodeRequest.SerializeToString, - response_deserializer=core__pb2.EditNodeResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.EditNodeRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.EditNodeResponse.FromString, ) self.DeleteNode = channel.unary_unary( '/core.CoreApi/DeleteNode', - request_serializer=core__pb2.DeleteNodeRequest.SerializeToString, - response_deserializer=core__pb2.DeleteNodeResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteNodeRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteNodeResponse.FromString, ) self.GetNodeLinks = channel.unary_unary( '/core.CoreApi/GetNodeLinks', - request_serializer=core__pb2.GetNodeLinksRequest.SerializeToString, - response_deserializer=core__pb2.GetNodeLinksResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeLinksRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeLinksResponse.FromString, ) self.AddLink = channel.unary_unary( '/core.CoreApi/AddLink', - request_serializer=core__pb2.AddLinkRequest.SerializeToString, - response_deserializer=core__pb2.AddLinkResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddLinkRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddLinkResponse.FromString, ) self.EditLink = channel.unary_unary( '/core.CoreApi/EditLink', - request_serializer=core__pb2.EditLinkRequest.SerializeToString, - response_deserializer=core__pb2.EditLinkResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.EditLinkRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.EditLinkResponse.FromString, ) self.DeleteLink = channel.unary_unary( '/core.CoreApi/DeleteLink', - request_serializer=core__pb2.DeleteLinkRequest.SerializeToString, - response_deserializer=core__pb2.DeleteLinkResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteLinkRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteLinkResponse.FromString, ) self.GetHooks = channel.unary_unary( '/core.CoreApi/GetHooks', - request_serializer=core__pb2.GetHooksRequest.SerializeToString, - response_deserializer=core__pb2.GetHooksResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetHooksRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetHooksResponse.FromString, ) self.AddHook = channel.unary_unary( '/core.CoreApi/AddHook', - request_serializer=core__pb2.AddHookRequest.SerializeToString, - response_deserializer=core__pb2.AddHookResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddHookRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddHookResponse.FromString, ) self.GetMobilityConfigs = channel.unary_unary( '/core.CoreApi/GetMobilityConfigs', - request_serializer=core__pb2.GetMobilityConfigsRequest.SerializeToString, - response_deserializer=core__pb2.GetMobilityConfigsResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigsResponse.FromString, ) self.GetMobilityConfig = channel.unary_unary( '/core.CoreApi/GetMobilityConfig', - request_serializer=core__pb2.GetMobilityConfigRequest.SerializeToString, - response_deserializer=core__pb2.GetMobilityConfigResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigResponse.FromString, ) self.SetMobilityConfig = channel.unary_unary( '/core.CoreApi/SetMobilityConfig', - request_serializer=core__pb2.SetMobilityConfigRequest.SerializeToString, - response_deserializer=core__pb2.SetMobilityConfigResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetMobilityConfigRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetMobilityConfigResponse.FromString, ) self.MobilityAction = channel.unary_unary( '/core.CoreApi/MobilityAction', - request_serializer=core__pb2.MobilityActionRequest.SerializeToString, - response_deserializer=core__pb2.MobilityActionResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.MobilityActionRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.MobilityActionResponse.FromString, ) self.GetServices = channel.unary_unary( '/core.CoreApi/GetServices', - request_serializer=core__pb2.GetServicesRequest.SerializeToString, - response_deserializer=core__pb2.GetServicesResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetServicesRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetServicesResponse.FromString, ) self.GetServiceDefaults = channel.unary_unary( '/core.CoreApi/GetServiceDefaults', - request_serializer=core__pb2.GetServiceDefaultsRequest.SerializeToString, - response_deserializer=core__pb2.GetServiceDefaultsResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetServiceDefaultsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetServiceDefaultsResponse.FromString, ) self.SetServiceDefaults = channel.unary_unary( '/core.CoreApi/SetServiceDefaults', - request_serializer=core__pb2.SetServiceDefaultsRequest.SerializeToString, - response_deserializer=core__pb2.SetServiceDefaultsResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetServiceDefaultsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetServiceDefaultsResponse.FromString, ) self.GetNodeService = channel.unary_unary( '/core.CoreApi/GetNodeService', - request_serializer=core__pb2.GetNodeServiceRequest.SerializeToString, - response_deserializer=core__pb2.GetNodeServiceResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceResponse.FromString, ) self.GetNodeServiceFile = channel.unary_unary( '/core.CoreApi/GetNodeServiceFile', - request_serializer=core__pb2.GetNodeServiceFileRequest.SerializeToString, - response_deserializer=core__pb2.GetNodeServiceFileResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceFileRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceFileResponse.FromString, ) self.SetNodeService = channel.unary_unary( '/core.CoreApi/SetNodeService', - request_serializer=core__pb2.SetNodeServiceRequest.SerializeToString, - response_deserializer=core__pb2.SetNodeServiceResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceResponse.FromString, ) self.SetNodeServiceFile = channel.unary_unary( '/core.CoreApi/SetNodeServiceFile', - request_serializer=core__pb2.SetNodeServiceFileRequest.SerializeToString, - response_deserializer=core__pb2.SetNodeServiceFileResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceFileRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceFileResponse.FromString, ) self.ServiceAction = channel.unary_unary( '/core.CoreApi/ServiceAction', - request_serializer=core__pb2.ServiceActionRequest.SerializeToString, - response_deserializer=core__pb2.ServiceActionResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.ServiceActionRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ServiceActionResponse.FromString, ) self.GetWlanConfig = channel.unary_unary( '/core.CoreApi/GetWlanConfig', - request_serializer=core__pb2.GetWlanConfigRequest.SerializeToString, - response_deserializer=core__pb2.GetWlanConfigResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetWlanConfigRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetWlanConfigResponse.FromString, ) self.SetWlanConfig = channel.unary_unary( '/core.CoreApi/SetWlanConfig', - request_serializer=core__pb2.SetWlanConfigRequest.SerializeToString, - response_deserializer=core__pb2.SetWlanConfigResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetWlanConfigRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetWlanConfigResponse.FromString, ) self.GetEmaneConfig = channel.unary_unary( '/core.CoreApi/GetEmaneConfig', - request_serializer=core__pb2.GetEmaneConfigRequest.SerializeToString, - response_deserializer=core__pb2.GetEmaneConfigResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneConfigRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneConfigResponse.FromString, ) self.SetEmaneConfig = channel.unary_unary( '/core.CoreApi/SetEmaneConfig', - request_serializer=core__pb2.SetEmaneConfigRequest.SerializeToString, - response_deserializer=core__pb2.SetEmaneConfigResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneConfigRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneConfigResponse.FromString, ) self.GetEmaneModels = channel.unary_unary( '/core.CoreApi/GetEmaneModels', - request_serializer=core__pb2.GetEmaneModelsRequest.SerializeToString, - response_deserializer=core__pb2.GetEmaneModelsResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelsResponse.FromString, ) self.GetEmaneModelConfig = channel.unary_unary( '/core.CoreApi/GetEmaneModelConfig', - request_serializer=core__pb2.GetEmaneModelConfigRequest.SerializeToString, - response_deserializer=core__pb2.GetEmaneModelConfigResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigResponse.FromString, ) self.SetEmaneModelConfig = channel.unary_unary( '/core.CoreApi/SetEmaneModelConfig', - request_serializer=core__pb2.SetEmaneModelConfigRequest.SerializeToString, - response_deserializer=core__pb2.SetEmaneModelConfigResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneModelConfigRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneModelConfigResponse.FromString, ) self.GetEmaneModelConfigs = channel.unary_unary( '/core.CoreApi/GetEmaneModelConfigs', - request_serializer=core__pb2.GetEmaneModelConfigsRequest.SerializeToString, - response_deserializer=core__pb2.GetEmaneModelConfigsResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigsRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigsResponse.FromString, ) self.SaveXml = channel.unary_unary( '/core.CoreApi/SaveXml', - request_serializer=core__pb2.SaveXmlRequest.SerializeToString, - response_deserializer=core__pb2.SaveXmlResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SaveXmlRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SaveXmlResponse.FromString, ) self.OpenXml = channel.unary_unary( '/core.CoreApi/OpenXml', - request_serializer=core__pb2.OpenXmlRequest.SerializeToString, - response_deserializer=core__pb2.OpenXmlResponse.FromString, + request_serializer=core_dot_api_dot_grpc_dot_core__pb2.OpenXmlRequest.SerializeToString, + response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.OpenXmlResponse.FromString, ) @@ -589,238 +589,238 @@ def add_CoreApiServicer_to_server(servicer, server): rpc_method_handlers = { 'CreateSession': grpc.unary_unary_rpc_method_handler( servicer.CreateSession, - request_deserializer=core__pb2.CreateSessionRequest.FromString, - response_serializer=core__pb2.CreateSessionResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.CreateSessionRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.CreateSessionResponse.SerializeToString, ), 'DeleteSession': grpc.unary_unary_rpc_method_handler( servicer.DeleteSession, - request_deserializer=core__pb2.DeleteSessionRequest.FromString, - response_serializer=core__pb2.DeleteSessionResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteSessionRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteSessionResponse.SerializeToString, ), 'GetSessions': grpc.unary_unary_rpc_method_handler( servicer.GetSessions, - request_deserializer=core__pb2.GetSessionsRequest.FromString, - response_serializer=core__pb2.GetSessionsResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionsResponse.SerializeToString, ), 'GetSession': grpc.unary_unary_rpc_method_handler( servicer.GetSession, - request_deserializer=core__pb2.GetSessionRequest.FromString, - response_serializer=core__pb2.GetSessionResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionResponse.SerializeToString, ), 'GetSessionOptions': grpc.unary_unary_rpc_method_handler( servicer.GetSessionOptions, - request_deserializer=core__pb2.GetSessionOptionsRequest.FromString, - response_serializer=core__pb2.GetSessionOptionsResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionOptionsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionOptionsResponse.SerializeToString, ), 'SetSessionOptions': grpc.unary_unary_rpc_method_handler( servicer.SetSessionOptions, - request_deserializer=core__pb2.SetSessionOptionsRequest.FromString, - response_serializer=core__pb2.SetSessionOptionsResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionOptionsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionOptionsResponse.SerializeToString, ), 'GetSessionLocation': grpc.unary_unary_rpc_method_handler( servicer.GetSessionLocation, - request_deserializer=core__pb2.GetSessionLocationRequest.FromString, - response_serializer=core__pb2.GetSessionLocationResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionLocationRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionLocationResponse.SerializeToString, ), 'SetSessionLocation': grpc.unary_unary_rpc_method_handler( servicer.SetSessionLocation, - request_deserializer=core__pb2.SetSessionLocationRequest.FromString, - response_serializer=core__pb2.SetSessionLocationResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionLocationRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionLocationResponse.SerializeToString, ), 'SetSessionState': grpc.unary_unary_rpc_method_handler( servicer.SetSessionState, - request_deserializer=core__pb2.SetSessionStateRequest.FromString, - response_serializer=core__pb2.SetSessionStateResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionStateRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionStateResponse.SerializeToString, ), 'NodeEvents': grpc.unary_stream_rpc_method_handler( servicer.NodeEvents, - request_deserializer=core__pb2.NodeEventsRequest.FromString, - response_serializer=core__pb2.NodeEvent.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.NodeEventsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.NodeEvent.SerializeToString, ), 'LinkEvents': grpc.unary_stream_rpc_method_handler( servicer.LinkEvents, - request_deserializer=core__pb2.LinkEventsRequest.FromString, - response_serializer=core__pb2.LinkEvent.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.LinkEventsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.LinkEvent.SerializeToString, ), 'SessionEvents': grpc.unary_stream_rpc_method_handler( servicer.SessionEvents, - request_deserializer=core__pb2.SessionEventsRequest.FromString, - response_serializer=core__pb2.SessionEvent.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SessionEventsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SessionEvent.SerializeToString, ), 'ConfigEvents': grpc.unary_stream_rpc_method_handler( servicer.ConfigEvents, - request_deserializer=core__pb2.ConfigEventsRequest.FromString, - response_serializer=core__pb2.ConfigEvent.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ConfigEventsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.ConfigEvent.SerializeToString, ), 'ExceptionEvents': grpc.unary_stream_rpc_method_handler( servicer.ExceptionEvents, - request_deserializer=core__pb2.ExceptionEventsRequest.FromString, - response_serializer=core__pb2.ExceptionEvent.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ExceptionEventsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.ExceptionEvent.SerializeToString, ), 'FileEvents': grpc.unary_stream_rpc_method_handler( servicer.FileEvents, - request_deserializer=core__pb2.FileEventsRequest.FromString, - response_serializer=core__pb2.FileEvent.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.FileEventsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.FileEvent.SerializeToString, ), 'AddNode': grpc.unary_unary_rpc_method_handler( servicer.AddNode, - request_deserializer=core__pb2.AddNodeRequest.FromString, - response_serializer=core__pb2.AddNodeResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddNodeRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddNodeResponse.SerializeToString, ), 'GetNode': grpc.unary_unary_rpc_method_handler( servicer.GetNode, - request_deserializer=core__pb2.GetNodeRequest.FromString, - response_serializer=core__pb2.GetNodeResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeResponse.SerializeToString, ), 'EditNode': grpc.unary_unary_rpc_method_handler( servicer.EditNode, - request_deserializer=core__pb2.EditNodeRequest.FromString, - response_serializer=core__pb2.EditNodeResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.EditNodeRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.EditNodeResponse.SerializeToString, ), 'DeleteNode': grpc.unary_unary_rpc_method_handler( servicer.DeleteNode, - request_deserializer=core__pb2.DeleteNodeRequest.FromString, - response_serializer=core__pb2.DeleteNodeResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteNodeRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteNodeResponse.SerializeToString, ), 'GetNodeLinks': grpc.unary_unary_rpc_method_handler( servicer.GetNodeLinks, - request_deserializer=core__pb2.GetNodeLinksRequest.FromString, - response_serializer=core__pb2.GetNodeLinksResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeLinksRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeLinksResponse.SerializeToString, ), 'AddLink': grpc.unary_unary_rpc_method_handler( servicer.AddLink, - request_deserializer=core__pb2.AddLinkRequest.FromString, - response_serializer=core__pb2.AddLinkResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddLinkRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddLinkResponse.SerializeToString, ), 'EditLink': grpc.unary_unary_rpc_method_handler( servicer.EditLink, - request_deserializer=core__pb2.EditLinkRequest.FromString, - response_serializer=core__pb2.EditLinkResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.EditLinkRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.EditLinkResponse.SerializeToString, ), 'DeleteLink': grpc.unary_unary_rpc_method_handler( servicer.DeleteLink, - request_deserializer=core__pb2.DeleteLinkRequest.FromString, - response_serializer=core__pb2.DeleteLinkResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteLinkRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteLinkResponse.SerializeToString, ), 'GetHooks': grpc.unary_unary_rpc_method_handler( servicer.GetHooks, - request_deserializer=core__pb2.GetHooksRequest.FromString, - response_serializer=core__pb2.GetHooksResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetHooksRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetHooksResponse.SerializeToString, ), 'AddHook': grpc.unary_unary_rpc_method_handler( servicer.AddHook, - request_deserializer=core__pb2.AddHookRequest.FromString, - response_serializer=core__pb2.AddHookResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddHookRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddHookResponse.SerializeToString, ), 'GetMobilityConfigs': grpc.unary_unary_rpc_method_handler( servicer.GetMobilityConfigs, - request_deserializer=core__pb2.GetMobilityConfigsRequest.FromString, - response_serializer=core__pb2.GetMobilityConfigsResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigsResponse.SerializeToString, ), 'GetMobilityConfig': grpc.unary_unary_rpc_method_handler( servicer.GetMobilityConfig, - request_deserializer=core__pb2.GetMobilityConfigRequest.FromString, - response_serializer=core__pb2.GetMobilityConfigResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigResponse.SerializeToString, ), 'SetMobilityConfig': grpc.unary_unary_rpc_method_handler( servicer.SetMobilityConfig, - request_deserializer=core__pb2.SetMobilityConfigRequest.FromString, - response_serializer=core__pb2.SetMobilityConfigResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetMobilityConfigRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetMobilityConfigResponse.SerializeToString, ), 'MobilityAction': grpc.unary_unary_rpc_method_handler( servicer.MobilityAction, - request_deserializer=core__pb2.MobilityActionRequest.FromString, - response_serializer=core__pb2.MobilityActionResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.MobilityActionRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.MobilityActionResponse.SerializeToString, ), 'GetServices': grpc.unary_unary_rpc_method_handler( servicer.GetServices, - request_deserializer=core__pb2.GetServicesRequest.FromString, - response_serializer=core__pb2.GetServicesResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetServicesRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetServicesResponse.SerializeToString, ), 'GetServiceDefaults': grpc.unary_unary_rpc_method_handler( servicer.GetServiceDefaults, - request_deserializer=core__pb2.GetServiceDefaultsRequest.FromString, - response_serializer=core__pb2.GetServiceDefaultsResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetServiceDefaultsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetServiceDefaultsResponse.SerializeToString, ), 'SetServiceDefaults': grpc.unary_unary_rpc_method_handler( servicer.SetServiceDefaults, - request_deserializer=core__pb2.SetServiceDefaultsRequest.FromString, - response_serializer=core__pb2.SetServiceDefaultsResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetServiceDefaultsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetServiceDefaultsResponse.SerializeToString, ), 'GetNodeService': grpc.unary_unary_rpc_method_handler( servicer.GetNodeService, - request_deserializer=core__pb2.GetNodeServiceRequest.FromString, - response_serializer=core__pb2.GetNodeServiceResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceResponse.SerializeToString, ), 'GetNodeServiceFile': grpc.unary_unary_rpc_method_handler( servicer.GetNodeServiceFile, - request_deserializer=core__pb2.GetNodeServiceFileRequest.FromString, - response_serializer=core__pb2.GetNodeServiceFileResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceFileRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceFileResponse.SerializeToString, ), 'SetNodeService': grpc.unary_unary_rpc_method_handler( servicer.SetNodeService, - request_deserializer=core__pb2.SetNodeServiceRequest.FromString, - response_serializer=core__pb2.SetNodeServiceResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceResponse.SerializeToString, ), 'SetNodeServiceFile': grpc.unary_unary_rpc_method_handler( servicer.SetNodeServiceFile, - request_deserializer=core__pb2.SetNodeServiceFileRequest.FromString, - response_serializer=core__pb2.SetNodeServiceFileResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceFileRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceFileResponse.SerializeToString, ), 'ServiceAction': grpc.unary_unary_rpc_method_handler( servicer.ServiceAction, - request_deserializer=core__pb2.ServiceActionRequest.FromString, - response_serializer=core__pb2.ServiceActionResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ServiceActionRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.ServiceActionResponse.SerializeToString, ), 'GetWlanConfig': grpc.unary_unary_rpc_method_handler( servicer.GetWlanConfig, - request_deserializer=core__pb2.GetWlanConfigRequest.FromString, - response_serializer=core__pb2.GetWlanConfigResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetWlanConfigRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetWlanConfigResponse.SerializeToString, ), 'SetWlanConfig': grpc.unary_unary_rpc_method_handler( servicer.SetWlanConfig, - request_deserializer=core__pb2.SetWlanConfigRequest.FromString, - response_serializer=core__pb2.SetWlanConfigResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetWlanConfigRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetWlanConfigResponse.SerializeToString, ), 'GetEmaneConfig': grpc.unary_unary_rpc_method_handler( servicer.GetEmaneConfig, - request_deserializer=core__pb2.GetEmaneConfigRequest.FromString, - response_serializer=core__pb2.GetEmaneConfigResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneConfigRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneConfigResponse.SerializeToString, ), 'SetEmaneConfig': grpc.unary_unary_rpc_method_handler( servicer.SetEmaneConfig, - request_deserializer=core__pb2.SetEmaneConfigRequest.FromString, - response_serializer=core__pb2.SetEmaneConfigResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneConfigRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneConfigResponse.SerializeToString, ), 'GetEmaneModels': grpc.unary_unary_rpc_method_handler( servicer.GetEmaneModels, - request_deserializer=core__pb2.GetEmaneModelsRequest.FromString, - response_serializer=core__pb2.GetEmaneModelsResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelsResponse.SerializeToString, ), 'GetEmaneModelConfig': grpc.unary_unary_rpc_method_handler( servicer.GetEmaneModelConfig, - request_deserializer=core__pb2.GetEmaneModelConfigRequest.FromString, - response_serializer=core__pb2.GetEmaneModelConfigResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigResponse.SerializeToString, ), 'SetEmaneModelConfig': grpc.unary_unary_rpc_method_handler( servicer.SetEmaneModelConfig, - request_deserializer=core__pb2.SetEmaneModelConfigRequest.FromString, - response_serializer=core__pb2.SetEmaneModelConfigResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneModelConfigRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneModelConfigResponse.SerializeToString, ), 'GetEmaneModelConfigs': grpc.unary_unary_rpc_method_handler( servicer.GetEmaneModelConfigs, - request_deserializer=core__pb2.GetEmaneModelConfigsRequest.FromString, - response_serializer=core__pb2.GetEmaneModelConfigsResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigsRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigsResponse.SerializeToString, ), 'SaveXml': grpc.unary_unary_rpc_method_handler( servicer.SaveXml, - request_deserializer=core__pb2.SaveXmlRequest.FromString, - response_serializer=core__pb2.SaveXmlResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SaveXmlRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SaveXmlResponse.SerializeToString, ), 'OpenXml': grpc.unary_unary_rpc_method_handler( servicer.OpenXml, - request_deserializer=core__pb2.OpenXmlRequest.FromString, - response_serializer=core__pb2.OpenXmlResponse.SerializeToString, + request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.OpenXmlRequest.FromString, + response_serializer=core_dot_api_dot_grpc_dot_core__pb2.OpenXmlResponse.SerializeToString, ), } generic_handler = grpc.method_handlers_generic_handler( diff --git a/daemon/core/api/tlv/broker.py b/daemon/core/api/tlv/broker.py index c168cde4..d7499eaa 100644 --- a/daemon/core/api/tlv/broker.py +++ b/daemon/core/api/tlv/broker.py @@ -692,7 +692,7 @@ class CoreBroker(object): # send a Configuration message for the broker object and inform the # server of its local name - tlvdata = "" + tlvdata = b"" tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.OBJECT.value, "broker") tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.TYPE.value, ConfigFlags.UPDATE.value) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.DATA_TYPES.value, (ConfigDataTypes.STRING.value,)) @@ -722,7 +722,7 @@ class CoreBroker(object): cmd = msg.get_tlv(ExecuteTlvs.COMMAND.value) res = msg.get_tlv(ExecuteTlvs.RESULT.value) - tlvdata = "" + tlvdata = b"" tlvdata += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, nodenum) tlvdata += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.NUMBER.value, execnum) tlvdata += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, cmd) @@ -1029,7 +1029,7 @@ class CoreBroker(object): server.instantiation_complete = True # broadcast out instantiate complete - tlvdata = "" + tlvdata = b"" tlvdata += coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value, EventTypes.INSTANTIATION_COMPLETE.value) message = coreapi.CoreEventMessage.pack(0, tlvdata) for session_client in self.session_clients: diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index ec88ce39..8d2b5ebf 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -157,7 +157,7 @@ class CoreTlvDataUint64(CoreTlvData): Helper class for packing uint64 data. """ data_format = "!2xQ" - data_type = long + data_type = int pad_len = 2 @@ -178,6 +178,7 @@ class CoreTlvDataString(CoreTlvData): """ if not isinstance(value, str): raise ValueError("value not a string: %s" % value) + value = value.encode("utf-8") if len(value) < 256: header_len = CoreTlv.header_len @@ -185,7 +186,7 @@ class CoreTlvDataString(CoreTlvData): header_len = CoreTlv.long_header_len pad_len = -(header_len + len(value)) % 4 - return len(value), value + "\0" * pad_len + return len(value), value + b"\0" * pad_len @classmethod def unpack(cls, data): @@ -195,7 +196,7 @@ class CoreTlvDataString(CoreTlvData): :param str data: unpack string data :return: unpacked string data """ - return data.rstrip("\0") + return data.rstrip(b"\0").decode("utf-8") class CoreTlvDataUint16List(CoreTlvData): @@ -266,7 +267,7 @@ class CoreTlvDataIpv4Addr(CoreTlvDataObj): return obj.addr @staticmethod - def new_obj(obj): + def new_obj(value): """ Retrieve Ipv4 address from a string representation. @@ -274,7 +275,9 @@ class CoreTlvDataIpv4Addr(CoreTlvDataObj): :return: Ipv4 address :rtype: core.misc.ipaddress.IpAddress """ - return IpAddress(af=socket.AF_INET, address=obj) + # value = value.decode("ISO-8859-1") + # value = socket.inet_ntoa(value) + return IpAddress(af=socket.AF_INET, address=value) class CoreTlvDataIPv6Addr(CoreTlvDataObj): @@ -304,6 +307,8 @@ class CoreTlvDataIPv6Addr(CoreTlvDataObj): :return: Ipv4 address :rtype: core.misc.ipaddress.IpAddress """ + # value = value.decode("ISO-8859-1") + # value = socket.inet_ntoa(value) return IpAddress(af=socket.AF_INET6, address=value) @@ -336,6 +341,8 @@ class CoreTlvDataMacAddr(CoreTlvDataObj): :rtype: core.misc.ipaddress.MacAddress """ # only use 48 bits + # value = value.decode("ISO-8859-1") + # value = socket.inet_ntoa(value) return MacAddress(address=value[2:]) @@ -397,12 +404,10 @@ class CoreTlv(object): :return: header and packed data """ tlv_len, tlv_data = cls.tlv_data_class_map[tlv_type].pack(value) - if tlv_len < 256: hdr = struct.pack(cls.header_format, tlv_type, tlv_len) else: hdr = struct.pack(cls.long_header_format, tlv_type, 0, tlv_len) - return hdr + tlv_data @classmethod diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index f9d505f1..5f123a07 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -190,7 +190,7 @@ class CoreHandler(socketserver.BaseRequestHandler): thumbs = "|".join(thumb_list) if num_sessions > 0: - tlv_data = "" + tlv_data = b"" if len(session_ids) > 0: tlv_data += coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, session_ids) if len(names) > 0: @@ -224,7 +224,7 @@ class CoreHandler(socketserver.BaseRequestHandler): (EventTlvs.NAME, event_data.name), (EventTlvs.DATA, event_data.data), (EventTlvs.TIME, event_data.time), - (EventTlvs.TIME, event_data.session) + (EventTlvs.SESSION, event_data.session) ]) message = coreapi.CoreEventMessage.pack(0, tlv_data) @@ -373,7 +373,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ logging.info("GUI has connected to session %d at %s", self.session.id, time.ctime()) - tlv_data = "" + tlv_data = b"" tlv_data += coreapi.CoreRegisterTlv.pack(RegisterTlvs.EXECUTE_SERVER.value, "core-daemon") tlv_data += coreapi.CoreRegisterTlv.pack(RegisterTlvs.EMULATION_SERVER.value, "core-daemon") tlv_data += coreapi.CoreRegisterTlv.pack(self.session.broker.config_type, self.session.broker.name) @@ -424,7 +424,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if message_len == 0: logging.warn("received message with no data") - data = "" + data = b"" while len(data) < message_len: data += self.request.recv(message_len - len(data)) if len(data) > message_len: @@ -504,7 +504,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :param message: message for replies :return: nothing """ - logging.debug("dispatching replies") + logging.debug("dispatching replies: %s", replies) for reply in replies: message_type, message_flags, message_length = coreapi.CoreMessage.unpack_header(reply) try: @@ -682,7 +682,7 @@ class CoreHandler(socketserver.BaseRequestHandler): # if we deleted a node broadcast out its removal if result and message.flags & MessageFlags.STRING.value: - tlvdata = "" + tlvdata = b"" tlvdata += coreapi.CoreNodeTlv.pack(NodeTlvs.NUMBER.value, node_id) flags = MessageFlags.DELETE.value | MessageFlags.LOCAL.value replies.append(coreapi.CoreNodeMessage.pack(flags, tlvdata)) @@ -779,7 +779,7 @@ class CoreHandler(socketserver.BaseRequestHandler): node = self.session.get_node(node_num) # build common TLV items for reply - tlv_data = "" + tlv_data = b"" if node_num is not None: tlv_data += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node_num) tlv_data += coreapi.CoreExecuteTlv.pack(ExecuteTlvs.NUMBER.value, execute_num) @@ -1667,7 +1667,7 @@ class CoreHandler(socketserver.BaseRequestHandler): :return: nothing """ if node_id in self.node_status_request: - tlv_data = "" + tlv_data = b"" tlv_data += coreapi.CoreNodeTlv.pack(NodeTlvs.NUMBER.value, node_id) tlv_data += coreapi.CoreNodeTlv.pack(NodeTlvs.EMULATION_ID.value, node_id) reply = coreapi.CoreNodeMessage.pack(MessageFlags.ADD.value | MessageFlags.LOCAL.value, tlv_data) diff --git a/daemon/core/api/tlv/structutils.py b/daemon/core/api/tlv/structutils.py index cfcc7a93..02f46583 100644 --- a/daemon/core/api/tlv/structutils.py +++ b/daemon/core/api/tlv/structutils.py @@ -15,7 +15,8 @@ def pack_values(clazz, packers): """ # iterate through tuples of values to pack - data = "" + logging.debug("packing: %s", packers) + data = b"" for packer in packers: # check if a transformer was provided for valid values transformer = None @@ -26,10 +27,6 @@ def pack_values(clazz, packers): else: raise RuntimeError("packer had more than 3 arguments") - # convert unicode to normal str for packing - if isinstance(value, unicode): - value = str(value) - # only pack actual values and avoid packing empty strings # protobuf defaults to empty strings and does no imply a value to set if value is None or (isinstance(value, str) and not value): diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 54099a35..9a155231 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -497,7 +497,7 @@ class EmaneManager(ModelManager): logging.info("Setting up default controlnet prefixes for distributed (%d configured)" % len(prefixes)) prefixes = ctrlnet.DEFAULT_PREFIX_LIST[0] vals = 'controlnet="%s"' % prefixes - tlvdata = "" + tlvdata = b"" tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.OBJECT.value, "session") tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.TYPE.value, 0) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.VALUES.value, vals) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 0e500d4d..b9d24f94 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1509,6 +1509,7 @@ class Session(object): assign_address = self.master prefix = prefixes[0] + logging.info("controlnet prefix: %s - %s", type(prefix), prefix) control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET) control_net = self.create_node(cls=control_net_class, _id=_id, prefix=prefix, assign_address=assign_address, diff --git a/daemon/core/location/event.py b/daemon/core/location/event.py index 19ed7ced..2c407286 100644 --- a/daemon/core/location/event.py +++ b/daemon/core/location/event.py @@ -5,6 +5,7 @@ event.py: event loop implementation using a heap queue and threads. import heapq import threading import time +from past.builtins import cmp class Timer(threading.Thread): diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index c5f786ac..c3192d9e 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -9,6 +9,7 @@ import os import threading import time from builtins import int +from past.builtins import cmp from core import utils from core.config import ConfigGroup diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 9cbd646a..a0410d92 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -687,7 +687,8 @@ class CoreNode(CoreNodeBase): output = output.split("\n") veth.flow_id = int(output[0].strip().split(":")[0]) + 1 logging.debug("interface flow index: %s - %s", veth.name, veth.flow_id) - veth.hwaddr = MacAddress.from_string(output[1].strip().split()[1]) + # TODO: mimic packed hwaddr + # veth.hwaddr = MacAddress.from_string(output[1].strip().split()[1]) logging.debug("interface mac: %s - %s", veth.name, veth.hwaddr) try: @@ -1062,11 +1063,13 @@ class CoreNetworkBase(NodeBase): if ipaddress.is_ipv4_address(ip): family = AF_INET ipl = socket.inet_pton(family, ip) + # ipl = ipl.decode("ISO-8859-1") interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip4_mask = mask else: family = AF_INET6 ipl = socket.inet_pton(family, ip) + # ipl = ipl.decode("ISO-8859-1") interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip6_mask = mask diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index 7ef7a005..b09da1fa 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -8,7 +8,7 @@ by invoking the vcmd shell command. import logging import os -import vcmd +from subprocess import Popen, PIPE from core import CoreCommandError, utils from core import constants @@ -28,7 +28,6 @@ class VnodeClient(object): """ self.name = name self.ctrlchnlname = ctrlchnlname - self.cmdchnl = vcmd.VCmd(self.ctrlchnlname) self._addr = {} def _verify_connection(self): @@ -48,7 +47,7 @@ class VnodeClient(object): :return: True if connected, False otherwise :rtype: bool """ - return self.cmdchnl.connected() + return True def close(self): """ @@ -56,7 +55,10 @@ class VnodeClient(object): :return: nothing """ - self.cmdchnl.close() + pass + + def _cmd_args(self): + return [constants.VCMD_BIN, "-c", self.ctrlchnlname, "--"] def cmd(self, args, wait=True): """ @@ -71,7 +73,9 @@ class VnodeClient(object): args = utils.split_args(args) # run command, return process when not waiting - p = self.cmdchnl.qcmd(args) + cmd = self._cmd_args() + args + logging.info("cmd wait(%s): %s", wait, cmd) + p = Popen(cmd, stdout=PIPE, stderr=PIPE) if not wait: return 0 @@ -94,7 +98,7 @@ class VnodeClient(object): stdout.close() stderr.close() status = p.wait() - return status, output.strip() + return status, output.decode("utf-8").strip() def check_cmd(self, args): """ @@ -120,7 +124,12 @@ class VnodeClient(object): """ self._verify_connection() args = utils.split_args(args) - return self.cmdchnl.popen(args) + # if isinstance(args, list): + # args = " ".join(args) + cmd = self._cmd_args() + args + logging.info("popen: %s", cmd) + p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE) + return p, p.stdin, p.stdout, p.stderr def icmd(self, args): """ @@ -150,7 +159,10 @@ class VnodeClient(object): # run command, return process when not waiting args = utils.split_args(args) - p = self.cmdchnl.redircmd(infd, outfd, errfd, args) + cmd = self._cmd_args() + args + logging.info("redircmd: %s", cmd) + p = Popen(cmd, stdin=infd, stdout=outfd, stderr=errfd) + if not wait: return p diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 1b628408..56848cba 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -31,7 +31,7 @@ class CoreInterface(object): self.node = node self.name = name - if not isinstance(mtu, (int, long)): + if not isinstance(mtu, int): raise ValueError self.mtu = mtu self.net = None @@ -142,7 +142,7 @@ class CoreInterface(object): """ # treat None and 0 as unchanged values current_value = self._params.get(key) - if current_value == value or current_value <= 0 and value <= 0: + if current_value is None or current_value == value or current_value <= 0 and value <= 0: return False self._params[key] = value @@ -174,6 +174,16 @@ class CoreInterface(object): """ self.poshook(self, x, y, z) + def __lt__(self, other): + """ + Used for comparisons of this object. + + :param other: other interface + :return: true if less than, false otherwise + :rtype: bool + """ + return id(self) < id(other) + class Veth(CoreInterface): """ diff --git a/daemon/core/nodes/ipaddress.py b/daemon/core/nodes/ipaddress.py index 864b0296..23144ce1 100644 --- a/daemon/core/nodes/ipaddress.py +++ b/daemon/core/nodes/ipaddress.py @@ -31,7 +31,9 @@ class MacAddress(object): :return: string representation :rtype: str """ - return ":".join("%02x" % ord(x) for x in self.addr) + logging.info("mac addr: %s", type(self.addr)) + addr = self.addr.decode("ISO-8859-1") + return ":".join("%02x" % ord(x) for x in addr) def to_link_local(self): """ @@ -93,7 +95,9 @@ class IpAddress(object): :return: """ # check if (af, addr) is valid + logging.info("ip address: %s", type(address)) if not socket.inet_ntop(af, address): + # if not socket.inet_ntop(af, address.encode("ISO-8859-1")): raise ValueError("invalid af/addr") self.af = af self.addr = address @@ -124,6 +128,7 @@ class IpAddress(object): :rtype: str """ return socket.inet_ntop(self.af, self.addr) + # return socket.inet_ntop(self.af, self.addr.encode("ISO-8859-1")) def __eq__(self, other): """ @@ -231,7 +236,7 @@ class IpPrefix(object): self.prefixlen = int(tmp[1]) else: self.prefixlen = self.addrlen - self.prefix = socket.inet_pton(self.af, tmp[0]) + self.prefix = socket.inet_pton(self.af, tmp[0]).decode("ISO-8859-1") if self.addrlen > self.prefixlen: addrbits = self.addrlen - self.prefixlen netmask = ((1 << self.prefixlen) - 1) << addrbits diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b76a035a..82e20017 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -855,11 +855,13 @@ class PtpNet(CoreNetwork): if ipaddress.is_ipv4_address(ip): family = AF_INET ipl = socket.inet_pton(family, ip) + # ipl = ipl.decode("ISO-8859-1") interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl) interface1_ip4_mask = mask else: family = AF_INET6 ipl = socket.inet_pton(family, ip) + # ipl = ipl.decode("ISO-8859-1") interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl) interface1_ip6_mask = mask @@ -873,11 +875,13 @@ class PtpNet(CoreNetwork): if ipaddress.is_ipv4_address(ip): family = AF_INET ipl = socket.inet_pton(family, ip) + # ipl = ipl.decode("ISO-8859-1") interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip4_mask = mask else: family = AF_INET6 ipl = socket.inet_pton(family, ip) + # ipl = ipl.decode("ISO-8859-1") interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip6_mask = mask diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index dabf3453..0cd200ba 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -4,7 +4,11 @@ sdt.py: Scripted Display Tool (SDT3D) helper import logging import socket -from urlparse import urlparse + +from future.standard_library import install_aliases +install_aliases() + +from urllib.parse import urlparse from core import constants from core.nodes.base import NodeBase, CoreNetworkBase diff --git a/daemon/core/utils.py b/daemon/core/utils.py index e2c0fedc..2a3cfdd3 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -2,6 +2,7 @@ Miscellaneous utility functions, wrappers around some subprocess procedures. """ +import fcntl import importlib import inspect import logging @@ -10,8 +11,6 @@ import shlex import subprocess import sys -import fcntl - from core import CoreCommandError DEVNULL = open(os.devnull, "wb") @@ -126,7 +125,7 @@ def make_tuple_fromstr(s, value_type): """ Create a tuple from a string. - :param str|unicode s: string to convert to a tuple + :param str s: string to convert to a tuple :param value_type: type of values to be contained within tuple :return: tuple from string :rtype: tuple @@ -148,7 +147,9 @@ def split_args(args): :return: shell-like syntax list :rtype: list """ - if isinstance(args, basestring): + logging.info("split args: %s - %s", args, type(args)) + if isinstance(args, str): + logging.info("splitting args") args = shlex.split(args) return args @@ -231,7 +232,7 @@ def check_cmd(args, **kwargs): status = p.wait() if status != 0: raise CoreCommandError(status, args, stdout) - return stdout.strip() + return stdout.decode("utf-8").strip() except OSError: raise CoreCommandError(-1, args) diff --git a/daemon/data/logging.conf b/daemon/data/logging.conf index 7f3d496f..46de6e92 100644 --- a/daemon/data/logging.conf +++ b/daemon/data/logging.conf @@ -14,7 +14,7 @@ } }, "root": { - "level": "INFO", + "level": "DEBUG", "handlers": ["console"] } } diff --git a/daemon/proto/Makefile.am b/daemon/proto/Makefile.am index 59665a6f..05f2d394 100644 --- a/daemon/proto/Makefile.am +++ b/daemon/proto/Makefile.am @@ -1,5 +1,5 @@ all: - $(PYTHON) -m grpc_tools.protoc -I . --python_out=../core/api/grpc --grpc_python_out=../core/api/grpc core.proto + $(PYTHON) -m grpc_tools.protoc -I . --python_out=.. --grpc_python_out=.. core/api/grpc/core.proto clean: -rm -f ../core/api/grpc/core_pb2* diff --git a/daemon/proto/core.proto b/daemon/proto/core/api/grpc/core.proto similarity index 100% rename from daemon/proto/core.proto rename to daemon/proto/core/api/grpc/core.proto diff --git a/netns/Makefile.am b/netns/Makefile.am index d000cb8e..77a8f228 100644 --- a/netns/Makefile.am +++ b/netns/Makefile.am @@ -30,38 +30,38 @@ netns_SOURCES = $(SRC_NETNS) # this triggers automake to run setup.py for building the Python libraries # actual library names are netns.so and vcmd.so # SOURCES line prevents 'make dist' from looking for a 'libnetns.c' file -if WANT_PYTHON -noinst_LIBRARIES = libnetns.a -libnetns_a_SOURCES = netnsmodule.c vcmdmodule.c -libnetns.a: - LDFLAGS="$(LDFLAGS) @libev_LIBS@" CFLAGS="$(CFLAGS) @libev_CFLAGS@" $(PYTHON) setup.py build_ext - -# Python libraries install -install-exec-local: - $(PYTHON) $(SETUPPY) $(SETUPPYFLAGS) install \ - --root=/$(DESTDIR) \ - --prefix=$(prefix) \ - --install-lib=$(pythondir) \ - --single-version-externally-managed \ - --no-compile - -# Python libraries uninstall -uninstall-hook: - rm -rf core_netns.egg-info - rm -rf $(DESTDIR)/$(pythondir)/core_netns-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg-info - rm -f $(DESTDIR)/$(bindir)/{vnoded,vcmd,netns} - rm -f $(DESTDIR)/$(pythondir)/{netns.so,vcmd.so} - -# Python libraries cleanup -clean-local: clean-local-check -.PHONY: clean-local-check -clean-local-check: - -rm -rf build - -distclean-local: - -rm -rf core_netns.egg-info - -endif +#if WANT_PYTHON +#noinst_LIBRARIES = libnetns.a +#libnetns_a_SOURCES = netnsmodule.c vcmdmodule.c +#libnetns.a: +# LDFLAGS="$(LDFLAGS) @libev_LIBS@" CFLAGS="$(CFLAGS) @libev_CFLAGS@" $(PYTHON) setup.py build_ext +# +## Python libraries install +#install-exec-local: +# $(PYTHON) $(SETUPPY) $(SETUPPYFLAGS) install \ +# --root=/$(DESTDIR) \ +# --prefix=$(prefix) \ +# --install-lib=$(pythondir) \ +# --single-version-externally-managed \ +# --no-compile +# +## Python libraries uninstall +#uninstall-hook: +# rm -rf core_netns.egg-info +# rm -rf $(DESTDIR)/$(pythondir)/core_netns-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg-info +# rm -f $(DESTDIR)/$(bindir)/{vnoded,vcmd,netns} +# rm -f $(DESTDIR)/$(pythondir)/{netns.so,vcmd.so} +# +## Python libraries cleanup +#clean-local: clean-local-check +#.PHONY: clean-local-check +#clean-local-check: +# -rm -rf build +# +#distclean-local: +# -rm -rf core_netns.egg-info +# +#endif # endif WANT_PYTHON # extra cruft to remove From 3de37f0f5ed1556722b616a115922d3a019c56e3 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 2 Jun 2019 19:23:49 -0700 Subject: [PATCH 0047/1992] fixed basestring check for 2/3 compatibility --- daemon/core/api/grpc/core_pb2.py | 1 + daemon/core/api/tlv/coreapi.py | 7 ++++--- daemon/core/api/tlv/structutils.py | 3 ++- daemon/core/emane/commeffect.py | 2 +- daemon/core/utils.py | 3 ++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/daemon/core/api/grpc/core_pb2.py b/daemon/core/api/grpc/core_pb2.py index d1531404..c59d19a4 100644 --- a/daemon/core/api/grpc/core_pb2.py +++ b/daemon/core/api/grpc/core_pb2.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! # source: core/api/grpc/core.proto diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index 8d2b5ebf..218dbdf8 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -7,9 +7,11 @@ CORE API messaging is leveraged for communication with the GUI. import socket import struct +from past.builtins import basestring from enum import Enum +from core.api.tlv import structutils from core.emulator.enumerations import ConfigTlvs from core.emulator.enumerations import EventTlvs from core.emulator.enumerations import EventTypes @@ -23,7 +25,6 @@ from core.emulator.enumerations import MessageTypes from core.emulator.enumerations import NodeTlvs from core.emulator.enumerations import RegisterTlvs from core.emulator.enumerations import SessionTlvs -from core.api.tlv import structutils from core.nodes.ipaddress import IpAddress from core.nodes.ipaddress import MacAddress @@ -176,8 +177,8 @@ class CoreTlvDataString(CoreTlvData): :return: length of data packed and the packed data :rtype: tuple """ - if not isinstance(value, str): - raise ValueError("value not a string: %s" % value) + if not isinstance(value, basestring): + raise ValueError("value not a string: %s" % type(value)) value = value.encode("utf-8") if len(value) < 256: diff --git a/daemon/core/api/tlv/structutils.py b/daemon/core/api/tlv/structutils.py index 02f46583..affca97a 100644 --- a/daemon/core/api/tlv/structutils.py +++ b/daemon/core/api/tlv/structutils.py @@ -3,6 +3,7 @@ Utilities for working with python struct data. """ import logging +from past.builtins import basestring def pack_values(clazz, packers): @@ -29,7 +30,7 @@ def pack_values(clazz, packers): # only pack actual values and avoid packing empty strings # protobuf defaults to empty strings and does no imply a value to set - if value is None or (isinstance(value, str) and not value): + if value is None or (isinstance(value, basestring) and not value): continue # transform values as needed diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index e9d9c9fe..109262e8 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -4,8 +4,8 @@ commeffect.py: EMANE CommEffect model for CORE import logging import os - from lxml import etree +from past.builtins import basestring from core.config import ConfigGroup from core.emane import emanemanifest diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 2a3cfdd3..1b53662d 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -10,6 +10,7 @@ import os import shlex import subprocess import sys +from past.builtins import basestring from core import CoreCommandError @@ -148,7 +149,7 @@ def split_args(args): :rtype: list """ logging.info("split args: %s - %s", args, type(args)) - if isinstance(args, str): + if isinstance(args, basestring): logging.info("splitting args") args = shlex.split(args) return args From 7a0edd59277fad8c8dfaea097eb65dbc37da30d9 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 2 Jun 2019 22:20:00 -0700 Subject: [PATCH 0048/1992] fixed bad logic introduced into interface setparams, fixed grpc edit link test --- .gitignore | 4 +- daemon/core/api/grpc/core_pb2.py | 6528 ------------------------- daemon/core/api/grpc/core_pb2_grpc.py | 828 ---- daemon/core/nodes/interface.py | 2 +- daemon/tests/test_grpc.py | 2 +- 5 files changed, 4 insertions(+), 7360 deletions(-) delete mode 100644 daemon/core/api/grpc/core_pb2.py delete mode 100644 daemon/core/api/grpc/core_pb2_grpc.py diff --git a/.gitignore b/.gitignore index dbf9e4c3..2cbc88c3 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,8 @@ debian stamp-h1 # generated protobuf files -daemon/core/grpc/core_pb2.py -daemon/core/grpc/core_pb2_grpc.py +daemon/core/api/grpc/core_pb2.py +daemon/core/api/grpc/core_pb2_grpc.py # python build directory dist diff --git a/daemon/core/api/grpc/core_pb2.py b/daemon/core/api/grpc/core_pb2.py deleted file mode 100644 index c59d19a4..00000000 --- a/daemon/core/api/grpc/core_pb2.py +++ /dev/null @@ -1,6528 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by the protocol buffer compiler. DO NOT EDIT! -# source: core/api/grpc/core.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf.internal import enum_type_wrapper -from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection -from google.protobuf import symbol_database as _symbol_database -# @@protoc_insertion_point(imports) - -_sym_db = _symbol_database.Default() - - - - -DESCRIPTOR = _descriptor.FileDescriptor( - name='core/api/grpc/core.proto', - package='core', - syntax='proto3', - serialized_options=None, - serialized_pb=_b('\n\x18\x63ore/api/grpc/core.proto\x12\x04\x63ore\"\"\n\x14\x43reateSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"F\n\x15\x43reateSessionResponse\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\"\"\n\x14\x44\x65leteSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\'\n\x15\x44\x65leteSessionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x14\n\x12GetSessionsRequest\"=\n\x13GetSessionsResponse\x12&\n\x08sessions\x18\x01 \x03(\x0b\x32\x14.core.SessionSummary\"\x1f\n\x11GetSessionRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"4\n\x12GetSessionResponse\x12\x1e\n\x07session\x18\x01 \x01(\x0b\x32\r.core.Session\"&\n\x18GetSessionOptionsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\">\n\x19GetSessionOptionsResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x91\x01\n\x18SetSessionOptionsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12:\n\x06\x63onfig\x18\x02 \x03(\x0b\x32*.core.SetSessionOptionsRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x19SetSessionOptionsResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\'\n\x19GetSessionLocationRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"M\n\x1aGetSessionLocationResponse\x12 \n\x08position\x18\x01 \x01(\x0b\x32\x0e.core.Position\x12\r\n\x05scale\x18\x02 \x01(\x02\"X\n\x19SetSessionLocationRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12 \n\x08position\x18\x02 \x01(\x0b\x32\x0e.core.Position\x12\r\n\x05scale\x18\x03 \x01(\x02\",\n\x1aSetSessionLocationResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"G\n\x16SetSessionStateRequest\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\")\n\x17SetSessionStateResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x1f\n\x11NodeEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"%\n\tNodeEvent\x12\x18\n\x04node\x18\x01 \x01(\x0b\x32\n.core.Node\"\x1f\n\x11LinkEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"N\n\tLinkEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x18\n\x04link\x18\x02 \x01(\x0b\x32\n.core.Link\"\"\n\x14SessionEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"f\n\x0cSessionEvent\x12\x0c\n\x04node\x18\x01 \x01(\x05\x12\r\n\x05\x65vent\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x04 \x01(\x0c\x12\x0c\n\x04time\x18\x05 \x01(\x02\x12\x0f\n\x07session\x18\x06 \x01(\x05\"!\n\x13\x43onfigEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\x9e\x02\n\x0b\x43onfigEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x0c\n\x04node\x18\x02 \x01(\x05\x12\x0e\n\x06object\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x12\n\ndata_types\x18\x05 \x03(\x05\x12\x13\n\x0b\x64\x61ta_values\x18\x06 \x01(\t\x12\x10\n\x08\x63\x61ptions\x18\x07 \x01(\t\x12\x0e\n\x06\x62itmap\x18\x08 \x01(\t\x12\x17\n\x0fpossible_values\x18\t \x01(\t\x12\x0e\n\x06groups\x18\n \x01(\t\x12\x0f\n\x07session\x18\x0b \x01(\t\x12\x11\n\tinterface\x18\x0c \x01(\x05\x12\x12\n\nnetwork_id\x18\r \x01(\x05\x12\x0e\n\x06opaque\x18\x0e \x01(\t\"$\n\x16\x45xceptionEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\x90\x01\n\x0e\x45xceptionEvent\x12\x0c\n\x04node\x18\x01 \x01(\x05\x12\x0f\n\x07session\x18\x02 \x01(\x05\x12#\n\x05level\x18\x03 \x01(\x0e\x32\x14.core.ExceptionLevel\x12\x0e\n\x06source\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61te\x18\x05 \x01(\t\x12\x0c\n\x04text\x18\x06 \x01(\t\x12\x0e\n\x06opaque\x18\x07 \x01(\t\"\x1f\n\x11\x46ileEventsRequest\x12\n\n\x02id\x18\x01 \x01(\x05\"\xc4\x01\n\tFileEvent\x12\'\n\x0cmessage_type\x18\x01 \x01(\x0e\x32\x11.core.MessageType\x12\x0c\n\x04node\x18\x02 \x01(\x05\x12\x0c\n\x04name\x18\x03 \x01(\t\x12\x0c\n\x04mode\x18\x04 \x01(\t\x12\x0e\n\x06number\x18\x05 \x01(\x05\x12\x0c\n\x04type\x18\x06 \x01(\t\x12\x0e\n\x06source\x18\x07 \x01(\t\x12\x0f\n\x07session\x18\x08 \x01(\x05\x12\x0c\n\x04\x64\x61ta\x18\t \x01(\x0c\x12\x17\n\x0f\x63ompressed_data\x18\n \x01(\x0c\";\n\x0e\x41\x64\x64NodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04node\x18\x02 \x01(\x0b\x32\n.core.Node\"\x1d\n\x0f\x41\x64\x64NodeResponse\x12\n\n\x02id\x18\x01 \x01(\x05\"-\n\x0eGetNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"P\n\x0fGetNodeResponse\x12\x18\n\x04node\x18\x01 \x01(\x0b\x32\n.core.Node\x12#\n\ninterfaces\x18\x02 \x03(\x0b\x32\x0f.core.Interface\"P\n\x0f\x45\x64itNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12 \n\x08position\x18\x03 \x01(\x0b\x32\x0e.core.Position\"\"\n\x10\x45\x64itNodeResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"0\n\x11\x44\x65leteNodeRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"$\n\x12\x44\x65leteNodeResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"2\n\x13GetNodeLinksRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\"1\n\x14GetNodeLinksResponse\x12\x19\n\x05links\x18\x01 \x03(\x0b\x32\n.core.Link\";\n\x0e\x41\x64\x64LinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04link\x18\x02 \x01(\x0b\x32\n.core.Link\"!\n\x0f\x41\x64\x64LinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x98\x01\n\x0f\x45\x64itLinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x10\n\x08node_one\x18\x02 \x01(\x05\x12\x10\n\x08node_two\x18\x03 \x01(\x05\x12\x15\n\rinterface_one\x18\x04 \x01(\x05\x12\x15\n\rinterface_two\x18\x05 \x01(\x05\x12\"\n\x07options\x18\x06 \x01(\x0b\x32\x11.core.LinkOptions\"\"\n\x10\x45\x64itLinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"v\n\x11\x44\x65leteLinkRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x10\n\x08node_one\x18\x02 \x01(\x05\x12\x10\n\x08node_two\x18\x03 \x01(\x05\x12\x15\n\rinterface_one\x18\x04 \x01(\x05\x12\x15\n\rinterface_two\x18\x05 \x01(\x05\"$\n\x12\x44\x65leteLinkResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\"\n\x0fGetHooksRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"-\n\x10GetHooksResponse\x12\x19\n\x05hooks\x18\x01 \x03(\x0b\x32\n.core.Hook\";\n\x0e\x41\x64\x64HookRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x18\n\x04hook\x18\x02 \x01(\x0b\x32\n.core.Hook\"!\n\x0f\x41\x64\x64HookResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\",\n\x19GetMobilityConfigsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\xf2\x01\n\x1aGetMobilityConfigsResponse\x12>\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32-.core.GetMobilityConfigsResponse.ConfigsEntry\x1a\x33\n\x0eMobilityConfig\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\x1a_\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12>\n\x05value\x18\x02 \x01(\x0b\x32/.core.GetMobilityConfigsResponse.MobilityConfig:\x02\x38\x01\"7\n\x18GetMobilityConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\">\n\x19GetMobilityConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\xa2\x01\n\x18SetMobilityConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12:\n\x06\x63onfig\x18\x03 \x03(\x0b\x32*.core.SetMobilityConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"+\n\x19SetMobilityConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"Z\n\x15MobilityActionRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12$\n\x06\x61\x63tion\x18\x03 \x01(\x0e\x32\x14.core.MobilityAction\"(\n\x16MobilityActionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"\x14\n\x12GetServicesRequest\"6\n\x13GetServicesResponse\x12\x1f\n\x08services\x18\x01 \x03(\x0b\x32\r.core.Service\",\n\x19GetServiceDefaultsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"E\n\x1aGetServiceDefaultsResponse\x12\'\n\x08\x64\x65\x66\x61ults\x18\x01 \x03(\x0b\x32\x15.core.ServiceDefaults\"U\n\x19SetServiceDefaultsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\'\n\x08\x64\x65\x66\x61ults\x18\x02 \x03(\x0b\x32\x15.core.ServiceDefaults\",\n\x1aSetServiceDefaultsResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"E\n\x15GetNodeServiceRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\"@\n\x16GetNodeServiceResponse\x12&\n\x07service\x18\x01 \x01(\x0b\x32\x15.core.NodeServiceData\"W\n\x19GetNodeServiceFileRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0c\n\x04\x66ile\x18\x04 \x01(\t\"*\n\x1aGetNodeServiceFileResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"z\n\x15SetNodeServiceRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0f\n\x07startup\x18\x04 \x03(\t\x12\x10\n\x08validate\x18\x05 \x03(\t\x12\x10\n\x08shutdown\x18\x06 \x03(\t\"(\n\x16SetNodeServiceResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"e\n\x19SetNodeServiceFileRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12\x0c\n\x04\x66ile\x18\x04 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\x0c\",\n\x1aSetNodeServiceFileResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"i\n\x14ServiceActionRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x0f\n\x07service\x18\x03 \x01(\t\x12#\n\x06\x61\x63tion\x18\x04 \x01(\x0e\x32\x13.core.ServiceAction\"\'\n\x15ServiceActionResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"3\n\x14GetWlanConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\":\n\x15GetWlanConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x9a\x01\n\x14SetWlanConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x36\n\x06\x63onfig\x18\x03 \x03(\x0b\x32&.core.SetWlanConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\'\n\x15SetWlanConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"(\n\x15GetEmaneConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\";\n\x16GetEmaneConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\x90\x01\n\x15SetEmaneConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\x37\n\x06\x63onfig\x18\x02 \x03(\x0b\x32\'.core.SetEmaneConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"(\n\x16SetEmaneConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\"(\n\x15GetEmaneModelsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"(\n\x16GetEmaneModelsResponse\x12\x0e\n\x06models\x18\x01 \x03(\t\"[\n\x1aGetEmaneModelConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x11\n\tinterface\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\"@\n\x1bGetEmaneModelConfigResponse\x12!\n\x06groups\x18\x01 \x03(\x0b\x32\x11.core.ConfigGroup\"\xc8\x01\n\x1aSetEmaneModelConfigRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\x12\n\n\x02id\x18\x02 \x01(\x05\x12\x11\n\tinterface\x18\x03 \x01(\x05\x12\r\n\x05model\x18\x04 \x01(\t\x12<\n\x06\x63onfig\x18\x05 \x03(\x0b\x32,.core.SetEmaneModelConfigRequest.ConfigEntry\x1a-\n\x0b\x43onfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"-\n\x1bSetEmaneModelConfigResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\".\n\x1bGetEmaneModelConfigsRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\x81\x02\n\x1cGetEmaneModelConfigsResponse\x12@\n\x07\x63onfigs\x18\x01 \x03(\x0b\x32/.core.GetEmaneModelConfigsResponse.ConfigsEntry\x1a?\n\x0bModelConfig\x12\r\n\x05model\x18\x01 \x01(\t\x12!\n\x06groups\x18\x02 \x03(\x0b\x32\x11.core.ConfigGroup\x1a^\n\x0c\x43onfigsEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12=\n\x05value\x18\x02 \x01(\x0b\x32..core.GetEmaneModelConfigsResponse.ModelConfig:\x02\x38\x01\"!\n\x0eSaveXmlRequest\x12\x0f\n\x07session\x18\x01 \x01(\x05\"\x1f\n\x0fSaveXmlResponse\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"\x1e\n\x0eOpenXmlRequest\x12\x0c\n\x04\x64\x61ta\x18\x01 \x01(\x0c\"2\n\x0fOpenXmlResponse\x12\x0e\n\x06result\x18\x01 \x01(\x08\x12\x0f\n\x07session\x18\x02 \x01(\x05\"E\n\x04Hook\x12!\n\x05state\x18\x01 \x01(\x0e\x32\x12.core.SessionState\x12\x0c\n\x04\x66ile\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\x0c\"6\n\x0fServiceDefaults\x12\x11\n\tnode_type\x18\x01 \x01(\t\x12\x10\n\x08services\x18\x02 \x03(\t\"&\n\x07Service\x12\r\n\x05group\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\xee\x01\n\x0fNodeServiceData\x12\x13\n\x0b\x65xecutables\x18\x01 \x03(\t\x12\x14\n\x0c\x64\x65pendencies\x18\x02 \x03(\t\x12\x0c\n\x04\x64irs\x18\x03 \x03(\t\x12\x0f\n\x07\x63onfigs\x18\x04 \x03(\t\x12\x0f\n\x07startup\x18\x05 \x03(\t\x12\x10\n\x08validate\x18\x06 \x03(\t\x12\x34\n\x0fvalidation_mode\x18\x07 \x01(\x0e\x32\x1b.core.ServiceValidationMode\x12\x18\n\x10validation_timer\x18\x08 \x01(\x05\x12\x10\n\x08shutdown\x18\t \x03(\t\x12\x0c\n\x04meta\x18\n \x01(\t\"@\n\x0b\x43onfigGroup\x12\x0c\n\x04name\x18\x01 \x01(\t\x12#\n\x07options\x18\x02 \x03(\x0b\x32\x12.core.ConfigOption\"X\n\x0c\x43onfigOption\x12\r\n\x05label\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05value\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\x12\x0e\n\x06select\x18\x05 \x03(\t\"n\n\x07Session\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\x12\x19\n\x05nodes\x18\x03 \x03(\x0b\x32\n.core.Node\x12\x19\n\x05links\x18\x04 \x03(\x0b\x32\n.core.Link\"N\n\x0eSessionSummary\x12\n\n\x02id\x18\x01 \x01(\x05\x12!\n\x05state\x18\x02 \x01(\x0e\x32\x12.core.SessionState\x12\r\n\x05nodes\x18\x03 \x01(\x05\"\xae\x01\n\x04Node\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x1c\n\x04type\x18\x03 \x01(\x0e\x32\x0e.core.NodeType\x12\r\n\x05model\x18\x04 \x01(\t\x12 \n\x08position\x18\x05 \x01(\x0b\x32\x0e.core.Position\x12\x10\n\x08services\x18\x06 \x03(\t\x12\r\n\x05\x65mane\x18\x07 \x01(\t\x12\x0c\n\x04icon\x18\x08 \x01(\t\x12\x0e\n\x06opaque\x18\t \x01(\t\"\xbc\x01\n\x04Link\x12\x10\n\x08node_one\x18\x01 \x01(\x05\x12\x10\n\x08node_two\x18\x02 \x01(\x05\x12\x1c\n\x04type\x18\x03 \x01(\x0e\x32\x0e.core.LinkType\x12&\n\rinterface_one\x18\x04 \x01(\x0b\x32\x0f.core.Interface\x12&\n\rinterface_two\x18\x05 \x01(\x0b\x32\x0f.core.Interface\x12\"\n\x07options\x18\x06 \x01(\x0b\x32\x11.core.LinkOptions\"\xba\x01\n\x0bLinkOptions\x12\x0e\n\x06opaque\x18\x01 \x01(\t\x12\x0e\n\x06jitter\x18\x02 \x01(\x02\x12\x0b\n\x03key\x18\x03 \x01(\t\x12\x0e\n\x06mburst\x18\x04 \x01(\x02\x12\x0b\n\x03mer\x18\x05 \x01(\x02\x12\x0b\n\x03per\x18\x06 \x01(\x02\x12\x11\n\tbandwidth\x18\x07 \x01(\x02\x12\r\n\x05\x62urst\x18\x08 \x01(\x02\x12\r\n\x05\x64\x65lay\x18\t \x01(\x02\x12\x0b\n\x03\x64up\x18\n \x01(\x02\x12\x16\n\x0eunidirectional\x18\x0b \x01(\x08\"\x9a\x01\n\tInterface\x12\n\n\x02id\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0b\n\x03mac\x18\x03 \x01(\t\x12\x0b\n\x03ip4\x18\x04 \x01(\t\x12\x0f\n\x07ip4mask\x18\x05 \x01(\x05\x12\x0b\n\x03ip6\x18\x06 \x01(\t\x12\x0f\n\x07ip6mask\x18\x07 \x01(\x05\x12\r\n\x05netid\x18\x08 \x01(\x05\x12\x0e\n\x06\x66lowid\x18\t \x01(\x05\x12\x0b\n\x03mtu\x18\n \x01(\x05\"R\n\x08Position\x12\t\n\x01x\x18\x01 \x01(\x02\x12\t\n\x01y\x18\x02 \x01(\x02\x12\t\n\x01z\x18\x03 \x01(\x02\x12\x0b\n\x03lat\x18\x04 \x01(\x02\x12\x0b\n\x03lon\x18\x05 \x01(\x02\x12\x0b\n\x03\x61lt\x18\x06 \x01(\x02*\x9f\x01\n\x0bMessageType\x12\x10\n\x0cMESSAGE_NONE\x10\x00\x12\x0f\n\x0bMESSAGE_ADD\x10\x01\x12\x12\n\x0eMESSAGE_DELETE\x10\x02\x12\x0f\n\x0bMESSAGE_CRI\x10\x04\x12\x11\n\rMESSAGE_LOCAL\x10\x08\x12\x12\n\x0eMESSAGE_STRING\x10\x10\x12\x10\n\x0cMESSAGE_TEXT\x10 \x12\x0f\n\x0bMESSAGE_TTY\x10@*-\n\x08LinkType\x12\x11\n\rLINK_WIRELESS\x10\x00\x12\x0e\n\nLINK_WIRED\x10\x01*\xa4\x01\n\x0cSessionState\x12\x0e\n\nSTATE_NONE\x10\x00\x12\x14\n\x10STATE_DEFINITION\x10\x01\x12\x17\n\x13STATE_CONFIGURATION\x10\x02\x12\x17\n\x13STATE_INSTANTIATION\x10\x03\x12\x11\n\rSTATE_RUNTIME\x10\x04\x12\x15\n\x11STATE_DATACOLLECT\x10\x05\x12\x12\n\x0eSTATE_SHUTDOWN\x10\x06*\x8b\x02\n\x08NodeType\x12\x10\n\x0cNODE_DEFAULT\x10\x00\x12\x11\n\rNODE_PHYSICAL\x10\x01\x12\x0c\n\x08NODE_TBD\x10\x03\x12\x0f\n\x0bNODE_SWITCH\x10\x04\x12\x0c\n\x08NODE_HUB\x10\x05\x12\x15\n\x11NODE_WIRELESS_LAN\x10\x06\x12\r\n\tNODE_RJ45\x10\x07\x12\x0f\n\x0bNODE_TUNNEL\x10\x08\x12\x10\n\x0cNODE_KTUNNEL\x10\t\x12\x0e\n\nNODE_EMANE\x10\n\x12\x13\n\x0fNODE_TAP_BRIDGE\x10\x0b\x12\x15\n\x11NODE_PEER_TO_PEER\x10\x0c\x12\x14\n\x10NODE_CONTROL_NET\x10\r\x12\x12\n\x0eNODE_EMANE_NET\x10\x0e*c\n\x15ServiceValidationMode\x12\x17\n\x13VALIDATION_BLOCKING\x10\x00\x12\x1b\n\x17VALIDATION_NON_BLOCKING\x10\x01\x12\x14\n\x10VALIDATION_TIMER\x10\x02*_\n\rServiceAction\x12\x11\n\rSERVICE_START\x10\x00\x12\x10\n\x0cSERVICE_STOP\x10\x01\x12\x13\n\x0fSERVICE_RESTART\x10\x02\x12\x14\n\x10SERVICE_VALIDATE\x10\x03*K\n\x0eMobilityAction\x12\x12\n\x0eMOBILITY_START\x10\x00\x12\x12\n\x0eMOBILITY_PAUSE\x10\x01\x12\x11\n\rMOBILITY_STOP\x10\x02*~\n\x0e\x45xceptionLevel\x12\x15\n\x11\x45XCEPTION_DEFAULT\x10\x00\x12\x13\n\x0f\x45XCEPTION_FATAL\x10\x01\x12\x13\n\x0f\x45XCEPTION_ERROR\x10\x02\x12\x15\n\x11\x45XCEPTION_WARNING\x10\x03\x12\x14\n\x10\x45XCEPTION_NOTICE\x10\x04\x32\xe2\x1b\n\x07\x43oreApi\x12J\n\rCreateSession\x12\x1a.core.CreateSessionRequest\x1a\x1b.core.CreateSessionResponse\"\x00\x12J\n\rDeleteSession\x12\x1a.core.DeleteSessionRequest\x1a\x1b.core.DeleteSessionResponse\"\x00\x12\x44\n\x0bGetSessions\x12\x18.core.GetSessionsRequest\x1a\x19.core.GetSessionsResponse\"\x00\x12\x41\n\nGetSession\x12\x17.core.GetSessionRequest\x1a\x18.core.GetSessionResponse\"\x00\x12V\n\x11GetSessionOptions\x12\x1e.core.GetSessionOptionsRequest\x1a\x1f.core.GetSessionOptionsResponse\"\x00\x12V\n\x11SetSessionOptions\x12\x1e.core.SetSessionOptionsRequest\x1a\x1f.core.SetSessionOptionsResponse\"\x00\x12Y\n\x12GetSessionLocation\x12\x1f.core.GetSessionLocationRequest\x1a .core.GetSessionLocationResponse\"\x00\x12Y\n\x12SetSessionLocation\x12\x1f.core.SetSessionLocationRequest\x1a .core.SetSessionLocationResponse\"\x00\x12P\n\x0fSetSessionState\x12\x1c.core.SetSessionStateRequest\x1a\x1d.core.SetSessionStateResponse\"\x00\x12:\n\nNodeEvents\x12\x17.core.NodeEventsRequest\x1a\x0f.core.NodeEvent\"\x00\x30\x01\x12:\n\nLinkEvents\x12\x17.core.LinkEventsRequest\x1a\x0f.core.LinkEvent\"\x00\x30\x01\x12\x43\n\rSessionEvents\x12\x1a.core.SessionEventsRequest\x1a\x12.core.SessionEvent\"\x00\x30\x01\x12@\n\x0c\x43onfigEvents\x12\x19.core.ConfigEventsRequest\x1a\x11.core.ConfigEvent\"\x00\x30\x01\x12I\n\x0f\x45xceptionEvents\x12\x1c.core.ExceptionEventsRequest\x1a\x14.core.ExceptionEvent\"\x00\x30\x01\x12:\n\nFileEvents\x12\x17.core.FileEventsRequest\x1a\x0f.core.FileEvent\"\x00\x30\x01\x12\x38\n\x07\x41\x64\x64Node\x12\x14.core.AddNodeRequest\x1a\x15.core.AddNodeResponse\"\x00\x12\x38\n\x07GetNode\x12\x14.core.GetNodeRequest\x1a\x15.core.GetNodeResponse\"\x00\x12;\n\x08\x45\x64itNode\x12\x15.core.EditNodeRequest\x1a\x16.core.EditNodeResponse\"\x00\x12\x41\n\nDeleteNode\x12\x17.core.DeleteNodeRequest\x1a\x18.core.DeleteNodeResponse\"\x00\x12G\n\x0cGetNodeLinks\x12\x19.core.GetNodeLinksRequest\x1a\x1a.core.GetNodeLinksResponse\"\x00\x12\x38\n\x07\x41\x64\x64Link\x12\x14.core.AddLinkRequest\x1a\x15.core.AddLinkResponse\"\x00\x12;\n\x08\x45\x64itLink\x12\x15.core.EditLinkRequest\x1a\x16.core.EditLinkResponse\"\x00\x12\x41\n\nDeleteLink\x12\x17.core.DeleteLinkRequest\x1a\x18.core.DeleteLinkResponse\"\x00\x12;\n\x08GetHooks\x12\x15.core.GetHooksRequest\x1a\x16.core.GetHooksResponse\"\x00\x12\x38\n\x07\x41\x64\x64Hook\x12\x14.core.AddHookRequest\x1a\x15.core.AddHookResponse\"\x00\x12Y\n\x12GetMobilityConfigs\x12\x1f.core.GetMobilityConfigsRequest\x1a .core.GetMobilityConfigsResponse\"\x00\x12V\n\x11GetMobilityConfig\x12\x1e.core.GetMobilityConfigRequest\x1a\x1f.core.GetMobilityConfigResponse\"\x00\x12V\n\x11SetMobilityConfig\x12\x1e.core.SetMobilityConfigRequest\x1a\x1f.core.SetMobilityConfigResponse\"\x00\x12M\n\x0eMobilityAction\x12\x1b.core.MobilityActionRequest\x1a\x1c.core.MobilityActionResponse\"\x00\x12\x44\n\x0bGetServices\x12\x18.core.GetServicesRequest\x1a\x19.core.GetServicesResponse\"\x00\x12Y\n\x12GetServiceDefaults\x12\x1f.core.GetServiceDefaultsRequest\x1a .core.GetServiceDefaultsResponse\"\x00\x12Y\n\x12SetServiceDefaults\x12\x1f.core.SetServiceDefaultsRequest\x1a .core.SetServiceDefaultsResponse\"\x00\x12M\n\x0eGetNodeService\x12\x1b.core.GetNodeServiceRequest\x1a\x1c.core.GetNodeServiceResponse\"\x00\x12Y\n\x12GetNodeServiceFile\x12\x1f.core.GetNodeServiceFileRequest\x1a .core.GetNodeServiceFileResponse\"\x00\x12M\n\x0eSetNodeService\x12\x1b.core.SetNodeServiceRequest\x1a\x1c.core.SetNodeServiceResponse\"\x00\x12Y\n\x12SetNodeServiceFile\x12\x1f.core.SetNodeServiceFileRequest\x1a .core.SetNodeServiceFileResponse\"\x00\x12J\n\rServiceAction\x12\x1a.core.ServiceActionRequest\x1a\x1b.core.ServiceActionResponse\"\x00\x12J\n\rGetWlanConfig\x12\x1a.core.GetWlanConfigRequest\x1a\x1b.core.GetWlanConfigResponse\"\x00\x12J\n\rSetWlanConfig\x12\x1a.core.SetWlanConfigRequest\x1a\x1b.core.SetWlanConfigResponse\"\x00\x12M\n\x0eGetEmaneConfig\x12\x1b.core.GetEmaneConfigRequest\x1a\x1c.core.GetEmaneConfigResponse\"\x00\x12M\n\x0eSetEmaneConfig\x12\x1b.core.SetEmaneConfigRequest\x1a\x1c.core.SetEmaneConfigResponse\"\x00\x12M\n\x0eGetEmaneModels\x12\x1b.core.GetEmaneModelsRequest\x1a\x1c.core.GetEmaneModelsResponse\"\x00\x12\\\n\x13GetEmaneModelConfig\x12 .core.GetEmaneModelConfigRequest\x1a!.core.GetEmaneModelConfigResponse\"\x00\x12\\\n\x13SetEmaneModelConfig\x12 .core.SetEmaneModelConfigRequest\x1a!.core.SetEmaneModelConfigResponse\"\x00\x12_\n\x14GetEmaneModelConfigs\x12!.core.GetEmaneModelConfigsRequest\x1a\".core.GetEmaneModelConfigsResponse\"\x00\x12\x38\n\x07SaveXml\x12\x14.core.SaveXmlRequest\x1a\x15.core.SaveXmlResponse\"\x00\x12\x38\n\x07OpenXml\x12\x14.core.OpenXmlRequest\x1a\x15.core.OpenXmlResponse\"\x00\x62\x06proto3') -) - -_MESSAGETYPE = _descriptor.EnumDescriptor( - name='MessageType', - full_name='core.MessageType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='MESSAGE_NONE', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MESSAGE_ADD', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MESSAGE_DELETE', index=2, number=2, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MESSAGE_CRI', index=3, number=4, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MESSAGE_LOCAL', index=4, number=8, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MESSAGE_STRING', index=5, number=16, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MESSAGE_TEXT', index=6, number=32, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MESSAGE_TTY', index=7, number=64, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=8208, - serialized_end=8367, -) -_sym_db.RegisterEnumDescriptor(_MESSAGETYPE) - -MessageType = enum_type_wrapper.EnumTypeWrapper(_MESSAGETYPE) -_LINKTYPE = _descriptor.EnumDescriptor( - name='LinkType', - full_name='core.LinkType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='LINK_WIRELESS', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='LINK_WIRED', index=1, number=1, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=8369, - serialized_end=8414, -) -_sym_db.RegisterEnumDescriptor(_LINKTYPE) - -LinkType = enum_type_wrapper.EnumTypeWrapper(_LINKTYPE) -_SESSIONSTATE = _descriptor.EnumDescriptor( - name='SessionState', - full_name='core.SessionState', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='STATE_NONE', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STATE_DEFINITION', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STATE_CONFIGURATION', index=2, number=2, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STATE_INSTANTIATION', index=3, number=3, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STATE_RUNTIME', index=4, number=4, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STATE_DATACOLLECT', index=5, number=5, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='STATE_SHUTDOWN', index=6, number=6, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=8417, - serialized_end=8581, -) -_sym_db.RegisterEnumDescriptor(_SESSIONSTATE) - -SessionState = enum_type_wrapper.EnumTypeWrapper(_SESSIONSTATE) -_NODETYPE = _descriptor.EnumDescriptor( - name='NodeType', - full_name='core.NodeType', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='NODE_DEFAULT', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_PHYSICAL', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_TBD', index=2, number=3, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_SWITCH', index=3, number=4, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_HUB', index=4, number=5, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_WIRELESS_LAN', index=5, number=6, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_RJ45', index=6, number=7, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_TUNNEL', index=7, number=8, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_KTUNNEL', index=8, number=9, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_EMANE', index=9, number=10, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_TAP_BRIDGE', index=10, number=11, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_PEER_TO_PEER', index=11, number=12, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_CONTROL_NET', index=12, number=13, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NODE_EMANE_NET', index=13, number=14, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=8584, - serialized_end=8851, -) -_sym_db.RegisterEnumDescriptor(_NODETYPE) - -NodeType = enum_type_wrapper.EnumTypeWrapper(_NODETYPE) -_SERVICEVALIDATIONMODE = _descriptor.EnumDescriptor( - name='ServiceValidationMode', - full_name='core.ServiceValidationMode', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='VALIDATION_BLOCKING', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='VALIDATION_NON_BLOCKING', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='VALIDATION_TIMER', index=2, number=2, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=8853, - serialized_end=8952, -) -_sym_db.RegisterEnumDescriptor(_SERVICEVALIDATIONMODE) - -ServiceValidationMode = enum_type_wrapper.EnumTypeWrapper(_SERVICEVALIDATIONMODE) -_SERVICEACTION = _descriptor.EnumDescriptor( - name='ServiceAction', - full_name='core.ServiceAction', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='SERVICE_START', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='SERVICE_STOP', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='SERVICE_RESTART', index=2, number=2, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='SERVICE_VALIDATE', index=3, number=3, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=8954, - serialized_end=9049, -) -_sym_db.RegisterEnumDescriptor(_SERVICEACTION) - -ServiceAction = enum_type_wrapper.EnumTypeWrapper(_SERVICEACTION) -_MOBILITYACTION = _descriptor.EnumDescriptor( - name='MobilityAction', - full_name='core.MobilityAction', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='MOBILITY_START', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MOBILITY_PAUSE', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='MOBILITY_STOP', index=2, number=2, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=9051, - serialized_end=9126, -) -_sym_db.RegisterEnumDescriptor(_MOBILITYACTION) - -MobilityAction = enum_type_wrapper.EnumTypeWrapper(_MOBILITYACTION) -_EXCEPTIONLEVEL = _descriptor.EnumDescriptor( - name='ExceptionLevel', - full_name='core.ExceptionLevel', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='EXCEPTION_DEFAULT', index=0, number=0, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='EXCEPTION_FATAL', index=1, number=1, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='EXCEPTION_ERROR', index=2, number=2, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='EXCEPTION_WARNING', index=3, number=3, - serialized_options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='EXCEPTION_NOTICE', index=4, number=4, - serialized_options=None, - type=None), - ], - containing_type=None, - serialized_options=None, - serialized_start=9128, - serialized_end=9254, -) -_sym_db.RegisterEnumDescriptor(_EXCEPTIONLEVEL) - -ExceptionLevel = enum_type_wrapper.EnumTypeWrapper(_EXCEPTIONLEVEL) -MESSAGE_NONE = 0 -MESSAGE_ADD = 1 -MESSAGE_DELETE = 2 -MESSAGE_CRI = 4 -MESSAGE_LOCAL = 8 -MESSAGE_STRING = 16 -MESSAGE_TEXT = 32 -MESSAGE_TTY = 64 -LINK_WIRELESS = 0 -LINK_WIRED = 1 -STATE_NONE = 0 -STATE_DEFINITION = 1 -STATE_CONFIGURATION = 2 -STATE_INSTANTIATION = 3 -STATE_RUNTIME = 4 -STATE_DATACOLLECT = 5 -STATE_SHUTDOWN = 6 -NODE_DEFAULT = 0 -NODE_PHYSICAL = 1 -NODE_TBD = 3 -NODE_SWITCH = 4 -NODE_HUB = 5 -NODE_WIRELESS_LAN = 6 -NODE_RJ45 = 7 -NODE_TUNNEL = 8 -NODE_KTUNNEL = 9 -NODE_EMANE = 10 -NODE_TAP_BRIDGE = 11 -NODE_PEER_TO_PEER = 12 -NODE_CONTROL_NET = 13 -NODE_EMANE_NET = 14 -VALIDATION_BLOCKING = 0 -VALIDATION_NON_BLOCKING = 1 -VALIDATION_TIMER = 2 -SERVICE_START = 0 -SERVICE_STOP = 1 -SERVICE_RESTART = 2 -SERVICE_VALIDATE = 3 -MOBILITY_START = 0 -MOBILITY_PAUSE = 1 -MOBILITY_STOP = 2 -EXCEPTION_DEFAULT = 0 -EXCEPTION_FATAL = 1 -EXCEPTION_ERROR = 2 -EXCEPTION_WARNING = 3 -EXCEPTION_NOTICE = 4 - - - -_CREATESESSIONREQUEST = _descriptor.Descriptor( - name='CreateSessionRequest', - full_name='core.CreateSessionRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.CreateSessionRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=34, - serialized_end=68, -) - - -_CREATESESSIONRESPONSE = _descriptor.Descriptor( - name='CreateSessionResponse', - full_name='core.CreateSessionResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.CreateSessionResponse.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='core.CreateSessionResponse.state', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=70, - serialized_end=140, -) - - -_DELETESESSIONREQUEST = _descriptor.Descriptor( - name='DeleteSessionRequest', - full_name='core.DeleteSessionRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.DeleteSessionRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=142, - serialized_end=176, -) - - -_DELETESESSIONRESPONSE = _descriptor.Descriptor( - name='DeleteSessionResponse', - full_name='core.DeleteSessionResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.DeleteSessionResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=178, - serialized_end=217, -) - - -_GETSESSIONSREQUEST = _descriptor.Descriptor( - name='GetSessionsRequest', - full_name='core.GetSessionsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=219, - serialized_end=239, -) - - -_GETSESSIONSRESPONSE = _descriptor.Descriptor( - name='GetSessionsResponse', - full_name='core.GetSessionsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='sessions', full_name='core.GetSessionsResponse.sessions', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=241, - serialized_end=302, -) - - -_GETSESSIONREQUEST = _descriptor.Descriptor( - name='GetSessionRequest', - full_name='core.GetSessionRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.GetSessionRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=304, - serialized_end=335, -) - - -_GETSESSIONRESPONSE = _descriptor.Descriptor( - name='GetSessionResponse', - full_name='core.GetSessionResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetSessionResponse.session', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=337, - serialized_end=389, -) - - -_GETSESSIONOPTIONSREQUEST = _descriptor.Descriptor( - name='GetSessionOptionsRequest', - full_name='core.GetSessionOptionsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.GetSessionOptionsRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=391, - serialized_end=429, -) - - -_GETSESSIONOPTIONSRESPONSE = _descriptor.Descriptor( - name='GetSessionOptionsResponse', - full_name='core.GetSessionOptionsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='groups', full_name='core.GetSessionOptionsResponse.groups', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=431, - serialized_end=493, -) - - -_SETSESSIONOPTIONSREQUEST_CONFIGENTRY = _descriptor.Descriptor( - name='ConfigEntry', - full_name='core.SetSessionOptionsRequest.ConfigEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='core.SetSessionOptionsRequest.ConfigEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='core.SetSessionOptionsRequest.ConfigEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=596, - serialized_end=641, -) - -_SETSESSIONOPTIONSREQUEST = _descriptor.Descriptor( - name='SetSessionOptionsRequest', - full_name='core.SetSessionOptionsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.SetSessionOptionsRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='config', full_name='core.SetSessionOptionsRequest.config', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_SETSESSIONOPTIONSREQUEST_CONFIGENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=496, - serialized_end=641, -) - - -_SETSESSIONOPTIONSRESPONSE = _descriptor.Descriptor( - name='SetSessionOptionsResponse', - full_name='core.SetSessionOptionsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetSessionOptionsResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=643, - serialized_end=686, -) - - -_GETSESSIONLOCATIONREQUEST = _descriptor.Descriptor( - name='GetSessionLocationRequest', - full_name='core.GetSessionLocationRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.GetSessionLocationRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=688, - serialized_end=727, -) - - -_GETSESSIONLOCATIONRESPONSE = _descriptor.Descriptor( - name='GetSessionLocationResponse', - full_name='core.GetSessionLocationResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='position', full_name='core.GetSessionLocationResponse.position', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='scale', full_name='core.GetSessionLocationResponse.scale', index=1, - number=2, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=729, - serialized_end=806, -) - - -_SETSESSIONLOCATIONREQUEST = _descriptor.Descriptor( - name='SetSessionLocationRequest', - full_name='core.SetSessionLocationRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.SetSessionLocationRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='position', full_name='core.SetSessionLocationRequest.position', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='scale', full_name='core.SetSessionLocationRequest.scale', index=2, - number=3, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=808, - serialized_end=896, -) - - -_SETSESSIONLOCATIONRESPONSE = _descriptor.Descriptor( - name='SetSessionLocationResponse', - full_name='core.SetSessionLocationResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetSessionLocationResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=898, - serialized_end=942, -) - - -_SETSESSIONSTATEREQUEST = _descriptor.Descriptor( - name='SetSessionStateRequest', - full_name='core.SetSessionStateRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.SetSessionStateRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='core.SetSessionStateRequest.state', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=944, - serialized_end=1015, -) - - -_SETSESSIONSTATERESPONSE = _descriptor.Descriptor( - name='SetSessionStateResponse', - full_name='core.SetSessionStateResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetSessionStateResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1017, - serialized_end=1058, -) - - -_NODEEVENTSREQUEST = _descriptor.Descriptor( - name='NodeEventsRequest', - full_name='core.NodeEventsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.NodeEventsRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1060, - serialized_end=1091, -) - - -_NODEEVENT = _descriptor.Descriptor( - name='NodeEvent', - full_name='core.NodeEvent', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='node', full_name='core.NodeEvent.node', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1093, - serialized_end=1130, -) - - -_LINKEVENTSREQUEST = _descriptor.Descriptor( - name='LinkEventsRequest', - full_name='core.LinkEventsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.LinkEventsRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1132, - serialized_end=1163, -) - - -_LINKEVENT = _descriptor.Descriptor( - name='LinkEvent', - full_name='core.LinkEvent', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='message_type', full_name='core.LinkEvent.message_type', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='link', full_name='core.LinkEvent.link', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1165, - serialized_end=1243, -) - - -_SESSIONEVENTSREQUEST = _descriptor.Descriptor( - name='SessionEventsRequest', - full_name='core.SessionEventsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.SessionEventsRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1245, - serialized_end=1279, -) - - -_SESSIONEVENT = _descriptor.Descriptor( - name='SessionEvent', - full_name='core.SessionEvent', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='node', full_name='core.SessionEvent.node', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='event', full_name='core.SessionEvent.event', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='core.SessionEvent.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data', full_name='core.SessionEvent.data', index=3, - number=4, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='time', full_name='core.SessionEvent.time', index=4, - number=5, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='session', full_name='core.SessionEvent.session', index=5, - number=6, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1281, - serialized_end=1383, -) - - -_CONFIGEVENTSREQUEST = _descriptor.Descriptor( - name='ConfigEventsRequest', - full_name='core.ConfigEventsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.ConfigEventsRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1385, - serialized_end=1418, -) - - -_CONFIGEVENT = _descriptor.Descriptor( - name='ConfigEvent', - full_name='core.ConfigEvent', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='message_type', full_name='core.ConfigEvent.message_type', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='node', full_name='core.ConfigEvent.node', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='object', full_name='core.ConfigEvent.object', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='core.ConfigEvent.type', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data_types', full_name='core.ConfigEvent.data_types', index=4, - number=5, type=5, cpp_type=1, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data_values', full_name='core.ConfigEvent.data_values', index=5, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='captions', full_name='core.ConfigEvent.captions', index=6, - number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='bitmap', full_name='core.ConfigEvent.bitmap', index=7, - number=8, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='possible_values', full_name='core.ConfigEvent.possible_values', index=8, - number=9, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='groups', full_name='core.ConfigEvent.groups', index=9, - number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='session', full_name='core.ConfigEvent.session', index=10, - number=11, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface', full_name='core.ConfigEvent.interface', index=11, - number=12, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='network_id', full_name='core.ConfigEvent.network_id', index=12, - number=13, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='opaque', full_name='core.ConfigEvent.opaque', index=13, - number=14, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1421, - serialized_end=1707, -) - - -_EXCEPTIONEVENTSREQUEST = _descriptor.Descriptor( - name='ExceptionEventsRequest', - full_name='core.ExceptionEventsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.ExceptionEventsRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1709, - serialized_end=1745, -) - - -_EXCEPTIONEVENT = _descriptor.Descriptor( - name='ExceptionEvent', - full_name='core.ExceptionEvent', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='node', full_name='core.ExceptionEvent.node', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='session', full_name='core.ExceptionEvent.session', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='level', full_name='core.ExceptionEvent.level', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='source', full_name='core.ExceptionEvent.source', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='date', full_name='core.ExceptionEvent.date', index=4, - number=5, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='text', full_name='core.ExceptionEvent.text', index=5, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='opaque', full_name='core.ExceptionEvent.opaque', index=6, - number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1748, - serialized_end=1892, -) - - -_FILEEVENTSREQUEST = _descriptor.Descriptor( - name='FileEventsRequest', - full_name='core.FileEventsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.FileEventsRequest.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1894, - serialized_end=1925, -) - - -_FILEEVENT = _descriptor.Descriptor( - name='FileEvent', - full_name='core.FileEvent', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='message_type', full_name='core.FileEvent.message_type', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='node', full_name='core.FileEvent.node', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='core.FileEvent.name', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='mode', full_name='core.FileEvent.mode', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='number', full_name='core.FileEvent.number', index=4, - number=5, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='core.FileEvent.type', index=5, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='source', full_name='core.FileEvent.source', index=6, - number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='session', full_name='core.FileEvent.session', index=7, - number=8, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data', full_name='core.FileEvent.data', index=8, - number=9, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='compressed_data', full_name='core.FileEvent.compressed_data', index=9, - number=10, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1928, - serialized_end=2124, -) - - -_ADDNODEREQUEST = _descriptor.Descriptor( - name='AddNodeRequest', - full_name='core.AddNodeRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.AddNodeRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='node', full_name='core.AddNodeRequest.node', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2126, - serialized_end=2185, -) - - -_ADDNODERESPONSE = _descriptor.Descriptor( - name='AddNodeResponse', - full_name='core.AddNodeResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.AddNodeResponse.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2187, - serialized_end=2216, -) - - -_GETNODEREQUEST = _descriptor.Descriptor( - name='GetNodeRequest', - full_name='core.GetNodeRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetNodeRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.GetNodeRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2218, - serialized_end=2263, -) - - -_GETNODERESPONSE = _descriptor.Descriptor( - name='GetNodeResponse', - full_name='core.GetNodeResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='node', full_name='core.GetNodeResponse.node', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interfaces', full_name='core.GetNodeResponse.interfaces', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2265, - serialized_end=2345, -) - - -_EDITNODEREQUEST = _descriptor.Descriptor( - name='EditNodeRequest', - full_name='core.EditNodeRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.EditNodeRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.EditNodeRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='position', full_name='core.EditNodeRequest.position', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2347, - serialized_end=2427, -) - - -_EDITNODERESPONSE = _descriptor.Descriptor( - name='EditNodeResponse', - full_name='core.EditNodeResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.EditNodeResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2429, - serialized_end=2463, -) - - -_DELETENODEREQUEST = _descriptor.Descriptor( - name='DeleteNodeRequest', - full_name='core.DeleteNodeRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.DeleteNodeRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.DeleteNodeRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2465, - serialized_end=2513, -) - - -_DELETENODERESPONSE = _descriptor.Descriptor( - name='DeleteNodeResponse', - full_name='core.DeleteNodeResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.DeleteNodeResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2515, - serialized_end=2551, -) - - -_GETNODELINKSREQUEST = _descriptor.Descriptor( - name='GetNodeLinksRequest', - full_name='core.GetNodeLinksRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetNodeLinksRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.GetNodeLinksRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2553, - serialized_end=2603, -) - - -_GETNODELINKSRESPONSE = _descriptor.Descriptor( - name='GetNodeLinksResponse', - full_name='core.GetNodeLinksResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='links', full_name='core.GetNodeLinksResponse.links', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2605, - serialized_end=2654, -) - - -_ADDLINKREQUEST = _descriptor.Descriptor( - name='AddLinkRequest', - full_name='core.AddLinkRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.AddLinkRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='link', full_name='core.AddLinkRequest.link', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2656, - serialized_end=2715, -) - - -_ADDLINKRESPONSE = _descriptor.Descriptor( - name='AddLinkResponse', - full_name='core.AddLinkResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.AddLinkResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2717, - serialized_end=2750, -) - - -_EDITLINKREQUEST = _descriptor.Descriptor( - name='EditLinkRequest', - full_name='core.EditLinkRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.EditLinkRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='node_one', full_name='core.EditLinkRequest.node_one', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='node_two', full_name='core.EditLinkRequest.node_two', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface_one', full_name='core.EditLinkRequest.interface_one', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface_two', full_name='core.EditLinkRequest.interface_two', index=4, - number=5, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='options', full_name='core.EditLinkRequest.options', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2753, - serialized_end=2905, -) - - -_EDITLINKRESPONSE = _descriptor.Descriptor( - name='EditLinkResponse', - full_name='core.EditLinkResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.EditLinkResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2907, - serialized_end=2941, -) - - -_DELETELINKREQUEST = _descriptor.Descriptor( - name='DeleteLinkRequest', - full_name='core.DeleteLinkRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.DeleteLinkRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='node_one', full_name='core.DeleteLinkRequest.node_one', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='node_two', full_name='core.DeleteLinkRequest.node_two', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface_one', full_name='core.DeleteLinkRequest.interface_one', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface_two', full_name='core.DeleteLinkRequest.interface_two', index=4, - number=5, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=2943, - serialized_end=3061, -) - - -_DELETELINKRESPONSE = _descriptor.Descriptor( - name='DeleteLinkResponse', - full_name='core.DeleteLinkResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.DeleteLinkResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3063, - serialized_end=3099, -) - - -_GETHOOKSREQUEST = _descriptor.Descriptor( - name='GetHooksRequest', - full_name='core.GetHooksRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetHooksRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3101, - serialized_end=3135, -) - - -_GETHOOKSRESPONSE = _descriptor.Descriptor( - name='GetHooksResponse', - full_name='core.GetHooksResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='hooks', full_name='core.GetHooksResponse.hooks', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3137, - serialized_end=3182, -) - - -_ADDHOOKREQUEST = _descriptor.Descriptor( - name='AddHookRequest', - full_name='core.AddHookRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.AddHookRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='hook', full_name='core.AddHookRequest.hook', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3184, - serialized_end=3243, -) - - -_ADDHOOKRESPONSE = _descriptor.Descriptor( - name='AddHookResponse', - full_name='core.AddHookResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.AddHookResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3245, - serialized_end=3278, -) - - -_GETMOBILITYCONFIGSREQUEST = _descriptor.Descriptor( - name='GetMobilityConfigsRequest', - full_name='core.GetMobilityConfigsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetMobilityConfigsRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3280, - serialized_end=3324, -) - - -_GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG = _descriptor.Descriptor( - name='MobilityConfig', - full_name='core.GetMobilityConfigsResponse.MobilityConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='groups', full_name='core.GetMobilityConfigsResponse.MobilityConfig.groups', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3421, - serialized_end=3472, -) - -_GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY = _descriptor.Descriptor( - name='ConfigsEntry', - full_name='core.GetMobilityConfigsResponse.ConfigsEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='core.GetMobilityConfigsResponse.ConfigsEntry.key', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='core.GetMobilityConfigsResponse.ConfigsEntry.value', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3474, - serialized_end=3569, -) - -_GETMOBILITYCONFIGSRESPONSE = _descriptor.Descriptor( - name='GetMobilityConfigsResponse', - full_name='core.GetMobilityConfigsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='configs', full_name='core.GetMobilityConfigsResponse.configs', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG, _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3327, - serialized_end=3569, -) - - -_GETMOBILITYCONFIGREQUEST = _descriptor.Descriptor( - name='GetMobilityConfigRequest', - full_name='core.GetMobilityConfigRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetMobilityConfigRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.GetMobilityConfigRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3571, - serialized_end=3626, -) - - -_GETMOBILITYCONFIGRESPONSE = _descriptor.Descriptor( - name='GetMobilityConfigResponse', - full_name='core.GetMobilityConfigResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='groups', full_name='core.GetMobilityConfigResponse.groups', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3628, - serialized_end=3690, -) - - -_SETMOBILITYCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( - name='ConfigEntry', - full_name='core.SetMobilityConfigRequest.ConfigEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='core.SetMobilityConfigRequest.ConfigEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='core.SetMobilityConfigRequest.ConfigEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=596, - serialized_end=641, -) - -_SETMOBILITYCONFIGREQUEST = _descriptor.Descriptor( - name='SetMobilityConfigRequest', - full_name='core.SetMobilityConfigRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.SetMobilityConfigRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.SetMobilityConfigRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='config', full_name='core.SetMobilityConfigRequest.config', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_SETMOBILITYCONFIGREQUEST_CONFIGENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3693, - serialized_end=3855, -) - - -_SETMOBILITYCONFIGRESPONSE = _descriptor.Descriptor( - name='SetMobilityConfigResponse', - full_name='core.SetMobilityConfigResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetMobilityConfigResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3857, - serialized_end=3900, -) - - -_MOBILITYACTIONREQUEST = _descriptor.Descriptor( - name='MobilityActionRequest', - full_name='core.MobilityActionRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.MobilityActionRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.MobilityActionRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='action', full_name='core.MobilityActionRequest.action', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3902, - serialized_end=3992, -) - - -_MOBILITYACTIONRESPONSE = _descriptor.Descriptor( - name='MobilityActionResponse', - full_name='core.MobilityActionResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.MobilityActionResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=3994, - serialized_end=4034, -) - - -_GETSERVICESREQUEST = _descriptor.Descriptor( - name='GetServicesRequest', - full_name='core.GetServicesRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4036, - serialized_end=4056, -) - - -_GETSERVICESRESPONSE = _descriptor.Descriptor( - name='GetServicesResponse', - full_name='core.GetServicesResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='services', full_name='core.GetServicesResponse.services', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4058, - serialized_end=4112, -) - - -_GETSERVICEDEFAULTSREQUEST = _descriptor.Descriptor( - name='GetServiceDefaultsRequest', - full_name='core.GetServiceDefaultsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetServiceDefaultsRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4114, - serialized_end=4158, -) - - -_GETSERVICEDEFAULTSRESPONSE = _descriptor.Descriptor( - name='GetServiceDefaultsResponse', - full_name='core.GetServiceDefaultsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='defaults', full_name='core.GetServiceDefaultsResponse.defaults', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4160, - serialized_end=4229, -) - - -_SETSERVICEDEFAULTSREQUEST = _descriptor.Descriptor( - name='SetServiceDefaultsRequest', - full_name='core.SetServiceDefaultsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.SetServiceDefaultsRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='defaults', full_name='core.SetServiceDefaultsRequest.defaults', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4231, - serialized_end=4316, -) - - -_SETSERVICEDEFAULTSRESPONSE = _descriptor.Descriptor( - name='SetServiceDefaultsResponse', - full_name='core.SetServiceDefaultsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetServiceDefaultsResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4318, - serialized_end=4362, -) - - -_GETNODESERVICEREQUEST = _descriptor.Descriptor( - name='GetNodeServiceRequest', - full_name='core.GetNodeServiceRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetNodeServiceRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.GetNodeServiceRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='service', full_name='core.GetNodeServiceRequest.service', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4364, - serialized_end=4433, -) - - -_GETNODESERVICERESPONSE = _descriptor.Descriptor( - name='GetNodeServiceResponse', - full_name='core.GetNodeServiceResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='service', full_name='core.GetNodeServiceResponse.service', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4435, - serialized_end=4499, -) - - -_GETNODESERVICEFILEREQUEST = _descriptor.Descriptor( - name='GetNodeServiceFileRequest', - full_name='core.GetNodeServiceFileRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetNodeServiceFileRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.GetNodeServiceFileRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='service', full_name='core.GetNodeServiceFileRequest.service', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='file', full_name='core.GetNodeServiceFileRequest.file', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4501, - serialized_end=4588, -) - - -_GETNODESERVICEFILERESPONSE = _descriptor.Descriptor( - name='GetNodeServiceFileResponse', - full_name='core.GetNodeServiceFileResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='data', full_name='core.GetNodeServiceFileResponse.data', index=0, - number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4590, - serialized_end=4632, -) - - -_SETNODESERVICEREQUEST = _descriptor.Descriptor( - name='SetNodeServiceRequest', - full_name='core.SetNodeServiceRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.SetNodeServiceRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.SetNodeServiceRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='service', full_name='core.SetNodeServiceRequest.service', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='startup', full_name='core.SetNodeServiceRequest.startup', index=3, - number=4, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='validate', full_name='core.SetNodeServiceRequest.validate', index=4, - number=5, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='shutdown', full_name='core.SetNodeServiceRequest.shutdown', index=5, - number=6, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4634, - serialized_end=4756, -) - - -_SETNODESERVICERESPONSE = _descriptor.Descriptor( - name='SetNodeServiceResponse', - full_name='core.SetNodeServiceResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetNodeServiceResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4758, - serialized_end=4798, -) - - -_SETNODESERVICEFILEREQUEST = _descriptor.Descriptor( - name='SetNodeServiceFileRequest', - full_name='core.SetNodeServiceFileRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.SetNodeServiceFileRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.SetNodeServiceFileRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='service', full_name='core.SetNodeServiceFileRequest.service', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='file', full_name='core.SetNodeServiceFileRequest.file', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data', full_name='core.SetNodeServiceFileRequest.data', index=4, - number=5, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4800, - serialized_end=4901, -) - - -_SETNODESERVICEFILERESPONSE = _descriptor.Descriptor( - name='SetNodeServiceFileResponse', - full_name='core.SetNodeServiceFileResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetNodeServiceFileResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4903, - serialized_end=4947, -) - - -_SERVICEACTIONREQUEST = _descriptor.Descriptor( - name='ServiceActionRequest', - full_name='core.ServiceActionRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.ServiceActionRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.ServiceActionRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='service', full_name='core.ServiceActionRequest.service', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='action', full_name='core.ServiceActionRequest.action', index=3, - number=4, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=4949, - serialized_end=5054, -) - - -_SERVICEACTIONRESPONSE = _descriptor.Descriptor( - name='ServiceActionResponse', - full_name='core.ServiceActionResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.ServiceActionResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5056, - serialized_end=5095, -) - - -_GETWLANCONFIGREQUEST = _descriptor.Descriptor( - name='GetWlanConfigRequest', - full_name='core.GetWlanConfigRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetWlanConfigRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.GetWlanConfigRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5097, - serialized_end=5148, -) - - -_GETWLANCONFIGRESPONSE = _descriptor.Descriptor( - name='GetWlanConfigResponse', - full_name='core.GetWlanConfigResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='groups', full_name='core.GetWlanConfigResponse.groups', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5150, - serialized_end=5208, -) - - -_SETWLANCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( - name='ConfigEntry', - full_name='core.SetWlanConfigRequest.ConfigEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='core.SetWlanConfigRequest.ConfigEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='core.SetWlanConfigRequest.ConfigEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=596, - serialized_end=641, -) - -_SETWLANCONFIGREQUEST = _descriptor.Descriptor( - name='SetWlanConfigRequest', - full_name='core.SetWlanConfigRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.SetWlanConfigRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.SetWlanConfigRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='config', full_name='core.SetWlanConfigRequest.config', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_SETWLANCONFIGREQUEST_CONFIGENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5211, - serialized_end=5365, -) - - -_SETWLANCONFIGRESPONSE = _descriptor.Descriptor( - name='SetWlanConfigResponse', - full_name='core.SetWlanConfigResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetWlanConfigResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5367, - serialized_end=5406, -) - - -_GETEMANECONFIGREQUEST = _descriptor.Descriptor( - name='GetEmaneConfigRequest', - full_name='core.GetEmaneConfigRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetEmaneConfigRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5408, - serialized_end=5448, -) - - -_GETEMANECONFIGRESPONSE = _descriptor.Descriptor( - name='GetEmaneConfigResponse', - full_name='core.GetEmaneConfigResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='groups', full_name='core.GetEmaneConfigResponse.groups', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5450, - serialized_end=5509, -) - - -_SETEMANECONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( - name='ConfigEntry', - full_name='core.SetEmaneConfigRequest.ConfigEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='core.SetEmaneConfigRequest.ConfigEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='core.SetEmaneConfigRequest.ConfigEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=596, - serialized_end=641, -) - -_SETEMANECONFIGREQUEST = _descriptor.Descriptor( - name='SetEmaneConfigRequest', - full_name='core.SetEmaneConfigRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.SetEmaneConfigRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='config', full_name='core.SetEmaneConfigRequest.config', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_SETEMANECONFIGREQUEST_CONFIGENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5512, - serialized_end=5656, -) - - -_SETEMANECONFIGRESPONSE = _descriptor.Descriptor( - name='SetEmaneConfigResponse', - full_name='core.SetEmaneConfigResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetEmaneConfigResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5658, - serialized_end=5698, -) - - -_GETEMANEMODELSREQUEST = _descriptor.Descriptor( - name='GetEmaneModelsRequest', - full_name='core.GetEmaneModelsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetEmaneModelsRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5700, - serialized_end=5740, -) - - -_GETEMANEMODELSRESPONSE = _descriptor.Descriptor( - name='GetEmaneModelsResponse', - full_name='core.GetEmaneModelsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='models', full_name='core.GetEmaneModelsResponse.models', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5742, - serialized_end=5782, -) - - -_GETEMANEMODELCONFIGREQUEST = _descriptor.Descriptor( - name='GetEmaneModelConfigRequest', - full_name='core.GetEmaneModelConfigRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetEmaneModelConfigRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.GetEmaneModelConfigRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface', full_name='core.GetEmaneModelConfigRequest.interface', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='model', full_name='core.GetEmaneModelConfigRequest.model', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5784, - serialized_end=5875, -) - - -_GETEMANEMODELCONFIGRESPONSE = _descriptor.Descriptor( - name='GetEmaneModelConfigResponse', - full_name='core.GetEmaneModelConfigResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='groups', full_name='core.GetEmaneModelConfigResponse.groups', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5877, - serialized_end=5941, -) - - -_SETEMANEMODELCONFIGREQUEST_CONFIGENTRY = _descriptor.Descriptor( - name='ConfigEntry', - full_name='core.SetEmaneModelConfigRequest.ConfigEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='core.SetEmaneModelConfigRequest.ConfigEntry.key', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='core.SetEmaneModelConfigRequest.ConfigEntry.value', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=596, - serialized_end=641, -) - -_SETEMANEMODELCONFIGREQUEST = _descriptor.Descriptor( - name='SetEmaneModelConfigRequest', - full_name='core.SetEmaneModelConfigRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.SetEmaneModelConfigRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='id', full_name='core.SetEmaneModelConfigRequest.id', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface', full_name='core.SetEmaneModelConfigRequest.interface', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='model', full_name='core.SetEmaneModelConfigRequest.model', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='config', full_name='core.SetEmaneModelConfigRequest.config', index=4, - number=5, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_SETEMANEMODELCONFIGREQUEST_CONFIGENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=5944, - serialized_end=6144, -) - - -_SETEMANEMODELCONFIGRESPONSE = _descriptor.Descriptor( - name='SetEmaneModelConfigResponse', - full_name='core.SetEmaneModelConfigResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.SetEmaneModelConfigResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6146, - serialized_end=6191, -) - - -_GETEMANEMODELCONFIGSREQUEST = _descriptor.Descriptor( - name='GetEmaneModelConfigsRequest', - full_name='core.GetEmaneModelConfigsRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.GetEmaneModelConfigsRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6193, - serialized_end=6239, -) - - -_GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG = _descriptor.Descriptor( - name='ModelConfig', - full_name='core.GetEmaneModelConfigsResponse.ModelConfig', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='model', full_name='core.GetEmaneModelConfigsResponse.ModelConfig.model', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='groups', full_name='core.GetEmaneModelConfigsResponse.ModelConfig.groups', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6340, - serialized_end=6403, -) - -_GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY = _descriptor.Descriptor( - name='ConfigsEntry', - full_name='core.GetEmaneModelConfigsResponse.ConfigsEntry', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='key', full_name='core.GetEmaneModelConfigsResponse.ConfigsEntry.key', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='core.GetEmaneModelConfigsResponse.ConfigsEntry.value', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=_b('8\001'), - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6405, - serialized_end=6499, -) - -_GETEMANEMODELCONFIGSRESPONSE = _descriptor.Descriptor( - name='GetEmaneModelConfigsResponse', - full_name='core.GetEmaneModelConfigsResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='configs', full_name='core.GetEmaneModelConfigsResponse.configs', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[_GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG, _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY, ], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6242, - serialized_end=6499, -) - - -_SAVEXMLREQUEST = _descriptor.Descriptor( - name='SaveXmlRequest', - full_name='core.SaveXmlRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='session', full_name='core.SaveXmlRequest.session', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6501, - serialized_end=6534, -) - - -_SAVEXMLRESPONSE = _descriptor.Descriptor( - name='SaveXmlResponse', - full_name='core.SaveXmlResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='data', full_name='core.SaveXmlResponse.data', index=0, - number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6536, - serialized_end=6567, -) - - -_OPENXMLREQUEST = _descriptor.Descriptor( - name='OpenXmlRequest', - full_name='core.OpenXmlRequest', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='data', full_name='core.OpenXmlRequest.data', index=0, - number=1, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6569, - serialized_end=6599, -) - - -_OPENXMLRESPONSE = _descriptor.Descriptor( - name='OpenXmlResponse', - full_name='core.OpenXmlResponse', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='result', full_name='core.OpenXmlResponse.result', index=0, - number=1, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='session', full_name='core.OpenXmlResponse.session', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6601, - serialized_end=6651, -) - - -_HOOK = _descriptor.Descriptor( - name='Hook', - full_name='core.Hook', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='state', full_name='core.Hook.state', index=0, - number=1, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='file', full_name='core.Hook.file', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='data', full_name='core.Hook.data', index=2, - number=3, type=12, cpp_type=9, label=1, - has_default_value=False, default_value=_b(""), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6653, - serialized_end=6722, -) - - -_SERVICEDEFAULTS = _descriptor.Descriptor( - name='ServiceDefaults', - full_name='core.ServiceDefaults', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='node_type', full_name='core.ServiceDefaults.node_type', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='services', full_name='core.ServiceDefaults.services', index=1, - number=2, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6724, - serialized_end=6778, -) - - -_SERVICE = _descriptor.Descriptor( - name='Service', - full_name='core.Service', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='group', full_name='core.Service.group', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='core.Service.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6780, - serialized_end=6818, -) - - -_NODESERVICEDATA = _descriptor.Descriptor( - name='NodeServiceData', - full_name='core.NodeServiceData', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='executables', full_name='core.NodeServiceData.executables', index=0, - number=1, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='dependencies', full_name='core.NodeServiceData.dependencies', index=1, - number=2, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='dirs', full_name='core.NodeServiceData.dirs', index=2, - number=3, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='configs', full_name='core.NodeServiceData.configs', index=3, - number=4, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='startup', full_name='core.NodeServiceData.startup', index=4, - number=5, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='validate', full_name='core.NodeServiceData.validate', index=5, - number=6, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='validation_mode', full_name='core.NodeServiceData.validation_mode', index=6, - number=7, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='validation_timer', full_name='core.NodeServiceData.validation_timer', index=7, - number=8, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='shutdown', full_name='core.NodeServiceData.shutdown', index=8, - number=9, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='meta', full_name='core.NodeServiceData.meta', index=9, - number=10, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=6821, - serialized_end=7059, -) - - -_CONFIGGROUP = _descriptor.Descriptor( - name='ConfigGroup', - full_name='core.ConfigGroup', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='core.ConfigGroup.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='options', full_name='core.ConfigGroup.options', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=7061, - serialized_end=7125, -) - - -_CONFIGOPTION = _descriptor.Descriptor( - name='ConfigOption', - full_name='core.ConfigOption', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='label', full_name='core.ConfigOption.label', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='core.ConfigOption.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='value', full_name='core.ConfigOption.value', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='core.ConfigOption.type', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='select', full_name='core.ConfigOption.select', index=4, - number=5, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=7127, - serialized_end=7215, -) - - -_SESSION = _descriptor.Descriptor( - name='Session', - full_name='core.Session', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.Session.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='core.Session.state', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='nodes', full_name='core.Session.nodes', index=2, - number=3, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='links', full_name='core.Session.links', index=3, - number=4, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=7217, - serialized_end=7327, -) - - -_SESSIONSUMMARY = _descriptor.Descriptor( - name='SessionSummary', - full_name='core.SessionSummary', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.SessionSummary.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='state', full_name='core.SessionSummary.state', index=1, - number=2, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='nodes', full_name='core.SessionSummary.nodes', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=7329, - serialized_end=7407, -) - - -_NODE = _descriptor.Descriptor( - name='Node', - full_name='core.Node', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.Node.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='core.Node.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='core.Node.type', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='model', full_name='core.Node.model', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='position', full_name='core.Node.position', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='services', full_name='core.Node.services', index=5, - number=6, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='emane', full_name='core.Node.emane', index=6, - number=7, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='icon', full_name='core.Node.icon', index=7, - number=8, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='opaque', full_name='core.Node.opaque', index=8, - number=9, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=7410, - serialized_end=7584, -) - - -_LINK = _descriptor.Descriptor( - name='Link', - full_name='core.Link', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='node_one', full_name='core.Link.node_one', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='node_two', full_name='core.Link.node_two', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='type', full_name='core.Link.type', index=2, - number=3, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface_one', full_name='core.Link.interface_one', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='interface_two', full_name='core.Link.interface_two', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='options', full_name='core.Link.options', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=7587, - serialized_end=7775, -) - - -_LINKOPTIONS = _descriptor.Descriptor( - name='LinkOptions', - full_name='core.LinkOptions', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='opaque', full_name='core.LinkOptions.opaque', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='jitter', full_name='core.LinkOptions.jitter', index=1, - number=2, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='key', full_name='core.LinkOptions.key', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='mburst', full_name='core.LinkOptions.mburst', index=3, - number=4, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='mer', full_name='core.LinkOptions.mer', index=4, - number=5, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='per', full_name='core.LinkOptions.per', index=5, - number=6, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='bandwidth', full_name='core.LinkOptions.bandwidth', index=6, - number=7, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='burst', full_name='core.LinkOptions.burst', index=7, - number=8, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='delay', full_name='core.LinkOptions.delay', index=8, - number=9, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='dup', full_name='core.LinkOptions.dup', index=9, - number=10, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='unidirectional', full_name='core.LinkOptions.unidirectional', index=10, - number=11, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=7778, - serialized_end=7964, -) - - -_INTERFACE = _descriptor.Descriptor( - name='Interface', - full_name='core.Interface', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='id', full_name='core.Interface.id', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='name', full_name='core.Interface.name', index=1, - number=2, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='mac', full_name='core.Interface.mac', index=2, - number=3, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='ip4', full_name='core.Interface.ip4', index=3, - number=4, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='ip4mask', full_name='core.Interface.ip4mask', index=4, - number=5, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='ip6', full_name='core.Interface.ip6', index=5, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='ip6mask', full_name='core.Interface.ip6mask', index=6, - number=7, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='netid', full_name='core.Interface.netid', index=7, - number=8, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='flowid', full_name='core.Interface.flowid', index=8, - number=9, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='mtu', full_name='core.Interface.mtu', index=9, - number=10, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=7967, - serialized_end=8121, -) - - -_POSITION = _descriptor.Descriptor( - name='Position', - full_name='core.Position', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='x', full_name='core.Position.x', index=0, - number=1, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='y', full_name='core.Position.y', index=1, - number=2, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='z', full_name='core.Position.z', index=2, - number=3, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='lat', full_name='core.Position.lat', index=3, - number=4, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='lon', full_name='core.Position.lon', index=4, - number=5, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - _descriptor.FieldDescriptor( - name='alt', full_name='core.Position.alt', index=5, - number=6, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - serialized_options=None, file=DESCRIPTOR), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - serialized_options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=8123, - serialized_end=8205, -) - -_CREATESESSIONRESPONSE.fields_by_name['state'].enum_type = _SESSIONSTATE -_GETSESSIONSRESPONSE.fields_by_name['sessions'].message_type = _SESSIONSUMMARY -_GETSESSIONRESPONSE.fields_by_name['session'].message_type = _SESSION -_GETSESSIONOPTIONSRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP -_SETSESSIONOPTIONSREQUEST_CONFIGENTRY.containing_type = _SETSESSIONOPTIONSREQUEST -_SETSESSIONOPTIONSREQUEST.fields_by_name['config'].message_type = _SETSESSIONOPTIONSREQUEST_CONFIGENTRY -_GETSESSIONLOCATIONRESPONSE.fields_by_name['position'].message_type = _POSITION -_SETSESSIONLOCATIONREQUEST.fields_by_name['position'].message_type = _POSITION -_SETSESSIONSTATEREQUEST.fields_by_name['state'].enum_type = _SESSIONSTATE -_NODEEVENT.fields_by_name['node'].message_type = _NODE -_LINKEVENT.fields_by_name['message_type'].enum_type = _MESSAGETYPE -_LINKEVENT.fields_by_name['link'].message_type = _LINK -_CONFIGEVENT.fields_by_name['message_type'].enum_type = _MESSAGETYPE -_EXCEPTIONEVENT.fields_by_name['level'].enum_type = _EXCEPTIONLEVEL -_FILEEVENT.fields_by_name['message_type'].enum_type = _MESSAGETYPE -_ADDNODEREQUEST.fields_by_name['node'].message_type = _NODE -_GETNODERESPONSE.fields_by_name['node'].message_type = _NODE -_GETNODERESPONSE.fields_by_name['interfaces'].message_type = _INTERFACE -_EDITNODEREQUEST.fields_by_name['position'].message_type = _POSITION -_GETNODELINKSRESPONSE.fields_by_name['links'].message_type = _LINK -_ADDLINKREQUEST.fields_by_name['link'].message_type = _LINK -_EDITLINKREQUEST.fields_by_name['options'].message_type = _LINKOPTIONS -_GETHOOKSRESPONSE.fields_by_name['hooks'].message_type = _HOOK -_ADDHOOKREQUEST.fields_by_name['hook'].message_type = _HOOK -_GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG.fields_by_name['groups'].message_type = _CONFIGGROUP -_GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG.containing_type = _GETMOBILITYCONFIGSRESPONSE -_GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY.fields_by_name['value'].message_type = _GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG -_GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY.containing_type = _GETMOBILITYCONFIGSRESPONSE -_GETMOBILITYCONFIGSRESPONSE.fields_by_name['configs'].message_type = _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY -_GETMOBILITYCONFIGRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP -_SETMOBILITYCONFIGREQUEST_CONFIGENTRY.containing_type = _SETMOBILITYCONFIGREQUEST -_SETMOBILITYCONFIGREQUEST.fields_by_name['config'].message_type = _SETMOBILITYCONFIGREQUEST_CONFIGENTRY -_MOBILITYACTIONREQUEST.fields_by_name['action'].enum_type = _MOBILITYACTION -_GETSERVICESRESPONSE.fields_by_name['services'].message_type = _SERVICE -_GETSERVICEDEFAULTSRESPONSE.fields_by_name['defaults'].message_type = _SERVICEDEFAULTS -_SETSERVICEDEFAULTSREQUEST.fields_by_name['defaults'].message_type = _SERVICEDEFAULTS -_GETNODESERVICERESPONSE.fields_by_name['service'].message_type = _NODESERVICEDATA -_SERVICEACTIONREQUEST.fields_by_name['action'].enum_type = _SERVICEACTION -_GETWLANCONFIGRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP -_SETWLANCONFIGREQUEST_CONFIGENTRY.containing_type = _SETWLANCONFIGREQUEST -_SETWLANCONFIGREQUEST.fields_by_name['config'].message_type = _SETWLANCONFIGREQUEST_CONFIGENTRY -_GETEMANECONFIGRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP -_SETEMANECONFIGREQUEST_CONFIGENTRY.containing_type = _SETEMANECONFIGREQUEST -_SETEMANECONFIGREQUEST.fields_by_name['config'].message_type = _SETEMANECONFIGREQUEST_CONFIGENTRY -_GETEMANEMODELCONFIGRESPONSE.fields_by_name['groups'].message_type = _CONFIGGROUP -_SETEMANEMODELCONFIGREQUEST_CONFIGENTRY.containing_type = _SETEMANEMODELCONFIGREQUEST -_SETEMANEMODELCONFIGREQUEST.fields_by_name['config'].message_type = _SETEMANEMODELCONFIGREQUEST_CONFIGENTRY -_GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG.fields_by_name['groups'].message_type = _CONFIGGROUP -_GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG.containing_type = _GETEMANEMODELCONFIGSRESPONSE -_GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY.fields_by_name['value'].message_type = _GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG -_GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY.containing_type = _GETEMANEMODELCONFIGSRESPONSE -_GETEMANEMODELCONFIGSRESPONSE.fields_by_name['configs'].message_type = _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY -_HOOK.fields_by_name['state'].enum_type = _SESSIONSTATE -_NODESERVICEDATA.fields_by_name['validation_mode'].enum_type = _SERVICEVALIDATIONMODE -_CONFIGGROUP.fields_by_name['options'].message_type = _CONFIGOPTION -_SESSION.fields_by_name['state'].enum_type = _SESSIONSTATE -_SESSION.fields_by_name['nodes'].message_type = _NODE -_SESSION.fields_by_name['links'].message_type = _LINK -_SESSIONSUMMARY.fields_by_name['state'].enum_type = _SESSIONSTATE -_NODE.fields_by_name['type'].enum_type = _NODETYPE -_NODE.fields_by_name['position'].message_type = _POSITION -_LINK.fields_by_name['type'].enum_type = _LINKTYPE -_LINK.fields_by_name['interface_one'].message_type = _INTERFACE -_LINK.fields_by_name['interface_two'].message_type = _INTERFACE -_LINK.fields_by_name['options'].message_type = _LINKOPTIONS -DESCRIPTOR.message_types_by_name['CreateSessionRequest'] = _CREATESESSIONREQUEST -DESCRIPTOR.message_types_by_name['CreateSessionResponse'] = _CREATESESSIONRESPONSE -DESCRIPTOR.message_types_by_name['DeleteSessionRequest'] = _DELETESESSIONREQUEST -DESCRIPTOR.message_types_by_name['DeleteSessionResponse'] = _DELETESESSIONRESPONSE -DESCRIPTOR.message_types_by_name['GetSessionsRequest'] = _GETSESSIONSREQUEST -DESCRIPTOR.message_types_by_name['GetSessionsResponse'] = _GETSESSIONSRESPONSE -DESCRIPTOR.message_types_by_name['GetSessionRequest'] = _GETSESSIONREQUEST -DESCRIPTOR.message_types_by_name['GetSessionResponse'] = _GETSESSIONRESPONSE -DESCRIPTOR.message_types_by_name['GetSessionOptionsRequest'] = _GETSESSIONOPTIONSREQUEST -DESCRIPTOR.message_types_by_name['GetSessionOptionsResponse'] = _GETSESSIONOPTIONSRESPONSE -DESCRIPTOR.message_types_by_name['SetSessionOptionsRequest'] = _SETSESSIONOPTIONSREQUEST -DESCRIPTOR.message_types_by_name['SetSessionOptionsResponse'] = _SETSESSIONOPTIONSRESPONSE -DESCRIPTOR.message_types_by_name['GetSessionLocationRequest'] = _GETSESSIONLOCATIONREQUEST -DESCRIPTOR.message_types_by_name['GetSessionLocationResponse'] = _GETSESSIONLOCATIONRESPONSE -DESCRIPTOR.message_types_by_name['SetSessionLocationRequest'] = _SETSESSIONLOCATIONREQUEST -DESCRIPTOR.message_types_by_name['SetSessionLocationResponse'] = _SETSESSIONLOCATIONRESPONSE -DESCRIPTOR.message_types_by_name['SetSessionStateRequest'] = _SETSESSIONSTATEREQUEST -DESCRIPTOR.message_types_by_name['SetSessionStateResponse'] = _SETSESSIONSTATERESPONSE -DESCRIPTOR.message_types_by_name['NodeEventsRequest'] = _NODEEVENTSREQUEST -DESCRIPTOR.message_types_by_name['NodeEvent'] = _NODEEVENT -DESCRIPTOR.message_types_by_name['LinkEventsRequest'] = _LINKEVENTSREQUEST -DESCRIPTOR.message_types_by_name['LinkEvent'] = _LINKEVENT -DESCRIPTOR.message_types_by_name['SessionEventsRequest'] = _SESSIONEVENTSREQUEST -DESCRIPTOR.message_types_by_name['SessionEvent'] = _SESSIONEVENT -DESCRIPTOR.message_types_by_name['ConfigEventsRequest'] = _CONFIGEVENTSREQUEST -DESCRIPTOR.message_types_by_name['ConfigEvent'] = _CONFIGEVENT -DESCRIPTOR.message_types_by_name['ExceptionEventsRequest'] = _EXCEPTIONEVENTSREQUEST -DESCRIPTOR.message_types_by_name['ExceptionEvent'] = _EXCEPTIONEVENT -DESCRIPTOR.message_types_by_name['FileEventsRequest'] = _FILEEVENTSREQUEST -DESCRIPTOR.message_types_by_name['FileEvent'] = _FILEEVENT -DESCRIPTOR.message_types_by_name['AddNodeRequest'] = _ADDNODEREQUEST -DESCRIPTOR.message_types_by_name['AddNodeResponse'] = _ADDNODERESPONSE -DESCRIPTOR.message_types_by_name['GetNodeRequest'] = _GETNODEREQUEST -DESCRIPTOR.message_types_by_name['GetNodeResponse'] = _GETNODERESPONSE -DESCRIPTOR.message_types_by_name['EditNodeRequest'] = _EDITNODEREQUEST -DESCRIPTOR.message_types_by_name['EditNodeResponse'] = _EDITNODERESPONSE -DESCRIPTOR.message_types_by_name['DeleteNodeRequest'] = _DELETENODEREQUEST -DESCRIPTOR.message_types_by_name['DeleteNodeResponse'] = _DELETENODERESPONSE -DESCRIPTOR.message_types_by_name['GetNodeLinksRequest'] = _GETNODELINKSREQUEST -DESCRIPTOR.message_types_by_name['GetNodeLinksResponse'] = _GETNODELINKSRESPONSE -DESCRIPTOR.message_types_by_name['AddLinkRequest'] = _ADDLINKREQUEST -DESCRIPTOR.message_types_by_name['AddLinkResponse'] = _ADDLINKRESPONSE -DESCRIPTOR.message_types_by_name['EditLinkRequest'] = _EDITLINKREQUEST -DESCRIPTOR.message_types_by_name['EditLinkResponse'] = _EDITLINKRESPONSE -DESCRIPTOR.message_types_by_name['DeleteLinkRequest'] = _DELETELINKREQUEST -DESCRIPTOR.message_types_by_name['DeleteLinkResponse'] = _DELETELINKRESPONSE -DESCRIPTOR.message_types_by_name['GetHooksRequest'] = _GETHOOKSREQUEST -DESCRIPTOR.message_types_by_name['GetHooksResponse'] = _GETHOOKSRESPONSE -DESCRIPTOR.message_types_by_name['AddHookRequest'] = _ADDHOOKREQUEST -DESCRIPTOR.message_types_by_name['AddHookResponse'] = _ADDHOOKRESPONSE -DESCRIPTOR.message_types_by_name['GetMobilityConfigsRequest'] = _GETMOBILITYCONFIGSREQUEST -DESCRIPTOR.message_types_by_name['GetMobilityConfigsResponse'] = _GETMOBILITYCONFIGSRESPONSE -DESCRIPTOR.message_types_by_name['GetMobilityConfigRequest'] = _GETMOBILITYCONFIGREQUEST -DESCRIPTOR.message_types_by_name['GetMobilityConfigResponse'] = _GETMOBILITYCONFIGRESPONSE -DESCRIPTOR.message_types_by_name['SetMobilityConfigRequest'] = _SETMOBILITYCONFIGREQUEST -DESCRIPTOR.message_types_by_name['SetMobilityConfigResponse'] = _SETMOBILITYCONFIGRESPONSE -DESCRIPTOR.message_types_by_name['MobilityActionRequest'] = _MOBILITYACTIONREQUEST -DESCRIPTOR.message_types_by_name['MobilityActionResponse'] = _MOBILITYACTIONRESPONSE -DESCRIPTOR.message_types_by_name['GetServicesRequest'] = _GETSERVICESREQUEST -DESCRIPTOR.message_types_by_name['GetServicesResponse'] = _GETSERVICESRESPONSE -DESCRIPTOR.message_types_by_name['GetServiceDefaultsRequest'] = _GETSERVICEDEFAULTSREQUEST -DESCRIPTOR.message_types_by_name['GetServiceDefaultsResponse'] = _GETSERVICEDEFAULTSRESPONSE -DESCRIPTOR.message_types_by_name['SetServiceDefaultsRequest'] = _SETSERVICEDEFAULTSREQUEST -DESCRIPTOR.message_types_by_name['SetServiceDefaultsResponse'] = _SETSERVICEDEFAULTSRESPONSE -DESCRIPTOR.message_types_by_name['GetNodeServiceRequest'] = _GETNODESERVICEREQUEST -DESCRIPTOR.message_types_by_name['GetNodeServiceResponse'] = _GETNODESERVICERESPONSE -DESCRIPTOR.message_types_by_name['GetNodeServiceFileRequest'] = _GETNODESERVICEFILEREQUEST -DESCRIPTOR.message_types_by_name['GetNodeServiceFileResponse'] = _GETNODESERVICEFILERESPONSE -DESCRIPTOR.message_types_by_name['SetNodeServiceRequest'] = _SETNODESERVICEREQUEST -DESCRIPTOR.message_types_by_name['SetNodeServiceResponse'] = _SETNODESERVICERESPONSE -DESCRIPTOR.message_types_by_name['SetNodeServiceFileRequest'] = _SETNODESERVICEFILEREQUEST -DESCRIPTOR.message_types_by_name['SetNodeServiceFileResponse'] = _SETNODESERVICEFILERESPONSE -DESCRIPTOR.message_types_by_name['ServiceActionRequest'] = _SERVICEACTIONREQUEST -DESCRIPTOR.message_types_by_name['ServiceActionResponse'] = _SERVICEACTIONRESPONSE -DESCRIPTOR.message_types_by_name['GetWlanConfigRequest'] = _GETWLANCONFIGREQUEST -DESCRIPTOR.message_types_by_name['GetWlanConfigResponse'] = _GETWLANCONFIGRESPONSE -DESCRIPTOR.message_types_by_name['SetWlanConfigRequest'] = _SETWLANCONFIGREQUEST -DESCRIPTOR.message_types_by_name['SetWlanConfigResponse'] = _SETWLANCONFIGRESPONSE -DESCRIPTOR.message_types_by_name['GetEmaneConfigRequest'] = _GETEMANECONFIGREQUEST -DESCRIPTOR.message_types_by_name['GetEmaneConfigResponse'] = _GETEMANECONFIGRESPONSE -DESCRIPTOR.message_types_by_name['SetEmaneConfigRequest'] = _SETEMANECONFIGREQUEST -DESCRIPTOR.message_types_by_name['SetEmaneConfigResponse'] = _SETEMANECONFIGRESPONSE -DESCRIPTOR.message_types_by_name['GetEmaneModelsRequest'] = _GETEMANEMODELSREQUEST -DESCRIPTOR.message_types_by_name['GetEmaneModelsResponse'] = _GETEMANEMODELSRESPONSE -DESCRIPTOR.message_types_by_name['GetEmaneModelConfigRequest'] = _GETEMANEMODELCONFIGREQUEST -DESCRIPTOR.message_types_by_name['GetEmaneModelConfigResponse'] = _GETEMANEMODELCONFIGRESPONSE -DESCRIPTOR.message_types_by_name['SetEmaneModelConfigRequest'] = _SETEMANEMODELCONFIGREQUEST -DESCRIPTOR.message_types_by_name['SetEmaneModelConfigResponse'] = _SETEMANEMODELCONFIGRESPONSE -DESCRIPTOR.message_types_by_name['GetEmaneModelConfigsRequest'] = _GETEMANEMODELCONFIGSREQUEST -DESCRIPTOR.message_types_by_name['GetEmaneModelConfigsResponse'] = _GETEMANEMODELCONFIGSRESPONSE -DESCRIPTOR.message_types_by_name['SaveXmlRequest'] = _SAVEXMLREQUEST -DESCRIPTOR.message_types_by_name['SaveXmlResponse'] = _SAVEXMLRESPONSE -DESCRIPTOR.message_types_by_name['OpenXmlRequest'] = _OPENXMLREQUEST -DESCRIPTOR.message_types_by_name['OpenXmlResponse'] = _OPENXMLRESPONSE -DESCRIPTOR.message_types_by_name['Hook'] = _HOOK -DESCRIPTOR.message_types_by_name['ServiceDefaults'] = _SERVICEDEFAULTS -DESCRIPTOR.message_types_by_name['Service'] = _SERVICE -DESCRIPTOR.message_types_by_name['NodeServiceData'] = _NODESERVICEDATA -DESCRIPTOR.message_types_by_name['ConfigGroup'] = _CONFIGGROUP -DESCRIPTOR.message_types_by_name['ConfigOption'] = _CONFIGOPTION -DESCRIPTOR.message_types_by_name['Session'] = _SESSION -DESCRIPTOR.message_types_by_name['SessionSummary'] = _SESSIONSUMMARY -DESCRIPTOR.message_types_by_name['Node'] = _NODE -DESCRIPTOR.message_types_by_name['Link'] = _LINK -DESCRIPTOR.message_types_by_name['LinkOptions'] = _LINKOPTIONS -DESCRIPTOR.message_types_by_name['Interface'] = _INTERFACE -DESCRIPTOR.message_types_by_name['Position'] = _POSITION -DESCRIPTOR.enum_types_by_name['MessageType'] = _MESSAGETYPE -DESCRIPTOR.enum_types_by_name['LinkType'] = _LINKTYPE -DESCRIPTOR.enum_types_by_name['SessionState'] = _SESSIONSTATE -DESCRIPTOR.enum_types_by_name['NodeType'] = _NODETYPE -DESCRIPTOR.enum_types_by_name['ServiceValidationMode'] = _SERVICEVALIDATIONMODE -DESCRIPTOR.enum_types_by_name['ServiceAction'] = _SERVICEACTION -DESCRIPTOR.enum_types_by_name['MobilityAction'] = _MOBILITYACTION -DESCRIPTOR.enum_types_by_name['ExceptionLevel'] = _EXCEPTIONLEVEL -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -CreateSessionRequest = _reflection.GeneratedProtocolMessageType('CreateSessionRequest', (_message.Message,), dict( - DESCRIPTOR = _CREATESESSIONREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.CreateSessionRequest) - )) -_sym_db.RegisterMessage(CreateSessionRequest) - -CreateSessionResponse = _reflection.GeneratedProtocolMessageType('CreateSessionResponse', (_message.Message,), dict( - DESCRIPTOR = _CREATESESSIONRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.CreateSessionResponse) - )) -_sym_db.RegisterMessage(CreateSessionResponse) - -DeleteSessionRequest = _reflection.GeneratedProtocolMessageType('DeleteSessionRequest', (_message.Message,), dict( - DESCRIPTOR = _DELETESESSIONREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.DeleteSessionRequest) - )) -_sym_db.RegisterMessage(DeleteSessionRequest) - -DeleteSessionResponse = _reflection.GeneratedProtocolMessageType('DeleteSessionResponse', (_message.Message,), dict( - DESCRIPTOR = _DELETESESSIONRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.DeleteSessionResponse) - )) -_sym_db.RegisterMessage(DeleteSessionResponse) - -GetSessionsRequest = _reflection.GeneratedProtocolMessageType('GetSessionsRequest', (_message.Message,), dict( - DESCRIPTOR = _GETSESSIONSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetSessionsRequest) - )) -_sym_db.RegisterMessage(GetSessionsRequest) - -GetSessionsResponse = _reflection.GeneratedProtocolMessageType('GetSessionsResponse', (_message.Message,), dict( - DESCRIPTOR = _GETSESSIONSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetSessionsResponse) - )) -_sym_db.RegisterMessage(GetSessionsResponse) - -GetSessionRequest = _reflection.GeneratedProtocolMessageType('GetSessionRequest', (_message.Message,), dict( - DESCRIPTOR = _GETSESSIONREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetSessionRequest) - )) -_sym_db.RegisterMessage(GetSessionRequest) - -GetSessionResponse = _reflection.GeneratedProtocolMessageType('GetSessionResponse', (_message.Message,), dict( - DESCRIPTOR = _GETSESSIONRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetSessionResponse) - )) -_sym_db.RegisterMessage(GetSessionResponse) - -GetSessionOptionsRequest = _reflection.GeneratedProtocolMessageType('GetSessionOptionsRequest', (_message.Message,), dict( - DESCRIPTOR = _GETSESSIONOPTIONSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetSessionOptionsRequest) - )) -_sym_db.RegisterMessage(GetSessionOptionsRequest) - -GetSessionOptionsResponse = _reflection.GeneratedProtocolMessageType('GetSessionOptionsResponse', (_message.Message,), dict( - DESCRIPTOR = _GETSESSIONOPTIONSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetSessionOptionsResponse) - )) -_sym_db.RegisterMessage(GetSessionOptionsResponse) - -SetSessionOptionsRequest = _reflection.GeneratedProtocolMessageType('SetSessionOptionsRequest', (_message.Message,), dict( - - ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( - DESCRIPTOR = _SETSESSIONOPTIONSREQUEST_CONFIGENTRY, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetSessionOptionsRequest.ConfigEntry) - )) - , - DESCRIPTOR = _SETSESSIONOPTIONSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetSessionOptionsRequest) - )) -_sym_db.RegisterMessage(SetSessionOptionsRequest) -_sym_db.RegisterMessage(SetSessionOptionsRequest.ConfigEntry) - -SetSessionOptionsResponse = _reflection.GeneratedProtocolMessageType('SetSessionOptionsResponse', (_message.Message,), dict( - DESCRIPTOR = _SETSESSIONOPTIONSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetSessionOptionsResponse) - )) -_sym_db.RegisterMessage(SetSessionOptionsResponse) - -GetSessionLocationRequest = _reflection.GeneratedProtocolMessageType('GetSessionLocationRequest', (_message.Message,), dict( - DESCRIPTOR = _GETSESSIONLOCATIONREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetSessionLocationRequest) - )) -_sym_db.RegisterMessage(GetSessionLocationRequest) - -GetSessionLocationResponse = _reflection.GeneratedProtocolMessageType('GetSessionLocationResponse', (_message.Message,), dict( - DESCRIPTOR = _GETSESSIONLOCATIONRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetSessionLocationResponse) - )) -_sym_db.RegisterMessage(GetSessionLocationResponse) - -SetSessionLocationRequest = _reflection.GeneratedProtocolMessageType('SetSessionLocationRequest', (_message.Message,), dict( - DESCRIPTOR = _SETSESSIONLOCATIONREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetSessionLocationRequest) - )) -_sym_db.RegisterMessage(SetSessionLocationRequest) - -SetSessionLocationResponse = _reflection.GeneratedProtocolMessageType('SetSessionLocationResponse', (_message.Message,), dict( - DESCRIPTOR = _SETSESSIONLOCATIONRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetSessionLocationResponse) - )) -_sym_db.RegisterMessage(SetSessionLocationResponse) - -SetSessionStateRequest = _reflection.GeneratedProtocolMessageType('SetSessionStateRequest', (_message.Message,), dict( - DESCRIPTOR = _SETSESSIONSTATEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetSessionStateRequest) - )) -_sym_db.RegisterMessage(SetSessionStateRequest) - -SetSessionStateResponse = _reflection.GeneratedProtocolMessageType('SetSessionStateResponse', (_message.Message,), dict( - DESCRIPTOR = _SETSESSIONSTATERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetSessionStateResponse) - )) -_sym_db.RegisterMessage(SetSessionStateResponse) - -NodeEventsRequest = _reflection.GeneratedProtocolMessageType('NodeEventsRequest', (_message.Message,), dict( - DESCRIPTOR = _NODEEVENTSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.NodeEventsRequest) - )) -_sym_db.RegisterMessage(NodeEventsRequest) - -NodeEvent = _reflection.GeneratedProtocolMessageType('NodeEvent', (_message.Message,), dict( - DESCRIPTOR = _NODEEVENT, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.NodeEvent) - )) -_sym_db.RegisterMessage(NodeEvent) - -LinkEventsRequest = _reflection.GeneratedProtocolMessageType('LinkEventsRequest', (_message.Message,), dict( - DESCRIPTOR = _LINKEVENTSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.LinkEventsRequest) - )) -_sym_db.RegisterMessage(LinkEventsRequest) - -LinkEvent = _reflection.GeneratedProtocolMessageType('LinkEvent', (_message.Message,), dict( - DESCRIPTOR = _LINKEVENT, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.LinkEvent) - )) -_sym_db.RegisterMessage(LinkEvent) - -SessionEventsRequest = _reflection.GeneratedProtocolMessageType('SessionEventsRequest', (_message.Message,), dict( - DESCRIPTOR = _SESSIONEVENTSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SessionEventsRequest) - )) -_sym_db.RegisterMessage(SessionEventsRequest) - -SessionEvent = _reflection.GeneratedProtocolMessageType('SessionEvent', (_message.Message,), dict( - DESCRIPTOR = _SESSIONEVENT, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SessionEvent) - )) -_sym_db.RegisterMessage(SessionEvent) - -ConfigEventsRequest = _reflection.GeneratedProtocolMessageType('ConfigEventsRequest', (_message.Message,), dict( - DESCRIPTOR = _CONFIGEVENTSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ConfigEventsRequest) - )) -_sym_db.RegisterMessage(ConfigEventsRequest) - -ConfigEvent = _reflection.GeneratedProtocolMessageType('ConfigEvent', (_message.Message,), dict( - DESCRIPTOR = _CONFIGEVENT, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ConfigEvent) - )) -_sym_db.RegisterMessage(ConfigEvent) - -ExceptionEventsRequest = _reflection.GeneratedProtocolMessageType('ExceptionEventsRequest', (_message.Message,), dict( - DESCRIPTOR = _EXCEPTIONEVENTSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ExceptionEventsRequest) - )) -_sym_db.RegisterMessage(ExceptionEventsRequest) - -ExceptionEvent = _reflection.GeneratedProtocolMessageType('ExceptionEvent', (_message.Message,), dict( - DESCRIPTOR = _EXCEPTIONEVENT, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ExceptionEvent) - )) -_sym_db.RegisterMessage(ExceptionEvent) - -FileEventsRequest = _reflection.GeneratedProtocolMessageType('FileEventsRequest', (_message.Message,), dict( - DESCRIPTOR = _FILEEVENTSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.FileEventsRequest) - )) -_sym_db.RegisterMessage(FileEventsRequest) - -FileEvent = _reflection.GeneratedProtocolMessageType('FileEvent', (_message.Message,), dict( - DESCRIPTOR = _FILEEVENT, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.FileEvent) - )) -_sym_db.RegisterMessage(FileEvent) - -AddNodeRequest = _reflection.GeneratedProtocolMessageType('AddNodeRequest', (_message.Message,), dict( - DESCRIPTOR = _ADDNODEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.AddNodeRequest) - )) -_sym_db.RegisterMessage(AddNodeRequest) - -AddNodeResponse = _reflection.GeneratedProtocolMessageType('AddNodeResponse', (_message.Message,), dict( - DESCRIPTOR = _ADDNODERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.AddNodeResponse) - )) -_sym_db.RegisterMessage(AddNodeResponse) - -GetNodeRequest = _reflection.GeneratedProtocolMessageType('GetNodeRequest', (_message.Message,), dict( - DESCRIPTOR = _GETNODEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetNodeRequest) - )) -_sym_db.RegisterMessage(GetNodeRequest) - -GetNodeResponse = _reflection.GeneratedProtocolMessageType('GetNodeResponse', (_message.Message,), dict( - DESCRIPTOR = _GETNODERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetNodeResponse) - )) -_sym_db.RegisterMessage(GetNodeResponse) - -EditNodeRequest = _reflection.GeneratedProtocolMessageType('EditNodeRequest', (_message.Message,), dict( - DESCRIPTOR = _EDITNODEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.EditNodeRequest) - )) -_sym_db.RegisterMessage(EditNodeRequest) - -EditNodeResponse = _reflection.GeneratedProtocolMessageType('EditNodeResponse', (_message.Message,), dict( - DESCRIPTOR = _EDITNODERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.EditNodeResponse) - )) -_sym_db.RegisterMessage(EditNodeResponse) - -DeleteNodeRequest = _reflection.GeneratedProtocolMessageType('DeleteNodeRequest', (_message.Message,), dict( - DESCRIPTOR = _DELETENODEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.DeleteNodeRequest) - )) -_sym_db.RegisterMessage(DeleteNodeRequest) - -DeleteNodeResponse = _reflection.GeneratedProtocolMessageType('DeleteNodeResponse', (_message.Message,), dict( - DESCRIPTOR = _DELETENODERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.DeleteNodeResponse) - )) -_sym_db.RegisterMessage(DeleteNodeResponse) - -GetNodeLinksRequest = _reflection.GeneratedProtocolMessageType('GetNodeLinksRequest', (_message.Message,), dict( - DESCRIPTOR = _GETNODELINKSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetNodeLinksRequest) - )) -_sym_db.RegisterMessage(GetNodeLinksRequest) - -GetNodeLinksResponse = _reflection.GeneratedProtocolMessageType('GetNodeLinksResponse', (_message.Message,), dict( - DESCRIPTOR = _GETNODELINKSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetNodeLinksResponse) - )) -_sym_db.RegisterMessage(GetNodeLinksResponse) - -AddLinkRequest = _reflection.GeneratedProtocolMessageType('AddLinkRequest', (_message.Message,), dict( - DESCRIPTOR = _ADDLINKREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.AddLinkRequest) - )) -_sym_db.RegisterMessage(AddLinkRequest) - -AddLinkResponse = _reflection.GeneratedProtocolMessageType('AddLinkResponse', (_message.Message,), dict( - DESCRIPTOR = _ADDLINKRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.AddLinkResponse) - )) -_sym_db.RegisterMessage(AddLinkResponse) - -EditLinkRequest = _reflection.GeneratedProtocolMessageType('EditLinkRequest', (_message.Message,), dict( - DESCRIPTOR = _EDITLINKREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.EditLinkRequest) - )) -_sym_db.RegisterMessage(EditLinkRequest) - -EditLinkResponse = _reflection.GeneratedProtocolMessageType('EditLinkResponse', (_message.Message,), dict( - DESCRIPTOR = _EDITLINKRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.EditLinkResponse) - )) -_sym_db.RegisterMessage(EditLinkResponse) - -DeleteLinkRequest = _reflection.GeneratedProtocolMessageType('DeleteLinkRequest', (_message.Message,), dict( - DESCRIPTOR = _DELETELINKREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.DeleteLinkRequest) - )) -_sym_db.RegisterMessage(DeleteLinkRequest) - -DeleteLinkResponse = _reflection.GeneratedProtocolMessageType('DeleteLinkResponse', (_message.Message,), dict( - DESCRIPTOR = _DELETELINKRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.DeleteLinkResponse) - )) -_sym_db.RegisterMessage(DeleteLinkResponse) - -GetHooksRequest = _reflection.GeneratedProtocolMessageType('GetHooksRequest', (_message.Message,), dict( - DESCRIPTOR = _GETHOOKSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetHooksRequest) - )) -_sym_db.RegisterMessage(GetHooksRequest) - -GetHooksResponse = _reflection.GeneratedProtocolMessageType('GetHooksResponse', (_message.Message,), dict( - DESCRIPTOR = _GETHOOKSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetHooksResponse) - )) -_sym_db.RegisterMessage(GetHooksResponse) - -AddHookRequest = _reflection.GeneratedProtocolMessageType('AddHookRequest', (_message.Message,), dict( - DESCRIPTOR = _ADDHOOKREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.AddHookRequest) - )) -_sym_db.RegisterMessage(AddHookRequest) - -AddHookResponse = _reflection.GeneratedProtocolMessageType('AddHookResponse', (_message.Message,), dict( - DESCRIPTOR = _ADDHOOKRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.AddHookResponse) - )) -_sym_db.RegisterMessage(AddHookResponse) - -GetMobilityConfigsRequest = _reflection.GeneratedProtocolMessageType('GetMobilityConfigsRequest', (_message.Message,), dict( - DESCRIPTOR = _GETMOBILITYCONFIGSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsRequest) - )) -_sym_db.RegisterMessage(GetMobilityConfigsRequest) - -GetMobilityConfigsResponse = _reflection.GeneratedProtocolMessageType('GetMobilityConfigsResponse', (_message.Message,), dict( - - MobilityConfig = _reflection.GeneratedProtocolMessageType('MobilityConfig', (_message.Message,), dict( - DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE_MOBILITYCONFIG, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse.MobilityConfig) - )) - , - - ConfigsEntry = _reflection.GeneratedProtocolMessageType('ConfigsEntry', (_message.Message,), dict( - DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse.ConfigsEntry) - )) - , - DESCRIPTOR = _GETMOBILITYCONFIGSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetMobilityConfigsResponse) - )) -_sym_db.RegisterMessage(GetMobilityConfigsResponse) -_sym_db.RegisterMessage(GetMobilityConfigsResponse.MobilityConfig) -_sym_db.RegisterMessage(GetMobilityConfigsResponse.ConfigsEntry) - -GetMobilityConfigRequest = _reflection.GeneratedProtocolMessageType('GetMobilityConfigRequest', (_message.Message,), dict( - DESCRIPTOR = _GETMOBILITYCONFIGREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetMobilityConfigRequest) - )) -_sym_db.RegisterMessage(GetMobilityConfigRequest) - -GetMobilityConfigResponse = _reflection.GeneratedProtocolMessageType('GetMobilityConfigResponse', (_message.Message,), dict( - DESCRIPTOR = _GETMOBILITYCONFIGRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetMobilityConfigResponse) - )) -_sym_db.RegisterMessage(GetMobilityConfigResponse) - -SetMobilityConfigRequest = _reflection.GeneratedProtocolMessageType('SetMobilityConfigRequest', (_message.Message,), dict( - - ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( - DESCRIPTOR = _SETMOBILITYCONFIGREQUEST_CONFIGENTRY, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetMobilityConfigRequest.ConfigEntry) - )) - , - DESCRIPTOR = _SETMOBILITYCONFIGREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetMobilityConfigRequest) - )) -_sym_db.RegisterMessage(SetMobilityConfigRequest) -_sym_db.RegisterMessage(SetMobilityConfigRequest.ConfigEntry) - -SetMobilityConfigResponse = _reflection.GeneratedProtocolMessageType('SetMobilityConfigResponse', (_message.Message,), dict( - DESCRIPTOR = _SETMOBILITYCONFIGRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetMobilityConfigResponse) - )) -_sym_db.RegisterMessage(SetMobilityConfigResponse) - -MobilityActionRequest = _reflection.GeneratedProtocolMessageType('MobilityActionRequest', (_message.Message,), dict( - DESCRIPTOR = _MOBILITYACTIONREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.MobilityActionRequest) - )) -_sym_db.RegisterMessage(MobilityActionRequest) - -MobilityActionResponse = _reflection.GeneratedProtocolMessageType('MobilityActionResponse', (_message.Message,), dict( - DESCRIPTOR = _MOBILITYACTIONRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.MobilityActionResponse) - )) -_sym_db.RegisterMessage(MobilityActionResponse) - -GetServicesRequest = _reflection.GeneratedProtocolMessageType('GetServicesRequest', (_message.Message,), dict( - DESCRIPTOR = _GETSERVICESREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetServicesRequest) - )) -_sym_db.RegisterMessage(GetServicesRequest) - -GetServicesResponse = _reflection.GeneratedProtocolMessageType('GetServicesResponse', (_message.Message,), dict( - DESCRIPTOR = _GETSERVICESRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetServicesResponse) - )) -_sym_db.RegisterMessage(GetServicesResponse) - -GetServiceDefaultsRequest = _reflection.GeneratedProtocolMessageType('GetServiceDefaultsRequest', (_message.Message,), dict( - DESCRIPTOR = _GETSERVICEDEFAULTSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetServiceDefaultsRequest) - )) -_sym_db.RegisterMessage(GetServiceDefaultsRequest) - -GetServiceDefaultsResponse = _reflection.GeneratedProtocolMessageType('GetServiceDefaultsResponse', (_message.Message,), dict( - DESCRIPTOR = _GETSERVICEDEFAULTSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetServiceDefaultsResponse) - )) -_sym_db.RegisterMessage(GetServiceDefaultsResponse) - -SetServiceDefaultsRequest = _reflection.GeneratedProtocolMessageType('SetServiceDefaultsRequest', (_message.Message,), dict( - DESCRIPTOR = _SETSERVICEDEFAULTSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetServiceDefaultsRequest) - )) -_sym_db.RegisterMessage(SetServiceDefaultsRequest) - -SetServiceDefaultsResponse = _reflection.GeneratedProtocolMessageType('SetServiceDefaultsResponse', (_message.Message,), dict( - DESCRIPTOR = _SETSERVICEDEFAULTSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetServiceDefaultsResponse) - )) -_sym_db.RegisterMessage(SetServiceDefaultsResponse) - -GetNodeServiceRequest = _reflection.GeneratedProtocolMessageType('GetNodeServiceRequest', (_message.Message,), dict( - DESCRIPTOR = _GETNODESERVICEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetNodeServiceRequest) - )) -_sym_db.RegisterMessage(GetNodeServiceRequest) - -GetNodeServiceResponse = _reflection.GeneratedProtocolMessageType('GetNodeServiceResponse', (_message.Message,), dict( - DESCRIPTOR = _GETNODESERVICERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetNodeServiceResponse) - )) -_sym_db.RegisterMessage(GetNodeServiceResponse) - -GetNodeServiceFileRequest = _reflection.GeneratedProtocolMessageType('GetNodeServiceFileRequest', (_message.Message,), dict( - DESCRIPTOR = _GETNODESERVICEFILEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetNodeServiceFileRequest) - )) -_sym_db.RegisterMessage(GetNodeServiceFileRequest) - -GetNodeServiceFileResponse = _reflection.GeneratedProtocolMessageType('GetNodeServiceFileResponse', (_message.Message,), dict( - DESCRIPTOR = _GETNODESERVICEFILERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetNodeServiceFileResponse) - )) -_sym_db.RegisterMessage(GetNodeServiceFileResponse) - -SetNodeServiceRequest = _reflection.GeneratedProtocolMessageType('SetNodeServiceRequest', (_message.Message,), dict( - DESCRIPTOR = _SETNODESERVICEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetNodeServiceRequest) - )) -_sym_db.RegisterMessage(SetNodeServiceRequest) - -SetNodeServiceResponse = _reflection.GeneratedProtocolMessageType('SetNodeServiceResponse', (_message.Message,), dict( - DESCRIPTOR = _SETNODESERVICERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetNodeServiceResponse) - )) -_sym_db.RegisterMessage(SetNodeServiceResponse) - -SetNodeServiceFileRequest = _reflection.GeneratedProtocolMessageType('SetNodeServiceFileRequest', (_message.Message,), dict( - DESCRIPTOR = _SETNODESERVICEFILEREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetNodeServiceFileRequest) - )) -_sym_db.RegisterMessage(SetNodeServiceFileRequest) - -SetNodeServiceFileResponse = _reflection.GeneratedProtocolMessageType('SetNodeServiceFileResponse', (_message.Message,), dict( - DESCRIPTOR = _SETNODESERVICEFILERESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetNodeServiceFileResponse) - )) -_sym_db.RegisterMessage(SetNodeServiceFileResponse) - -ServiceActionRequest = _reflection.GeneratedProtocolMessageType('ServiceActionRequest', (_message.Message,), dict( - DESCRIPTOR = _SERVICEACTIONREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ServiceActionRequest) - )) -_sym_db.RegisterMessage(ServiceActionRequest) - -ServiceActionResponse = _reflection.GeneratedProtocolMessageType('ServiceActionResponse', (_message.Message,), dict( - DESCRIPTOR = _SERVICEACTIONRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ServiceActionResponse) - )) -_sym_db.RegisterMessage(ServiceActionResponse) - -GetWlanConfigRequest = _reflection.GeneratedProtocolMessageType('GetWlanConfigRequest', (_message.Message,), dict( - DESCRIPTOR = _GETWLANCONFIGREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetWlanConfigRequest) - )) -_sym_db.RegisterMessage(GetWlanConfigRequest) - -GetWlanConfigResponse = _reflection.GeneratedProtocolMessageType('GetWlanConfigResponse', (_message.Message,), dict( - DESCRIPTOR = _GETWLANCONFIGRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetWlanConfigResponse) - )) -_sym_db.RegisterMessage(GetWlanConfigResponse) - -SetWlanConfigRequest = _reflection.GeneratedProtocolMessageType('SetWlanConfigRequest', (_message.Message,), dict( - - ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( - DESCRIPTOR = _SETWLANCONFIGREQUEST_CONFIGENTRY, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetWlanConfigRequest.ConfigEntry) - )) - , - DESCRIPTOR = _SETWLANCONFIGREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetWlanConfigRequest) - )) -_sym_db.RegisterMessage(SetWlanConfigRequest) -_sym_db.RegisterMessage(SetWlanConfigRequest.ConfigEntry) - -SetWlanConfigResponse = _reflection.GeneratedProtocolMessageType('SetWlanConfigResponse', (_message.Message,), dict( - DESCRIPTOR = _SETWLANCONFIGRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetWlanConfigResponse) - )) -_sym_db.RegisterMessage(SetWlanConfigResponse) - -GetEmaneConfigRequest = _reflection.GeneratedProtocolMessageType('GetEmaneConfigRequest', (_message.Message,), dict( - DESCRIPTOR = _GETEMANECONFIGREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneConfigRequest) - )) -_sym_db.RegisterMessage(GetEmaneConfigRequest) - -GetEmaneConfigResponse = _reflection.GeneratedProtocolMessageType('GetEmaneConfigResponse', (_message.Message,), dict( - DESCRIPTOR = _GETEMANECONFIGRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneConfigResponse) - )) -_sym_db.RegisterMessage(GetEmaneConfigResponse) - -SetEmaneConfigRequest = _reflection.GeneratedProtocolMessageType('SetEmaneConfigRequest', (_message.Message,), dict( - - ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( - DESCRIPTOR = _SETEMANECONFIGREQUEST_CONFIGENTRY, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetEmaneConfigRequest.ConfigEntry) - )) - , - DESCRIPTOR = _SETEMANECONFIGREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetEmaneConfigRequest) - )) -_sym_db.RegisterMessage(SetEmaneConfigRequest) -_sym_db.RegisterMessage(SetEmaneConfigRequest.ConfigEntry) - -SetEmaneConfigResponse = _reflection.GeneratedProtocolMessageType('SetEmaneConfigResponse', (_message.Message,), dict( - DESCRIPTOR = _SETEMANECONFIGRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetEmaneConfigResponse) - )) -_sym_db.RegisterMessage(SetEmaneConfigResponse) - -GetEmaneModelsRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelsRequest', (_message.Message,), dict( - DESCRIPTOR = _GETEMANEMODELSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneModelsRequest) - )) -_sym_db.RegisterMessage(GetEmaneModelsRequest) - -GetEmaneModelsResponse = _reflection.GeneratedProtocolMessageType('GetEmaneModelsResponse', (_message.Message,), dict( - DESCRIPTOR = _GETEMANEMODELSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneModelsResponse) - )) -_sym_db.RegisterMessage(GetEmaneModelsResponse) - -GetEmaneModelConfigRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigRequest', (_message.Message,), dict( - DESCRIPTOR = _GETEMANEMODELCONFIGREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigRequest) - )) -_sym_db.RegisterMessage(GetEmaneModelConfigRequest) - -GetEmaneModelConfigResponse = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigResponse', (_message.Message,), dict( - DESCRIPTOR = _GETEMANEMODELCONFIGRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigResponse) - )) -_sym_db.RegisterMessage(GetEmaneModelConfigResponse) - -SetEmaneModelConfigRequest = _reflection.GeneratedProtocolMessageType('SetEmaneModelConfigRequest', (_message.Message,), dict( - - ConfigEntry = _reflection.GeneratedProtocolMessageType('ConfigEntry', (_message.Message,), dict( - DESCRIPTOR = _SETEMANEMODELCONFIGREQUEST_CONFIGENTRY, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigRequest.ConfigEntry) - )) - , - DESCRIPTOR = _SETEMANEMODELCONFIGREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigRequest) - )) -_sym_db.RegisterMessage(SetEmaneModelConfigRequest) -_sym_db.RegisterMessage(SetEmaneModelConfigRequest.ConfigEntry) - -SetEmaneModelConfigResponse = _reflection.GeneratedProtocolMessageType('SetEmaneModelConfigResponse', (_message.Message,), dict( - DESCRIPTOR = _SETEMANEMODELCONFIGRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SetEmaneModelConfigResponse) - )) -_sym_db.RegisterMessage(SetEmaneModelConfigResponse) - -GetEmaneModelConfigsRequest = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigsRequest', (_message.Message,), dict( - DESCRIPTOR = _GETEMANEMODELCONFIGSREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsRequest) - )) -_sym_db.RegisterMessage(GetEmaneModelConfigsRequest) - -GetEmaneModelConfigsResponse = _reflection.GeneratedProtocolMessageType('GetEmaneModelConfigsResponse', (_message.Message,), dict( - - ModelConfig = _reflection.GeneratedProtocolMessageType('ModelConfig', (_message.Message,), dict( - DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE_MODELCONFIG, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse.ModelConfig) - )) - , - - ConfigsEntry = _reflection.GeneratedProtocolMessageType('ConfigsEntry', (_message.Message,), dict( - DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse.ConfigsEntry) - )) - , - DESCRIPTOR = _GETEMANEMODELCONFIGSRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.GetEmaneModelConfigsResponse) - )) -_sym_db.RegisterMessage(GetEmaneModelConfigsResponse) -_sym_db.RegisterMessage(GetEmaneModelConfigsResponse.ModelConfig) -_sym_db.RegisterMessage(GetEmaneModelConfigsResponse.ConfigsEntry) - -SaveXmlRequest = _reflection.GeneratedProtocolMessageType('SaveXmlRequest', (_message.Message,), dict( - DESCRIPTOR = _SAVEXMLREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SaveXmlRequest) - )) -_sym_db.RegisterMessage(SaveXmlRequest) - -SaveXmlResponse = _reflection.GeneratedProtocolMessageType('SaveXmlResponse', (_message.Message,), dict( - DESCRIPTOR = _SAVEXMLRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SaveXmlResponse) - )) -_sym_db.RegisterMessage(SaveXmlResponse) - -OpenXmlRequest = _reflection.GeneratedProtocolMessageType('OpenXmlRequest', (_message.Message,), dict( - DESCRIPTOR = _OPENXMLREQUEST, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.OpenXmlRequest) - )) -_sym_db.RegisterMessage(OpenXmlRequest) - -OpenXmlResponse = _reflection.GeneratedProtocolMessageType('OpenXmlResponse', (_message.Message,), dict( - DESCRIPTOR = _OPENXMLRESPONSE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.OpenXmlResponse) - )) -_sym_db.RegisterMessage(OpenXmlResponse) - -Hook = _reflection.GeneratedProtocolMessageType('Hook', (_message.Message,), dict( - DESCRIPTOR = _HOOK, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.Hook) - )) -_sym_db.RegisterMessage(Hook) - -ServiceDefaults = _reflection.GeneratedProtocolMessageType('ServiceDefaults', (_message.Message,), dict( - DESCRIPTOR = _SERVICEDEFAULTS, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ServiceDefaults) - )) -_sym_db.RegisterMessage(ServiceDefaults) - -Service = _reflection.GeneratedProtocolMessageType('Service', (_message.Message,), dict( - DESCRIPTOR = _SERVICE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.Service) - )) -_sym_db.RegisterMessage(Service) - -NodeServiceData = _reflection.GeneratedProtocolMessageType('NodeServiceData', (_message.Message,), dict( - DESCRIPTOR = _NODESERVICEDATA, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.NodeServiceData) - )) -_sym_db.RegisterMessage(NodeServiceData) - -ConfigGroup = _reflection.GeneratedProtocolMessageType('ConfigGroup', (_message.Message,), dict( - DESCRIPTOR = _CONFIGGROUP, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ConfigGroup) - )) -_sym_db.RegisterMessage(ConfigGroup) - -ConfigOption = _reflection.GeneratedProtocolMessageType('ConfigOption', (_message.Message,), dict( - DESCRIPTOR = _CONFIGOPTION, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.ConfigOption) - )) -_sym_db.RegisterMessage(ConfigOption) - -Session = _reflection.GeneratedProtocolMessageType('Session', (_message.Message,), dict( - DESCRIPTOR = _SESSION, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.Session) - )) -_sym_db.RegisterMessage(Session) - -SessionSummary = _reflection.GeneratedProtocolMessageType('SessionSummary', (_message.Message,), dict( - DESCRIPTOR = _SESSIONSUMMARY, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.SessionSummary) - )) -_sym_db.RegisterMessage(SessionSummary) - -Node = _reflection.GeneratedProtocolMessageType('Node', (_message.Message,), dict( - DESCRIPTOR = _NODE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.Node) - )) -_sym_db.RegisterMessage(Node) - -Link = _reflection.GeneratedProtocolMessageType('Link', (_message.Message,), dict( - DESCRIPTOR = _LINK, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.Link) - )) -_sym_db.RegisterMessage(Link) - -LinkOptions = _reflection.GeneratedProtocolMessageType('LinkOptions', (_message.Message,), dict( - DESCRIPTOR = _LINKOPTIONS, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.LinkOptions) - )) -_sym_db.RegisterMessage(LinkOptions) - -Interface = _reflection.GeneratedProtocolMessageType('Interface', (_message.Message,), dict( - DESCRIPTOR = _INTERFACE, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.Interface) - )) -_sym_db.RegisterMessage(Interface) - -Position = _reflection.GeneratedProtocolMessageType('Position', (_message.Message,), dict( - DESCRIPTOR = _POSITION, - __module__ = 'core.api.grpc.core_pb2' - # @@protoc_insertion_point(class_scope:core.Position) - )) -_sym_db.RegisterMessage(Position) - - -_SETSESSIONOPTIONSREQUEST_CONFIGENTRY._options = None -_GETMOBILITYCONFIGSRESPONSE_CONFIGSENTRY._options = None -_SETMOBILITYCONFIGREQUEST_CONFIGENTRY._options = None -_SETWLANCONFIGREQUEST_CONFIGENTRY._options = None -_SETEMANECONFIGREQUEST_CONFIGENTRY._options = None -_SETEMANEMODELCONFIGREQUEST_CONFIGENTRY._options = None -_GETEMANEMODELCONFIGSRESPONSE_CONFIGSENTRY._options = None - -_COREAPI = _descriptor.ServiceDescriptor( - name='CoreApi', - full_name='core.CoreApi', - file=DESCRIPTOR, - index=0, - serialized_options=None, - serialized_start=9257, - serialized_end=12811, - methods=[ - _descriptor.MethodDescriptor( - name='CreateSession', - full_name='core.CoreApi.CreateSession', - index=0, - containing_service=None, - input_type=_CREATESESSIONREQUEST, - output_type=_CREATESESSIONRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='DeleteSession', - full_name='core.CoreApi.DeleteSession', - index=1, - containing_service=None, - input_type=_DELETESESSIONREQUEST, - output_type=_DELETESESSIONRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetSessions', - full_name='core.CoreApi.GetSessions', - index=2, - containing_service=None, - input_type=_GETSESSIONSREQUEST, - output_type=_GETSESSIONSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetSession', - full_name='core.CoreApi.GetSession', - index=3, - containing_service=None, - input_type=_GETSESSIONREQUEST, - output_type=_GETSESSIONRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetSessionOptions', - full_name='core.CoreApi.GetSessionOptions', - index=4, - containing_service=None, - input_type=_GETSESSIONOPTIONSREQUEST, - output_type=_GETSESSIONOPTIONSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetSessionOptions', - full_name='core.CoreApi.SetSessionOptions', - index=5, - containing_service=None, - input_type=_SETSESSIONOPTIONSREQUEST, - output_type=_SETSESSIONOPTIONSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetSessionLocation', - full_name='core.CoreApi.GetSessionLocation', - index=6, - containing_service=None, - input_type=_GETSESSIONLOCATIONREQUEST, - output_type=_GETSESSIONLOCATIONRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetSessionLocation', - full_name='core.CoreApi.SetSessionLocation', - index=7, - containing_service=None, - input_type=_SETSESSIONLOCATIONREQUEST, - output_type=_SETSESSIONLOCATIONRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetSessionState', - full_name='core.CoreApi.SetSessionState', - index=8, - containing_service=None, - input_type=_SETSESSIONSTATEREQUEST, - output_type=_SETSESSIONSTATERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='NodeEvents', - full_name='core.CoreApi.NodeEvents', - index=9, - containing_service=None, - input_type=_NODEEVENTSREQUEST, - output_type=_NODEEVENT, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='LinkEvents', - full_name='core.CoreApi.LinkEvents', - index=10, - containing_service=None, - input_type=_LINKEVENTSREQUEST, - output_type=_LINKEVENT, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SessionEvents', - full_name='core.CoreApi.SessionEvents', - index=11, - containing_service=None, - input_type=_SESSIONEVENTSREQUEST, - output_type=_SESSIONEVENT, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='ConfigEvents', - full_name='core.CoreApi.ConfigEvents', - index=12, - containing_service=None, - input_type=_CONFIGEVENTSREQUEST, - output_type=_CONFIGEVENT, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='ExceptionEvents', - full_name='core.CoreApi.ExceptionEvents', - index=13, - containing_service=None, - input_type=_EXCEPTIONEVENTSREQUEST, - output_type=_EXCEPTIONEVENT, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='FileEvents', - full_name='core.CoreApi.FileEvents', - index=14, - containing_service=None, - input_type=_FILEEVENTSREQUEST, - output_type=_FILEEVENT, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='AddNode', - full_name='core.CoreApi.AddNode', - index=15, - containing_service=None, - input_type=_ADDNODEREQUEST, - output_type=_ADDNODERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetNode', - full_name='core.CoreApi.GetNode', - index=16, - containing_service=None, - input_type=_GETNODEREQUEST, - output_type=_GETNODERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='EditNode', - full_name='core.CoreApi.EditNode', - index=17, - containing_service=None, - input_type=_EDITNODEREQUEST, - output_type=_EDITNODERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='DeleteNode', - full_name='core.CoreApi.DeleteNode', - index=18, - containing_service=None, - input_type=_DELETENODEREQUEST, - output_type=_DELETENODERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetNodeLinks', - full_name='core.CoreApi.GetNodeLinks', - index=19, - containing_service=None, - input_type=_GETNODELINKSREQUEST, - output_type=_GETNODELINKSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='AddLink', - full_name='core.CoreApi.AddLink', - index=20, - containing_service=None, - input_type=_ADDLINKREQUEST, - output_type=_ADDLINKRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='EditLink', - full_name='core.CoreApi.EditLink', - index=21, - containing_service=None, - input_type=_EDITLINKREQUEST, - output_type=_EDITLINKRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='DeleteLink', - full_name='core.CoreApi.DeleteLink', - index=22, - containing_service=None, - input_type=_DELETELINKREQUEST, - output_type=_DELETELINKRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetHooks', - full_name='core.CoreApi.GetHooks', - index=23, - containing_service=None, - input_type=_GETHOOKSREQUEST, - output_type=_GETHOOKSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='AddHook', - full_name='core.CoreApi.AddHook', - index=24, - containing_service=None, - input_type=_ADDHOOKREQUEST, - output_type=_ADDHOOKRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetMobilityConfigs', - full_name='core.CoreApi.GetMobilityConfigs', - index=25, - containing_service=None, - input_type=_GETMOBILITYCONFIGSREQUEST, - output_type=_GETMOBILITYCONFIGSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetMobilityConfig', - full_name='core.CoreApi.GetMobilityConfig', - index=26, - containing_service=None, - input_type=_GETMOBILITYCONFIGREQUEST, - output_type=_GETMOBILITYCONFIGRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetMobilityConfig', - full_name='core.CoreApi.SetMobilityConfig', - index=27, - containing_service=None, - input_type=_SETMOBILITYCONFIGREQUEST, - output_type=_SETMOBILITYCONFIGRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='MobilityAction', - full_name='core.CoreApi.MobilityAction', - index=28, - containing_service=None, - input_type=_MOBILITYACTIONREQUEST, - output_type=_MOBILITYACTIONRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetServices', - full_name='core.CoreApi.GetServices', - index=29, - containing_service=None, - input_type=_GETSERVICESREQUEST, - output_type=_GETSERVICESRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetServiceDefaults', - full_name='core.CoreApi.GetServiceDefaults', - index=30, - containing_service=None, - input_type=_GETSERVICEDEFAULTSREQUEST, - output_type=_GETSERVICEDEFAULTSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetServiceDefaults', - full_name='core.CoreApi.SetServiceDefaults', - index=31, - containing_service=None, - input_type=_SETSERVICEDEFAULTSREQUEST, - output_type=_SETSERVICEDEFAULTSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetNodeService', - full_name='core.CoreApi.GetNodeService', - index=32, - containing_service=None, - input_type=_GETNODESERVICEREQUEST, - output_type=_GETNODESERVICERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetNodeServiceFile', - full_name='core.CoreApi.GetNodeServiceFile', - index=33, - containing_service=None, - input_type=_GETNODESERVICEFILEREQUEST, - output_type=_GETNODESERVICEFILERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetNodeService', - full_name='core.CoreApi.SetNodeService', - index=34, - containing_service=None, - input_type=_SETNODESERVICEREQUEST, - output_type=_SETNODESERVICERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetNodeServiceFile', - full_name='core.CoreApi.SetNodeServiceFile', - index=35, - containing_service=None, - input_type=_SETNODESERVICEFILEREQUEST, - output_type=_SETNODESERVICEFILERESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='ServiceAction', - full_name='core.CoreApi.ServiceAction', - index=36, - containing_service=None, - input_type=_SERVICEACTIONREQUEST, - output_type=_SERVICEACTIONRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetWlanConfig', - full_name='core.CoreApi.GetWlanConfig', - index=37, - containing_service=None, - input_type=_GETWLANCONFIGREQUEST, - output_type=_GETWLANCONFIGRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetWlanConfig', - full_name='core.CoreApi.SetWlanConfig', - index=38, - containing_service=None, - input_type=_SETWLANCONFIGREQUEST, - output_type=_SETWLANCONFIGRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetEmaneConfig', - full_name='core.CoreApi.GetEmaneConfig', - index=39, - containing_service=None, - input_type=_GETEMANECONFIGREQUEST, - output_type=_GETEMANECONFIGRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetEmaneConfig', - full_name='core.CoreApi.SetEmaneConfig', - index=40, - containing_service=None, - input_type=_SETEMANECONFIGREQUEST, - output_type=_SETEMANECONFIGRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetEmaneModels', - full_name='core.CoreApi.GetEmaneModels', - index=41, - containing_service=None, - input_type=_GETEMANEMODELSREQUEST, - output_type=_GETEMANEMODELSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetEmaneModelConfig', - full_name='core.CoreApi.GetEmaneModelConfig', - index=42, - containing_service=None, - input_type=_GETEMANEMODELCONFIGREQUEST, - output_type=_GETEMANEMODELCONFIGRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SetEmaneModelConfig', - full_name='core.CoreApi.SetEmaneModelConfig', - index=43, - containing_service=None, - input_type=_SETEMANEMODELCONFIGREQUEST, - output_type=_SETEMANEMODELCONFIGRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='GetEmaneModelConfigs', - full_name='core.CoreApi.GetEmaneModelConfigs', - index=44, - containing_service=None, - input_type=_GETEMANEMODELCONFIGSREQUEST, - output_type=_GETEMANEMODELCONFIGSRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='SaveXml', - full_name='core.CoreApi.SaveXml', - index=45, - containing_service=None, - input_type=_SAVEXMLREQUEST, - output_type=_SAVEXMLRESPONSE, - serialized_options=None, - ), - _descriptor.MethodDescriptor( - name='OpenXml', - full_name='core.CoreApi.OpenXml', - index=46, - containing_service=None, - input_type=_OPENXMLREQUEST, - output_type=_OPENXMLRESPONSE, - serialized_options=None, - ), -]) -_sym_db.RegisterServiceDescriptor(_COREAPI) - -DESCRIPTOR.services_by_name['CoreApi'] = _COREAPI - -# @@protoc_insertion_point(module_scope) diff --git a/daemon/core/api/grpc/core_pb2_grpc.py b/daemon/core/api/grpc/core_pb2_grpc.py deleted file mode 100644 index fc3a622c..00000000 --- a/daemon/core/api/grpc/core_pb2_grpc.py +++ /dev/null @@ -1,828 +0,0 @@ -# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT! -import grpc - -from core.api.grpc import core_pb2 as core_dot_api_dot_grpc_dot_core__pb2 - - -class CoreApiStub(object): - # missing associated documentation comment in .proto file - pass - - def __init__(self, channel): - """Constructor. - - Args: - channel: A grpc.Channel. - """ - self.CreateSession = channel.unary_unary( - '/core.CoreApi/CreateSession', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.CreateSessionRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.CreateSessionResponse.FromString, - ) - self.DeleteSession = channel.unary_unary( - '/core.CoreApi/DeleteSession', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteSessionRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteSessionResponse.FromString, - ) - self.GetSessions = channel.unary_unary( - '/core.CoreApi/GetSessions', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionsResponse.FromString, - ) - self.GetSession = channel.unary_unary( - '/core.CoreApi/GetSession', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionResponse.FromString, - ) - self.GetSessionOptions = channel.unary_unary( - '/core.CoreApi/GetSessionOptions', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionOptionsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionOptionsResponse.FromString, - ) - self.SetSessionOptions = channel.unary_unary( - '/core.CoreApi/SetSessionOptions', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionOptionsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionOptionsResponse.FromString, - ) - self.GetSessionLocation = channel.unary_unary( - '/core.CoreApi/GetSessionLocation', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionLocationRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionLocationResponse.FromString, - ) - self.SetSessionLocation = channel.unary_unary( - '/core.CoreApi/SetSessionLocation', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionLocationRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionLocationResponse.FromString, - ) - self.SetSessionState = channel.unary_unary( - '/core.CoreApi/SetSessionState', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionStateRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionStateResponse.FromString, - ) - self.NodeEvents = channel.unary_stream( - '/core.CoreApi/NodeEvents', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.NodeEventsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.NodeEvent.FromString, - ) - self.LinkEvents = channel.unary_stream( - '/core.CoreApi/LinkEvents', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.LinkEventsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.LinkEvent.FromString, - ) - self.SessionEvents = channel.unary_stream( - '/core.CoreApi/SessionEvents', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SessionEventsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SessionEvent.FromString, - ) - self.ConfigEvents = channel.unary_stream( - '/core.CoreApi/ConfigEvents', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.ConfigEventsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ConfigEvent.FromString, - ) - self.ExceptionEvents = channel.unary_stream( - '/core.CoreApi/ExceptionEvents', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.ExceptionEventsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ExceptionEvent.FromString, - ) - self.FileEvents = channel.unary_stream( - '/core.CoreApi/FileEvents', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.FileEventsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.FileEvent.FromString, - ) - self.AddNode = channel.unary_unary( - '/core.CoreApi/AddNode', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddNodeRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddNodeResponse.FromString, - ) - self.GetNode = channel.unary_unary( - '/core.CoreApi/GetNode', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeResponse.FromString, - ) - self.EditNode = channel.unary_unary( - '/core.CoreApi/EditNode', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.EditNodeRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.EditNodeResponse.FromString, - ) - self.DeleteNode = channel.unary_unary( - '/core.CoreApi/DeleteNode', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteNodeRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteNodeResponse.FromString, - ) - self.GetNodeLinks = channel.unary_unary( - '/core.CoreApi/GetNodeLinks', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeLinksRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeLinksResponse.FromString, - ) - self.AddLink = channel.unary_unary( - '/core.CoreApi/AddLink', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddLinkRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddLinkResponse.FromString, - ) - self.EditLink = channel.unary_unary( - '/core.CoreApi/EditLink', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.EditLinkRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.EditLinkResponse.FromString, - ) - self.DeleteLink = channel.unary_unary( - '/core.CoreApi/DeleteLink', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteLinkRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteLinkResponse.FromString, - ) - self.GetHooks = channel.unary_unary( - '/core.CoreApi/GetHooks', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetHooksRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetHooksResponse.FromString, - ) - self.AddHook = channel.unary_unary( - '/core.CoreApi/AddHook', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddHookRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddHookResponse.FromString, - ) - self.GetMobilityConfigs = channel.unary_unary( - '/core.CoreApi/GetMobilityConfigs', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigsResponse.FromString, - ) - self.GetMobilityConfig = channel.unary_unary( - '/core.CoreApi/GetMobilityConfig', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigResponse.FromString, - ) - self.SetMobilityConfig = channel.unary_unary( - '/core.CoreApi/SetMobilityConfig', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetMobilityConfigRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetMobilityConfigResponse.FromString, - ) - self.MobilityAction = channel.unary_unary( - '/core.CoreApi/MobilityAction', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.MobilityActionRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.MobilityActionResponse.FromString, - ) - self.GetServices = channel.unary_unary( - '/core.CoreApi/GetServices', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetServicesRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetServicesResponse.FromString, - ) - self.GetServiceDefaults = channel.unary_unary( - '/core.CoreApi/GetServiceDefaults', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetServiceDefaultsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetServiceDefaultsResponse.FromString, - ) - self.SetServiceDefaults = channel.unary_unary( - '/core.CoreApi/SetServiceDefaults', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetServiceDefaultsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetServiceDefaultsResponse.FromString, - ) - self.GetNodeService = channel.unary_unary( - '/core.CoreApi/GetNodeService', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceResponse.FromString, - ) - self.GetNodeServiceFile = channel.unary_unary( - '/core.CoreApi/GetNodeServiceFile', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceFileRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceFileResponse.FromString, - ) - self.SetNodeService = channel.unary_unary( - '/core.CoreApi/SetNodeService', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceResponse.FromString, - ) - self.SetNodeServiceFile = channel.unary_unary( - '/core.CoreApi/SetNodeServiceFile', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceFileRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceFileResponse.FromString, - ) - self.ServiceAction = channel.unary_unary( - '/core.CoreApi/ServiceAction', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.ServiceActionRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ServiceActionResponse.FromString, - ) - self.GetWlanConfig = channel.unary_unary( - '/core.CoreApi/GetWlanConfig', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetWlanConfigRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetWlanConfigResponse.FromString, - ) - self.SetWlanConfig = channel.unary_unary( - '/core.CoreApi/SetWlanConfig', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetWlanConfigRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetWlanConfigResponse.FromString, - ) - self.GetEmaneConfig = channel.unary_unary( - '/core.CoreApi/GetEmaneConfig', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneConfigRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneConfigResponse.FromString, - ) - self.SetEmaneConfig = channel.unary_unary( - '/core.CoreApi/SetEmaneConfig', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneConfigRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneConfigResponse.FromString, - ) - self.GetEmaneModels = channel.unary_unary( - '/core.CoreApi/GetEmaneModels', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelsResponse.FromString, - ) - self.GetEmaneModelConfig = channel.unary_unary( - '/core.CoreApi/GetEmaneModelConfig', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigResponse.FromString, - ) - self.SetEmaneModelConfig = channel.unary_unary( - '/core.CoreApi/SetEmaneModelConfig', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneModelConfigRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneModelConfigResponse.FromString, - ) - self.GetEmaneModelConfigs = channel.unary_unary( - '/core.CoreApi/GetEmaneModelConfigs', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigsRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigsResponse.FromString, - ) - self.SaveXml = channel.unary_unary( - '/core.CoreApi/SaveXml', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.SaveXmlRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SaveXmlResponse.FromString, - ) - self.OpenXml = channel.unary_unary( - '/core.CoreApi/OpenXml', - request_serializer=core_dot_api_dot_grpc_dot_core__pb2.OpenXmlRequest.SerializeToString, - response_deserializer=core_dot_api_dot_grpc_dot_core__pb2.OpenXmlResponse.FromString, - ) - - -class CoreApiServicer(object): - # missing associated documentation comment in .proto file - pass - - def CreateSession(self, request, context): - """session rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteSession(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetSessions(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetSession(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetSessionOptions(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetSessionOptions(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetSessionLocation(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetSessionLocation(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetSessionState(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def NodeEvents(self, request, context): - """event streams - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def LinkEvents(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SessionEvents(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ConfigEvents(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ExceptionEvents(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def FileEvents(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def AddNode(self, request, context): - """node rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetNode(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def EditNode(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteNode(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetNodeLinks(self, request, context): - """link rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def AddLink(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def EditLink(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def DeleteLink(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetHooks(self, request, context): - """hook rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def AddHook(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetMobilityConfigs(self, request, context): - """mobility rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetMobilityConfig(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetMobilityConfig(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def MobilityAction(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetServices(self, request, context): - """service rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetServiceDefaults(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetServiceDefaults(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetNodeService(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetNodeServiceFile(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetNodeService(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetNodeServiceFile(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def ServiceAction(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetWlanConfig(self, request, context): - """wlan rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetWlanConfig(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetEmaneConfig(self, request, context): - """emane rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetEmaneConfig(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetEmaneModels(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetEmaneModelConfig(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SetEmaneModelConfig(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def GetEmaneModelConfigs(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def SaveXml(self, request, context): - """xml rpc - """ - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - def OpenXml(self, request, context): - # missing associated documentation comment in .proto file - pass - context.set_code(grpc.StatusCode.UNIMPLEMENTED) - context.set_details('Method not implemented!') - raise NotImplementedError('Method not implemented!') - - -def add_CoreApiServicer_to_server(servicer, server): - rpc_method_handlers = { - 'CreateSession': grpc.unary_unary_rpc_method_handler( - servicer.CreateSession, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.CreateSessionRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.CreateSessionResponse.SerializeToString, - ), - 'DeleteSession': grpc.unary_unary_rpc_method_handler( - servicer.DeleteSession, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteSessionRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteSessionResponse.SerializeToString, - ), - 'GetSessions': grpc.unary_unary_rpc_method_handler( - servicer.GetSessions, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionsResponse.SerializeToString, - ), - 'GetSession': grpc.unary_unary_rpc_method_handler( - servicer.GetSession, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionResponse.SerializeToString, - ), - 'GetSessionOptions': grpc.unary_unary_rpc_method_handler( - servicer.GetSessionOptions, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionOptionsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionOptionsResponse.SerializeToString, - ), - 'SetSessionOptions': grpc.unary_unary_rpc_method_handler( - servicer.SetSessionOptions, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionOptionsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionOptionsResponse.SerializeToString, - ), - 'GetSessionLocation': grpc.unary_unary_rpc_method_handler( - servicer.GetSessionLocation, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionLocationRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetSessionLocationResponse.SerializeToString, - ), - 'SetSessionLocation': grpc.unary_unary_rpc_method_handler( - servicer.SetSessionLocation, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionLocationRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionLocationResponse.SerializeToString, - ), - 'SetSessionState': grpc.unary_unary_rpc_method_handler( - servicer.SetSessionState, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionStateRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetSessionStateResponse.SerializeToString, - ), - 'NodeEvents': grpc.unary_stream_rpc_method_handler( - servicer.NodeEvents, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.NodeEventsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.NodeEvent.SerializeToString, - ), - 'LinkEvents': grpc.unary_stream_rpc_method_handler( - servicer.LinkEvents, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.LinkEventsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.LinkEvent.SerializeToString, - ), - 'SessionEvents': grpc.unary_stream_rpc_method_handler( - servicer.SessionEvents, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SessionEventsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SessionEvent.SerializeToString, - ), - 'ConfigEvents': grpc.unary_stream_rpc_method_handler( - servicer.ConfigEvents, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ConfigEventsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.ConfigEvent.SerializeToString, - ), - 'ExceptionEvents': grpc.unary_stream_rpc_method_handler( - servicer.ExceptionEvents, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ExceptionEventsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.ExceptionEvent.SerializeToString, - ), - 'FileEvents': grpc.unary_stream_rpc_method_handler( - servicer.FileEvents, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.FileEventsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.FileEvent.SerializeToString, - ), - 'AddNode': grpc.unary_unary_rpc_method_handler( - servicer.AddNode, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddNodeRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddNodeResponse.SerializeToString, - ), - 'GetNode': grpc.unary_unary_rpc_method_handler( - servicer.GetNode, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeResponse.SerializeToString, - ), - 'EditNode': grpc.unary_unary_rpc_method_handler( - servicer.EditNode, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.EditNodeRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.EditNodeResponse.SerializeToString, - ), - 'DeleteNode': grpc.unary_unary_rpc_method_handler( - servicer.DeleteNode, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteNodeRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteNodeResponse.SerializeToString, - ), - 'GetNodeLinks': grpc.unary_unary_rpc_method_handler( - servicer.GetNodeLinks, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeLinksRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeLinksResponse.SerializeToString, - ), - 'AddLink': grpc.unary_unary_rpc_method_handler( - servicer.AddLink, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddLinkRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddLinkResponse.SerializeToString, - ), - 'EditLink': grpc.unary_unary_rpc_method_handler( - servicer.EditLink, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.EditLinkRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.EditLinkResponse.SerializeToString, - ), - 'DeleteLink': grpc.unary_unary_rpc_method_handler( - servicer.DeleteLink, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteLinkRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.DeleteLinkResponse.SerializeToString, - ), - 'GetHooks': grpc.unary_unary_rpc_method_handler( - servicer.GetHooks, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetHooksRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetHooksResponse.SerializeToString, - ), - 'AddHook': grpc.unary_unary_rpc_method_handler( - servicer.AddHook, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.AddHookRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.AddHookResponse.SerializeToString, - ), - 'GetMobilityConfigs': grpc.unary_unary_rpc_method_handler( - servicer.GetMobilityConfigs, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigsResponse.SerializeToString, - ), - 'GetMobilityConfig': grpc.unary_unary_rpc_method_handler( - servicer.GetMobilityConfig, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetMobilityConfigResponse.SerializeToString, - ), - 'SetMobilityConfig': grpc.unary_unary_rpc_method_handler( - servicer.SetMobilityConfig, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetMobilityConfigRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetMobilityConfigResponse.SerializeToString, - ), - 'MobilityAction': grpc.unary_unary_rpc_method_handler( - servicer.MobilityAction, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.MobilityActionRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.MobilityActionResponse.SerializeToString, - ), - 'GetServices': grpc.unary_unary_rpc_method_handler( - servicer.GetServices, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetServicesRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetServicesResponse.SerializeToString, - ), - 'GetServiceDefaults': grpc.unary_unary_rpc_method_handler( - servicer.GetServiceDefaults, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetServiceDefaultsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetServiceDefaultsResponse.SerializeToString, - ), - 'SetServiceDefaults': grpc.unary_unary_rpc_method_handler( - servicer.SetServiceDefaults, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetServiceDefaultsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetServiceDefaultsResponse.SerializeToString, - ), - 'GetNodeService': grpc.unary_unary_rpc_method_handler( - servicer.GetNodeService, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceResponse.SerializeToString, - ), - 'GetNodeServiceFile': grpc.unary_unary_rpc_method_handler( - servicer.GetNodeServiceFile, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceFileRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetNodeServiceFileResponse.SerializeToString, - ), - 'SetNodeService': grpc.unary_unary_rpc_method_handler( - servicer.SetNodeService, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceResponse.SerializeToString, - ), - 'SetNodeServiceFile': grpc.unary_unary_rpc_method_handler( - servicer.SetNodeServiceFile, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceFileRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetNodeServiceFileResponse.SerializeToString, - ), - 'ServiceAction': grpc.unary_unary_rpc_method_handler( - servicer.ServiceAction, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.ServiceActionRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.ServiceActionResponse.SerializeToString, - ), - 'GetWlanConfig': grpc.unary_unary_rpc_method_handler( - servicer.GetWlanConfig, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetWlanConfigRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetWlanConfigResponse.SerializeToString, - ), - 'SetWlanConfig': grpc.unary_unary_rpc_method_handler( - servicer.SetWlanConfig, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetWlanConfigRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetWlanConfigResponse.SerializeToString, - ), - 'GetEmaneConfig': grpc.unary_unary_rpc_method_handler( - servicer.GetEmaneConfig, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneConfigRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneConfigResponse.SerializeToString, - ), - 'SetEmaneConfig': grpc.unary_unary_rpc_method_handler( - servicer.SetEmaneConfig, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneConfigRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneConfigResponse.SerializeToString, - ), - 'GetEmaneModels': grpc.unary_unary_rpc_method_handler( - servicer.GetEmaneModels, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelsResponse.SerializeToString, - ), - 'GetEmaneModelConfig': grpc.unary_unary_rpc_method_handler( - servicer.GetEmaneModelConfig, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigResponse.SerializeToString, - ), - 'SetEmaneModelConfig': grpc.unary_unary_rpc_method_handler( - servicer.SetEmaneModelConfig, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneModelConfigRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SetEmaneModelConfigResponse.SerializeToString, - ), - 'GetEmaneModelConfigs': grpc.unary_unary_rpc_method_handler( - servicer.GetEmaneModelConfigs, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigsRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.GetEmaneModelConfigsResponse.SerializeToString, - ), - 'SaveXml': grpc.unary_unary_rpc_method_handler( - servicer.SaveXml, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.SaveXmlRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.SaveXmlResponse.SerializeToString, - ), - 'OpenXml': grpc.unary_unary_rpc_method_handler( - servicer.OpenXml, - request_deserializer=core_dot_api_dot_grpc_dot_core__pb2.OpenXmlRequest.FromString, - response_serializer=core_dot_api_dot_grpc_dot_core__pb2.OpenXmlResponse.SerializeToString, - ), - } - generic_handler = grpc.method_handlers_generic_handler( - 'core.CoreApi', rpc_method_handlers) - server.add_generic_rpc_handlers((generic_handler,)) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 56848cba..deaaf261 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -142,7 +142,7 @@ class CoreInterface(object): """ # treat None and 0 as unchanged values current_value = self._params.get(key) - if current_value is None or current_value == value or current_value <= 0 and value <= 0: + if value is None or current_value == value or current_value <= 0 and value <= 0: return False self._params[key] = value diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 286ea7ef..deddfb48 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -373,7 +373,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.edit_link(session.id, node.id, switch.id, options) + response = client.edit_link(session.id, node.id, switch.id, options, interface_one=interface.id) # then assert response.result is True From 40176e861d38465ce3cad9faac32dc84e0a3a677 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 2 Jun 2019 22:31:23 -0700 Subject: [PATCH 0049/1992] fixed unit test for vnode client, due to changes using python directly --- daemon/tests/test_core.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 176bcdd1..514c3a0a 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -4,10 +4,10 @@ Unit tests for testing basic CORE networks. import os import stat +import subprocess import threading import pytest -from mock import MagicMock from core.emulator.emudata import NodeOptions from core.emulator.enumerations import MessageFlags @@ -113,9 +113,9 @@ class TestCore: status, output = client.cmd_output(command) assert not status p, stdin, stdout, stderr = client.popen(command) - assert not p.status() + assert not p.wait() assert not client.icmd(command) - assert not client.redircmd(MagicMock(), MagicMock(), MagicMock(), command) + assert not client.redircmd(subprocess.PIPE, subprocess.PIPE, subprocess.PIPE, command) assert not client.shcmd(command[0]) # check various command using command line From db9d13aff86d3cde7fdbb4d6a11f6e94b92444b2 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sun, 2 Jun 2019 22:53:39 -0700 Subject: [PATCH 0050/1992] update to setparams check to avoid python3 errors, and fix to ipprefix --- daemon/core/nodes/interface.py | 5 ++++- daemon/core/nodes/ipaddress.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index deaaf261..5424bc1b 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -141,8 +141,11 @@ class CoreInterface(object): :return: True if parameter changed, False otherwise """ # treat None and 0 as unchanged values + if value is None or value <= 0: + return False + current_value = self._params.get(key) - if value is None or current_value == value or current_value <= 0 and value <= 0: + if current_value is not None and current_value == value: return False self._params[key] = value diff --git a/daemon/core/nodes/ipaddress.py b/daemon/core/nodes/ipaddress.py index 23144ce1..f1a763d1 100644 --- a/daemon/core/nodes/ipaddress.py +++ b/daemon/core/nodes/ipaddress.py @@ -236,7 +236,7 @@ class IpPrefix(object): self.prefixlen = int(tmp[1]) else: self.prefixlen = self.addrlen - self.prefix = socket.inet_pton(self.af, tmp[0]).decode("ISO-8859-1") + self.prefix = socket.inet_pton(self.af, tmp[0]) if self.addrlen > self.prefixlen: addrbits = self.addrlen - self.prefixlen netmask = ((1 << self.prefixlen) - 1) << addrbits From bb5e68ad8b9044de047ce8f6f4a37e06c74ccd64 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 3 Jun 2019 11:49:43 -0700 Subject: [PATCH 0051/1992] 2/3 fixes for ip address and mac byte handling --- daemon/core/api/tlv/coreapi.py | 6 ------ daemon/core/nodes/base.py | 2 -- daemon/core/nodes/ipaddress.py | 23 ++++++++++------------- daemon/core/nodes/network.py | 4 ---- 4 files changed, 10 insertions(+), 25 deletions(-) diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index 218dbdf8..40227bcc 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -276,8 +276,6 @@ class CoreTlvDataIpv4Addr(CoreTlvDataObj): :return: Ipv4 address :rtype: core.misc.ipaddress.IpAddress """ - # value = value.decode("ISO-8859-1") - # value = socket.inet_ntoa(value) return IpAddress(af=socket.AF_INET, address=value) @@ -308,8 +306,6 @@ class CoreTlvDataIPv6Addr(CoreTlvDataObj): :return: Ipv4 address :rtype: core.misc.ipaddress.IpAddress """ - # value = value.decode("ISO-8859-1") - # value = socket.inet_ntoa(value) return IpAddress(af=socket.AF_INET6, address=value) @@ -342,8 +338,6 @@ class CoreTlvDataMacAddr(CoreTlvDataObj): :rtype: core.misc.ipaddress.MacAddress """ # only use 48 bits - # value = value.decode("ISO-8859-1") - # value = socket.inet_ntoa(value) return MacAddress(address=value[2:]) diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index a0410d92..219757d2 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1063,13 +1063,11 @@ class CoreNetworkBase(NodeBase): if ipaddress.is_ipv4_address(ip): family = AF_INET ipl = socket.inet_pton(family, ip) - # ipl = ipl.decode("ISO-8859-1") interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip4_mask = mask else: family = AF_INET6 ipl = socket.inet_pton(family, ip) - # ipl = ipl.decode("ISO-8859-1") interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip6_mask = mask diff --git a/daemon/core/nodes/ipaddress.py b/daemon/core/nodes/ipaddress.py index f1a763d1..111bb38c 100644 --- a/daemon/core/nodes/ipaddress.py +++ b/daemon/core/nodes/ipaddress.py @@ -6,6 +6,7 @@ import logging import random import socket import struct +from builtins import bytes from builtins import range from socket import AF_INET from socket import AF_INET6 @@ -31,9 +32,7 @@ class MacAddress(object): :return: string representation :rtype: str """ - logging.info("mac addr: %s", type(self.addr)) - addr = self.addr.decode("ISO-8859-1") - return ":".join("%02x" % ord(x) for x in addr) + return ":".join("%02x" % x for x in bytearray(self.addr)) def to_link_local(self): """ @@ -63,7 +62,7 @@ class MacAddress(object): :return: mac address class :rtype: MacAddress """ - addr = "".join(chr(int(x, 16)) for x in s.split(":")) + addr = b"".join(bytes([int(x, 16)]) for x in s.split(":")) return cls(addr) @classmethod @@ -95,9 +94,7 @@ class IpAddress(object): :return: """ # check if (af, addr) is valid - logging.info("ip address: %s", type(address)) if not socket.inet_ntop(af, address): - # if not socket.inet_ntop(af, address.encode("ISO-8859-1")): raise ValueError("invalid af/addr") self.af = af self.addr = address @@ -128,7 +125,6 @@ class IpAddress(object): :rtype: str """ return socket.inet_ntop(self.af, self.addr) - # return socket.inet_ntop(self.af, self.addr.encode("ISO-8859-1")) def __eq__(self, other): """ @@ -159,14 +155,14 @@ class IpAddress(object): logging.exception("error during addition") return NotImplemented - tmp = [ord(x) for x in self.addr] + tmp = [x for x in bytearray(self.addr)] for i in range(len(tmp) - 1, -1, -1): x = tmp[i] + carry tmp[i] = x & 0xff carry = x >> 8 if carry == 0: break - addr = "".join(chr(x) for x in tmp) + addr = bytes(tmp) return self.__class__(self.af, addr) def __sub__(self, other): @@ -237,12 +233,13 @@ class IpPrefix(object): else: self.prefixlen = self.addrlen self.prefix = socket.inet_pton(self.af, tmp[0]) + self.prefix = bytes(self.prefix) if self.addrlen > self.prefixlen: addrbits = self.addrlen - self.prefixlen netmask = ((1 << self.prefixlen) - 1) << addrbits - prefix = "" + prefix = bytes(b"") for i in range(-1, -(addrbits >> 3) - 2, -1): - prefix = chr(ord(self.prefix[i]) & (netmask & 0xff)) + prefix + prefix = bytes([self.prefix[i] & (netmask & 0xff)]) + prefix netmask >>= 8 self.prefix = self.prefix[:i] + prefix @@ -323,11 +320,11 @@ class IpPrefix(object): self.af == AF_INET and tmp == (1 << (self.addrlen - self.prefixlen)) - 1): raise ValueError("invalid hostid for prefix %s: %s" % (self, hostid)) - addr = "" + addr = bytes(b"") prefix_endpoint = -1 for i in range(-1, -(self.addrlen >> 3) - 1, -1): prefix_endpoint = i - addr = chr(ord(self.prefix[i]) | (tmp & 0xff)) + addr + addr = bytes([self.prefix[i] | (tmp & 0xff)]) + addr tmp >>= 8 if not tmp: break diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 82e20017..b76a035a 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -855,13 +855,11 @@ class PtpNet(CoreNetwork): if ipaddress.is_ipv4_address(ip): family = AF_INET ipl = socket.inet_pton(family, ip) - # ipl = ipl.decode("ISO-8859-1") interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl) interface1_ip4_mask = mask else: family = AF_INET6 ipl = socket.inet_pton(family, ip) - # ipl = ipl.decode("ISO-8859-1") interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl) interface1_ip6_mask = mask @@ -875,13 +873,11 @@ class PtpNet(CoreNetwork): if ipaddress.is_ipv4_address(ip): family = AF_INET ipl = socket.inet_pton(family, ip) - # ipl = ipl.decode("ISO-8859-1") interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip4_mask = mask else: family = AF_INET6 ipl = socket.inet_pton(family, ip) - # ipl = ipl.decode("ISO-8859-1") interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl) interface2_ip6_mask = mask From 69b1297002e4385ea33ba96f0973f39978c4f8f4 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 3 Jun 2019 13:34:54 -0700 Subject: [PATCH 0052/1992] updated protobuf to use string instead of bytes for 2/3 compatibility for now, updated default service in grpc tests, fixed byte string issues for python3 in coreapi --- daemon/core/api/grpc/client.py | 4 ++-- daemon/core/api/grpc/server.py | 8 ++++---- daemon/core/api/tlv/coreapi.py | 4 ++-- daemon/proto/core/api/grpc/core.proto | 16 ++++++++-------- daemon/tests/test_grpc.py | 14 +++++++------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 90141162..a1d218be 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -757,7 +757,7 @@ class CoreGrpcClient(object): """ request = core_pb2.SaveXmlRequest(session_id=session_id) response = self.stub.SaveXml(request) - with open(file_path, "wb") as xml_file: + with open(file_path, "w") as xml_file: xml_file.write(response.data) def open_xml(self, file_path): @@ -768,7 +768,7 @@ class CoreGrpcClient(object): :return: response with opened session id :rtype: core_pb2.OpenXmlResponse """ - with open(file_path, "rb") as xml_file: + with open(file_path, "r") as xml_file: data = xml_file.read() request = core_pb2.OpenXmlRequest(data=data) return self.stub.OpenXml(request) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 4d11cc46..a7955cb1 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -438,14 +438,14 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): if last_check is not None: interval = now - last_check throughputs_event = core_pb2.ThroughputsEvent() - for key, current_rxtx in stats.iteritems(): + for key in stats: + current_rxtx = stats[key] previous_rxtx = last_stats.get(key) if not previous_rxtx: continue rx_kbps = (current_rxtx["rx"] - previous_rxtx["rx"]) * 8.0 / interval tx_kbps = (current_rxtx["tx"] - previous_rxtx["tx"]) * 8.0 / interval throughput = rx_kbps + tx_kbps - print "%s - %s" % (key, throughput) if key.startswith("veth"): key = key.split(".") node_id = int(_INTERFACE_REGEX.search(key[0]).group()) @@ -922,7 +922,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): _, temp_path = tempfile.mkstemp() session.save_xml(temp_path) - with open(temp_path, "rb") as xml_file: + with open(temp_path, "r") as xml_file: data = xml_file.read() return core_pb2.SaveXmlResponse(data=data) @@ -933,7 +933,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session.set_state(EventTypes.CONFIGURATION_STATE) _, temp_path = tempfile.mkstemp() - with open(temp_path, "wb") as xml_file: + with open(temp_path, "w") as xml_file: xml_file.write(request.data) try: diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index 40227bcc..73cb0daf 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -219,12 +219,12 @@ class CoreTlvDataUint16List(CoreTlvData): if not isinstance(values, tuple): raise ValueError("value not a tuple: %s" % values) - data = "" + data = b"" for value in values: data += struct.pack(cls.data_format, value) pad_len = -(CoreTlv.header_len + len(data)) % 4 - return len(data), data + "\0" * pad_len + return len(data), data + b"\0" * pad_len @classmethod def unpack(cls, data): diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 013135f2..8bbf676b 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -242,7 +242,7 @@ message SessionEvent { int32 node_id = 1; int32 event = 2; string name = 3; - bytes data = 4; + string data = 4; float time = 5; int32 session_id = 6; } @@ -283,8 +283,8 @@ message FileEvent { string type = 6; string source = 7; int32 session_id = 8; - bytes data = 9; - bytes compressed_data = 10; + string data = 9; + string compressed_data = 10; } message AddNodeRequest { @@ -487,7 +487,7 @@ message GetNodeServiceFileRequest { } message GetNodeServiceFileResponse { - bytes data = 1; + string data = 1; } message SetNodeServiceRequest { @@ -508,7 +508,7 @@ message SetNodeServiceFileRequest { int32 node_id = 2; string service = 3; string file = 4; - bytes data = 5; + string data = 5; } message SetNodeServiceFileResponse { @@ -610,11 +610,11 @@ message SaveXmlRequest { } message SaveXmlResponse { - bytes data = 1; + string data = 1; } message OpenXmlRequest { - bytes data = 1; + string data = 1; } message OpenXmlResponse { @@ -712,7 +712,7 @@ message ExceptionLevel { message Hook { SessionState.Enum state = 1; string file = 2; - bytes data = 3; + string data = 3; } message ServiceDefaults { diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 8daf88b8..416147d7 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -677,7 +677,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.get_node_service(session.id, node.id, "IPForward") + response = client.get_node_service(session.id, node.id, "DefaultRoute") # then assert len(response.service.configs) > 0 @@ -690,7 +690,7 @@ class TestGrpc: # then with client.context_connect(): - response = client.get_node_service_file(session.id, node.id, "IPForward", "ipforward.sh") + response = client.get_node_service_file(session.id, node.id, "DefaultRoute", "defaultroute.sh") # then assert response.data is not None @@ -700,7 +700,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node() - service_name = "IPForward" + service_name = "DefaultRoute" validate = ["echo hello"] # then @@ -717,8 +717,8 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node() - service_name = "IPForward" - file_name = "ipforward.sh" + service_name = "DefaultRoute" + file_name = "defaultroute.sh" file_data = "echo hello" # then @@ -735,7 +735,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() node = session.add_node() - service_name = "IPForward" + service_name = "DefaultRoute" # then with client.context_connect(): @@ -881,7 +881,7 @@ class TestGrpc: with client.context_connect(): client.events(session.id, handle_event) time.sleep(0.1) - file_data = session.services.get_service_file(node, "IPForward", "ipforward.sh") + file_data = session.services.get_service_file(node, "DefaultRoute", "defaultroute.sh") session.broadcast_file(file_data) # then From 597bd219949caa4067f90e88d2fe9e0be40e5f04 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 3 Jun 2019 14:36:21 -0700 Subject: [PATCH 0053/1992] updated logging.warn to logging.warning due to deprecation in python3, fixed python 2/3 filter issue in tests, fixed utf decoding for some missed popen commands --- daemon/core/api/tlv/broker.py | 20 ++++++------- daemon/core/api/tlv/corehandlers.py | 44 ++++++++++++++-------------- daemon/core/emane/commeffect.py | 4 +-- daemon/core/emane/emanemanager.py | 2 +- daemon/core/emane/emanemodel.py | 2 +- daemon/core/emulator/session.py | 4 +-- daemon/core/location/mobility.py | 8 ++--- daemon/core/nodes/client.py | 16 +++++----- daemon/core/nodes/network.py | 7 ++++- daemon/core/services/coreservices.py | 19 ++++++------ daemon/core/utils.py | 2 +- daemon/core/xml/emanexml.py | 2 +- daemon/tests/test_core.py | 4 +-- daemon/tests/test_links.py | 2 +- 14 files changed, 70 insertions(+), 66 deletions(-) diff --git a/daemon/core/api/tlv/broker.py b/daemon/core/api/tlv/broker.py index d7499eaa..d94195ab 100644 --- a/daemon/core/api/tlv/broker.py +++ b/daemon/core/api/tlv/broker.py @@ -236,7 +236,7 @@ class CoreBroker(object): return 0 if len(msghdr) != coreapi.CoreMessage.header_len: - logging.warn("warning: broker received not enough data len=%s", len(msghdr)) + logging.warning("warning: broker received not enough data len=%s", len(msghdr)) return len(msghdr) msgtype, msgflags, msglen = coreapi.CoreMessage.unpack_header(msghdr) @@ -413,7 +413,7 @@ class CoreBroker(object): remotenum = n2num if key in self.tunnels.keys(): - logging.warn("tunnel with key %s (%s-%s) already exists!", key, n1num, n2num) + logging.warning("tunnel with key %s (%s-%s) already exists!", key, n1num, n2num) else: _id = key & ((1 << 16) - 1) logging.info("adding tunnel for %s-%s to %s with key %s", n1num, n2num, remoteip, key) @@ -452,17 +452,17 @@ class CoreBroker(object): # add other nets here that do not require tunnels if nodeutils.is_node(net, NodeTypes.EMANE_NET): - logging.warn("emane network does not require a tunnel") + logging.warning("emane network does not require a tunnel") return None server_interface = getattr(net, "serverintf", None) if nodeutils.is_node(net, NodeTypes.CONTROL_NET) and server_interface is not None: - logging.warn("control networks with server interfaces do not need a tunnel") + logging.warning("control networks with server interfaces do not need a tunnel") return None servers = self.getserversbynode(node_id) if len(servers) < 2: - logging.warn("not enough servers to create a tunnel: %s", servers) + logging.warning("not enough servers to create a tunnel: %s", servers) return None hosts = [] @@ -678,7 +678,7 @@ class CoreBroker(object): """ server = self.getserverbyname(servername) if server is None: - logging.warn("ignoring unknown server: %s", servername) + logging.warning("ignoring unknown server: %s", servername) return if server.sock is None or server.host is None or server.port is None: @@ -754,10 +754,10 @@ class CoreBroker(object): try: nodecls = nodeutils.get_node_class(NodeTypes(nodetype)) except KeyError: - logging.warn("broker invalid node type %s", nodetype) + logging.warning("broker invalid node type %s", nodetype) return handle_locally, servers if nodecls is None: - logging.warn("broker unimplemented node type %s", nodetype) + logging.warning("broker unimplemented node type %s", nodetype) return handle_locally, servers if issubclass(nodecls, CoreNetworkBase) and nodetype != NodeTypes.WIRELESS_LAN.value: # network node replicated on all servers; could be optimized @@ -1091,12 +1091,12 @@ class CoreBroker(object): control_nets = value.split() if len(control_nets) < 2: - logging.warn("multiple controlnet prefixes do not exist") + logging.warning("multiple controlnet prefixes do not exist") return servers = self.session.broker.getservernames() if len(servers) < 2: - logging.warn("not distributed") + logging.warning("not distributed") return servers.remove("localhost") diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 5f123a07..3f776d3d 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -120,7 +120,7 @@ class CoreHandler(socketserver.BaseRequestHandler): time.sleep(1) wait += 1 if wait == timeout: - logging.warn("queue failed to be empty, finishing request handler") + logging.warning("queue failed to be empty, finishing request handler") break logging.info("client disconnected: notifying threads") @@ -129,7 +129,7 @@ class CoreHandler(socketserver.BaseRequestHandler): logging.info("waiting for thread: %s", thread.getName()) thread.join(timeout) if thread.isAlive(): - logging.warn("joining %s failed: still alive after %s sec", thread.getName(), timeout) + logging.warning("joining %s failed: still alive after %s sec", thread.getName(), timeout) logging.info("connection closed: %s", self.client_address) if self.session: @@ -422,7 +422,7 @@ class CoreHandler(socketserver.BaseRequestHandler): message_type, message_flags, message_len = coreapi.CoreMessage.unpack_header(header) if message_len == 0: - logging.warn("received message with no data") + logging.warning("received message with no data") data = b"" while len(data) < message_len: @@ -629,7 +629,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ replies = [] if message.flags & MessageFlags.ADD.value and message.flags & MessageFlags.DELETE.value: - logging.warn("ignoring invalid message: add and delete flag both set") + logging.warning("ignoring invalid message: add and delete flag both set") return () node_type = None @@ -1009,7 +1009,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.location.reset() else: if not config_data.data_values: - logging.warn("location data missing") + logging.warning("location data missing") else: values = [float(x) for x in config_data.data_values.split("|")] @@ -1144,7 +1144,7 @@ class CoreHandler(socketserver.BaseRequestHandler): node = self.session.get_node(node_id) if node is None: - logging.warn("request to configure service for unknown node %s", node_id) + logging.warning("request to configure service for unknown node %s", node_id) return replies services = ServiceShim.servicesfromopaque(opaque) @@ -1247,7 +1247,7 @@ class CoreHandler(socketserver.BaseRequestHandler): model_class = self.session.mobility.models.get(object_name) if not model_class: - logging.warn("model class does not exist: %s", object_name) + logging.warning("model class does not exist: %s", object_name) return [] config = self.session.mobility.get_model_config(node_id, object_name) @@ -1256,7 +1256,7 @@ class CoreHandler(socketserver.BaseRequestHandler): elif message_type != ConfigFlags.RESET: # store the configuration values for later use, when the node if not object_name: - logging.warn("no configuration object for node: %s", node_id) + logging.warning("no configuration object for node: %s", node_id) return [] parsed_config = {} @@ -1317,7 +1317,7 @@ class CoreHandler(socketserver.BaseRequestHandler): model_class = self.session.emane.models.get(object_name) if not model_class: - logging.warn("model class does not exist: %s", object_name) + logging.warning("model class does not exist: %s", object_name) return [] config = self.session.emane.get_model_config(node_id, object_name) @@ -1326,7 +1326,7 @@ class CoreHandler(socketserver.BaseRequestHandler): elif message_type != ConfigFlags.RESET: # store the configuration values for later use, when the node if not object_name: - logging.warn("no configuration object for node: %s", node_id) + logging.warning("no configuration object for node: %s", node_id) return [] parsed_config = {} @@ -1353,11 +1353,11 @@ class CoreHandler(socketserver.BaseRequestHandler): compressed_data = message.get_tlv(FileTlvs.COMPRESSED_DATA.value) if compressed_data: - logging.warn("Compressed file data not implemented for File message.") + logging.warning("Compressed file data not implemented for File message.") return () if source_name and data: - logging.warn("ignoring invalid File message: source and data TLVs are both present") + logging.warning("ignoring invalid File message: source and data TLVs are both present") return () # some File Messages store custom files in services, @@ -1435,7 +1435,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.start_mobility(node_ids=(node.id,)) return () - logging.warn("dropping unhandled Event message with node number") + logging.warning("dropping unhandled Event message with node number") return () self.session.set_state(event_type) @@ -1454,7 +1454,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.send_node_emulation_id(_id) elif event_type == EventTypes.RUNTIME_STATE: if self.session.master: - logging.warn("Unexpected event message: RUNTIME state received at session master") + logging.warning("Unexpected event message: RUNTIME state received at session master") else: # master event queue is started in session.checkruntime() self.session.start_events() @@ -1462,7 +1462,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.data_collect() elif event_type == EventTypes.SHUTDOWN_STATE: if self.session.master: - logging.warn("Unexpected event message: SHUTDOWN state received at session master") + logging.warning("Unexpected event message: SHUTDOWN state received at session master") elif event_type in {EventTypes.START, EventTypes.STOP, EventTypes.RESTART, EventTypes.PAUSE, EventTypes.RECONFIGURE}: handled = False @@ -1477,7 +1477,7 @@ class CoreHandler(socketserver.BaseRequestHandler): self.session.mobility_event(event_data) handled = True if not handled: - logging.warn("Unhandled event message: event type %s ", event_type.name) + logging.warning("Unhandled event message: event type %s ", event_type.name) elif event_type == EventTypes.FILE_OPEN: filename = event_data.name self.session.open_xml(filename, start=False) @@ -1492,14 +1492,14 @@ class CoreHandler(socketserver.BaseRequestHandler): name = event_data.name data = event_data.data if etime is None: - logging.warn("Event message scheduled event missing start time") + logging.warning("Event message scheduled event missing start time") return () if message.flags & MessageFlags.ADD.value: self.session.add_event(float(etime), node=node, name=name, data=data) else: raise NotImplementedError else: - logging.warn("unhandled event message: event type %s", event_type) + logging.warning("unhandled event message: event type %s", event_type) return () @@ -1518,7 +1518,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: node = self.session.get_node(node_id) except KeyError: - logging.warn("ignoring event for service '%s', unknown node '%s'", name, node_id) + logging.warning("ignoring event for service '%s', unknown node '%s'", name, node_id) return fail = "" @@ -1556,7 +1556,7 @@ class CoreHandler(socketserver.BaseRequestHandler): if num > 1: unknown_data += ", " num -= 1 - logging.warn("Event requested for unknown service(s): %s", unknown_data) + logging.warning("Event requested for unknown service(s): %s", unknown_data) unknown_data = "Unknown:" + unknown_data event_data = EventData( @@ -1595,7 +1595,7 @@ class CoreHandler(socketserver.BaseRequestHandler): session = self.coreemu.sessions.get(session_id) if session is None: - logging.warn("session %s not found", session_id) + logging.warning("session %s not found", session_id) continue logging.info("request to modify to session: %s", session.id) @@ -1655,7 +1655,7 @@ class CoreHandler(socketserver.BaseRequestHandler): logging.info("request to terminate session %s", session_id) self.coreemu.delete_session(session_id) else: - logging.warn("unhandled session flags for session %s", session_id) + logging.warning("unhandled session flags for session %s", session_id) return () diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index 109262e8..318ac034 100644 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -115,11 +115,11 @@ class EmaneCommEffectModel(emanemodel.EmaneModel): """ service = self.session.emane.service if service is None: - logging.warn("%s: EMANE event service unavailable", self.name) + logging.warning("%s: EMANE event service unavailable", self.name) return if netif is None or netif2 is None: - logging.warn("%s: missing NEM information", self.name) + logging.warning("%s: missing NEM information", self.name) return # TODO: batch these into multiple events per transmission diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 9a155231..aec34e2f 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -807,7 +807,7 @@ class EmaneManager(ModelManager): for event in events: txnemid, attrs = event if "latitude" not in attrs or "longitude" not in attrs or "altitude" not in attrs: - logging.warn("dropped invalid location event") + logging.warning("dropped invalid location event") continue # yaw,pitch,roll,azimuth,elevation,velocity are unhandled diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index 6e5b641d..bfbfc119 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -156,4 +156,4 @@ class EmaneModel(WirelessModel): :param core.netns.vif.Veth netif2: interface two :return: nothing """ - logging.warn("emane model(%s) does not support link configuration", self.name) + logging.warning("emane model(%s) does not support link configuration", self.name) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index b9d24f94..89484951 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1038,7 +1038,7 @@ class Session(object): if os.path.isfile(environment_config_file): utils.load_config(environment_config_file, env) except IOError: - logging.warn("environment configuration file does not exist: %s", environment_config_file) + logging.warning("environment configuration file does not exist: %s", environment_config_file) # attempt to read and add user environment file if self.user: @@ -1625,7 +1625,7 @@ class Session(object): if current_time > 0: if event_time <= current_time: - logging.warn("could not schedule past event for time %s (run time is now %s)", event_time, current_time) + logging.warning("could not schedule past event for time %s (run time is now %s)", event_time, current_time) return event_time = event_time - current_time diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index c3192d9e..af75bc12 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -71,7 +71,7 @@ class MobilityManager(ModelManager): try: node = self.session.get_node(node_id) except KeyError: - logging.warn("skipping mobility configuration for unknown node: %s", node_id) + logging.warning("skipping mobility configuration for unknown node: %s", node_id) continue for model_name in self.models: @@ -111,7 +111,7 @@ class MobilityManager(ModelManager): try: cls = self.models[model] except KeyError: - logging.warn("Ignoring event for unknown model '%s'", model) + logging.warning("Ignoring event for unknown model '%s'", model) continue if cls.config_type in [RegisterTlvs.WIRELESS.value, RegisterTlvs.MOBILITY.value]: @@ -120,11 +120,11 @@ class MobilityManager(ModelManager): continue if model is None: - logging.warn("Ignoring event, %s has no model", node.name) + logging.warning("Ignoring event, %s has no model", node.name) continue if cls.name != model.name: - logging.warn("Ignoring event for %s wrong model %s,%s", node.name, cls.name, model.name) + logging.warning("Ignoring event for %s wrong model %s,%s", node.name, cls.name, model.name) continue if event_type == EventTypes.STOP.value or event_type == EventTypes.RESTART.value: diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index b09da1fa..486a0c9f 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -169,7 +169,7 @@ class VnodeClient(object): # wait for and return exit status status = p.wait() if status: - logging.warn("cmd exited with status %s: %s", status, args) + logging.warning("cmd exited with status %s: %s", status, args) return status def term(self, sh="/bin/sh"): @@ -247,16 +247,16 @@ class VnodeClient(object): elif line[3] == "link": interface["inet6link"].append(line[1]) else: - logging.warn("unknown scope: %s" % line[3]) + logging.warning("unknown scope: %s" % line[3]) err = stderr.read() stdout.close() stderr.close() status = p.wait() if status: - logging.warn("nonzero exist status (%s) for cmd: %s", status, args) + logging.warning("nonzero exist status (%s) for cmd: %s", status, args) if err: - logging.warn("error output: %s", err) + logging.warning("error output: %s", err) self._addr[ifname] = interface return interface @@ -275,11 +275,11 @@ class VnodeClient(object): # ignore first line stdout.readline() # second line has count names - tmp = stdout.readline().strip().split("|") + tmp = stdout.readline().decode("utf-8").strip().split("|") rxkeys = tmp[1].split() txkeys = tmp[2].split() for line in stdout: - line = line.strip().split() + line = line.decode("utf-8").strip().split() devname, tmp = line[0].split(":") if tmp: line.insert(1, tmp) @@ -296,9 +296,9 @@ class VnodeClient(object): stderr.close() status = p.wait() if status: - logging.warn("nonzero exist status (%s) for cmd: %s", status, args) + logging.warning("nonzero exist status (%s) for cmd: %s", status, args) if err: - logging.warn("error output: %s", err) + logging.warning("error output: %s", err) if ifname is not None: return stats[ifname] else: diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b76a035a..2a57932d 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -481,7 +481,12 @@ class CoreNetwork(CoreNetworkBase): netem += ["loss", "%s%%" % min(loss, 100)] if duplicate is not None and duplicate > 0: netem += ["duplicate", "%s%%" % min(duplicate, 100)] - if delay <= 0 and jitter <= 0 and loss <= 0 and duplicate <= 0: + + delay_check = delay is None or delay <= 0 + jitter_check = jitter is None or jitter <= 0 + loss_check = loss is None or loss <= 0 + duplicate_check = duplicate is None or duplicate <= 0 + if all([delay_check, jitter_check, loss_check, duplicate_check]): # possibly remove netem if it exists and parent queue wasn't removed if not netif.getparam("has_netem"): return diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 378b1a78..59448fd2 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -7,14 +7,13 @@ a list of available services to the GUI and for configuring individual services. """ +import enum import logging import time from multiprocessing.pool import ThreadPool -import enum -from core.constants import which - from core import CoreCommandError, utils +from core.constants import which from core.emulator.data import FileData from core.emulator.enumerations import MessageFlags from core.emulator.enumerations import RegisterTlvs @@ -318,7 +317,7 @@ class CoreServices(object): logging.debug("checking for service with service manager: %s", name) service = ServiceManager.get(name) if not service: - logging.warn("default service %s is unknown", name) + logging.warning("default service %s is unknown", name) else: results.append(service) return results @@ -376,7 +375,7 @@ class CoreServices(object): for service_name in services: service = self.get_service(node.id, service_name, default_service=True) if not service: - logging.warn("unknown service(%s) for node(%s)", service_name, node.name) + logging.warning("unknown service(%s) for node(%s)", service_name, node.name) continue logging.info("adding service to node(%s): %s", node.name, service_name) node.addservice(service) @@ -464,15 +463,15 @@ class CoreServices(object): :return: nothing """ logging.info("starting node(%s) service(%s) validation(%s)", node.name, service.name, - service.validation_mode.name) + service.validation_mode.name) # create service directories for directory in service.dirs: try: node.privatedir(directory) except (CoreCommandError, ValueError) as e: - logging.warn("error mounting private dir '%s' for service '%s': %s", - directory, service.name, e) + logging.warning("error mounting private dir '%s' for service '%s': %s", + directory, service.name, e) # create service files self.create_service_files(node, service) @@ -640,13 +639,13 @@ class CoreServices(object): # retrieve custom service service = self.get_service(node_id, service_name) if service is None: - logging.warn("received file name for unknown service: %s", service_name) + logging.warning("received file name for unknown service: %s", service_name) return # validate file being set is valid config_files = service.configs if file_name not in config_files: - logging.warn("received unknown file(%s) for service(%s)", file_name, service_name) + logging.warning("received unknown file(%s) for service(%s)", file_name, service_name) return # set custom service file data diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 1b53662d..07d2104c 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -377,7 +377,7 @@ def load_classes(path, clazz): # validate path exists logging.debug("attempting to load modules from path: %s", path) if not os.path.isdir(path): - logging.warn("invalid custom module directory specified" ": %s" % path) + logging.warning("invalid custom module directory specified" ": %s" % path) # check if path is in sys.path parent_path = os.path.dirname(path) if parent_path not in sys.path: diff --git a/daemon/core/xml/emanexml.py b/daemon/core/xml/emanexml.py index 1696f790..a4833e24 100644 --- a/daemon/core/xml/emanexml.py +++ b/daemon/core/xml/emanexml.py @@ -112,7 +112,7 @@ def build_node_platform_xml(emane_manager, control_net, node, nem_id, platform_x nem_entries = {} if node.model is None: - logging.warn("warning: EmaneNode %s has no associated model", node.name) + logging.warning("warning: EmaneNode %s has no associated model", node.name) return nem_entries for netif in node.netifs(): diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 514c3a0a..4750a24e 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -36,9 +36,9 @@ def createclients(sessiondir, clientcls=VnodeClient, cmdchnlfilterfunc=None): :rtype: list """ direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir)) - cmdchnls = filter(lambda x: stat.S_ISSOCK(os.stat(x).st_mode), direntries) + cmdchnls = list(filter(lambda x: stat.S_ISSOCK(os.stat(x).st_mode), direntries)) if cmdchnlfilterfunc: - cmdchnls = filter(cmdchnlfilterfunc, cmdchnls) + cmdchnls = list(filter(cmdchnlfilterfunc, cmdchnls)) cmdchnls.sort() return map(lambda x: clientcls(os.path.basename(x), x), cmdchnls) diff --git a/daemon/tests/test_links.py b/daemon/tests/test_links.py index fa3a3a5c..00e8fbd9 100644 --- a/daemon/tests/test_links.py +++ b/daemon/tests/test_links.py @@ -31,7 +31,7 @@ def iperf(from_node, to_node, ip_prefixes): vcmd, stdin, stdout, stderr = to_node.client.popen(["iperf", "-s", "-u", "-y", "C"]) from_node.cmd(["iperf", "-u", "-t", "5", "-c", address]) to_node.cmd(["killall", "-9", "iperf"]) - return stdout.read().strip() + return stdout.read().decode("utf-8").strip() class TestLinks: From 1af41d3ec6e5f9b4eab7d15b8913ebac6e19ba5e Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Mon, 3 Jun 2019 14:51:56 -0700 Subject: [PATCH 0054/1992] updated corefx to use protobuf bytes to string changes --- .../com/core/client/grpc/CoreGrpcClient.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index bc9098b3..4c1484d4 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -18,6 +18,8 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -527,7 +529,7 @@ public class CoreGrpcClient implements ICoreClient { .build(); try { CoreProto.GetNodeServiceFileResponse response = blockingStub.getNodeServiceFile(request); - return response.getData().toStringUtf8(); + return response.getData(); } catch (StatusRuntimeException ex) { throw new IOException(ex); } @@ -600,7 +602,7 @@ public class CoreGrpcClient implements ICoreClient { .setNodeId(node.getId()) .setService(serviceName) .setFile(serviceFile.getName()) - .setData(ByteString.copyFromUtf8(serviceFile.getData())) + .setData(serviceFile.getData()) .build(); try { CoreProto.SetNodeServiceFileResponse response = blockingStub.setNodeServiceFile(request); @@ -696,7 +698,7 @@ public class CoreGrpcClient implements ICoreClient { try { CoreProto.SaveXmlResponse response = blockingStub.saveXml(request); try (PrintWriter writer = new PrintWriter(file)) { - writer.print(response.getData().toStringUtf8()); + writer.print(response.getData()); } } catch (StatusRuntimeException ex) { throw new IOException(ex); @@ -705,16 +707,18 @@ public class CoreGrpcClient implements ICoreClient { @Override public SessionOverview openSession(File file) throws IOException { - ByteString data = ByteString.readFrom(new FileInputStream(file)); - CoreProto.OpenXmlRequest request = CoreProto.OpenXmlRequest.newBuilder() - .setData(data) - .build(); try { + byte[] encoded = Files.readAllBytes(file.toPath()); + String data = new String(encoded, StandardCharsets.UTF_8); + CoreProto.OpenXmlRequest request = CoreProto.OpenXmlRequest.newBuilder() + .setData(data) + .build(); + CoreProto.OpenXmlResponse response = blockingStub.openXml(request); SessionOverview sessionOverview = new SessionOverview(); sessionOverview.setId(response.getSessionId()); return sessionOverview; - } catch (StatusRuntimeException ex) { + } catch (IOException | StatusRuntimeException ex) { throw new IOException(ex); } } @@ -873,7 +877,7 @@ public class CoreGrpcClient implements ICoreClient { public boolean createHook(Hook hook) throws IOException { CoreProto.Hook hookProto = CoreProto.Hook.newBuilder() .setStateValue(hook.getState()) - .setData(ByteString.copyFromUtf8(hook.getData())) + .setData(hook.getData()) .setFile(hook.getFile()) .build(); CoreProto.AddHookRequest request = CoreProto.AddHookRequest.newBuilder() @@ -896,7 +900,7 @@ public class CoreGrpcClient implements ICoreClient { for (CoreProto.Hook protoHook : response.getHooksList()) { Hook hook = new Hook(); hook.setFile(protoHook.getFile()); - hook.setData(protoHook.getData().toStringUtf8()); + hook.setData(protoHook.getData()); hook.setState(protoHook.getStateValue()); hooks.add(hook); } @@ -1193,7 +1197,7 @@ public class CoreGrpcClient implements ICoreClient { // mobility script event } else if (state.getValue() <= 9) { Integer nodeId = event.getNodeId(); - String[] values = event.getData().toStringUtf8().split("\\s+"); + String[] values = event.getData().split("\\s+"); Integer start = Integer.parseInt(values[0].split("=")[1]); Integer end = Integer.parseInt(values[1].split("=")[1]); logger.info(String.format("node(%s) mobility event (%s) - start(%s) stop(%s)", From c9baf66f3d04564858f7e602460fe3eb1a673d37 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 3 Jun 2019 16:49:55 -0700 Subject: [PATCH 0055/1992] fixed utils.cmd_output to utf-8 --- daemon/core/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 07d2104c..366c8b44 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -207,7 +207,7 @@ def cmd_output(args): p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, _ = p.communicate() status = p.wait() - return status, stdout.strip() + return status, stdout.decode("utf-8").strip() except OSError: raise CoreCommandError(-1, args) From 823fda9c97971432ed27ba26eb83a356e7cd6615 Mon Sep 17 00:00:00 2001 From: Kevin Larson Date: Mon, 3 Jun 2019 18:22:06 -0700 Subject: [PATCH 0056/1992] Switched write to writebytes for python3 support --- daemon/core/xml/corexml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index df9101c1..a3bcf07e 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -13,7 +13,7 @@ from core.nodes.ipaddress import MacAddress def write_xml_file(xml_element, file_path, doctype=None): xml_data = etree.tostring(xml_element, xml_declaration=True, pretty_print=True, encoding="UTF-8", doctype=doctype) - with open(file_path, "w") as xml_file: + with open(file_path, "wb") as xml_file: xml_file.write(xml_data) From c4c1a10f050bec3d8d1b49663ddb0fd32de694d3 Mon Sep 17 00:00:00 2001 From: Kevin Larson Date: Mon, 3 Jun 2019 18:22:25 -0700 Subject: [PATCH 0057/1992] Added compatible idioms for configparser and queue to support both python2 and python3 --- daemon/core/api/tlv/corehandlers.py | 6 +++--- daemon/scripts/core-daemon | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 3f776d3d..50a62e1c 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -4,7 +4,7 @@ socket server request handlers leveraged by core servers. import logging import os -import queue +from queue import Queue, Empty import shlex import shutil import socketserver @@ -69,7 +69,7 @@ class CoreHandler(socketserver.BaseRequestHandler): MessageTypes.EVENT.value: self.handle_event_message, MessageTypes.SESSION.value: self.handle_session_message, } - self.message_queue = queue.Queue() + self.message_queue = Queue() self.node_status_request = {} self._shutdown_lock = threading.Lock() self._sessions_lock = threading.Lock() @@ -466,7 +466,7 @@ class CoreHandler(socketserver.BaseRequestHandler): try: message = self.message_queue.get(timeout=1) self.handle_message(message) - except queue.Empty: + except Empty: pass def handle_message(self, message): diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index 309342b0..14170326 100755 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -6,7 +6,7 @@ message handlers are defined and some support for sending messages. """ import argparse -import configparser +from configparser import ConfigParser import logging import sys import threading @@ -107,7 +107,7 @@ def get_merged_config(filename): if args.configfile is not None: filename = args.configfile del args.configfile - cfg = configparser.ConfigParser(defaults) + cfg = ConfigParser(defaults) cfg.read(filename) section = "core-daemon" From f5efecabf0573a50aedbf192882cd838908ea699 Mon Sep 17 00:00:00 2001 From: bharnden Date: Mon, 3 Jun 2019 21:22:11 -0700 Subject: [PATCH 0058/1992] byte string fix in coreapi, different approach to urlparse compatibility to avoid other issues --- daemon/core/api/tlv/coreapi.py | 4 +--- daemon/core/plugins/sdt.py | 8 ++------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index 73cb0daf..627e6714 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -748,13 +748,11 @@ class CoreMessage(object): :return: packed data :rtype: str """ - tlv_data = "" keys = sorted(self.tlv_data.keys()) - + tlv_data = b"" for key in keys: value = self.tlv_data[key] tlv_data += self.tlv_class.pack(key, value) - return tlv_data def repack(self): diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 0cd200ba..79d7597c 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -4,14 +4,9 @@ sdt.py: Scripted Display Tool (SDT3D) helper import logging import socket - -from future.standard_library import install_aliases -install_aliases() - -from urllib.parse import urlparse +from future.moves.urllib.parse import urlparse from core import constants -from core.nodes.base import NodeBase, CoreNetworkBase from core.emulator.enumerations import EventTypes from core.emulator.enumerations import LinkTlvs from core.emulator.enumerations import LinkTypes @@ -20,6 +15,7 @@ from core.emulator.enumerations import MessageTypes from core.emulator.enumerations import NodeTlvs from core.emulator.enumerations import NodeTypes from core.nodes import nodeutils +from core.nodes.base import NodeBase, CoreNetworkBase # TODO: A named tuple may be more appropriate, than abusing a class dict like this From 23ae71bed3368784f5286526da3ab56001c58367 Mon Sep 17 00:00:00 2001 From: bharnden Date: Mon, 3 Jun 2019 21:59:29 -0700 Subject: [PATCH 0059/1992] make netns install programs to bin, since c extension is no longer used, added hack to endforce dist-packages --- configure.ac | 2 ++ netns/Makefile.am | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 17637088..340f6139 100644 --- a/configure.ac +++ b/configure.ac @@ -110,6 +110,8 @@ if test "x$enable_daemon" = "xyes"; then AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname]) AM_PATH_PYTHON(2.7) + pythondir=`echo ${pythondir} | sed s,site-packages,dist-packages,` + AC_SUBST(pythondir,$pythondir) AC_CHECK_PROG(brctl_path, brctl, $as_dir, no, $SEARCHPATH) if test "x$brctl_path" = "xno" ; then diff --git a/netns/Makefile.am b/netns/Makefile.am index 77a8f228..3fdc0cb6 100644 --- a/netns/Makefile.am +++ b/netns/Makefile.am @@ -20,7 +20,7 @@ SRC_VCMD = vcmd_main.c vnode_client.c \ vnode_client.h SRC_NETNS = netns_main.c netns.c netns.h -noinst_PROGRAMS = vnoded vcmd netns +bin_PROGRAMS = vnoded vcmd netns vnoded_LDADD = @libev_LIBS@ vnoded_SOURCES = $(SRC_COMMON) $(SRC_VNODED) vcmd_LDADD = @libev_LIBS@ From 675dd0614d93636a4bf3d9efd276f77198cc1c1b Mon Sep 17 00:00:00 2001 From: bharnden Date: Tue, 4 Jun 2019 10:54:09 -0700 Subject: [PATCH 0060/1992] fixed bad variable name in session.py --- daemon/core/emulator/session.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 89484951..60f2d282 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1249,7 +1249,7 @@ class Session(object): for node_id in self.nodes: node = self.nodes[node_id] is_p2p_ctrlnet = nodeutils.is_node(node, (NodeTypes.PEER_TO_PEER, NodeTypes.CONTROL_NET)) - is_tap = nodeutils.is_node(node, NodeTypes.TAP_BRIDGE) and not nodeutils.is_node(x, NodeTypes.TUNNEL) + is_tap = nodeutils.is_node(node, NodeTypes.TAP_BRIDGE) and not nodeutils.is_node(node, NodeTypes.TUNNEL) if is_p2p_ctrlnet or is_tap: continue From d1114bd804bd932a13638beed2ba47f0513fbd8e Mon Sep 17 00:00:00 2001 From: bharnden Date: Tue, 4 Jun 2019 10:55:08 -0700 Subject: [PATCH 0061/1992] fixed byte/str issue in coreapi tlv mac addr --- daemon/core/api/tlv/coreapi.py | 2 +- daemon/core/api/tlv/structutils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index 627e6714..e5b09f27 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -326,7 +326,7 @@ class CoreTlvDataMacAddr(CoreTlvDataObj): :return: """ # extend to 64 bits - return "\0\0" + obj.addr + return b"\0\0" + obj.addr @staticmethod def new_obj(value): diff --git a/daemon/core/api/tlv/structutils.py b/daemon/core/api/tlv/structutils.py index affca97a..9f6e82f3 100644 --- a/daemon/core/api/tlv/structutils.py +++ b/daemon/core/api/tlv/structutils.py @@ -38,7 +38,7 @@ def pack_values(clazz, packers): value = transformer(value) # pack and add to existing data - logging.debug("packing: %s - %s", tlv_type, value) + logging.debug("packing: %s - %s type(%s)", tlv_type, value, type(value)) data += clazz.pack(tlv_type.value, value) return data From 6c861a6ff8db65c39643fb670865ac04f7e1cc4b Mon Sep 17 00:00:00 2001 From: bharnden Date: Tue, 4 Jun 2019 10:59:02 -0700 Subject: [PATCH 0062/1992] fixed issues related to writing xml with tunnel nodes using othernet, fixed reading x,y as int instead of float due to packing issues for tlv --- daemon/core/xml/corexml.py | 90 +++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 39 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index a3bcf07e..91c6ece6 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -390,10 +390,13 @@ class CoreXmlWriter(object): is_network_or_rj45 = isinstance(node, (core.nodes.base.CoreNetworkBase, core.nodes.physical.Rj45Node)) is_controlnet = nodeutils.is_node(node, NodeTypes.CONTROL_NET) if is_network_or_rj45 and not is_controlnet: + logging.info("network node: %s", node) self.write_network(node) # device node elif isinstance(node, core.nodes.base.CoreNodeBase): self.write_device(node) + else: + logging.error("unknown node: %s", node) # add known links links.extend(node.all_link_data(0)) @@ -409,7 +412,7 @@ class CoreXmlWriter(object): if nodeutils.is_node(node, (NodeTypes.SWITCH, NodeTypes.HUB)): for netif in node.netifs(sort=True): othernet = getattr(netif, "othernet", None) - if othernet and othernet.id != node.id: + if othernet and othernet.id == node.id: logging.info("writer ignoring node(%s) othernet(%s)", node.name, othernet.name) return @@ -434,6 +437,29 @@ class CoreXmlWriter(object): device = DeviceElement(self.session, node) self.devices.append(device.element) + def create_interface_element(self, element_name, node_id, interface_id, mac, ip4, ip4_mask, ip6, ip6_mask): + interface = etree.Element(element_name) + node = self.session.get_node(node_id) + interface_name = None + if not nodeutils.is_node(node, NodeTypes.TUNNEL): + node_interface = node.netif(interface_id) + interface_name = node_interface.name + + # check if emane interface + if nodeutils.is_node(node_interface.net, NodeTypes.EMANE): + nem = node_interface.net.getnemid(node_interface) + add_attribute(interface, "nem", nem) + + add_attribute(interface, "id", interface_id) + add_attribute(interface, "name", interface_name) + add_attribute(interface, "mac", mac) + add_attribute(interface, "ip4", ip4) + add_attribute(interface, "ip4_mask", ip4_mask) + add_attribute(interface, "ip6", ip6) + add_attribute(interface, "ip6_mask", ip6_mask) + + return interface + def create_link_element(self, link_data): link_element = etree.Element("link") add_attribute(link_element, "node_one", link_data.node1_id) @@ -441,44 +467,30 @@ class CoreXmlWriter(object): # check for interface one if link_data.interface1_id is not None: - interface_one = etree.Element("interface_one") - node = self.session.get_node(link_data.node1_id) - node_interface = node.netif(link_data.interface1_id) - - add_attribute(interface_one, "id", link_data.interface1_id) - add_attribute(interface_one, "name", node_interface.name) - add_attribute(interface_one, "mac", link_data.interface1_mac) - add_attribute(interface_one, "ip4", link_data.interface1_ip4) - add_attribute(interface_one, "ip4_mask", link_data.interface1_ip4_mask) - add_attribute(interface_one, "ip6", link_data.interface1_ip6) - add_attribute(interface_one, "ip6_mask", link_data.interface1_ip6_mask) - - # check if emane interface - if nodeutils.is_node(node_interface.net, NodeTypes.EMANE): - nem = node_interface.net.getnemid(node_interface) - add_attribute(interface_one, "nem", nem) - + interface_one = self.create_interface_element( + "interface_one", + link_data.node1_id, + link_data.interface1_id, + link_data.interface1_mac, + link_data.interface1_ip4, + link_data.interface1_ip4_mask, + link_data.interface1_ip6, + link_data.interface1_ip6_mask + ) link_element.append(interface_one) # check for interface two if link_data.interface2_id is not None: - interface_two = etree.Element("interface_two") - node = self.session.get_node(link_data.node2_id) - node_interface = node.netif(link_data.interface2_id) - - add_attribute(interface_two, "id", link_data.interface2_id) - add_attribute(interface_two, "name", node_interface.name) - add_attribute(interface_two, "mac", link_data.interface2_mac) - add_attribute(interface_two, "ip4", link_data.interface2_ip4) - add_attribute(interface_two, "ip4_mask", link_data.interface2_ip4_mask) - add_attribute(interface_two, "ip6", link_data.interface2_ip6) - add_attribute(interface_two, "ip6_mask", link_data.interface2_ip6_mask) - - # check if emane interface - if nodeutils.is_node(node_interface.net, NodeTypes.EMANE): - nem = node_interface.net.getnemid(node_interface) - add_attribute(interface_two, "nem", nem) - + interface_two = self.create_interface_element( + "interface_two", + link_data.node2_id, + link_data.interface2_id, + link_data.interface2_mac, + link_data.interface2_ip4, + link_data.interface2_ip4_mask, + link_data.interface2_ip6, + link_data.interface2_ip6_mask + ) link_element.append(interface_two) # check for options @@ -709,8 +721,8 @@ class CoreXmlReader(object): position_element = device_element.find("position") if position_element is not None: - x = get_float(position_element, "x") - y = get_float(position_element, "y") + x = get_int(position_element, "x") + y = get_int(position_element, "y") if all([x, y]): node_options.set_position(x, y) @@ -731,8 +743,8 @@ class CoreXmlReader(object): position_element = network_element.find("position") if position_element is not None: - x = get_float(position_element, "x") - y = get_float(position_element, "y") + x = get_int(position_element, "x") + y = get_int(position_element, "y") if all([x, y]): node_options.set_position(x, y) From f78736ebfdb8636c5b208ad0d1241d441785dcbb Mon Sep 17 00:00:00 2001 From: bharnden Date: Tue, 4 Jun 2019 14:42:40 -0700 Subject: [PATCH 0063/1992] updated coresendmsg and core-manage to be 2/3 compliant --- daemon/scripts/core-manage | 8 +++--- daemon/scripts/coresendmsg | 54 +++++++++++++++++++------------------- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/daemon/scripts/core-manage b/daemon/scripts/core-manage index 87d23a24..93247f6f 100644 --- a/daemon/scripts/core-manage +++ b/daemon/scripts/core-manage @@ -119,13 +119,13 @@ class FileUpdater(object): search = "emane_models =" elif target == "nodetype": if self.options.userpath is None: - raise ValueError, "missing user path" + raise ValueError("missing user path") filename = os.path.join(self.options.userpath, "nodes.conf") search = self.data else: - raise ValueError, "unknown target" + raise ValueError("unknown target") if not os.path.exists(filename): - raise ValueError, "file %s does not exist" % filename + raise ValueError("file %s does not exist" % filename) return search, filename def update_file(self, fn=None): @@ -236,7 +236,7 @@ def main(): try: up = FileUpdater(action, target, data, options) r = up.process() - except Exception, e: + except Exception as e: sys.stderr.write("Exception: %s\n" % e) sys.exit(1) if not r: diff --git a/daemon/scripts/coresendmsg b/daemon/scripts/coresendmsg index d8d8a2d3..03551e8a 100644 --- a/daemon/scripts/coresendmsg +++ b/daemon/scripts/coresendmsg @@ -19,9 +19,9 @@ def print_available_tlvs(t, tlv_class): """ Print a TLV list. """ - print "TLVs available for %s message:" % t + print("TLVs available for %s message:" % t) for tlv in sorted([tlv for tlv in tlv_class.tlv_type_map], key=lambda x: x.name): - print "%s:%s" % (tlv.value, tlv.name) + print("%s:%s" % (tlv.value, tlv.name)) def print_examples(name): @@ -52,9 +52,9 @@ def print_examples(name): "srcname=\"./test.log\"", "move a test.log file from host to node 2"), ] - print "Example %s invocations:" % name + print("Example %s invocations:" % name) for cmd, descr in examples: - print " %s %s\n\t\t%s" % (name, cmd, descr) + print(" %s %s\n\t\t%s" % (name, cmd, descr)) def receive_message(sock): @@ -68,7 +68,7 @@ def receive_message(sock): data = sock.recv(4096) msghdr = data[:coreapi.CoreMessage.header_len] except KeyboardInterrupt: - print "CTRL+C pressed" + print("CTRL+C pressed") sys.exit(1) if len(msghdr) == 0: @@ -84,11 +84,11 @@ def receive_message(sock): except KeyError: msg = coreapi.CoreMessage(msgflags, msghdr, msgdata) msg.message_type = msgtype - print "unimplemented CORE message type: %s" % msg.type_str() + print("unimplemented CORE message type: %s" % msg.type_str()) return msg if len(data) > msglen + coreapi.CoreMessage.header_len: - print "received a message of type %d, dropping %d bytes of extra data" \ - % (msgtype, len(data) - (msglen + coreapi.CoreMessage.header_len)) + print("received a message of type %d, dropping %d bytes of extra data" \ + % (msgtype, len(data) - (msglen + coreapi.CoreMessage.header_len))) return msgcls(msgflags, msghdr, msgdata) @@ -103,15 +103,15 @@ def connect_to_session(sock, requested): smsg = coreapi.CoreSessionMessage.pack(flags, tlvdata) sock.sendall(smsg) - print "waiting for session list..." + print("waiting for session list...") smsgreply = receive_message(sock) if smsgreply is None: - print "disconnected" + print("disconnected") return False sessstr = smsgreply.get_tlv(SessionTlvs.NUMBER.value) if sessstr is None: - print "missing session numbers" + print("missing session numbers") return False # join the first session (that is not our own connection) @@ -119,7 +119,7 @@ def connect_to_session(sock, requested): sessions = sessstr.split("|") sessions.remove(str(localport)) if len(sessions) == 0: - print "no sessions to join" + print("no sessions to join") return False if not requested: @@ -127,10 +127,10 @@ def connect_to_session(sock, requested): elif requested in sessions: session = requested else: - print "requested session not found!" + print("requested session not found!") return False - print "joining session: %s" % session + print("joining session: %s" % session) tlvdata = coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, session) flags = MessageFlags.ADD.value smsg = coreapi.CoreSessionMessage.pack(flags, tlvdata) @@ -142,12 +142,12 @@ def receive_response(sock, opt): """ Receive and print a CORE message from the given socket. """ - print "waiting for response..." + print("waiting for response...") msg = receive_message(sock) if msg is None: - print "disconnected from %s:%s" % (opt.address, opt.port) + print("disconnected from %s:%s" % (opt.address, opt.port)) sys.exit(0) - print "received message:", msg + print("received message: %s" % msg) def main(): @@ -162,13 +162,13 @@ def main(): usagestr += "Supported message flags (flags=f1,f2,...):\n %s" % flags parser = optparse.OptionParser(usage=usagestr) parser.set_defaults( - port=CORE_API_PORT, - address="localhost", - session=None, - listen=False, - examples=False, - tlvs=False, - tcp=False + port=CORE_API_PORT, + address="localhost", + session=None, + listen=False, + examples=False, + tlvs=False, + tcp=False ) parser.add_option("-H", dest="examples", action="store_true", @@ -217,7 +217,7 @@ def main(): # build a message consisting of TLVs from "type=value" arguments flagstr = "" - tlvdata = "" + tlvdata = b"" for a in args: typevalue = a.split("=") if len(typevalue) < 2: @@ -255,11 +255,11 @@ def main(): try: sock.connect((opt.address, opt.port)) except Exception as e: - print "Error connecting to %s:%s:\n\t%s" % (opt.address, opt.port, e) + print("Error connecting to %s:%s:\n\t%s" % (opt.address, opt.port, e)) sys.exit(1) if not connect_to_session(sock, opt.session): - print "warning: continuing without joining a session!" + print("warning: continuing without joining a session!") sock.sendall(msg) if opt.listen: From 86ada3da0b173de724b2e237865fd2f477a23511 Mon Sep 17 00:00:00 2001 From: bharnden Date: Tue, 4 Jun 2019 15:41:15 -0700 Subject: [PATCH 0064/1992] updated requirements.txt to latest tested 2/3 environment and updated setup.py to reflect current dependencies --- daemon/requirements.txt | 6 ++++-- daemon/setup.py.in | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/daemon/requirements.txt b/daemon/requirements.txt index 9678f4ea..24b20f62 100644 --- a/daemon/requirements.txt +++ b/daemon/requirements.txt @@ -1,7 +1,9 @@ +configparser==3.7.4 enum34==1.1.6 -configparser=3.5.0 future==0.17.1 futures==3.2.0 -grpcio==1.18.0 +grpcio==1.21.1 +grpcio-tools==1.21.1 lxml==4.3.3 +protobuf==3.8.0 six==1.12.0 diff --git a/daemon/setup.py.in b/daemon/setup.py.in index c7f972f7..cfbbb82f 100644 --- a/daemon/setup.py.in +++ b/daemon/setup.py.in @@ -42,9 +42,10 @@ setup( version="@PACKAGE_VERSION@", packages=find_packages(), install_requires=[ - "enum34", "configparser", + "enum34", "future", + "grpcio", "lxml" ], tests_require=[ From 996a7715beeeb06302ebf113743ad52837bb687c Mon Sep 17 00:00:00 2001 From: bharnden Date: Tue, 4 Jun 2019 16:03:07 -0700 Subject: [PATCH 0065/1992] fixed ignoring adding nodes to xml and xml links from network to network --- daemon/core/xml/corexml.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 91c6ece6..ce15873e 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -1,4 +1,5 @@ import logging +from core.nodes.base import CoreNetworkBase from lxml import etree import core.nodes.base @@ -390,7 +391,6 @@ class CoreXmlWriter(object): is_network_or_rj45 = isinstance(node, (core.nodes.base.CoreNetworkBase, core.nodes.physical.Rj45Node)) is_controlnet = nodeutils.is_node(node, NodeTypes.CONTROL_NET) if is_network_or_rj45 and not is_controlnet: - logging.info("network node: %s", node) self.write_network(node) # device node elif isinstance(node, core.nodes.base.CoreNodeBase): @@ -406,16 +406,9 @@ class CoreXmlWriter(object): def write_network(self, node): # ignore p2p and other nodes that are not part of the api if not node.apitype: + logging.warning("ignoring node with no apitype: %s", node) return - # ignore nodes tied to a different network - if nodeutils.is_node(node, (NodeTypes.SWITCH, NodeTypes.HUB)): - for netif in node.netifs(sort=True): - othernet = getattr(netif, "othernet", None) - if othernet and othernet.id == node.id: - logging.info("writer ignoring node(%s) othernet(%s)", node.name, othernet.name) - return - network = NetworkElement(self.session, node) self.networks.append(network.element) @@ -441,7 +434,7 @@ class CoreXmlWriter(object): interface = etree.Element(element_name) node = self.session.get_node(node_id) interface_name = None - if not nodeutils.is_node(node, NodeTypes.TUNNEL): + if not isinstance(node, CoreNetworkBase): node_interface = node.netif(interface_id) interface_name = node_interface.name From c26277820936937f828b6187f016ef8d83fd3eb3 Mon Sep 17 00:00:00 2001 From: bharnden Date: Tue, 4 Jun 2019 21:49:44 -0700 Subject: [PATCH 0066/1992] just use setuptools in setup.py --- daemon/setup.py.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/setup.py.in b/daemon/setup.py.in index cfbbb82f..60a128e7 100644 --- a/daemon/setup.py.in +++ b/daemon/setup.py.in @@ -5,8 +5,7 @@ Defines how CORE will be built for installation. import glob import os -from setuptools import find_packages -from distutils.core import setup +from setuptools import find_packages, setup _CORE_DIR = "/etc/core" _MAN_DIR = "share/man/man1" From 4381615f1db6ab397fbead1ea8433fd126d31292 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Wed, 5 Jun 2019 09:54:11 -0700 Subject: [PATCH 0067/1992] fixed issue for link data not including per/loss when it is a ptp node, for host to host links --- daemon/core/config.py | 5 ----- daemon/core/nodes/interface.py | 1 + daemon/core/nodes/network.py | 1 + 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/daemon/core/config.py b/daemon/core/config.py index e62bdcfd..c81c01bb 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -159,7 +159,6 @@ class ConfigurableManager(object): :param int node_id: node id to clear configurations for, default is None and clears all configurations :return: nothing """ - logging.debug("resetting all configurations: %s", self.__class__.__name__) if not node_id: self.node_configurations.clear() elif node_id in self.node_configurations: @@ -175,7 +174,6 @@ class ConfigurableManager(object): :param str config_type: configuration type to store configuration for :return: nothing """ - logging.debug("setting config for node(%s) type(%s): %s=%s", node_id, config_type, _id, value) node_configs = self.node_configurations.setdefault(node_id, OrderedDict()) node_type_configs = node_configs.setdefault(config_type, OrderedDict()) node_type_configs[_id] = value @@ -204,7 +202,6 @@ class ConfigurableManager(object): :return: configuration value :rtype str """ - logging.debug("getting config for node(%s) type(%s): %s", node_id, config_type, _id) result = default node_type_configs = self.get_configs(node_id, config_type) if node_type_configs: @@ -220,7 +217,6 @@ class ConfigurableManager(object): :return: configurations :rtype: dict """ - logging.debug("getting configs for node(%s) type(%s)", node_id, config_type) result = None node_configs = self.node_configurations.get(node_id) if node_configs: @@ -235,7 +231,6 @@ class ConfigurableManager(object): :return: all configuration types for a node :rtype: dict """ - logging.debug("getting all configs for node(%s)", node_id) return self.node_configurations.get(node_id) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 5424bc1b..38b34ea1 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -141,6 +141,7 @@ class CoreInterface(object): :return: True if parameter changed, False otherwise """ # treat None and 0 as unchanged values + logging.debug("setting param: %s - %s", key, value) if value is None or value <= 0: return False diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 2a57932d..899f636f 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -894,6 +894,7 @@ class PtpNet(CoreNetwork): unidirectional=unidirectional, delay=if1.getparam("delay"), bandwidth=if1.getparam("bw"), + per=if1.getparam("loss"), dup=if1.getparam("duplicate"), jitter=if1.getparam("jitter"), interface1_id=if1.node.getifindex(if1), From e59a8bf66d23bf757f8b61e7202029bbef260268 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Thu, 6 Jun 2019 11:33:22 -0700 Subject: [PATCH 0068/1992] changes to add back in coresendmsg udp support --- daemon/core/corehandlers.py | 122 +++++++++++++++++++++++++++++++++++- daemon/core/coreserver.py | 31 ++++++++- daemon/scripts/core-daemon | 38 ++++++++--- daemon/scripts/coresendmsg | 11 +++- 4 files changed, 189 insertions(+), 13 deletions(-) diff --git a/daemon/core/corehandlers.py b/daemon/core/corehandlers.py index 4a38bd3c..ee3dc2e9 100644 --- a/daemon/core/corehandlers.py +++ b/daemon/core/corehandlers.py @@ -136,6 +136,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): logging.info("connection closed: %s", self.client_address) if self.session: # remove client from session broker and shutdown if there are no clients + clients = self.server.session_clients.setdefault(self.session.session_id, []) + clients.remove(self) self.remove_session_handlers() self.session.broker.session_clients.remove(self) if not self.session.broker.session_clients and not self.session.is_active(): @@ -307,6 +309,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): """ logging.debug("handling broadcast node: %s", node_data) message = dataconversion.convert_node(node_data) + try: self.sendall(message) except IOError: @@ -534,7 +537,8 @@ class CoreHandler(SocketServer.BaseRequestHandler): # TODO: add shutdown handler for session self.session = self.coreemu.create_session(port, master=False) - # self.session.shutdown_handlers.append(self.session_shutdown) + clients = self.server.session_clients.setdefault(self.session.session_id, []) + clients.append(self) logging.debug("created new session for client: %s", self.session.session_id) # TODO: hack to associate this handler with this sessions broker for broadcasting @@ -1017,7 +1021,7 @@ class CoreHandler(SocketServer.BaseRequestHandler): self.session.location.setrefgeo(lat, lon, alt) self.session.location.refscale = values[5] logging.info("location configured: %s = %s scale=%s", self.session.location.refxyz, - self.session.location.refgeo, self.session.location.refscale) + self.session.location.refgeo, self.session.location.refscale) logging.info("location configured: UTM%s", self.session.location.refutm) def handle_config_metadata(self, message_type, config_data): @@ -1770,3 +1774,117 @@ class CoreHandler(SocketServer.BaseRequestHandler): self.session.broadcast_config(config_data) logging.info("informed GUI about %d nodes and %d links", len(nodes_data), len(links_data)) + + +class CoreUdpHandler(CoreHandler): + def __init__(self, request, client_address, server): + self.message_handlers = { + MessageTypes.NODE.value: self.handle_node_message, + MessageTypes.LINK.value: self.handle_link_message, + MessageTypes.EXECUTE.value: self.handle_execute_message, + MessageTypes.REGISTER.value: self.handle_register_message, + MessageTypes.CONFIG.value: self.handle_config_message, + MessageTypes.FILE.value: self.handle_file_message, + MessageTypes.INTERFACE.value: self.handle_interface_message, + MessageTypes.EVENT.value: self.handle_event_message, + MessageTypes.SESSION.value: self.handle_session_message, + } + self.master = False + self.session = None + SocketServer.BaseRequestHandler.__init__(self, request, client_address, server) + + def setup(self): + """ + Client has connected, set up a new connection. + :return: nothing + """ + logging.info("new UDP connection: %s", self.client_address) + + def receive_message(self): + data = self.request[0] + header = data[:coreapi.CoreMessage.header_len] + if len(header) < coreapi.CoreMessage.header_len: + raise IOError("error receiving header (received %d bytes)" % len(header)) + + message_type, message_flags, message_len = coreapi.CoreMessage.unpack_header(header) + if message_len == 0: + logging.warning("received message with no data") + return + + if len(data) != coreapi.CoreMessage.header_len + message_len: + logging.error("received message length does not match received data (%s != %s)", + len(data), coreapi.CoreMessage.header_len + message_len) + raise IOError + + try: + message_class = coreapi.CLASS_MAP[message_type] + message = message_class(message_flags, header, data[coreapi.CoreMessage.header_len:]) + return message + except KeyError: + message = coreapi.CoreMessage(message_flags, header, data[coreapi.CoreMessage.header_len:]) + message.msgtype = message_type + logging.exception("unimplemented core message type: %s", message.type_str()) + + def handle(self): + message = self.receive_message() + sessions = message.session_numbers() + message.queuedtimes = 0 + if sessions: + for session_id in sessions: + session = self.server.mainserver.coreemu.sessions.get(session_id) + if session: + logging.debug("session handling message: %s", session.session_id) + self.session = session + self.handle_message(message) + self.broadcast(message) + else: + logging.error("session %d in %s message not found.", session_id, message.type_str()) + else: + # no session specified, find an existing one + session = None + node_count = 0 + for session_id in self.server.mainserver.coreemu.sessions: + current_session = self.server.mainserver.coreemu.sessions[session_id] + current_node_count = current_session.get_node_count() + if current_session.state == EventTypes.RUNTIME_STATE.value and current_node_count > node_count: + node_count = current_node_count + session = current_session + + if session or message.message_type == MessageTypes.REGISTER.value: + self.session = session + self.handle_message(message) + self.broadcast(message) + else: + logging.error("no active session, dropping %s message.", message.type_str()) + + def broadcast(self, message): + if not isinstance(message, (coreapi.CoreNodeMessage, coreapi.CoreLinkMessage)): + return + + clients = self.server.mainserver.session_clients.setdefault(self.session.session_id, []) + for client in clients: + try: + client.sendall(message.raw_message) + except IOError: + logging.error("error broadcasting") + + def finish(self): + return SocketServer.BaseRequestHandler.finish(self) + + def queuemsg(self, msg): + """ + UDP handlers are short-lived and do not have message queues. + + :param bytes msg: message to queue + :return: + """ + raise Exception("Unable to queue %s message for later processing using UDP!" % msg) + + def sendall(self, data): + """ + Use sendto() on the connectionless UDP socket. + + :param data: + :return: + """ + self.request[1].sendto(data, self.client_address) diff --git a/daemon/core/coreserver.py b/daemon/core/coreserver.py index 2c052b05..97f6b9d0 100644 --- a/daemon/core/coreserver.py +++ b/daemon/core/coreserver.py @@ -23,8 +23,37 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): :param tuple[str, int] server_address: server host and port to use :param class handler_class: request handler :param dict config: configuration setting - :return: """ self.coreemu = CoreEmu(config) self.config = config + self.session_clients = {} SocketServer.TCPServer.__init__(self, server_address, handler_class) + + +class CoreUdpServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer): + """ + UDP server class, manages sessions and spawns request handlers for + incoming connections. + """ + daemon_threads = True + allow_reuse_address = True + + def __init__(self, server_address, handler_class, mainserver): + """ + Server class initialization takes configuration data and calls + the SocketServer constructor + + :param server_address: + :param class handler_class: request handler + :param mainserver: + """ + self.mainserver = mainserver + SocketServer.UDPServer.__init__(self, server_address, handler_class) + + def start(self): + """ + Thread target to run concurrently with the TCP server. + + :return: nothing + """ + self.serve_forever() diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index 34c516d3..d6142e94 100644 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -9,13 +9,14 @@ import ConfigParser import logging import optparse import sys +import threading import time -from core import load_logging_config from core import constants from core import enumerations -from core.corehandlers import CoreHandler -from core.coreserver import CoreServer +from core import load_logging_config +from core.corehandlers import CoreHandler, CoreUdpHandler +from core.coreserver import CoreServer, CoreUdpServer from core.misc.utils import close_onexec load_logging_config() @@ -30,6 +31,21 @@ def banner(): logging.info("CORE daemon v.%s started %s", constants.COREDPY_VERSION, time.ctime()) +def start_udp(mainserver, server_address): + """ + Start a thread running a UDP server on the same host,port for + connectionless requests. + + :param CoreServer mainserver: main core tcp server to piggy back off of + :param server_address: + :return: CoreUdpServer + """ + mainserver.udpserver = CoreUdpServer(server_address, CoreUdpHandler, mainserver) + mainserver.udpthread = threading.Thread(target=mainserver.udpserver.start) + mainserver.udpthread.daemon = True + mainserver.udpthread.start() + + def cored(cfg, use_ovs): """ Start the CoreServer object and enter the server loop. @@ -44,18 +60,24 @@ def cored(cfg, use_ovs): host = "localhost" try: - server = CoreServer((host, port), CoreHandler, cfg) + address = (host, port) + server = CoreServer(address, CoreHandler, cfg) if use_ovs: from core.netns.openvswitch import OVS_NODES server.coreemu.update_nodes(OVS_NODES) + + # start udp server + start_udp(server, address) + + # close handlers + close_onexec(server.fileno()) + + logging.info("tcp/udp servers started, listening on: %s:%s", host, port) + server.serve_forever() except: logging.exception("error starting main server on: %s:%s", host, port) sys.exit(1) - close_onexec(server.fileno()) - logging.info("server started, listening on: %s:%s", host, port) - server.serve_forever() - def get_merged_config(filename): """ diff --git a/daemon/scripts/coresendmsg b/daemon/scripts/coresendmsg index be6e030b..d08360b8 100644 --- a/daemon/scripts/coresendmsg +++ b/daemon/scripts/coresendmsg @@ -186,6 +186,8 @@ def main(): help="Listen for a response message and print it.") parser.add_option("-t", "--list-tlvs", dest="tlvs", action="store_true", help="List TLVs for the specified message type.") + parser.add_option("--tcp", dest="tcp", action="store_true", + help="Use TCP instead of UDP and connect to a session default: %s" % parser.defaults["tcp"]) def usage(msg=None, err=0): sys.stdout.write("\n") @@ -249,7 +251,12 @@ def main(): msg = msg_cls.pack(flags, tlvdata) - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + if opt.tcp: + protocol = socket.SOCK_STREAM + else: + protocol = socket.SOCK_DGRAM + + sock = socket.socket(socket.AF_INET, protocol) sock.setblocking(True) try: @@ -258,7 +265,7 @@ def main(): print "Error connecting to %s:%s:\n\t%s" % (opt.address, opt.port, e) sys.exit(1) - if not connect_to_session(sock, opt.session): + if opt.tcp and not connect_to_session(sock, opt.session): print "warning: continuing without joining a session!" sock.sendall(msg) From 0b770d8350f4db983c9b33ef360a4ab3caa6539f Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Thu, 6 Jun 2019 11:43:39 -0700 Subject: [PATCH 0069/1992] reverted enclosing system start in exception handler --- daemon/scripts/core-daemon | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/daemon/scripts/core-daemon b/daemon/scripts/core-daemon index d6142e94..b6b5b767 100644 --- a/daemon/scripts/core-daemon +++ b/daemon/scripts/core-daemon @@ -65,19 +65,19 @@ def cored(cfg, use_ovs): if use_ovs: from core.netns.openvswitch import OVS_NODES server.coreemu.update_nodes(OVS_NODES) - - # start udp server - start_udp(server, address) - - # close handlers - close_onexec(server.fileno()) - - logging.info("tcp/udp servers started, listening on: %s:%s", host, port) - server.serve_forever() except: logging.exception("error starting main server on: %s:%s", host, port) sys.exit(1) + # start udp server + start_udp(server, address) + + # close handlers + close_onexec(server.fileno()) + + logging.info("tcp/udp servers started, listening on: %s:%s", host, port) + server.serve_forever() + def get_merged_config(filename): """ From 174bae6de1136ef4bdb272a27f7e694d13600dc0 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Thu, 6 Jun 2019 14:59:35 -0400 Subject: [PATCH 0070/1992] Update usage.md Added icons for the toolbar buttons. --- docs/usage.md | 54 +++++++++++++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index fa029e2c..744c33a2 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -69,51 +69,51 @@ The toolbar is a row of buttons that runs vertically along the left side of the When CORE is in Edit mode (the default), the vertical Editing Toolbar exists on the left side of the CORE window. Below are brief descriptions for each toolbar item, starting from the top. Most of the tools are grouped into related sub-menus, which appear when you click on their group icon. -* |select| *Selection Tool* - default tool for selecting, moving, configuring nodes -* |start| *Start button* - starts Execute mode, instantiates the emulation -* |link| *Link* - the Link Tool allows network links to be drawn between two nodes by clicking and dragging the mouse -* |router| *Network-layer virtual nodes* - * |router| *Router* - runs Quagga OSPFv2 and OSPFv3 routing to forward packets - * |host| *Host* - emulated server machine having a default route, runs SSH server - * |pc| *PC* - basic emulated machine having a default route, runs no processes by default - * |mdr| *MDR* - runs Quagga OSPFv3 MDR routing for MANET-optimized routing - * |router_green| *PRouter* - physical router represents a real testbed machine - * |document_properties| *Edit* - edit node types button invokes the CORE Node Types dialog. New types of nodes may be created having different icons and names. The default services that are started with each node type can be changed here. -* |hub| *Link-layer nodes* - * |hub| *Hub* - the Ethernet hub forwards incoming packets to every connected node - * |lanswitch| *Switch* - the Ethernet switch intelligently forwards incoming packets to attached hosts using an Ethernet address hash table - * |wlan| *Wireless LAN* - when routers are connected to this WLAN node, they join a wireless network and an antenna is drawn instead of a connecting line; the WLAN node typically controls connectivity between attached wireless nodes based on the distance between them - * |rj45| *RJ45* - with the RJ45 Physical Interface Tool, emulated nodes can be linked to real physical interfaces; using this tool, real networks and devices can be physically connected to the live-running emulation - * |tunnel| *Tunnel* - the Tunnel Tool allows connecting together more than one CORE emulation using GRE tunnels +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/select.gif) *Selection Tool* - default tool for selecting, moving, configuring nodes +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/start.gif) *Start button* - starts Execute mode, instantiates the emulation +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/link.gif) *Link* - the Link Tool allows network links to be drawn between two nodes by clicking and dragging the mouse +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/router.gif) *Network-layer virtual nodes* + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/router.gif) *Router* - runs Quagga OSPFv2 and OSPFv3 routing to forward packets + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/host.gif) *Host* - emulated server machine having a default route, runs SSH server + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/pc.gif) *PC* - basic emulated machine having a default route, runs no processes by default + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/mdr.gif) *MDR* - runs Quagga OSPFv3 MDR routing for MANET-optimized routing + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/router_green.gif) *PRouter* - physical router represents a real testbed machine + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/document-properties.gif) *Edit* - edit node types button invokes the CORE Node Types dialog. New types of nodes may be created having different icons and names. The default services that are started with each node type can be changed here. +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/hub.gif) *Link-layer nodes* + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/hub.gif) *Hub* - the Ethernet hub forwards incoming packets to every connected node + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/lanswitch.gif) *Switch* - the Ethernet switch intelligently forwards incoming packets to attached hosts using an Ethernet address hash table + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/wlan.gif) *Wireless LAN* - when routers are connected to this WLAN node, they join a wireless network and an antenna is drawn instead of a connecting line; the WLAN node typically controls connectivity between attached wireless nodes based on the distance between them + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/rj45.gif) *RJ45* - with the RJ45 Physical Interface Tool, emulated nodes can be linked to real physical interfaces; using this tool, real networks and devices can be physically connected to the live-running emulation + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/tunnel.gif) *Tunnel* - the Tunnel Tool allows connecting together more than one CORE emulation using GRE tunnels * *Annotation Tools* - * |marker| *Marker* - for drawing marks on the canvas - * |oval| *Oval* - for drawing circles on the canvas that appear in the background - * |rectangle| *Rectangle* - for drawing rectangles on the canvas that appear in the background - * |text| *Text* - for placing text captions on the canvas + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/marker.gif) *Marker* - for drawing marks on the canvas + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/oval.gif) *Oval* - for drawing circles on the canvas that appear in the background + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/rectangle.gif) *Rectangle* - for drawing rectangles on the canvas that appear in the background + * ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/text.gif) *Text* - for placing text captions on the canvas ### Execution Toolbar When the Start button is pressed, CORE switches to Execute mode, and the Edit toolbar on the left of the CORE window is replaced with the Execution toolbar Below are the items on this toolbar, starting from the top. -* |select| *Selection Tool* - in Execute mode, the Selection Tool can be used for moving nodes around the canvas, and double-clicking on a node will open a shell window for that node; right-clicking on a node invokes a pop-up menu of run-time options for that node -* |stop| *Stop button* - stops Execute mode, terminates the emulation, returns CORE to edit mode. -* |observe| *Observer Widgets Tool* - clicking on this magnifying glass icon +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/select.gif) *Selection Tool* - in Execute mode, the Selection Tool can be used for moving nodes around the canvas, and double-clicking on a node will open a shell window for that node; right-clicking on a node invokes a pop-up menu of run-time options for that node +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/stop.gif) *Stop button* - stops Execute mode, terminates the emulation, returns CORE to edit mode. +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/observe.gif) *Observer Widgets Tool* - clicking on this magnifying glass icon invokes a menu for easily selecting an Observer Widget. The icon has a darker gray background when an Observer Widget is active, during which time moving the mouse over a node will pop up an information display for that node. -* |plot| *Plot Tool* - with this tool enabled, clicking on any link will +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/plot.gif) *Plot Tool* - with this tool enabled, clicking on any link will activate the Throughput Widget and draw a small, scrolling throughput plot on the canvas. The plot shows the real-time kbps traffic for that link. The plots may be dragged around the canvas; right-click on a plot to remove it. -* |marker| *Marker* - for drawing freehand lines on the canvas, useful during +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/marker.gif) *Marker* - for drawing freehand lines on the canvas, useful during demonstrations; markings are not saved -* |twonode| *Two-node Tool* - click to choose a starting and ending node, and +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/twonode.gif) *Two-node Tool* - click to choose a starting and ending node, and run a one-time *traceroute* between those nodes or a continuous *ping -R* between nodes. The output is displayed in real time in a results box, while the IP addresses are parsed and the complete network path is highlighted on the CORE display. -* |run| *Run Tool* - this tool allows easily running a command on all or a +* ![alt text](https://github.com/coreemu/core/blob/master/gui/icons/tiny/run.gif) *Run Tool* - this tool allows easily running a command on all or a subset of all nodes. A list box allows selecting any of the nodes. A text entry box allows entering any command. The command should return immediately, otherwise the display will block awaiting response. The *ping* command, for From 994fe042e140770c3c4651dcd3ac7b189cf413c9 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Thu, 6 Jun 2019 13:02:20 -0700 Subject: [PATCH 0071/1992] updates to just leverage broker clients instead of repeating logic for now, until broker is refactored --- daemon/core/corehandlers.py | 9 ++------- daemon/core/coreserver.py | 1 - 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/daemon/core/corehandlers.py b/daemon/core/corehandlers.py index ee3dc2e9..c550e141 100644 --- a/daemon/core/corehandlers.py +++ b/daemon/core/corehandlers.py @@ -136,8 +136,6 @@ class CoreHandler(SocketServer.BaseRequestHandler): logging.info("connection closed: %s", self.client_address) if self.session: # remove client from session broker and shutdown if there are no clients - clients = self.server.session_clients.setdefault(self.session.session_id, []) - clients.remove(self) self.remove_session_handlers() self.session.broker.session_clients.remove(self) if not self.session.broker.session_clients and not self.session.is_active(): @@ -537,8 +535,6 @@ class CoreHandler(SocketServer.BaseRequestHandler): # TODO: add shutdown handler for session self.session = self.coreemu.create_session(port, master=False) - clients = self.server.session_clients.setdefault(self.session.session_id, []) - clients.append(self) logging.debug("created new session for client: %s", self.session.session_id) # TODO: hack to associate this handler with this sessions broker for broadcasting @@ -1798,7 +1794,7 @@ class CoreUdpHandler(CoreHandler): Client has connected, set up a new connection. :return: nothing """ - logging.info("new UDP connection: %s", self.client_address) + pass def receive_message(self): data = self.request[0] @@ -1861,8 +1857,7 @@ class CoreUdpHandler(CoreHandler): if not isinstance(message, (coreapi.CoreNodeMessage, coreapi.CoreLinkMessage)): return - clients = self.server.mainserver.session_clients.setdefault(self.session.session_id, []) - for client in clients: + for client in self.session.broker.session_clients: try: client.sendall(message.raw_message) except IOError: diff --git a/daemon/core/coreserver.py b/daemon/core/coreserver.py index 97f6b9d0..5ba232da 100644 --- a/daemon/core/coreserver.py +++ b/daemon/core/coreserver.py @@ -26,7 +26,6 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): """ self.coreemu = CoreEmu(config) self.config = config - self.session_clients = {} SocketServer.TCPServer.__init__(self, server_address, handler_class) From 89877ffe6f4aa5d27ec0d2aa1b4a004e9f6d26cd Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Thu, 6 Jun 2019 16:34:26 -0700 Subject: [PATCH 0072/1992] updated docs for python files --- daemon/core/emulator/emudata.py | 34 ++++++++++++++++++++++++++++---- daemon/core/emulator/session.py | 27 +++++++++++++------------ daemon/core/location/mobility.py | 2 +- 3 files changed, 45 insertions(+), 18 deletions(-) diff --git a/daemon/core/emulator/emudata.py b/daemon/core/emulator/emudata.py index 102d0312..ed54e20a 100644 --- a/daemon/core/emulator/emudata.py +++ b/daemon/core/emulator/emudata.py @@ -32,7 +32,7 @@ def create_interface(node, network, interface_data): Create an interface for a node on a network using provided interface data. :param node: node to create interface for - :param network: network to associate interface with + :param core.nodes.base.CoreNetworkBase network: network to associate interface with :param core.emulator.emudata.InterfaceData interface_data: interface data :return: created interface """ @@ -134,7 +134,7 @@ class LinkOptions(object): """ Create a LinkOptions object. - :param core.enumerations.LinkTypes _type: type of link, defaults to wired + :param core.emulator.enumerations.LinkTypes _type: type of link, defaults to wired """ self.type = _type self.session = None @@ -206,7 +206,7 @@ class IpPrefixes(object): Creates interface data for linking nodes, using the nodes unique id for generation, along with a random mac address, unless provided. - :param core.coreobj.PyCoreNode node: node to create interface for + :param core.nodes.base.CoreNode node: node to create interface for :param str name: name to set for interface, default is eth{id} :param str mac: mac address to use for this interface, default is random generation :return: new interface data for the provided node @@ -255,7 +255,7 @@ class InterfaceData(object): :param int _id: interface id :param str name: name for interface - :param core.misc.ipaddress.MacAddress mac: mac address + :param core.nodes.ipaddress.MacAddress mac: mac address :param str ip4: ipv4 address :param int ip4_mask: ipv4 bit mask :param str ip6: ipv6 address @@ -270,24 +270,50 @@ class InterfaceData(object): self.ip6_mask = ip6_mask def has_ip4(self): + """ + Determines if interface has an ip4 address. + + :return: True if has ip4, False otherwise + """ return all([self.ip4, self.ip4_mask]) def has_ip6(self): + """ + Determines if interface has an ip6 address. + + :return: True if has ip6, False otherwise + """ return all([self.ip6, self.ip6_mask]) def ip4_address(self): + """ + Retrieve a string representation of the ip4 address and netmask. + + :return: ip4 string or None + """ if self.has_ip4(): return "%s/%s" % (self.ip4, self.ip4_mask) else: return None def ip6_address(self): + """ + Retrieve a string representation of the ip6 address and netmask. + + :return: ip4 string or None + """ if self.has_ip6(): return "%s/%s" % (self.ip6, self.ip6_mask) else: return None def get_addresses(self): + """ + Returns a list of ip4 and ip6 address when present. + + :return: list of addresses + :rtype: list + """ ip4 = self.ip4_address() ip6 = self.ip6_address() return [i for i in [ip4, ip6] if i] diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 60f2d282..556fa88e 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -314,7 +314,7 @@ class Session(object): :param int node_two_id: node two id :param int interface_one_id: interface id for node one :param int interface_two_id: interface id for node two - :param core.enumerations.LinkTypes link_type: link type to delete + :param core.emulator.enumerations.LinkTypes link_type: link type to delete :return: nothing """ # get node objects identified by link data @@ -452,7 +452,7 @@ class Session(object): """ Add a node to the session, based on the provided node data. - :param core.enumerations.NodeTypes _type: type of node to create + :param core.emulator.enumerations.NodeTypes _type: type of node to create :param int _id: id for node, defaults to None for generated id :param core.emulator.emudata.NodeOptions node_options: data to create node with :return: created node @@ -574,7 +574,7 @@ class Session(object): """ Broadcast node location to all listeners. - :param core.netns.nodes.PyCoreObj node: node to broadcast location for + :param core.nodes.base.NodeBase node: node to broadcast location for :return: nothing """ node_data = NodeData( @@ -688,7 +688,7 @@ class Session(object): """ Handle a mobility event. - :param core.data.EventData event_data: event data to handle + :param core.emulator.data.EventData event_data: event data to handle :return: nothing """ self.mobility.handleevent(event_data) @@ -700,7 +700,7 @@ class Session(object): :param int _id: int for node, defaults to None and will be generated :param core.emulator.emudata.NodeOptions node_options: options for emane node, model will always be "mdr" :return: new emane node - :rtype: core.netns.nodes.CoreNode + :rtype: core.nodes.network.WlanNode """ if not node_options: node_options = NodeOptions() @@ -768,7 +768,7 @@ class Session(object): """ Handle exception data that should be provided to exception handlers. - :param core.data.ExceptionData exception_data: exception data to send out + :param core.emulator.data.ExceptionData exception_data: exception data to send out :return: nothing """ @@ -779,7 +779,7 @@ class Session(object): """ Handle node data that should be provided to node handlers. - :param core.data.ExceptionData node_data: node data to send out + :param core.emulator.data.ExceptionData node_data: node data to send out :return: nothing """ @@ -801,7 +801,7 @@ class Session(object): """ Handle config data that should be provided to config handlers. - :param core.data.ConfigData config_data: config data to send out + :param core.emulator.data.ConfigData config_data: config data to send out :return: nothing """ @@ -812,7 +812,7 @@ class Session(object): """ Handle link data that should be provided to link handlers. - :param core.data.ExceptionData link_data: link data to send out + :param core.emulator.data.ExceptionData link_data: link data to send out :return: nothing """ @@ -889,7 +889,7 @@ class Session(object): :param str hook_type: hook type :param str file_name: file name for hook :param str source_name: source name - :param data: hook data + :param str data: hook data :return: nothing """ logging.info("setting state hook: %s - %s from %s", hook_type, file_name, source_name) @@ -1018,7 +1018,8 @@ class Session(object): variables. :param bool state: flag to determine if session state should be included - :return: + :return: environment variables + :rtype: dict """ env = os.environ.copy() env["SESSION"] = "%s" % self.id @@ -1532,7 +1533,7 @@ class Session(object): If conf_reqd is False, the control network may be built even when the user has not configured one (e.g. for EMANE.) - :param core.netns.vnode.CoreNode node: node to add or remove control interface + :param core.nodes.base.CoreNode node: node to add or remove control interface :param int net_index: network index :param bool remove: flag to check if it should be removed :param bool conf_required: flag to check if conf is required @@ -1615,7 +1616,7 @@ class Session(object): start of the runtime state. :param event_time: event time - :param core.netns.vnode.CoreNode node: node to add event for + :param core.nodes.base.CoreNode node: node to add event for :param str name: name of event :param data: data for event :return: nothing diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index af75bc12..360dd305 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -611,7 +611,7 @@ class WayPointMobility(WirelessModel): """ Create a WayPointMobility instance. - :param core.session.Session session: CORE session instance + :param core.emulator.session.Session session: CORE session instance :param int _id: object id :return: """ From db7bfe12bd83dc718da77e61e487d509771b68e2 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Fri, 7 Jun 2019 08:59:16 -0700 Subject: [PATCH 0073/1992] pass on updated pydocs to reflect path changes --- daemon/core/api/tlv/broker.py | 4 +-- daemon/core/api/tlv/coreapi.py | 12 ++++---- daemon/core/api/tlv/corehandlers.py | 34 +++++++++++------------ daemon/core/api/tlv/dataconversion.py | 4 +-- daemon/core/emane/emanemodel.py | 2 +- daemon/core/location/mobility.py | 13 ++++----- daemon/core/nodes/base.py | 34 +++++++++-------------- daemon/core/nodes/client.py | 5 +--- daemon/core/nodes/interface.py | 18 ++++++------ daemon/core/nodes/ipaddress.py | 2 +- daemon/core/nodes/network.py | 40 +++++++++++++-------------- daemon/core/nodes/nodeutils.py | 5 ++-- daemon/core/nodes/openvswitch.py | 23 +++++---------- daemon/core/nodes/physical.py | 14 +++++----- daemon/core/plugins/sdt.py | 6 ++-- daemon/core/services/coreservices.py | 28 +++++++++---------- daemon/core/utils.py | 4 +-- 17 files changed, 111 insertions(+), 137 deletions(-) diff --git a/daemon/core/api/tlv/broker.py b/daemon/core/api/tlv/broker.py index d94195ab..d63a8ca2 100644 --- a/daemon/core/api/tlv/broker.py +++ b/daemon/core/api/tlv/broker.py @@ -96,7 +96,7 @@ class CoreBroker(object): """ Creates a CoreBroker instance. - :param core.session.Session session: session this manager is tied to + :param core.emulator.session.Session session: session this manager is tied to :return: nothing """ @@ -901,7 +901,7 @@ class CoreBroker(object): opaque data in the link message, otherwise use the IP of the message sender (the master server). - :param coreapi.CoreLinkMessage msg: + :param core.api.tlv.coreapi.CoreLinkMessage msg: link message :param bool first_is_local: is first local :return: host address :rtype: str diff --git a/daemon/core/api/tlv/coreapi.py b/daemon/core/api/tlv/coreapi.py index e5b09f27..e0cd938d 100644 --- a/daemon/core/api/tlv/coreapi.py +++ b/daemon/core/api/tlv/coreapi.py @@ -243,7 +243,7 @@ class CoreTlvDataUint16List(CoreTlvData): Retrieves a unint 16 list from a string :param str value: string representation of a uint 16 list - :return: unint 16 list + :return: uint 16 list :rtype: list """ return tuple(int(x) for x in value.split()) @@ -274,7 +274,7 @@ class CoreTlvDataIpv4Addr(CoreTlvDataObj): :param str value: value to get Ipv4 address from :return: Ipv4 address - :rtype: core.misc.ipaddress.IpAddress + :rtype: core.nodes.ipaddress.IpAddress """ return IpAddress(af=socket.AF_INET, address=value) @@ -292,7 +292,7 @@ class CoreTlvDataIPv6Addr(CoreTlvDataObj): """ Retrieve Ipv6 address value from object. - :param core.misc.ipaddress.IpAddress obj: ip address to get value from + :param core.nodes.ipaddress.IpAddress obj: ip address to get value from :return: """ return obj.addr @@ -304,7 +304,7 @@ class CoreTlvDataIPv6Addr(CoreTlvDataObj): :param str value: value to get Ipv4 address from :return: Ipv4 address - :rtype: core.misc.ipaddress.IpAddress + :rtype: core.nodes.ipaddress.IpAddress """ return IpAddress(af=socket.AF_INET6, address=value) @@ -322,7 +322,7 @@ class CoreTlvDataMacAddr(CoreTlvDataObj): """ Retrieve Ipv6 address value from object. - :param core.misc.ipaddress.MacAddress obj: mac address to get value from + :param core.nodes.ipaddress.MacAddress obj: mac address to get value from :return: """ # extend to 64 bits @@ -335,7 +335,7 @@ class CoreTlvDataMacAddr(CoreTlvDataObj): :param str value: value to get Ipv4 address from :return: Ipv4 address - :rtype: core.misc.ipaddress.MacAddress + :rtype: core.nodes.ipaddress.MacAddress """ # only use 48 bits return MacAddress(address=value[2:]) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 50a62e1c..e57ec54e 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -213,7 +213,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Callback to handle an event broadcast out from a session. - :param core.data.EventData event_data: event data to handle + :param core.emulator.data.EventData event_data: event data to handle :return: nothing """ logging.debug("handling broadcast event: %s", event_data) @@ -237,7 +237,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Callback to handle a file broadcast out from a session. - :param core.data.FileData file_data: file data to handle + :param core.emulator.data.FileData file_data: file data to handle :return: nothing """ logging.debug("handling broadcast file: %s", file_data) @@ -264,7 +264,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Callback to handle a config broadcast out from a session. - :param core.data.ConfigData config_data: config data to handle + :param core.emulator.data.ConfigData config_data: config data to handle :return: nothing """ logging.debug("handling broadcast config: %s", config_data) @@ -278,7 +278,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Callback to handle an exception broadcast out from a session. - :param core.data.ExceptionData exception_data: exception data to handle + :param core.emulator.data.ExceptionData exception_data: exception data to handle :return: nothing """ logging.debug("handling broadcast exception: %s", exception_data) @@ -301,7 +301,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Callback to handle an node broadcast out from a session. - :param core.data.NodeData node_data: node data to handle + :param core.emulator.data.NodeData node_data: node data to handle :return: nothing """ logging.debug("handling broadcast node: %s", node_data) @@ -315,7 +315,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Callback to handle an link broadcast out from a session. - :param core.data.LinkData link_data: link data to handle + :param core.emulator.data.LinkData link_data: link data to handle :return: nothing """ logging.debug("handling broadcast link: %s", link_data) @@ -407,7 +407,7 @@ class CoreHandler(socketserver.BaseRequestHandler): Receive data and return a CORE API message object. :return: received message - :rtype: coreapi.CoreMessage + :rtype: core.api.tlv.coreapi.CoreMessage """ try: header = self.request.recv(coreapi.CoreMessage.header_len) @@ -586,7 +586,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Sends an exception for display within the GUI. - :param core.enumerations.ExceptionLevel level: level for exception + :param core.emulator.enumerations.ExceptionLevel level: level for exception :param str source: source where exception came from :param str text: details about exception :param int node: node id, if related to a specific node @@ -624,7 +624,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Node Message handler - :param coreapi.CoreNodeMessage message: node message + :param core.api.coreapi.CoreNodeMessage message: node message :return: replies to node message """ replies = [] @@ -696,7 +696,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Link Message handler - :param coreapi.CoreLinkMessage message: link message to handle + :param core.api.tlv.coreapi.CoreLinkMessage message: link message to handle :return: link message replies """ node_one_id = message.get_tlv(LinkTlvs.N1_NUMBER.value) @@ -756,7 +756,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Execute Message handler - :param coreapi.CoreExecMessage message: execute message to handle + :param core.api.tlv.coreapi.CoreExecMessage message: execute message to handle :return: reply messages """ node_num = message.get_tlv(ExecuteTlvs.NODE.value) @@ -831,7 +831,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Register Message Handler - :param coreapi.CoreRegMessage message: register message to handle + :param core.api.tlv.coreapi.CoreRegMessage message: register message to handle :return: reply messages """ replies = [] @@ -923,7 +923,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Configuration Message handler - :param coreapi.CoreConfMessage message: configuration message to handle + :param core.api.tlv.coreapi.CoreConfMessage message: configuration message to handle :return: reply messages """ # convert config message to standard config data object @@ -1341,7 +1341,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ File Message handler - :param coreapi.CoreFileMessage message: file message to handle + :param core.api.tlv.coreapi.CoreFileMessage message: file message to handle :return: reply messages """ if message.flags & MessageFlags.ADD.value: @@ -1405,7 +1405,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Event Message handler - :param coreapi.CoreEventMessage message: event message to handle + :param core.api.tlv.coreapi.CoreEventMessage message: event message to handle :return: reply messages """ event_data = EventData( @@ -1508,7 +1508,7 @@ class CoreHandler(socketserver.BaseRequestHandler): 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 + :param core.emulator.enumerations.EventData event_data: event data to handle :return: nothing """ event_type = event_data.event_type @@ -1573,7 +1573,7 @@ class CoreHandler(socketserver.BaseRequestHandler): """ Session Message handler - :param coreapi.CoreSessionMessage message: session message to handle + :param core.api.tlv.coreapi.CoreSessionMessage message: session message to handle :return: reply messages """ session_id_str = message.get_tlv(SessionTlvs.NUMBER.value) diff --git a/daemon/core/api/tlv/dataconversion.py b/daemon/core/api/tlv/dataconversion.py index 781afbc2..b8808f42 100644 --- a/daemon/core/api/tlv/dataconversion.py +++ b/daemon/core/api/tlv/dataconversion.py @@ -11,7 +11,7 @@ def convert_node(node_data): """ Convenience method for converting NodeData to a packed TLV message. - :param core.data.NodeData node_data: node data to convert + :param core.emulator.data.NodeData node_data: node data to convert :return: packed node message """ tlv_data = structutils.pack_values(coreapi.CoreNodeTlv, [ @@ -43,7 +43,7 @@ def convert_config(config_data): """ Convenience method for converting ConfigData to a packed TLV message. - :param core.data.ConfigData config_data: config data to convert + :param core.emulator.data.ConfigData config_data: config data to convert :return: packed message """ tlv_data = structutils.pack_values(coreapi.CoreConfigTlv, [ diff --git a/daemon/core/emane/emanemodel.py b/daemon/core/emane/emanemodel.py index bfbfc119..3f00f921 100644 --- a/daemon/core/emane/emanemodel.py +++ b/daemon/core/emane/emanemodel.py @@ -147,7 +147,7 @@ class EmaneModel(WirelessModel): """ Invoked when a Link Message is received. Default is unimplemented. - :param core.netns.vif.Veth netif: interface one + :param core.nodes.interface.Veth netif: interface one :param bw: bandwidth to set to :param delay: packet delay to set to :param loss: packet loss to set to diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 360dd305..8303813f 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -41,7 +41,7 @@ class MobilityManager(ModelManager): """ Creates a MobilityManager instance. - :param core.session.Session session: session this manager is tied to + :param core.emulator.session.Session session: session this manager is tied to """ super(MobilityManager, self).__init__() self.session = session @@ -468,8 +468,6 @@ class BasicRangeModel(WirelessModel): with self.wlan._linked_lock: linked = self.wlan.linked(a, b) - logging.debug("checking range netif1(%s) netif2(%s): linked(%s) actual(%s) > config(%s)", - a.name, b.name, linked, d, self.range) if d > self.range: if linked: logging.debug("was linked, unlinking") @@ -533,8 +531,8 @@ class BasicRangeModel(WirelessModel): """ Send a wireless link/unlink API message to the GUI. - :param core.coreobj.PyCoreNetIf netif: interface one - :param core.coreobj.PyCoreNetIf netif2: interface two + :param core.nodes.interface.CoreInterface netif: interface one + :param core.nodes.interface.CoreInterface netif2: interface two :param bool unlink: unlink or not :return: nothing """ @@ -702,7 +700,7 @@ class WayPointMobility(WirelessModel): Calculate next node location and update its coordinates. Returns True if the node's position has changed. - :param core.netns.vnode.CoreNode node: node to move + :param core.nodes.base.CoreNode node: node to move :param dt: move factor :return: True if node was moved, False otherwise :rtype: bool @@ -929,9 +927,8 @@ class Ns2ScriptedMobility(WayPointMobility): """ Creates a Ns2ScriptedMobility instance. - :param core.session.Session session: CORE session instance + :param core.emulator.session.Session session: CORE session instance :param int _id: object id - :param config: values """ super(Ns2ScriptedMobility, self).__init__(session=session, _id=_id) self._netifs = {} diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 219757d2..05b45b4c 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -1,5 +1,5 @@ """ -PyCoreNode and LxcNode classes that implement the network namespac virtual node. +Defines the base logic for nodes used within core. """ import errno @@ -11,9 +11,10 @@ import signal import socket import string import threading -from builtins import range from socket import AF_INET, AF_INET6 +from builtins import range + from core import CoreCommandError, utils from core import constants from core.emulator.data import NodeData, LinkData @@ -21,7 +22,6 @@ from core.emulator.enumerations import NodeTypes, LinkTypes from core.nodes import client, nodeutils, ipaddress from core.nodes.interface import TunTap, CoreInterface from core.nodes.interface import Veth -from core.nodes.ipaddress import MacAddress _DEFAULT_MTU = 1500 @@ -39,7 +39,7 @@ class NodeBase(object): """ Creates a PyCoreObj instance. - :param core.session.Session session: CORE session object + :param core.emulator.session.Session session: CORE session object :param int _id: id :param str name: object name :param bool start: start value @@ -226,7 +226,7 @@ class CoreNodeBase(NodeBase): """ Create a CoreNodeBase instance. - :param core.session.Session session: CORE session object + :param core.emulator.session.Session session: CORE session object :param int _id: object id :param str name: object name :param bool start: boolean for starting @@ -417,14 +417,6 @@ class CoreNodeBase(NodeBase): class CoreNode(CoreNodeBase): """ Provides standard core node logic. - - :var nodedir: str - :var ctrlchnlname: str - :var client: core.netns.vnodeclient.VnodeClient - :var pid: int - :var up: bool - :var lock: threading.RLock - :var _mounts: list[tuple[str, str]] """ apitype = NodeTypes.DEFAULT.value valid_address_types = {"inet", "inet6", "inet6link"} @@ -433,7 +425,7 @@ class CoreNode(CoreNodeBase): """ Create a CoreNode instance. - :param core.session.Session session: core session instance + :param core.emulator.session.Session session: core session instance :param int _id: object id :param str name: object name :param str nodedir: node directory @@ -646,7 +638,7 @@ class CoreNode(CoreNodeBase): :param int ifindex: index for the new interface :param str ifname: name for the new interface - :param net: network to associate interface with + :param core.nodes.base.CoreNetworkBase net: network to associate interface with :return: nothing """ with self.lock: @@ -736,7 +728,7 @@ class CoreNode(CoreNodeBase): Set hardware addres for an interface. :param int ifindex: index of interface to set hardware address for - :param core.misc.ipaddress.MacAddress addr: hardware address to set + :param core.nodes.ipaddress.MacAddress addr: hardware address to set :return: nothing :raises CoreCommandError: when a non-zero exit status occurs """ @@ -819,9 +811,9 @@ class CoreNode(CoreNodeBase): """ Create a new network interface. - :param net: network to associate with + :param core.nodes.base.CoreNetworkBase net: network to associate with :param list addrlist: addresses to add on the interface - :param core.misc.ipaddress.MacAddress hwaddr: hardware address to set for interface + :param core.nodes.ipaddress.MacAddress hwaddr: hardware address to set for interface :param int ifindex: index of interface to create :param str ifname: name for interface :return: interface index @@ -864,7 +856,7 @@ class CoreNode(CoreNodeBase): Connect a node. :param str ifname: name of interface to connect - :param core.netns.nodes.LxcNode othernode: node to connect to + :param core.nodes.CoreNodeBase othernode: node to connect to :param str otherifname: interface name to connect to :return: nothing """ @@ -970,9 +962,9 @@ class CoreNetworkBase(NodeBase): def __init__(self, session, _id, name, start=True): """ - Create a PyCoreNet instance. + Create a CoreNetworkBase instance. - :param core.session.Session session: CORE session object + :param core.emulator.session.Session session: CORE session object :param int _id: object id :param str name: object name :param bool start: should object start diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index 486a0c9f..8530804b 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -1,8 +1,7 @@ """ client.py: implementation of the VnodeClient class for issuing commands over a control channel to the vnoded process running in a network namespace. -The control channel can be accessed via calls to the vcmd Python module or -by invoking the vcmd shell command. +The control channel can be accessed via calls using the vcmd shell. """ import logging @@ -124,8 +123,6 @@ class VnodeClient(object): """ self._verify_connection() args = utils.split_args(args) - # if isinstance(args, list): - # args = " ".join(args) cmd = self._cmd_args() + args logging.info("popen: %s", cmd) p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE) diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 38b34ea1..3d8b6dc5 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -24,7 +24,7 @@ class CoreInterface(object): """ Creates a PyCoreNetIf instance. - :param core.coreobj.PyCoreNode node: node for interface + :param core.nodes.base.CoreNode node: node for interface :param str name: interface name :param mtu: mtu value """ @@ -67,7 +67,7 @@ class CoreInterface(object): """ Attach network. - :param core.coreobj.PyCoreNet net: network to attach + :param core.nodes.base.CoreNetworkBase net: network to attach :return: nothing """ if self.net: @@ -109,7 +109,7 @@ class CoreInterface(object): """ Set hardware address. - :param core.misc.ipaddress.MacAddress addr: hardware address to set to. + :param core.nodes.ipaddress.MacAddress addr: hardware address to set to. :return: nothing """ self.hwaddr = addr @@ -199,7 +199,7 @@ class Veth(CoreInterface): """ Creates a VEth instance. - :param core.netns.vnode.SimpleLxcNode node: related core node + :param core.nodes.base.CoreNode node: related core node :param str name: interface name :param str localname: interface local name :param mtu: interface mtu @@ -260,11 +260,11 @@ class TunTap(CoreInterface): """ Create a TunTap instance. - :param core.netns.vnode.SimpleLxcNode node: related core node + :param core.nodes.base.CoreNode node: related core node :param str name: interface name :param str localname: local interface name :param mtu: interface mtu - :param net: related network + :param core.nodes.base.CoreNetworkBase net: related network :param bool start: start flag """ CoreInterface.__init__(self, node=node, name=name, mtu=mtu) @@ -420,9 +420,9 @@ class GreTap(CoreInterface): """ Creates a GreTap instance. - :param core.netns.vnode.SimpleLxcNode node: related core node + :param core.nodes.base.CoreNode node: related core node :param str name: interface name - :param core.session.Session session: core session instance + :param core.emulator.session.Session session: core session instance :param mtu: interface mtu :param str remoteip: remote address :param int _id: object id @@ -493,6 +493,6 @@ class GreTap(CoreInterface): :param flags: link flags :return: link data - :rtype: list[core.data.LinkData] + :rtype: list[core.emulator.data.LinkData] """ return [] diff --git a/daemon/core/nodes/ipaddress.py b/daemon/core/nodes/ipaddress.py index 111bb38c..cf9eadeb 100644 --- a/daemon/core/nodes/ipaddress.py +++ b/daemon/core/nodes/ipaddress.py @@ -215,7 +215,7 @@ class IpPrefix(object): Create a IpPrefix instance. :param int af: address family for ip prefix - :param prefixstr: ip prefix string + :param str prefixstr: ip prefix string """ # prefixstr format: address/prefixlen tmp = prefixstr.split("/") diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 899f636f..5520e375 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -1,6 +1,5 @@ """ -PyCoreNet and LxBrNet classes that implement virtual networks using -Linux Ethernet bridging and ebtables rules. +Defines network nodes used within core. """ import logging @@ -343,7 +342,7 @@ class CoreNetwork(CoreNetworkBase): """ Detach a network interface. - :param core.netns.vif.Veth netif: network interface to detach + :param core.nodes.interface.Veth netif: network interface to detach :return: nothing """ if self.up: @@ -355,8 +354,8 @@ class CoreNetwork(CoreNetworkBase): """ Determine if the provided network interfaces are linked. - :param core.netns.vif.Veth netif1: interface one - :param core.netns.vif.Veth netif2: interface two + :param core.nodes.interface.CoreInterface netif1: interface one + :param core.nodes.interface.CoreInterface netif2: interface two :return: True if interfaces are linked, False otherwise :rtype: bool """ @@ -385,8 +384,8 @@ class CoreNetwork(CoreNetworkBase): Unlink two PyCoreNetIfs, resulting in adding or removing ebtables filtering rules. - :param core.netns.vif.Veth netif1: interface one - :param core.netns.vif.Veth netif2: interface two + :param core.nodes.interface.CoreInterface netif1: interface one + :param core.nodes.interface.CoreInterface netif2: interface two :return: nothing """ with self._linked_lock: @@ -401,8 +400,8 @@ class CoreNetwork(CoreNetworkBase): Link two PyCoreNetIfs together, resulting in adding or removing ebtables filtering rules. - :param core.netns.vif.Veth netif1: interface one - :param core.netns.vif.Veth netif2: interface two + :param core.nodes.interface.CoreInterface netif1: interface one + :param core.nodes.interface.CoreInterface netif2: interface two :return: nothing """ with self._linked_lock: @@ -417,7 +416,7 @@ class CoreNetwork(CoreNetworkBase): """ Configure link parameters by applying tc queuing disciplines on the interface. - :param core.netns.vif.Veth netif: interface one + :param core.nodes.interface.Veth netif: interface one :param bw: bandwidth to set to :param delay: packet delay to set to :param loss: packet loss to set to @@ -549,9 +548,9 @@ class CoreNetwork(CoreNetworkBase): Return the interface of that links this net with another net (that were linked using linknet()). - :param core.netns.vnet.LxBrNet net: interface to get link for + :param core.nodes.base.CoreNetworkBase net: interface to get link for :return: interface the provided network is linked to - :rtype: core.netns.vnet.LxBrNet + :rtype: core.nodes.interface.CoreInterface """ for netif in self.netifs(): if hasattr(netif, "othernet") and netif.othernet == net: @@ -584,7 +583,7 @@ class GreTapBridge(CoreNetwork): """ Create a GreTapBridge instance. - :param core.session.Session session: core session instance + :param core.emulator.session.Session session: core session instance :param str remoteip: remote address :param int _id: object id :param str name: object name @@ -593,7 +592,6 @@ class GreTapBridge(CoreNetwork): :param ttl: ttl value :param key: gre tap key :param bool start: start flag - :return: """ CoreNetwork.__init__(self, session=session, _id=_id, name=name, policy=policy, start=False) self.grekey = key @@ -685,7 +683,7 @@ class CtrlNet(CoreNetwork): """ Creates a CtrlNet instance. - :param core.session.Session session: core session instance + :param core.emulator.session.Session session: core session instance :param int _id: node id :param str name: node namee :param prefix: control network ipv4 prefix @@ -740,7 +738,7 @@ class CtrlNet(CoreNetwork): def detectoldbridge(self): """ - Occassionally, control net bridges from previously closed sessions are not cleaned up. + Occasionally, control net bridges from previously closed sessions are not cleaned up. Check if there are old control net bridges and delete them :return: True if an old bridge was detected, False otherwise @@ -825,7 +823,7 @@ class PtpNet(CoreNetwork): :param float lon: longitude :param float alt: altitude :return: node data object - :rtype: core.data.NodeData + :rtype: core.emulator.data.NodeData """ return None @@ -836,7 +834,7 @@ class PtpNet(CoreNetwork): :param flags: message flags :return: list of link data - :rtype: list[core.data.LinkData] + :rtype: list[core.emulator.data.LinkData] """ all_links = [] @@ -997,7 +995,7 @@ class WlanNode(CoreNetwork): """ Attach a network interface. - :param core.netns.vif.VEth netif: network interface + :param core.nodes.interface.CoreInterface netif: network interface :return: nothing """ CoreNetwork.attach(self, netif) @@ -1013,7 +1011,7 @@ class WlanNode(CoreNetwork): """ Sets the mobility and wireless model. - :param core.mobility.WirelessModel.cls model: wireless model to set to + :param core.location.mobility.WirelessModel.cls model: wireless model to set to :param dict config: configuration for model being set :return: nothing """ @@ -1056,7 +1054,7 @@ class WlanNode(CoreNetwork): :param flags: message flags :return: list of link data - :rtype: list[core.data.LinkData] + :rtype: list[core.emulator.data.LinkData] """ all_links = CoreNetwork.all_link_data(self, flags) diff --git a/daemon/core/nodes/nodeutils.py b/daemon/core/nodes/nodeutils.py index 2aa88b8f..95c53165 100644 --- a/daemon/core/nodes/nodeutils.py +++ b/daemon/core/nodes/nodeutils.py @@ -23,7 +23,7 @@ def _convert_map(x, y): :param dict x: dictionary to reduce node items into :param tuple y: current node item - :return: + :return: human readable name mapping of the node map """ x[y[0].name] = y[1] return x @@ -33,7 +33,6 @@ def update_node_map(node_map): """ Update the current node map with the provided node map values. - :param dict node_map: node map to update with """ global _NODE_MAP @@ -70,7 +69,7 @@ def get_node_type(node_class): :param class node_class: node class to get type for :return: node type - :rtype: core.enumerations.NodeTypes + :rtype: core.emulator.enumerations.NodeTypes """ global _NODE_MAP node_type_map = {_NODE_MAP[x]: x for x in _NODE_MAP} diff --git a/daemon/core/nodes/openvswitch.py b/daemon/core/nodes/openvswitch.py index facff596..12923f4a 100644 --- a/daemon/core/nodes/openvswitch.py +++ b/daemon/core/nodes/openvswitch.py @@ -53,12 +53,11 @@ class OvsNet(CoreNetworkBase): """ Creates an OvsNet instance. - :param core.session.Session session: session this object is a part of - :param _id: - :param name: - :param start: - :param policy: - :return: + :param core.emulator.session.Session session: session this object is a part of + :param int _id: object id + :param str name: object name + :param bool start: start flag + :param policy: network policy """ CoreNetworkBase.__init__(self, session, _id, name, start) @@ -173,7 +172,7 @@ class OvsNet(CoreNetworkBase): def link(self, interface_one, interface_two): """ - Link two PyCoreNetIfs together, resulting in adding or removing + Link two interfaces together, resulting in adding or removing ebtables filtering rules. """ with self._linked_lock: @@ -299,19 +298,11 @@ class OvsNet(CoreNetworkBase): interface = Veth(node=None, name=name, localname=localname, mtu=1500, net=self, start=self.up) self.attach(interface) if network.up: - # this is similar to net.attach() but uses netif.name instead - # of localname + # this is similar to net.attach() but uses netif.name instead of localname utils.check_cmd([constants.OVS_BIN, "add-port", network.bridge_name, interface.name]) utils.check_cmd([constants.IP_BIN, "link", "set", interface.name, "up"]) - # TODO: is there a native method for this? see if this causes issues - # i = network.newifindex() - # network._netif[i] = interface - # with network._linked_lock: - # network._linked[interface] = {} - # this method call is equal to the above, with a interface.netifi = call network.attach(interface) - interface.net = self interface.othernet = network return interface diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 388cddb6..9c57bcb1 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -99,7 +99,7 @@ class PhysicalNode(CoreNodeBase): def sethwaddr(self, ifindex, addr): """ - same as SimpleLxcNode.sethwaddr() + Set hardware address for an interface. """ self._netif[ifindex].sethwaddr(addr) ifname = self.ifname(ifindex) @@ -108,7 +108,7 @@ class PhysicalNode(CoreNodeBase): def addaddr(self, ifindex, addr): """ - same as SimpleLxcNode.addaddr() + Add an address to an interface. """ if self.up: self.check_cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.ifname(ifindex)]) @@ -117,7 +117,7 @@ class PhysicalNode(CoreNodeBase): def deladdr(self, ifindex, addr): """ - same as SimpleLxcNode.deladdr() + Delete an address from an interface. """ try: self._netif[ifindex].deladdr(addr) @@ -258,7 +258,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): """ Create an RJ45Node instance. - :param core.session.Session session: core session instance + :param core.emulator.session.Session session: core session instance :param int _id: node id :param str name: node name :param mtu: rj45 mtu @@ -336,7 +336,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): represents an interface, we do not create another object here, but attach ourselves to the given network. - :param core.coreobj.PyCoreNet net: new network instance + :param core.nodes.base.CoreNetworkBase net: new network instance :param list[str] addrlist: address list :param str hwaddr: hardware address :param int ifindex: interface index @@ -392,7 +392,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): :param int ifindex: interface index to retrieve :param net: network to retrieve :return: a network interface - :rtype: core.coreobj.PyCoreNetIf + :rtype: core.nodes.interface,CoreInterface """ if net is not None and net == self.net: return self @@ -409,7 +409,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): """ Retrieve network interface index. - :param core.coreobj.PyCoreNetIf netif: network interface to retrieve index for + :param core.nodes.interface.CoreInterface netif: network interface to retrieve index for :return: interface index, None otherwise :rtype: int """ diff --git a/daemon/core/plugins/sdt.py b/daemon/core/plugins/sdt.py index 79d7597c..0b8e3e9e 100644 --- a/daemon/core/plugins/sdt.py +++ b/daemon/core/plugins/sdt.py @@ -61,7 +61,7 @@ class Sdt(object): """ Creates a Sdt instance. - :param core.session.Session session: session this manager is tied to + :param core.emulator.session.Session session: session this manager is tied to """ self.session = session self.sock = None @@ -83,7 +83,7 @@ class Sdt(object): """ Handler for node updates, specifically for updating their location. - :param core.data.NodeData node_data: node data being updated + :param core.emulator.data.NodeData node_data: node data being updated :return: nothing """ x = node_data.x_position @@ -103,7 +103,7 @@ class Sdt(object): """ Handler for link updates, checking for wireless link/unlink messages. - :param core.data.LinkData link_data: link data being updated + :param core.emulator.data.LinkData link_data: link data being updated :return: nothing """ if link_data.link_type == LinkTypes.WIRELESS.value: diff --git a/daemon/core/services/coreservices.py b/daemon/core/services/coreservices.py index 59448fd2..9b3c04b1 100644 --- a/daemon/core/services/coreservices.py +++ b/daemon/core/services/coreservices.py @@ -125,7 +125,7 @@ class ServiceShim(object): Convert service properties into a string list of key=value pairs, separated by "|". - :param core.netns.vnode.CoreNode node: node to get value list for + :param core.nodes.base.CoreNode node: node to get value list for :param CoreService service: service to get value list for :return: value list string :rtype: str @@ -441,7 +441,7 @@ class CoreServices(object): """ Start all service boot paths found, based on dependencies. - :param core.netns.vnode.LxcNode node: node to start services on + :param core.nodes.base.CoreNode node: node to start services on :param list[CoreService] boot_path: service to start in dependent order :return: nothing """ @@ -458,7 +458,7 @@ class CoreServices(object): Start a service on a node. Create private dirs, generate config files, and execute startup commands. - :param core.netns.vnode.LxcNode node: node to boot services on + :param core.nodes.base.CoreNode node: node to boot services on :param CoreService service: service to start :return: nothing """ @@ -511,7 +511,7 @@ class CoreServices(object): config references an existing file that should be copied. Returns True for local files, False for generated. - :param core.netns.vnode.LxcNode node: node to copy service for + :param core.nodes.base.CoreNode node: node to copy service for :param str filename: file name for a configured service :param str cfg: configuration string :return: True if successful, False otherwise @@ -530,7 +530,7 @@ class CoreServices(object): """ Run the validation command(s) for a service. - :param core.netns.vnode.LxcNode node: node to validate service for + :param core.nodes.base.CoreNode node: node to validate service for :param CoreService service: service to validate :return: service validation status :rtype: int @@ -567,7 +567,7 @@ class CoreServices(object): """ Stop a service on a node. - :param core.netns.vnode.LxcNode node: node to stop a service on + :param core.nodes.base.CoreNode node: node to stop a service on :param CoreService service: service to stop :return: status for stopping the services :rtype: str @@ -586,7 +586,7 @@ class CoreServices(object): 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 core.netns.vnode.LxcNode node: node to get service file from + :param core.nodes.base.CoreNode node: node to get service file from :param str service_name: service to get file from :param str filename: file name to retrieve :return: file message for node @@ -655,7 +655,7 @@ class CoreServices(object): """ Startup a node service. - :param PyCoreNode node: node to reconfigure service for + :param core.nodes.base.CoreNode node: node to reconfigure service for :param CoreService service: service to reconfigure :param bool wait: determines if we should wait to validate startup :return: status of startup @@ -682,7 +682,7 @@ class CoreServices(object): """ Creates node service files. - :param PyCoreNode node: node to reconfigure service for + :param core.nodes.base.CoreNode node: node to reconfigure service for :param CoreService service: service to reconfigure :return: nothing """ @@ -715,7 +715,7 @@ class CoreServices(object): """ Reconfigure a node service. - :param PyCoreNode node: node to reconfigure service for + :param core.nodes.base.CoreNode node: node to reconfigure service for :param CoreService service: service to reconfigure :return: nothing """ @@ -805,7 +805,7 @@ class CoreService(object): returns the cls._configs tuple, but this method may be overriden to provide node-specific filenames that may be based on other services. - :param core.netns.vnode.LxcNode node: node to generate config for + :param core.nodes.base.CoreNode node: node to generate config for :return: configuration files :rtype: tuple """ @@ -819,7 +819,7 @@ class CoreService(object): Return the configuration string to be written to a file or sent to the GUI for customization. - :param core.netns.vnode.LxcNode node: node to generate config for + :param core.nodes.base.CoreNode node: node to generate config for :param str filename: file name to generate config for :return: nothing """ @@ -833,7 +833,7 @@ class CoreService(object): overridden to provide node-specific commands that may be based on other services. - :param core.netns.vnode.LxcNode node: node to get startup for + :param core.nodes.base.CoreNode node: node to get startup for :return: startup commands :rtype: tuple """ @@ -847,7 +847,7 @@ class CoreService(object): overridden to provide node-specific commands that may be based on other services. - :param core.netns.vnode.LxcNode node: node to validate + :param core.nodes.base.CoreNode node: node to validate :return: validation commands :rtype: tuple """ diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 366c8b44..5b18f230 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -313,8 +313,8 @@ def expand_corepath(pathname, session=None, node=None): Expand a file path given session information. :param str pathname: file path to expand - :param core.session.Session session: core session object to expand path with - :param core.netns.LxcNode node: node to expand path with + :param core.emulator.session.Session session: core session object to expand path with + :param core.nodes.base.CoreNode node: node to expand path with :return: expanded path :rtype: str """ From b448c6ebf08bee281cb2a1fba320ab9e74fd1f68 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Fri, 7 Jun 2019 09:10:34 -0700 Subject: [PATCH 0074/1992] bumped version to 5.2.2 and fixed issue in gui that prevented moving nodes while mobility was occuring --- configure.ac | 2 +- gui/wlan.tcl | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 10c1c245..bd9127c3 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 5.2.1, core-dev@nrl.navy.mil) +AC_INIT(core, 5.2.2, core-dev@nrl.navy.mil) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) diff --git a/gui/wlan.tcl b/gui/wlan.tcl index b110a4aa..bea770a7 100644 --- a/gui/wlan.tcl +++ b/gui/wlan.tcl @@ -243,7 +243,6 @@ proc moveNode { c node img xpos ypos dx dy } { "wlanlink && need_redraw"] { redrawWlanLink $wlanlink } - $c dtag node selected $c delete -withtags selectmark $c dtag link need_redraw $c dtag wlanlink need_redraw From 8ce6703875335c61a5ee4dec4918a9d733e8bf0a Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 7 Jun 2019 21:48:47 -0700 Subject: [PATCH 0075/1992] removed unwanted man page --- man/core-xen-cleanup.1 | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 man/core-xen-cleanup.1 diff --git a/man/core-xen-cleanup.1 b/man/core-xen-cleanup.1 deleted file mode 100644 index 92e1f3be..00000000 --- a/man/core-xen-cleanup.1 +++ /dev/null @@ -1,28 +0,0 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH CORE-XEN-CLEANUP "1" "2014-08-06" "CORE-XEN-CLEANUP" "User Commands" -.SH NAME -core-xen-cleanup \- clean-up script for CORE Xen domUs -.SH DESCRIPTION -usage: core\-xen\-cleanup [\-d] -.IP -Clean up all CORE Xen domUs, bridges, interfaces, and session -directories. Options: -.TP -\fB\-h\fR -show this help message and exit -.TP -\fB\-d\fR -also kill the Python daemon -.SH "SEE ALSO" -.BR core-gui(1), -.BR core-daemon(1), -.BR coresendmsg(1), -.BR core-cleanup(1), -.BR vcmd(1), -.BR vnoded(1) -.SH BUGS -Warning! This script will remove logical volumes that match the name "/dev/vg*/c*-n*-" on all volume groups. Use with care. -Report bugs to -.BI https://github.com/coreemu/core/issues - - From 5bf53f1ad83d4a5d738a85f70caea6563095af2f Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 7 Jun 2019 21:49:15 -0700 Subject: [PATCH 0076/1992] cleared out netns make for building python wrappers --- netns/Makefile.am | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/netns/Makefile.am b/netns/Makefile.am index 3fdc0cb6..91aaa605 100644 --- a/netns/Makefile.am +++ b/netns/Makefile.am @@ -27,43 +27,6 @@ vcmd_LDADD = @libev_LIBS@ vcmd_SOURCES = $(SRC_COMMON) $(SRC_VCMD) netns_SOURCES = $(SRC_NETNS) -# this triggers automake to run setup.py for building the Python libraries -# actual library names are netns.so and vcmd.so -# SOURCES line prevents 'make dist' from looking for a 'libnetns.c' file -#if WANT_PYTHON -#noinst_LIBRARIES = libnetns.a -#libnetns_a_SOURCES = netnsmodule.c vcmdmodule.c -#libnetns.a: -# LDFLAGS="$(LDFLAGS) @libev_LIBS@" CFLAGS="$(CFLAGS) @libev_CFLAGS@" $(PYTHON) setup.py build_ext -# -## Python libraries install -#install-exec-local: -# $(PYTHON) $(SETUPPY) $(SETUPPYFLAGS) install \ -# --root=/$(DESTDIR) \ -# --prefix=$(prefix) \ -# --install-lib=$(pythondir) \ -# --single-version-externally-managed \ -# --no-compile -# -## Python libraries uninstall -#uninstall-hook: -# rm -rf core_netns.egg-info -# rm -rf $(DESTDIR)/$(pythondir)/core_netns-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg-info -# rm -f $(DESTDIR)/$(bindir)/{vnoded,vcmd,netns} -# rm -f $(DESTDIR)/$(pythondir)/{netns.so,vcmd.so} -# -## Python libraries cleanup -#clean-local: clean-local-check -#.PHONY: clean-local-check -#clean-local-check: -# -rm -rf build -# -#distclean-local: -# -rm -rf core_netns.egg-info -# -#endif -# endif WANT_PYTHON - # extra cruft to remove DISTCLEANFILES = Makefile.in MANIFEST From 7594afec8280b5c1aefdcd38304b8970388a2b81 Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 7 Jun 2019 21:49:48 -0700 Subject: [PATCH 0077/1992] updated all scripts to be executable --- daemon/scripts/core-cleanup | 0 daemon/scripts/core-manage | 0 daemon/scripts/coresendmsg | 0 3 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 daemon/scripts/core-cleanup mode change 100644 => 100755 daemon/scripts/core-manage mode change 100644 => 100755 daemon/scripts/coresendmsg diff --git a/daemon/scripts/core-cleanup b/daemon/scripts/core-cleanup old mode 100644 new mode 100755 diff --git a/daemon/scripts/core-manage b/daemon/scripts/core-manage old mode 100644 new mode 100755 diff --git a/daemon/scripts/coresendmsg b/daemon/scripts/coresendmsg old mode 100644 new mode 100755 From 1b71061cdbf944cb6fff6078e6f11634e8509359 Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 7 Jun 2019 21:50:32 -0700 Subject: [PATCH 0078/1992] make building new man pages simpler, updated generated man pages --- man/Makefile.am | 16 +++++------ man/core-cleanup.1 | 21 ++++----------- man/core-daemon.1 | 63 +++++++++++++++++-------------------------- man/core-gui.1 | 32 +++++++++++++--------- man/core-manage.1 | 33 +++++++++++------------ man/coresendmsg.1 | 67 ++++++++-------------------------------------- man/netns.1 | 20 +++----------- man/vcmd.1 | 18 +++---------- man/vnoded.1 | 17 +++--------- 9 files changed, 94 insertions(+), 193 deletions(-) diff --git a/man/Makefile.am b/man/Makefile.am index f411548c..e7e65295 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -19,14 +19,14 @@ man_MANS = $(GUI_MANS) $(DAEMON_MANS) .PHONY: generate-mans generate-mans: - $(HELP2MAN) --source CORE 'sh $(top_srcdir)/gui/core-gui' -o core-gui.1.new - $(HELP2MAN) --no-info --source CORE $(top_srcdir)/netns/vnoded -o vnoded.1.new - $(HELP2MAN) --no-info --source CORE $(top_srcdir)/netns/vcmd -o vcmd.1.new - $(HELP2MAN) --no-info --source CORE $(top_srcdir)/netns/netns -o netns.1.new - $(HELP2MAN) --version-string=$(PACKAGE_VERSION) --no-info --source CORE $(top_srcdir)/daemon/scripts/core-daemon -o core-daemon.1.new - $(HELP2MAN) --version-string=$(PACKAGE_VERSION) --no-info --source CORE $(top_srcdir)/daemon/scripts/coresendmsg -o coresendmsg.1.new - $(HELP2MAN) --version-string=$(PACKAGE_VERSION) --no-info --source CORE $(top_srcdir)/daemon/scripts/core-cleanup -o core-cleanup.1.new - $(HELP2MAN) --version-string=$(PACKAGE_VERSION) --no-info --source CORE $(top_srcdir)/daemon/scripts/core-manage -o core-manage.1.new + $(HELP2MAN) --source CORE 'sh $(top_srcdir)/gui/core-gui' -o core-gui.1 + $(HELP2MAN) --no-info --source CORE $(top_srcdir)/netns/vnoded -o vnoded.1 + $(HELP2MAN) --no-info --source CORE $(top_srcdir)/netns/vcmd -o vcmd.1 + $(HELP2MAN) --no-info --source CORE $(top_srcdir)/netns/netns -o netns.1 + $(HELP2MAN) --version-string=$(PACKAGE_VERSION) --no-info --source CORE $(top_srcdir)/daemon/scripts/core-daemon -o core-daemon.1 + $(HELP2MAN) --version-string=$(PACKAGE_VERSION) --no-info --source CORE $(top_srcdir)/daemon/scripts/coresendmsg -o coresendmsg.1 + $(HELP2MAN) --version-string=$(PACKAGE_VERSION) --no-info --source CORE $(top_srcdir)/daemon/scripts/core-cleanup -o core-cleanup.1 + $(HELP2MAN) --version-string=$(PACKAGE_VERSION) --no-info --source CORE $(top_srcdir)/daemon/scripts/core-manage -o core-manage.1 .PHONY: diff diff: diff --git a/man/core-cleanup.1 b/man/core-cleanup.1 index 2ef10474..64aa18fb 100644 --- a/man/core-cleanup.1 +++ b/man/core-cleanup.1 @@ -1,9 +1,9 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH CORE-CLEANUP "1" "2014-08-06" "CORE-CLEANUP" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. +.TH CORE-CLEANUP "1" "June 2019" "CORE" "User Commands" .SH NAME -core-cleanup \- clean-up script for CORE +core-cleanup \- manual page for core-cleanup 5.3.0 .SH DESCRIPTION -usage: core\-cleanup [\-d [\-l]] +usage: ../daemon/scripts/core\-cleanup [\-d [\-l]] .IP Clean up all CORE namespaces processes, bridges, interfaces, and session directories. Options: @@ -15,15 +15,4 @@ show this help message and exit also kill the Python daemon .TP \fB\-l\fR -remove the core-daemon.log file -.SH "SEE ALSO" -.BR core-gui(1), -.BR core-daemon(1), -.BR coresendmsg(1), -.BR vcmd(1), -.BR vnoded(1) -.SH BUGS -Report bugs to -.BI https://github.com/coreemu/core/issues - - +remove the core\-daemon.log file diff --git a/man/core-daemon.1 b/man/core-daemon.1 index f6f8a61e..c8061e0d 100644 --- a/man/core-daemon.1 +++ b/man/core-daemon.1 @@ -1,52 +1,37 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH CORE-DAEMON "1" "2014-08-06" "CORE-DAEMON" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. +.TH CORE-DAEMON "1" "June 2019" "CORE" "User Commands" .SH NAME -core-daemon \- CORE daemon manages emulation sessions started from GUI or scripts -.SH SYNOPSIS -.B core-daemon -[\fI-h\fR] [\fIoptions\fR] [\fIargs\fR] +core-daemon \- manual page for core-daemon 5.3.0 .SH DESCRIPTION -CORE daemon instantiates Linux network namespace nodes. -.SH OPTIONS +usage: core\-daemon [\-h] [\-f CONFIGFILE] [\-p PORT] [\-n NUMTHREADS] [\-\-ovs] +.IP +[\-\-grpc] [\-\-grpc\-port GRPCPORT] +[\-\-grpc\-address GRPCADDRESS] +.PP +CORE daemon v.5.3.0 instantiates Linux network namespace nodes. +.SS "optional arguments:" .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP -\fB\-f\fR CONFIGFILE, \fB\-\-configfile\fR=\fICONFIGFILE\fR +\fB\-f\fR CONFIGFILE, \fB\-\-configfile\fR CONFIGFILE read config from specified file; default = -/etc/core/core.conf +\fI\,/etc/core/core.conf\/\fP .TP -\fB\-d\fR, \fB\-\-daemonize\fR -run in background as daemon; default=False -.TP -\fB\-e\fR EXECFILE, \fB\-\-execute\fR=\fIEXECFILE\fR -execute a Python/XML\-based session -.TP -\fB\-l\fR LOGFILE, \fB\-\-logfile\fR=\fILOGFILE\fR -log output to specified file; default = -/var/log/core-daemon.log -.TP -\fB\-p\fR PORT, \fB\-\-port\fR=\fIPORT\fR +\fB\-p\fR PORT, \fB\-\-port\fR PORT port number to listen on; default = 4038 .TP -\fB\-i\fR PIDFILE, \fB\-\-pidfile\fR=\fIPIDFILE\fR -filename to write pid to; default = /var/run/core-daemon.pid -.TP -\fB\-t\fR NUMTHREADS, \fB\-\-numthreads\fR=\fINUMTHREADS\fR +\fB\-n\fR NUMTHREADS, \fB\-\-numthreads\fR NUMTHREADS number of server threads; default = 1 .TP -\fB\-v\fR, \fB\-\-verbose\fR -enable verbose logging; default = False +\fB\-\-ovs\fR +enable experimental ovs mode, default is false .TP -\fB\-g\fR, \fB\-\-debug\fR -enable debug logging; default = False -.SH "SEE ALSO" -.BR core-gui(1), -.BR coresendmsg(1), -.BR netns(1), -.BR vcmd(1), -.BR vnoded(1) -.SH BUGS -Report bugs to -.BI https://github.com/coreemu/core/issues - +\fB\-\-grpc\fR +enable grpc api, default is false +.TP +\fB\-\-grpc\-port\fR GRPCPORT +grpc port to listen on; default 50051 +.TP +\fB\-\-grpc\-address\fR GRPCADDRESS +grpc address to listen on; default localhost diff --git a/man/core-gui.1 b/man/core-gui.1 index f646bb5f..5792e0c6 100644 --- a/man/core-gui.1 +++ b/man/core-gui.1 @@ -1,11 +1,14 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH CORE-GUI "1" "2014-08-06" "CORE-GUI" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. +.TH CORE-GUI "1" "June 2019" "CORE" "User Commands" .SH NAME -core-gui \- Common Open Research Emulator (CORE) graphical user interface +core-gui \- manual page for core-gui version 5.3.0 (20190607) .SH SYNOPSIS .B core-gui -[\fI-h|-v\fR] [\fI-b|-c \fR] [\fI-s\fR] [\fI-a address\fR] [\fI-p port\fR] [\fI\fR] +[\fI\,-h|-v\/\fR] [\fI\,-b|-c \/\fR] [\fI\,-s\/\fR] [\fI\,-a address\/\fR] [\fI\,-p port\/\fR] .SH DESCRIPTION +.IP +[] +.PP Launches the CORE Tcl/Tk X11 GUI or starts an imn\-based emulation. .TP \-(\fB\-h\fR)elp @@ -26,7 +29,7 @@ start in execute mode, not edit mode \-(\fB\-a\fR)ddress connect to the specified IP address (default 127.0.0.1) .TP -\-(\fB\-p\fR)ort +\-(\fB\-p\fR)port connect to the specified TCP port (default 4038) .TP @@ -34,11 +37,14 @@ connect to the specified TCP port (default 4038) .PP With no parameters, starts the GUI in edit mode with a blank canvas. .SH "SEE ALSO" -.BR core-daemon(1), -.BR coresendmsg(1), -.BR vcmd(1), -.BR vnoded(1) -.SH BUGS -Report bugs to -.BI https://github.com/coreemu/core/issues - +The full documentation for +.B core-gui +is maintained as a Texinfo manual. If the +.B info +and +.B core-gui +programs are properly installed at your site, the command +.IP +.B info core-gui +.PP +should give you access to the complete manual. diff --git a/man/core-manage.1 b/man/core-manage.1 index 6b17f833..7e7c6f42 100644 --- a/man/core-manage.1 +++ b/man/core-manage.1 @@ -1,34 +1,31 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH CORE-MANAGE "1" "2014-08-06" "CORE-MANAGE" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. +.TH CORE-MANAGE "1" "June 2019" "CORE" "User Commands" .SH NAME -core-manage \- helper tool to add, remove, or check for services and models in a CORE installation. +core-manage \- manual page for core-manage 5.3.0 .SH SYNOPSIS .B core-manage -[\fI-h\fR] [\fIoptions\fR] \fI \fR +[\fI\,-h\/\fR] [\fI\,options\/\fR] \fI\, \/\fR .SH DESCRIPTION -.TP - should be one of: add, remove, check -.TP - should be one of: service, model -.TP - is the text to add, remove, check +Helper tool to add, remove, or check for services, models, and node types +in a CORE installation. .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR show this help message and exit .TP -\fB\-\-userpath\fR=\fIUSERPATH\fR +\fB\-\-userpath\fR=\fI\,USERPATH\/\fR use the specified user path (e.g. "$HOME/.core") to access nodes.conf .TP \fB\-v\fR, \fB\-\-verbose\fR be verbose when performing action .SH EXAMPLES -.TP +.IP core\-manage add service newrouting -.TP -core\-manage \fB\-v\fR check model RfPipe -.TP -core\-manage \fB\-\-userpath=\fR"$HOME/.core" add nodetype "{ftp ftp.gif ftp.gif {DefaultRoute FTP} netns {FTP server} }" -.SH "SEE ALSO" -.BR core-daemon(1) +core\-manage \-v check model RfPipe +core\-manage \-\-userpath="$HOME/.core" add nodetype "{ftp ftp.gif ftp.gif {DefaultRoute FTP} netns {FTP server} }" +.SS "Arguments:" +.IP + should be one of: add, remove, check + should be one of: service, model, nodetype + is the text to add, remove, check diff --git a/man/coresendmsg.1 b/man/coresendmsg.1 index 772e3cda..9a42e29a 100644 --- a/man/coresendmsg.1 +++ b/man/coresendmsg.1 @@ -1,17 +1,17 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH CORESENDMSG "1" "2014-08-06" "CORESENDMSG" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. +.TH CORESENDMSG "1" "June 2019" "CORE" "User Commands" .SH NAME -coresendmsg \- send a CORE API message to the core-daemon daemon +coresendmsg \- manual page for coresendmsg 5.3.0 .SH SYNOPSIS .B coresendmsg -[\fI-h|-H\fR] [\fIoptions\fR] [\fImessage-type\fR] [\fIflags=flags\fR] [\fImessage-TLVs\fR] +[\fI\,-h|-H\/\fR] [\fI\,options\/\fR] [\fI\,message-type\/\fR] [\fI\,flags=flags\/\fR] [\fI\,message-TLVs\/\fR] .SH DESCRIPTION .SS "Supported message types:" .IP -['node', 'link', 'exec', 'reg', 'conf', 'file', 'iface', 'event', 'sess', 'excp'] +['NODE', 'LINK', 'EXECUTE', 'REGISTER', 'CONFIG', 'FILE', 'INTERFACE', 'EVENT', 'SESSION', 'EXCEPTION'] .SS "Supported message flags (flags=f1,f2,...):" .IP -['add', 'del', 'cri', 'loc', 'str', 'txt', 'tty'] +['ADD', 'DELETE', 'CRI', 'LOCAL', 'STRING', 'TEXT', 'TTY'] .SH OPTIONS .TP \fB\-h\fR, \fB\-\-help\fR @@ -20,13 +20,13 @@ show this help message and exit \fB\-H\fR show example usage help message and exit .TP -\fB\-p\fR PORT, \fB\-\-port\fR=\fIPORT\fR +\fB\-p\fR PORT, \fB\-\-port\fR=\fI\,PORT\/\fR TCP port to connect to, default: 4038 .TP -\fB\-a\fR ADDRESS, \fB\-\-address\fR=\fIADDRESS\fR +\fB\-a\fR ADDRESS, \fB\-\-address\fR=\fI\,ADDRESS\/\fR Address to connect to, default: localhost .TP -\fB\-s\fR SESSION, \fB\-\-session\fR=\fISESSION\fR +\fB\-s\fR SESSION, \fB\-\-session\fR=\fI\,SESSION\/\fR Session to join, default: None .TP \fB\-l\fR, \fB\-\-listen\fR @@ -35,51 +35,6 @@ Listen for a response message and print it. \fB\-t\fR, \fB\-\-list\-tlvs\fR List TLVs for the specified message type. .TP -\fB\-T\fR, \fB\-\-tcp\fR -Use TCP instead of UDP and connect to a session, +\fB\-\-tcp\fR +Use TCP instead of UDP and connect to a session default: False -.PP -Usage: coresendmsg [\-h|\-H] [options] [message\-type] [flags=flags] [message\-TLVs] -.SS "Supported message types:" -.IP -['node', 'link', 'exec', 'reg', 'conf', 'file', 'iface', 'event', 'sess', 'excp'] -.SS "Supported message flags (flags=f1,f2,...):" -.IP -['add', 'del', 'cri', 'loc', 'str', 'txt', 'tty'] -.TP -\fB\-h\fR, \fB\-\-help\fR -show this help message and exit -.TP -\fB\-H\fR -show example usage help message and exit -.TP -\fB\-p\fR PORT, \fB\-\-port\fR=\fIPORT\fR -TCP port to connect to, default: 4038 -.TP -\fB\-a\fR ADDRESS, \fB\-\-address\fR=\fIADDRESS\fR -Address to connect to, default: localhost -.TP -\fB\-s\fR SESSION, \fB\-\-session\fR=\fISESSION\fR -Session to join, default: None -.TP -\fB\-l\fR, \fB\-\-listen\fR -Listen for a response message and print it. -.TP -\fB\-t\fR, \fB\-\-list\-tlvs\fR -List TLVs for the specified message type. -.TP -\fB\-T\fR, \fB\-\-tcp\fR -Use TCP instead of UDP and connect to a session, -default: False -.SH "EXAMPLES" -.TP -A list of examples is available using the following command: -coresendmsg \-H -.SH "SEE ALSO" -.BR core-gui(1), -.BR core-daemon(1), -.BR vcmd(1), -.BR vnoded(1) -.SH BUGS -Report bugs to -.BI https://github.com/coreemu/core/issues diff --git a/man/netns.1 b/man/netns.1 index b5308b22..5cb6e312 100644 --- a/man/netns.1 +++ b/man/netns.1 @@ -1,10 +1,10 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH NETNS "1" "2014-08-06" "NETNS" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. +.TH NETNS "1" "June 2019" "CORE" "User Commands" .SH NAME -netns \- run commands within a network namespace +netns \- manual page for netns version 5.3.0 .SH SYNOPSIS .B netns -[\fI-h|-V\fR] [\fI-w\fR] \fI-- command \fR[\fIargs\fR...] +[\fI\,-h|-V\/\fR] [\fI\,-w\/\fR] \fI\,-- command \/\fR[\fI\,args\/\fR...] .SH DESCRIPTION Run the specified command in a new network namespace. .SH OPTIONS @@ -17,15 +17,3 @@ show version number and exit .TP \fB\-w\fR wait for command to complete (useful for interactive commands) -.SH "SEE ALSO" -.BR core-gui(1), -.BR core-daemon(1), -.BR coresendmsg(1), -.BR unshare(1), -.BR vcmd(1), -.BR vnoded(1) -.SH BUGS -Report bugs to -.BI https://github.com/coreemu/core/issues - - diff --git a/man/vcmd.1 b/man/vcmd.1 index 5544f4f9..79f14f50 100644 --- a/man/vcmd.1 +++ b/man/vcmd.1 @@ -1,10 +1,10 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH VCMD "1" "2014-08-06" "VCMD" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. +.TH VCMD "1" "June 2019" "CORE" "User Commands" .SH NAME -vcmd \- run a command in a network namespace created by vnoded +vcmd \- manual page for vcmd version 5.3.0 .SH SYNOPSIS .B vcmd -[\fI-h|-V\fR] [\fI-v\fR] [\fI-q|-i|-I\fR] \fI-c -- command args\fR... +[\fI\,-h|-V\/\fR] [\fI\,-v\/\fR] [\fI\,-q|-i|-I\/\fR] \fI\,-c -- command args\/\fR... .SH DESCRIPTION Run the specified command in the Linux namespace container specified by the control , with the specified arguments. @@ -30,13 +30,3 @@ run the command non\-interactively (without PTY) .TP \fB\-c\fR control channel name (e.g. '/tmp/pycore.45647/n3') -.SH "SEE ALSO" -.BR core-gui(1), -.BR core-daemon(1), -.BR coresendmsg(1), -.BR netns(1), -.BR vnoded(1), -.SH BUGS -Report bugs to -.BI https://github.com/coreemu/core/issues - diff --git a/man/vnoded.1 b/man/vnoded.1 index 34b39e37..841b00e9 100644 --- a/man/vnoded.1 +++ b/man/vnoded.1 @@ -1,10 +1,10 @@ -.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.40.4. -.TH VNODED "1" "2014-08-06" "VNODED" "User Commands" +.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.47.3. +.TH VNODED "1" "June 2019" "CORE" "User Commands" .SH NAME -vnoded \- network namespace daemon used by CORE to create a lightweight container +vnoded \- manual page for vnoded version 5.3.0 .SH SYNOPSIS .B vnoded -[\fI-h|-V\fR] [\fI-v\fR] [\fI-n\fR] [\fI-C \fR] [\fI-l \fR] [\fI-p \fR] \fI-c \fR +[\fI\,-h|-V\/\fR] [\fI\,-v\/\fR] [\fI\,-n\/\fR] [\fI\,-C \/\fR] [\fI\,-l \/\fR] [\fI\,-p \/\fR] \fI\,-c \/\fR .SH DESCRIPTION Linux namespace container server daemon runs as PID 1 in the container. Normally this process is launched automatically by the CORE daemon. @@ -33,12 +33,3 @@ write process id to the specified file .TP \fB\-c\fR establish the specified for receiving control commands -.SH "SEE ALSO" -.BR core-gui(1), -.BR core-daemon(1), -.BR coresendmsg(1), -.BR vcmd(1), -.SH BUGS -Report bugs to -.BI https://github.com/coreemu/core/issues - From 579c0d5783a4852e1b920729c94728de1174b6fb Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 7 Jun 2019 21:51:24 -0700 Subject: [PATCH 0079/1992] version bumped to 5.3.0 and removed empty email field from package maintaners --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index a9d25d39..f38ac290 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. # this defines the CORE version number, must be static for AC_INIT -AC_INIT(core, 5.2.2) +AC_INIT(core, 5.3.0) # autoconf and automake initialization AC_CONFIG_SRCDIR([netns/version.h.in]) @@ -14,7 +14,7 @@ AM_INIT_AUTOMAKE([tar-ustar]) # define variables used for packaging and date display PACKAGE_DATE=m4_esyscmd_s([date +%Y%m%d]) PACKAGE_VENDOR="CORE Developers" -PACKAGE_MAINTAINERS="$PACKAGE_VENDOR <$PACKAGE_BUGREPORT>" +PACKAGE_MAINTAINERS="$PACKAGE_VENDOR" # core specific variables CORE_LIB_DIR="\${prefix}/lib/core" From 39bcdcaf7972110002ad0d51ac0f41d56a93f8f0 Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 7 Jun 2019 22:07:13 -0700 Subject: [PATCH 0080/1992] Updated fpm buiilds to build from destdir and include everything for a simpler single file installation. Also removing python specific dependencies, to use requirements.txt as alternative --- Makefile.am | 68 ++++++++++++++++++----------------------------------- 1 file changed, 23 insertions(+), 45 deletions(-) diff --git a/Makefile.am b/Makefile.am index 42dd4af6..ea114acf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -44,38 +44,18 @@ DISTCLEANFILES = aclocal.m4 \ MAINTAINERCLEANFILES = .version \ .version.date -define fpm-python = -fpm -s python -t $1 \ - -m "$(PACKAGE_MAINTAINERS)" \ - --vendor "$(PACKAGE_VENDOR)" \ - $2 -endef - -define fpm-gui = -fpm -s dir -t $1 -n core-gui \ +define fpm-rpm = +fpm -s dir -t rpm -n core \ -m "$(PACKAGE_MAINTAINERS)" \ --license "BSD" \ - --description "Common Open Research Emulator GUI front-end" \ + --description "Common Open Research Emulator" \ --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core-gui_VERSION_ARCH.$1 \ + -p core_VERSION_ARCH.rpm \ -v $(PACKAGE_VERSION) \ - -d "bash" \ + --rpm-init scripts/core-daemon \ -d "tcl" \ -d "tk" \ - $2 \ - -C $(DESTDIR) -endef - -define fpm-daemon-rpm = -fpm -s python -t rpm \ - -p NAME_sysv_VERSION_ARCH.rpm \ - --rpm-init scripts/core-daemon \ - --python-install-bin $(bindir) \ - --python-install-data $(prefix) \ - --python-install-lib $(pythondir) \ - -m "$(PACKAGE_MAINTAINERS)" \ - --vendor "$(PACKAGE_VENDOR)" \ -d "procps-ng" \ -d "bash >= 3.0" \ -d "bridge-utils" \ @@ -83,19 +63,23 @@ fpm -s python -t rpm \ -d "iproute" \ -d "libev" \ -d "net-tools" \ - -d "python >= 2.7, python < 3.0" \ - netns/setup.py daemon/setup.py + -d "python >= 2.7" \ + -C $(DESTDIR) endef -define fpm-daemon-deb = -fpm -s python -t deb \ - -p NAME_$1_VERSION_ARCH.deb \ - --python-install-bin $(bindir) \ - --python-install-data $(prefix) \ - --python-install-lib $(pythondir) \ - $2 $3 \ +define fpm-deb = +fpm -s dir -t deb -n core \ -m "$(PACKAGE_MAINTAINERS)" \ + --license "BSD" \ + --description "Common Open Research Emulator" \ + --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ + -p core_VERSION_ARCH.deb \ + -v $(PACKAGE_VERSION) \ + --deb-systemd scripts/core-daemon.service \ + -d "tcl" \ + -d "tk" \ + -d "libtk-img" \ -d "procps" \ -d "libc6 >= 2.14" \ -d "bash >= 3.0" \ @@ -103,21 +87,15 @@ fpm -s python -t deb \ -d "ebtables" \ -d "iproute2" \ -d "libev4" \ - -d "python (>= 2.7), python (<< 3.0)" \ - --deb-recommends quagga \ - netns/setup.py daemon/setup.py + -d "python >= 2.7" \ + -C $(DESTDIR) endef .PHONY: fpm fpm: clean-local-fpm - $(MAKE) -C gui install DESTDIR=$(DESTDIR) - $(call fpm-gui,rpm) - $(call fpm-gui,deb,-d "libtk-img") - $(call fpm-python,rpm,ns3/setup.py) - $(call fpm-python,deb,ns3/setup.py) - $(call fpm-daemon-rpm) - $(call fpm-daemon-deb,sysv,--deb-init,scripts/core-daemon) - $(call fpm-daemon-deb,systemd,--deb-systemd,scripts/core-daemon.service) + $(MAKE) install DESTDIR=$(DESTDIR) + $(call fpm-deb) + $(call fpm-rpm) .PHONY: clean-local-fpm clean-local-fpm: From e2f2d9dca06c9cf9aab5cb4abc16ddc351df6950 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 8 Jun 2019 14:01:26 -0700 Subject: [PATCH 0081/1992] added xml tests for network to network links and verifying saving/loading of link options for network and ptp cases --- daemon/tests/test_xml.py | 185 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 179 insertions(+), 6 deletions(-) diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index f093b225..e5e8cf99 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -3,7 +3,7 @@ from xml.etree import ElementTree import pytest from core.emane.ieee80211abg import EmaneIeee80211abgModel -from core.emulator.emudata import NodeOptions +from core.emulator.emudata import NodeOptions, LinkOptions from core.emulator.enumerations import NodeTypes from core.location.mobility import BasicRangeModel from core.services.utility import SshService @@ -16,7 +16,6 @@ class TestXml: :param session: session for test :param tmpdir: tmpdir to create data in - :param str version: xml version to write and parse """ # create hook file_name = "runtime_hook.sh" @@ -51,7 +50,6 @@ class TestXml: :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 @@ -104,7 +102,6 @@ class TestXml: :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 @@ -168,7 +165,6 @@ class TestXml: :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 wlan @@ -230,7 +226,6 @@ class TestXml: :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 emane node for networking the core nodes @@ -290,3 +285,181 @@ class TestXml: assert session.get_node(n2_id) assert session.get_node(emane_id) assert value == "1" + + def test_network_to_network(self, session, tmpdir): + """ + Test xml generation when dealing with network to network nodes. + + :param session: session for test + :param tmpdir: tmpdir to create data in + """ + # create nodes + switch_one = session.add_node(_type=NodeTypes.SWITCH) + switch_two = session.add_node(_type=NodeTypes.SWITCH) + + # link nodes + session.add_link(switch_one.id, switch_two.id) + + # instantiate session + session.instantiate() + + # get ids for nodes + n1_id = switch_one.id + n2_id = switch_two.id + + # save xml + xml_file = tmpdir.join("session.xml") + file_path = xml_file.strpath + session.save_xml(file_path) + + # 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_node(n1_id) + with pytest.raises(KeyError): + assert not session.get_node(n2_id) + + # load saved xml + session.open_xml(file_path, start=True) + + # verify nodes have been recreated + switch_one = session.get_node(n1_id) + switch_two = session.get_node(n2_id) + assert switch_one + assert switch_two + assert len(switch_one.all_link_data(0) + switch_two.all_link_data(0)) == 1 + + def test_link_options(self, session, tmpdir, ip_prefixes): + """ + Test xml client methods for a ptp network. + + :param session: session for test + :param tmpdir: tmpdir to create data in + :param ip_prefixes: generates ip addresses for nodes + """ + # create nodes + node_one = session.add_node() + interface_one = ip_prefixes.create_interface(node_one) + switch = session.add_node(_type=NodeTypes.SWITCH) + + # create link + link_options = LinkOptions() + link_options.per = 20 + link_options.bandwidth = 50000 + link_options.jitter = 10 + link_options.delay = 30 + link_options.dup = 5 + session.add_link(node_one.id, switch.id, interface_one, link_options=link_options) + + # instantiate session + session.instantiate() + + # get ids for nodes + n1_id = node_one.id + n2_id = switch.id + + # save xml + xml_file = tmpdir.join("session.xml") + file_path = xml_file.strpath + session.save_xml(file_path) + + # 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_node(n1_id) + with pytest.raises(KeyError): + assert not session.get_node(n2_id) + + # load saved xml + session.open_xml(file_path, start=True) + + # verify nodes have been recreated + assert session.get_node(n1_id) + assert session.get_node(n2_id) + links = [] + for node_id in session.nodes: + node = session.nodes[node_id] + links += node.all_link_data(0) + link = links[0] + assert link_options.per == link.per + assert link_options.bandwidth == link.bandwidth + assert link_options.jitter == link.jitter + assert link_options.delay == link.delay + assert link_options.dup == link.dup + + def test_link_options_ptp(self, session, tmpdir, ip_prefixes): + """ + Test xml client methods for a ptp network. + + :param session: session for test + :param tmpdir: tmpdir to create data in + :param ip_prefixes: generates ip addresses for nodes + """ + # create nodes + node_one = session.add_node() + interface_one = ip_prefixes.create_interface(node_one) + node_two = session.add_node() + interface_two = ip_prefixes.create_interface(node_two) + + # create link + link_options = LinkOptions() + link_options.per = 20 + link_options.bandwidth = 50000 + link_options.jitter = 10 + link_options.delay = 30 + link_options.dup = 5 + session.add_link(node_one.id, node_two.id, interface_one, interface_two, link_options) + + # instantiate session + session.instantiate() + + # get ids for nodes + n1_id = node_one.id + n2_id = node_two.id + + # save xml + xml_file = tmpdir.join("session.xml") + file_path = xml_file.strpath + session.save_xml(file_path) + + # 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_node(n1_id) + with pytest.raises(KeyError): + assert not session.get_node(n2_id) + + # load saved xml + session.open_xml(file_path, start=True) + + # verify nodes have been recreated + assert session.get_node(n1_id) + assert session.get_node(n2_id) + links = [] + for node_id in session.nodes: + node = session.nodes[node_id] + links += node.all_link_data(0) + link = links[0] + assert link_options.per == link.per + assert link_options.bandwidth == link.bandwidth + assert link_options.jitter == link.jitter + assert link_options.delay == link.delay + assert link_options.dup == link.dup From 191d392c46728d24cb35a0433d07f4a303023a13 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 8 Jun 2019 16:56:39 -0700 Subject: [PATCH 0082/1992] updated grpc node x,y types, added new grpc session position type, grpc updated link options to use int, fixed corehandlers handling of dup, fixed corexml type handling for link options, updated mobility config types to correlate with link options --- daemon/core/api/grpc/client.py | 2 +- daemon/core/api/grpc/server.py | 2 +- daemon/core/api/tlv/corehandlers.py | 5 ++- daemon/core/emulator/session.py | 7 +-- daemon/core/location/mobility.py | 24 +++++----- daemon/core/nodes/network.py | 4 +- daemon/core/nodes/openvswitch.py | 4 +- daemon/core/xml/corexml.py | 30 ++++++++----- daemon/proto/core/api/grpc/core.proto | 33 +++++++++----- daemon/tests/test_xml.py | 65 +++++++++++++++++++++++++++ 10 files changed, 131 insertions(+), 45 deletions(-) diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index a1d218be..9a4f0d63 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -246,7 +246,7 @@ class CoreGrpcClient(object): :rtype: core_pb2.SetSessionLocationResponse :raises grpc.RpcError: when session doesn't exist """ - position = core_pb2.Position(x=x, y=y, z=z, lat=lat, lon=lon, alt=alt) + position = core_pb2.SessionPosition(x=x, y=y, z=z, lat=lat, lon=lon, alt=alt) request = core_pb2.SetSessionLocationRequest(session_id=session_id, position=position, scale=scale) return self.stub.SetSessionLocation(request) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index a7955cb1..120eb98e 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -195,7 +195,7 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): session = self.get_session(request.session_id, context) x, y, z = session.location.refxyz lat, lon, alt = session.location.refgeo - position = core_pb2.Position(x=x, y=y, z=z, lat=lat, lon=lon, alt=alt) + position = core_pb2.SessionPosition(x=x, y=y, z=z, lat=lat, lon=lon, alt=alt) return core_pb2.GetSessionLocationResponse(position=position, scale=session.location.refscale) def SetSessionLocation(self, request, context): diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index 5a145704..efb2a6bc 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -324,6 +324,9 @@ class CoreHandler(socketserver.BaseRequestHandler): per = "" if link_data.per is not None: per = str(link_data.per) + dup = "" + if link_data.dup is not None: + dup = str(link_data.dup) tlv_data = structutils.pack_values(coreapi.CoreLinkTlv, [ (LinkTlvs.N1_NUMBER, link_data.node1_id), @@ -331,7 +334,7 @@ class CoreHandler(socketserver.BaseRequestHandler): (LinkTlvs.DELAY, link_data.delay), (LinkTlvs.BANDWIDTH, link_data.bandwidth), (LinkTlvs.PER, per), - (LinkTlvs.DUP, link_data.dup), + (LinkTlvs.DUP, dup), (LinkTlvs.JITTER, link_data.jitter), (LinkTlvs.MER, link_data.mer), (LinkTlvs.BURST, link_data.burst), diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 556fa88e..de2a38c3 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -367,8 +367,7 @@ class Session(object): if node_two: node_two.lock.release() - def update_link(self, node_one_id, node_two_id, interface_one_id=None, interface_two_id=None, - link_options=LinkOptions()): + def update_link(self, node_one_id, node_two_id, interface_one_id=None, interface_two_id=None, link_options=None): """ Update link information between nodes. @@ -379,6 +378,9 @@ class Session(object): :param core.emulator.emudata.LinkOptions link_options: data to update link with :return: nothing """ + if not link_options: + link_options = LinkOptions() + # get node objects identified by link data node_one, node_two, net_one, net_two, _tunnel = self._link_nodes(node_one_id, node_two_id) @@ -441,7 +443,6 @@ class Session(object): link_config(net_one, interface_one, link_options, interface_two=interface_two) if not link_options.unidirectional: link_config(net_one, interface_two, link_options, interface_two=interface_one) - finally: if node_one: node_one.lock.release() diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 8303813f..4c74447a 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -318,11 +318,11 @@ class BasicRangeModel(WirelessModel): name = "basic_range" options = [ Configuration(_id="range", _type=ConfigDataTypes.UINT32, default="275", label="wireless range (pixels)"), - Configuration(_id="bandwidth", _type=ConfigDataTypes.UINT32, default="54000000", label="bandwidth (bps)"), - Configuration(_id="jitter", _type=ConfigDataTypes.FLOAT, default="0.0", label="transmission jitter (usec)"), - Configuration(_id="delay", _type=ConfigDataTypes.FLOAT, default="5000.0", + Configuration(_id="bandwidth", _type=ConfigDataTypes.UINT64, default="54000000", label="bandwidth (bps)"), + Configuration(_id="jitter", _type=ConfigDataTypes.UINT64, default="0", label="transmission jitter (usec)"), + Configuration(_id="delay", _type=ConfigDataTypes.UINT64, default="5000", label="transmission delay (usec)"), - Configuration(_id="error", _type=ConfigDataTypes.FLOAT, default="0.0", label="error rate (%)") + Configuration(_id="error", _type=ConfigDataTypes.STRING, default="0", label="error rate (%)") ] @classmethod @@ -358,19 +358,19 @@ class BasicRangeModel(WirelessModel): :param dict config: values to convert :return: nothing """ - self.range = float(config["range"]) + self.range = int(config["range"]) logging.info("basic range model configured for WLAN %d using range %d", self.wlan.id, self.range) self.bw = int(config["bandwidth"]) - if self.bw == 0.0: + if self.bw == 0: self.bw = None - self.delay = float(config["delay"]) - if self.delay == 0.0: + self.delay = int(config["delay"]) + if self.delay == 0: self.delay = None - self.loss = float(config["error"]) - if self.loss == 0.0: + self.loss = int(config["error"]) + if self.loss == 0: self.loss = None - self.jitter = float(config["jitter"]) - if self.jitter == 0.0: + self.jitter = int(config["jitter"]) + if self.jitter == 0: self.jitter = None def setlinkparams(self): diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 5520e375..b2be9fd8 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -459,10 +459,10 @@ class CoreNetwork(CoreNetworkBase): netem = ["netem"] changed = max(changed, netif.setparam("delay", delay)) if loss is not None: - loss = float(loss) + loss = int(loss) changed = max(changed, netif.setparam("loss", loss)) if duplicate is not None: - duplicate = float(duplicate) + duplicate = int(duplicate) changed = max(changed, netif.setparam("duplicate", duplicate)) changed = max(changed, netif.setparam("jitter", jitter)) if not changed: diff --git a/daemon/core/nodes/openvswitch.py b/daemon/core/nodes/openvswitch.py index 12923f4a..33bea7f3 100644 --- a/daemon/core/nodes/openvswitch.py +++ b/daemon/core/nodes/openvswitch.py @@ -225,11 +225,11 @@ class OvsNet(CoreNetworkBase): delay_changed = netif.setparam("delay", delay) if loss is not None: - loss = float(loss) + loss = int(loss) loss_changed = netif.setparam("loss", loss) if duplicate is not None: - duplicate = float(duplicate) + duplicate = int(duplicate) duplicate_changed = netif.setparam("duplicate", duplicate) jitter_changed = netif.setparam("jitter", jitter) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index ce15873e..83f9f22e 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -755,9 +755,11 @@ class CoreXmlReader(object): if link_elements is None: return + node_sets = set() for link_element in link_elements.iterchildren(): node_one = get_int(link_element, "node_one") node_two = get_int(link_element, "node_two") + node_set = frozenset((node_one, node_two)) interface_one_element = link_element.find("interface_one") interface_one = None @@ -772,15 +774,15 @@ class CoreXmlReader(object): options_element = link_element.find("options") link_options = LinkOptions() if options_element is not None: - link_options.bandwidth = get_float(options_element, "bandwidth") - link_options.burst = get_float(options_element, "burst") - link_options.delay = get_float(options_element, "delay") - link_options.dup = get_float(options_element, "dup") - link_options.mer = get_float(options_element, "mer") - link_options.mburst = get_float(options_element, "mburst") - link_options.jitter = get_float(options_element, "jitter") - link_options.key = get_float(options_element, "key") - link_options.per = get_float(options_element, "per") + link_options.bandwidth = get_int(options_element, "bandwidth") + link_options.burst = get_int(options_element, "burst") + link_options.delay = get_int(options_element, "delay") + link_options.dup = get_int(options_element, "dup") + link_options.mer = get_int(options_element, "mer") + link_options.mburst = get_int(options_element, "mburst") + link_options.jitter = get_int(options_element, "jitter") + link_options.key = get_int(options_element, "key") + link_options.per = get_int(options_element, "per") link_options.unidirectional = get_int(options_element, "unidirectional") link_options.session = options_element.get("session") link_options.emulation_id = get_int(options_element, "emulation_id") @@ -788,5 +790,11 @@ class CoreXmlReader(object): link_options.opaque = options_element.get("opaque") link_options.gui_attributes = options_element.get("gui_attributes") - logging.info("reading link node_one(%s) node_two(%s)", node_one, node_two) - self.session.add_link(node_one, node_two, interface_one, interface_two, link_options) + if link_options.unidirectional == 1 and node_set in node_sets: + logging.info("updating link node_one(%s) node_two(%s): %s", node_one, node_two, link_options) + self.session.update_link(node_one, node_two, interface_one, interface_two, link_options) + else: + logging.info("adding link node_one(%s) node_two(%s): %s", node_one, node_two, link_options) + self.session.add_link(node_one, node_two, interface_one, interface_two, link_options) + + node_sets.add(node_set) diff --git a/daemon/proto/core/api/grpc/core.proto b/daemon/proto/core/api/grpc/core.proto index 8bbf676b..6f34070f 100644 --- a/daemon/proto/core/api/grpc/core.proto +++ b/daemon/proto/core/api/grpc/core.proto @@ -172,13 +172,13 @@ message GetSessionLocationRequest { } message GetSessionLocationResponse { - Position position = 1; + SessionPosition position = 1; float scale = 2; } message SetSessionLocationRequest { int32 session_id = 1; - Position position = 2; + SessionPosition position = 2; float scale = 3; } @@ -787,15 +787,15 @@ message Link { message LinkOptions { string opaque = 1; - float jitter = 2; - string key = 3; - float mburst = 4; - float mer = 5; - float per = 6; - float bandwidth = 7; - float burst = 8; - float delay = 9; - float dup = 10; + int64 jitter = 2; + int32 key = 3; + int32 mburst = 4; + int32 mer = 5; + int32 per = 6; + int64 bandwidth = 7; + int32 burst = 8; + int64 delay = 9; + int32 dup = 10; bool unidirectional = 11; } @@ -812,7 +812,7 @@ message Interface { int32 mtu = 10; } -message Position { +message SessionPosition { float x = 1; float y = 2; float z = 3; @@ -820,3 +820,12 @@ message Position { float lon = 5; float alt = 6; } + +message Position { + int32 x = 1; + int32 y = 2; + int32 z = 3; + float lat = 4; + float lon = 5; + float alt = 6; +} diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index e5e8cf99..11bb2706 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -463,3 +463,68 @@ class TestXml: assert link_options.jitter == link.jitter assert link_options.delay == link.delay assert link_options.dup == link.dup + + def test_link_options_bidirectional(self, session, tmpdir, ip_prefixes): + """ + Test xml client methods for a ptp network. + + :param session: session for test + :param tmpdir: tmpdir to create data in + :param ip_prefixes: generates ip addresses for nodes + """ + # create nodes + node_one = session.add_node() + interface_one = ip_prefixes.create_interface(node_one) + node_two = session.add_node() + interface_two = ip_prefixes.create_interface(node_two) + + # create link + link_options_one = LinkOptions() + link_options_one.bandwidth = 5000 + link_options_one.unidirectional = 1 + session.add_link(node_one.id, node_two.id, interface_one, interface_two, link_options_one) + link_options_two = LinkOptions() + link_options_two.bandwidth = 10000 + link_options_two.unidirectional = 1 + session.update_link(node_two.id, node_one.id, interface_two.id, interface_one.id, link_options_two) + + # instantiate session + session.instantiate() + + # get ids for nodes + n1_id = node_one.id + n2_id = node_two.id + + # save xml + xml_file = tmpdir.join("session.xml") + file_path = xml_file.strpath + session.save_xml(file_path) + + # 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_node(n1_id) + with pytest.raises(KeyError): + assert not session.get_node(n2_id) + + # load saved xml + session.open_xml(file_path, start=True) + + # verify nodes have been recreated + assert session.get_node(n1_id) + assert session.get_node(n2_id) + links = [] + for node_id in session.nodes: + node = session.nodes[node_id] + links += node.all_link_data(0) + assert len(links) == 2 + link_one = links[0] + link_two = links[1] + assert link_options_one.bandwidth == link_one.bandwidth + assert link_options_two.bandwidth == link_two.bandwidth From 10df7d35ee729259bdf20e805153702f5a8b02d3 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 8 Jun 2019 21:46:07 -0700 Subject: [PATCH 0083/1992] fixed saving bidirectional params for ptp networks, storing to xml and loading back into running environment --- daemon/core/nodes/network.py | 10 +++++----- daemon/core/xml/corexml.py | 2 +- daemon/tests/test_xml.py | 20 ++++++++++++++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index b2be9fd8..dd4cbed7 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -843,7 +843,6 @@ class PtpNet(CoreNetwork): return all_links if1, if2 = self._netif.values() - unidirectional = 0 if if1.getparams() != if2.getparams(): unidirectional = 1 @@ -918,10 +917,11 @@ class PtpNet(CoreNetwork): message_type=0, node1_id=if2.node.id, node2_id=if1.node.id, - delay=if1.getparam("delay"), - bandwidth=if1.getparam("bw"), - dup=if1.getparam("duplicate"), - jitter=if1.getparam("jitter"), + delay=if2.getparam("delay"), + bandwidth=if2.getparam("bw"), + per=if2.getparam("loss"), + dup=if2.getparam("duplicate"), + jitter=if2.getparam("jitter"), unidirectional=1, interface1_id=if2.node.getifindex(if2), interface2_id=if1.node.getifindex(if1) diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index 83f9f22e..fb99e061 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -792,7 +792,7 @@ class CoreXmlReader(object): if link_options.unidirectional == 1 and node_set in node_sets: logging.info("updating link node_one(%s) node_two(%s): %s", node_one, node_two, link_options) - self.session.update_link(node_one, node_two, interface_one, interface_two, link_options) + self.session.update_link(node_one, node_two, interface_one.id, interface_two.id, link_options) else: logging.info("adding link node_one(%s) node_two(%s): %s", node_one, node_two, link_options) self.session.add_link(node_one, node_two, interface_one, interface_two, link_options) diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index 11bb2706..7e8dabd1 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -480,12 +480,20 @@ class TestXml: # create link link_options_one = LinkOptions() - link_options_one.bandwidth = 5000 link_options_one.unidirectional = 1 + link_options_one.bandwidth = 5000 + link_options_one.delay = 10 + link_options_one.per = 5 + link_options_one.dup = 5 + link_options_one.jitter = 5 session.add_link(node_one.id, node_two.id, interface_one, interface_two, link_options_one) link_options_two = LinkOptions() - link_options_two.bandwidth = 10000 link_options_two.unidirectional = 1 + link_options_two.bandwidth = 10000 + link_options_two.delay = 20 + link_options_two.per = 10 + link_options_two.dup = 10 + link_options_two.jitter = 10 session.update_link(node_two.id, node_one.id, interface_two.id, interface_one.id, link_options_two) # instantiate session @@ -527,4 +535,12 @@ class TestXml: link_one = links[0] link_two = links[1] assert link_options_one.bandwidth == link_one.bandwidth + assert link_options_one.delay == link_one.delay + assert link_options_one.per == link_one.per + assert link_options_one.dup == link_one.dup + assert link_options_one.jitter == link_one.jitter assert link_options_two.bandwidth == link_two.bandwidth + assert link_options_two.delay == link_two.delay + assert link_options_two.per == link_two.per + assert link_options_two.dup == link_two.dup + assert link_options_two.jitter == link_two.jitter From 223bcb64f1953b2a2a478fee928caf51d5f6e567 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 8 Jun 2019 22:00:02 -0700 Subject: [PATCH 0084/1992] added way to denote which python packages we are building for fpm --- Makefile.am | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile.am b/Makefile.am index ea114acf..8ebb65d2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -51,7 +51,7 @@ fpm -s dir -t rpm -n core \ --description "Common Open Research Emulator" \ --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core_VERSION_ARCH.rpm \ + -p core_python$(FPM_PYTHON)_VERSION_ARCH.rpm \ -v $(PACKAGE_VERSION) \ --rpm-init scripts/core-daemon \ -d "tcl" \ @@ -63,7 +63,7 @@ fpm -s dir -t rpm -n core \ -d "iproute" \ -d "libev" \ -d "net-tools" \ - -d "python >= 2.7" \ + -d "python >= $(FPM_PYTHON)" \ -C $(DESTDIR) endef @@ -74,7 +74,7 @@ fpm -s dir -t deb -n core \ --description "Common Open Research Emulator" \ --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core_VERSION_ARCH.deb \ + -p core_python$(FPM_PYTHON)_VERSION_ARCH.deb \ -v $(PACKAGE_VERSION) \ --deb-systemd scripts/core-daemon.service \ -d "tcl" \ @@ -87,7 +87,7 @@ fpm -s dir -t deb -n core \ -d "ebtables" \ -d "iproute2" \ -d "libev4" \ - -d "python >= 2.7" \ + -d "python >= $(FPM_PYTHON)" \ -C $(DESTDIR) endef From fc654a31a870f2a915821f29dd30b71c6868e6a1 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 10 Jun 2019 11:41:27 -0700 Subject: [PATCH 0085/1992] update to installation docs, tweaks to handling python2/3 package builds --- Makefile.am | 15 ++- configure.ac | 4 +- daemon/Makefile.am | 14 ++- docs/index.md | 2 +- docs/install.md | 292 ++++++++++++++++----------------------------- netns/Makefile.am | 6 - ns3/Makefile.am | 12 +- 7 files changed, 138 insertions(+), 207 deletions(-) diff --git a/Makefile.am b/Makefile.am index 8ebb65d2..9c0f2bec 100644 --- a/Makefile.am +++ b/Makefile.am @@ -44,6 +44,13 @@ DISTCLEANFILES = aclocal.m4 \ MAINTAINERCLEANFILES = .version \ .version.date + +if PYTHON3 +PYTHON_DEP = python >= 3.0 +else +PYTHON_DEP = python >= 2.7, python < 3.0 +endif + define fpm-rpm = fpm -s dir -t rpm -n core \ -m "$(PACKAGE_MAINTAINERS)" \ @@ -51,7 +58,7 @@ fpm -s dir -t rpm -n core \ --description "Common Open Research Emulator" \ --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core_python$(FPM_PYTHON)_VERSION_ARCH.rpm \ + -p core_$(PYTHON)_VERSION_ARCH.rpm \ -v $(PACKAGE_VERSION) \ --rpm-init scripts/core-daemon \ -d "tcl" \ @@ -63,7 +70,7 @@ fpm -s dir -t rpm -n core \ -d "iproute" \ -d "libev" \ -d "net-tools" \ - -d "python >= $(FPM_PYTHON)" \ + -d "$(PYTHON_DEP)" \ -C $(DESTDIR) endef @@ -74,7 +81,7 @@ fpm -s dir -t deb -n core \ --description "Common Open Research Emulator" \ --url https://github.com/coreemu/core \ --vendor "$(PACKAGE_VENDOR)" \ - -p core_python$(FPM_PYTHON)_VERSION_ARCH.deb \ + -p core_$(PYTHON)_VERSION_ARCH.deb \ -v $(PACKAGE_VERSION) \ --deb-systemd scripts/core-daemon.service \ -d "tcl" \ @@ -87,7 +94,7 @@ fpm -s dir -t deb -n core \ -d "ebtables" \ -d "iproute2" \ -d "libev4" \ - -d "python >= $(FPM_PYTHON)" \ + -d "$(PYTHON_DEP)" \ -C $(DESTDIR) endef diff --git a/configure.ac b/configure.ac index f38ac290..0dde34ea 100644 --- a/configure.ac +++ b/configure.ac @@ -110,8 +110,7 @@ if test "x$enable_daemon" = "xyes"; then AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname]) AM_PATH_PYTHON(2.7) - pythondir=`echo ${pythondir} | sed s,site-packages,dist-packages,` - AC_SUBST(pythondir,$pythondir) + AM_CONDITIONAL([PYTHON3], [test "$PYTHON" == "python3"]) AC_CHECK_PROG(brctl_path, brctl, $as_dir, no, $SEARCHPATH) if test "x$brctl_path" = "xno" ; then @@ -266,6 +265,7 @@ GUI: Daemon: Daemon path: ${bindir} Daemon config: ${CORE_CONF_DIR} + Python: ${PYTHON} Python modules: ${pythondir} Logs: ${CORE_STATE_DIR}/log diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 769098bb..2f112b85 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -11,7 +11,13 @@ SETUPPY = setup.py SETUPPYFLAGS = -v if WANT_DOCS - DOCS = doc +DOCS = doc +endif + +if PYTHON3 +PYTHONLIBDIR=$(libdir)/python3/dist-packages +else +PYTHONLIBDIR=$(pythondir) endif SUBDIRS = proto $(DOCS) @@ -29,7 +35,7 @@ install-exec-hook: $(PYTHON) $(SETUPPY) $(SETUPPYFLAGS) install \ --root=/$(DESTDIR) \ --prefix=$(prefix) \ - --install-lib=$(pythondir) \ + --install-lib=$(PYTHONLIBDIR) \ --single-version-externally-managed # Python package uninstall @@ -38,8 +44,8 @@ uninstall-hook: rm -rf $(DESTDIR)/$(datadir)/core rm -f $(addprefix $(DESTDIR)/$(datarootdir)/man/man1/, $(MAN_FILES)) rm -f $(addprefix $(DESTDIR)/$(bindir)/,$(SCRIPT_FILES)) - rm -rf $(DESTDIR)/$(pythondir)/core-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg-info - rm -rf $(DESTDIR)/$(pythondir)/core + rm -rf $(DESTDIR)/$(PYTHONLIBDIR)/core-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg-info + rm -rf $(DESTDIR)/$(PYTHONLIBDIR)/core # Python package cleanup clean-local: diff --git a/docs/index.md b/docs/index.md index f516b648..c4dd2a24 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,7 +16,7 @@ CORE is typically used for network and protocol research, demonstrations, applic * [Architecture](architecture.md) * [Installation](install.md) -* [Usage](usage.md) +* [Using the GUI](usage.md) * [Python Scripting](scripting.md) * [Node Types](machine.md) * [CTRLNET](ctrlnet.md) diff --git a/docs/install.md b/docs/install.md index fb161f78..6dc82bc6 100644 --- a/docs/install.md +++ b/docs/install.md @@ -6,78 +6,105 @@ # Overview -This section will describe how to set up a CORE machine. Note that the easiest way to install CORE is using a binary package on Ubuntu or Fedora/CentOS (deb or rpm) using the distribution's package manager to automatically install dependencies. +This section will describe how to install CORE from source or from a pre-built package. -Ubuntu and Fedora/CentOS Linux are the recommended distributions for running CORE. However, these distributions are not strictly required. CORE will likely work on other flavors of Linux as well. +# Required Hardware -The primary dependencies are Tcl/Tk (8.5 or newer) for the GUI, and Python 2.7 for the CORE daemon. +Any computer capable of running Linux should be able to run CORE. Since the physical machine will be hosting numerous +virtual machines, as a general rule you should select a machine having as much RAM and CPU resources as possible. -CORE files are installed to the following directories, when the installation prefix is */usr*. +# Operating System + +CORE requires a Linux operating system because it uses virtualization provided by the kernel. It does not run on +Windows or Mac OS X operating systems (unless it is running within a virtual machine guest.) The virtualization +technology that CORE currently uses is Linux network namespaces. + +Ubuntu and Fedora/CentOS Linux are the recommended distributions for running CORE. However, these distributions are +not strictly required. CORE will likely work on other flavors of Linux as well, assuming dependencies are met. + +**NOTE: CORE Services determine what run on each node. You may require other software packages depending on the +services you wish to use. For example, the HTTP service will require the apache2 package.** + +# Installed Files + +CORE files are installed to the following directories, when the installation prefix is **/usr**. Install Path | Description -------------|------------ /usr/bin/core-gui|GUI startup command /usr/bin/core-daemon|Daemon startup command -/usr/bin/|Misc. helper commands/scripts +/usr/bin/{core-cleanup, coresendmsg, core-manage}|Misc. helper commands/scripts /usr/lib/core|GUI files -/usr/lib/python2.7/dist-packages/core|Python modules for daemon/scripts -/etc/core/|Daemon configuration files +/usr/lib/python{2.7,3.X}/dist-packages/core|Python modules for daemon/scripts +/etc/core/|Daemon and log configuration files ~/.core/|User-specific GUI preferences and scenario files /usr/share/core/|Example scripts and scenarios /usr/share/man/man1/|Command man pages /etc/init.d/core-daemon|SysV startup script for daemon /etc/systemd/system/core-daemon.service|Systemd startup script for daemon -## Prerequisites +# Pre-Req Installing OSPF MDR -A Linux operating system is required. The GUI uses the Tcl/Tk scripting toolkit, and the CORE daemon requires Python. Details of the individual software packages required can be found in the installation steps. +Virtual networks generally require some form of routing in order to work (e.g. to automatically populate routing +tables for routing packets from one subnet to another.) CORE builds OSPF routing protocol configurations by +default when the blue router node type is used. -## Required Hardware +* [OSPF MANET Designated Routers](http://www.nrl.navy.mil/itd/ncs/products/ospf-manet) (MDR) - the Quagga routing +suite with a modified version of OSPFv3, optimized for use with mobile wireless networks. The **mdr** node type +(and the MDR service) requires this variant of Quagga. -Any computer capable of running Linux should be able to run CORE. Since the physical machine will be hosting numerous virtual machines, as a general rule you should select a machine having as much RAM and CPU resources as possible. +## Ubuntu <= 16.04 and Fedora/CentOS -## Required Software - -CORE requires a Linux operating system because it uses virtualization provided by the kernel. It does not run on Windows or Mac OS X operating systems (unless it is running within a virtual machine guest.) The virtualization technology that CORE currently uses is Linux network namespaces. - -The CORE GUI requires the X.Org X Window system (X11), or can run over a remote X11 session. For specific Tcl/Tk, Python, and other libraries required to run CORE. - -**NOTE: CORE *Services* determine what run on each node. You may require other software packages depending on the services you wish to use. For example, the *HTTP* service will require the *apache2* package.** - -## Installing from Packages - -The easiest way to install CORE is using the pre-built packages. The package managers on Ubuntu or Fedora/CentOS will automatically install dependencies for you. You can obtain the CORE packages from [CORE GitHub](https://github.com/coreemu/core/releases). - -### Installing from Packages on Ubuntu - -Install Quagga for routing. If you plan on working with wireless networks, we recommend installing [OSPF MDR](http://www.nrl.navy.mil/itd/ncs/products/ospf-manet) (replace *amd64* below with *i386* if needed to match your architecture): +There is a built package which can be used. ```shell wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/quagga-0.99.21mr2.2/quagga-mr_0.99.21mr2.2_amd64.deb sudo dpkg -i quagga-mr_0.99.21mr2.2_amd64.deb ``` -Or, for the regular Ubuntu version of Quagga: +## Ubuntu >= 18.04 + +Requires building from source, from the latest nightly snapshot. ```shell -sudo apt-get install quagga +wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/nightly_snapshots/quagga-svnsnap.tgz +tar xzf quagga-svnsnap.tgz +cd quagga +./configure --enable-user=root --enable-group=root --with-cflags=-ggdb \ + --sysconfdir=/usr/local/etc/quagga --enable-vtysh \ + --localstatedir=/var/run/quagga +make +sudo make install ``` -Install the CORE deb packages for Ubuntu from command line. +Note that the configuration directory */usr/local/etc/quagga* shown for Quagga above could be */etc/quagga*, +if you create a symbolic link from */etc/quagga/Quagga.conf -> /usr/local/etc/quagga/Quagga.conf* on the host. +The *quaggaboot.sh* script in a Linux network namespace will try and do this for you if needed. + +If you try to run quagga after installing from source and get an error such as: ```shell -sudo dpkg -i python-core_*.deb -sudo dpkg -i core-gui_*.deb +error while loading shared libraries libzebra.so.0 ``` -Start the CORE daemon as root, the systemd installation will auto start the daemon, but you can use the commands below if need be. +this is usually a sign that you have to run ```sudo ldconfig```` to refresh the cache file. + +# Installing from Packages + +The easiest way to install CORE is using the pre-built packages. The package managers on Ubuntu or Fedora/CentOS +will help in automatically installing most dependencies for you. + +You can obtain the CORE packages from [CORE Releases](https://github.com/coreemu/core/releases). + +## Ubuntu + +Ubuntu package defaults to using systemd for running as a service. ```shell -# systemd -sudo systemctl start core-daemon - -# sysv -sudo service core-daemon start +# python2 +sudo apt ./core_python_$VERSION_amd64.deb +# python3 +sudo apt ./core_python3_$VERSION_amd64.deb ``` Run the CORE GUI as a normal user: @@ -86,47 +113,24 @@ Run the CORE GUI as a normal user: core-gui ``` -After running the *core-gui* command, a GUI should appear with a canvas for drawing topologies. Messages will print out on the console about connecting to the CORE daemon. +After running the *core-gui* command, a GUI should appear with a canvas for drawing topologies. +Messages will print out on the console about connecting to the CORE daemon. -### Installing from Packages on Fedora/CentOS +## Fedora/CentOS -The commands shown here should be run as root. The *x86_64* architecture is shown in the examples below, replace with *i686* is using a 32-bit architecture. - -**CentOS 7 Only: in order to install *tkimg* package you must build from source.** - -Make sure the system is up to date. +**NOTE: tkimg is not required for the core-gui, but if you get an error message about it you can install the package +on CentOS <= 6, or build from source otherwise** ```shell -yum update +# python2 +yum install ./core_python_$VERSION_x86_64.rpm +# python3 +yum install ./core_python3_$VERSION_x86_64.rpm ``` -**Optional (Fedora 17+): Fedora 17 and newer have an additional prerequisite providing the required netem kernel modules (otherwise skip this step and have the package manager install it for you.)** - -```shell -yum install kernel-modules-extra -``` - -Install Quagga for routing. If you plan on working with wireless networks, we recommend installing [OSPF MDR](http://www.nrl.navy.mil/itd/ncs/products/ospf-manet): - -```shell -wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/quagga-0.99.21mr2.2/quagga-0.99.21mr2.2-1.el6.x86_64.rpm -sudo yum install quagga-0.99.21mr2.2-1.el6.x86_64.rpm -``` - -Or, for the regular Fedora/CentOS version of Quagga: - -```shell -yum install quagga -``` - -Install the CORE RPM packages and automatically resolve dependencies: - -```shell -yum install python-core_*.rpm -yum install core-gui_*.rpm -``` - -Turn off SELINUX by setting *SELINUX=disabled* in the */etc/sysconfig/selinux* file, and adding *selinux=0* to the kernel line in your */etc/grub.conf* file; on Fedora 15 and newer, disable sandboxd using ```chkconfig sandbox off```; you need to reboot in order for this change to take effect +Turn off SELINUX by setting *SELINUX=disabled* in the */etc/sysconfig/selinux* file, and adding *selinux=0* to the +kernel line in your */etc/grub.conf* file; on Fedora 15 and newer, disable sandboxd using ```chkconfig sandbox off```; +you need to reboot in order for this change to take effect Turn off firewalls: @@ -145,7 +149,7 @@ iptables -F ip6tables -F ``` -Start the CORE daemon as root. +Start the CORE daemon. ```shell # systemd @@ -164,151 +168,65 @@ core-gui After running the *core-gui* command, a GUI should appear with a canvas for drawing topologies. Messages will print out on the console about connecting to the CORE daemon. -### Installing from Source +# Building and Installing from Source -This option is listed here for developers and advanced users who are comfortable patching and building source code. Please consider using the binary packages instead for a simplified install experience. +This option is listed here for developers and advanced users who are comfortable patching and building source code. +Please consider using the binary packages instead for a simplified install experience. -To build CORE from source on Ubuntu, first install these development packages. These packages are not required for normal binary package installs. - -#### Ubuntu 18.04 pre-reqs +## Pre-Reqs Ubuntu 18.04 ```shell -sudo apt install automake pkg-config gcc libev-dev bridge-utils ebtables python-dev python-sphinx python-setuptools python-lxml python-enum34 tk libtk-img +sudo apt install automake pkg-config gcc libev-dev bridge-utils ebtables python-dev python-setuptools tk libtk-img ``` -#### Ubuntu 16.04 Requirements +## Pre-Reqs Ubuntu 16.04 ```shell -sudo apt-get install automake bridge-utils ebtables python-dev libev-dev python-sphinx python-setuptools python-enum34 python-lxml libtk-img +sudo apt-get install automake bridge-utils ebtables python-dev libev-dev python-setuptools libtk-img ``` - -#### CentOS 7 with Gnome Desktop Requirements +### CentOS 7 with Gnome Desktop Requirements ```shell -sudo yum -y install automake gcc python-devel libev-devel python-sphinx tk python-lxml python-enum34 +sudo yum -y install automake gcc python-devel libev-devel tk ``` -You can obtain the CORE source from the [CORE GitHub](https://github.com/coreemu/core) page. Choose either a stable release version or the development snapshot available in the *nightly_snapshots* directory. +## Build and Install -```shell -tar xzf core-*.tar.gz -cd core-* -``` - -#### Tradional Autotools Build ```shell ./bootstrap.sh -./configure +# point at desired python version binary for use +PYTHON=python ./configure make sudo make install ``` -#### Build Documentation +## Build Documentation + +Building documentation requires python-sphinx not noted above. + ```shell +# install python sphinx +sudo apt install python(3)-sphinx +sudo yum install python(3)-sphinx + ./bootstrap.sh ./configure make doc ``` -#### Build Packages -Install fpm: http://fpm.readthedocs.io/en/latest/installing.html +### Build Packages Build package commands, DESTDIR is used for gui packaging only +* Install [fpm](http://fpm.readthedocs.io/en/latest/installing.html) + ```shell ./bootstrap.sh -./configure +# point at desired python version binary for use +PYTHON=python ./configure make -mkdir /tmp/core-gui -make fpm DESTDIR=/tmp/core-gui - -``` -This will produce: - -* CORE GUI rpm/deb files - * core-gui_$VERSION_$ARCH -* CORE ns3 rpm/deb files - * python-core-ns3_$VERSION_$ARCH -* CORE python rpm/deb files for SysV and systemd service types - * python-core-sysv_$VERSION_$ARCH - * python-core-systemd_$VERSION_$ARCH - - -### Quagga Routing Software - -Virtual networks generally require some form of routing in order to work (e.g. to automatically populate routing tables for routing packets from one subnet to another.) CORE builds OSPF routing protocol configurations by default when the blue router node type is used. The OSPF protocol is available from the [Quagga open source routing suit](http://www.quagga.net). - -Quagga is not specified as a dependency for the CORE packages because there are two different Quagga packages that you may use: - -* [Quagga](http://www.quagga.net) - the standard version of Quagga, suitable for static wired networks, and usually available via your distribution's package manager. - -* [OSPF MANET Designated Routers](http://www.nrl.navy.mil/itd/ncs/products/ospf-manet) (MDR) - the Quagga routing suite with a modified version of OSPFv3, optimized for use with mobile wireless networks. The *mdr* node type (and the MDR service) requires this variant of Quagga. - -If you plan on working with wireless networks, we recommend installing OSPF MDR; otherwise install the standard version of Quagga using your package manager or from source. - -#### Installing Quagga from Packages - -To install the standard version of Quagga from packages, use your package manager (Linux). - -Ubuntu users: - -```shell -sudo apt-get install quagga +mkdir /tmp/core-build +make fpm DESTDIR=/tmp/core-build ``` -Fedora/CentOS users: - -```shell -sudo yum install quagga -``` - -To install the Quagga variant having OSPFv3 MDR, first download the appropriate package, and install using the package manager. - -Ubuntu users: -```shell -wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/quagga-0.99.21mr2.2/quagga-mr_0.99.21mr2.2_amd64.deb -sudo dpkg -i quagga-mr_0.99.21mr2.2_amd64.deb -``` - -Replace *amd64* with *i686* if using a 32-bit architecture. - -Fedora/CentOS users: - -```shell -wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/quagga-0.99.21mr2.2/quagga-0.99.21mr2.2-1.el6.x86_64.rpm -sudo yum install quagga-0.99.21mr2.2-1.el6.x86_64.rpm -```` - -Replace *x86_64* with *i686* if using a 32-bit architecture. - -#### Compiling Quagga for CORE - -To compile Quagga to work with CORE on Linux: - -```shell -wget https://downloads.pf.itd.nrl.navy.mil/ospf-manet/quagga-0.99.21mr2.2/quagga-0.99.21mr2.2.tar.gz -tar xzf quagga-0.99.21mr2.2.tar.gz -cd quagga-0.99 -./configure --enable-user=root --enable-group=root --with-cflags=-ggdb \\ - --sysconfdir=/usr/local/etc/quagga --enable-vtysh \\ - --localstatedir=/var/run/quagga -make -sudo make install -``` - -Note that the configuration directory */usr/local/etc/quagga* shown for Quagga above could be */etc/quagga*, if you create a symbolic link from */etc/quagga/Quagga.conf -> /usr/local/etc/quagga/Quagga.conf* on the host. The *quaggaboot.sh* script in a Linux network namespace will try and do this for you if needed. - -If you try to run quagga after installing from source and get an error such as: - -```shell -error while loading shared libraries libzebra.so.0 -``` - -this is usually a sign that you have to run ```sudo ldconfig```` to refresh the cache file. - -### VCORE - -CORE is capable of running inside of a virtual machine, using software such as VirtualBox, VMware Server or QEMU. However, CORE itself is performing machine virtualization in order to realize multiple emulated nodes, and running CORE virtually adds additional contention for the physical resources. **For performance reasons, this is not recommended.** Timing inside of a VM often has problems. If you do run CORE from within a VM, it is recommended that you view the GUI with remote X11 over SSH, so the virtual machine does not need to emulate the video card with the X11 application. - -A CORE virtual machine is provided for download, named VCORE. This is the perhaps the easiest way to get CORE up and running as the machine is already set up for you. This may be adequate for initially evaluating the tool but keep in mind the performance limitations of running within VirtualBox or VMware. To install the virtual machine, you first need to obtain VirtualBox from http://www.virtualbox.org, or VMware Server or Player from http://www.vmware.com (this commercial software is distributed for free.) Once virtualization software has been installed, you can import the virtual machine appliance using the *vbox* file for VirtualBox or the *vmx* file for VMware. See the documentation that comes with VCORE for login information. - +This will produce and RPM and Deb package for the currently configured python version. diff --git a/netns/Makefile.am b/netns/Makefile.am index 91aaa605..cf0e1220 100644 --- a/netns/Makefile.am +++ b/netns/Makefile.am @@ -8,8 +8,6 @@ # AM_CFLAGS = -Wall -fno-strict-aliasing -O3 -g @libev_CFLAGS@ -SETUPPY = setup.py -SETUPPYFLAGS = -v SRC_COMMON = vnode_msg.c vnode_cmd.c vnode_chnl.c vnode_io.c \ vnode_msg.h vnode_cmd.h vnode_chnl.h vnode_io.h \ @@ -29,7 +27,3 @@ netns_SOURCES = $(SRC_NETNS) # extra cruft to remove DISTCLEANFILES = Makefile.in MANIFEST - -# include source files for Python libraries with distribution tarball -EXTRA_DIST = setup.py MANIFEST.in - diff --git a/ns3/Makefile.am b/ns3/Makefile.am index 62086e82..c1858665 100644 --- a/ns3/Makefile.am +++ b/ns3/Makefile.am @@ -9,6 +9,12 @@ if WANT_PYTHON +if PYTHON3 +PYTHONLIBDIR=$(libdir)/python3/dist-packages +else +PYTHONLIBDIR=$(pythondir) +endif + SETUPPY = setup.py SETUPPYFLAGS = -v @@ -22,15 +28,15 @@ install-exec-hook: $(PYTHON) $(SETUPPY) $(SETUPPYFLAGS) install \ --root=/$(DESTDIR) \ --prefix=$(prefix) \ - --install-lib=$(pythondir) \ + --install-lib=$(PYTHONLIBDIR) \ --single-version-externally-managed \ --no-compile # Python package uninstall uninstall-hook: -rm -rf core_ns3.egg-info - -rm -rf $(DESTDIR)/$(pythondir)/core_ns3-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg-info - -rm -rf $(DESTDIR)/$(pythondir)/corens3 + -rm -rf $(DESTDIR)/$(PYTHONLIBDIR)/core_ns3-$(PACKAGE_VERSION)-py$(PYTHON_VERSION).egg-info + -rm -rf $(DESTDIR)/$(PYTHONLIBDIR)/corens3 -rm -rf $(DESTDIR)/$(datadir)/corens3 # Python package cleanup From 43627805ee5ad02f2d3c68971190d6fd6ba35a79 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 10 Jun 2019 12:26:44 -0700 Subject: [PATCH 0086/1992] small tweak to building python2/3 and install.md documentation --- configure.ac | 3 +-- docs/install.md | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/configure.ac b/configure.ac index 0dde34ea..4d6b2ec0 100644 --- a/configure.ac +++ b/configure.ac @@ -110,7 +110,7 @@ if test "x$enable_daemon" = "xyes"; then AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname]) AM_PATH_PYTHON(2.7) - AM_CONDITIONAL([PYTHON3], [test "$PYTHON" == "python3"]) + AM_CONDITIONAL([PYTHON3], [test "x$PYTHON" == "xpython3"]) AC_CHECK_PROG(brctl_path, brctl, $as_dir, no, $SEARCHPATH) if test "x$brctl_path" = "xno" ; then @@ -266,7 +266,6 @@ Daemon: Daemon path: ${bindir} Daemon config: ${CORE_CONF_DIR} Python: ${PYTHON} - Python modules: ${pythondir} Logs: ${CORE_STATE_DIR}/log Startup: ${with_startup} diff --git a/docs/install.md b/docs/install.md index 6dc82bc6..f5673995 100644 --- a/docs/install.md +++ b/docs/install.md @@ -1,4 +1,3 @@ - # CORE Installation * Table of Contents @@ -35,7 +34,7 @@ Install Path | Description /usr/bin/core-daemon|Daemon startup command /usr/bin/{core-cleanup, coresendmsg, core-manage}|Misc. helper commands/scripts /usr/lib/core|GUI files -/usr/lib/python{2.7,3.X}/dist-packages/core|Python modules for daemon/scripts +/usr/lib/python{2.7,3}/dist-packages/core|Python modules for daemon/scripts /etc/core/|Daemon and log configuration files ~/.core/|User-specific GUI preferences and scenario files /usr/share/core/|Example scripts and scenarios @@ -195,8 +194,10 @@ sudo yum -y install automake gcc python-devel libev-devel tk ```shell ./bootstrap.sh -# point at desired python version binary for use -PYTHON=python ./configure +# for python2 +PYTHON=python2 ./configure +# for python3 +PYTHON=python3 ./configure make sudo make install ``` @@ -206,12 +207,18 @@ sudo make install Building documentation requires python-sphinx not noted above. ```shell -# install python sphinx -sudo apt install python(3)-sphinx -sudo yum install python(3)-sphinx +# install python2 sphinx +sudo apt install python-sphinx +sudo yum install python-sphinx +# install python3 sphinx +sudo apt install python3-sphinx +sudo yum install python3-sphinx ./bootstrap.sh -./configure +# for python2 +PYTHON=python2 ./configure +# for python3 +PYTHON=python3 ./configure make doc ``` @@ -222,8 +229,10 @@ Build package commands, DESTDIR is used for gui packaging only ```shell ./bootstrap.sh -# point at desired python version binary for use -PYTHON=python ./configure +# for python2 +PYTHON=python2 ./configure +# for python3 +PYTHON=python3 ./configure make mkdir /tmp/core-build make fpm DESTDIR=/tmp/core-build From 0692ab6f68a7c1e2346314995e8353310793d71e Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 10 Jun 2019 13:15:34 -0700 Subject: [PATCH 0087/1992] added check for grpcio-tools in autoconf, updated install docs to denote python dependencies and grpcio-tools for building from source --- configure.ac | 1 + docs/install.md | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 4d6b2ec0..d8a01a2c 100644 --- a/configure.ac +++ b/configure.ac @@ -111,6 +111,7 @@ if test "x$enable_daemon" = "xyes"; then AM_PATH_PYTHON(2.7) AM_CONDITIONAL([PYTHON3], [test "x$PYTHON" == "xpython3"]) + AS_IF([$PYTHON -m grpc_tools.protoc -h &> /dev/null], [], [AC_MSG_ERROR([please install python grpcio-tools])]) AC_CHECK_PROG(brctl_path, brctl, $as_dir, no, $SEARCHPATH) if test "x$brctl_path" = "xno" ; then diff --git a/docs/install.md b/docs/install.md index f5673995..442d9146 100644 --- a/docs/install.md +++ b/docs/install.md @@ -42,6 +42,26 @@ Install Path | Description /etc/init.d/core-daemon|SysV startup script for daemon /etc/systemd/system/core-daemon.service|Systemd startup script for daemon +# Pre-Req Python Requirements + +The newly added gRPC API which depends on python library grpcio is not commonly found within system repos. +To account for this it would be recommended to install the python dependencies using the **requirements.txt** found in +the latest release. + +```shell +sudo pip install -r requirements.txt +``` + +## Ubuntu 19.04 + +Ubuntu 19.04 can provide all the packages needed at the system level and can be installed as follows: +```shell +# python 2 +sudo apt install python-configparser python-enum34 python-future python-grpcio python-lxml +# python 3 +sudo apt install python3-configparser python3-enum34 python3-future python3-grpcio python3-lxml +``` + # Pre-Req Installing OSPF MDR Virtual networks generally require some form of routing in order to work (e.g. to automatically populate routing @@ -172,6 +192,17 @@ After running the *core-gui* command, a GUI should appear with a canvas for draw This option is listed here for developers and advanced users who are comfortable patching and building source code. Please consider using the binary packages instead for a simplified install experience. +## Pre-Req All + +Python module grpcio-tools is currently needed to generate code from the CORE protobuf file during the build. + +```shell +# python2 +pip2 install grpcio-tools +# python3 +pip3 install grpcio-tools +``` + ## Pre-Reqs Ubuntu 18.04 ```shell @@ -184,7 +215,7 @@ sudo apt install automake pkg-config gcc libev-dev bridge-utils ebtables python- sudo apt-get install automake bridge-utils ebtables python-dev libev-dev python-setuptools libtk-img ``` -### CentOS 7 with Gnome Desktop Requirements +## Pre-Reqs CentOS 7 ```shell sudo yum -y install automake gcc python-devel libev-devel tk @@ -222,7 +253,7 @@ PYTHON=python3 ./configure make doc ``` -### Build Packages +## Build Packages Build package commands, DESTDIR is used for gui packaging only * Install [fpm](http://fpm.readthedocs.io/en/latest/installing.html) From c7ce0e47f46c43c4ffc960a3f2d1ac2cd70e398e Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Mon, 10 Jun 2019 13:36:40 -0700 Subject: [PATCH 0088/1992] corefx updates to account for latest protobuf file --- .../com/core/client/grpc/CoreGrpcClient.java | 38 +++++++++---------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 4c1484d4..972c30be 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -6,7 +6,6 @@ import com.core.client.rest.ServiceFile; import com.core.client.rest.WlanConfig; import com.core.data.*; import com.core.ui.dialogs.MobilityPlayerDialog; -import com.google.protobuf.ByteString; import io.grpc.Context; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -15,7 +14,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; @@ -38,8 +36,8 @@ public class CoreGrpcClient implements ICoreClient { private CoreProto.Node nodeToProto(CoreNode node) { CoreProto.Position position = CoreProto.Position.newBuilder() - .setX(node.getPosition().getX().floatValue()) - .setY(node.getPosition().getY().floatValue()) + .setX(node.getPosition().getX().intValue()) + .setY(node.getPosition().getY().intValue()) .build(); CoreProto.Node.Builder builder = CoreProto.Node.newBuilder() .addAllServices(node.getServices()) @@ -90,31 +88,31 @@ public class CoreGrpcClient implements ICoreClient { unidirectional = true; } if (options.getBandwidth() != null) { - builder.setBandwidth(options.getBandwidth().floatValue()); + builder.setBandwidth(options.getBandwidth().intValue()); } if (options.getBurst() != null) { - builder.setBurst(options.getBurst().floatValue()); + builder.setBurst(options.getBurst().intValue()); } if (options.getDelay() != null) { - builder.setDelay(options.getDelay().floatValue()); + builder.setDelay(options.getDelay().intValue()); } if (options.getDup() != null) { - builder.setDup(options.getDup().floatValue()); + builder.setDup(options.getDup().intValue()); } if (options.getJitter() != null) { - builder.setJitter(options.getJitter().floatValue()); + builder.setJitter(options.getJitter().intValue()); } if (options.getMburst() != null) { - builder.setMburst(options.getMburst().floatValue()); + builder.setMburst(options.getMburst().intValue()); } if (options.getMer() != null) { - builder.setMer(options.getMer().floatValue()); + builder.setMer(options.getMer().intValue()); } if (options.getPer() != null) { - builder.setPer(options.getPer().floatValue()); + builder.setPer(options.getPer().intValue()); } if (options.getKey() != null) { - builder.setKey(options.getKey().toString()); + builder.setKey(options.getKey()); } if (options.getOpaque() != null) { builder.setOpaque(options.getOpaque()); @@ -196,8 +194,8 @@ public class CoreGrpcClient implements ICoreClient { options.setJitter((double) protoOptions.getJitter()); options.setPer((double) protoOptions.getPer()); options.setBurst((double) protoOptions.getBurst()); - if (!protoOptions.getKey().isEmpty()) { - options.setKey(Integer.parseInt(protoOptions.getKey())); + if (protoOptions.hasField(CoreProto.LinkOptions.getDescriptor().findFieldByName("key"))) { + options.setKey(protoOptions.getKey()); } options.setMburst((double) protoOptions.getMburst()); options.setMer((double) protoOptions.getMer()); @@ -784,8 +782,8 @@ public class CoreGrpcClient implements ICoreClient { @Override public boolean editNode(CoreNode node) throws IOException { CoreProto.Position position = CoreProto.Position.newBuilder() - .setX(node.getPosition().getX().floatValue()) - .setY(node.getPosition().getY().floatValue()) + .setX(node.getPosition().getX().intValue()) + .setY(node.getPosition().getY().intValue()) .build(); CoreProto.EditNodeRequest request = CoreProto.EditNodeRequest.newBuilder() .setSessionId(sessionId) @@ -1104,13 +1102,13 @@ public class CoreGrpcClient implements ICoreClient { } CoreProto.Position.Builder positionBuilder = CoreProto.Position.newBuilder(); if (config.getPosition().getX() != null) { - positionBuilder.setX(config.getPosition().getX().floatValue()); + positionBuilder.setX(config.getPosition().getX().intValue()); } if (config.getPosition().getY() != null) { - positionBuilder.setY(config.getPosition().getY().floatValue()); + positionBuilder.setY(config.getPosition().getY().intValue()); } if (config.getPosition().getZ() != null) { - positionBuilder.setZ(config.getPosition().getZ().floatValue()); + positionBuilder.setZ(config.getPosition().getZ().intValue()); } if (config.getLocation().getLongitude() != null) { positionBuilder.setLon(config.getLocation().getLongitude().floatValue()); From df55f90f245c19640dfbab3593019b23d0feb471 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 10 Jun 2019 14:59:42 -0700 Subject: [PATCH 0089/1992] cleaned up selinux section and better clarity for using fpm in install.md --- docs/install.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/install.md b/docs/install.md index 442d9146..08cd797d 100644 --- a/docs/install.md +++ b/docs/install.md @@ -147,9 +147,19 @@ yum install ./core_python_$VERSION_x86_64.rpm yum install ./core_python3_$VERSION_x86_64.rpm ``` -Turn off SELINUX by setting *SELINUX=disabled* in the */etc/sysconfig/selinux* file, and adding *selinux=0* to the -kernel line in your */etc/grub.conf* file; on Fedora 15 and newer, disable sandboxd using ```chkconfig sandbox off```; -you need to reboot in order for this change to take effect +Disabling SELINUX: + +```shell +# change the following in /etc/sysconfig/selinux +SELINUX=disabled + +# add the following to the kernel line in /etc/grub.conf +selinux=0 + +# Fedora 15 and newer, disable sandboxd +# reboot in order for this change to take effect +chkconfig sandbox off +``` Turn off firewalls: @@ -254,7 +264,9 @@ make doc ``` ## Build Packages -Build package commands, DESTDIR is used for gui packaging only +Build package commands, DESTDIR is used to make install into and then for packaging by fpm. + +**NOTE: clean the DESTDIR if re-using the same directory** * Install [fpm](http://fpm.readthedocs.io/en/latest/installing.html) From 1018143cfe65a1fc197163d90ca164895d684f70 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 10 Jun 2019 15:37:32 -0700 Subject: [PATCH 0090/1992] updated CHANGELOG and converted it to markdown format --- Changelog => CHANGELOG.md | 187 ++++++++++++++++++++++---------------- Makefile.am | 2 +- 2 files changed, 109 insertions(+), 80 deletions(-) rename Changelog => CHANGELOG.md (74%) diff --git a/Changelog b/CHANGELOG.md similarity index 74% rename from Changelog rename to CHANGELOG.md index 49d2d0d0..b8daae53 100644 --- a/Changelog +++ b/CHANGELOG.md @@ -1,53 +1,89 @@ -2019-03-25 CORE 5.2.1 - * Packaging: - - documentation no longer builds by default, must use configure flag - - added configure flag to allow only building vcmd - - sphinx will no long be required when not building documentation - * Services: - - Added source NAT service - - Fixed DHCP service for Ubuntu 18.04 - * BUGFIXES: - - #188 - properly remove session on delete TLV API call - - #192 - updated default gnome terminal command for nodes to be Ubuntu 18.04 compatible - - #193 - updates to service validation, will retry on failure and better exception logging - - #195 - TLV link message data fix - - #196 - fix to avoid clearing out default services - - #197 - removed wireless_link_all API from EmuSession - - #216 - updated default WLAN bandwidth to 54Mbps - - #223 - fix to saving RJ45 to session XML files +## 2019-06-10 CORE 5.3.0 + * Enhancements + - python 2 / 3 support + - added new API using [gRPC](https://grpc.io/) + - --grpc --grpc-port --grpc-address flags added to core-daemon + - core.api.grpc.client.CoreGrpcClient, provides a convenience wrapper for leveraging the API + * Docs + - Updates to installation instructions for latest changes + * Services + - Added FRR service + * EMANE + - Added EMANE prefix configuration when looking for emane model manifest files + - requires configuring **emane_prefix** in /etc/core/core.conf + * Cleanup + - Refactoring of the core python package structure, trying to help provide better organization and + logical groupings + * Issues + - \#246 - Fixed network to network link handling when reading xml files + - \#236 - Fixed storing/reading of link configuration values within xml files + - \#170 - FRR Service + - \#155 - EMANE path configuration + - \#233 - Python 3 support + - \#245 - Fixed bidirectional link configurations when reading from xml files + - \#208 - gRPC API + - Fixed link configuration dup handling when loaded from xml files -2018-05-22 CORE 5.1 - * DAEMON: - - removed and cleared out code that is either legacy or no longer supported (Xen, BSD, Kernel patching, RPM/DEB specific files) - - default nodes are now set in the node map - - moved ns3 and netns directories to the top of the repo - - changes to make use of fpm as the tool for building packages - - removed usage of logzero to avoid dependency issues for built packages - - removed daemon addons directory - - added CoreEmu to core.emulator.coreemu to help begin serving as the basis for a more formal API for scripting and creating new external APIs out of - - cleaned up logging, moved more logging to DEBUG from INFO, tried to mold INFO message to be more simple and informative - - EMANE 1.0.1-1.21 supported - - updates to leverage EMANE python bindings for dynamically parsing phy/mac manifest files - - example custom EMANE model lives under /usr/share/core/examples/myemane/examplemodel.py - - EMANE TDMA model now supports an option to start a TDMA schedule when running - - fixed issues with coresendmsg script due to code refactoring - - added make target for generating documentation "make doc" - - Python 2.7+ is now required - - ns3 is no longer bundled by default, but will be produced as a separate package for installation - * GUI: - - updated broken help links in GUI Help->About +## 2019-06-07 CORE 5.2.2 + * Enhancements: + - adds back in core-daemon udp support for coresendmsg, people may have depended on previously for certain scenarios + * Bug Fixes: + - fixes issue in GUI that would prevent moving nodes during mobility scenarios + +## 2019-03-25 CORE 5.2.1 * Packaging: - - fixed PYTHON_PATH to PYTHONPATH in sysv script - - added make command to leverage FPM as the tool for creating deb/rpm packages going forward, there is documentation within README.md to try it out - * TEST: - - fixed some broken tests - - new test cases based on CoreEmu usage + - documentation no longer builds by default, must use configure flag + - added configure flag to allow only building vcmd + - sphinx will no long be required when not building documentation + * Services: + - Added source NAT service + - Fixed DHCP service for Ubuntu 18.04 * BUGFIXES: - - #142 - duplication of custom services - - #136 - sphinx-apidoc command not found - - #137 - make command fails when using distclean + - \#188 - properly remove session on delete TLV API call + - \#192 - updated default gnome terminal command for nodes to be Ubuntu 18.04 compatible + - \#193 - updates to service validation, will retry on failure and better exception logging + - \#195 - TLV link message data fix + - \#196 - fix to avoid clearing out default services + - \#197 - removed wireless_link_all API from EmuSession + - \#216 - updated default WLAN bandwidth to 54Mbps + - \#223 - fix to saving RJ45 to session XML files + +## 2018-05-22 CORE 5.1 + * DAEMON: + - removed and cleared out code that is either legacy or no longer supported (Xen, BSD, Kernel patching, RPM/DEB + specific files) + - default nodes are now set in the node map + - moved ns3 and netns directories to the top of the repo + - changes to make use of fpm as the tool for building packages + - removed usage of logzero to avoid dependency issues for built packages + - removed daemon addons directory + - added CoreEmu to core.emulator.coreemu to help begin serving as the basis for a more formal API for scripting + and creating new external APIs out of + - cleaned up logging, moved more logging to DEBUG from INFO, tried to mold INFO message to be more simple and + informative + - EMANE 1.0.1-1.21 supported + - updates to leverage EMANE python bindings for dynamically parsing phy/mac manifest files + - example custom EMANE model lives under /usr/share/core/examples/myemane/examplemodel.py + - EMANE TDMA model now supports an option to start a TDMA schedule when running + - fixed issues with coresendmsg script due to code refactoring + - added make target for generating documentation "make doc" + - Python 2.7+ is now required + - ns3 is no longer bundled by default, but will be produced as a separate package for installation + * GUI: + - updated broken help links in GUI Help->About + * Packaging: + - fixed PYTHON_PATH to PYTHONPATH in sysv script + - added make command to leverage FPM as the tool for creating deb/rpm packages going forward, there is documentation + within README.md to try it out + * TEST: + - fixed some broken tests + - new test cases based on CoreEmu usage + * BUGFIXES: + - \#142 - duplication of custom services + - \#136 - sphinx-apidoc command not found + - \#137 - make command fails when using distclean -2017-09-01 CORE 5.0 +## 2017-09-01 CORE 5.0 * DEVELOPMENT: - support for editorconfig to help standardize development across IDEs, from the defined configuration file - support for sonarqube analysis, from the defined configuration file @@ -65,7 +101,7 @@ * BUGFIXES: - merged pull requests for: #115, #110, #109, #107, #106, #105, #103, #102, #101, #96 -2015-06-05 CORE 4.8 +## 2015-06-05 CORE 4.8 * EMANE: - support for EMANE 0.9.2 - run emane in each container when using EMANE 0.9.2 @@ -101,8 +137,7 @@ - improved detecting when a distributed emulation is already running - improved documentation -2014-08-06 CORE 4.7 - +## 2014-08-06 CORE 4.7 * EMANE: - support for EMANE 0.9.1 - fix error when using Comm Effect model with loss/duplicate string values @@ -145,12 +180,9 @@ - fixed the following bugs: #150, 169, 188, 220, 225, 230, 231, 242, 244, 247, 248, 250, 251 -2013-09-25 CORE 4.6 - - * NOTE: cored is now core-daemon, and core is now core-gui (for Debian - acceptance) - * NOTE: /etc/init.d/core is now /etc/init.d/core-daemon (for insserv - compatibility) +## 2013-09-25 CORE 4.6 + * NOTE: cored is now core-daemon, and core is now core-gui (for Debian acceptance) + * NOTE: /etc/init.d/core is now /etc/init.d/core-daemon (for insserv compatibility) * EMANE: - don't start EMANE locally if no local NEMs - EMANE poststartup() to re-transmit location events during initialization @@ -163,7 +195,6 @@ - added EMANE model to CORE converter - parse lat/long/alt from node messages, for moving nodes using command-line - fix bug #196 incorrect distance when traversing UTM zones - * GUI: - added Cut, Copy, and Paste options to the Edit menu - paste will copy selected services and take care of node and interface @@ -173,7 +204,6 @@ - "~", "%SESSION%", "%SESSION_DIR%", "%SESSION_USER%", "%NODE%", "%NODENAME%" - use CORE_DATA_DIR insteadof LIBDIR - fix Adjacency Widget to work with OSPFv2 only networks - * BUILD: - build/packaging improvements for inclusion on Debian - fix error when running scenario with a mobility script in batch mode @@ -181,7 +211,6 @@ - renamed core-cleanup.sh to core-cleanup for Debian conformance - don't always generate man pages from Makefile; new manpages for coresendmsg and core-daemon - * BUGFIXES: - don't auto-assign IPv4/IPv6 addresses when none received in Link Messages (session reconnect) - fixed lock view @@ -195,8 +224,7 @@ - fix the following bugs: 166, 172, 177, 178, 192, 194, 196, 201, 202, 205, 206, 210, 212, 213, 214, 221 -2013-04-13 CORE 4.5 - +## 2013-04-13 CORE 4.5 * GUI: - improved behavior when starting GUI without daemon, or using File New after connection with daemon is lost - fix various GUI issues when reconnecting to a session @@ -209,7 +237,8 @@ - added "--addons" startup mode to pass control to code included from addons dir - added "Locked" entry to View menu to prevent moving items - use currently selected node type when invoking a topology generator - - updated throughput plots with resizing, color picker, plot labels, locked scales, and save/load plot configuration with imn file + - updated throughput plots with resizing, color picker, plot labels, locked scales, and save/load plot + configuration with imn file - improved session dialog * EMANE: - EMANE 0.8.1 support with backwards-compatibility for 0.7.4 @@ -225,10 +254,12 @@ - XML import and export - renamed "cored.py" to "cored", "coresendmsg.py" to "coresendmsg" - code reorganization and clean-up - - updated XML export to write NetworkPlan, MotionPlan, and ServicePlan within a Scenario tag, added new "Save As XML..." File menu entry + - updated XML export to write NetworkPlan, MotionPlan, and ServicePlan within a Scenario tag, added new + "Save As XML..." File menu entry - added script_start/pause/stop options to Ns2ScriptedMobility - "python" source sub-directory renamed to "daemon" - - added "cored -e" option to execute a Python script, adding its session to the active sessions list, allowing for GUI connection + - added "cored -e" option to execute a Python script, adding its session to the active sessions list, allowing for + GUI connection - support comma-separated list for custom_services_dir in core.conf file - updated kernel patches for Linux kernel 3.5 - support RFC 6164-style IPv6 /127 addressing @@ -236,10 +267,10 @@ - integrate ns-3 node location between CORE and ns-3 simulation - added ns-3 random walk mobility example - updated ns-3 Wifi example to allow GUI connection and moving of nodes - * fixed the following bugs: 54, 103, 111, 136, 145, 153, 157, 160, 161, 162, 164, 165, 168, 170, 171, 173, 174, 176, 184, 190, 193 - -2012-09-25 CORE 4.4 + * fixed the following bugs: 54, 103, 111, 136, 145, 153, 157, 160, 161, 162, 164, 165, 168, 170, 171, 173, 174, 176, + 184, 190, 193 +## 2012-09-25 CORE 4.4 * GUI: - real-time bandwidth plotting tool - added Wireshark and tshark right-click menu items @@ -277,11 +308,10 @@ - support /etc/core/environment and ~/.core/environment files - added Ns2ScriptedMobility model to Python, removed from the GUI - namespace nodes mount a private /sys + - fixed the following bugs: 80, 81, 84, 99, 104, 109, 110, 122, 124, 131, 133, 134, 135, 137, 140, 143, 144, 146, + 147, 151, 154, 155 - - fixed the following bugs: 80, 81, 84, 99, 104, 109, 110, 122, 124, 131, 133, 134, 135, 137, 140, 143, 144, 146, 147, 151, 154, 155 - -2012-03-07 CORE 4.3 - +## 2012-03-07 CORE 4.3 * EMANE 0.7.2 and 0.7.3 support * hook scripts: customize actions at any of six different session states * Check Emulation Light (CEL) exception feedback system @@ -298,8 +328,7 @@ * added PhysicalNode class for joining real nodes with emulated networks * fixed the following bugs: 50, 75, 76, 79, 82, 83, 85, 86, 89, 90, 92, 94, 96, 98, 100, 112, 113, 116, 119, 120 -2011-08-19 CORE 4.2 - +## 2011-08-19 CORE 4.2 * EMANE 0.7.1 support - support for Bypass model, Universal PHY, logging, realtime * configurable MAC addresses @@ -311,9 +340,10 @@ * new security services, custom service becomes UserDefined * new services and Python scripting chapters in manual * fixes to distributed emulation, linking tunnels/RJ45s with WLANs/hubs/switches - * fixed the following bugs: 18, 32, 34, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 58, 60, 62, 64, 65, 66, 68, 71, 72, 74 + * fixed the following bugs: 18, 32, 34, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 58, 60, 62, 64, + 65, 66, 68, 71, 72, 74 -2011-01-05 CORE 4.1 +## 2011-01-05 CORE 4.1 * new icons for toolbars and nodes * node services introduced, node models deprecated * customizable node types @@ -325,7 +355,7 @@ * EMANE 0.6.4 support * numerous bugfixes -2010-08-17 CORE 4.0 +## 2010-08-17 CORE 4.0 * Python framework with Linux network namespace (netns) support (Linux netns is now the primary supported platform) * ability to close the GUI and later reconnect to a running session (netns only) * EMANE integration (netns only) @@ -334,9 +364,8 @@ * use of /etc/core instead of /usr/local/etc/core * various bugfixes -2009-09-15 CORE 3.5 +## 2009-09-15 CORE 3.5 -2009-06-23 CORE 3.4 - -2009-03-11 CORE 3.3 +## 2009-06-23 CORE 3.4 +## 2009-03-11 CORE 3.3 diff --git a/Makefile.am b/Makefile.am index 9c0f2bec..3a4f7dca 100644 --- a/Makefile.am +++ b/Makefile.am @@ -28,7 +28,7 @@ EXTRA_DIST = bootstrap.sh \ LICENSE \ README.md \ ASSIGNMENT_OF_COPYRIGHT.pdf \ - Changelog \ + CHANGELOG.md \ .version \ .version.date From d37db217b50114bdf2efe8bc4c0a996cd2b51957 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Mon, 10 Jun 2019 15:59:00 -0700 Subject: [PATCH 0091/1992] cleanup on README and CHANGELOG markdown --- CHANGELOG.md | 678 +++++++++++++++++++++++++-------------------------- README.md | 8 +- 2 files changed, 343 insertions(+), 343 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8daae53..adfcbf43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,368 +1,368 @@ ## 2019-06-10 CORE 5.3.0 - * Enhancements - - python 2 / 3 support - - added new API using [gRPC](https://grpc.io/) - - --grpc --grpc-port --grpc-address flags added to core-daemon - - core.api.grpc.client.CoreGrpcClient, provides a convenience wrapper for leveraging the API - * Docs - - Updates to installation instructions for latest changes - * Services - - Added FRR service - * EMANE - - Added EMANE prefix configuration when looking for emane model manifest files - - requires configuring **emane_prefix** in /etc/core/core.conf - * Cleanup - - Refactoring of the core python package structure, trying to help provide better organization and - logical groupings - * Issues - - \#246 - Fixed network to network link handling when reading xml files - - \#236 - Fixed storing/reading of link configuration values within xml files - - \#170 - FRR Service - - \#155 - EMANE path configuration - - \#233 - Python 3 support - - \#245 - Fixed bidirectional link configurations when reading from xml files - - \#208 - gRPC API - - Fixed link configuration dup handling when loaded from xml files +* Enhancements + * python 2 / 3 support + * added new API using [gRPC](https://grpc.io/) + * --grpc --grpc-port --grpc-address flags added to core-daemon + * core.api.grpc.client.CoreGrpcClient, provides a convenience wrapper for leveraging the API +* Docs + * Updates to installation instructions for latest changes +* Services + * Added FRR service +* EMANE + * Added EMANE prefix configuration when looking for emane model manifest files + * requires configuring **emane_prefix** in /etc/core/core.conf +* Cleanup + * Refactoring of the core python package structure, trying to help provide better organization and + logical groupings +* Issues + * \#246 - Fixed network to network link handling when reading xml files + * \#236 - Fixed storing/reading of link configuration values within xml files + * \#170 - FRR Service + * \#155 - EMANE path configuration + * \#233 - Python 3 support + * \#245 - Fixed bidirectional link configurations when reading from xml files + * \#208 - gRPC API + * Fixed link configuration dup handling when loaded from xml files ## 2019-06-07 CORE 5.2.2 - * Enhancements: - - adds back in core-daemon udp support for coresendmsg, people may have depended on previously for certain scenarios - * Bug Fixes: - - fixes issue in GUI that would prevent moving nodes during mobility scenarios +* Enhancements: + * adds back in core-daemon udp support for coresendmsg, people may have depended on previously for certain scenarios +* Bug Fixes: + * fixes issue in GUI that would prevent moving nodes during mobility scenarios ## 2019-03-25 CORE 5.2.1 - * Packaging: - - documentation no longer builds by default, must use configure flag - - added configure flag to allow only building vcmd - - sphinx will no long be required when not building documentation - * Services: - - Added source NAT service - - Fixed DHCP service for Ubuntu 18.04 - * BUGFIXES: - - \#188 - properly remove session on delete TLV API call - - \#192 - updated default gnome terminal command for nodes to be Ubuntu 18.04 compatible - - \#193 - updates to service validation, will retry on failure and better exception logging - - \#195 - TLV link message data fix - - \#196 - fix to avoid clearing out default services - - \#197 - removed wireless_link_all API from EmuSession - - \#216 - updated default WLAN bandwidth to 54Mbps - - \#223 - fix to saving RJ45 to session XML files +* Packaging: + * documentation no longer builds by default, must use configure flag + * added configure flag to allow only building vcmd + * sphinx will no long be required when not building documentation +* Services: + * Added source NAT service + * Fixed DHCP service for Ubuntu 18.04 +* BUGFIXES: + * \#188 - properly remove session on delete TLV API call + * \#192 - updated default gnome terminal command for nodes to be Ubuntu 18.04 compatible + * \#193 - updates to service validation, will retry on failure and better exception logging + * \#195 - TLV link message data fix + * \#196 - fix to avoid clearing out default services + * \#197 - removed wireless_link_all API from EmuSession + * \#216 - updated default WLAN bandwidth to 54Mbps + * \#223 - fix to saving RJ45 to session XML files ## 2018-05-22 CORE 5.1 - * DAEMON: - - removed and cleared out code that is either legacy or no longer supported (Xen, BSD, Kernel patching, RPM/DEB - specific files) - - default nodes are now set in the node map - - moved ns3 and netns directories to the top of the repo - - changes to make use of fpm as the tool for building packages - - removed usage of logzero to avoid dependency issues for built packages - - removed daemon addons directory - - added CoreEmu to core.emulator.coreemu to help begin serving as the basis for a more formal API for scripting - and creating new external APIs out of - - cleaned up logging, moved more logging to DEBUG from INFO, tried to mold INFO message to be more simple and - informative - - EMANE 1.0.1-1.21 supported - - updates to leverage EMANE python bindings for dynamically parsing phy/mac manifest files - - example custom EMANE model lives under /usr/share/core/examples/myemane/examplemodel.py - - EMANE TDMA model now supports an option to start a TDMA schedule when running - - fixed issues with coresendmsg script due to code refactoring - - added make target for generating documentation "make doc" - - Python 2.7+ is now required - - ns3 is no longer bundled by default, but will be produced as a separate package for installation - * GUI: - - updated broken help links in GUI Help->About - * Packaging: - - fixed PYTHON_PATH to PYTHONPATH in sysv script - - added make command to leverage FPM as the tool for creating deb/rpm packages going forward, there is documentation - within README.md to try it out - * TEST: - - fixed some broken tests - - new test cases based on CoreEmu usage - * BUGFIXES: - - \#142 - duplication of custom services - - \#136 - sphinx-apidoc command not found - - \#137 - make command fails when using distclean - +* DAEMON: + * removed and cleared out code that is either legacy or no longer supported (Xen, BSD, Kernel patching, RPM/DEB + specific files) + * default nodes are now set in the node map + * moved ns3 and netns directories to the top of the repo + * changes to make use of fpm as the tool for building packages + * removed usage of logzero to avoid dependency issues for built packages + * removed daemon addons directory + * added CoreEmu to core.emulator.coreemu to help begin serving as the basis for a more formal API for scripting + and creating new external APIs out of + * cleaned up logging, moved more logging to DEBUG from INFO, tried to mold INFO message to be more simple and + informative + * EMANE 1.0.1-1.21 supported + * updates to leverage EMANE python bindings for dynamically parsing phy/mac manifest files + * example custom EMANE model lives under /usr/share/core/examples/myemane/examplemodel.py + * EMANE TDMA model now supports an option to start a TDMA schedule when running + * fixed issues with coresendmsg script due to code refactoring + * added make target for generating documentation "make doc" + * Python 2.7+ is now required + * ns3 is no longer bundled by default, but will be produced as a separate package for installation +* GUI: + * updated broken help links in GUI Help->About +* Packaging: + * fixed PYTHON_PATH to PYTHONPATH in sysv script + * added make command to leverage FPM as the tool for creating deb/rpm packages going forward, there is documentation + within README.md to try it out +* TEST: + * fixed some broken tests + * new test cases based on CoreEmu usage +* BUGFIXES: + * \#142 - duplication of custom services + * \#136 - sphinx-apidoc command not found + * \#137 - make command fails when using distclean + ## 2017-09-01 CORE 5.0 - * DEVELOPMENT: - - support for editorconfig to help standardize development across IDEs, from the defined configuration file - - support for sonarqube analysis, from the defined configuration file - * DAEMON: - - code cleanup and improvements to adhere to coding standards (SonarQube) - - leverage "logzero" module to make easy usage of the standard logging module - - improvements to documentation across the code base - - initial work to separate the dependence on TCP API messaging from the core library (easier core scripting) - - beta support for running core in Open vSwitch mode, leveraging Open vSwitch bridges, instead of Linux bridges - * SERVICES: - - added Ryu SDN controller service - - added Open vSwitch service - * TEST: - - added unit/integration tests to support validating changes going forward - * BUGFIXES: - - merged pull requests for: #115, #110, #109, #107, #106, #105, #103, #102, #101, #96 +* DEVELOPMENT: + * support for editorconfig to help standardize development across IDEs, from the defined configuration file + * support for sonarqube analysis, from the defined configuration file +* DAEMON: + * code cleanup and improvements to adhere to coding standards (SonarQube) + * leverage "logzero" module to make easy usage of the standard logging module + * improvements to documentation across the code base + * initial work to separate the dependence on TCP API messaging from the core library (easier core scripting) + * beta support for running core in Open vSwitch mode, leveraging Open vSwitch bridges, instead of Linux bridges +* SERVICES: + * added Ryu SDN controller service + * added Open vSwitch service +* TEST: + * added unit/integration tests to support validating changes going forward +* BUGFIXES: + * merged pull requests for: #115, #110, #109, #107, #106, #105, #103, #102, #101, #96 ## 2015-06-05 CORE 4.8 - * EMANE: - - support for EMANE 0.9.2 - - run emane in each container when using EMANE 0.9.2 - - support using separate control networks for EMANE OTA and event traffic - * GUI: - - fixed an issue where the adjacency widget lines pointed to old node positions - - fixed an issue where not all EMANE 0.9.x IEEE 802.11 MAC parameter were configurable - - fixed an issue related to running python scripts from the GUI when using tcl/tk version 8.6 - - improved batch mode execution to display the check emulation light status - - improved managing multiple sessions - - improved support for using multiple canvases - - added a reload option to the file menu to revert back to a saved scenario - * DAEMON: - - support exporting scenarios in NRL Network Modeling Framework 1.0 XML format - - support importing scenarios in NRL Network Modeling Framework 1.0 XML format - - support exporting the deployed scenario state in NRL NMF XML 1.0 format - - improved EMANE post-startup processing to better synchronize distributed emulations - - improved how addresses are assigned to tun/tap devices - - added support for python state-change callbacks - * SERVICES: - - added mgen sink and mgen actor services - - added oslrv2 and olsr.org services - - added a docker service - * BUILD: - - improved the install/uninstall process - - improved debian and rpm packaging - * BUGFIXES: - - updated the http service for ubuntu 14.04 - - improved included examples - - shortened the length of network interface names - - improved how the core system service manages running the core daemon - - fixed an issues related to applying session configuration setting - - improved detecting when a distributed emulation is already running - - improved documentation +* EMANE: + * support for EMANE 0.9.2 + * run emane in each container when using EMANE 0.9.2 + * support using separate control networks for EMANE OTA and event traffic +* GUI: + * fixed an issue where the adjacency widget lines pointed to old node positions + * fixed an issue where not all EMANE 0.9.x IEEE 802.11 MAC parameter were configurable + * fixed an issue related to running python scripts from the GUI when using tcl/tk version 8.6 + * improved batch mode execution to display the check emulation light status + * improved managing multiple sessions + * improved support for using multiple canvases + * added a reload option to the file menu to revert back to a saved scenario +* DAEMON: + * support exporting scenarios in NRL Network Modeling Framework 1.0 XML format + * support importing scenarios in NRL Network Modeling Framework 1.0 XML format + * support exporting the deployed scenario state in NRL NMF XML 1.0 format + * improved EMANE post-startup processing to better synchronize distributed emulations + * improved how addresses are assigned to tun/tap devices + * added support for python state-change callbacks +* SERVICES: + * added mgen sink and mgen actor services + * added oslrv2 and olsr.org services + * added a docker service +* BUILD: + * improved the install/uninstall process + * improved debian and rpm packaging +* BUGFIXES: + * updated the http service for ubuntu 14.04 + * improved included examples + * shortened the length of network interface names + * improved how the core system service manages running the core daemon + * fixed an issues related to applying session configuration setting + * improved detecting when a distributed emulation is already running + * improved documentation ## 2014-08-06 CORE 4.7 - * EMANE: - - support for EMANE 0.9.1 - - fix error when using Comm Effect model with loss/duplicate string values - - enable flow control in virtual transport if enabled in the MAC model - - fix bug #150 where EMANE event service/address port were not used - * GUI: - - support Tcl/Tk 8.6 when available - - added --(a)ddress and --(p)ort arguments to core-gui command-line - - added File > Execute XML or Python script... option - - added File > Execute Python script with options... menu item - - when executing Python script from GUI, run in background thread, wait for - RUNTIME state - - enter RUNTIME state when start button pressed with empty canvas - - added support for asymmetric link effects - - support link delays up to 274 seconds (netem maximum) - - allow runtime changes of WLAN link effects - * DAEMON: - - set NODE_NAME, NODE_NUMBER, SESSION_SHORT in default vnoded environment - - changed host device naming to use veth, tap prefixes; b.n.SS for bridges - - allow parsing XML files into live running session - - enable link effects between hub/switch and hub/switch connections - - update MDR service to use broadcast interfaces for non-WLAN links - - allow node class to be specified when initializing XML parser - - save and parse canvas origin (reference point) and scale in MP XML - - up/down control script session option - - fix hash calculation used to determine GRE tunnel keys - - use shell script to detach SMF on startup - - added NRL services for mgen sink and nrlolsrv2 - - use SDT URL session option - - added core-manage tool for addons to add/remove/check services, models, - and custom node types - * API: - - implement local flag in Execute Message for running host commands - - jitter changed to 64-bit value to align with delay in Link Message - - added unidirectional link flag TLV to Link Message - - added reconfigure event type for re-generating service config files - - return errors in API with failed services - * BUGFIXES: - - fix HTTP service running under Ubuntu - - fixed the following bugs: #150, 169, 188, 220, 225, 230, 231, 242, 244, - 247, 248, 250, 251 +* EMANE: + * support for EMANE 0.9.1 + * fix error when using Comm Effect model with loss/duplicate string values + * enable flow control in virtual transport if enabled in the MAC model + * fix bug #150 where EMANE event service/address port were not used +* GUI: + * support Tcl/Tk 8.6 when available + * added --(a)ddress and --(p)ort arguments to core-gui command-line + * added File > Execute XML or Python script... option + * added File > Execute Python script with options... menu item + * when executing Python script from GUI, run in background thread, wait for + RUNTIME state + * enter RUNTIME state when start button pressed with empty canvas + * added support for asymmetric link effects + * support link delays up to 274 seconds (netem maximum) + * allow runtime changes of WLAN link effects +* DAEMON: + * set NODE_NAME, NODE_NUMBER, SESSION_SHORT in default vnoded environment + * changed host device naming to use veth, tap prefixes; b.n.SS for bridges + * allow parsing XML files into live running session + * enable link effects between hub/switch and hub/switch connections + * update MDR service to use broadcast interfaces for non-WLAN links + * allow node class to be specified when initializing XML parser + * save and parse canvas origin (reference point) and scale in MP XML + * up/down control script session option + * fix hash calculation used to determine GRE tunnel keys + * use shell script to detach SMF on startup + * added NRL services for mgen sink and nrlolsrv2 + * use SDT URL session option + * added core-manage tool for addons to add/remove/check services, models, + and custom node types +* API: + * implement local flag in Execute Message for running host commands + * jitter changed to 64-bit value to align with delay in Link Message + * added unidirectional link flag TLV to Link Message + * added reconfigure event type for re-generating service config files + * return errors in API with failed services +* BUGFIXES: + * fix HTTP service running under Ubuntu + * fixed the following bugs: #150, 169, 188, 220, 225, 230, 231, 242, 244, + 247, 248, 250, 251 ## 2013-09-25 CORE 4.6 - * NOTE: cored is now core-daemon, and core is now core-gui (for Debian acceptance) - * NOTE: /etc/init.d/core is now /etc/init.d/core-daemon (for insserv compatibility) - * EMANE: - - don't start EMANE locally if no local NEMs - - EMANE poststartup() to re-transmit location events during initialization - - added debug port to EMANE options - - added a basic EMANE 802.11 CORE Python script example - - expose transport XML block generation to EmaneModels - - expose NEM entry to the EmaneModel so it can be overridden by a model - - add the control interface bridge prior to starting EMANE, as some models may - - depend on the controlnet functionality - - added EMANE model to CORE converter - - parse lat/long/alt from node messages, for moving nodes using command-line - - fix bug #196 incorrect distance when traversing UTM zones - * GUI: - - added Cut, Copy, and Paste options to the Edit menu - - paste will copy selected services and take care of node and interface - - renumbering - - implement Edit > Find dialog for searching nodes and links - - when copying existing file for a service, perform string replacement of: - - "~", "%SESSION%", "%SESSION_DIR%", "%SESSION_USER%", "%NODE%", "%NODENAME%" - - use CORE_DATA_DIR insteadof LIBDIR - - fix Adjacency Widget to work with OSPFv2 only networks - * BUILD: - - build/packaging improvements for inclusion on Debian - - fix error when running scenario with a mobility script in batch mode - - include Linux kernel patches for 3.8 - - renamed core-cleanup.sh to core-cleanup for Debian conformance - - don't always generate man pages from Makefile; new manpages for - coresendmsg and core-daemon - * BUGFIXES: - - don't auto-assign IPv4/IPv6 addresses when none received in Link Messages (session reconnect) - - fixed lock view - - fix GUI spinbox errors for Tk 8.5.8 (RHEL/CentOS 6.2) - - fix broker node count for distributed session entering the RUNTIME state when - - (non-EMANE) WLANs or GreTapBridges are involved; - - fix "file exists" error message when distributed session number is re-used - - and servers file is written - - fix bug #194 configuration dialog too long, make dialog scrollable/resizable - - allow float values for loss and duplicates percent - - fix the following bugs: 166, 172, 177, 178, 192, 194, 196, 201, 202, - 205, 206, 210, 212, 213, 214, 221 +* NOTE: cored is now core-daemon, and core is now core-gui (for Debian acceptance) +* NOTE: /etc/init.d/core is now /etc/init.d/core-daemon (for insserv compatibility) +* EMANE: + * don't start EMANE locally if no local NEMs + * EMANE poststartup() to re-transmit location events during initialization + * added debug port to EMANE options + * added a basic EMANE 802.11 CORE Python script example + * expose transport XML block generation to EmaneModels + * expose NEM entry to the EmaneModel so it can be overridden by a model + * add the control interface bridge prior to starting EMANE, as some models may + * depend on the controlnet functionality + * added EMANE model to CORE converter + * parse lat/long/alt from node messages, for moving nodes using command-line + * fix bug #196 incorrect distance when traversing UTM zones +* GUI: + * added Cut, Copy, and Paste options to the Edit menu + * paste will copy selected services and take care of node and interface + * renumbering + * implement Edit > Find dialog for searching nodes and links + * when copying existing file for a service, perform string replacement of: + * "~", "%SESSION%", "%SESSION_DIR%", "%SESSION_USER%", "%NODE%", "%NODENAME%" + * use CORE_DATA_DIR insteadof LIBDIR + * fix Adjacency Widget to work with OSPFv2 only networks +* BUILD: + * build/packaging improvements for inclusion on Debian + * fix error when running scenario with a mobility script in batch mode + * include Linux kernel patches for 3.8 + * renamed core-cleanup.sh to core-cleanup for Debian conformance + * don't always generate man pages from Makefile; new manpages for + coresendmsg and core-daemon +* BUGFIXES: + * don't auto-assign IPv4/IPv6 addresses when none received in Link Messages (session reconnect) + * fixed lock view + * fix GUI spinbox errors for Tk 8.5.8 (RHEL/CentOS 6.2) + * fix broker node count for distributed session entering the RUNTIME state when + * (non-EMANE) WLANs or GreTapBridges are involved; + * fix "file exists" error message when distributed session number is re-used + * and servers file is written + * fix bug #194 configuration dialog too long, make dialog scrollable/resizable + * allow float values for loss and duplicates percent + * fix the following bugs: 166, 172, 177, 178, 192, 194, 196, 201, 202, + 205, 206, 210, 212, 213, 214, 221 ## 2013-04-13 CORE 4.5 - * GUI: - - improved behavior when starting GUI without daemon, or using File New after connection with daemon is lost - - fix various GUI issues when reconnecting to a session - - support 3D GUI via output to SDT3D - - added "Execute Python script..." entry to the File Menu - - support user-defined terminal program instead of hard-coded xterm - - added session options for "enable RJ45s", "preserve session dir" - - added buttons to the IP Addresses dialog for removing all/selected IPv4/IPv6 - - allow sessions with multiple canvases to enter RUNTIME state - - added "--addons" startup mode to pass control to code included from addons dir - - added "Locked" entry to View menu to prevent moving items - - use currently selected node type when invoking a topology generator - - updated throughput plots with resizing, color picker, plot labels, locked scales, and save/load plot - configuration with imn file - - improved session dialog - * EMANE: - - EMANE 0.8.1 support with backwards-compatibility for 0.7.4 - - extend CommEffect model to generate CommEffect events upon receipt of Link Messages having link effects - * Services: - - updated FTP service with root directory for anonymous users - - added HTTP, PCAP, BIRD, RADVD, and Babel services - - support copying existing files instead of always generating them - - added "Services..." entry to node right-click menu - - added "View" button for side-by-side comparison when copying customized config files - - updated Quagga daemons to wait for zebra.vty VTY file before starting - * General: - - XML import and export - - renamed "cored.py" to "cored", "coresendmsg.py" to "coresendmsg" - - code reorganization and clean-up - - updated XML export to write NetworkPlan, MotionPlan, and ServicePlan within a Scenario tag, added new - "Save As XML..." File menu entry - - added script_start/pause/stop options to Ns2ScriptedMobility - - "python" source sub-directory renamed to "daemon" - - added "cored -e" option to execute a Python script, adding its session to the active sessions list, allowing for - GUI connection - - support comma-separated list for custom_services_dir in core.conf file - - updated kernel patches for Linux kernel 3.5 - - support RFC 6164-style IPv6 /127 addressing - * ns-3: - - integrate ns-3 node location between CORE and ns-3 simulation - - added ns-3 random walk mobility example - - updated ns-3 Wifi example to allow GUI connection and moving of nodes - * fixed the following bugs: 54, 103, 111, 136, 145, 153, 157, 160, 161, 162, 164, 165, 168, 170, 171, 173, 174, 176, - 184, 190, 193 +* GUI: + * improved behavior when starting GUI without daemon, or using File New after connection with daemon is lost + * fix various GUI issues when reconnecting to a session + * support 3D GUI via output to SDT3D + * added "Execute Python script..." entry to the File Menu + * support user-defined terminal program instead of hard-coded xterm + * added session options for "enable RJ45s", "preserve session dir" + * added buttons to the IP Addresses dialog for removing all/selected IPv4/IPv6 + * allow sessions with multiple canvases to enter RUNTIME state + * added "--addons" startup mode to pass control to code included from addons dir + * added "Locked" entry to View menu to prevent moving items + * use currently selected node type when invoking a topology generator + * updated throughput plots with resizing, color picker, plot labels, locked scales, and save/load plot + configuration with imn file + * improved session dialog +* EMANE: + * EMANE 0.8.1 support with backwards-compatibility for 0.7.4 + * extend CommEffect model to generate CommEffect events upon receipt of Link Messages having link effects +* Services: + * updated FTP service with root directory for anonymous users + * added HTTP, PCAP, BIRD, RADVD, and Babel services + * support copying existing files instead of always generating them + * added "Services..." entry to node right-click menu + * added "View" button for side-by-side comparison when copying customized config files + * updated Quagga daemons to wait for zebra.vty VTY file before starting +* General: + * XML import and export + * renamed "cored.py" to "cored", "coresendmsg.py" to "coresendmsg" + * code reorganization and clean-up + * updated XML export to write NetworkPlan, MotionPlan, and ServicePlan within a Scenario tag, added new + "Save As XML..." File menu entry + * added script_start/pause/stop options to Ns2ScriptedMobility + * "python" source sub-directory renamed to "daemon" + * added "cored -e" option to execute a Python script, adding its session to the active sessions list, allowing for + GUI connection + * support comma-separated list for custom_services_dir in core.conf file + * updated kernel patches for Linux kernel 3.5 + * support RFC 6164-style IPv6 /127 addressing +* ns-3: + * integrate ns-3 node location between CORE and ns-3 simulation + * added ns-3 random walk mobility example + * updated ns-3 Wifi example to allow GUI connection and moving of nodes +* fixed the following bugs: 54, 103, 111, 136, 145, 153, 157, 160, 161, 162, 164, 165, 168, 170, 171, 173, 174, 176, +184, 190, 193 ## 2012-09-25 CORE 4.4 - * GUI: - - real-time bandwidth plotting tool - - added Wireshark and tshark right-click menu items - - X,Y coordinates shown in the status bar - - updated GUI attribute option to link messages for changing color/width/dash - - added sample IPsec and VPN scenarios, how many nodes script - - added jitter parameter to WLANs - - renamed Experiment menu to Session menu, added session options - - use 'key=value' configuration for services, EMANE models, WLAN models, etc. - - save only service values that have been customized - - copy service parameters from one customized service to another - - right-click menu to start/stop/restart each service - * EMANE: - - EMANE 0.7.4 support - - added support for EMANE CommEffect model and Comm Effect controller GUI - - added support for EMANE Raw Transport when using RJ45 devices - * Services: - - improved service customization; allow a service to define custom Tcl tab - - added vtysh.conf for Quagga service to support 'write mem' - - support scheduled events and services that start N seconds after runtime - - added UCARP service - * Documentation: - - converted the CORE manual to reStructuredText using Sphinx; added Python docs - * General: - - Python code reorganization - - improved cored.py thread locking - - merged xen branch into trunk - - added an event queue to a session with notion of time zero - - added UDP support to cored.py - - use UDP by default in coresendmsg.py; added '-H' option to print examples - - enter a bash shell by default when running vcmd with no arguments - - fixes to distributed emulation entering runtime state - - write 'nodes' file upon session startup - - make session number and other attributes available in environment - - support /etc/core/environment and ~/.core/environment files - - added Ns2ScriptedMobility model to Python, removed from the GUI - - namespace nodes mount a private /sys - - fixed the following bugs: 80, 81, 84, 99, 104, 109, 110, 122, 124, 131, 133, 134, 135, 137, 140, 143, 144, 146, - 147, 151, 154, 155 +* GUI: + * real-time bandwidth plotting tool + * added Wireshark and tshark right-click menu items + * X,Y coordinates shown in the status bar + * updated GUI attribute option to link messages for changing color/width/dash + * added sample IPsec and VPN scenarios, how many nodes script + * added jitter parameter to WLANs + * renamed Experiment menu to Session menu, added session options + * use 'key=value' configuration for services, EMANE models, WLAN models, etc. + * save only service values that have been customized + * copy service parameters from one customized service to another + * right-click menu to start/stop/restart each service +* EMANE: + * EMANE 0.7.4 support + * added support for EMANE CommEffect model and Comm Effect controller GUI + * added support for EMANE Raw Transport when using RJ45 devices +* Services: + * improved service customization; allow a service to define custom Tcl tab + * added vtysh.conf for Quagga service to support 'write mem' + * support scheduled events and services that start N seconds after runtime + * added UCARP service +* Documentation: + * converted the CORE manual to reStructuredText using Sphinx; added Python docs +* General: + * Python code reorganization + * improved cored.py thread locking + * merged xen branch into trunk + * added an event queue to a session with notion of time zero + * added UDP support to cored.py + * use UDP by default in coresendmsg.py; added '-H' option to print examples + * enter a bash shell by default when running vcmd with no arguments + * fixes to distributed emulation entering runtime state + * write 'nodes' file upon session startup + * make session number and other attributes available in environment + * support /etc/core/environment and ~/.core/environment files + * added Ns2ScriptedMobility model to Python, removed from the GUI + * namespace nodes mount a private /sys + * fixed the following bugs: 80, 81, 84, 99, 104, 109, 110, 122, 124, 131, 133, 134, 135, 137, 140, 143, 144, 146, + 147, 151, 154, 155 ## 2012-03-07 CORE 4.3 - * EMANE 0.7.2 and 0.7.3 support - * hook scripts: customize actions at any of six different session states - * Check Emulation Light (CEL) exception feedback system - * added FTP and XORP services, and service validate commands - * services can flag when customization is required - * Python classes to support ns-3 simulation experiments - * write state, node X,Y position, and servers to pycore session dir - * removed over 9,000 lines of unused GUI code - * performance monitoring script - * batch mode improvements and --closebatch option - * export session to EmulationScript XML files - * basic range model moved from GUI to Python, supports 3D coordinates - * improved WLAN dialog with tabs - * added PhysicalNode class for joining real nodes with emulated networks - * fixed the following bugs: 50, 75, 76, 79, 82, 83, 85, 86, 89, 90, 92, 94, 96, 98, 100, 112, 113, 116, 119, 120 +* EMANE 0.7.2 and 0.7.3 support +* hook scripts: customize actions at any of six different session states +* Check Emulation Light (CEL) exception feedback system +* added FTP and XORP services, and service validate commands +* services can flag when customization is required +* Python classes to support ns-3 simulation experiments +* write state, node X,Y position, and servers to pycore session dir +* removed over 9,000 lines of unused GUI code +* performance monitoring script +* batch mode improvements and --closebatch option +* export session to EmulationScript XML files +* basic range model moved from GUI to Python, supports 3D coordinates +* improved WLAN dialog with tabs +* added PhysicalNode class for joining real nodes with emulated networks +* fixed the following bugs: 50, 75, 76, 79, 82, 83, 85, 86, 89, 90, 92, 94, 96, 98, 100, 112, 113, 116, 119, 120 ## 2011-08-19 CORE 4.2 - * EMANE 0.7.1 support - - support for Bypass model, Universal PHY, logging, realtime - * configurable MAC addresses - * control interfaces (backchannel between node and host) - * service customization dialog improved (tabbed) - * new testing scripts for MDR and EMANE performance testing - * improved upgrading of old imn files - * new coresendmsg.py utility (deprecates libcoreapi and coreapisend) - * new security services, custom service becomes UserDefined - * new services and Python scripting chapters in manual - * fixes to distributed emulation, linking tunnels/RJ45s with WLANs/hubs/switches - * fixed the following bugs: 18, 32, 34, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 58, 60, 62, 64, - 65, 66, 68, 71, 72, 74 +* EMANE 0.7.1 support + * support for Bypass model, Universal PHY, logging, realtime +* configurable MAC addresses +* control interfaces (backchannel between node and host) +* service customization dialog improved (tabbed) +* new testing scripts for MDR and EMANE performance testing +* improved upgrading of old imn files +* new coresendmsg.py utility (deprecates libcoreapi and coreapisend) +* new security services, custom service becomes UserDefined +* new services and Python scripting chapters in manual +* fixes to distributed emulation, linking tunnels/RJ45s with WLANs/hubs/switches +* fixed the following bugs: 18, 32, 34, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 58, 60, 62, 64, +65, 66, 68, 71, 72, 74 ## 2011-01-05 CORE 4.1 - * new icons for toolbars and nodes - * node services introduced, node models deprecated - * customizable node types - * traffic flow editor with MGEN support - * user configs moved from /etc/core/`*` to ~/.core/ - * allocate addresses from custom IPv4/IPv6 prefixes - * distributed emulation using GRE tunnels - * FreeBSD 8.1 now uses cored.py - * EMANE 0.6.4 support - * numerous bugfixes +* new icons for toolbars and nodes +* node services introduced, node models deprecated +* customizable node types +* traffic flow editor with MGEN support +* user configs moved from /etc/core/`*` to ~/.core/ +* allocate addresses from custom IPv4/IPv6 prefixes +* distributed emulation using GRE tunnels +* FreeBSD 8.1 now uses cored.py +* EMANE 0.6.4 support +* numerous bugfixes ## 2010-08-17 CORE 4.0 - * Python framework with Linux network namespace (netns) support (Linux netns is now the primary supported platform) - * ability to close the GUI and later reconnect to a running session (netns only) - * EMANE integration (netns only) - * new topology generators, host file generator - * user-editable Observer Widgets - * use of /etc/core instead of /usr/local/etc/core - * various bugfixes +* Python framework with Linux network namespace (netns) support (Linux netns is now the primary supported platform) +* ability to close the GUI and later reconnect to a running session (netns only) +* EMANE integration (netns only) +* new topology generators, host file generator +* user-editable Observer Widgets +* use of /etc/core instead of /usr/local/etc/core +* various bugfixes ## 2009-09-15 CORE 3.5 diff --git a/README.md b/README.md index 76435683..a0af9390 100644 --- a/README.md +++ b/README.md @@ -17,13 +17,13 @@ scripting network emulation. ## Documentation and Examples * Documentation hosted on GitHub - * + * * Basic Script Examples - * [Examples](daemon/examples/api) + * [Examples](daemon/examples/api) * Custom Service Example - * [sample.py](daemon/examples/myservices/sample.py) + * [sample.py](daemon/examples/myservices/sample.py) * Custom Emane Model Example - * [examplemodel.py](daemon/examples/myemane/examplemodel.py) + * [examplemodel.py](daemon/examples/myemane/examplemodel.py) ## Support From 756f8fd3e99ea2e2896a7b0809bcd044220304c0 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 8 Jun 2019 22:46:21 -0700 Subject: [PATCH 0092/1992] fixed python3 specific dependency issue for fpm definition --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 3a4f7dca..6bf34253 100644 --- a/Makefile.am +++ b/Makefile.am @@ -46,7 +46,7 @@ MAINTAINERCLEANFILES = .version \ if PYTHON3 -PYTHON_DEP = python >= 3.0 +PYTHON_DEP = python3 >= 3.0 else PYTHON_DEP = python >= 2.7, python < 3.0 endif From 4535cef89ef4268e80184dbeff81902db9d55f56 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Mon, 10 Jun 2019 07:51:00 -0500 Subject: [PATCH 0093/1992] Updated usage.md Deleted services division --- docs/usage.md | 149 -------------------------------------------------- 1 file changed, 149 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index 744c33a2..09f549b3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -834,155 +834,6 @@ to the Linux bridging and ebtables rules that are used. The basic range wireless model does not support distributed emulation, but EMANE does. -## Services - -CORE uses the concept of services to specify what processes or scripts run on a -node when it is started. Layer-3 nodes such as routers and PCs are defined by -the services that they run. - -Services may be customized for each node, or new custom services can be -created. New node types can be created each having a different name, icon, and -set of default services. Each service defines the per-node directories, -configuration files, startup index, starting commands, validation commands, -shutdown commands, and meta-data associated with a node. - -**NOTE:** - Network namespace nodes do not undergo the normal Linux boot process - using the **init**, **upstart**, or **systemd** frameworks. These - lightweight nodes use configured CORE *services*. - -### Default Services and Node Types - -Here are the default node types and their services: - -* *router* - zebra, OSFPv2, OSPFv3, and IPForward services for IGP - link-state routing. -* *host* - DefaultRoute and SSH services, representing an SSH server having a - default route when connected directly to a router. -* *PC* - DefaultRoute service for having a default route when connected - directly to a router. -* *mdr* - zebra, OSPFv3MDR, and IPForward services for - wireless-optimized MANET Designated Router routing. -* *prouter* - a physical router, having the same default services as the - *router* node type; for incorporating Linux testbed machines into an - emulation. - -Configuration files can be automatically generated by each service. For -example, CORE automatically generates routing protocol configuration for the -router nodes in order to simplify the creation of virtual networks. - -To change the services associated with a node, double-click on the node to -invoke its configuration dialog and click on the *Services...* button, -or right-click a node a choose *Services...* from the menu. -Services are enabled or disabled by clicking on their names. The button next to -each service name allows you to customize all aspects of this service for this -node. For example, special route redistribution commands could be inserted in -to the Quagga routing configuration associated with the zebra service. - -To change the default services associated with a node type, use the Node Types -dialog available from the *Edit* button at the end of the Layer-3 nodes -toolbar, or choose *Node types...* from the *Session* menu. Note that -any new services selected are not applied to existing nodes if the nodes have -been customized. - -The node types are saved in a **~/.core/nodes.conf** file, not with the -**.imn** file. Keep this in mind when changing the default services for -existing node types; it may be better to simply create a new node type. It is -recommended that you do not change the default built-in node types. The -**nodes.conf** file can be copied between CORE machines to save your custom -types. - -### Customizing a Service - -A service can be fully customized for a particular node. From the node's -configuration dialog, click on the button next to the service name to invoke -the service customization dialog for that service. -The dialog has three tabs for configuring the different aspects of the service: -files, directories, and startup/shutdown. - -**NOTE:** - A **yellow** customize icon next to a service indicates that service - requires customization (e.g. the *Firewall* service). - A **green** customize icon indicates that a custom configuration exists. - Click the *Defaults* button when customizing a service to remove any - customizations. - -The Files tab is used to display or edit the configuration files or scripts that -are used for this service. Files can be selected from a drop-down list, and -their contents are displayed in a text entry below. The file contents are -generated by the CORE daemon based on the network topology that exists at -the time the customization dialog is invoked. - -The Directories tab shows the per-node directories for this service. For the -default types, CORE nodes share the same filesystem tree, except for these -per-node directories that are defined by the services. For example, the -**/var/run/quagga** directory needs to be unique for each node running -the Zebra service, because Quagga running on each node needs to write separate -PID files to that directory. - -**NOTE:** - The **/var/log** and **/var/run** directories are - mounted uniquely per-node by default. - Per-node mount targets can be found in **/tmp/pycore.nnnnn/nN.conf/** - (where *nnnnn* is the session number and *N* is the node number.) - -The Startup/shutdown tab lists commands that are used to start and stop this -service. The startup index allows configuring when this service starts relative -to the other services enabled for this node; a service with a lower startup -index value is started before those with higher values. Because shell scripts -generated by the Files tab will not have execute permissions set, the startup -commands should include the shell name, with -something like ```sh script.sh```. - -Shutdown commands optionally terminate the process(es) associated with this -service. Generally they send a kill signal to the running process using the -*kill* or *killall* commands. If the service does not terminate -the running processes using a shutdown command, the processes will be killed -when the *vnoded* daemon is terminated (with *kill -9*) and -the namespace destroyed. It is a good practice to -specify shutdown commands, which will allow for proper process termination, and -for run-time control of stopping and restarting services. - -Validate commands are executed following the startup commands. A validate -command can execute a process or script that should return zero if the service -has started successfully, and have a non-zero return value for services that -have had a problem starting. For example, the *pidof* command will check -if a process is running and return zero when found. When a validate command -produces a non-zero return value, an exception is generated, which will cause -an error to be displayed in the Check Emulation Light. - -**TIP:** - To start, stop, and restart services during run-time, right-click a - node and use the *Services...* menu. - -### Creating new Services - -Services can save time required to configure nodes, especially if a number -of nodes require similar configuration procedures. New services can be -introduced to automate tasks. - -The easiest way to capture the configuration of a new process into a service -is by using the **UserDefined** service. This is a blank service where any -aspect may be customized. The UserDefined service is convenient for testing -ideas for a service before adding a new service type. - -To introduce new service types, a **myservices/** directory exists in the -user's CORE configuration directory, at **~/.core/myservices/**. A detailed -**README.txt** file exists in that directory to outline the steps necessary -for adding a new service. First, you need to create a small Python file that -defines the service; then the **custom_services_dir** entry must be set -in the **/etc/core/core.conf** configuration file. A sample is provided in -the **myservices/** directory. - -**NOTE:** - The directory name used in **custom_services_dir** should be unique and - should not correspond to - any existing Python module name. For example, don't use the name **subprocess** - or **services**. - -If you have created a new service type that may be useful to others, please -consider contributing it to the CORE project. - ## Check Emulation Light The |cel| Check Emulation Light, or CEL, is located in the bottom right-hand corner From 4c5686fdea33cd2618dd318957b72a44d21a380e Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Mon, 10 Jun 2019 08:19:28 -0500 Subject: [PATCH 0094/1992] Updated services.md Added the information regarding services removed from the usage.md document --- docs/services.md | 151 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 4 deletions(-) diff --git a/docs/services.md b/docs/services.md index 793e6f99..1e67a543 100644 --- a/docs/services.md +++ b/docs/services.md @@ -3,11 +3,154 @@ * Table of Contents {:toc} -## Custom Services +## Services -CORE supports custom developed services by way of dynamically loading user created python files. -Custom services should be placed within the path defined by **custom_services_dir** in the CORE -configuration file. This path cannot end in **/services**. +CORE uses the concept of services to specify what processes or scripts run on a +node when it is started. Layer-3 nodes such as routers and PCs are defined by +the services that they run. + +Services may be customized for each node, or new custom services can be +created. New node types can be created each having a different name, icon, and +set of default services. Each service defines the per-node directories, +configuration files, startup index, starting commands, validation commands, +shutdown commands, and meta-data associated with a node. + +**NOTE:** + Network namespace nodes do not undergo the normal Linux boot process + using the **init**, **upstart**, or **systemd** frameworks. These + lightweight nodes use configured CORE *services*. + +### Default Services and Node Types + +Here are the default node types and their services: + +* *router* - zebra, OSFPv2, OSPFv3, and IPForward services for IGP + link-state routing. +* *host* - DefaultRoute and SSH services, representing an SSH server having a + default route when connected directly to a router. +* *PC* - DefaultRoute service for having a default route when connected + directly to a router. +* *mdr* - zebra, OSPFv3MDR, and IPForward services for + wireless-optimized MANET Designated Router routing. +* *prouter* - a physical router, having the same default services as the + *router* node type; for incorporating Linux testbed machines into an + emulation. + +Configuration files can be automatically generated by each service. For +example, CORE automatically generates routing protocol configuration for the +router nodes in order to simplify the creation of virtual networks. + +To change the services associated with a node, double-click on the node to +invoke its configuration dialog and click on the *Services...* button, +or right-click a node a choose *Services...* from the menu. +Services are enabled or disabled by clicking on their names. The button next to +each service name allows you to customize all aspects of this service for this +node. For example, special route redistribution commands could be inserted in +to the Quagga routing configuration associated with the zebra service. + +To change the default services associated with a node type, use the Node Types +dialog available from the *Edit* button at the end of the Layer-3 nodes +toolbar, or choose *Node types...* from the *Session* menu. Note that +any new services selected are not applied to existing nodes if the nodes have +been customized. + +The node types are saved in a **~/.core/nodes.conf** file, not with the +**.imn** file. Keep this in mind when changing the default services for +existing node types; it may be better to simply create a new node type. It is +recommended that you do not change the default built-in node types. The +**nodes.conf** file can be copied between CORE machines to save your custom +types. + +### Customizing a Service + +A service can be fully customized for a particular node. From the node's +configuration dialog, click on the button next to the service name to invoke +the service customization dialog for that service. +The dialog has three tabs for configuring the different aspects of the service: +files, directories, and startup/shutdown. + +**NOTE:** + A **yellow** customize icon next to a service indicates that service + requires customization (e.g. the *Firewall* service). + A **green** customize icon indicates that a custom configuration exists. + Click the *Defaults* button when customizing a service to remove any + customizations. + +The Files tab is used to display or edit the configuration files or scripts that +are used for this service. Files can be selected from a drop-down list, and +their contents are displayed in a text entry below. The file contents are +generated by the CORE daemon based on the network topology that exists at +the time the customization dialog is invoked. + +The Directories tab shows the per-node directories for this service. For the +default types, CORE nodes share the same filesystem tree, except for these +per-node directories that are defined by the services. For example, the +**/var/run/quagga** directory needs to be unique for each node running +the Zebra service, because Quagga running on each node needs to write separate +PID files to that directory. + +**NOTE:** + The **/var/log** and **/var/run** directories are + mounted uniquely per-node by default. + Per-node mount targets can be found in **/tmp/pycore.nnnnn/nN.conf/** + (where *nnnnn* is the session number and *N* is the node number.) + +The Startup/shutdown tab lists commands that are used to start and stop this +service. The startup index allows configuring when this service starts relative +to the other services enabled for this node; a service with a lower startup +index value is started before those with higher values. Because shell scripts +generated by the Files tab will not have execute permissions set, the startup +commands should include the shell name, with +something like ```sh script.sh```. + +Shutdown commands optionally terminate the process(es) associated with this +service. Generally they send a kill signal to the running process using the +*kill* or *killall* commands. If the service does not terminate +the running processes using a shutdown command, the processes will be killed +when the *vnoded* daemon is terminated (with *kill -9*) and +the namespace destroyed. It is a good practice to +specify shutdown commands, which will allow for proper process termination, and +for run-time control of stopping and restarting services. + +Validate commands are executed following the startup commands. A validate +command can execute a process or script that should return zero if the service +has started successfully, and have a non-zero return value for services that +have had a problem starting. For example, the *pidof* command will check +if a process is running and return zero when found. When a validate command +produces a non-zero return value, an exception is generated, which will cause +an error to be displayed in the Check Emulation Light. + +**TIP:** + To start, stop, and restart services during run-time, right-click a + node and use the *Services...* menu. + +### Creating new Services + +Services can save time required to configure nodes, especially if a number +of nodes require similar configuration procedures. New services can be +introduced to automate tasks. + +The easiest way to capture the configuration of a new process into a service +is by using the **UserDefined** service. This is a blank service where any +aspect may be customized. The UserDefined service is convenient for testing +ideas for a service before adding a new service type. + +To introduce new service types, a **myservices/** directory exists in the +user's CORE configuration directory, at **~/.core/myservices/**. A detailed +**README.txt** file exists in that directory to outline the steps necessary +for adding a new service. First, you need to create a small Python file that +defines the service; then the **custom_services_dir** entry must be set +in the **/etc/core/core.conf** configuration file. A sample is provided in +the **myservices/** directory. + +**NOTE:** + The directory name used in **custom_services_dir** should be unique and + should not correspond to + any existing Python module name. For example, don't use the name **subprocess** + or **services**. + +If you have created a new service type that may be useful to others, please +consider contributing it to the CORE project. Here is an example service with documentation describing functionality: [Example Service](exampleservice.html) From 4485d9b3c5a08b4dd2959a30a3d7c33a433d3459 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Mon, 10 Jun 2019 14:41:30 -0500 Subject: [PATCH 0095/1992] Formated install from source instructions on install.md --- docs/install.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docs/install.md b/docs/install.md index fb161f78..40208d21 100644 --- a/docs/install.md +++ b/docs/install.md @@ -170,27 +170,35 @@ This option is listed here for developers and advanced users who are comfortable To build CORE from source on Ubuntu, first install these development packages. These packages are not required for normal binary package installs. -#### Ubuntu 18.04 pre-reqs +You can obtain the CORE source from the [CORE GitHub](https://github.com/coreemu/core) page. Choose either a stable release version or the development snapshot available in the *nightly_snapshots* directory. + +#### Install Requirements + +##### Ubuntu 18.04 Requirements ```shell sudo apt install automake pkg-config gcc libev-dev bridge-utils ebtables python-dev python-sphinx python-setuptools python-lxml python-enum34 tk libtk-img ``` -#### Ubuntu 16.04 Requirements +##### Ubuntu 16.04 Requirements ```shell sudo apt-get install automake bridge-utils ebtables python-dev libev-dev python-sphinx python-setuptools python-enum34 python-lxml libtk-img ``` -#### CentOS 7 with Gnome Desktop Requirements +##### CentOS 7 with Gnome Desktop Requirements ```shell sudo yum -y install automake gcc python-devel libev-devel python-sphinx tk python-lxml python-enum34 ``` -You can obtain the CORE source from the [CORE GitHub](https://github.com/coreemu/core) page. Choose either a stable release version or the development snapshot available in the *nightly_snapshots* directory. +#### Download and Extract Source Code +##### Download +You can obtain the CORE source code from the [CORE GitHub](https://github.com/coreemu/core) page. + +##### Extract ```shell tar xzf core-*.tar.gz cd core-* From 777e224a87285c67adc576edf44932a4e9f17fd7 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Mon, 10 Jun 2019 14:47:15 -0500 Subject: [PATCH 0096/1992] Fixed misspelled word --- docs/install.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/install.md b/docs/install.md index 40208d21..6966c6f7 100644 --- a/docs/install.md +++ b/docs/install.md @@ -204,7 +204,7 @@ tar xzf core-*.tar.gz cd core-* ``` -#### Tradional Autotools Build +#### Traditional Autotools Build ```shell ./bootstrap.sh ./configure From 6b4b82d2f656c128c4be8bb22e903f60aed35903 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Mon, 10 Jun 2019 15:28:52 -0500 Subject: [PATCH 0097/1992] Fixed misspelled word in update.md --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 09f549b3..c844be81 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -23,7 +23,7 @@ __Note: The CORE GUI is currently in a state of transition. The replacement can ## Prerequisites -Beyond instaling CORE, you must have the CORE daemon running. This is done on the command line with either Systemd or SysV +Beyond installing CORE, you must have the CORE daemon running. This is done on the command line with either Systemd or SysV ```shell # systed sudo systemctl daemon-reload From 5b1c9a6e686f7d14766b418a5659800ed2fdce00 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 11 Jun 2019 12:05:04 -0700 Subject: [PATCH 0098/1992] small tweaks to documentation, initial add of a section for grpc, moved ns3 to experimental section --- docs/experimental.md | 9 ++++ docs/grpc.md | 121 +++++++++++++++++++++++++++++++++++++++++++ docs/index.md | 43 +++++++++------ docs/scripting.md | 6 +-- 4 files changed, 161 insertions(+), 18 deletions(-) create mode 100644 docs/experimental.md create mode 100644 docs/grpc.md diff --git a/docs/experimental.md b/docs/experimental.md new file mode 100644 index 00000000..7fdd896e --- /dev/null +++ b/docs/experimental.md @@ -0,0 +1,9 @@ +# Experimental Features + +Below are current features that are considered experimental and will likely have issues. + +# NS3 + +Experimental support for scripting CORE nodes with NS3. + +[NS# Overview](ns3.md) diff --git a/docs/grpc.md b/docs/grpc.md new file mode 100644 index 00000000..d9743506 --- /dev/null +++ b/docs/grpc.md @@ -0,0 +1,121 @@ +# Using the gRPC API + +By default the gRPC API is currently not turned on by default. There are a couple ways that this can be enabled +to use. + +## Enabling gRPC + +### HTTP Proxy + +Since gRPC is HTTP2 based, proxy configurations can cause issue. Clear out your proxy when running if needed. + +### Daemon Options + +The gRPC API is enabled through options provided to the **core-daemon**. + +```shell +usage: core-daemon [-h] [-f CONFIGFILE] [-p PORT] [-n NUMTHREADS] [--ovs] + [--grpc] [--grpc-port GRPCPORT] + [--grpc-address GRPCADDRESS] + +CORE daemon v.5.3.0 instantiates Linux network namespace nodes. + +optional arguments: + -h, --help show this help message and exit + -f CONFIGFILE, --configfile CONFIGFILE + read config from specified file; default = + /etc/core/core.conf + -p PORT, --port PORT port number to listen on; default = 4038 + -n NUMTHREADS, --numthreads NUMTHREADS + number of server threads; default = 1 + --ovs enable experimental ovs mode, default is false + --grpc enable grpc api, default is false + --grpc-port GRPCPORT grpc port to listen on; default 50051 + --grpc-address GRPCADDRESS + grpc address to listen on; default localhost +``` + +### Enabling in Service Files + +Modify service files to append the --grpc options as desired. + +For sysv services /etc/init.d/core-daemon +```shell +CMD="PYTHONPATH=/usr/lib/python3.6/site-packages python3 /usr/bin/$NAME --grpc" +``` + +For systemd service /lib/systemd/system/core-daemon.service +```shell +ExecStart=@PYTHON@ @bindir@/core-daemon --grpc +``` + +### Enabling from Command Line + +```shell +sudo core-daemon --grpc +``` + +## Python Client + +A python client wrapper is provided at **core.api.grpc.client.CoreGrpcClient**. + +Below is a small example using it. + +```python +import logging +from builtins import range + +from core.api.grpc import client, core_pb2 + + +def log_event(event): + logging.info("event: %s", event) + + +def main(): + core = client.CoreGrpcClient() + + with core.context_connect(): + # create session + response = core.create_session() + logging.info("created session: %s", response) + + # handle events session may broadcast + session_id = response.session_id + core.events(session_id, log_event) + + # change session state + response = core.set_session_state(session_id, core_pb2.SessionState.CONFIGURATION) + logging.info("set session state: %s", response) + + # create switch node + switch = core_pb2.Node(type=core_pb2.NodeType.SWITCH) + response = core.add_node(session_id, switch) + logging.info("created switch: %s", response) + switch_id = response.node_id + + # helper to create interfaces + interface_helper = client.InterfaceHelper(ip4_prefix="10.83.0.0/16") + + for i in range(2): + # create node + position = core_pb2.Position(x=50 + 50 * i, y=50) + node = core_pb2.Node(position=position) + response = core.add_node(session_id, node) + logging.info("created node: %s", response) + node_id = response.node_id + + # create link + interface_one = interface_helper.create_interface(node_id, 0) + response = core.add_link(session_id, node_id, switch_id, interface_one) + logging.info("created link: %s", response) + + # change session state + response = core.set_session_state(session_id, core_pb2.SessionState.INSTANTIATION) + logging.info("set session state: %s", response) + + +if __name__ == "__main__": + logging.basicConfig(level=logging.DEBUG) + main() +``` diff --git a/docs/index.md b/docs/index.md index c4dd2a24..6d0a0477 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,9 +2,13 @@ ## Introduction -CORE (Common Open Research Emulator) is a tool for building virtual networks. As an emulator, CORE builds a representation of a real computer network that runs in real time, as opposed to simulation, where abstract models are used. The live-running emulation can be connected to physical networks and routers. It provides an environment for running real applications and protocols, taking advantage of virtualization provided by the Linux operating system. +CORE (Common Open Research Emulator) is a tool for building virtual networks. As an emulator, CORE builds a +representation of a real computer network that runs in real time, as opposed to simulation, where abstract models are +used. The live-running emulation can be connected to physical networks and routers. It provides an environment for +running real applications and protocols, taking advantage of virtualization provided by the Linux operating system. -CORE is typically used for network and protocol research, demonstrations, application and platform testing, evaluating networking scenarios, security studies, and increasing the size of physical test networks. +CORE is typically used for network and protocol research, demonstrations, application and platform testing, evaluating +networking scenarios, security studies, and increasing the size of physical test networks. ### Key Features * Efficient and scalable @@ -14,22 +18,31 @@ CORE is typically used for network and protocol research, demonstrations, applic ## Topics -* [Architecture](architecture.md) -* [Installation](install.md) -* [Using the GUI](usage.md) -* [Python Scripting](scripting.md) -* [Node Types](machine.md) -* [CTRLNET](ctrlnet.md) -* [Services](services.md) -* [EMANE](emane.md) -* [NS3](ns3.md) -* [Performance](performance.md) -* [Developers Guide](devguide.md) +| Topic | Description| +|-------|------------| +|[Architecture](architecture.md)|Overview of the architecture| +|[Installation](install.md)|Installing from source, packages, & other dependencies| +|[Using the GUI](usage.md)|Details on the different node types and options in the GUI| +|[Python Scripting](scripting.md)|How to write python scripts for creating a CORE session| +|[gRPC API](grpc.md)|How to enable and use the gRPC API| +|[Node Types](machine.md)|Overview of node types supported within CORE| +|[CTRLNET](ctrlnet.md)|How to use control networks to communicate with nodes from host| +|[Services](services.md)|Overview of provided services and creating custom ones| +|[EMANE](emane.md)|Overview of EMANE integration and integrating custom EMANE models| +|[Performance](performance.md)|Notes on performance when using CORE| +|[Developers Guide](devguide.md)|Overview of topics when developing CORE| +|[Experimental](experimental.md)|Experimental features for use with or within CORE| ## Credits -The CORE project was derived from the open source IMUNES project from the University of Zagreb in 2004. In 2006, changes for CORE were released back to that project, some items of which were adopted. Marko Zec is the primary developer from the University of Zagreb responsible for the IMUNES (GUI) and VirtNet (kernel) projects. Ana Kukec and Miljenko Mikuc are known contributors. +The CORE project was derived from the open source IMUNES project from the University of Zagreb in 2004. In 2006, +changes for CORE were released back to that project, some items of which were adopted. Marko Zec is the +primary developer from the University of Zagreb responsible for the IMUNES (GUI) and VirtNet (kernel) projects. Ana +Kukec and Miljenko Mikuc are known contributors. -Jeff Ahrenholz has been the primary Boeing developer of CORE, and has written this manual. Tom Goff designed the Python framework and has made significant contributions. Claudiu Danilov, Rod Santiago, Kevin Larson, Gary Pei, Phil Spagnolo, and Ian Chakeres have contributed code to CORE. Dan Mackley helped develop the CORE API, originally to interface with a simulator. Jae Kim and Tom Henderson have supervised the project and provided direction. +Jeff Ahrenholz has been the primary Boeing developer of CORE, and has written this manual. Tom Goff designed the +Python framework and has made significant contributions. Claudiu Danilov, Rod Santiago, Kevin Larson, Gary Pei, +Phil Spagnolo, and Ian Chakeres have contributed code to CORE. Dan Mackley helped develop the CORE API, originally to +interface with a simulator. Jae Kim and Tom Henderson have supervised the project and provided direction. Copyright (c) 2005-2018, the Boeing Company. diff --git a/docs/scripting.md b/docs/scripting.md index efb77ff7..9f8594ea 100644 --- a/docs/scripting.md +++ b/docs/scripting.md @@ -17,8 +17,8 @@ Here are the basic elements of a CORE Python script: ```python from core.emulator.coreemu import CoreEmu from core.emulator.emudata import IpPrefixes -from core.enumerations import EventTypes -from core.enumerations import NodeTypes +from core.emulator.enumerations import EventTypes +from core.emulator.enumerations import NodeTypes # ip generator for example prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") @@ -34,7 +34,7 @@ session.set_state(EventTypes.CONFIGURATION_STATE) switch = session.add_node(_type=NodeTypes.SWITCH) # create nodes -for _ in xrange(options.nodes): +for _ in range(2): node = session.add_node() interface = prefixes.create_interface(node) session.add_link(node.id, switch.id, interface_one=interface) From 20dc78dfc4f34c2a46bba07ff8308dfdf092d35c Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Tue, 11 Jun 2019 14:43:34 -0500 Subject: [PATCH 0099/1992] Update services.md --- docs/services.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/services.md b/docs/services.md index 1e67a543..5aaf3593 100644 --- a/docs/services.md +++ b/docs/services.md @@ -154,3 +154,29 @@ consider contributing it to the CORE project. Here is an example service with documentation describing functionality: [Example Service](exampleservice.html) + +### Available Services + +#### BIRD Internet Routing Daemon +The [BIRD Internet Routing Daemon](https://bird.network.cz/) is a routing daemon; i.e., a software responsible for managing kernel packet forwarding tables. It aims to develop a dynamic IP routing daemon with full support of all modern routing protocols, easy to use configuration interface and powerful route filtering language, primarily targeted on (but not limited to) Linux and other UNIX-like systems and distributed under the GNU General Public License. BIRD has a free implementation of several well known and common routing and router-supplemental protocols, namely RIP, RIPng, OSPFv2, OSPFv3, BGP, BFD, and NDP/RA. BIRD supports IPv4 and IPv6 address families, Linux kernel and several BSD variants (tested on FreeBSD, NetBSD and OpenBSD). BIRD consists of bird daemon and birdc interactive CLI client used for supervision. + +In order to be able to use the BIRD Internet Routing Protocol, you must first install project on your machine. + + +##### BIRD Package Install +```shell +sudo apt-get install bird +``` + +##### BIRD Source Code Install +You can download BIRD source code from it's [official repository.](https://gitlab.labs.nic.cz/labs/bird/) +```shell +./configure +make +su +make install +vi /usr/local/etc/bird.conf +``` +The installation will place the bird directory inside */etc* where you will also find its config file. + +In order to be able to do use the Bird Internet Routing Protocol, you must modify *bird.conf* due to the fact that the given configuration file is not configured beyond allowing the bird daemon to start, which means that nothing else will happen if you run it. Keeran Marquis has a very detailed example on [Configuring BGP using Bird on Ubuntu](https://blog.marquis.co/configuring-bgp-using-bird-on-ubuntu-14-04lts/) which can be used as a building block to implement your custom routing daemon. From 38c58d9603d9eaa5707a0e6a19f5164a3385f9ba Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 11 Jun 2019 13:11:36 -0700 Subject: [PATCH 0100/1992] fixed tlv api changing wlan config during runtime, fixed for grpc as well and added grpc test --- daemon/core/api/grpc/server.py | 5 ++++- daemon/core/api/tlv/corehandlers.py | 7 +++++++ daemon/core/nodes/network.py | 4 ++-- daemon/tests/test_grpc.py | 15 +++++++++++++-- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 120eb98e..f7d1ffa0 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -854,7 +854,10 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SetWlanConfig(self, request, context): logging.debug("set wlan config: %s", request) session = self.get_session(request.session_id, context) - session.mobility.set_model_config(request.node_id, BasicRangeModel.name, request.config) + node = self.get_node(session, request.node_id, context) + session.mobility.set_model_config(node.id, BasicRangeModel.name, request.config) + if session.state == EventTypes.RUNTIME_STATE.value: + node.updatemodel(request.config) return core_pb2.SetWlanConfigResponse(result=True) def GetEmaneConfig(self, request, context): diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index efb2a6bc..bc2362a8 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -40,6 +40,7 @@ from core.emulator.enumerations import NodeTlvs from core.emulator.enumerations import NodeTypes from core.emulator.enumerations import RegisterTlvs from core.emulator.enumerations import SessionTlvs +from core.location.mobility import BasicRangeModel from core.nodes import nodeutils from core.services.coreservices import ServiceManager from core.services.coreservices import ServiceShim @@ -1268,6 +1269,12 @@ class CoreHandler(socketserver.BaseRequestHandler): parsed_config = ConfigShim.str_to_dict(values_str) self.session.mobility.set_model_config(node_id, object_name, parsed_config) + if self.session.state == EventTypes.RUNTIME_STATE.value and object_name == BasicRangeModel.name: + try: + node = self.session.get_node(node_id) + node.updatemodel(parsed_config) + except KeyError: + logging.error("skipping mobility configuration for unknown node: %s", node_id) return replies diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index dd4cbed7..667a8229 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -1039,14 +1039,14 @@ class WlanNode(CoreNetwork): if not self.model: raise ValueError("no model set to update for node(%s)", self.id) logging.info("node(%s) updating model(%s): %s", self.id, self.model.name, config) - self.model.set_configs(config, node_id=self.id) + self.model.update_config(config) if self.model.position_callback: for netif in self.netifs(): netif.poshook = self.model.position_callback if netif.node is not None: x, y, z = netif.node.position.get() netif.poshook(netif, x, y, z) - self.model.updateconfig() + self.model.setlinkparams() def all_link_data(self, flags): """ diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index 416147d7..990f80cb 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -458,18 +458,29 @@ class TestGrpc: # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() + session.set_state(EventTypes.CONFIGURATION_STATE) wlan = session.add_node(_type=NodeTypes.WIRELESS_LAN) + wlan.setmodel(BasicRangeModel, BasicRangeModel.default_values()) + session.instantiate() range_key = "range" - range_value = "300" + range_value = "50" # then with client.context_connect(): - response = client.set_wlan_config(session.id, wlan.id, {range_key: range_value}) + response = client.set_wlan_config(session.id, wlan.id, { + range_key: range_value, + "delay": "0", + "loss": "0", + "bandwidth": "50000", + "error": "0", + "jitter": "0" + }) # then assert response.result is True config = session.mobility.get_model_config(wlan.id, BasicRangeModel.name) assert config[range_key] == range_value + assert wlan.model.range == int(range_value) def test_get_emane_config(self, grpc_server): # given From e0dcb194cceb0cf0e5f1da10d1fffbd64c5d9334 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 11 Jun 2019 13:23:19 -0700 Subject: [PATCH 0101/1992] removed duplicate code updating basic range model updates --- daemon/core/nodes/network.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 667a8229..a39acc71 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -1018,14 +1018,7 @@ class WlanNode(CoreNetwork): logging.info("adding model: %s", model.name) if model.config_type == RegisterTlvs.WIRELESS.value: self.model = model(session=self.session, _id=self.id) - self.model.update_config(config) - if self.model.position_callback: - for netif in self.netifs(): - netif.poshook = self.model.position_callback - if netif.node is not None: - x, y, z = netif.node.position.get() - netif.poshook(netif, x, y, z) - self.model.setlinkparams() + self.updatemodel(config) elif model.config_type == RegisterTlvs.MOBILITY.value: self.mobility = model(session=self.session, _id=self.id) self.mobility.update_config(config) @@ -1046,7 +1039,6 @@ class WlanNode(CoreNetwork): if netif.node is not None: x, y, z = netif.node.position.get() netif.poshook(netif, x, y, z) - self.model.setlinkparams() def all_link_data(self, flags): """ From e7d12b9746422e39499854f62a958d355f86ecb7 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 11 Jun 2019 15:07:36 -0700 Subject: [PATCH 0102/1992] fixing mobility working in python2/3 due to using __cmp__ and node updates not being ints --- daemon/core/location/mobility.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index 4c74447a..a7153186 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -9,7 +9,7 @@ import os import threading import time from builtins import int -from past.builtins import cmp +from functools import total_ordering from core import utils from core.config import ConfigGroup @@ -561,6 +561,7 @@ class BasicRangeModel(WirelessModel): return all_links +@total_ordering class WayPoint(object): """ Maintains information regarding waypoints. @@ -580,18 +581,17 @@ class WayPoint(object): self.coords = coords self.speed = speed - def __cmp__(self, other): - """ - Custom comparison method for waypoints. + def __eq__(self, other): + return (self.time, self.nodenum) == (other.time, other.nodedum) - :param WayPoint other: waypoint to compare to - :return: the comparison result against the other waypoint - :rtype: int - """ - tmp = cmp(self.time, other.time) - if tmp == 0: - tmp = cmp(self.nodenum, other.nodenum) - return tmp + def __ne__(self, other): + return not self == other + + def __lt__(self, other): + result = self.time < other.time + if result: + result = self.nodenum < other.nodenum + return result class WayPointMobility(WirelessModel): @@ -836,7 +836,12 @@ class WayPointMobility(WirelessModel): :param z: z position :return: nothing """ - # this would cause PyCoreNetIf.poshook() callback (range calculation) + if x is not None: + x = int(x) + if y is not None: + y = int(y) + if z is not None: + z = int(z) node.position.set(x, y, z) node_data = node.data(message_type=0) self.session.broadcast_node(node_data) From a9e622c32f45d7905c91b227d44b887a2c0c7c30 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 11 Jun 2019 15:27:50 -0700 Subject: [PATCH 0103/1992] fixed issue when getting wlan config range value from gui as a float string, cannot be parsed to int by python3 --- daemon/core/location/mobility.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/location/mobility.py b/daemon/core/location/mobility.py index a7153186..a63dce65 100644 --- a/daemon/core/location/mobility.py +++ b/daemon/core/location/mobility.py @@ -358,7 +358,7 @@ class BasicRangeModel(WirelessModel): :param dict config: values to convert :return: nothing """ - self.range = int(config["range"]) + self.range = int(float(config["range"])) logging.info("basic range model configured for WLAN %d using range %d", self.wlan.id, self.range) self.bw = int(config["bandwidth"]) if self.bw == 0: From fca79fcf949aff59a49d895bd85cca08cd7f48c9 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Tue, 11 Jun 2019 15:55:11 -0700 Subject: [PATCH 0104/1992] small change for possibility of runtime mobility configuration --- daemon/core/api/tlv/corehandlers.py | 5 +++-- daemon/core/nodes/network.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/daemon/core/api/tlv/corehandlers.py b/daemon/core/api/tlv/corehandlers.py index bc2362a8..40ac9e94 100644 --- a/daemon/core/api/tlv/corehandlers.py +++ b/daemon/core/api/tlv/corehandlers.py @@ -1269,10 +1269,11 @@ class CoreHandler(socketserver.BaseRequestHandler): parsed_config = ConfigShim.str_to_dict(values_str) self.session.mobility.set_model_config(node_id, object_name, parsed_config) - if self.session.state == EventTypes.RUNTIME_STATE.value and object_name == BasicRangeModel.name: + if self.session.state == EventTypes.RUNTIME_STATE.value: try: node = self.session.get_node(node_id) - node.updatemodel(parsed_config) + if object_name == BasicRangeModel.name: + node.updatemodel(parsed_config) except KeyError: logging.error("skipping mobility configuration for unknown node: %s", node_id) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index a39acc71..bb5b269d 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -1026,7 +1026,7 @@ class WlanNode(CoreNetwork): def update_mobility(self, config): if not self.mobility: raise ValueError("no mobility set to update for node(%s)", self.id) - self.mobility.set_configs(config, node_id=self.id) + self.mobility.update_config(config) def updatemodel(self, config): if not self.model: From ac9dab097a33478815609d303ef597d8b4d20508 Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Wed, 12 Jun 2019 09:44:10 -0700 Subject: [PATCH 0105/1992] default logging to INFO --- daemon/data/logging.conf | 2 +- scripts/core-daemon.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/data/logging.conf b/daemon/data/logging.conf index 46de6e92..7f3d496f 100644 --- a/daemon/data/logging.conf +++ b/daemon/data/logging.conf @@ -14,7 +14,7 @@ } }, "root": { - "level": "DEBUG", + "level": "INFO", "handlers": ["console"] } } diff --git a/scripts/core-daemon.in b/scripts/core-daemon.in index a865fcef..c578c397 100644 --- a/scripts/core-daemon.in +++ b/scripts/core-daemon.in @@ -20,7 +20,7 @@ NAME=`basename $0` PIDFILE="@CORE_STATE_DIR@/run/$NAME.pid" LOG="@CORE_STATE_DIR@/log/$NAME.log" -CMD="PYTHONPATH=@pythondir@ @PYTHON@ @bindir@/$NAME" +CMD="@PYTHON@ @bindir@/$NAME" get_pid() { cat "$PIDFILE" From c1113cc19293085a7512c17f95c6bf3f6a997cb2 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Wed, 12 Jun 2019 13:40:51 -0500 Subject: [PATCH 0106/1992] Added FRRouting documentation --- docs/services.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/docs/services.md b/docs/services.md index 5aaf3593..f3300679 100644 --- a/docs/services.md +++ b/docs/services.md @@ -160,7 +160,7 @@ Here is an example service with documentation describing functionality: #### BIRD Internet Routing Daemon The [BIRD Internet Routing Daemon](https://bird.network.cz/) is a routing daemon; i.e., a software responsible for managing kernel packet forwarding tables. It aims to develop a dynamic IP routing daemon with full support of all modern routing protocols, easy to use configuration interface and powerful route filtering language, primarily targeted on (but not limited to) Linux and other UNIX-like systems and distributed under the GNU General Public License. BIRD has a free implementation of several well known and common routing and router-supplemental protocols, namely RIP, RIPng, OSPFv2, OSPFv3, BGP, BFD, and NDP/RA. BIRD supports IPv4 and IPv6 address families, Linux kernel and several BSD variants (tested on FreeBSD, NetBSD and OpenBSD). BIRD consists of bird daemon and birdc interactive CLI client used for supervision. -In order to be able to use the BIRD Internet Routing Protocol, you must first install project on your machine. +In order to be able to use the BIRD Internet Routing Protocol, you must first install the project on your machine. ##### BIRD Package Install @@ -179,4 +179,64 @@ vi /usr/local/etc/bird.conf ``` The installation will place the bird directory inside */etc* where you will also find its config file. -In order to be able to do use the Bird Internet Routing Protocol, you must modify *bird.conf* due to the fact that the given configuration file is not configured beyond allowing the bird daemon to start, which means that nothing else will happen if you run it. Keeran Marquis has a very detailed example on [Configuring BGP using Bird on Ubuntu](https://blog.marquis.co/configuring-bgp-using-bird-on-ubuntu-14-04lts/) which can be used as a building block to implement your custom routing daemon. +In order to be able to do use the Bird Internet Routing Protocol, you must modify *bird.conf* due to the fact that the given configuration file is not configured beyond allowing the bird daemon to start, which means that nothing else will happen if you run it. Keeran Marquis has a very detailed example on [Configuring BGP using Bird on Ubuntu](https://blog.marquis.co/configuring-bgp-using-bird-on-ubuntu-14-04lts/) which can be used as a building block to implement your custom routing daemon. + + +#### FRRouting +FRRouting is a routing software package that provides TCP/IP based routing services with routing protocols support such as BGP, RIP, OSPF, IS-IS and more. FRR also supports special BGP Route Reflector and Route Server behavior. In addition to traditional IPv4 routing protocols, FRR also supports IPv6 routing protocols. With an SNMP daemon that supports the AgentX protocol, FRR provides routing protocol MIB read-only access (SNMP Support). + +FRR currently supports the following protocols: +* BGP +* OSPFv2 +* OSPFv3 +* RIPv1 +* RIPv2 +* RIPng +* IS-IS +* PIM-SM/MSDP +* LDP +* BFD +* Babel +* PBR +* OpenFabric +* EIGRP (alpha) +* NHRP (alpha) + +##### FRRouting Package Install +```shell +sudo apt install curl +curl -s https://deb.frrouting.org/frr/keys.asc | sudo apt-key add - +FRRVER="frr-stable" +echo deb https://deb.frrouting.org/frr $(lsb_release -s -c) $FRRVER | sudo tee -a /etc/apt/sources.list.d/frr.list +sudo apt update && sudo apt install frr frr-pythontools +``` + +##### FRRouting Source Code Install +Building FRR from source is the best way to ensure you have the latest features and bug fixes. Details for each supported platform, including dependency package listings, permissions, and other gotchas, are in the developer’s documentation. + +FRR’s source is available on the project [GitHub page](https://github.com/FRRouting/frr). +```shell +git clone https://github.com/FRRouting/frr.git +``` + +Change into your FRR source directory and issue: +```shell +./bootstrap.sh +``` +Then, choose the configuration options that you wish to use for the installation. You can find these options on FRR's [official webpage](http://docs.frrouting.org/en/latest/installation.html). Once you have chosen your configure options, run the configure script and pass the options you chose: +```shell +./configure \ + --prefix=/usr \ + --enable-exampledir=/usr/share/doc/frr/examples/ \ + --localstatedir=/var/run/frr \ + --sbindir=/usr/lib/frr \ + --sysconfdir=/etc/frr \ + --enable-pimd \ + --enable-watchfrr \ + ... +``` +After configuring the software, you are ready to build and install it in your system. +```shell +make && sudo make install +``` +If everything finishes successfully, FRR should be installed. From d724ee289bd3a1408822f0da5e64e51b8714e48c Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Wed, 12 Jun 2019 12:41:01 -0700 Subject: [PATCH 0107/1992] added dependency and usage of ethtool to disable rx/tx checksums, since they will never be valid from a veth. also lowered some noisy logging and removed other logging --- Makefile.am | 2 ++ daemon/core/constants.py.in | 1 + daemon/core/nodes/base.py | 1 + daemon/core/nodes/client.py | 6 +++--- daemon/core/utils.py | 2 -- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 6bf34253..bfa5313f 100644 --- a/Makefile.am +++ b/Makefile.am @@ -61,6 +61,7 @@ fpm -s dir -t rpm -n core \ -p core_$(PYTHON)_VERSION_ARCH.rpm \ -v $(PACKAGE_VERSION) \ --rpm-init scripts/core-daemon \ + -d "ethtool" \ -d "tcl" \ -d "tk" \ -d "procps-ng" \ @@ -84,6 +85,7 @@ fpm -s dir -t deb -n core \ -p core_$(PYTHON)_VERSION_ARCH.deb \ -v $(PACKAGE_VERSION) \ --deb-systemd scripts/core-daemon.service \ + -d "ethtool" \ -d "tcl" \ -d "tk" \ -d "libtk-img" \ diff --git a/daemon/core/constants.py.in b/daemon/core/constants.py.in index 0cb3750e..a84a375a 100644 --- a/daemon/core/constants.py.in +++ b/daemon/core/constants.py.in @@ -20,6 +20,7 @@ VCMD_BIN = which("vcmd") BRCTL_BIN = which("brctl") SYSCTL_BIN = which("sysctl") IP_BIN = which("ip") +ETHTOOL_BIN = which("ethtool") TC_BIN = which("tc") EBTABLES_BIN = which("ebtables") MOUNT_BIN = which("mount") diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 05b45b4c..555eda78 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -668,6 +668,7 @@ class CoreNode(CoreNodeBase): if self.up: utils.check_cmd([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)]) self.check_cmd([constants.IP_BIN, "link", "set", veth.name, "name", ifname]) + self.check_cmd([constants.ETHTOOL_BIN, "-K", ifname, "rx", "off", "tx", "off"]) veth.name = ifname diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index 8530804b..d2d6cc4d 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -73,7 +73,7 @@ class VnodeClient(object): # run command, return process when not waiting cmd = self._cmd_args() + args - logging.info("cmd wait(%s): %s", wait, cmd) + logging.debug("cmd wait(%s): %s", wait, cmd) p = Popen(cmd, stdout=PIPE, stderr=PIPE) if not wait: return 0 @@ -124,7 +124,7 @@ class VnodeClient(object): self._verify_connection() args = utils.split_args(args) cmd = self._cmd_args() + args - logging.info("popen: %s", cmd) + logging.debug("popen: %s", cmd) p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE) return p, p.stdin, p.stdout, p.stderr @@ -157,7 +157,7 @@ class VnodeClient(object): # run command, return process when not waiting args = utils.split_args(args) cmd = self._cmd_args() + args - logging.info("redircmd: %s", cmd) + logging.debug("redircmd: %s", cmd) p = Popen(cmd, stdin=infd, stdout=outfd, stderr=errfd) if not wait: diff --git a/daemon/core/utils.py b/daemon/core/utils.py index 5b18f230..ac76e693 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -148,9 +148,7 @@ def split_args(args): :return: shell-like syntax list :rtype: list """ - logging.info("split args: %s - %s", args, type(args)) if isinstance(args, basestring): - logging.info("splitting args") args = shlex.split(args) return args From 4f4c4c4f60194eff8032aa4df433f0093f3c8c2f Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Wed, 12 Jun 2019 16:46:53 -0700 Subject: [PATCH 0108/1992] updated jackson dependency in corefx --- corefx/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/corefx/pom.xml b/corefx/pom.xml index 149d4ac1..21839cc6 100644 --- a/corefx/pom.xml +++ b/corefx/pom.xml @@ -13,7 +13,7 @@ 1.8 1.8 2.1.1 - 2.9.6 + 2.9.9 1.20.0 @@ -153,4 +153,4 @@ - \ No newline at end of file + From 2a1f6388c51f6b61ffd26113fcbc6c89e2c925d9 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Thu, 13 Jun 2019 13:42:50 -0500 Subject: [PATCH 0109/1992] Added docker services documentation --- docs/services.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/services.md b/docs/services.md index f3300679..14092492 100644 --- a/docs/services.md +++ b/docs/services.md @@ -240,3 +240,40 @@ After configuring the software, you are ready to build and install it in your sy make && sudo make install ``` If everything finishes successfully, FRR should be installed. + +#### Docker +Docker service allows running docker containers within CORE nodes. +The running of Docker within a CORE node allows for additional extensibility to +the CORE services. This allows network applications and protocols to be easily +packaged and run on any node. + +This service will add a new group to the services list. This will have a service called Docker which will just start the docker service within the node but not run anything. It will also scan all docker images on the host machine. If any are tagged with 'core' then they will be added as a service to the Docker group. The image will then be auto run if that service is selected. + +This requires a recent version of Docker. This was tested using a PPA on Ubuntu with version 1.2.0. The version in the standard Ubuntu repo is to old for this purpose (we need --net host). + +##### Docker Installation +To use Docker services, you must first install the Docker python image. This is used to interface with Docker from the python service. + +```shell +sudo apt-get install docker.io +sudo apt-get install python-pip +pip install docker-py +``` +Once everything runs successfully, a Docker group under services will appear. An example use case is to pull an image from [Docker](https://hub.docker.com/). A test image has been uploaded for this purpose: +```shell +sudo docker pull stuartmarsden/multicastping +``` +This downloads an image which is based on Ubuntu 14.04 with python and twisted. It runs a simple program that sends a multicast ping and listens and records any it receives. In order for this to appear as a docker service it must be tagged with core. +Find out the id by running 'sudo docker images'. You should see all installed images and the one you want looks like this: +```shell +stuartmarsden/multicastping latest 4833487e66d2 20 hours +ago 487 MB +``` +The id will be different on your machine so use it in the following command: +```shell +sudo docker tag 4833487e66d2 stuartmarsden/multicastping:core +``` +This image will be listed in the services after we restart the core-daemon: +```shell +sudo service core-daemon restart +``` From a715a77c0d3e0d63c3633cbdef44fff22a720053 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Thu, 13 Jun 2019 14:05:13 -0500 Subject: [PATCH 0110/1992] Fixed bird config path --- docs/services.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/services.md b/docs/services.md index 14092492..6f1f4940 100644 --- a/docs/services.md +++ b/docs/services.md @@ -175,7 +175,7 @@ You can download BIRD source code from it's [official repository.](https://gitla make su make install -vi /usr/local/etc/bird.conf +vi /etc/bird/bird.conf ``` The installation will place the bird directory inside */etc* where you will also find its config file. From 56993ec44f6c2ba334ee20e50f4b051574f3299e Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Fri, 14 Jun 2019 13:43:19 -0700 Subject: [PATCH 0111/1992] corefx removed apache dependency no longer needed, added ipaddress maven dependency for helping generate and deal with node addresses, updated some logic when linking nodes --- corefx/pom.xml | 10 +- .../com/core/client/grpc/CoreGrpcClient.java | 27 ++-- .../java/com/core/data/CoreInterface.java | 10 +- .../java/com/core/graph/CoreAddresses.java | 92 +++++++++---- .../java/com/core/graph/NetworkGraph.java | 129 ++++++++++++------ .../main/java/com/core/ui/LinkDetails.java | 13 +- .../main/java/com/core/ui/NodeDetails.java | 13 +- 7 files changed, 197 insertions(+), 97 deletions(-) diff --git a/corefx/pom.xml b/corefx/pom.xml index 21839cc6..f5ce8b17 100644 --- a/corefx/pom.xml +++ b/corefx/pom.xml @@ -84,11 +84,6 @@ 1.18.0 provided - - commons-net - commons-net - 3.6 - com.jfoenix jfoenix @@ -114,6 +109,11 @@ guava 20.0 + + com.github.seancfoley + ipaddress + 5.0.2 + diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index 972c30be..dfe46f19 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -6,6 +6,8 @@ import com.core.client.rest.ServiceFile; import com.core.client.rest.WlanConfig; import com.core.data.*; import com.core.ui.dialogs.MobilityPlayerDialog; +import inet.ipaddr.IPAddress; +import inet.ipaddr.IPAddressString; import io.grpc.Context; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; @@ -123,6 +125,9 @@ public class CoreGrpcClient implements ICoreClient { private CoreProto.Interface interfaceToProto(CoreInterface coreInterface) { CoreProto.Interface.Builder builder = CoreProto.Interface.newBuilder(); + if (coreInterface.getId() != null) { + builder.setId(coreInterface.getId()); + } if (coreInterface.getName() != null) { builder.setName(coreInterface.getName()); } @@ -130,16 +135,16 @@ public class CoreGrpcClient implements ICoreClient { builder.setMac(coreInterface.getMac()); } if (coreInterface.getIp4() != null) { - builder.setIp4(coreInterface.getIp4()); + builder.setIp4(coreInterface.getIp4().toAddressString().getHostAddress().toString()); } - if (coreInterface.getIp4Mask() != null) { - builder.setIp4Mask(coreInterface.getIp4Mask()); + if (coreInterface.getIp4() != null) { + builder.setIp4Mask(coreInterface.getIp4().getPrefixLength()); } if (coreInterface.getIp6() != null) { - builder.setIp6(coreInterface.getIp6()); + builder.setIp6(coreInterface.getIp6().toAddressString().getHostAddress().toString()); } - if (coreInterface.getIp6Mask() != null) { - builder.setIp6Mask(Integer.parseInt(coreInterface.getIp6Mask())); + if (coreInterface.getIp6() != null) { + builder.setIp6Mask(coreInterface.getIp6().getPrefixLength()); } return builder.build(); } @@ -170,10 +175,12 @@ public class CoreGrpcClient implements ICoreClient { coreInterface.setId(protoInterface.getId()); coreInterface.setName(protoInterface.getName()); coreInterface.setMac(protoInterface.getMac()); - coreInterface.setIp4(protoInterface.getIp4()); - coreInterface.setIp4Mask(protoInterface.getIp4Mask()); - coreInterface.setIp6(protoInterface.getIp6()); - coreInterface.setIp6Mask(Integer.toString(protoInterface.getIp6Mask())); + String ip4String = String.format("%s/%s", protoInterface.getIp4(), protoInterface.getIp4Mask()); + IPAddress ip4 = new IPAddressString(ip4String).getAddress(); + coreInterface.setIp4(ip4); + String ip6String = String.format("%s/%s", protoInterface.getIp6(), protoInterface.getIp6Mask()); + IPAddress ip6 = new IPAddressString(ip6String).getAddress(); + coreInterface.setIp6(ip6); return coreInterface; } diff --git a/corefx/src/main/java/com/core/data/CoreInterface.java b/corefx/src/main/java/com/core/data/CoreInterface.java index f159f10e..200aeabd 100644 --- a/corefx/src/main/java/com/core/data/CoreInterface.java +++ b/corefx/src/main/java/com/core/data/CoreInterface.java @@ -1,6 +1,6 @@ package com.core.data; -import com.fasterxml.jackson.annotation.JsonProperty; +import inet.ipaddr.IPAddress; import lombok.Data; import lombok.NoArgsConstructor; @@ -10,10 +10,6 @@ public class CoreInterface { private Integer id; private String name; private String mac; - private String ip4; - @JsonProperty("ip4mask") - private Integer ip4Mask; - private String ip6; - @JsonProperty("ip6mask") - private String ip6Mask; + private IPAddress ip4; + private IPAddress ip6; } diff --git a/corefx/src/main/java/com/core/graph/CoreAddresses.java b/corefx/src/main/java/com/core/graph/CoreAddresses.java index 5db724b6..554145da 100644 --- a/corefx/src/main/java/com/core/graph/CoreAddresses.java +++ b/corefx/src/main/java/com/core/graph/CoreAddresses.java @@ -1,41 +1,87 @@ package com.core.graph; import com.core.data.CoreInterface; +import inet.ipaddr.IPAddress; +import inet.ipaddr.IPAddressString; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.Collection; +import java.util.Comparator; +import java.util.Set; public class CoreAddresses { private static final Logger logger = LogManager.getLogger(); - public static final int IP4_MASK = 24; - public static final int IP4_INDEX = (IP4_MASK / 8) - 1; + private IPAddress defaultSubnet = new IPAddressString("10.0.0.0/24").getAddress(); - private String ip4Base; - - public CoreAddresses(String ip4Base) { - this.ip4Base = ip4Base; + private IPAddress getMaxAddress(Set interfaces) { + return interfaces.stream() + .map(CoreInterface::getIp4) + .max(Comparator.comparingInt(x -> x.toIPv4().intValue())) + .orElseGet(() -> defaultSubnet); } - public int getSubnet(Collection nodeOneInterfaces, Collection nodeTwoInterfaces) { - int subOne = getMaxSubnet(nodeOneInterfaces); - int subTwo = getMaxSubnet(nodeTwoInterfaces); - logger.info("next subnet: {} - {}", subOne, subTwo); - return Math.max(subOne, subTwo) + 1; - } + public IPAddress getSubnet(Set nodeOneInterfaces, Set nodeTwoInterfaces, + boolean nodeOneIsNetwork, boolean nodeTwoIsNetwork) { + IPAddress nodeOneMax = getMaxAddress(nodeOneInterfaces); + IPAddress nodeTwoMax = getMaxAddress(nodeTwoInterfaces); - private int getMaxSubnet(Collection coreInterfaces) { - int sub = 0; - for (CoreInterface coreInterface : coreInterfaces) { - String[] values = coreInterface.getIp4().split("\\."); - int currentSub = Integer.parseInt(values[IP4_INDEX]); - logger.info("checking {} value {}", coreInterface.getIp4(), currentSub); - sub = Math.max(currentSub, sub); + logger.info("node one max: {}, node two max: {}", nodeOneMax, nodeTwoMax); + logger.info("max one compared to two: {} - {}", + nodeOneMax.toIPv4().intValue(), nodeTwoMax.toIPv4().intValue()); + boolean shouldBump; + boolean isDefault; + IPAddress subnet; + + if (nodeOneMax.toIPv4().intValue() > nodeTwoMax.toIPv4().intValue()) { + subnet = nodeOneMax; + isDefault = nodeOneMax == defaultSubnet; + shouldBump = !nodeOneIsNetwork && !isDefault; + } else { + subnet = nodeTwoMax; + isDefault = nodeTwoMax == defaultSubnet; + shouldBump = !nodeTwoIsNetwork && !isDefault; } - return sub; + + logger.info("found max address: {} - {}", isDefault, subnet); + if (!isDefault) { + subnet = subnet.toPrefixBlock(); + } + + if (shouldBump) { + logger.info("incrementing subnet for host to host"); + subnet = subnet.incrementBoundary(1); + } + + logger.info("found subnet: {}", subnet); + return subnet; } - public String getIp4Address(int sub, int id) { - return String.format("%s.%s.%s", ip4Base, sub, id); + public static void main(String... args) { + IPAddress addresses = new IPAddressString("10.0.100.1/24").getAddress(); + IPAddress addresses3 = new IPAddressString("10.0.0.2/24").getAddress(); + IPAddress addresses1 = new IPAddressString("10.0.1.0/24").getAddress(); + IPAddress addresses2 = new IPAddressString("10.0.2.0/24").getAddress(); + System.out.println(String.format("compare to greater: %s", addresses.compareTo(addresses3))); + System.out.println(String.format("compare to greater: %s", addresses3.compareTo(addresses1))); + System.out.println(String.format("compare to greater: %s", addresses.toInetAddress())); + + IPAddress address = addresses.increment(1); + IPAddress address1 = addresses1.increment(1); + IPAddress address2 = addresses2.increment(1); + System.out.println(String.format("divisions: %s", address.toPrefixBlock())); + System.out.println(String.format("divisions: %s", address1.toPrefixBlock())); + System.out.println(String.format("divisions: %s", address2.toPrefixBlock())); + System.out.println(String.format("compares: %s", address1.compareTo(address2))); + System.out.println(String.format("compares: %s", address1.compareTo(address))); + System.out.println(String.format("compares: %s", address2.getSection(2, 3))); + System.out.println(String.format("compares: %s", address2.getSegment(2))); + System.out.println(String.format("address: %s", address2)); + + IPAddress prefixBlock = address1.toPrefixBlock(); + System.out.println(String.format("prefix block: %s", prefixBlock)); + IPAddress update = new IPAddressString("0.0.1.0").getAddress(); + IPAddress nextAddress = prefixBlock.incrementBoundary(1); +// nextAddress.setPrefixLength(prefixBlock.getPrefixLength(), true); + System.out.println(String.format("prefix block update: %s", nextAddress)); } } diff --git a/corefx/src/main/java/com/core/graph/NetworkGraph.java b/corefx/src/main/java/com/core/graph/NetworkGraph.java index 7d55cdb2..2d9ec7af 100644 --- a/corefx/src/main/java/com/core/graph/NetworkGraph.java +++ b/corefx/src/main/java/com/core/graph/NetworkGraph.java @@ -20,9 +20,9 @@ import edu.uci.ics.jung.visualization.control.GraphMouseListener; import edu.uci.ics.jung.visualization.control.ModalGraphMouse; import edu.uci.ics.jung.visualization.decorators.EdgeShape; import edu.uci.ics.jung.visualization.renderers.Renderer; +import inet.ipaddr.IPAddress; import javafx.application.Platform; import lombok.Data; -import org.apache.commons.net.util.SubnetUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -32,7 +32,6 @@ import java.awt.event.MouseEvent; import java.awt.geom.Ellipse2D; import java.io.IOException; import java.util.*; -import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -50,8 +49,7 @@ public class NetworkGraph { private EditingModalGraphMouse graphMouse; private AnnotationControls annotationControls; - private SubnetUtils subnetUtils = new SubnetUtils("10.0.0.0/24"); - private CoreAddresses coreAddresses = new CoreAddresses("10.0"); + private CoreAddresses coreAddresses = new CoreAddresses(); private NodeType nodeType; private Map nodeMap = new ConcurrentHashMap<>(); private int vertexId = 1; @@ -287,40 +285,91 @@ public class NetworkGraph { private void handleEdgeAdded(GraphEvent.Edge edgeEvent) { CoreLink link = edgeEvent.getEdge(); - if (!link.isLoaded()) { - Pair endpoints = graph.getEndpoints(link); - - CoreNode nodeOne = endpoints.getFirst(); - CoreNode nodeTwo = endpoints.getSecond(); - - // create interfaces for nodes - int sub = coreAddresses.getSubnet( - getInterfaces(nodeOne), - getInterfaces(nodeTwo) - ); - - link.setNodeOne(nodeOne.getId()); - if (isNode(nodeOne)) { - int interfaceOneId = nextInterfaceId(nodeOne); - CoreInterface interfaceOne = createInterface(nodeOne, sub, interfaceOneId); - link.setInterfaceOne(interfaceOne); - } - - link.setNodeTwo(nodeTwo.getId()); - if (isNode(nodeTwo)) { - int interfaceTwoId = nextInterfaceId(nodeTwo); - CoreInterface interfaceTwo = createInterface(nodeTwo, sub, interfaceTwoId); - link.setInterfaceTwo(interfaceTwo); - } - - boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo); - link.setVisible(isVisible); - - logger.info("adding user created edge: {}", link); + if (link.isLoaded()) { + return; } + + Pair endpoints = graph.getEndpoints(link); + + CoreNode nodeOne = endpoints.getFirst(); + CoreNode nodeTwo = endpoints.getSecond(); + + // check if a node being linked is a network node + Set nodeOneInterfaces; + boolean nodeOneIsNetwork = false; + if (isNode(nodeOne)) { + nodeOneInterfaces = getNodeInterfaces(nodeOne); + } else { + nodeOneInterfaces = getNetworkInterfaces(nodeOne, new HashSet<>()); + nodeOneIsNetwork = true; + } + + Set nodeTwoInterfaces; + boolean nodeTwoIsNetwork = false; + if (isNode(nodeTwo)) { + nodeTwoInterfaces = getNodeInterfaces(nodeTwo); + } else { + nodeTwoInterfaces = getNetworkInterfaces(nodeTwo, new HashSet<>()); + nodeTwoIsNetwork = true; + } + + // create interfaces for nodes + IPAddress subnet = coreAddresses.getSubnet(nodeOneInterfaces, nodeTwoInterfaces, + nodeOneIsNetwork, nodeTwoIsNetwork); + + link.setNodeOne(nodeOne.getId()); + if (isNode(nodeOne)) { + int interfaceOneId = nextInterfaceId(nodeOne); + CoreInterface interfaceOne = createInterface(nodeOne, interfaceOneId, subnet); + link.setInterfaceOne(interfaceOne); + } + + link.setNodeTwo(nodeTwo.getId()); + if (isNode(nodeTwo)) { + int interfaceTwoId = nextInterfaceId(nodeTwo); + CoreInterface interfaceTwo = createInterface(nodeTwo, interfaceTwoId, subnet); + link.setInterfaceTwo(interfaceTwo); + } + + boolean isVisible = !checkForWirelessNode(nodeOne, nodeTwo); + link.setVisible(isVisible); + logger.info("adding user created edge: {}", link); } - public List getInterfaces(CoreNode node) { + public Set getNetworkInterfaces(CoreNode node, Set visited) { + Set interfaces = new HashSet<>(); + if (visited.contains(node)) { + return interfaces; + } + visited.add(node); + + logger.info("checking network node links: {}", node); + for (CoreLink link : graph.getIncidentEdges(node)) { + logger.info("checking link: {}", link); + if (link.getNodeOne() == null && link.getNodeTwo() == null) { + continue; + } + + // ignore oneself + CoreNode currentNode = getVertex(link.getNodeOne()); + CoreInterface currentInterface = link.getInterfaceOne(); + if (node.getId().equals(link.getNodeOne())) { + currentNode = getVertex(link.getNodeTwo()); + currentInterface = link.getInterfaceTwo(); + } + + if (isNode(currentNode)) { + interfaces.add(currentInterface); + } else { + Set nextInterfaces = getNetworkInterfaces(currentNode, visited); + interfaces.addAll(nextInterfaces); + } + } + + return interfaces; + } + + public Set getNodeInterfaces(CoreNode node) { return graph.getIncidentEdges(node).stream() .map(link -> { if (node.getId().equals(link.getNodeOne())) { @@ -330,7 +379,7 @@ public class NetworkGraph { } }) .filter(Objects::nonNull) - .collect(Collectors.toList()); + .collect(Collectors.toSet()); } private int nextInterfaceId(CoreNode node) { @@ -360,13 +409,13 @@ public class NetworkGraph { return node.getType() == NodeType.DEFAULT; } - private CoreInterface createInterface(CoreNode node, int sub, int interfaceId) { + private CoreInterface createInterface(CoreNode node, int interfaceId, IPAddress subnet) { CoreInterface coreInterface = new CoreInterface(); coreInterface.setId(interfaceId); coreInterface.setName(String.format("eth%s", interfaceId)); - String nodeOneIp4 = coreAddresses.getIp4Address(sub, node.getId()); - coreInterface.setIp4(nodeOneIp4); - coreInterface.setIp4Mask(CoreAddresses.IP4_MASK); + IPAddress address = subnet.increment(node.getId()); + coreInterface.setIp4(address); + coreInterface.setIp6(address.toIPv6()); return coreInterface; } diff --git a/corefx/src/main/java/com/core/ui/LinkDetails.java b/corefx/src/main/java/com/core/ui/LinkDetails.java index 5a0c78d0..b01c5e40 100644 --- a/corefx/src/main/java/com/core/ui/LinkDetails.java +++ b/corefx/src/main/java/com/core/ui/LinkDetails.java @@ -11,6 +11,7 @@ import com.core.ui.textfields.DoubleFilter; import com.core.utils.FxmlUtils; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXTextField; +import inet.ipaddr.IPAddress; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; @@ -129,8 +130,8 @@ public class LinkDetails extends ScrollPane { if (coreInterface.getMac() != null) { addRow("MAC", coreInterface.getMac(), true); } - addIp4Address(coreInterface.getIp4(), coreInterface.getIp4Mask()); - addIp6Address(coreInterface.getIp6(), coreInterface.getIp6Mask()); + addIp4Address(coreInterface.getIp4()); + addIp6Address(coreInterface.getIp6()); } private void addRow(String labelText, String value, boolean disabled) { @@ -155,18 +156,18 @@ public class LinkDetails extends ScrollPane { return textField; } - private void addIp4Address(String ip, Integer mask) { + private void addIp4Address(IPAddress ip) { if (ip == null) { return; } - addRow("IP4", String.format("%s/%s", ip, mask), true); + addRow("IP4", ip.toString(), true); } - private void addIp6Address(String ip, String mask) { + private void addIp6Address(IPAddress ip) { if (ip == null) { return; } - addRow("IP6", String.format("%s/%s", ip, mask), true); + addRow("IP6", ip.toString(), true); } private void clear() { diff --git a/corefx/src/main/java/com/core/ui/NodeDetails.java b/corefx/src/main/java/com/core/ui/NodeDetails.java index c1e9751c..0fca8a95 100644 --- a/corefx/src/main/java/com/core/ui/NodeDetails.java +++ b/corefx/src/main/java/com/core/ui/NodeDetails.java @@ -10,6 +10,7 @@ import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXListView; import com.jfoenix.controls.JFXScrollPane; import com.jfoenix.controls.JFXTextField; +import inet.ipaddr.IPAddress; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; @@ -147,8 +148,8 @@ public class NodeDetails extends ScrollPane { if (coreInterface.getMac() != null) { addRow("MAC", coreInterface.getMac(), true); } - addIp4Address(coreInterface.getIp4(), coreInterface.getIp4Mask()); - addIp6Address(coreInterface.getIp6(), coreInterface.getIp6Mask()); + addIp4Address(coreInterface.getIp4()); + addIp6Address(coreInterface.getIp6()); } private void addRow(String labelText, String value, boolean disabled) { @@ -158,18 +159,18 @@ public class NodeDetails extends ScrollPane { gridPane.addRow(index++, label, textField); } - private void addIp4Address(String ip, Integer mask) { + private void addIp4Address(IPAddress ip) { if (ip == null) { return; } - addRow("IP4", String.format("%s/%s", ip, mask), true); + addRow("IP4", ip.toString(), true); } - private void addIp6Address(String ip, String mask) { + private void addIp6Address(IPAddress ip) { if (ip == null) { return; } - addRow("IP6", String.format("%s/%s", ip, mask), true); + addRow("IP6", ip.toString(), true); } private void clear() { From 541e719c075c7737c19e917c6452241394876c05 Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Fri, 14 Jun 2019 13:59:18 -0700 Subject: [PATCH 0112/1992] corefx moved some data objects into a common package, removed libraries and code for using previous experimental rest interface --- corefx/pom.xml | 15 +- .../java/com/core/client/ICoreClient.java | 4 +- .../com/core/client/grpc/CoreGrpcClient.java | 2 - .../com/core/client/rest/CoreRestClient.java | 430 ------------------ .../java/com/core/client/rest/GetConfig.java | 12 - .../core/client/rest/GetDefaultServices.java | 16 - .../com/core/client/rest/GetEmaneModels.java | 11 - .../java/com/core/client/rest/GetHooks.java | 12 - .../core/client/rest/GetMobilityConfigs.java | 12 - .../com/core/client/rest/GetServices.java | 13 - .../com/core/client/rest/GetSessions.java | 14 - .../java/com/core/client/rest/SetConfig.java | 16 - .../com/core/client/rest/SetEmaneConfig.java | 13 - .../core/client/rest/SetEmaneModelConfig.java | 14 - .../{client/rest => data}/ServiceFile.java | 2 +- .../{client/rest => data}/WlanConfig.java | 2 +- .../com/core/ui/dialogs/NodeWlanDialog.java | 2 +- .../com/core/ui/dialogs/ServiceDialog.java | 2 +- .../main/java/com/core/utils/WebUtils.java | 174 ------- .../com/core/websocket/CoreWebSocket.java | 130 ------ 20 files changed, 9 insertions(+), 887 deletions(-) delete mode 100644 corefx/src/main/java/com/core/client/rest/CoreRestClient.java delete mode 100644 corefx/src/main/java/com/core/client/rest/GetConfig.java delete mode 100644 corefx/src/main/java/com/core/client/rest/GetDefaultServices.java delete mode 100644 corefx/src/main/java/com/core/client/rest/GetEmaneModels.java delete mode 100644 corefx/src/main/java/com/core/client/rest/GetHooks.java delete mode 100644 corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java delete mode 100644 corefx/src/main/java/com/core/client/rest/GetServices.java delete mode 100644 corefx/src/main/java/com/core/client/rest/GetSessions.java delete mode 100644 corefx/src/main/java/com/core/client/rest/SetConfig.java delete mode 100644 corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java delete mode 100644 corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java rename corefx/src/main/java/com/core/{client/rest => data}/ServiceFile.java (87%) rename corefx/src/main/java/com/core/{client/rest => data}/WlanConfig.java (86%) delete mode 100644 corefx/src/main/java/com/core/utils/WebUtils.java delete mode 100644 corefx/src/main/java/com/core/websocket/CoreWebSocket.java diff --git a/corefx/pom.xml b/corefx/pom.xml index f5ce8b17..af957aa2 100644 --- a/corefx/pom.xml +++ b/corefx/pom.xml @@ -15,6 +15,7 @@ 2.1.1 2.9.9 1.20.0 + 2.9.0 @@ -58,25 +59,15 @@ jackson-annotations ${jackson.version} - - com.squareup.okhttp3 - okhttp - 3.11.0 - org.apache.logging.log4j log4j-api - 2.9.0 + ${log4j.version} org.apache.logging.log4j log4j-core - 2.9.0 - - - io.socket - socket.io-client - 0.8.3 + ${log4j.version} org.projectlombok diff --git a/corefx/src/main/java/com/core/client/ICoreClient.java b/corefx/src/main/java/com/core/client/ICoreClient.java index 7e0350aa..d5c5c8fe 100644 --- a/corefx/src/main/java/com/core/client/ICoreClient.java +++ b/corefx/src/main/java/com/core/client/ICoreClient.java @@ -1,8 +1,8 @@ package com.core.client; import com.core.Controller; -import com.core.client.rest.ServiceFile; -import com.core.client.rest.WlanConfig; +import com.core.data.ServiceFile; +import com.core.data.WlanConfig; import com.core.data.*; import java.io.File; diff --git a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java index dfe46f19..2c626b73 100644 --- a/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java +++ b/corefx/src/main/java/com/core/client/grpc/CoreGrpcClient.java @@ -2,8 +2,6 @@ package com.core.client.grpc; import com.core.Controller; import com.core.client.ICoreClient; -import com.core.client.rest.ServiceFile; -import com.core.client.rest.WlanConfig; import com.core.data.*; import com.core.ui.dialogs.MobilityPlayerDialog; import inet.ipaddr.IPAddress; diff --git a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java b/corefx/src/main/java/com/core/client/rest/CoreRestClient.java deleted file mode 100644 index cecc5bf9..00000000 --- a/corefx/src/main/java/com/core/client/rest/CoreRestClient.java +++ /dev/null @@ -1,430 +0,0 @@ -package com.core.client.rest; - -import com.core.Controller; -import com.core.client.ICoreClient; -import com.core.data.*; -import com.core.utils.WebUtils; -import com.core.websocket.CoreWebSocket; -import lombok.Data; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.File; -import java.io.IOException; -import java.net.URISyntaxException; -import java.util.*; - -@Data -public class CoreRestClient implements ICoreClient { - private static final Logger logger = LogManager.getLogger(); - private String address; - private int port; - private Integer sessionId; - private SessionState sessionState; - private CoreWebSocket coreWebSocket; - - @Override - public void setConnection(String address, int port) { - this.address = address; - this.port = port; - } - - @Override - public boolean isLocalConnection() { - return address.equals("127.0.0.1") || address.equals("localhost"); - } - - @Override - public Integer currentSession() { - return sessionId; - } - - @Override - public void updateState(SessionState state) { - sessionState = state; - } - - @Override - public void updateSession(Integer sessionId) { - this.sessionId = sessionId; - } - - private String getUrl(String path) { - return String.format("http://%s:%s/%s", address, port, path); - } - - @Override - public SessionOverview createSession() throws IOException { - String url = getUrl("sessions"); - return WebUtils.post(url, SessionOverview.class); - } - - @Override - public boolean deleteSession(Integer sessionId) throws IOException { - String path = String.format("sessions/%s", sessionId); - String url = getUrl(path); - return WebUtils.delete(url); - } - - public Map> getServices() throws IOException { - String url = getUrl("services"); - GetServices getServices = WebUtils.getJson(url, GetServices.class); - return getServices.getGroups(); - } - - @Override - public Session getSession(Integer sessionId) throws IOException { - String path = String.format("sessions/%s", sessionId); - String url = getUrl(path); - return WebUtils.getJson(url, Session.class); - } - - @Override - public List getSessions() throws IOException { - String url = getUrl("sessions"); - GetSessions getSessions = WebUtils.getJson(url, GetSessions.class); - return getSessions.getSessions(); - } - - @Override - public boolean start(Collection nodes, Collection links, List hooks) throws IOException { - boolean result = setState(SessionState.DEFINITION); - if (!result) { - return false; - } - - result = setState(SessionState.CONFIGURATION); - if (!result) { - return false; - } - - for (Hook hook : hooks) { - if (!createHook(hook)) { - return false; - } - } - - for (CoreNode node : nodes) { - // must pre-configure wlan nodes, if not already - if (node.getNodeType().getValue() == NodeType.WLAN) { - WlanConfig config = getWlanConfig(node); - setWlanConfig(node, config); - } - - if (!createNode(node)) { - return false; - } - } - - for (CoreLink link : links) { - if (!createLink(link)) { - return false; - } - } - - return setState(SessionState.INSTANTIATION); - } - - @Override - public boolean stop() throws IOException { - return setState(SessionState.SHUTDOWN); - } - - @Override - public boolean setState(SessionState state) throws IOException { - String url = getUrl(String.format("sessions/%s/state", sessionId)); - Map data = new HashMap<>(); - data.put("state", state.getValue()); - boolean result = WebUtils.putJson(url, data); - - if (result) { - sessionState = state; - } - return result; - } - - private boolean uploadFile(File file) throws IOException { - String url = getUrl("upload"); - return WebUtils.postFile(url, file); - } - - @Override - public boolean startThroughput(Controller controller) throws IOException { - String url = getUrl("throughput/start"); - return WebUtils.putJson(url); - } - - @Override - public boolean stopThroughput() throws IOException { - String url = getUrl("throughput/stop"); - return WebUtils.putJson(url); - } - - @Override - public Map> getDefaultServices() throws IOException { - String url = getUrl(String.format("sessions/%s/services/default", sessionId)); - GetDefaultServices getDefaultServices = WebUtils.getJson(url, GetDefaultServices.class); - return getDefaultServices.getDefaults(); - } - - @Override - public boolean setDefaultServices(Map> defaults) throws IOException { - String url = getUrl(String.format("sessions/%s/services/default", sessionId)); - return WebUtils.postJson(url, defaults); - } - - @Override - public CoreService getService(CoreNode node, String serviceName) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName)); - return WebUtils.getJson(url, CoreService.class); - } - - @Override - public boolean setService(CoreNode node, String serviceName, CoreService service) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s", sessionId, node.getId(), serviceName)); - return WebUtils.putJson(url, service); - } - - @Override - public String getServiceFile(CoreNode node, String serviceName, String fileName) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(), - serviceName)); - Map args = new HashMap<>(); - args.put("file", fileName); - return WebUtils.getJson(url, String.class, args); - } - - @Override - public boolean startService(CoreNode node, String serviceName) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/start", sessionId, node.getId(), - serviceName)); - return WebUtils.putJson(url); - } - - @Override - public boolean stopService(CoreNode node, String serviceName) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/stop", sessionId, node.getId(), - serviceName)); - return WebUtils.putJson(url); - } - - @Override - public boolean restartService(CoreNode node, String serviceName) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/restart", sessionId, node.getId(), - serviceName)); - return WebUtils.putJson(url); - } - - @Override - public boolean validateService(CoreNode node, String serviceName) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/validate", sessionId, node.getId(), - serviceName)); - return WebUtils.putJson(url); - } - - @Override - public boolean setServiceFile(CoreNode node, String serviceName, ServiceFile serviceFile) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/services/%s/file", sessionId, node.getId(), - serviceName)); - return WebUtils.putJson(url, serviceFile); - } - - @Override - public List getEmaneModels() throws IOException { - String url = getUrl(String.format("sessions/%s/emane/models", sessionId)); - GetEmaneModels getEmaneModels = WebUtils.getJson(url, GetEmaneModels.class); - return getEmaneModels.getModels(); - } - - @Override - public List getEmaneModelConfig(Integer id, String model) throws IOException { - String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId)); - Map args = new HashMap<>(); - args.put("node", id.toString()); - args.put("name", model); - GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args); - return getConfig.getGroups(); - } - - @Override - public List getEmaneConfig(CoreNode node) throws IOException { - String url = getUrl(String.format("sessions/%s/emane/config", sessionId)); - Map args = new HashMap<>(); - args.put("node", node.getId().toString()); - GetConfig getConfig = WebUtils.getJson(url, GetConfig.class, args); - return getConfig.getGroups(); - } - - @Override - public boolean setEmaneConfig(CoreNode node, List options) throws IOException { - String url = getUrl(String.format("sessions/%s/emane/config", sessionId)); - SetEmaneConfig setEmaneConfig = new SetEmaneConfig(); - setEmaneConfig.setNode(node.getId()); - setEmaneConfig.setValues(options); - return WebUtils.putJson(url, setEmaneConfig); - } - - @Override - public boolean setEmaneModelConfig(Integer id, String model, List options) throws IOException { - String url = getUrl(String.format("sessions/%s/emane/model/config", sessionId)); - SetEmaneModelConfig setEmaneModelConfig = new SetEmaneModelConfig(); - setEmaneModelConfig.setNode(id); - setEmaneModelConfig.setName(model); - setEmaneModelConfig.setValues(options); - return WebUtils.putJson(url, setEmaneModelConfig); - } - - @Override - public boolean isRunning() { - return sessionState == SessionState.RUNTIME; - } - - @Override - public void saveSession(File file) throws IOException { - String path = String.format("sessions/%s/xml", sessionId); - String url = getUrl(path); - WebUtils.getFile(url, file); - } - - @Override - public SessionOverview openSession(File file) throws IOException { - String url = getUrl("sessions/xml"); - return WebUtils.postFile(url, file, SessionOverview.class); - } - - @Override - public List getSessionConfig() throws IOException { - String url = getUrl(String.format("sessions/%s/options", sessionId)); - GetConfig getConfig = WebUtils.getJson(url, GetConfig.class); - return getConfig.getGroups(); - } - - @Override - public boolean setSessionConfig(List configOptions) throws IOException { - String url = getUrl(String.format("sessions/%s/options", sessionId)); - SetConfig setConfig = new SetConfig(configOptions); - return WebUtils.putJson(url, setConfig); - } - - @Override - public LocationConfig getLocationConfig() throws IOException { - String url = getUrl(String.format("sessions/%s/location", sessionId)); - return WebUtils.getJson(url, LocationConfig.class); - } - - @Override - public boolean setLocationConfig(LocationConfig config) throws IOException { - String url = getUrl(String.format("sessions/%s/location", sessionId)); - return WebUtils.putJson(url, config); - } - - @Override - public String nodeCommand(CoreNode node, String command) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/command", sessionId, node.getId())); - return WebUtils.putJson(url, command, String.class); - } - - @Override - public boolean createNode(CoreNode node) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes", sessionId)); - return WebUtils.postJson(url, node); - } - - - @Override - public boolean editNode(CoreNode node) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s", sessionId, node.getId())); - return WebUtils.putJson(url, node); - } - - @Override - public boolean deleteNode(CoreNode node) throws IOException { - String url = getUrl(String.format("/sessions/%s/nodes/%s", sessionId, node.getId())); - return WebUtils.delete(url); - } - - @Override - public boolean createLink(CoreLink link) throws IOException { - String url = getUrl(String.format("sessions/%s/links", sessionId)); - return WebUtils.postJson(url, link); - } - - @Override - public boolean editLink(CoreLink link) throws IOException { - String url = getUrl(String.format("sessions/%s/links", sessionId)); - return WebUtils.putJson(url, link); - } - - @Override - public boolean createHook(Hook hook) throws IOException { - String url = getUrl(String.format("sessions/%s/hooks", sessionId)); - return WebUtils.postJson(url, hook); - } - - @Override - public List getHooks() throws IOException { - String url = getUrl(String.format("sessions/%s/hooks", sessionId)); - GetHooks getHooks = WebUtils.getJson(url, GetHooks.class); - return getHooks.getHooks(); - } - - @Override - public WlanConfig getWlanConfig(CoreNode node) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId())); - return WebUtils.getJson(url, WlanConfig.class); - } - - @Override - public boolean setWlanConfig(CoreNode node, WlanConfig config) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/wlan", sessionId, node.getId())); - return WebUtils.putJson(url, config); - } - - @Override - public String getTerminalCommand(CoreNode node) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/terminal", sessionId, node.getId())); - return WebUtils.getJson(url, String.class); - } - - @Override - public boolean setMobilityConfig(CoreNode node, MobilityConfig config) throws IOException { - boolean uploaded = uploadFile(config.getScriptFile()); - if (!uploaded) { - throw new IOException("failed to upload mobility script"); - } - - String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId())); - config.setFile(config.getScriptFile().getName()); - return WebUtils.postJson(url, config); - } - - @Override - public Map getMobilityConfigs() throws IOException { - String url = getUrl(String.format("sessions/%s/mobility/configs", sessionId)); - GetMobilityConfigs getMobilityConfigs = WebUtils.getJson(url, GetMobilityConfigs.class); - return getMobilityConfigs.getConfigurations(); - } - - @Override - public MobilityConfig getMobilityConfig(CoreNode node) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/mobility", sessionId, node.getId())); - return WebUtils.getJson(url, MobilityConfig.class); - } - - @Override - public boolean mobilityAction(CoreNode node, String action) throws IOException { - String url = getUrl(String.format("sessions/%s/nodes/%s/mobility/%s", sessionId, node.getId(), action)); - return WebUtils.putJson(url); - } - - @Override - public void setupEventHandlers(Controller controller) throws IOException { - coreWebSocket.stop(); - coreWebSocket = new CoreWebSocket(controller); - try { - coreWebSocket.start(address, port); - } catch (URISyntaxException ex) { - throw new IOException("error starting web socket", ex); - } - } -} diff --git a/corefx/src/main/java/com/core/client/rest/GetConfig.java b/corefx/src/main/java/com/core/client/rest/GetConfig.java deleted file mode 100644 index 61a843b6..00000000 --- a/corefx/src/main/java/com/core/client/rest/GetConfig.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.core.client.rest; - -import com.core.data.ConfigGroup; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class GetConfig { - private List groups = new ArrayList<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java b/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java deleted file mode 100644 index b134cbc0..00000000 --- a/corefx/src/main/java/com/core/client/rest/GetDefaultServices.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.core.client.rest; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class GetDefaultServices { - private Map> defaults = new HashMap<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java b/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java deleted file mode 100644 index 43cef1c6..00000000 --- a/corefx/src/main/java/com/core/client/rest/GetEmaneModels.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.core.client.rest; - -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class GetEmaneModels { - private List models = new ArrayList<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/GetHooks.java b/corefx/src/main/java/com/core/client/rest/GetHooks.java deleted file mode 100644 index fe26e828..00000000 --- a/corefx/src/main/java/com/core/client/rest/GetHooks.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.core.client.rest; - -import com.core.data.Hook; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class GetHooks { - private List hooks = new ArrayList<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java b/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java deleted file mode 100644 index e5905b7e..00000000 --- a/corefx/src/main/java/com/core/client/rest/GetMobilityConfigs.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.core.client.rest; - -import com.core.data.MobilityConfig; -import lombok.Data; - -import java.util.HashMap; -import java.util.Map; - -@Data -public class GetMobilityConfigs { - private Map configurations = new HashMap<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/GetServices.java b/corefx/src/main/java/com/core/client/rest/GetServices.java deleted file mode 100644 index 17e04ddc..00000000 --- a/corefx/src/main/java/com/core/client/rest/GetServices.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.core.client.rest; - -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; -import java.util.Map; - -@Data -@NoArgsConstructor -public class GetServices { - private Map> groups; -} diff --git a/corefx/src/main/java/com/core/client/rest/GetSessions.java b/corefx/src/main/java/com/core/client/rest/GetSessions.java deleted file mode 100644 index 674fc83a..00000000 --- a/corefx/src/main/java/com/core/client/rest/GetSessions.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.core.client.rest; - -import com.core.data.SessionOverview; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.ArrayList; -import java.util.List; - -@Data -@NoArgsConstructor -public class GetSessions { - private List sessions = new ArrayList<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/SetConfig.java b/corefx/src/main/java/com/core/client/rest/SetConfig.java deleted file mode 100644 index 0d3f11ea..00000000 --- a/corefx/src/main/java/com/core/client/rest/SetConfig.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.core.client.rest; - -import com.core.data.ConfigOption; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.ArrayList; -import java.util.List; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class SetConfig { - private List values = new ArrayList<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java b/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java deleted file mode 100644 index ee947c51..00000000 --- a/corefx/src/main/java/com/core/client/rest/SetEmaneConfig.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.core.client.rest; - -import com.core.data.ConfigOption; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class SetEmaneConfig { - private Integer node; - private List values = new ArrayList<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java b/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java deleted file mode 100644 index e5c37092..00000000 --- a/corefx/src/main/java/com/core/client/rest/SetEmaneModelConfig.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.core.client.rest; - -import com.core.data.ConfigOption; -import lombok.Data; - -import java.util.ArrayList; -import java.util.List; - -@Data -public class SetEmaneModelConfig { - private Integer node; - private String name; - private List values = new ArrayList<>(); -} diff --git a/corefx/src/main/java/com/core/client/rest/ServiceFile.java b/corefx/src/main/java/com/core/data/ServiceFile.java similarity index 87% rename from corefx/src/main/java/com/core/client/rest/ServiceFile.java rename to corefx/src/main/java/com/core/data/ServiceFile.java index 977ed984..f30efcd0 100644 --- a/corefx/src/main/java/com/core/client/rest/ServiceFile.java +++ b/corefx/src/main/java/com/core/data/ServiceFile.java @@ -1,4 +1,4 @@ -package com.core.client.rest; +package com.core.data; import lombok.AllArgsConstructor; import lombok.Data; diff --git a/corefx/src/main/java/com/core/client/rest/WlanConfig.java b/corefx/src/main/java/com/core/data/WlanConfig.java similarity index 86% rename from corefx/src/main/java/com/core/client/rest/WlanConfig.java rename to corefx/src/main/java/com/core/data/WlanConfig.java index 45b9805f..26048d73 100644 --- a/corefx/src/main/java/com/core/client/rest/WlanConfig.java +++ b/corefx/src/main/java/com/core/data/WlanConfig.java @@ -1,4 +1,4 @@ -package com.core.client.rest; +package com.core.data; import lombok.Data; diff --git a/corefx/src/main/java/com/core/ui/dialogs/NodeWlanDialog.java b/corefx/src/main/java/com/core/ui/dialogs/NodeWlanDialog.java index 8cbc7868..e11a498f 100644 --- a/corefx/src/main/java/com/core/ui/dialogs/NodeWlanDialog.java +++ b/corefx/src/main/java/com/core/ui/dialogs/NodeWlanDialog.java @@ -1,7 +1,7 @@ package com.core.ui.dialogs; import com.core.Controller; -import com.core.client.rest.WlanConfig; +import com.core.data.WlanConfig; import com.core.data.CoreNode; import com.core.ui.Toast; import com.jfoenix.controls.JFXButton; diff --git a/corefx/src/main/java/com/core/ui/dialogs/ServiceDialog.java b/corefx/src/main/java/com/core/ui/dialogs/ServiceDialog.java index ab526fc6..635997e8 100644 --- a/corefx/src/main/java/com/core/ui/dialogs/ServiceDialog.java +++ b/corefx/src/main/java/com/core/ui/dialogs/ServiceDialog.java @@ -1,7 +1,7 @@ package com.core.ui.dialogs; import com.core.Controller; -import com.core.client.rest.ServiceFile; +import com.core.data.ServiceFile; import com.core.data.CoreNode; import com.core.data.CoreService; import com.jfoenix.controls.JFXButton; diff --git a/corefx/src/main/java/com/core/utils/WebUtils.java b/corefx/src/main/java/com/core/utils/WebUtils.java deleted file mode 100644 index fec69313..00000000 --- a/corefx/src/main/java/com/core/utils/WebUtils.java +++ /dev/null @@ -1,174 +0,0 @@ -package com.core.utils; - -import com.fasterxml.jackson.core.type.TypeReference; -import okhttp3.*; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.*; -import java.util.Collections; -import java.util.Map; - -public final class WebUtils { - private static final Logger logger = LogManager.getLogger(); - private static final OkHttpClient client = new OkHttpClient(); - private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); - - private WebUtils() { - } - - public static T getJson(String url, Class clazz) throws IOException { - return getJson(url, clazz, Collections.emptyMap()); - } - - public static T getJson(String url, Class clazz, Map args) throws IOException { - logger.debug("get json: {}", url); - HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder(); - args.forEach(urlBuilder::addQueryParameter); - HttpUrl httpUrl = urlBuilder.build(); - - Request request = new Request.Builder() - .url(httpUrl) - .build(); - String response = readResponse(request); - return JsonUtils.read(response, clazz); - } - - public static void getFile(String url, File file) throws IOException { - logger.debug("get file: {}", url); - Request request = new Request.Builder() - .url(url) - .build(); - try (Response response = client.newCall(request).execute()) { - InputStream input = response.body().byteStream(); - try (OutputStream output = new FileOutputStream(file)) { - int count; - byte[] data = new byte[1024]; - while ((count = input.read(data)) != -1) { - output.write(data, 0, count); - } - } - } - } - - public static boolean postFile(String url, File file) throws IOException { - MediaType mediaType = MediaType.parse("File/*"); - RequestBody requestBody = new MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("file", file.getName(), RequestBody.create(mediaType, file)) - .build(); - - Request request = new Request.Builder() - .url(url) - .post(requestBody) - .build(); - - try (Response response = client.newCall(request).execute()) { - return response.isSuccessful(); - } - } - - public static T postFile(String url, File file, Class clazz) throws IOException { - MediaType mediaType = MediaType.parse("File/*"); - RequestBody requestBody = new MultipartBody.Builder() - .setType(MultipartBody.FORM) - .addFormDataPart("file", file.getName(), RequestBody.create(mediaType, file)) - .build(); - - Request request = new Request.Builder() - .url(url) - .post(requestBody) - .build(); - - String response = readResponse(request); - return JsonUtils.read(response, clazz); - } - - public static T getJson(String url, TypeReference reference) throws IOException { - Request request = new Request.Builder() - .url(url) - .build(); - String response = readResponse(request); - return JsonUtils.getMapper().readValue(response, reference); - } - - private static String readResponse(Request request) throws IOException { - try (Response response = client.newCall(request).execute()) { - ResponseBody body = response.body(); - if (body == null) { - throw new IOException("failed to received body"); - } else { - return body.string(); - } - } - } - - public static boolean postJson(String url, Object json) throws IOException { - logger.debug("post json: {} - {}", url, json); - RequestBody body = RequestBody.create(JSON, JsonUtils.toString(json)); - Request request = new Request.Builder() - .url(url) - .post(body) - .build(); - try (Response response = client.newCall(request).execute()) { - return response.isSuccessful(); - } - } - - public static boolean putJson(String url) throws IOException { - logger.debug("put json: {}", url); - RequestBody body = new FormBody.Builder().build(); - Request request = new Request.Builder() - .url(url) - .put(body) - .build(); - try (Response response = client.newCall(request).execute()) { - return response.isSuccessful(); - } - } - - public static boolean putJson(String url, Object json) throws IOException { - logger.debug("put json: {} - {}", url, json); - RequestBody body = RequestBody.create(JSON, JsonUtils.toString(json)); - Request request = new Request.Builder() - .url(url) - .put(body) - .build(); - try (Response response = client.newCall(request).execute()) { - return response.isSuccessful(); - } - } - - public static T putJson(String url, Object json, Class clazz) throws IOException { - logger.debug("put json: {} - {}", url, json); - RequestBody body = RequestBody.create(JSON, JsonUtils.toString(json)); - Request request = new Request.Builder() - .url(url) - .put(body) - .build(); - String response = readResponse(request); - return JsonUtils.read(response, clazz); - } - - public static T post(String url, Class clazz) throws IOException { - logger.debug("post: {}", url); - RequestBody body = new FormBody.Builder().build(); - Request request = new Request.Builder() - .url(url) - .post(body) - .build(); - String response = readResponse(request); - return JsonUtils.read(response, clazz); - } - - public static boolean delete(String url) throws IOException { - logger.debug("delete: {}", url); - Request request = new Request.Builder() - .url(url) - .delete() - .build(); - try (Response response = client.newCall(request).execute()) { - return response.isSuccessful(); - } - } -} diff --git a/corefx/src/main/java/com/core/websocket/CoreWebSocket.java b/corefx/src/main/java/com/core/websocket/CoreWebSocket.java deleted file mode 100644 index 3019c622..00000000 --- a/corefx/src/main/java/com/core/websocket/CoreWebSocket.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.core.websocket; - -import com.core.Controller; -import com.core.data.*; -import com.core.ui.dialogs.MobilityPlayerDialog; -import com.core.utils.JsonUtils; -import io.socket.client.IO; -import io.socket.client.Socket; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.io.IOException; -import java.net.URISyntaxException; - -public class CoreWebSocket { - private static final Logger logger = LogManager.getLogger(); - private final Controller controller; - private Thread socketThread; - private Socket socket; - - public CoreWebSocket(Controller controller) { - this.controller = controller; - } - - public void start(String address, int port) throws URISyntaxException { - socket = IO.socket(String.format("http://%s:%s", address, port)); - socket.on(Socket.EVENT_CONNECT, args -> logger.info("connected to web socket")); - socket.on("node", this::handleNodes); - socket.on("event", this::handleEvents); - socket.on("config", this::handleConfigs); - socket.on("link", this::handleLinks); - socket.on("throughput", this::handleThroughputs); - socket.on(Socket.EVENT_DISCONNECT, args -> logger.info("disconnected from web socket")); - - logger.info("attempting to connect to web socket!"); - socketThread = new Thread(socket::connect); - socketThread.setDaemon(true); - socketThread.start(); - } - - public void stop() { - if (socketThread != null) { - socket.close(); - socketThread.interrupt(); - } - } - - private void handleThroughputs(Object... args) { - for (Object arg : args) { - logger.info("throughput update: {}", arg); - try { - Throughputs throughputs = JsonUtils.read(arg.toString(), Throughputs.class); - controller.handleThroughputs(throughputs); - } catch (IOException ex) { - logger.error("error getting throughputs", ex); - } - } - } - - private void handleNodes(Object... args) { - for (Object arg : args) { - try { - CoreNode node = JsonUtils.read(arg.toString(), CoreNode.class); - logger.info("core node update: {}", node); - controller.getNetworkGraph().setNodeLocation(node); - } catch (IOException ex) { - logger.error("error getting core node", ex); - } - } - } - - private void handleEvents(Object... args) { - for (Object arg : args) { - try { - CoreEvent event = JsonUtils.read(arg.toString(), CoreEvent.class); - logger.info("handling broadcast event: {}", event); - SessionState state = SessionState.get(event.getEventType().getValue()); - if (state == null) { - logger.warn("unknown event type: {}", event.getEventType().getValue()); - return; - } - - // session state event - if (state.getValue() <= 6) { - logger.info("event updating session state: {}", state); - controller.getCoreClient().updateState(state); - // mobility script event - } else if (state.getValue() <= 9) { - Integer nodeId = event.getNode(); - String[] values = event.getData().split("\\s+"); - Integer start = Integer.parseInt(values[0].split("=")[1]); - Integer end = Integer.parseInt(values[1].split("=")[1]); - logger.info(String.format("node(%s) mobility event (%s) - start(%s) stop(%s)", - nodeId, state, start, end)); - logger.info("all dialogs: {}", controller.getMobilityPlayerDialogs().keySet()); - MobilityPlayerDialog mobilityPlayerDialog = controller.getMobilityPlayerDialogs().get(nodeId); - mobilityPlayerDialog.event(state, start, end); - } - } catch (IOException ex) { - logger.error("error getting core event", ex); - } - } - } - - private void handleLinks(Object... args) { - for (Object arg : args) { - try { - CoreLink link = JsonUtils.read(arg.toString(), CoreLink.class); - logger.info("handling broadcast link: {}", link); - MessageFlags flag = MessageFlags.get(link.getMessageType()); - if (MessageFlags.DELETE == flag) { - logger.info("delete"); - controller.getNetworkGraph().removeWirelessLink(link); - } else if (MessageFlags.ADD == flag) { - link.setLoaded(true); - controller.getNetworkGraph().addLink(link); - } - controller.getNetworkGraph().getGraphViewer().repaint(); - } catch (IOException ex) { - logger.error("error handling broadcast link", ex); - } - } - } - - private void handleConfigs(Object... args) { - for (Object arg : args) { - logger.info("handling broadcast config: {}", arg); - } - } -} From 6288e11bccf61d2770ec075a1c202cdc55909cbc Mon Sep 17 00:00:00 2001 From: Blake Harnden Date: Fri, 14 Jun 2019 14:14:00 -0700 Subject: [PATCH 0113/1992] grpc - fixed issue when handling wlan configurations --- daemon/core/api/grpc/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index f7d1ffa0..72e390ff 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -854,9 +854,9 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): def SetWlanConfig(self, request, context): logging.debug("set wlan config: %s", request) session = self.get_session(request.session_id, context) - node = self.get_node(session, request.node_id, context) - session.mobility.set_model_config(node.id, BasicRangeModel.name, request.config) + session.mobility.set_model_config(request.node_id, BasicRangeModel.name, request.config) if session.state == EventTypes.RUNTIME_STATE.value: + node = self.get_node(session, request.node_id, context) node.updatemodel(request.config) return core_pb2.SetWlanConfigResponse(result=True) From 778021c5531ab9f3aa89981038373610d51e4a82 Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Fri, 14 Jun 2019 14:17:41 -0700 Subject: [PATCH 0114/1992] corefx - removed jackson annotations from data fields as there is no longer a need to deal with them in json form --- .../main/java/com/core/data/CoreEvent.java | 6 ----- .../src/main/java/com/core/data/CoreLink.java | 22 ------------------- .../src/main/java/com/core/data/CoreNode.java | 6 ----- .../main/java/com/core/data/CoreService.java | 3 --- corefx/src/main/java/com/core/data/Hook.java | 2 -- .../com/core/data/InterfaceThroughput.java | 2 -- .../java/com/core/data/MobilityConfig.java | 7 ------ 7 files changed, 48 deletions(-) diff --git a/corefx/src/main/java/com/core/data/CoreEvent.java b/corefx/src/main/java/com/core/data/CoreEvent.java index 9ac8ed03..718097d8 100644 --- a/corefx/src/main/java/com/core/data/CoreEvent.java +++ b/corefx/src/main/java/com/core/data/CoreEvent.java @@ -1,6 +1,5 @@ package com.core.data; -import com.fasterxml.jackson.annotation.JsonSetter; import lombok.Data; @Data @@ -11,9 +10,4 @@ public class CoreEvent { private Double time; private EventType eventType; private String data; - - @JsonSetter("event_type") - public void setEventType(int value) { - eventType = EventType.get(value); - } } diff --git a/corefx/src/main/java/com/core/data/CoreLink.java b/corefx/src/main/java/com/core/data/CoreLink.java index f3d4a215..88c6ff30 100644 --- a/corefx/src/main/java/com/core/data/CoreLink.java +++ b/corefx/src/main/java/com/core/data/CoreLink.java @@ -1,7 +1,5 @@ package com.core.data; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -12,36 +10,16 @@ import lombok.NoArgsConstructor; public class CoreLink { @EqualsAndHashCode.Include private Integer id; - - @JsonIgnore private Float weight = 1.0f; - - @JsonIgnore private boolean loaded = true; - - @JsonIgnore private double throughput; - - @JsonIgnore private boolean visible = true; - - @JsonProperty("message_type") private Integer messageType; - private Integer type = 1; - - @JsonProperty("node_one") private Integer nodeOne; - - @JsonProperty("node_two") private Integer nodeTwo; - - @JsonProperty("interface_one") private CoreInterface interfaceOne; - - @JsonProperty("interface_two") private CoreInterface interfaceTwo; - private CoreLinkOptions options = new CoreLinkOptions(); public CoreLink(Integer id) { diff --git a/corefx/src/main/java/com/core/data/CoreNode.java b/corefx/src/main/java/com/core/data/CoreNode.java index f441bd69..ab5240a8 100644 --- a/corefx/src/main/java/com/core/data/CoreNode.java +++ b/corefx/src/main/java/com/core/data/CoreNode.java @@ -2,7 +2,6 @@ package com.core.data; import com.core.graph.RadioIcon; import com.core.utils.IconUtils; -import com.fasterxml.jackson.annotation.JsonIgnore; import edu.uci.ics.jung.visualization.LayeredIcon; import lombok.Data; import lombok.EqualsAndHashCode; @@ -27,15 +26,10 @@ public class CoreNode { private Set services = new HashSet<>(); private String emane; private String url; - @JsonIgnore private NodeType nodeType; - @JsonIgnore private String icon; - @JsonIgnore private boolean loaded = true; - @JsonIgnore private LayeredIcon graphIcon; - @JsonIgnore private RadioIcon radioIcon = new RadioIcon(); public CoreNode(Integer id) { diff --git a/corefx/src/main/java/com/core/data/CoreService.java b/corefx/src/main/java/com/core/data/CoreService.java index ed9dd230..c33aebed 100644 --- a/corefx/src/main/java/com/core/data/CoreService.java +++ b/corefx/src/main/java/com/core/data/CoreService.java @@ -1,6 +1,5 @@ package com.core.data; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.util.ArrayList; @@ -14,9 +13,7 @@ public class CoreService { private List configs = new ArrayList<>(); private List startup = new ArrayList<>(); private List validate = new ArrayList<>(); - @JsonProperty("validation_mode") private String validationMode; - @JsonProperty("validation_timer") private String validationTimer; private List shutdown = new ArrayList<>(); private String meta; diff --git a/corefx/src/main/java/com/core/data/Hook.java b/corefx/src/main/java/com/core/data/Hook.java index 35b1a934..652976b0 100644 --- a/corefx/src/main/java/com/core/data/Hook.java +++ b/corefx/src/main/java/com/core/data/Hook.java @@ -1,13 +1,11 @@ package com.core.data; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; @Data public class Hook { private String file; private Integer state; - @JsonIgnore private String stateDisplay; private String data; } diff --git a/corefx/src/main/java/com/core/data/InterfaceThroughput.java b/corefx/src/main/java/com/core/data/InterfaceThroughput.java index 1c6303e0..8df86501 100644 --- a/corefx/src/main/java/com/core/data/InterfaceThroughput.java +++ b/corefx/src/main/java/com/core/data/InterfaceThroughput.java @@ -1,12 +1,10 @@ package com.core.data; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; @Data public class InterfaceThroughput { private int node; - @JsonProperty("interface") private int nodeInterface; private double throughput; } diff --git a/corefx/src/main/java/com/core/data/MobilityConfig.java b/corefx/src/main/java/com/core/data/MobilityConfig.java index 67b5d769..f16de6ff 100644 --- a/corefx/src/main/java/com/core/data/MobilityConfig.java +++ b/corefx/src/main/java/com/core/data/MobilityConfig.java @@ -1,7 +1,5 @@ package com.core.data; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import java.io.File; @@ -9,17 +7,12 @@ import java.io.File; @Data public class MobilityConfig { private String file; - @JsonIgnore private File scriptFile; - @JsonProperty("refresh_ms") private Integer refresh; private String loop; private String autostart; private String map; - @JsonProperty("script_start") private String startScript; - @JsonProperty("script_pause") private String pauseScript; - @JsonProperty("script_stop") private String stopScript; } From 9b485c8209a9bbda1099d519fb6f0e0c8f4b04c6 Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Fri, 14 Jun 2019 17:59:14 -0700 Subject: [PATCH 0115/1992] corefx changes to support similarly bumping subnet linking to be the same as previous gui --- .../java/com/core/graph/CoreAddresses.java | 91 ++++++------------- .../java/com/core/graph/NetworkGraph.java | 47 +++++----- 2 files changed, 48 insertions(+), 90 deletions(-) diff --git a/corefx/src/main/java/com/core/graph/CoreAddresses.java b/corefx/src/main/java/com/core/graph/CoreAddresses.java index 554145da..dc1dcc5d 100644 --- a/corefx/src/main/java/com/core/graph/CoreAddresses.java +++ b/corefx/src/main/java/com/core/graph/CoreAddresses.java @@ -8,80 +8,43 @@ import org.apache.logging.log4j.Logger; import java.util.Comparator; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; public class CoreAddresses { private static final Logger logger = LogManager.getLogger(); - private IPAddress defaultSubnet = new IPAddressString("10.0.0.0/24").getAddress(); + private IPAddress currentSubnet = new IPAddressString("10.0.0.0/24").getAddress(); + private AtomicBoolean firstSubnet = new AtomicBoolean(true); + + public IPAddress nextSubnet() { + logger.info("getting next subnet: {}", currentSubnet); + if (!firstSubnet.getAndSet(false)) { + currentSubnet = currentSubnet.incrementBoundary(1).toPrefixBlock(); + } + logger.info("getting updated boundary: {}", currentSubnet); + return currentSubnet; + } + + public IPAddress findSubnet(Set interfaces) { + IPAddress subnet; + logger.info("finding subnet from interfaces: {}", interfaces); + if (interfaces.isEmpty()) { + subnet = nextSubnet(); + } else { + IPAddress maxAddress = getMaxAddress(interfaces); + subnet = maxAddress.toPrefixBlock(); + } + return subnet; + } private IPAddress getMaxAddress(Set interfaces) { return interfaces.stream() .map(CoreInterface::getIp4) .max(Comparator.comparingInt(x -> x.toIPv4().intValue())) - .orElseGet(() -> defaultSubnet); - } - - public IPAddress getSubnet(Set nodeOneInterfaces, Set nodeTwoInterfaces, - boolean nodeOneIsNetwork, boolean nodeTwoIsNetwork) { - IPAddress nodeOneMax = getMaxAddress(nodeOneInterfaces); - IPAddress nodeTwoMax = getMaxAddress(nodeTwoInterfaces); - - logger.info("node one max: {}, node two max: {}", nodeOneMax, nodeTwoMax); - logger.info("max one compared to two: {} - {}", - nodeOneMax.toIPv4().intValue(), nodeTwoMax.toIPv4().intValue()); - boolean shouldBump; - boolean isDefault; - IPAddress subnet; - - if (nodeOneMax.toIPv4().intValue() > nodeTwoMax.toIPv4().intValue()) { - subnet = nodeOneMax; - isDefault = nodeOneMax == defaultSubnet; - shouldBump = !nodeOneIsNetwork && !isDefault; - } else { - subnet = nodeTwoMax; - isDefault = nodeTwoMax == defaultSubnet; - shouldBump = !nodeTwoIsNetwork && !isDefault; - } - - logger.info("found max address: {} - {}", isDefault, subnet); - if (!isDefault) { - subnet = subnet.toPrefixBlock(); - } - - if (shouldBump) { - logger.info("incrementing subnet for host to host"); - subnet = subnet.incrementBoundary(1); - } - - logger.info("found subnet: {}", subnet); - return subnet; + .orElseGet(() -> currentSubnet); } public static void main(String... args) { - IPAddress addresses = new IPAddressString("10.0.100.1/24").getAddress(); - IPAddress addresses3 = new IPAddressString("10.0.0.2/24").getAddress(); - IPAddress addresses1 = new IPAddressString("10.0.1.0/24").getAddress(); - IPAddress addresses2 = new IPAddressString("10.0.2.0/24").getAddress(); - System.out.println(String.format("compare to greater: %s", addresses.compareTo(addresses3))); - System.out.println(String.format("compare to greater: %s", addresses3.compareTo(addresses1))); - System.out.println(String.format("compare to greater: %s", addresses.toInetAddress())); - - IPAddress address = addresses.increment(1); - IPAddress address1 = addresses1.increment(1); - IPAddress address2 = addresses2.increment(1); - System.out.println(String.format("divisions: %s", address.toPrefixBlock())); - System.out.println(String.format("divisions: %s", address1.toPrefixBlock())); - System.out.println(String.format("divisions: %s", address2.toPrefixBlock())); - System.out.println(String.format("compares: %s", address1.compareTo(address2))); - System.out.println(String.format("compares: %s", address1.compareTo(address))); - System.out.println(String.format("compares: %s", address2.getSection(2, 3))); - System.out.println(String.format("compares: %s", address2.getSegment(2))); - System.out.println(String.format("address: %s", address2)); - - IPAddress prefixBlock = address1.toPrefixBlock(); - System.out.println(String.format("prefix block: %s", prefixBlock)); - IPAddress update = new IPAddressString("0.0.1.0").getAddress(); - IPAddress nextAddress = prefixBlock.incrementBoundary(1); -// nextAddress.setPrefixLength(prefixBlock.getPrefixLength(), true); - System.out.println(String.format("prefix block update: %s", nextAddress)); + IPAddress addresses = new IPAddressString("10.0.0.0/16").getAddress(); + System.out.println(String.format("address: %s", addresses.increment(257))); } } diff --git a/corefx/src/main/java/com/core/graph/NetworkGraph.java b/corefx/src/main/java/com/core/graph/NetworkGraph.java index 2d9ec7af..5e7f7259 100644 --- a/corefx/src/main/java/com/core/graph/NetworkGraph.java +++ b/corefx/src/main/java/com/core/graph/NetworkGraph.java @@ -75,9 +75,8 @@ public class NetworkGraph { graphViewer.setBackground(Color.WHITE); graphViewer.getRenderer().getVertexLabelRenderer().setPosition(Renderer.VertexLabel.Position.S); - RenderContext renderContext = graphViewer.getRenderContext(); - // node render properties + RenderContext renderContext = graphViewer.getRenderContext(); renderContext.setVertexLabelTransformer(CoreNode::getName); renderContext.setVertexLabelRenderer(nodeLabelRenderer); renderContext.setVertexShapeTransformer(node -> { @@ -288,44 +287,39 @@ public class NetworkGraph { if (link.isLoaded()) { return; } - Pair endpoints = graph.getEndpoints(link); - CoreNode nodeOne = endpoints.getFirst(); CoreNode nodeTwo = endpoints.getSecond(); + boolean nodeOneIsDefault = isNode(nodeOne); + boolean nodeTwoIsDefault = isNode(nodeTwo); - // check if a node being linked is a network node - Set nodeOneInterfaces; - boolean nodeOneIsNetwork = false; - if (isNode(nodeOne)) { - nodeOneInterfaces = getNodeInterfaces(nodeOne); + // check what we are linking together + IPAddress subnet = null; + Set interfaces; + if (nodeOneIsDefault && nodeTwoIsDefault) { + subnet = coreAddresses.nextSubnet(); + logger.info("linking node to node using subnet: {}", subnet); + } else if (nodeOneIsDefault) { + interfaces = getNetworkInterfaces(nodeTwo, new HashSet<>()); + subnet = coreAddresses.findSubnet(interfaces); + logger.info("linking node one to network using subnet: {}", subnet); + } else if (nodeTwoIsDefault) { + interfaces = getNetworkInterfaces(nodeOne, new HashSet<>()); + subnet = coreAddresses.findSubnet(interfaces); + logger.info("linking node two to network using subnet: {}", subnet); } else { - nodeOneInterfaces = getNetworkInterfaces(nodeOne, new HashSet<>()); - nodeOneIsNetwork = true; + logger.info("subnet not needed for linking networks together"); } - Set nodeTwoInterfaces; - boolean nodeTwoIsNetwork = false; - if (isNode(nodeTwo)) { - nodeTwoInterfaces = getNodeInterfaces(nodeTwo); - } else { - nodeTwoInterfaces = getNetworkInterfaces(nodeTwo, new HashSet<>()); - nodeTwoIsNetwork = true; - } - - // create interfaces for nodes - IPAddress subnet = coreAddresses.getSubnet(nodeOneInterfaces, nodeTwoInterfaces, - nodeOneIsNetwork, nodeTwoIsNetwork); - link.setNodeOne(nodeOne.getId()); - if (isNode(nodeOne)) { + if (nodeOneIsDefault) { int interfaceOneId = nextInterfaceId(nodeOne); CoreInterface interfaceOne = createInterface(nodeOne, interfaceOneId, subnet); link.setInterfaceOne(interfaceOne); } link.setNodeTwo(nodeTwo.getId()); - if (isNode(nodeTwo)) { + if (nodeTwoIsDefault) { int interfaceTwoId = nextInterfaceId(nodeTwo); CoreInterface interfaceTwo = createInterface(nodeTwo, interfaceTwoId, subnet); link.setInterfaceTwo(interfaceTwo); @@ -414,6 +408,7 @@ public class NetworkGraph { coreInterface.setId(interfaceId); coreInterface.setName(String.format("eth%s", interfaceId)); IPAddress address = subnet.increment(node.getId()); + logger.info("creating interface for node({}): {}", node.getId(), address); coreInterface.setIp4(address); coreInterface.setIp6(address.toIPv6()); return coreInterface; From 0661f5d601cd65c87cbed2d15ef1d5a0793d8f12 Mon Sep 17 00:00:00 2001 From: bharnden Date: Fri, 14 Jun 2019 23:04:18 -0700 Subject: [PATCH 0116/1992] corefx - updated proto link --- corefx/src/main/proto/core.proto | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/corefx/src/main/proto/core.proto b/corefx/src/main/proto/core.proto index a00ebb81..cec58963 120000 --- a/corefx/src/main/proto/core.proto +++ b/corefx/src/main/proto/core.proto @@ -1 +1 @@ -../../../../daemon/proto/core.proto \ No newline at end of file +../../../../daemon/proto/core/api/grpc/core.proto \ No newline at end of file From 338439b42959b2e8452b0b4b9f2704a797857f17 Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 15 Jun 2019 00:43:17 -0700 Subject: [PATCH 0117/1992] corefx - changes to reuse deleted subnets --- .../java/com/core/graph/CoreAddresses.java | 23 +++++++++++-- .../java/com/core/graph/NetworkGraph.java | 33 +++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/corefx/src/main/java/com/core/graph/CoreAddresses.java b/corefx/src/main/java/com/core/graph/CoreAddresses.java index dc1dcc5d..fdd872c4 100644 --- a/corefx/src/main/java/com/core/graph/CoreAddresses.java +++ b/corefx/src/main/java/com/core/graph/CoreAddresses.java @@ -7,18 +7,31 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.Comparator; +import java.util.Queue; import java.util.Set; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; public class CoreAddresses { private static final Logger logger = LogManager.getLogger(); - private IPAddress currentSubnet = new IPAddressString("10.0.0.0/24").getAddress(); + private IPAddress currentSubnet = new IPAddressString("10.0.0.0/24").getAddress().toPrefixBlock(); private AtomicBoolean firstSubnet = new AtomicBoolean(true); + private Queue deleted = new LinkedBlockingQueue<>(); + + public void reuseSubnet(IPAddress subnet) { + deleted.add(subnet); + } public IPAddress nextSubnet() { logger.info("getting next subnet: {}", currentSubnet); if (!firstSubnet.getAndSet(false)) { - currentSubnet = currentSubnet.incrementBoundary(1).toPrefixBlock(); + IPAddress deletedSubnet = deleted.poll(); + if (deletedSubnet != null) { + currentSubnet = deletedSubnet; + } else { + currentSubnet = currentSubnet.incrementBoundary(1).toPrefixBlock(); + } } logger.info("getting updated boundary: {}", currentSubnet); return currentSubnet; @@ -43,6 +56,12 @@ public class CoreAddresses { .orElseGet(() -> currentSubnet); } + public void reset() { + deleted.clear(); + firstSubnet.set(true); + currentSubnet = new IPAddressString("10.0.0.0/24").getAddress().toPrefixBlock(); + } + public static void main(String... args) { IPAddress addresses = new IPAddressString("10.0.0.0/16").getAddress(); System.out.println(String.format("address: %s", addresses.increment(257))); diff --git a/corefx/src/main/java/com/core/graph/NetworkGraph.java b/corefx/src/main/java/com/core/graph/NetworkGraph.java index 5e7f7259..8dfe4e84 100644 --- a/corefx/src/main/java/com/core/graph/NetworkGraph.java +++ b/corefx/src/main/java/com/core/graph/NetworkGraph.java @@ -248,6 +248,7 @@ public class NetworkGraph { } nodeMap.clear(); graphViewer.repaint(); + coreAddresses.reset(); } public void updatePositions() { @@ -417,6 +418,38 @@ public class NetworkGraph { private void handleEdgeRemoved(GraphEvent.Edge edgeEvent) { CoreLink link = edgeEvent.getEdge(); logger.info("removed edge: {}", link); + CoreNode nodeOne = getVertex(link.getNodeOne()); + CoreInterface interfaceOne = link.getInterfaceOne(); + CoreNode nodeTwo = getVertex(link.getNodeTwo()); + CoreInterface interfaceTwo = link.getInterfaceTwo(); + boolean nodeOneIsDefault = isNode(nodeOne); + boolean nodeTwoIsDefault = isNode(nodeTwo); + + // check what we are unlinking + Set interfaces; + IPAddress subnet = null; + if (nodeOneIsDefault && nodeTwoIsDefault) { + subnet = interfaceOne.getIp4().toPrefixBlock(); + logger.info("unlinking node to node reuse subnet: {}", subnet); + } else if (nodeOneIsDefault) { + interfaces = getNetworkInterfaces(nodeTwo, new HashSet<>()); + if (interfaces.isEmpty()) { + subnet = interfaceOne.getIp4().toPrefixBlock(); + logger.info("unlinking node one from network reuse subnet: {}", subnet); + } + } else if (nodeTwoIsDefault) { + interfaces = getNetworkInterfaces(nodeOne, new HashSet<>()); + if (interfaces.isEmpty()) { + subnet = interfaceTwo.getIp4().toPrefixBlock(); + logger.info("unlinking node two from network reuse subnet: {}", subnet); + } + } else { + logger.info("nothing to do when unlinking networks"); + } + + if (subnet != null) { + coreAddresses.reuseSubnet(subnet); + } } private void handleVertexAdded(GraphEvent.Vertex vertexEvent) { From 0af3629ac66c72757e384e47636990bf12f96f7a Mon Sep 17 00:00:00 2001 From: bharnden Date: Sat, 15 Jun 2019 10:42:55 -0700 Subject: [PATCH 0118/1992] corefx - fixed some issues loading xml due to grpc change, updated subnet logic to help handle loaded scenarios. grpc - fixed issue when loading network links --- .../src/main/java/com/core/data/NodeType.java | 6 ++-- .../java/com/core/graph/CoreAddresses.java | 32 ++++++++++++------- .../java/com/core/graph/NetworkGraph.java | 9 +++++- daemon/core/api/grpc/server.py | 15 ++++++--- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/corefx/src/main/java/com/core/data/NodeType.java b/corefx/src/main/java/com/core/data/NodeType.java index 4af7dc3d..3b070799 100644 --- a/corefx/src/main/java/com/core/data/NodeType.java +++ b/corefx/src/main/java/com/core/data/NodeType.java @@ -73,11 +73,9 @@ public class NodeType { return ID_LOOKUP.values().stream() .filter(nodeType -> { boolean sameType = nodeType.getValue() == type; - boolean sameModel; - if (model != null) { + boolean sameModel = true; + if (!model.isEmpty()) { sameModel = model.equals(nodeType.getModel()); - } else { - sameModel = nodeType.getModel() == null; } return sameType && sameModel; }) diff --git a/corefx/src/main/java/com/core/graph/CoreAddresses.java b/corefx/src/main/java/com/core/graph/CoreAddresses.java index fdd872c4..70ab5e06 100644 --- a/corefx/src/main/java/com/core/graph/CoreAddresses.java +++ b/corefx/src/main/java/com/core/graph/CoreAddresses.java @@ -6,7 +6,9 @@ import inet.ipaddr.IPAddressString; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.beans.IndexedPropertyDescriptor; import java.util.Comparator; +import java.util.HashSet; import java.util.Queue; import java.util.Set; import java.util.concurrent.ArrayBlockingQueue; @@ -16,8 +18,14 @@ import java.util.concurrent.atomic.AtomicBoolean; public class CoreAddresses { private static final Logger logger = LogManager.getLogger(); private IPAddress currentSubnet = new IPAddressString("10.0.0.0/24").getAddress().toPrefixBlock(); - private AtomicBoolean firstSubnet = new AtomicBoolean(true); private Queue deleted = new LinkedBlockingQueue<>(); + private Set usedSubnets = new HashSet<>(); + + public void usedAddress(IPAddress address) { + logger.info("adding used address: {} - {}", address, address.toPrefixBlock()); + usedSubnets.add(address.toPrefixBlock()); + logger.info("used subnets: {}", usedSubnets); + } public void reuseSubnet(IPAddress subnet) { deleted.add(subnet); @@ -25,16 +33,18 @@ public class CoreAddresses { public IPAddress nextSubnet() { logger.info("getting next subnet: {}", currentSubnet); - if (!firstSubnet.getAndSet(false)) { - IPAddress deletedSubnet = deleted.poll(); - if (deletedSubnet != null) { - currentSubnet = deletedSubnet; - } else { - currentSubnet = currentSubnet.incrementBoundary(1).toPrefixBlock(); - } + // skip existing subnets, when loaded from file + while (usedSubnets.contains(currentSubnet)) { + currentSubnet = currentSubnet.incrementBoundary(1).toPrefixBlock(); } - logger.info("getting updated boundary: {}", currentSubnet); - return currentSubnet; + + // re-use any deleted subnets + IPAddress next = deleted.poll(); + if (next == null) { + next = currentSubnet; + currentSubnet = currentSubnet.incrementBoundary(1).toPrefixBlock(); + } + return next; } public IPAddress findSubnet(Set interfaces) { @@ -58,7 +68,7 @@ public class CoreAddresses { public void reset() { deleted.clear(); - firstSubnet.set(true); + usedSubnets.clear(); currentSubnet = new IPAddressString("10.0.0.0/24").getAddress().toPrefixBlock(); } diff --git a/corefx/src/main/java/com/core/graph/NetworkGraph.java b/corefx/src/main/java/com/core/graph/NetworkGraph.java index 8dfe4e84..da3389ef 100644 --- a/corefx/src/main/java/com/core/graph/NetworkGraph.java +++ b/corefx/src/main/java/com/core/graph/NetworkGraph.java @@ -286,6 +286,13 @@ public class NetworkGraph { private void handleEdgeAdded(GraphEvent.Edge edgeEvent) { CoreLink link = edgeEvent.getEdge(); if (link.isLoaded()) { + // load addresses to avoid duplication + if (link.getInterfaceOne().getIp4() != null) { + coreAddresses.usedAddress(link.getInterfaceOne().getIp4()); + } + if (link.getInterfaceTwo().getIp4() != null) { + coreAddresses.usedAddress(link.getInterfaceTwo().getIp4()); + } return; } Pair endpoints = graph.getEndpoints(link); @@ -508,7 +515,7 @@ public class NetworkGraph { } private boolean isWirelessNode(CoreNode node) { - return node.getType() == NodeType.EMANE || node.getType() == NodeType.WLAN; + return node != null && (node.getType() == NodeType.EMANE || node.getType() == NodeType.WLAN); } private boolean checkForWirelessNode(CoreNode nodeOne, CoreNode nodeTwo) { diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 72e390ff..b2c26f42 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -17,6 +17,7 @@ from core.emulator.emudata import NodeOptions, InterfaceData, LinkOptions from core.emulator.enumerations import NodeTypes, EventTypes, LinkTypes from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility from core.nodes import nodeutils +from core.nodes.base import CoreNetworkBase from core.nodes.ipaddress import MacAddress from core.services.coreservices import ServiceManager @@ -73,18 +74,24 @@ def convert_link(session, link_data): interface_one = None if link_data.interface1_id is not None: node = session.get_node(link_data.node1_id) - interface = node.netif(link_data.interface1_id) + interface_name = None + if not isinstance(node, CoreNetworkBase): + interface = node.netif(link_data.interface1_id) + interface_name = interface.name interface_one = core_pb2.Interface( - id=link_data.interface1_id, name=interface.name, mac=convert_value(link_data.interface1_mac), + id=link_data.interface1_id, name=interface_name, mac=convert_value(link_data.interface1_mac), ip4=convert_value(link_data.interface1_ip4), ip4mask=link_data.interface1_ip4_mask, ip6=convert_value(link_data.interface1_ip6), ip6mask=link_data.interface1_ip6_mask) interface_two = None if link_data.interface2_id is not None: node = session.get_node(link_data.node2_id) - interface = node.netif(link_data.interface2_id) + interface_name = None + if not isinstance(node, CoreNetworkBase): + interface = node.netif(link_data.interface2_id) + interface_name = interface.name interface_two = core_pb2.Interface( - id=link_data.interface2_id, name=interface.name, mac=convert_value(link_data.interface2_mac), + id=link_data.interface2_id, name=interface_name, mac=convert_value(link_data.interface2_mac), ip4=convert_value(link_data.interface2_ip4), ip4mask=link_data.interface2_ip4_mask, ip6=convert_value(link_data.interface2_ip6), ip6mask=link_data.interface2_ip6_mask) From d8505418a6704cee2792ba1c6c6b46d5a2b9ac68 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Mon, 17 Jun 2019 08:23:03 -0500 Subject: [PATCH 0119/1992] Updated services.md with quagga documentation --- docs/services.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/services.md b/docs/services.md index 6f1f4940..fc5f9491 100644 --- a/docs/services.md +++ b/docs/services.md @@ -277,3 +277,24 @@ This image will be listed in the services after we restart the core-daemon: ```shell sudo service core-daemon restart ``` + +#### Quagga Routing Suite + Quagga is a routing software suite, providing implementations of OSPFv2, OSPFv3, RIP v1 and v2, RIPng and BGP-4 for Unix platforms, particularly FreeBSD, Linux, Solaris and NetBSD. Quagga is a fork of GNU Zebra which was developed by Kunihiro Ishiguro. +The Quagga architecture consists of a core daemon, zebra, which acts as an abstraction layer to the underlying Unix kernel and presents the Zserv API over a Unix or TCP stream to Quagga clients. It is these Zserv clients which typically implement a routing protocol and communicate routing updates to the zebra daemon. + +##### Quagga Package Install +```shell +sudo apt-get install quagga +``` + +##### Quagga Source Install +First, download the source code from their [official webpage](https://www.quagga.net/). +```shell +sudo apt-get install gawk +``` +Extract the tarball, go to the directory of your currently extracted code and issue the following commands. +```shell +./configure +make +sudo make install +``` From 9ff327b933d7e4008d23c5477b1bdff23e8e9849 Mon Sep 17 00:00:00 2001 From: SaintYomar Date: Mon, 17 Jun 2019 10:49:05 -0500 Subject: [PATCH 0120/1992] Updated services with security documentation --- docs/services.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/services.md b/docs/services.md index fc5f9491..39d6b9bc 100644 --- a/docs/services.md +++ b/docs/services.md @@ -241,6 +241,7 @@ make && sudo make install ``` If everything finishes successfully, FRR should be installed. + #### Docker Docker service allows running docker containers within CORE nodes. The running of Docker within a CORE node allows for additional extensibility to @@ -278,6 +279,7 @@ This image will be listed in the services after we restart the core-daemon: sudo service core-daemon restart ``` + #### Quagga Routing Suite Quagga is a routing software suite, providing implementations of OSPFv2, OSPFv3, RIP v1 and v2, RIPng and BGP-4 for Unix platforms, particularly FreeBSD, Linux, Solaris and NetBSD. Quagga is a fork of GNU Zebra which was developed by Kunihiro Ishiguro. The Quagga architecture consists of a core daemon, zebra, which acts as an abstraction layer to the underlying Unix kernel and presents the Zserv API over a Unix or TCP stream to Quagga clients. It is these Zserv clients which typically implement a routing protocol and communicate routing updates to the zebra daemon. @@ -298,3 +300,33 @@ Extract the tarball, go to the directory of your currently extracted code and is make sudo make install ``` + + +#### Software Defined Networking +Ryu is a component-based software defined networking framework. Ryu provides software components with well defined API that make it easy for developers to create new network management and control applications. Ryu supports various protocols for managing network devices, such as OpenFlow, Netconf, OF-config, etc. About OpenFlow, Ryu supports fully 1.0, 1.2, 1.3, 1.4, 1.5 and Nicira Extensions. All of the code is freely available under the Apache 2.0 license. +```shell +``` + +##### Installation +###### Prerequisites +```shell +sudo apt-get install gcc python-dev libffi-dev libssl-dev libxml2-dev libxslt1-dev zlib1g-dev +``` +###### Ryu Package Install +```shell +pip install ryu +``` +###### Ryu Source Install +```shell +git clone git://github.com/osrg/ryu.git +cd ryu; pip install . +``` + + +#### Security Services +The security services offer a wide variety of protocols capable of satisfying the most use cases available. Security services such as IP security protocols, for providing security at the IP layer, as well as the suite of protocols designed to provide that security, through authentication and encryption of IP network packets. Virtual Private Networks (VPNs) and Firewalls are also available for use to the user. + +##### Installation +```shell +sudo apt-get install ipsec-tools racoon openvpn +``` From 5b671ccb0020068f7a9c096457d1fc32f41d9fca Mon Sep 17 00:00:00 2001 From: "Blake J. Harnden" Date: Mon, 17 Jun 2019 10:05:22 -0700 Subject: [PATCH 0121/1992] corefx - small tweaks to node/link details page --- .../main/java/com/core/ui/LinkDetails.java | 27 ++++------ .../main/java/com/core/ui/NodeDetails.java | 52 ++++++++----------- .../src/main/resources/fxml/link_details.fxml | 2 +- .../src/main/resources/fxml/node_details.fxml | 5 +- 4 files changed, 35 insertions(+), 51 deletions(-) diff --git a/corefx/src/main/java/com/core/ui/LinkDetails.java b/corefx/src/main/java/com/core/ui/LinkDetails.java index b01c5e40..6acfaf45 100644 --- a/corefx/src/main/java/com/core/ui/LinkDetails.java +++ b/corefx/src/main/java/com/core/ui/LinkDetails.java @@ -44,10 +44,9 @@ public class LinkDetails extends ScrollPane { public void setLink(CoreLink link) { NetworkGraph graph = controller.getNetworkGraph(); ICoreClient coreClient = controller.getCoreClient(); - clear(); - addSeparator(); + CoreNode nodeOne = graph.getVertex(link.getNodeOne()); CoreInterface interfaceOne = link.getInterfaceOne(); addLabel(nodeOne.getName()); @@ -55,7 +54,6 @@ public class LinkDetails extends ScrollPane { addInterface(interfaceOne); } - addSeparator(); CoreNode nodeTwo = graph.getVertex(link.getNodeTwo()); CoreInterface interfaceTwo = link.getInterfaceTwo(); addLabel(nodeTwo.getName()); @@ -63,7 +61,6 @@ public class LinkDetails extends ScrollPane { addInterface(interfaceTwo); } - addSeparator(); addLabel("Properties"); JFXTextField bandwidthField = addRow("Bandwidth (bps)", link.getOptions().getBandwidth()); JFXTextField delayField = addRow("Delay (us)", link.getOptions().getDelay()); @@ -127,14 +124,15 @@ public class LinkDetails extends ScrollPane { private void addInterface(CoreInterface coreInterface) { addRow("Interface", coreInterface.getName(), true); - if (coreInterface.getMac() != null) { - addRow("MAC", coreInterface.getMac(), true); - } - addIp4Address(coreInterface.getIp4()); - addIp6Address(coreInterface.getIp6()); + addRow("MAC", coreInterface.getMac(), true); + addAddress("IP4", coreInterface.getIp4()); + addAddress("IP6", coreInterface.getIp6()); } private void addRow(String labelText, String value, boolean disabled) { + if (value == null) { + return; + } Label label = new Label(labelText); JFXTextField textField = new JFXTextField(value); textField.setDisable(disabled); @@ -156,18 +154,11 @@ public class LinkDetails extends ScrollPane { return textField; } - private void addIp4Address(IPAddress ip) { + private void addAddress(String label, IPAddress ip) { if (ip == null) { return; } - addRow("IP4", ip.toString(), true); - } - - private void addIp6Address(IPAddress ip) { - if (ip == null) { - return; - } - addRow("IP6", ip.toString(), true); + addRow(label, ip.toString(), true); } private void clear() { diff --git a/corefx/src/main/java/com/core/ui/NodeDetails.java b/corefx/src/main/java/com/core/ui/NodeDetails.java index 0fca8a95..fb9f4be2 100644 --- a/corefx/src/main/java/com/core/ui/NodeDetails.java +++ b/corefx/src/main/java/com/core/ui/NodeDetails.java @@ -44,31 +44,28 @@ public class NodeDetails extends ScrollPane { public void setNode(CoreNode node) { clear(); title.setText(node.getName()); - addSeparator(); + addLabel("Properties"); + addRow("ID", node.getId().toString(), true); if (node.getType() == NodeType.DEFAULT) { addRow("Model", node.getModel(), true); } else { addRow("Type", node.getNodeType().getDisplay(), true); } + addRow("EMANE", node.getEmane(), true); + addRow("X", node.getPosition().getX().toString(), true); + addRow("Y", node.getPosition().getY().toString(), true); - if (node.getEmane() != null) { - addRow("EMANE", node.getEmane(), true); - } - - addSeparator(); - addLabel("Position"); - if (node.getPosition().getX() != null) { - addRow("X", node.getPosition().getX().toString(), true); - } - if (node.getPosition().getY() != null) { - addRow("Y", node.getPosition().getY().toString(), true); - } - - addSeparator(); addLabel("Interfaces"); + boolean firstInterface = true; for (CoreLink link : controller.getNetworkGraph().getGraph().getIncidentEdges(node)) { + if (firstInterface) { + firstInterface = false; + } else { + addSeparator(); + } + CoreNode linkedNode; CoreInterface coreInterface; if (node.getId().equals(link.getNodeOne())) { @@ -83,7 +80,6 @@ public class NodeDetails extends ScrollPane { continue; } - addSeparator(); if (linkedNode.getType() == NodeType.EMANE) { String emaneModel = linkedNode.getEmane(); String linkedLabel = String.format("%s - %s", linkedNode.getName(), emaneModel); @@ -109,8 +105,8 @@ public class NodeDetails extends ScrollPane { if (services.isEmpty()) { services = controller.getDefaultServices().getOrDefault(node.getModel(), Collections.emptySet()); } + if (!services.isEmpty()) { - addSeparator(); addLabel("Services"); JFXListView listView = new JFXListView<>(); listView.setMouseTransparent(true); @@ -145,32 +141,26 @@ public class NodeDetails extends ScrollPane { private void addInterface(CoreInterface coreInterface, CoreNode linkedNode) { addRow("Linked To", linkedNode.getName(), true); addRow("Interface", coreInterface.getName(), true); - if (coreInterface.getMac() != null) { - addRow("MAC", coreInterface.getMac(), true); - } - addIp4Address(coreInterface.getIp4()); - addIp6Address(coreInterface.getIp6()); + addRow("MAC", coreInterface.getMac(), true); + addAddress("IP4", coreInterface.getIp4()); + addAddress("IP6", coreInterface.getIp6()); } private void addRow(String labelText, String value, boolean disabled) { + if (value == null) { + return; + } Label label = new Label(labelText); JFXTextField textField = new JFXTextField(value); textField.setDisable(disabled); gridPane.addRow(index++, label, textField); } - private void addIp4Address(IPAddress ip) { + private void addAddress(String label, IPAddress ip) { if (ip == null) { return; } - addRow("IP4", ip.toString(), true); - } - - private void addIp6Address(IPAddress ip) { - if (ip == null) { - return; - } - addRow("IP6", ip.toString(), true); + addRow(label, ip.toString(), true); } private void clear() { diff --git a/corefx/src/main/resources/fxml/link_details.fxml b/corefx/src/main/resources/fxml/link_details.fxml index 5d6a8bd3..b7239ecf 100644 --- a/corefx/src/main/resources/fxml/link_details.fxml +++ b/corefx/src/main/resources/fxml/link_details.fxml @@ -20,7 +20,7 @@ - +

xpnT8yvU8*S=oZI6M20)2rI> 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