From e825b94e13dcf2fff01bc5c444a929a5282476f4 Mon Sep 17 00:00:00 2001 From: ahrenholz Date: Tue, 23 Sep 2014 16:26:22 +0000 Subject: [PATCH] support for EMANE 0.9.2 by running emane process in each container use control network for data and events use internal transport instead of emanetransportd for 0.9.2 (Boeing r1881) --- daemon/core/emane/bypass.py | 3 +- daemon/core/emane/commeffect.py | 1 + daemon/core/emane/emane.py | 234 ++++++++++++++++++++++++++---- daemon/core/emane/ieee80211abg.py | 22 +-- daemon/core/emane/nodes.py | 4 +- daemon/core/emane/rfpipe.py | 1 + daemon/core/netns/nodes.py | 43 +++--- daemon/core/session.py | 110 +++++++------- 8 files changed, 296 insertions(+), 122 deletions(-) diff --git a/daemon/core/emane/bypass.py b/daemon/core/emane/bypass.py index 0725c4ef..044c3ed2 100644 --- a/daemon/core/emane/bypass.py +++ b/daemon/core/emane/bypass.py @@ -27,7 +27,7 @@ class EmaneBypassModel(EmaneModel): ] # value groupings - _confgroups = "Bypass Parameters:1-1" + _confgroups = "Bypass Parameters:1-1" def buildnemxmlfiles(self, e, ifc): ''' Build the necessary nem, mac, and phy XMLs in the given path. @@ -42,6 +42,7 @@ class EmaneBypassModel(EmaneModel): nemdoc = e.xmldoc("nem") nem = nemdoc.getElementsByTagName("nem").pop() nem.setAttribute("name", "BYPASS NEM") + e.appendtransporttonem(nemdoc, nem, self.objid) mactag = nemdoc.createElement("mac") mactag.setAttribute("definition", self.macxmlname(ifc)) nem.appendChild(mactag) diff --git a/daemon/core/emane/commeffect.py b/daemon/core/emane/commeffect.py index e796ebd2..239b9da9 100755 --- a/daemon/core/emane/commeffect.py +++ b/daemon/core/emane/commeffect.py @@ -95,6 +95,7 @@ class EmaneCommEffectModel(EmaneModel): nem = nemdoc.getElementsByTagName("nem").pop() nem.setAttribute("name", "commeffect NEM") nem.setAttribute("type", "unstructured") + e.appendtransporttonem(nemdoc, nem, self.objid) nem.appendChild(e.xmlshimdefinition(nemdoc, self.shimxmlname(ifc))) e.xmlwrite(nemdoc, self.nemxmlname(ifc)) diff --git a/daemon/core/emane/emane.py b/daemon/core/emane/emane.py index 56db4d93..8db329c2 100644 --- a/daemon/core/emane/emane.py +++ b/daemon/core/emane/emane.py @@ -32,6 +32,14 @@ try: from emanesh.events import LocationEvent except Exception, e: pass +# EMANE 0.9.2 +try: + from emanesh import remotecontrolportapi_pb2 + HAVE092 = ('TYPE_COMPONENT_TRANSPORT' in + dir(remotecontrolportapi_pb2.Response.Query.Manifest.NEM.Component)) + del remotecontrolportapi_pb2 +except Exception, e: + HAVE092 = False class Emane(ConfigurableManager): @@ -67,7 +75,7 @@ class Emane(ConfigurableManager): self.emane_config = EmaneGlobalModel(session, None, self.verbose) session.broker.handlers += (self.handledistributed, ) self.loadmodels() - self.initeventservice() + self.service = None def detectversion(self): ''' Detects the installed EMANE version and sets self.version. @@ -81,6 +89,7 @@ class Emane(ConfigurableManager): ''' # for further study: different EMANE versions on distributed machines try: + # TODO: fix BUG here -- killall may kill this process too status, result = cmdresult(['emane', '--version']) except OSError: status = -1 @@ -111,17 +120,16 @@ class Emane(ConfigurableManager): self.emane_config.getdefaultvalues())[1] group, port = self.emane_config.valueof('eventservicegroup', values).split(':') - dev = self.emane_config.valueof('eventservicedevice', values) - otachannel = None - ota_enable = self.emane_config.valueof('otamanagerchannelenable', - values) - if self.emane_config.offontobool(ota_enable): - ogroup, oport = self.emane_config.valueof('otamanagergroup', - values).split(':') - odev = self.emane_config.valueof('otamanagerdevice', values) - otachannel = (ogroup, int(oport), odev) + if self.version > self.EMANE091 and \ + 'ctrlnet' in self.session._objs: + # direct EMANE events towards control net bridge + dev = self.session.obj('ctrlnet').brname + else: + dev = self.emane_config.valueof('eventservicedevice', values) + # disabled otachannel for event service + # only needed for e.g. antennaprofile events xmit by models self.service = EventService(eventchannel=(group, int(port), dev), - otachannel=otachannel) + otachannel=None) return True if filename is not None: tmp = os.getenv(self.EVENTCFGVAR) @@ -181,6 +189,17 @@ class Emane(ConfigurableManager): self._objs[obj.objid] = obj self._objslock.release() + def getnodes(self): + ''' Return a set of CoreNodes that are linked to an EmaneNode, + e.g. containers having one or more radio interfaces. + ''' + # assumes self._objslock already held + r = set() + for e in self._objs.values(): + for netif in e.netifs(): + r.add(netif.node) + return r + def getmodels(self, n): ''' Used with XML export; see ConfigurableManager.getmodels() ''' @@ -238,14 +257,25 @@ class Emane(ConfigurableManager): r = self.setup() if r != Emane.SUCCESS: return r # NOT_NEEDED or NOT_READY + if self.versionstr == "": + raise ValueError, "EMANE version not properly detected" with self._objslock: - self.buildxml() - self.starteventmonitor() - if self.numnems() > 0: - # TODO: check and return failure for these methods - self.startdaemons() - self.installnetifs() - return Emane.SUCCESS + if self.version < self.EMANE092: + self.buildxml() + self.initeventservice() + self.starteventmonitor() + if self.numnems() > 0: + # TODO: check and return failure for these methods + self.startdaemons() + self.installnetifs() + else: + self.buildxml2() + self.initeventservice() + self.starteventmonitor() + if self.numnems() > 0: + self.startdaemons2() + self.installnetifs(do_netns=False) + return Emane.SUCCESS def poststartup(self): ''' Retransmit location events now that all NEMs are active. @@ -369,7 +399,9 @@ class Emane(ConfigurableManager): return False def buildxml(self): - ''' Build all of the XML files required to run EMANE. + ''' Build all of the XML files required to run EMANE on the host. + NEMs run in a single host emane process, with TAP devices pushed + into namespaces. ''' # assume self._objslock is already held here if self.verbose: @@ -379,6 +411,20 @@ class Emane(ConfigurableManager): self.buildtransportxml() self.buildeventservicexml() + def buildxml2(self): + ''' Build XML files required to run EMANE on each node. + NEMs run inside containers using the control network for passing + events and data. + ''' + # control network bridge needs to exist when eventservice binds to it + self.session.addremovectrlif(node=None, remove=False, conf_reqd=False) + # assume self._objslock is already held here + if self.verbose: + self.info("Emane.buildxml2()") + self.buildplatformxml2() + self.buildnemxml() + self.buildeventservicexml() + def xmldoc(self, doctype): ''' Returns an XML xml.minidom.Document with a DOCTYPE tag set to the provided doctype string, and an initial element having the same @@ -473,8 +519,8 @@ class Emane(ConfigurableManager): self.emane_config.getdefaultvalues())[1] doc = self.xmldoc("platform") plat = doc.getElementsByTagName("platform").pop() - platformid = self.emane_config.valueof("platform_id_start", values) if self.version < self.EMANE091: + platformid = self.emane_config.valueof("platform_id_start", values) plat.setAttribute("name", "Platform %s" % platformid) plat.setAttribute("id", platformid) @@ -521,6 +567,47 @@ class Emane(ConfigurableManager): self.transformport += 1 self.xmlwrite(doc, "platform.xml") + def newplatformxmldoc(self, values): + doc = self.xmldoc("platform") + plat = doc.getElementsByTagName("platform").pop() + names = list(self.emane_config.getnames()) + platform_names = names[:len(self.emane_config._confmatrix_platform)] + platform_names.remove('platform_id_start') + # append all platform options (except starting id) to doc + map( lambda n: plat.appendChild(self.xmlparam(doc, n, \ + self.emane_config.valueof(n, values))), platform_names) + return doc + + def buildplatformxml2(self): + ''' Build a platform.xml file now that all nodes are configured. + ''' + values = self.getconfig(None, "emane", + self.emane_config.getdefaultvalues())[1] + nemid = int(self.emane_config.valueof("nem_id_start", values)) + platformxmls = {} + + # assume self._objslock is already held here + for n in sorted(self._objs.keys()): + emanenode = self._objs[n] + nems = emanenode.buildplatformxmlentry(self.xmldoc("platform")) + for netif in sorted(nems, key=lambda n: n.node.objid): + # set ID, endpoints here + nementry = nems[netif] + nementry.setAttribute("id", "%d" % nemid) + nodenum = netif.node.objid + if nodenum not in platformxmls: + platformxmls[nodenum] = self.newplatformxmldoc(values) + doc = platformxmls[nodenum] + plat = doc.getElementsByTagName("platform").pop() + plat.appendChild(nementry) + emanenode.setnemid(netif, nemid) + macstr = self._hwaddr_prefix + ":00:00:" + macstr += "%02X:%02X" % ((nemid >> 8) & 0xFF, nemid & 0xFF) + netif.sethwaddr(MacAddr.fromstring(macstr)) + nemid += 1 + for nodenum in sorted(platformxmls.keys()): + self.xmlwrite(platformxmls[nodenum], "platform%d.xml" % nodenum) + def buildnemxml(self): ''' Builds the xxxnem.xml, xxxmac.xml, and xxxphy.xml files which are defined on a per-EmaneNode basis. @@ -529,6 +616,19 @@ class Emane(ConfigurableManager): emanenode = self._objs[n] nems = emanenode.buildnemxmlfiles(self) + def appendtransporttonem(self, doc, nem, nodenum): + ''' Given a nem XML node and EMANE WLAN node number, append + a tag to the NEM definition, required for using + EMANE's internal transport. + ''' + if self.version < self.EMANE092: + return + emanenode = self._objs[nodenum] + transtag = doc.createElement("transport") + transtag.setAttribute("definition", + emanenode.transportxmlname("virtual")) + nem.appendChild(transtag) + def buildtransportxml(self): ''' Calls emanegentransportxml using a platform.xml file to build the transportdaemon*.xml. @@ -624,15 +724,70 @@ class Emane(ConfigurableManager): None, errmsg) self.info(errmsg) + def startdaemons2(self): + ''' Start one EMANE daemon per node having a radio. + Add a control network even if the user has not configured one. + ''' + if self.verbose: + self.info("Emane.startdaemons()") + loglevel = "2" + cfgloglevel = self.session.getcfgitemint("emane_log_level") + realtime = self.session.getcfgitembool("emane_realtime", True) + if cfgloglevel: + self.info("setting user-defined EMANE log level: %d" % cfgloglevel) + loglevel = str(cfgloglevel) + emanecmd = ["emane", "-d", "--logl", loglevel] + if realtime: + emanecmd += "-r", + + values = self.getconfig(None, "emane", + self.emane_config.getdefaultvalues())[1] + otagroup, otaport = self.emane_config.valueof('otamanagergroup', + values).split(':') + otadev = self.emane_config.valueof('otamanagerdevice', values) + + for node in self.getnodes(): + path = self.session.sessiondir + n = node.objid + # control network not yet started here + self.session.addremovectrlif(node, remove=False, conf_reqd=False) + # multicast route is needed for OTA data on ctrl0 + cmd = [IP_BIN, "route", "add", otagroup, "dev", otadev] + #rc = node.cmd(cmd, wait=True) + node.cmd(cmd, wait=True) + + try: + cmd = emanecmd + ["-f", os.path.join(path, "emane%d.log" % n), + os.path.join(path, "platform%d.xml" % n)] + if self.verbose: + self.info("Emane.startdaemons() running %s" % str(cmd)) + #node.cmd(cmd, cwd=path, wait=True) + #status, result = node.cmdresult(cmd, cwd=path, wait=True) + status = node.cmd(cmd, wait=True) + if self.verbose: + self.info("Emane.startdaemons2() return code %d" % status) + except Exception, e: + errmsg = "error starting emane: %s" % e + self.session.exception(coreapi.CORE_EXCP_LEVEL_FATAL, "emane", + n, errmsg) + self.info(errmsg) + def stopdaemons(self): ''' Kill the appropriate EMANE daemons. ''' # TODO: we may want to improve this if we had the PIDs from the # specific EMANE daemons that we've started - subprocess.call(["killall", "-q", "emane"]) - subprocess.call(["killall", "-q", "emanetransportd"]) + cmd = ["killall", "-q", "emane"] + if self.version > self.EMANE091: + for node in self.getnodes(): + if node.up: + node.cmd(cmd, wait=False) + # TODO: RJ45 node + else: + subprocess.call(cmd) + subprocess.call(["killall", "-q", "emanetransportd"]) - def installnetifs(self): + def installnetifs(self, do_netns=True): ''' Install TUN/TAP virtual interfaces into their proper namespaces now that the EMANE daemons are running. ''' @@ -640,7 +795,7 @@ class Emane(ConfigurableManager): emanenode = self._objs[n] if self.verbose: self.info("Emane.installnetifs() for node %d" % n) - emanenode.installnetifs() + emanenode.installnetifs(do_netns) def deinstallnetifs(self): ''' Uninstall TUN/TAP virtual interfaces. @@ -893,7 +1048,8 @@ class EmaneModel(WirelessModel): type = "raw" trans = doc.createElement("transport") trans.setAttribute("definition", n.transportxmlname(type)) - trans.setAttribute("group", "1") + if self.session.emane.version < self.session.emane.EMANE092: + trans.setAttribute("group", "1") param = doc.createElement("param") param.setAttribute("name", "device") if type == "raw": @@ -902,6 +1058,9 @@ class EmaneModel(WirelessModel): else: # virtual TAP name e.g. 'n3.0.17' param.setAttribute("value", ifc.localname) + if self.session.emane.version > self.session.emane.EMANE091: + param.setAttribute("value", ifc.name) + trans.appendChild(param) return trans @@ -967,6 +1126,8 @@ class EmaneModel(WirelessModel): values = maketuplefromstr(value, str) except SyntaxError: return None + if not hasattr(values, '__iter__'): + return None if len(values) < 2: return None return addparamlisttoparent(dom, parent=None, name=name, values=values) @@ -978,17 +1139,24 @@ class EmaneGlobalModel(EmaneModel): def __init__(self, session, objid = None, verbose = False): EmaneModel.__init__(self, session, objid, verbose) + # Over-The-Air channel required for EMANE 0.9.2 + _DEFAULT_OTA = '0' + _DEFAULT_DEV = 'lo' + if HAVE092: + _DEFAULT_OTA = '1' + _DEFAULT_DEV = 'ctrl0' + _name = "emane" _confmatrix_platform_base = [ - ("otamanagerchannelenable", coreapi.CONF_DATA_TYPE_BOOL, '0', + ("otamanagerchannelenable", coreapi.CONF_DATA_TYPE_BOOL, _DEFAULT_OTA, 'on,off', 'enable OTA Manager channel'), ("otamanagergroup", coreapi.CONF_DATA_TYPE_STRING, '224.1.2.8:45702', '', 'OTA Manager group'), - ("otamanagerdevice", coreapi.CONF_DATA_TYPE_STRING, 'lo', + ("otamanagerdevice", coreapi.CONF_DATA_TYPE_STRING, _DEFAULT_DEV, '', 'OTA Manager device'), ("eventservicegroup", coreapi.CONF_DATA_TYPE_STRING, '224.1.2.8:45703', '', 'Event Service group'), - ("eventservicedevice", coreapi.CONF_DATA_TYPE_STRING, 'lo', + ("eventservicedevice", coreapi.CONF_DATA_TYPE_STRING, _DEFAULT_DEV, '', 'Event Service device'), ("platform_id_start", coreapi.CONF_DATA_TYPE_INT32, '1', '', 'starting Platform ID'), @@ -1013,14 +1181,20 @@ class EmaneGlobalModel(EmaneModel): ("nem_id_start", coreapi.CONF_DATA_TYPE_INT32, '1', '', 'starting NEM ID'), ] + _confmatrix_nem_092 = [ + ("nem_id_start", coreapi.CONF_DATA_TYPE_INT32, '1', + '', 'starting NEM ID'), + ] + if 'EventService' in globals(): _confmatrix_platform = _confmatrix_platform_base + \ _confmatrix_platform_091 + if HAVE092: + _confmatrix_nem = _confmatrix_nem_092 else: _confmatrix_platform = _confmatrix_platform_base + \ _confmatrix_platform_081 _confmatrix = _confmatrix_platform + _confmatrix_nem _confgroups = "Platform Attributes:1-%d|NEM Parameters:%d-%d" % \ (len(_confmatrix_platform), len(_confmatrix_platform) + 1, - len(_confmatrix)) - + len(_confmatrix)) \ No newline at end of file diff --git a/daemon/core/emane/ieee80211abg.py b/daemon/core/emane/ieee80211abg.py index 3e9c06ba..ad24c134 100644 --- a/daemon/core/emane/ieee80211abg.py +++ b/daemon/core/emane/ieee80211abg.py @@ -52,9 +52,9 @@ class EmaneIeee80211abgModel(EmaneModel): ("pcrcurveuri", coreapi.CONF_DATA_TYPE_STRING, '%s/ieee80211pcr.xml' % xml_path, '', 'SINR/PCR curve file'), - ("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0', + ("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0', 'On,Off', 'enable traffic flow control'), - ("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10', + ("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10', '', 'number of flow control tokens'), ] _confmatrix_mac_081 = [ @@ -79,7 +79,7 @@ class EmaneIeee80211abgModel(EmaneModel): else: _confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_081 # PHY parameters from Universal PHY - _confmatrix_phy = EmaneUniversalModel._confmatrix + _confmatrix_phy = EmaneUniversalModel._confmatrix _confmatrix = _confmatrix_mac + _confmatrix_phy # value groupings @@ -93,20 +93,14 @@ class EmaneIeee80211abgModel(EmaneModel): nXXemane_ieee80211abgnem.xml, nXXemane_ieee80211abgemac.xml, nXXemane_ieee80211abgphy.xml are used. ''' - # use the network-wide config values or interface(NEM)-specific values? - if ifc is None: - values = e.getconfig(self.objid, self._name, - self.getdefaultvalues())[1] - else: - nodenum = ifc.node.objid - values = e.getconfig(nodenum, self._name, None)[1] - if values is None: - # do not build specific files for this NEM when config is same - # as the network - return + values = e.getifcconfig(self.objid, self._name, + self.getdefaultvalues(), ifc) + if values is None: + return nemdoc = e.xmldoc("nem") nem = nemdoc.getElementsByTagName("nem").pop() nem.setAttribute("name", "ieee80211abg NEM") + e.appendtransporttonem(nemdoc, nem, self.objid) mactag = nemdoc.createElement("mac") mactag.setAttribute("definition", self.macxmlname(ifc)) nem.appendChild(mactag) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index 3dbe871e..bb5fac8d 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -197,7 +197,7 @@ class EmaneNode(EmaneNet): return "n%strans%s.xml" % (self.objid, type) - def installnetifs(self): + def installnetifs(self, do_netns=True): ''' Install TAP devices into their namespaces. This is done after EMANE daemons have been started, because that is their only chance to bind to the TAPs. @@ -210,7 +210,7 @@ class EmaneNode(EmaneNet): self.objid, warntxt) for netif in self.netifs(): - if "virtual" in netif.transport_type.lower(): + if do_netns and "virtual" in netif.transport_type.lower(): netif.install() # if we are listening for EMANE events, don't generate them if self.session.emane.doeventmonitor(): diff --git a/daemon/core/emane/rfpipe.py b/daemon/core/emane/rfpipe.py index a85c6220..9c96da43 100644 --- a/daemon/core/emane/rfpipe.py +++ b/daemon/core/emane/rfpipe.py @@ -86,6 +86,7 @@ class EmaneRfPipeModel(EmaneModel): nemdoc = e.xmldoc("nem") nem = nemdoc.getElementsByTagName("nem").pop() nem.setAttribute("name", "RF-PIPE NEM") + e.appendtransporttonem(nemdoc, nem, self.objid) mactag = nemdoc.createElement("mac") mactag.setAttribute("definition", self.macxmlname(ifc)) nem.appendChild(mactag) diff --git a/daemon/core/netns/nodes.py b/daemon/core/netns/nodes.py index e8b784b3..50b453b3 100644 --- a/daemon/core/netns/nodes.py +++ b/daemon/core/netns/nodes.py @@ -20,6 +20,7 @@ from core.coreobj import PyCoreNode class CtrlNet(LxBrNet): policy = "ACCEPT" CTRLIF_IDX_BASE = 99 # base control interface index + DEFAULT_PREFIX = "172.16.0.0/24" def __init__(self, session, objid = "ctrlnet", name = None, verbose = False, netid = 1, prefix = None, @@ -168,7 +169,7 @@ class HubNode(LxBrNet): apitype = coreapi.CORE_NODE_HUB policy = "ACCEPT" type = "hub" - + def __init__(self, session, objid = None, name = None, verbose = False, start = True): ''' the Hub node forwards packets to all bridge ports by turning off @@ -184,7 +185,7 @@ class WlanNode(LxBrNet): linktype = coreapi.CORE_LINK_WIRELESS policy = "DROP" type = "wlan" - + def __init__(self, session, objid = None, name = None, verbose = False, start = True, policy = None): LxBrNet.__init__(self, session, objid, name, verbose, start, policy) @@ -192,7 +193,7 @@ class WlanNode(LxBrNet): self.model = None # mobility model such as scripted self.mobility = None - + def attach(self, netif): LxBrNet.attach(self, netif) if self.model: @@ -203,7 +204,7 @@ class WlanNode(LxBrNet): # invokes any netif.poshook netif.setposition(x, y, z) #self.model.setlinkparams() - + def setmodel(self, model, config): ''' Mobility and wireless model. ''' @@ -222,7 +223,7 @@ class WlanNode(LxBrNet): elif model._type == coreapi.CORE_TLV_REG_MOBILITY: self.mobility = model(session=self.session, objid=self.objid, verbose=self.verbose, values=config) - + def updatemodel(self, model_name, values): ''' Allow for model updates during runtime (similar to setmodel().) ''' @@ -241,7 +242,7 @@ class WlanNode(LxBrNet): (x,y,z) = netif.node.position.get() netif.poshook(netif, x, y, z) self.model.setlinkparams() - + def tolinkmsgs(self, flags): msgs = LxBrNet.tolinkmsgs(self, flags) if self.model: @@ -255,7 +256,7 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): ''' apitype = coreapi.CORE_NODE_RJ45 type = "rj45" - + def __init__(self, session, objid = None, name = None, mtu = 1500, verbose = False, start = True): PyCoreNode.__init__(self, session, objid, name, verbose=verbose, @@ -283,7 +284,7 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): (IP_BIN, self.localname)) return self.up = True - + def shutdown(self): ''' Bring the interface down. Remove any addresses and queuing disciplines. @@ -295,16 +296,16 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): mutecall([TC_BIN, "qdisc", "del", "dev", self.localname, "root"]) self.up = False self.restorestate() - + def attachnet(self, net): PyCoreNetIf.attachnet(self, net) - + def detachnet(self): PyCoreNetIf.detachnet(self) def newnetif(self, net = None, addrlist = [], hwaddr = None, - ifindex = None, ifname = None): - ''' This is called when linking with another node. Since this node + 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. ''' @@ -325,7 +326,7 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): return ifindex finally: self.lock.release() - + def delnetif(self, ifindex): if ifindex is None: ifindex = 0 @@ -349,12 +350,12 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): if ifindex == self.ifindex: return self return None - + def getifindex(self, netif): if netif != self: return None return self.ifindex - + def addaddr(self, addr): if self.up: check_call([IP_BIN, "addr", "add", str(addr), "dev", self.name]) @@ -364,7 +365,7 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): if self.up: check_call([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 @@ -395,7 +396,7 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): 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. ''' @@ -404,19 +405,19 @@ class RJ45Node(PyCoreNode, PyCoreNetIf): check_call([IP_BIN, "addr", "add", addr[0], "dev", self.localname]) else: - check_call([IP_BIN, "addr", "add", addr[0], "brd", addr[1], + check_call([IP_BIN, "addr", "add", addr[0], "brd", addr[1], "dev", self.localname]) if self.old_up: check_call([IP_BIN, "link", "set", self.localname, "up"]) - + def setposition(self, x=None, y=None, z=None): ''' Use setposition() from both parent classes. ''' PyCoreObj.setposition(self, x, y, z) # invoke any poshook PyCoreNetIf.setposition(self, x, y, z) - - + + diff --git a/daemon/core/session.py b/daemon/core/session.py index 73f14d59..ba962d89 100644 --- a/daemon/core/session.py +++ b/daemon/core/session.py @@ -201,7 +201,7 @@ class Session(object): except Exception, e: self.warn("sendall() error: %s" % e) self._handlerslock.release() - + def gethandler(self): ''' Get one of the connected handlers, preferrably the master. ''' @@ -214,7 +214,7 @@ class Session(object): for handler in self._handlers: return handler - def setstate(self, state, info = False, sendevent = False, + def setstate(self, state, info = False, sendevent = False, returnevent = False): ''' Set the session state. When info is true, log the state change event using the session handler's info method. When sendevent is @@ -255,7 +255,7 @@ class Session(object): ''' Retrieve the current state of the session. ''' return self._state - + def writestate(self, state): ''' Write the current state to a state file in the session dir. ''' @@ -286,9 +286,9 @@ class Session(object): check_call(["/bin/sh", filename], cwd=self.sessiondir, env=self.getenviron()) except Exception, e: - self.warn("Error running hook '%s' for state %s: %s" % + self.warn("Error running hook '%s' for state %s: %s" % (filename, state, e)) - + def sethook(self, type, filename, srcname, data): ''' Store a hook from a received File Message. ''' @@ -308,12 +308,12 @@ class Session(object): # (this allows hooks in the definition and configuration states) if self.getstate() == state: self.runhook(state, hooks = [hook,]) - + def delhooks(self): ''' Clear the hook scripts dict. ''' self._hooks = {} - + def getenviron(self, state=True): ''' Get an environment suitable for a subprocess.Popen call. This is the current process environment with some session-specific @@ -362,14 +362,14 @@ class Session(object): gid = os.stat(self.sessiondir).st_gid os.chown(self.sessiondir, uid, gid) except Exception, e: - self.warn("Failed to set permission on %s: %s" % (self.sessiondir, e)) + self.warn("Failed to set permission on %s: %s" % (self.sessiondir, e)) self.user = user def objs(self): ''' Return iterator over the emulation object dictionary. ''' return self._objs.itervalues() - + def getobjid(self): ''' Return a unique, random object id. ''' @@ -400,7 +400,7 @@ class Session(object): if objid not in self._objs: raise KeyError, "unknown object id %s" % (objid) return self._objs[objid] - + def objbyname(self, name): ''' Get an emulation object using its name attribute. ''' @@ -438,7 +438,7 @@ class Session(object): k, o = self._objs.popitem() o.shutdown() self._objslock.release() - + def writeobjs(self): ''' Write objects to a 'nodes' file in the session dir. The 'nodes' file lists: @@ -524,15 +524,15 @@ class Session(object): num = len(self._objs) self.info(" file=%s thumb=%s nc=%s/%s" % \ (self.filename, self.thumbnail, self.node_count, num)) - + def exception(self, level, source, objid, text): - ''' Generate an Exception Message + ''' Generate an Exception Message ''' vals = (objid, str(self.sessionid), level, source, time.ctime(), text) types = ("NODE", "SESSION", "LEVEL", "SOURCE", "DATE", "TEXT") tlvdata = "" for (t,v) in zip(types, vals): - if v is not None: + if v is not None: tlvdata += coreapi.CoreExceptionTlv.pack( eval("coreapi.CORE_TLV_EXCP_%s" % t), v) msg = coreapi.CoreExceptionMessage.pack(0, tlvdata) @@ -589,7 +589,7 @@ class Session(object): # nodes on slave servers that will be booted and those servers will # send a node status response message self.checkruntime() - + def getnodecount(self): ''' Returns the number of CoreNodes and CoreNets, except for those that are not considered in the GUI's node count. @@ -599,7 +599,7 @@ class Session(object): count = len(filter(lambda(x): \ not isinstance(x, (nodes.PtpNet, nodes.CtrlNet)), self.objs())) - # on Linux, GreTapBridges are auto-created, not part of + # on Linux, GreTapBridges are auto-created, not part of # GUI's node count if 'GreTapBridge' in globals(): count -= len(filter(lambda(x): \ @@ -624,7 +624,7 @@ class Session(object): nc = self.getnodecount() # count booted nodes not emulated on this server # TODO: let slave server determine RUNTIME and wait for Event Message - # broker.getbootocunt() counts all CoreNodes from status reponse + # broker.getbootocunt() counts all CoreNodes from status reponse # messages, plus any remote WLANs; remote EMANE, hub, switch, etc. # are already counted in self._objs nc += self.broker.getbootcount() @@ -653,7 +653,7 @@ class Session(object): self.updatectrlifhosts(remove=True) self.addremovectrlif(node=None, remove=True) # self.checkshutdown() is currently invoked from node delete handler - + def checkshutdown(self): ''' Check if we have entered the shutdown state, when no running nodes and links remain. @@ -665,7 +665,7 @@ class Session(object): # can enter SHUTDOWN replies = () if nc == 0: - replies = self.setstate(state=coreapi.CORE_EVENT_SHUTDOWN_STATE, + replies = self.setstate(state=coreapi.CORE_EVENT_SHUTDOWN_STATE, info=True, sendevent=True, returnevent=True) self.sdt.shutdown() return replies @@ -682,13 +682,13 @@ class Session(object): self.master = h.master return True return False - + def shortsessionid(self): ''' Return a shorter version of the session ID, appropriate for interface names, where length may be limited. ''' return (self.sessionid >> 8) ^ (self.sessionid & ((1 << 8) - 1)) - + def sendnodeemuid(self, handler, nodenum): ''' Send back node messages to the GUI for node messages that had the status request flag. @@ -711,7 +711,7 @@ class Session(object): del handler.nodestatusreq[nodenum] def bootnodes(self, handler): - ''' Invoke the boot() procedure for all nodes and send back node + ''' Invoke the boot() procedure for all nodes and send back node messages to the GUI for node messages that had the status request flag. ''' @@ -724,7 +724,7 @@ class Session(object): n.boot() self.sendnodeemuid(handler, n.objid) self.updatectrlifhosts() - + def validatenodes(self): with self._objslock: for n in self.objs(): @@ -735,23 +735,21 @@ class Session(object): if isinstance(n, nodes.RJ45Node): continue n.validate() - - def addremovectrlnet(self, remove=False): - ''' Create a control network bridge as necessary. + + def addremovectrlnet(self, remove=False, conf_reqd=True): + ''' Create a control network bridge as necessary. When the remove flag is True, remove the bridge that connects control interfaces. ''' - prefix = None - try: - if self.cfg['controlnet']: - prefix = self.cfg['controlnet'] - except KeyError: - pass + prefix = self.cfg.get('controlnet') if hasattr(self.options, 'controlnet'): prefix = self.options.controlnet if not prefix: - return None # no controlnet needed - + if conf_reqd: + return None # no controlnet needed + else: + prefix = nodes.CtrlNet.DEFAULT_PREFIX + # return any existing controlnet bridge id = "ctrlnet" try: @@ -776,7 +774,7 @@ class Session(object): new_uds = self.options.controlnet_updown_script if new_uds: updown_script = new_uds - + prefixes = prefix.split() if len(prefixes) > 1: assign_address = True @@ -814,16 +812,20 @@ class Session(object): self.broker.addnodemap(server, id) return ctrlnet - def addremovectrlif(self, node, remove=False): + def addremovectrlif(self, node, remove=False, conf_reqd=True): ''' Add a control interface to a node when a 'controlnet' prefix is listed in the config file or session options. Uses addremovectrlnet() to build or remove the control bridge. + If conf_reqd is False, the control network may be built even + when the user has not configured one (e.g. for EMANE.) ''' - ctrlnet = self.addremovectrlnet(remove) + ctrlnet = self.addremovectrlnet(remove, conf_reqd) if ctrlnet is None: return if node is None: return + if node.netif(ctrlnet.CTRLIF_IDX_BASE): + return # ctrl0 already exists ctrlip = node.objid try: addrlist = ["%s/%s" % (ctrlnet.prefix.addr(ctrlip), @@ -873,7 +875,7 @@ class Session(object): return time.time() - self._time else: return 0.0 - + def addevent(self, etime, node=None, name=None, data=None): ''' Add an event to the event queue, with a start time relative to the start of the runtime state. @@ -905,7 +907,7 @@ class Session(object): else: n = self.obj(node) n.cmd(shlex.split(data), wait=False) - + def sendobjs(self): ''' Return API messages that describe the current session. ''' @@ -983,7 +985,7 @@ class Session(object): typeflags = coreapi.CONF_TYPE_FLAGS_UPDATE) if meta: replies.append(meta) - + self.info("informing GUI about %d nodes and %d links" % (nn, nl)) return replies @@ -994,25 +996,25 @@ class SessionConfig(ConfigurableManager, Configurable): _type = coreapi.CORE_TLV_REG_UTILITY _confmatrix = [ ("controlnet", coreapi.CONF_DATA_TYPE_STRING, '', '', - 'Control network'), + 'Control network'), ("controlnet_updown_script", coreapi.CONF_DATA_TYPE_STRING, '', '', - 'Control network script'), - ("enablerj45", coreapi.CONF_DATA_TYPE_BOOL, '1', 'On,Off', + 'Control network script'), + ("enablerj45", coreapi.CONF_DATA_TYPE_BOOL, '1', 'On,Off', 'Enable RJ45s'), ("preservedir", coreapi.CONF_DATA_TYPE_BOOL, '0', 'On,Off', 'Preserve session dir'), - ("enablesdt", coreapi.CONF_DATA_TYPE_BOOL, '0', 'On,Off', + ("enablesdt", coreapi.CONF_DATA_TYPE_BOOL, '0', 'On,Off', 'Enable SDT3D output'), ("sdturl", coreapi.CONF_DATA_TYPE_STRING, Sdt.DEFAULT_SDT_URL, '', 'SDT3D URL'), ] _confgroups = "Options:1-%d" % len(_confmatrix) - + def __init__(self, session): ConfigurableManager.__init__(self, session) session.broker.handlers += (self.handledistributed, ) self.reset() - + def reset(self): defaults = self.getdefaultvalues() for k in self.getnames(): @@ -1022,9 +1024,9 @@ class SessionConfig(ConfigurableManager, Configurable): v = self.valueof(k, defaults) v = self.offontobool(v) setattr(self, k, v) - + def configure_values(self, msg, values): - return self.configure_values_keyvalues(msg, values, self, + return self.configure_values_keyvalues(msg, values, self, self.getnames()) def configure_request(self, msg, typeflags = coreapi.CONF_TYPE_FLAGS_NONE): @@ -1036,7 +1038,7 @@ class SessionConfig(ConfigurableManager, Configurable): v = "" values.append("%s" % v) return self.toconfmsg(0, nodenum, typeflags, values) - + def handledistributed(self, msg): ''' Handle the session options config message as it has reached the broker. Options requiring modification for distributed operation should @@ -1057,7 +1059,7 @@ class SessionConfig(ConfigurableManager, Configurable): key, value = v.split('=', 1) if key == "controlnet": self.handledistributedcontrolnet(msg, values, values.index(v)) - + def handledistributedcontrolnet(self, msg, values, idx): ''' Modify Config Message if multiple control network prefixes are defined. Map server names to prefixes and repack the message before @@ -1068,7 +1070,7 @@ class SessionConfig(ConfigurableManager, Configurable): controlnets = value.split() if len(controlnets) < 2: return # multiple controlnet prefixes do not exist - + servers = self.session.broker.getserverlist() if len(servers) < 2: return # not distributed @@ -1102,7 +1104,7 @@ class SessionMetaData(ConfigurableManager): raise ValueError, "invalid key in metdata: %s" % kv self.additem(key, value) return None - + def configure_request(self, msg, typeflags = coreapi.CONF_TYPE_FLAGS_NONE): nodenum = msg.gettlv(coreapi.CORE_TLV_CONF_NODE) values_str = "|".join(map(lambda(k,v): "%s=%s" % (k,v), self.items())) @@ -1125,10 +1127,10 @@ class SessionMetaData(ConfigurableManager): values_str) msg = coreapi.CoreConfMessage.pack(flags, tlvdata) return msg - + def additem(self, key, value): self.configs[key] = value - + def items(self): return self.configs.iteritems()