From 405fe50ff5eaded8b6b3172cab2ddb7430dafeae Mon Sep 17 00:00:00 2001 From: Rod A Santiago Date: Tue, 26 Apr 2016 15:07:55 -0700 Subject: [PATCH 01/28] comment out print_stack call in linkconfig --- daemon/core/netns/vnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/core/netns/vnet.py b/daemon/core/netns/vnet.py index d09d59ea..2c364fbb 100644 --- a/daemon/core/netns/vnet.py +++ b/daemon/core/netns/vnet.py @@ -348,8 +348,8 @@ class LxBrNet(PyCoreNet): interface. ''' - sys.stderr.write("enter linkconfig() ...\n") - traceback.print_stack() + #sys.stderr.write("enter linkconfig() ...\n") + #traceback.print_stack() if devname is None: devname = netif.localname From f03311b50a57a1f55ea82cfef450f0547081d01d Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Mon, 5 Sep 2016 17:02:59 -0400 Subject: [PATCH 02/28] daemon: Add support for EMANE 1.0.1. --- daemon/core/emane/emane.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/daemon/core/emane/emane.py b/daemon/core/emane/emane.py index 69ed3be1..078ca1c0 100644 --- a/daemon/core/emane/emane.py +++ b/daemon/core/emane/emane.py @@ -45,8 +45,8 @@ class Emane(ConfigurableManager): (SUCCESS, NOT_NEEDED, NOT_READY) = (0, 1, 2) EVENTCFGVAR = 'LIBEMANEEVENTSERVICECONFIG' # possible self.version values - (EMANEUNK, EMANE074, EMANE081, EMANE091, EMANE092, EMANE093) = \ - (0, 7, 8, 91, 92, 93) + (EMANEUNK, EMANE074, EMANE081, EMANE091, EMANE092, EMANE093, EMANE101) = \ + (0, 7, 8, 91, 92, 93, 101) DEFAULT_LOG_LEVEL = 3 def __init__(self, session): @@ -1175,6 +1175,8 @@ def emane_version(): v = Emane.EMANE092 elif result.startswith('0.9.3'): v = Emane.EMANE093 + elif result.startswith('1.0.1'): + v = Emane.EMANE101 return v, result.strip() # set version variables for the Emane class From d270da8f414f39bdca4dbb6c7f4580d6919b0b81 Mon Sep 17 00:00:00 2001 From: adamson <> Date: Mon, 5 Sep 2016 17:05:13 -0400 Subject: [PATCH 03/28] added daemon/core/emane/tdma.py module to support EMANE 1.0.1 TDMA model --- daemon/core/emane/rfpipe.py | 4 +- daemon/core/emane/tdma.py | 113 ++++++++++++++++++++++++++++++++++++ daemon/core/services/nrl.py | 2 +- 3 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 daemon/core/emane/tdma.py diff --git a/daemon/core/emane/rfpipe.py b/daemon/core/emane/rfpipe.py index 36722524..6eb329e9 100644 --- a/daemon/core/emane/rfpipe.py +++ b/daemon/core/emane/rfpipe.py @@ -124,8 +124,8 @@ class EmaneRfPipeModel(EmaneModel): values = list(values) values[i] = self.emane074_fixup(values[i], 1000) # append MAC options to macdoc - map( lambda n: mac.appendChild(e.xmlparam(macdoc, n, \ - self.valueof(n, values))), macnames) + map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, \ + self.valueof(n, values))), macnames) e.xmlwrite(macdoc, self.macxmlname(ifc)) phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames) diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py new file mode 100644 index 00000000..6c82d595 --- /dev/null +++ b/daemon/core/emane/tdma.py @@ -0,0 +1,113 @@ + +# +# CORE +# Copyright (c)2013 Company. +# See the LICENSE file included in this distribution. +# +# author: Name +# +''' +tdma.py: EMANE TDMA model bindings for CORE +''' + +import sys +import string +try: + from emanesh.events import EventService +except: + pass +from core.api import coreapi +from core.constants import * +from emane import Emane, EmaneModel +from universal import EmaneUniversalModel + +class EmaneTdmaModel(EmaneModel): + def __init__(self, session, objid = None, verbose = False): + EmaneModel.__init__(self, session, objid, verbose) + + # model name + _name = "emane_tdma" + if Emane.version >= Emane.EMANE101: + xml_path = '/usr/share/emane/xml/models/mac/tdmaeventscheduler' + else: + raise Exception("EMANE TDMA requires EMANE 1.0.1 or greater") + + + # MAC parameters + _confmatrix_mac = [ + ("enablepromiscuousmode", coreapi.CONF_DATA_TYPE_BOOL, '0', + 'True,False', 'enable promiscuous mode'), + ("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0', + 'On,Off', 'enable traffic flow control'), + ("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10', + '', 'number of flow control tokens'), + ("fragmentcheckthreshold", coreapi.CONF_DATA_TYPE_UINT16, '2', + '', 'rate in seconds for check if fragment reassembly efforts should be abandoned'), + ("fragmenttimeoutthreshold", coreapi.CONF_DATA_TYPE_UINT16, '5', + '', 'threshold in seconds to wait for another packet fragment for reassembly'), + ('neighbormetricdeletetime', coreapi.CONF_DATA_TYPE_FLOAT, '60.0', + '', 'neighbor RF reception timeout for removal from neighbor table (sec)'), + ('neighbormetricupdateinterval', coreapi.CONF_DATA_TYPE_FLOAT, '1.0', + '', 'neighbor table update interval (sec)'), + ("pcrcurveuri", coreapi.CONF_DATA_TYPE_STRING, '%s/tdmabasemodelpcr.xml' % xml_path, + '', 'SINR/PCR curve file'), + ("queue.aggregationenable", coreapi.CONF_DATA_TYPE_BOOL, '1', + 'On,Off', 'enable transmit packet aggregation'), + ('queue.aggregationslotthreshold', coreapi.CONF_DATA_TYPE_FLOAT, '90.0', + '', 'percentage of a slot that must be filled in order to conclude aggregation'), + ("queue.depth", coreapi.CONF_DATA_TYPE_UINT16, '256', + '', 'size of the per service class downstream packet queues (packets)'), + ("queue.fragmentationenable", coreapi.CONF_DATA_TYPE_BOOL, '1', + 'On,Off', 'enable packet fragmentation (over multiple slots)'), + ("queue.strictdequeueenable", coreapi.CONF_DATA_TYPE_BOOL, '0', + 'On,Off', 'enable strict dequeueing to specified queues only'), + ] + + # PHY parameters from Universal PHY + _confmatrix_phy = EmaneUniversalModel._confmatrix + + _confmatrix = _confmatrix_mac + _confmatrix_phy + + # value groupings + _confgroups = "TDMA MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % \ + (len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(_confmatrix)) + + def buildnemxmlfiles(self, e, ifc): + ''' Build the necessary nem, mac, and phy XMLs in the given path. + If an individual NEM has a nonstandard config, we need to build + that file also. Otherwise the WLAN-wide nXXemane_tdmanem.xml, + nXXemane_tdmamac.xml, nXXemane_tdmaphy.xml are used. + ''' + 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", "TDMA NEM") + mactag = nemdoc.createElement("mac") + mactag.setAttribute("definition", self.macxmlname(ifc)) + nem.appendChild(mactag) + phytag = nemdoc.createElement("phy") + phytag.setAttribute("definition", self.phyxmlname(ifc)) + nem.appendChild(phytag) + e.xmlwrite(nemdoc, self.nemxmlname(ifc)) + + names = list(self.getnames()) + macnames = names[:len(self._confmatrix_mac)] + phynames = names[len(self._confmatrix_mac):] + # make any changes to the mac/phy names here to e.g. exclude them from + # the XML output + + macdoc = e.xmldoc("mac") + mac = macdoc.getElementsByTagName("mac").pop() + mac.setAttribute("name", "TDMA MAC") + mac.setAttribute("library", "tdmaeventschedulerradiomodel") + # append MAC options to macdoc + map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, \ + self.valueof(n, values))), macnames) + e.xmlwrite(macdoc, self.macxmlname(ifc)) + + phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames) + e.xmlwrite(phydoc, self.phyxmlname(ifc)) + diff --git a/daemon/core/services/nrl.py b/daemon/core/services/nrl.py index 5afe202c..481185bf 100644 --- a/daemon/core/services/nrl.py +++ b/daemon/core/services/nrl.py @@ -91,7 +91,7 @@ class NrlNhdp(NrlService): servicenames = map(lambda x: x._name, services) if "SMF" in servicenames: - cmd += " -flooding ecds" + cmd += " -flooding ecds-etx sticky" cmd += " -smfClient %s_smf" % node.name netifs = filter(lambda x: not getattr(x, 'control', False), \ From 1c91f41742d2ffb2d5ca6eaae344e72e8c1ac552 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Mon, 5 Sep 2016 17:08:49 -0400 Subject: [PATCH 04/28] daemon: Remove unused traceback code introduced by 68532cb. --- daemon/core/netns/vnet.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/daemon/core/netns/vnet.py b/daemon/core/netns/vnet.py index d09d59ea..8dd1c494 100644 --- a/daemon/core/netns/vnet.py +++ b/daemon/core/netns/vnet.py @@ -11,8 +11,6 @@ vnet.py: PyCoreNet and LxBrNet classes that implement virtual networks using Linux Ethernet bridging and ebtables rules. ''' -import traceback - import os, sys, threading, time, subprocess from core.api import coreapi @@ -347,10 +345,6 @@ class LxBrNet(PyCoreNet): ''' Configure link parameters by applying tc queuing disciplines on the interface. ''' - - sys.stderr.write("enter linkconfig() ...\n") - traceback.print_stack() - if devname is None: devname = netif.localname tc = [TC_BIN, "qdisc", "replace", "dev", devname] From e5e75756eac94bb3a66609b4f82fa35cea7a58f5 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Mon, 5 Sep 2016 17:11:10 -0400 Subject: [PATCH 05/28] daemon: Remove session-related changes introduced by 68532cb. --- daemon/core/session.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/daemon/core/session.py b/daemon/core/session.py index 9e226f53..223bd9e9 100644 --- a/daemon/core/session.py +++ b/daemon/core/session.py @@ -14,6 +14,7 @@ that manages a CORE session. import os, sys, tempfile, shutil, shlex, atexit, gc, pwd import threading, time, random import traceback +import subprocess from core.api import coreapi if os.uname()[0] == "Linux": @@ -289,8 +290,16 @@ class Session(object): self.warn("Error writing hook '%s': %s" % (filename, e)) self.info("Running hook %s for state %s" % (filename, state)) try: - check_call(["/bin/sh", filename], cwd=self.sessiondir, - env=self.getenviron()) + stdout = open(os.path.join(self.sessiondir, + filename + '.log'), 'w') + stderr = subprocess.STDOUT + except: + stdout = None + stderr = None + try: + check_call(["/bin/sh", filename], stdin=open(os.devnull, 'r'), + stdout=stdout, stderr=stderr, close_fds=True, + cwd=self.sessiondir, env=self.getenviron()) except Exception, e: self.warn("Error running hook '%s' for state %s: %s" % (filename, state, e)) From f8da9334d870f2f565ed8ef2801a939ae9c9f410 Mon Sep 17 00:00:00 2001 From: adamson <> Date: Tue, 6 Sep 2016 10:16:31 -0400 Subject: [PATCH 06/28] fix to EMANE TDMA XML generation --- daemon/core/emane/tdma.py | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/core/emane/tdma.py b/daemon/core/emane/tdma.py index 6c82d595..cc705f56 100644 --- a/daemon/core/emane/tdma.py +++ b/daemon/core/emane/tdma.py @@ -85,6 +85,7 @@ class EmaneTdmaModel(EmaneModel): nemdoc = e.xmldoc("nem") nem = nemdoc.getElementsByTagName("nem").pop() nem.setAttribute("name", "TDMA NEM") + e.appendtransporttonem(nemdoc, nem, self.objid, ifc) mactag = nemdoc.createElement("mac") mactag.setAttribute("definition", self.macxmlname(ifc)) nem.appendChild(mactag) From c0a9c6c9afec0697f43826bdcc7ac28ee22678f5 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Tue, 6 Sep 2016 10:38:38 -0400 Subject: [PATCH 07/28] daemon: Remove unused variable. --- daemon/core/emane/emane.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/core/emane/emane.py b/daemon/core/emane/emane.py index 078ca1c0..45086a15 100644 --- a/daemon/core/emane/emane.py +++ b/daemon/core/emane/emane.py @@ -746,7 +746,7 @@ class Emane(ConfigurableManager): ''' for n in sorted(self._objs.keys()): emanenode = self._objs[n] - nems = emanenode.buildnemxmlfiles(self) + emanenode.buildnemxmlfiles(self) def appendtransporttonem(self, doc, nem, nodenum, ifc=None): ''' Given a nem XML node and EMANE WLAN node number, append From cdc3071bedd7497bd802a8773a501e6910daaf88 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Tue, 6 Sep 2016 10:30:08 -0400 Subject: [PATCH 08/28] autoconf: Remove an unused variable. --- configure.ac | 9 --------- 1 file changed, 9 deletions(-) diff --git a/configure.ac b/configure.ac index 93c3b917..a81fa8e7 100644 --- a/configure.ac +++ b/configure.ac @@ -198,15 +198,6 @@ if test "x$enable_daemon" = "xyes" ; then AC_CHECK_FUNCS([atexit dup2 gettimeofday memset socket strerror uname]) fi -# simple architecture detection -if test `uname -m` = "x86_64"; then - ARCH=amd64 -else - ARCH=i386 -fi -AC_MSG_RESULT([using architecture $ARCH]) -AC_SUBST(ARCH) - # Host-specific detection want_linux_netns=no want_bsd=no From 4cf8c62149d424e4e5c6e66e9efd6bf6dddf71fe Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Tue, 6 Sep 2016 10:30:40 -0400 Subject: [PATCH 09/28] packaging: Update Debian packaging. --- packaging/deb/core-daemon.install.in | 33 +++++++++++++--------------- packaging/deb/core-gui.install.in | 14 +++++------- 2 files changed, 21 insertions(+), 26 deletions(-) diff --git a/packaging/deb/core-daemon.install.in b/packaging/deb/core-daemon.install.in index 886be338..4656c188 100644 --- a/packaging/deb/core-daemon.install.in +++ b/packaging/deb/core-daemon.install.in @@ -1,23 +1,20 @@ #! /usr/bin/dh-exec @SBINDIR@ @CORE_CONF_DIR@ -# configure prints a warning if CORE_DATA_DIR is used here -# ATdatarootdirAT is expanding to ${datarootdir}/man/man1/ -/usr/share/core/examples/corens3 -/usr/share/core/examples/*.py -/usr/share/core/examples/hooks -/usr/share/core/examples/myservices -/usr/share/core/examples/netns -/usr/share/core/examples/services -# ATmandirAT is expanding to ${datarootdir}/man/man1/core-daemon.1 -/usr/share/man/man1/vnoded.1 -/usr/share/man/man1/vcmd.1 -/usr/share/man/man1/netns.1 -/usr/share/man/man1/core-daemon.1 -/usr/share/man/man1/coresendmsg.1 -/usr/share/man/man1/core-cleanup.1 -/usr/share/man/man1/core-xen-cleanup.1 -# ATpythondirAT is expanding to ${prefix}/lib/python2.7/dist-packages -/usr/lib/python2.7/dist-packages +@CORE_DATA_DIR@/examples/corens3 +@CORE_DATA_DIR@/examples/*.py +@CORE_DATA_DIR@/examples/hooks +@CORE_DATA_DIR@/examples/myservices +@CORE_DATA_DIR@/examples/netns +@CORE_DATA_DIR@/examples/services +# ATmandirAT is expanding to ${datarootdir}/man +@datarootdir@/man/man1/vnoded.1 +@datarootdir@/man/man1/vcmd.1 +@datarootdir@/man/man1/netns.1 +@datarootdir@/man/man1/core-daemon.1 +@datarootdir@/man/man1/coresendmsg.1 +@datarootdir@/man/man1/core-cleanup.1 +@datarootdir@/man/man1/core-xen-cleanup.1 +@pyprefix@/lib/python2.7/dist-packages /etc/init.d /etc/logrotate.d diff --git a/packaging/deb/core-gui.install.in b/packaging/deb/core-gui.install.in index a8e85893..89bbc19c 100644 --- a/packaging/deb/core-gui.install.in +++ b/packaging/deb/core-gui.install.in @@ -1,11 +1,9 @@ #! /usr/bin/dh-exec @BINDIR@/core-gui @CORE_LIB_DIR@ -# configure prints a warning if CORE_DATA_DIR is used here -# ATdatarootdirAT is expanding to ${datarootdir}/man/man1/ -/usr/share/core/icons -/usr/share/core/examples/configs -/usr/share/pixmaps -/usr/share/applications -# ATmandirAT is expanding to ${datarootdir}/man/man1/core-gui.1 -/usr/share/man/man1/core-gui.1 +@CORE_DATA_DIR@/icons +@CORE_DATA_DIR@/examples/configs +@datarootdir@/pixmaps +@datarootdir@/applications +# ATmandirAT is expanding to ${datarootdir}/man +@datarootdir@/man/man1/core-gui.1 From dc735c5d8f237de9db6fa6a6d5809908f8514b61 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Tue, 6 Sep 2016 10:33:26 -0400 Subject: [PATCH 10/28] gitignore: Ignore distribution tar files. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 6289e42c..a403186e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,6 @@ config.h.in config.log config.status configure +core-*.tar.gz debian stamp-h1 From 82f22fde5eae322ded8907284829aaa4cb0027f0 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Tue, 6 Sep 2016 10:33:44 -0400 Subject: [PATCH 11/28] packaging: Fix RPM spec changelog date. --- packaging/rpm/core.spec.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/rpm/core.spec.in b/packaging/rpm/core.spec.in index 1c6d5da6..6e92f6ec 100644 --- a/packaging/rpm/core.spec.in +++ b/packaging/rpm/core.spec.in @@ -402,7 +402,7 @@ fi %{_sbindir}/vnoded %changelog -* Thu Jun 5 2015 CORE Developers - 4.8 +* Fri Jun 5 2015 CORE Developers - 4.8 - Support for NRL Network Modeling Framework (NMF) XML representation, bugfixes * Wed Aug 6 2014 Jeff Ahrenholz - 4.7 - EMANE 0.9.1, asymmetric links, bugfixes From a85a1e4ece301d629d47120f019645a1af93a05d Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Tue, 6 Sep 2016 11:14:16 -0400 Subject: [PATCH 12/28] packaging: Add a makefile to directly build Debian packages. --- .gitignore | 1 + packaging/deb.mk | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 packaging/deb.mk diff --git a/.gitignore b/.gitignore index a403186e..8a56c67a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.debbuild .deps .version .version.date diff --git a/packaging/deb.mk b/packaging/deb.mk new file mode 100644 index 00000000..14116c12 --- /dev/null +++ b/packaging/deb.mk @@ -0,0 +1,56 @@ +DEBBUILD = .debbuild + +CORE_VERSION = $(shell cat .version 2> /dev/null) + +COREBUILD = $(DEBBUILD)/core-$(CORE_VERSION) + +.PHONY: all +all: clean .version build + +.PHONY: clean +clean: + rm -rf $(DEBBUILD) + +.PHONY: build +build: changelog + cd $(COREBUILD) && dpkg-buildpackage -b -us -uc + @printf "\ndebian packages built in $(DEBBUILD)\n\n" + +.PHONY: changelog +changelog: debian + echo "core ($(CORE_VERSION)-1) unstable; urgency=low" > $(COREBUILD)/debian/changelog.generated + echo " * interim package generated from source" >> $(COREBUILD)/debian/changelog.generated + echo " -- CORE Developers $$(date -R)" >> $(COREBUILD)/debian/changelog.generated + cd $(COREBUILD)/debian && \ + { test ! -L changelog && mv -f changelog changelog.save; } && \ + { test "$$(readlink changelog)" = "changelog.generated" || \ + ln -sf changelog.generated changelog; } + +.PHONY: debian +debian: corebuild + cd $(COREBUILD) && ln -s packaging/deb debian + +.PHONY: corebuild +corebuild: $(DEBBUILD) dist + tar -C $(DEBBUILD) -xzf core-$(CORE_VERSION).tar.gz + +.PHONY: dist +dist: Makefile + $(MAKE) dist + +Makefile: configure + ./configure + +configure: bootstrap.sh + ./bootstrap.sh + +bootstrap.sh: + @printf "\nERROR: make must be called from the top-level directory:\n" + @printf " make -f packaging/$(lastword $(MAKEFILE_LIST))\n\n" + @false + +.version: Makefile + $(MAKE) $@ + +$(DEBBUILD): + mkdir -p $@ From 5911c7a69aaf1d9595123347d9689d928ab2a2a2 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Tue, 6 Sep 2016 11:15:55 -0400 Subject: [PATCH 13/28] packaging: Update RPM spec to include the EMANE TDMA model bindings. --- packaging/rpm/core.spec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/rpm/core.spec.in b/packaging/rpm/core.spec.in index 6e92f6ec..7a586ba3 100644 --- a/packaging/rpm/core.spec.in +++ b/packaging/rpm/core.spec.in @@ -336,6 +336,7 @@ fi %{python_sitelib}/core/emane/__init__.py* %{python_sitelib}/core/emane/nodes.py* %{python_sitelib}/core/emane/rfpipe.py* +%{python_sitelib}/core/emane/tdma.py* %{python_sitelib}/core/emane/universal.py* %{python_sitelib}/core/__init__.py* %{python_sitelib}/core/location.py* From 5d80716550b039fb01399b7eff249d773cd82324 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Tue, 6 Sep 2016 11:14:40 -0400 Subject: [PATCH 14/28] packaging: Add a makefile to directly build RPM packages. --- .gitignore | 1 + packaging/rpm.mk | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 packaging/rpm.mk diff --git a/.gitignore b/.gitignore index 8a56c67a..3c6a8ea2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .debbuild .deps +.rpmbuild .version .version.date Makefile diff --git a/packaging/rpm.mk b/packaging/rpm.mk new file mode 100644 index 00000000..cb70c141 --- /dev/null +++ b/packaging/rpm.mk @@ -0,0 +1,40 @@ +RPMBUILD = .rpmbuild + +CORE_VERSION = $(shell cat .version 2> /dev/null) + +.PHONY: all +all: clean .version build + +.PHONY: clean +clean: + rm -rf $(RPMBUILD) + +.PHONY: build +build: dist + for d in SOURCES SPECS; do mkdir -p $(RPMBUILD)/$$d; done + cp -afv core-$(CORE_VERSION).tar.gz $(RPMBUILD)/SOURCES + cp -afv packaging/rpm/core.spec $(RPMBUILD)/SPECS + rpmbuild -bb --clean $(RPMBUILD)/SPECS/core.spec \ + --define "_topdir $$PWD/.rpmbuild" + @printf "\nRPM packages saved in $(RPMBUILD)/RPMS\n\n" + +.PHONY: dist +dist: Makefile + $(MAKE) dist + +Makefile: configure + ./configure --prefix=/usr --exec-prefix=/usr + +configure: bootstrap.sh + ./bootstrap.sh + +bootstrap.sh: + @printf "\nERROR: make must be called from the top-level directory:\n" + @printf " make -f packaging/$(lastword $(MAKEFILE_LIST))\n\n" + @false + +.version: Makefile + $(MAKE) $@ + +$(RPMBUILD): + mkdir -p $@ From 9913168896819edf1dbca66f526d2bbe1f83b30b Mon Sep 17 00:00:00 2001 From: Rod A Santiago Date: Wed, 14 Sep 2016 18:05:52 -0700 Subject: [PATCH 15/28] Fixed sendnodeemuid to use the handler's sendall() method instead of BaseRequestHandler's request.sendall(). Added a method to retrieve a configuration item by key. --- daemon/core/session.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/daemon/core/session.py b/daemon/core/session.py index 223bd9e9..85fb49d3 100644 --- a/daemon/core/session.py +++ b/daemon/core/session.py @@ -766,7 +766,7 @@ class Session(object): | coreapi.CORE_API_LOC_FLAG, tlvdata) try: - handler.request.sendall(reply) + handler.sendall(reply) except Exception, e: self.warn("sendall() for node: %d error: %s" % (nodenum, e)) del handler.nodestatusreq[nodenum] @@ -1250,6 +1250,13 @@ class SessionMetaData(ConfigurableManager): def additem(self, key, value): self.configs[key] = value + def getitem(self, key): + try: + return self.configs[key] + except KeyError: + pass + return None + def items(self): return self.configs.iteritems() From 2d745c339f344dec3c9489246ee8cfd43b686e21 Mon Sep 17 00:00:00 2001 From: Rod A Santiago Date: Wed, 14 Sep 2016 18:06:19 -0700 Subject: [PATCH 16/28] Moved classes CoreRequestHandler, CoreDatagramRequestHandler, CoreServer, CoreUdpServer to a separate core.coreserver module. This will allow future definition of auxiliary request handlers for providing alternative interfaces with CORE. --- daemon/core/coreserver.py | 1713 +++++++++++++++++++++++++++++++++++++ daemon/sbin/core-daemon | 1574 ++-------------------------------- 2 files changed, 1763 insertions(+), 1524 deletions(-) create mode 100644 daemon/core/coreserver.py diff --git a/daemon/core/coreserver.py b/daemon/core/coreserver.py new file mode 100644 index 00000000..7849d069 --- /dev/null +++ b/daemon/core/coreserver.py @@ -0,0 +1,1713 @@ +#!/usr/bin/env python +# +# CORE +# Copyright (c)2010-2016 the Boeing Company. +# See the LICENSE file included in this distribution. +# +# authors: Tom Goff +# Jeff Ahrenholz +# Rod Santiago +# + + +import SocketServer, sys, threading, time, traceback +import os, gc, shlex, shutil +from core import pycore +from core.api import coreapi +from core.misc.utils import hexdump, cmdresult, mutedetach, closeonexec +from core.misc.xmlsession import opensessionxml, savesessionxml + + +''' +Defines server classes and request handlers for TCP and UDP. Also defined here is a TCP based auxiliary server class for supporting externally defined handlers. +''' + + + +class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): + ''' TCP server class, manages sessions and spawns request handlers for + incoming connections. + ''' + daemon_threads = True + allow_reuse_address = True + servers = set() + + def __init__(self, server_address, RequestHandlerClass, cfg = None): + ''' Server class initialization takes configuration data and calls + the SocketServer constructor + ''' + self.cfg = cfg + self._sessions = {} + self._sessionslock = threading.Lock() + self.newserver(self) + SocketServer.TCPServer.__init__(self, server_address, + RequestHandlerClass) + + @classmethod + def newserver(cls, server): + cls.servers.add(server) + + @classmethod + def delserver(cls, server): + try: + cls.servers.remove(server) + except KeyError: + pass + + def shutdown(self): + for session in self._sessions.values(): + session.shutdown() + if self.cfg['daemonize']: + pidfilename = self.cfg['pidfile'] + try: + os.unlink(pidfilename) + except OSError: + pass + self.delserver(self) + + def addsession(self, session): + ''' Add a session to our dictionary of sessions, ensuring a unique + session number + ''' + self._sessionslock.acquire() + try: + if session.sessionid in self._sessions: + raise KeyError, "non-unique session id %s for %s" % \ + (session.sessionid, session) + self._sessions[session.sessionid] = session + finally: + self._sessionslock.release() + return session + + def delsession(self, session): + ''' Remove a session from our dictionary of sessions. + ''' + with self._sessionslock: + if session.sessionid not in self._sessions: + print "session id %s not found (sessions=%s)" % \ + (session.sessionid, self._sessions.keys()) + else: + del(self._sessions[session.sessionid]) + return session + + def getsessionids(self): + ''' Return a list of active session numbers. + ''' + with self._sessionslock: + sids = self._sessions.keys() + return sids + + def getsession(self, sessionid = None, useexisting = True): + ''' Create a new session or retrieve an existing one from our + dictionary of sessions. When the sessionid=0 and the useexisting + flag is set, return on of the existing sessions. + ''' + if not useexisting: + session = pycore.Session(sessionid, cfg = self.cfg, server = self) + self.addsession(session) + return session + + with self._sessionslock: + # look for the specified session id + if sessionid in self._sessions: + session = self._sessions[sessionid] + else: + session = None + # pick an existing session + if sessionid == 0: + for s in self._sessions.itervalues(): + if s.getstate() == coreapi.CORE_EVENT_RUNTIME_STATE: + if session is None: + session = s + elif s.node_count > session.node_count: + session = s + if session is None: + for s in self._sessions.itervalues(): + session = s + break + return session + + def tosessionmsg(self, flags = 0): + ''' Build CORE API Sessions message based on current session info. + ''' + idlist = [] + namelist = [] + filelist = [] + nclist = [] + datelist = [] + thumblist = [] + num_sessions = 0 + + with self._sessionslock: + for sessionid in self._sessions: + session = self._sessions[sessionid] + # debug: session.dumpsession() + num_sessions += 1 + idlist.append(str(sessionid)) + name = session.name + if name is None: + name = "" + namelist.append(name) + file = session.filename + if file is None: + file = "" + filelist.append(file) + nc = session.node_count + if nc is None: + nc = "" + nclist.append(str(nc)) + datelist.append(time.ctime(session._time)) + thumb = session.thumbnail + if thumb is None: + thumb = "" + thumblist.append(thumb) + sids = "|".join(idlist) + names = "|".join(namelist) + files = "|".join(filelist) + ncs = "|".join(nclist) + dates = "|".join(datelist) + thumbs = "|".join(thumblist) + + if num_sessions > 0: + tlvdata = "" + if len(sids) > 0: + tlvdata += coreapi.CoreSessionTlv.pack( \ + coreapi.CORE_TLV_SESS_NUMBER, sids) + if len(names) > 0: + tlvdata += coreapi.CoreSessionTlv.pack( \ + coreapi.CORE_TLV_SESS_NAME, names) + if len(files) > 0: + tlvdata += coreapi.CoreSessionTlv.pack( \ + coreapi.CORE_TLV_SESS_FILE, files) + if len(ncs) > 0: + tlvdata += coreapi.CoreSessionTlv.pack( \ + coreapi.CORE_TLV_SESS_NODECOUNT, ncs) + if len(dates) > 0: + tlvdata += coreapi.CoreSessionTlv.pack( \ + coreapi.CORE_TLV_SESS_DATE, dates) + if len(thumbs) > 0: + tlvdata += coreapi.CoreSessionTlv.pack( \ + coreapi.CORE_TLV_SESS_THUMB, thumbs) + msg = coreapi.CoreSessionMessage.pack(flags, tlvdata) + else: + msg = None + return(msg) + + def dumpsessions(self): + ''' Debug print all session info. + ''' + print "sessions:" + self._sessionslock.acquire() + try: + for sessionid in self._sessions: + print sessionid, + finally: + self._sessionslock.release() + print "" + sys.stdout.flush() + + def setsessionmaster(self, handler): + ''' Call the setmaster() method for every session. Returns True when + a session having the given handler was updated. + ''' + found = False + self._sessionslock.acquire() + try: + for sessionid in self._sessions: + found = self._sessions[sessionid].setmaster(handler) + if found is True: + break + finally: + self._sessionslock.release() + return found + + +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, RequestHandlerClass, mainserver): + ''' Server class initialization takes configuration data and calls + the SocketServer constructor + ''' + self.mainserver = mainserver + SocketServer.UDPServer.__init__(self, server_address, + RequestHandlerClass) + + def start(self): + ''' Thread target to run concurrently with the TCP server. + ''' + self.serve_forever() + + + + +class CoreAuxServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): + ''' An auxiliary TCP server. + ''' + daemon_threads = True + allow_reuse_address = True + + def __init__(self, server_address, RequestHandlerClass, mainserver): + self.mainserver = mainserver + sys.stdout.write("auxiliary server started, listening on: %s:%s\n" % server_address) + sys.stdout.flush() + SocketServer.TCPServer.__init__(self, server_address, RequestHandlerClass) + + def start(self): + self.serve_forever() + + def setsessionmaster(self, handler): + return self.mainserver.setsessionmaster(handler) + + def getsession(self, sessionid = None, useexisting = True): + return self.mainserver.getsession(sessionid, useexisting) + + def tosessionmsg(self, flags = 0): + return self.mainserver.tosessionmsg(flags) + + + + + + +class CoreRequestHandler(SocketServer.BaseRequestHandler): + ''' The SocketServer class uses the RequestHandler class for servicing + requests, mainly through the handle() method. The CoreRequestHandler + has the following basic flow: + 1. Client connects and request comes in via handle(). + 2. handle() calls recvmsg() in a loop. + 3. recvmsg() does a recv() call on the socket performs basic + checks that this we received a CoreMessage, returning it. + 4. The message data is queued using queuemsg(). + 5. The handlerthread() thread pops messages from the queue and uses + handlemsg() to invoke the appropriate handler for that message type. + + ''' + + maxmsgqueuedtimes = 8 + + def __init__(self, request, client_address, server): + self.done = False + self.msghandler = { + coreapi.CORE_API_NODE_MSG: self.handlenodemsg, + coreapi.CORE_API_LINK_MSG: self.handlelinkmsg, + coreapi.CORE_API_EXEC_MSG: self.handleexecmsg, + coreapi.CORE_API_REG_MSG: self.handleregmsg, + coreapi.CORE_API_CONF_MSG: self.handleconfmsg, + coreapi.CORE_API_FILE_MSG: self.handlefilemsg, + coreapi.CORE_API_IFACE_MSG: self.handleifacemsg, + coreapi.CORE_API_EVENT_MSG: self.handleeventmsg, + coreapi.CORE_API_SESS_MSG: self.handlesessionmsg, + } + self.msgq = [] + self.msgcv = threading.Condition() + self.nodestatusreq = {} + numthreads = int(server.cfg['numthreads']) + if numthreads < 1: + raise ValueError, \ + "invalid number of threads: %s" % numthreads + self.handlerthreads = [] + while numthreads: + t = threading.Thread(target = self.handlerthread) + self.handlerthreads.append(t) + t.start() + numthreads -= 1 + self.master = False + self.verbose = bool(server.cfg['verbose'].lower() == "true") + self.debug = bool(server.cfg['debug'].lower() == "true") + self.session = None + #self.numwlan = 0 + closeonexec(request.fileno()) + SocketServer.BaseRequestHandler.__init__(self, request, + client_address, server) + + def setup(self): + ''' Client has connected, set up a new connection. + ''' + self.info("new TCP connection: %s:%s" % self.client_address) + #self.register() + + + def finish(self): + ''' Client has disconnected, end this request handler and disconnect + from the session. Shutdown sessions that are not running. + ''' + if self.verbose: + self.info("client disconnected: notifying threads") + max_attempts = 5 + timeout = 0.0625 # wait for 1.9375s max + while len(self.msgq) > 0 and max_attempts > 0: + if self.verbose: + self.info("%d messages remain in queue (%d)" % \ + (len(self.msgq), max_attempts)) + max_attempts -= 1 + self.msgcv.acquire() + self.msgcv.notifyAll() # drain msgq before dying + self.msgcv.release() + time.sleep(timeout) # allow time for msg processing + timeout *= 2 # backoff timer + self.msgcv.acquire() + self.done = True + self.msgcv.notifyAll() + self.msgcv.release() + for t in self.handlerthreads: + if self.verbose: + self.info("waiting for thread: %s" % t.getName()) + timeout = 2.0 # seconds + t.join(timeout) + if t.isAlive(): + self.warn("joining %s failed: still alive after %s sec" % + (t.getName(), timeout)) + self.info("connection closed: %s:%s" % self.client_address) + if self.session: + self.session.disconnect(self) + return SocketServer.BaseRequestHandler.finish(self) + + + def info(self, msg): + ''' Utility method for writing output to stdout. + ''' + print msg + sys.stdout.flush() + + + def warn(self, msg): + ''' Utility method for writing output to stderr. + ''' + print >> sys.stderr, msg + sys.stderr.flush() + + def register(self): + ''' Return a Register Message + ''' + self.info("GUI has connected to session %d at %s" % \ + (self.session.sessionid, time.ctime())) + tlvdata = "" + tlvdata += coreapi.CoreRegTlv.pack(coreapi.CORE_TLV_REG_EXECSRV, + "core-daemon") + tlvdata += coreapi.CoreRegTlv.pack(coreapi.CORE_TLV_REG_EMULSRV, + "core-daemon") + tlvdata += self.session.confobjs_to_tlvs() + return coreapi.CoreRegMessage.pack(coreapi.CORE_API_ADD_FLAG, tlvdata) + + def sendall(self, data): + ''' Send raw data to the other end of this TCP connection + using socket's sendall(). + ''' + return self.request.sendall(data) + + def recvmsg(self): + ''' Receive data and return a CORE API message object. + ''' + try: + msghdr = self.request.recv(coreapi.CoreMessage.hdrsiz) + if self.debug and len(msghdr) > 0: + self.info("received message header:\n%s" % hexdump(msghdr)) + except Exception, e: + raise IOError, "error receiving header (%s)" % e + if len(msghdr) != coreapi.CoreMessage.hdrsiz: + if len(msghdr) == 0: + raise EOFError, "client disconnected" + else: + raise IOError, "invalid message header size" + msgtype, msgflags, msglen = coreapi.CoreMessage.unpackhdr(msghdr) + if msglen == 0: + self.warn("received message with no data") + data = "" + while len(data) < msglen: + data += self.request.recv(msglen - len(data)) + if self.debug: + self.info("received message data:\n%s" % hexdump(data)) + if len(data) > msglen: + self.warn("received message length does not match received data " \ + "(%s != %s)" % (len(data), msglen)) + raise IOError + try: + msgcls = coreapi.msg_class(msgtype) + msg = msgcls(msgflags, msghdr, data) + except KeyError: + msg = coreapi.CoreMessage(msgflags, msghdr, data) + msg.msgtype = msgtype + self.warn("unimplemented core message type: %s" % msg.typestr()) + return msg + + + def queuemsg(self, msg): + ''' Queue an API message for later processing. + ''' + if msg.queuedtimes >= self.maxmsgqueuedtimes: + self.warn("dropping message queued %d times: %s" % + (msg.queuedtimes, msg)) + return + if self.debug: + self.info("queueing msg (queuedtimes = %s): type %s" % + (msg.queuedtimes, msg.msgtype)) + msg.queuedtimes += 1 + self.msgcv.acquire() + self.msgq.append(msg) + self.msgcv.notify() + self.msgcv.release() + + def handlerthread(self): + ''' CORE API message handling loop that is spawned for each server + thread; get CORE API messages from the incoming message queue, + and call handlemsg() for processing. + ''' + while not self.done: + # get a coreapi.CoreMessage() from the incoming queue + self.msgcv.acquire() + while not self.msgq: + self.msgcv.wait() + if self.done: + self.msgcv.release() + return + msg = self.msgq.pop(0) + self.msgcv.release() + self.handlemsg(msg) + + + def handlemsg(self, msg): + ''' Handle an incoming message; dispatch based on message type, + optionally sending replies. + ''' + if self.session and self.session.broker.handlemsg(msg): + if self.debug: + self.info("%s forwarding message:\n%s" % + (threading.currentThread().getName(), msg)) + return + + if self.debug: + self.info("%s handling message:\n%s" % + (threading.currentThread().getName(), msg)) + + if msg.msgtype not in self.msghandler: + self.warn("no handler for message type: %s" % + msg.typestr()) + return + msghandler = self.msghandler[msg.msgtype] + + try: + replies = msghandler(msg) + self.dispatchreplies(replies,msg) + except Exception, e: + self.warn("%s: exception while handling msg:\n%s\n%s" % + (threading.currentThread().getName(), msg, + traceback.format_exc())) + + # Added to allow the auxiliary handlers to define a different behavior when replying + # to messages from clients + def dispatchreplies(self, replies, msg): + ''' + Dispatch replies by CORE to message msg previously received from the client. + ''' + for reply in replies: + if self.debug: + msgtype, msgflags, msglen = \ + coreapi.CoreMessage.unpackhdr(reply) + try: + rmsg = coreapi.msg_class(msgtype)(msgflags, + reply[:coreapi.CoreMessage.hdrsiz], + reply[coreapi.CoreMessage.hdrsiz:]) + except KeyError: + # multiple TLVs of same type cause KeyError exception + rmsg = "CoreMessage (type %d flags %d length %d)" % \ + (msgtype, msgflags, msglen) + self.info("%s: reply msg:\n%s" % + (threading.currentThread().getName(), rmsg)) + try: + self.sendall(reply) + except Exception, e: + self.warn("Error sending reply data: %s" % e) + + + def handle(self): + ''' Handle a new connection request from a client. Dispatch to the + recvmsg() method for receiving data into CORE API messages, and + add them to an incoming message queue. + ''' + # use port as session id + port = self.request.getpeername()[1] + self.session = self.server.getsession(sessionid = port, + useexisting = False) + self.session.connect(self) + while True: + try: + msg = self.recvmsg() + except EOFError: + break + except IOError, e: + self.warn("IOError: %s" % e) + break + msg.queuedtimes = 0 + self.queuemsg(msg) + if (msg.msgtype == coreapi.CORE_API_SESS_MSG): + # delay is required for brief connections, allow session joining + time.sleep(0.125) + self.session.broadcast(self, msg) + #self.session.shutdown() + #del self.session + gc.collect() +# print "gc count:", gc.get_count() +# for o in gc.get_objects(): +# if isinstance(o, pycore.PyCoreObj): +# print "XXX XXX XXX PyCoreObj:", o +# for r in gc.get_referrers(o): +# print "XXX XXX XXX referrer:", gc.get_referrers(o) + + + def handlenodemsg(self, msg): + ''' Node Message handler + ''' + replies = [] + if msg.flags & coreapi.CORE_API_ADD_FLAG and \ + msg.flags & coreapi.CORE_API_DEL_FLAG: + self.warn("ignoring invalid message: " + "add and delete flag both set") + return () + nodenum = msg.tlvdata[coreapi.CORE_TLV_NODE_NUMBER] + nodexpos = msg.gettlv(coreapi.CORE_TLV_NODE_XPOS) + nodeypos = msg.gettlv(coreapi.CORE_TLV_NODE_YPOS) + canvas = msg.gettlv(coreapi.CORE_TLV_NODE_CANVAS) + icon = msg.gettlv(coreapi.CORE_TLV_NODE_ICON) + lat = msg.gettlv(coreapi.CORE_TLV_NODE_LAT) + lng = msg.gettlv(coreapi.CORE_TLV_NODE_LONG) + alt = msg.gettlv(coreapi.CORE_TLV_NODE_ALT) + if nodexpos is None and nodeypos is None and \ + lat is not None and lng is not None and alt is not None: + (x, y, z) = self.session.location.getxyz(float(lat), float(lng), + float(alt)) + nodexpos = int(x) + nodeypos = int(y) + # GUI can't handle lat/long, so generate another X/Y position message + tlvdata = "" + tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_NUMBER, + nodenum) + tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_XPOS, + nodexpos) + tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_YPOS, + nodeypos) + self.session.broadcastraw(self, coreapi.CoreNodeMessage.pack(0, tlvdata)) + + if msg.flags & coreapi.CORE_API_ADD_FLAG: + nodetype = msg.tlvdata[coreapi.CORE_TLV_NODE_TYPE] + try: + nodecls = coreapi.node_class(nodetype) + except KeyError: + try: + nodetypestr = " (%s)" % coreapi.node_types[nodetype] + except KeyError: + nodetypestr = "" + self.warn("warning: unimplemented node type: %s%s" % \ + (nodetype, nodetypestr)) + return () + start = False + if self.session.getstate() > coreapi.CORE_EVENT_DEFINITION_STATE: + start = True + + nodename = msg.tlvdata[coreapi.CORE_TLV_NODE_NAME] + model = msg.gettlv(coreapi.CORE_TLV_NODE_MODEL) + clsargs = { 'verbose': self.verbose, 'start': start } + if nodetype == coreapi.CORE_NODE_XEN: + clsargs['model'] = model + if nodetype == coreapi.CORE_NODE_RJ45: + if hasattr(self.session.options, 'enablerj45'): + if self.session.options.enablerj45 == '0': + clsargs['start'] = False + # this instantiates an object of class nodecls, + # creating the node or network + n = self.session.addobj(cls = nodecls, objid = nodenum, + name = nodename, **clsargs) + if nodexpos is not None and nodeypos is not None: + n.setposition(nodexpos, nodeypos, None) + if canvas is not None: + n.canvas = canvas + if icon is not None: + n.icon = icon + opaque = msg.gettlv(coreapi.CORE_TLV_NODE_OPAQUE) + if opaque is not None: + n.opaque = opaque + + # add services to a node, either from its services TLV or + # through the configured defaults for this node type + if nodetype == coreapi.CORE_NODE_DEF or \ + nodetype == coreapi.CORE_NODE_PHYS or \ + nodetype == coreapi.CORE_NODE_XEN: + if model is None: + # TODO: default model from conf file? + model = "router" + n.type = model + services_str = msg.gettlv(coreapi.CORE_TLV_NODE_SERVICES) + self.session.services.addservicestonode(n, model, services_str, + self.verbose) + # boot nodes if they are added after runtime (like + # session.bootnodes()) + if self.session.getstate() == coreapi.CORE_EVENT_RUNTIME_STATE: + if isinstance(n, pycore.nodes.PyCoreNode) and \ + not isinstance(n, pycore.nodes.RJ45Node): + self.session.writeobjs() + self.session.addremovectrlif(node=n, remove=False) + n.boot() + # self.session.updatectrlifhosts() + # n.validate() + if msg.flags & coreapi.CORE_API_STR_FLAG: + self.nodestatusreq[nodenum] = True + self.session.sendnodeemuid(self, nodenum) + + elif msg.flags & coreapi.CORE_API_STR_FLAG: + self.nodestatusreq[nodenum] = True + + elif msg.flags & coreapi.CORE_API_DEL_FLAG: + n = None + try: + n = self.session.obj(nodenum) + except KeyError: + pass + self.session.delobj(nodenum) + + if msg.flags & coreapi.CORE_API_STR_FLAG: + tlvdata = "" + tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_NUMBER, + nodenum) + flags = coreapi.CORE_API_DEL_FLAG | coreapi.CORE_API_LOC_FLAG + replies.append(coreapi.CoreNodeMessage.pack(flags, tlvdata)) + for reply in self.session.checkshutdown(): + replies.append(reply) + # Node modify message (no add/del flag) + else: + n = None + try: + n = self.session.obj(nodenum) + except KeyError: + if self.verbose: + self.warn("ignoring node message: unknown node number %s" \ + % nodenum) + #nodeemuid = msg.gettlv(coreapi.CORE_TLV_NODE_EMUID) + if nodexpos is None or nodeypos is None: + if self.verbose: + self.info("ignoring node message: nothing to do") + else: + if n: + n.setposition(nodexpos, nodeypos, None) + if n: + if canvas is not None: + n.canvas = canvas + if icon is not None: + n.icon = icon + + return replies + + + def handlelinkmsg(self, msg): + ''' Link Message handler + ''' + + nodenum1 = msg.gettlv(coreapi.CORE_TLV_LINK_N1NUMBER) + ifindex1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1NUM) + ipv41 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1IP4) + ipv4mask1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1IP4MASK) + mac1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1MAC) + ipv61 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1IP6) + ipv6mask1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1IP6MASK) + ifname1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1NAME) + + nodenum2 = msg.gettlv(coreapi.CORE_TLV_LINK_N2NUMBER) + ifindex2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2NUM) + ipv42 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2IP4) + ipv4mask2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2IP4MASK) + mac2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2MAC) + ipv62 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2IP6) + ipv6mask2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2IP6MASK) + ifname2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2NAME) + + node1 = None + node2 = None + net = None + net2 = None + + uni = msg.gettlv(coreapi.CORE_TLV_LINK_UNI) + if uni is not None and uni == 1: + unidirectional = True + else: + unidirectional = False + + + # one of the nodes may exist on a remote server + if nodenum1 is not None and nodenum2 is not None: + t = self.session.broker.gettunnel(nodenum1, nodenum2) + if isinstance(t, pycore.nodes.PyCoreNet): + net = t + if t.remotenum == nodenum1: + nodenum1 = None + else: + nodenum2 = None + # PhysicalNode connected via GreTap tunnel; uses adoptnetif() below + elif t is not None: + if t.remotenum == nodenum1: + nodenum1 = None + else: + nodenum2 = None + + + if nodenum1 is not None: + try: + n = self.session.obj(nodenum1) + except KeyError: + # XXX wait and queue this message to try again later + # XXX maybe this should be done differently + time.sleep(0.125) + self.queuemsg(msg) + return () + if isinstance(n, pycore.nodes.PyCoreNode): + node1 = n + elif isinstance(n, pycore.nodes.PyCoreNet): + if net is None: + net = n + else: + net2 = n + else: + raise ValueError, "unexpected object class: %s" % n + + if nodenum2 is not None: + try: + n = self.session.obj(nodenum2) + except KeyError: + # XXX wait and queue this message to try again later + # XXX maybe this should be done differently + time.sleep(0.125) + self.queuemsg(msg) + return () + if isinstance(n, pycore.nodes.PyCoreNode): + node2 = n + elif isinstance(n, pycore.nodes.PyCoreNet): + if net is None: + net = n + else: + net2 = n + else: + raise ValueError, "unexpected object class: %s" % n + + link_msg_type = msg.gettlv(coreapi.CORE_TLV_LINK_TYPE) + + if node1: + node1.lock.acquire() + if node2: + node2.lock.acquire() + + try: + if link_msg_type == coreapi.CORE_LINK_WIRELESS: + ''' Wireless link/unlink event + ''' + numwlan = 0 + objs = [node1, node2, net, net2] + objs = filter( lambda(x): x is not None, objs ) + if len(objs) < 2: + raise ValueError, "wireless link/unlink message between unknown objects" + + nets = objs[0].commonnets(objs[1]) + for (netcommon, netif1, netif2) in nets: + if not isinstance(netcommon, pycore.nodes.WlanNode) and \ + not isinstance(netcommon, pycore.nodes.EmaneNode): + continue + if msg.flags & coreapi.CORE_API_ADD_FLAG: + netcommon.link(netif1, netif2) + elif msg.flags & coreapi.CORE_API_DEL_FLAG: + netcommon.unlink(netif1, netif2) + else: + raise ValueError, "invalid flags for wireless link/unlink message" + numwlan += 1 + if numwlan == 0: + raise ValueError, \ + "no common network found for wireless link/unlink" + + elif msg.flags & coreapi.CORE_API_ADD_FLAG: + ''' Add a new link. + ''' + start = False + if self.session.getstate() > coreapi.CORE_EVENT_DEFINITION_STATE: + start = True + + if node1 and node2 and not net: + # a new wired link + net = self.session.addobj(cls = pycore.nodes.PtpNet, + verbose = self.verbose, + start = start) + + bw = msg.gettlv(coreapi.CORE_TLV_LINK_BW) + delay = msg.gettlv(coreapi.CORE_TLV_LINK_DELAY) + loss = msg.gettlv(coreapi.CORE_TLV_LINK_PER) + duplicate = msg.gettlv(coreapi.CORE_TLV_LINK_DUP) + jitter = msg.gettlv(coreapi.CORE_TLV_LINK_JITTER) + key = msg.gettlv(coreapi.CORE_TLV_LINK_KEY) + + netaddrlist = [] + #print " n1=%s n2=%s net=%s net2=%s" % (node1, node2, net, net2) + if node1 and net: + addrlist = [] + if ipv41 is not None and ipv4mask1 is not None: + addrlist.append("%s/%s" % (ipv41, ipv4mask1)) + if ipv61 is not None and ipv6mask1 is not None: + addrlist.append("%s/%s" % (ipv61, ipv6mask1)) + if ipv42 is not None and ipv4mask2 is not None: + netaddrlist.append("%s/%s" % (ipv42, ipv4mask2)) + if ipv62 is not None and ipv6mask2 is not None: + netaddrlist.append("%s/%s" % (ipv62, ipv6mask2)) + ifindex1 = node1.newnetif(net, addrlist = addrlist, + hwaddr = mac1, ifindex = ifindex1, ifname=ifname1) + net.linkconfig(node1.netif(ifindex1, net), bw = bw, + delay = delay, loss = loss, + duplicate = duplicate, jitter = jitter) + if node1 is None and net: + if ipv41 is not None and ipv4mask1 is not None: + netaddrlist.append("%s/%s" % (ipv41, ipv4mask1)) + # don't add this address again if node2 and net + ipv41 = None + if ipv61 is not None and ipv6mask1 is not None: + netaddrlist.append("%s/%s" % (ipv61, ipv6mask1)) + # don't add this address again if node2 and net + ipv61 = None + if node2 and net: + addrlist = [] + if ipv42 is not None and ipv4mask2 is not None: + addrlist.append("%s/%s" % (ipv42, ipv4mask2)) + if ipv62 is not None and ipv6mask2 is not None: + addrlist.append("%s/%s" % (ipv62, ipv6mask2)) + if ipv41 is not None and ipv4mask1 is not None: + netaddrlist.append("%s/%s" % (ipv41, ipv4mask1)) + if ipv61 is not None and ipv6mask1 is not None: + netaddrlist.append("%s/%s" % (ipv61, ipv6mask1)) + ifindex2 = node2.newnetif(net, addrlist = addrlist, + hwaddr = mac2, ifindex = ifindex2, ifname=ifname2) + if not unidirectional: + net.linkconfig(node2.netif(ifindex2, net), bw = bw, + delay = delay, loss = loss, + duplicate = duplicate, jitter = jitter) + if node2 is None and net2: + if ipv42 is not None and ipv4mask2 is not None: + netaddrlist.append("%s/%s" % (ipv42, ipv4mask2)) + if ipv62 is not None and ipv6mask2 is not None: + netaddrlist.append("%s/%s" % (ipv62, ipv6mask2)) + + # tunnel node finalized with this link message + if key and isinstance(net, pycore.nodes.TunnelNode): + net.setkey(key) + if len(netaddrlist) > 0: + net.addrconfig(netaddrlist) + if key and isinstance(net2, pycore.nodes.TunnelNode): + net2.setkey(key) + if len(netaddrlist) > 0: + net2.addrconfig(netaddrlist) + + if net and net2: + # two layer-2 networks linked together + if isinstance(net2, pycore.nodes.RJ45Node): + netif = net2.linknet(net) # RJ45 nodes have different linknet() + else: + netif = net.linknet(net2) + net.linkconfig(netif, bw = bw, delay = delay, loss = loss, + duplicate = duplicate, jitter = jitter) + if not unidirectional: + netif.swapparams('_params_up') + net2.linkconfig(netif, bw = bw, delay = delay, loss = loss, + duplicate = duplicate, jitter = jitter, + devname = netif.name) + netif.swapparams('_params_up') + + + elif net is None and net2 is None and \ + (node1 is None or node2 is None): + # apply address/parameters to PhysicalNodes + fx = (bw, delay, loss, duplicate, jitter) + addrlist = [] + if node1 and isinstance(node1, pycore.pnodes.PhysicalNode): + if ipv41 is not None and ipv4mask1 is not None: + addrlist.append("%s/%s" % (ipv41, ipv4mask1)) + if ipv61 is not None and ipv6mask1 is not None: + addrlist.append("%s/%s" % (ipv61, ipv6mask1)) + node1.adoptnetif(t, ifindex1, mac1, addrlist) + node1.linkconfig(t, bw, delay, loss, duplicate, jitter) + elif node2 and isinstance(node2, pycore.pnodes.PhysicalNode): + if ipv42 is not None and ipv4mask2 is not None: + addrlist.append("%s/%s" % (ipv42, ipv4mask2)) + if ipv62 is not None and ipv6mask2 is not None: + addrlist.append("%s/%s" % (ipv62, ipv6mask2)) + node2.adoptnetif(t, ifindex2, mac2, addrlist) + node2.linkconfig(t, bw, delay, loss, duplicate, jitter) + # delete a link + elif msg.flags & coreapi.CORE_API_DEL_FLAG: + ''' Remove a link. + ''' + if node1 and node2: + # TODO: fix this for the case where ifindex[1,2] are + # not specified + # a wired unlink event, delete the connecting bridge + netif1 = node1.netif(ifindex1) + netif2 = node2.netif(ifindex2) + if netif1 is None and netif2 is None: + nets = node1.commonnets(node2) + for (netcommon, tmp1, tmp2) in nets: + if (net and netcommon == net) or net is None: + netif1 = tmp1 + netif2 = tmp2 + break + if netif1 is None or netif2 is None: + pass + elif netif1.net or netif2.net: + if netif1.net != netif2.net: + if not netif1.up or not netif2.up: + pass + else: + raise ValueError, "no common network found" + net = netif1.net + netif1.detachnet() + netif2.detachnet() + if net.numnetif() == 0: + self.session.delobj(net.objid) + node1.delnetif(ifindex1) + node2.delnetif(ifindex2) + else: + ''' Modify a link. + ''' + bw = msg.gettlv(coreapi.CORE_TLV_LINK_BW) + delay = msg.gettlv(coreapi.CORE_TLV_LINK_DELAY) + loss = msg.gettlv(coreapi.CORE_TLV_LINK_PER) + duplicate = msg.gettlv(coreapi.CORE_TLV_LINK_DUP) + jitter = msg.gettlv(coreapi.CORE_TLV_LINK_JITTER) + numnet = 0 + # TODO: clean up all this logic. Having the add flag or not + # should use the same code block. + if node1 is None and node2 is None: + if net and net2: + # modify link between nets + netif = net.getlinknetif(net2) + upstream = False + if netif is None: + upstream = True + netif = net2.getlinknetif(net) + if netif is None: + raise ValueError, "modify unknown link between nets" + if upstream: + netif.swapparams('_params_up') + net.linkconfig(netif, bw = bw, delay = delay, + loss = loss, duplicate = duplicate, + jitter = jitter, devname = netif.name) + netif.swapparams('_params_up') + else: + net.linkconfig(netif, bw = bw, delay = delay, + loss = loss, duplicate = duplicate, + jitter = jitter) + if not unidirectional: + if upstream: + net2.linkconfig(netif, bw = bw, delay = delay, + loss = loss, + duplicate = duplicate, + jitter = jitter) + else: + netif.swapparams('_params_up') + net2.linkconfig(netif, bw = bw, delay = delay, + loss = loss, + duplicate = duplicate, + jitter = jitter, + devname = netif.name) + netif.swapparams('_params_up') + else: + raise ValueError, "modify link for unknown nodes" + elif node1 is None: + # node1 = layer 2node, node2 = layer3 node + net.linkconfig(node2.netif(ifindex2, net), bw = bw, + delay = delay, loss = loss, + duplicate = duplicate, jitter = jitter) + elif node2 is None: + # node2 = layer 2node, node1 = layer3 node + net.linkconfig(node1.netif(ifindex1, net), bw = bw, + delay = delay, loss = loss, + duplicate = duplicate, jitter = jitter) + else: + nets = node1.commonnets(node2) + for (net, netif1, netif2) in nets: + if ifindex1 is not None and \ + ifindex1 != node1.getifindex(netif1): + continue + net.linkconfig(netif1, bw = bw, delay = delay, + loss = loss, duplicate = duplicate, + jitter = jitter, netif2 = netif2) + if not unidirectional: + net.linkconfig(netif2, bw = bw, delay = delay, + loss = loss, duplicate = duplicate, + jitter = jitter, netif2 = netif1) + numnet += 1 + if numnet == 0: + raise ValueError, "no common network found" + + + finally: + if node1: + node1.lock.release() + if node2: + node2.lock.release() + return () + + def handleexecmsg(self, msg): + ''' Execute Message handler + ''' + nodenum = msg.gettlv(coreapi.CORE_TLV_EXEC_NODE) + execnum = msg.gettlv(coreapi.CORE_TLV_EXEC_NUM) + exectime = msg.gettlv(coreapi.CORE_TLV_EXEC_TIME) + cmd = msg.gettlv(coreapi.CORE_TLV_EXEC_CMD) + + # local flag indicates command executed locally, not on a node + if nodenum is None and not msg.flags & coreapi.CORE_API_LOC_FLAG: + raise ValueError, "Execute Message is missing node number." + if execnum is None: + raise ValueError, "Execute Message is missing execution number." + if exectime is not None: + self.session.addevent(exectime, node=nodenum, name=None, data=cmd) + return () + + try: + n = self.session.obj(nodenum) + except KeyError: + # XXX wait and queue this message to try again later + # XXX maybe this should be done differently + if not msg.flags & coreapi.CORE_API_LOC_FLAG: + time.sleep(0.125) + self.queuemsg(msg) + return () + else: + pass + # build common TLV items for reply + tlvdata = "" + if nodenum is not None: + tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_NODE, + nodenum) + tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_NUM, execnum) + tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_CMD, cmd) + + if msg.flags & coreapi.CORE_API_TTY_FLAG: + if nodenum is None: + raise NotImplementedError + # echo back exec message with cmd for spawning interactive terminal + if cmd == "bash": + cmd = "/bin/bash" + res = n.termcmdstring(cmd) + tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_RESULT, + res) + reply = coreapi.CoreExecMessage.pack(coreapi.CORE_API_TTY_FLAG, + tlvdata) + return (reply, ) + else: + if self.verbose: + self.info("execute message with cmd = '%s'" % cmd) + # execute command and send a response + if msg.flags & coreapi.CORE_API_STR_FLAG or \ + msg.flags & coreapi.CORE_API_TXT_FLAG: + # shlex.split() handles quotes within the string + if msg.flags & coreapi.CORE_API_LOC_FLAG: + status, res = cmdresult(shlex.split(cmd)) + else: + status, res = n.cmdresult(shlex.split(cmd)) + if self.verbose: + self.info("done exec cmd='%s' with status=%d res=(%d bytes)" + % (cmd, status, len(res))) + if msg.flags & coreapi.CORE_API_TXT_FLAG: + tlvdata += coreapi.CoreExecTlv.pack( \ + coreapi.CORE_TLV_EXEC_RESULT, res) + if msg.flags & coreapi.CORE_API_STR_FLAG: + tlvdata += coreapi.CoreExecTlv.pack( \ + coreapi.CORE_TLV_EXEC_STATUS, status) + reply = coreapi.CoreExecMessage.pack(0, tlvdata) + return (reply, ) + # execute the command with no response + else: + if msg.flags & coreapi.CORE_API_LOC_FLAG: + mutedetach(shlex.split(cmd)) + else: + n.cmd(shlex.split(cmd), wait=False) + return () + + + def handleregmsg(self, msg): + ''' Register Message Handler + ''' + replies = [] + # execute a Python script or XML file + ex = msg.gettlv(coreapi.CORE_TLV_REG_EXECSRV) + if ex: + try: + self.info("executing '%s'" % ex) + if not isinstance(self.server, CoreServer): # CoreUdpServer): + server = self.server.mainserver + # elif isinstance(self.server, CoreAuxServer): + # server = self.server.mainserver + else: + server = self.server + if msg.flags & coreapi.CORE_API_STR_FLAG: + old_session_ids = set(server.getsessionids()) + sys.argv = shlex.split(ex) + filename = sys.argv[0] + if os.path.splitext(filename)[1].lower() == '.xml': + session = server.getsession(useexisting=False) + try: + opensessionxml(session, filename, start=True) + except: + session.shutdown() + server.delsession(session) + raise + else: + t = threading.Thread(target = execfile, + args=(filename, {'__file__': filename, + 'server': server})) + t.daemon = True + t.start() + time.sleep(0.25) # allow time for session creation + if msg.flags & coreapi.CORE_API_STR_FLAG: + new_session_ids = set(server.getsessionids()) + new_sid = new_session_ids.difference(old_session_ids) + try: + sid = new_sid.pop() + self.info("executed '%s' as session %d" % (ex, sid)) + except KeyError: + self.info("executed '%s' with unknown session ID" % ex) + return replies + self.info("checking session %d for RUNTIME state" % sid) + session = self.server.getsession(sessionid=sid, useexisting=True) + retries = 10 + # wait for session to enter RUNTIME state, to prevent GUI from + # connecting while nodes are still being instantiated + while session.getstate() != coreapi.CORE_EVENT_RUNTIME_STATE: + self.info("waiting for session %d to enter RUNTIME state" % sid) + time.sleep(1) + retries -= 1 + if retries <= 0: + self.info("session %d did not enter RUNTIME state" % sid) + return replies + tlvdata = coreapi.CoreRegTlv.pack( \ + coreapi.CORE_TLV_REG_EXECSRV, ex) + tlvdata += coreapi.CoreRegTlv.pack( \ + coreapi.CORE_TLV_REG_SESSION, "%s" % sid) + msg = coreapi.CoreRegMessage.pack(0, tlvdata) + replies.append(msg) + except Exception, e: + self.warn("error executing '%s': %s" % \ + (ex, traceback.format_exc())) + tlvdata = coreapi.CoreExceptionTlv.pack( \ + coreapi.CORE_TLV_EXCP_LEVEL, 2) + tlvdata += coreapi.CoreExceptionTlv.pack( \ + coreapi.CORE_TLV_EXCP_TEXT, str(e)) + msg = coreapi.CoreExceptionMessage.pack(0, tlvdata) + replies.append(msg) + return replies + + gui = msg.gettlv(coreapi.CORE_TLV_REG_GUI) + if gui is None: + self.info("ignoring Register message") + else: + # register capabilities with the GUI + self.master = True + found = self.server.setsessionmaster(self) + replies.append(self.register()) + replies.append(self.server.tosessionmsg()) + return replies + + def handleconfmsg(self, msg): + ''' Configuration Message handler + ''' + nodenum = msg.gettlv(coreapi.CORE_TLV_CONF_NODE) + objname = msg.gettlv(coreapi.CORE_TLV_CONF_OBJ) + if self.verbose: + self.info("Configuration message for %s node %s" % \ + (objname, nodenum)) + # dispatch to any registered callback for this object type + replies = self.session.confobj(objname, self.session, msg) + # config requests usually have a reply with default data + return replies + + def handlefilemsg(self, msg): + ''' File Message handler + ''' + if msg.flags & coreapi.CORE_API_ADD_FLAG: + nodenum = msg.gettlv(coreapi.CORE_TLV_NODE_NUMBER) + filename = msg.gettlv(coreapi.CORE_TLV_FILE_NAME) + type = msg.gettlv(coreapi.CORE_TLV_FILE_TYPE) + srcname = msg.gettlv(coreapi.CORE_TLV_FILE_SRCNAME) + data = msg.gettlv(coreapi.CORE_TLV_FILE_DATA) + cmpdata = msg.gettlv(coreapi.CORE_TLV_FILE_CMPDATA) + + if cmpdata is not None: + self.warn("Compressed file data not implemented for File " \ + "message.") + return () + if srcname is not None and data is not None: + self.warn("ignoring invalid File message: source and data " \ + "TLVs are both present") + return () + + # some File Messages store custom files in services, + # prior to node creation + if type is not None: + if type[:8] == "service:": + self.session.services.setservicefile(nodenum, type, + filename, srcname, data) + return () + elif type[:5] == "hook:": + self.session.sethook(type, filename, srcname, data) + return () + # writing a file to the host + if nodenum is None: + if srcname is not None: + shutil.copy2(srcname, filename) + else: + with open(filename, "w") as f: + f.write(data) + return () + try: + n = self.session.obj(nodenum) + except KeyError: + # XXX wait and queue this message to try again later + # XXX maybe this should be done differently + self.warn("File message for %s for node number %s queued." % \ + (filename, nodenum)) + time.sleep(0.125) + self.queuemsg(msg) + return () + if srcname is not None: + n.addfile(srcname, filename) + elif data is not None: + n.nodefile(filename, data) + else: + raise NotImplementedError + return () + + def handleifacemsg(self, msg): + ''' Interface Message handler + ''' + self.info("ignoring Interface message") + return () + + def handleeventmsg(self, msg): + ''' Event Message handler + ''' + eventtype = msg.gettlv(coreapi.CORE_TLV_EVENT_TYPE) + if eventtype is None: + raise NotImplementedError, "Event message missing event type" + node = msg.gettlv(coreapi.CORE_TLV_EVENT_NODE) + + if self.verbose: + self.info("EVENT %d: %s at %s" % \ + (eventtype, coreapi.event_types[eventtype], time.ctime())) + if eventtype <= coreapi.CORE_EVENT_SHUTDOWN_STATE: + if node is not None: + try: + n = self.session.obj(node) + except KeyError: + raise KeyError, "Event message for unknown node %d" % node + if eventtype == coreapi.CORE_EVENT_INSTANTIATION_STATE: + # configure mobility models for WLAN added during runtime + if isinstance(n, pycore.nodes.WlanNode): + return (self.session.mobility.startup(nodenums=(n.objid,))) + self.warn("dropping unhandled Event message with node number") + return () + self.session.setstate(state=eventtype, info=True, sendevent=False) + + if eventtype == coreapi.CORE_EVENT_DEFINITION_STATE: + # clear all session objects in order to receive new definitions + self.session.delobjs() + self.session.delhooks() + self.session.broker.reset() + elif eventtype == coreapi.CORE_EVENT_CONFIGURATION_STATE: + pass + elif eventtype == coreapi.CORE_EVENT_INSTANTIATION_STATE: + if len(self.handlerthreads) > 1: + # TODO: sync handler threads here before continuing + time.sleep(2.0) # XXX + # done receiving node/link configuration, ready to instantiate + self.session.instantiate(handler=self) + elif eventtype == coreapi.CORE_EVENT_RUNTIME_STATE: + if self.session.master: + self.warn("Unexpected event message: RUNTIME state received " \ + "at session master") + else: + # master event queue is started in session.checkruntime() + self.session.evq.run() + elif eventtype == coreapi.CORE_EVENT_DATACOLLECT_STATE: + self.session.datacollect() + elif eventtype == coreapi.CORE_EVENT_SHUTDOWN_STATE: + if self.session.master: + self.warn("Unexpected event message: SHUTDOWN state received " \ + "at session master") + elif eventtype in (coreapi.CORE_EVENT_START, coreapi.CORE_EVENT_STOP, \ + coreapi.CORE_EVENT_RESTART, \ + coreapi.CORE_EVENT_PAUSE, \ + coreapi.CORE_EVENT_RECONFIGURE): + handled = False + name = msg.gettlv(coreapi.CORE_TLV_EVENT_NAME) + if name: + # TODO: register system for event message handlers, + # like confobjs + if name.startswith("service:"): + self.session.services.handleevent(msg) + handled = True + elif name.startswith("mobility:"): + self.session.mobility.handleevent(msg) + handled = True + else: + pass + if not handled: + self.warn("Unhandled event message: event type %s (%s)" % \ + (eventtype, coreapi.state_name(eventtype))) + elif eventtype == coreapi.CORE_EVENT_FILE_OPEN: + self.session.delobjs() + self.session.delhooks() + self.session.broker.reset() + filename = msg.tlvdata[coreapi.CORE_TLV_EVENT_NAME] + opensessionxml(self.session, filename) + return self.session.sendobjs() + elif eventtype == coreapi.CORE_EVENT_FILE_SAVE: + filename = msg.tlvdata[coreapi.CORE_TLV_EVENT_NAME] + savesessionxml(self.session, filename, self.session.cfg['xmlfilever']) + elif eventtype == coreapi.CORE_EVENT_SCHEDULED: + etime = msg.gettlv(coreapi.CORE_TLV_EVENT_TIME) + node = msg.gettlv(coreapi.CORE_TLV_EVENT_NODE) + name = msg.gettlv(coreapi.CORE_TLV_EVENT_NAME) + data = msg.gettlv(coreapi.CORE_TLV_EVENT_DATA) + if etime is None: + self.warn("Event message scheduled event missing start time") + return () + if msg.flags & coreapi.CORE_API_ADD_FLAG: + self.session.addevent(float(etime), node=node, name=name, + data=data) + else: + raise NotImplementedError + else: + self.warn("Unhandled event message: event type %d" % eventtype) + return () + + def handlesessionmsg(self, msg): + ''' Session Message handler + ''' + replies = [] + sid_str = msg.gettlv(coreapi.CORE_TLV_SESS_NUMBER) + name_str = msg.gettlv(coreapi.CORE_TLV_SESS_NAME) + file_str = msg.gettlv(coreapi.CORE_TLV_SESS_FILE) + nc_str = msg.gettlv(coreapi.CORE_TLV_SESS_NODECOUNT) + thumb = msg.gettlv(coreapi.CORE_TLV_SESS_THUMB) + user = msg.gettlv(coreapi.CORE_TLV_SESS_USER) + sids = coreapi.str_to_list(sid_str) + names = coreapi.str_to_list(name_str) + files = coreapi.str_to_list(file_str) + ncs = coreapi.str_to_list(nc_str) + self.info("SESSION message flags=0x%x sessions=%s" % (msg.flags, sid_str)) + + if msg.flags == 0: + # modify a session + i = 0 + for sid in sids: + sid = int(sid) + if sid == 0: + session = self.session + else: + session = self.server.getsession(sessionid = sid, + useexisting = True) + if session is None: + self.info("session %s not found" % sid) + i += 1 + continue + self.info("request to modify to session %s" % session.sessionid) + if names is not None: + session.name = names[i] + if files is not None: + session.filename = files[i] + if ncs is not None: + session.node_count = ncs[i] + if thumb is not None: + session.setthumbnail(thumb) + if user is not None: + session.setuser(user) + i += 1 + else: + if msg.flags & coreapi.CORE_API_STR_FLAG and not \ + msg.flags & coreapi.CORE_API_ADD_FLAG: + # status request flag: send list of sessions + return (self.server.tosessionmsg(), ) + # handle ADD or DEL flags + for sid in sids: + sid = int(sid) + session = self.server.getsession(sessionid = sid, + useexisting = True) + if session is None: + self.info("session %s not found (flags=0x%x)" % \ + (sid, msg.flags)) + continue + if session.server is None: + # this needs to be set when executing a Python script + session.server = self.server + if msg.flags & coreapi.CORE_API_ADD_FLAG: + # connect to the first session that exists + self.info("request to connect to session %s" % sid) + # this may shutdown the session if no handlers exist + self.session.disconnect(self) + self.session = session + self.session.connect(self) + if user is not None: + self.session.setuser(user) + if msg.flags & coreapi.CORE_API_STR_FLAG: + replies.extend(self.session.sendobjs()) + elif msg.flags & coreapi.CORE_API_DEL_FLAG: + # shut down the specified session(s) + self.info("request to terminate session %s" % sid) + session.setstate(state=coreapi.CORE_EVENT_DATACOLLECT_STATE, + info=True, sendevent=True) + session.setstate(state=coreapi.CORE_EVENT_SHUTDOWN_STATE, + info=True, sendevent=True) + session.shutdown() + else: + self.warn("unhandled session flags for session %s" % sid) + return replies + +class CoreDatagramRequestHandler(CoreRequestHandler): + ''' A child of the CoreRequestHandler class for handling connectionless + UDP messages. No new session is created; messages are handled immediately or + sometimes queued on existing session handlers. + ''' + + def __init__(self, request, client_address, server): + # TODO: decide which messages cannot be handled with connectionless UDP + self.msghandler = { + coreapi.CORE_API_NODE_MSG: self.handlenodemsg, + coreapi.CORE_API_LINK_MSG: self.handlelinkmsg, + coreapi.CORE_API_EXEC_MSG: self.handleexecmsg, + coreapi.CORE_API_REG_MSG: self.handleregmsg, + coreapi.CORE_API_CONF_MSG: self.handleconfmsg, + coreapi.CORE_API_FILE_MSG: self.handlefilemsg, + coreapi.CORE_API_IFACE_MSG: self.handleifacemsg, + coreapi.CORE_API_EVENT_MSG: self.handleeventmsg, + coreapi.CORE_API_SESS_MSG: self.handlesessionmsg, + } + self.nodestatusreq = {} + self.master = False + self.session = None + self.verbose = bool(server.mainserver.cfg['verbose'].lower() == "true") + self.debug = bool(server.mainserver.cfg['debug'].lower() == "true") + SocketServer.BaseRequestHandler.__init__(self, request, + client_address, server) + + def setup(self): + ''' Client has connected, set up a new connection. + ''' + if self.verbose: + self.info("new UDP connection: %s:%s" % self.client_address) + + def handle(self): + msg = self.recvmsg() + + def finish(self): + return SocketServer.BaseRequestHandler.finish(self) + + def recvmsg(self): + ''' Receive data, parse a CoreMessage and queue it onto an existing + session handler's queue, if available. + ''' + data = self.request[0] + socket = self.request[1] + msghdr = data[:coreapi.CoreMessage.hdrsiz] + if len(msghdr) < coreapi.CoreMessage.hdrsiz: + raise IOError, "error receiving header (received %d bytes)" % \ + len(msghdr) + msgtype, msgflags, msglen = coreapi.CoreMessage.unpackhdr(msghdr) + if msglen == 0: + self.warn("received message with no data") + return + if len(data) != coreapi.CoreMessage.hdrsiz + msglen: + self.warn("received message length does not match received data " \ + "(%s != %s)" % \ + (len(data), coreapi.CoreMessage.hdrsiz + msglen)) + raise IOError + elif self.verbose: + self.info("UDP socket received message type=%d len=%d" % \ + (msgtype, msglen)) + try: + msgcls = coreapi.msg_class(msgtype) + msg = msgcls(msgflags, msghdr, data[coreapi.CoreMessage.hdrsiz:]) + except KeyError: + msg = coreapi.CoreMessage(msgflags, msghdr, + data[coreapi.CoreMessage.hdrsiz:]) + msg.msgtype = msgtype + self.warn("unimplemented core message type: %s" % msg.typestr()) + return + sids = msg.sessionnumbers() + msg.queuedtimes = 0 + #self.info("UDP message has session numbers: %s" % sids) + if len(sids) > 0: + for sid in sids: + sess = self.server.mainserver.getsession(sessionid=sid, + useexisting=True) + if sess: + self.session = sess + sess.broadcast(self, msg) + self.handlemsg(msg) + else: + self.warn("Session %d in %s message not found." % \ + (sid, msg.typestr())) + else: + # no session specified, find an existing one + sess = self.server.mainserver.getsession(sessionid=0, + useexisting=True) + if sess or msg.msgtype == coreapi.CORE_API_REG_MSG: + self.session = sess + if sess: + sess.broadcast(self, msg) + self.handlemsg(msg) + else: + self.warn("No active session, dropping %s message." % \ + msg.typestr()) + + def queuemsg(self, msg): + ''' UDP handlers are short-lived and do not have message queues. + ''' + raise Exception, "Unable to queue %s message for later processing " \ + "using UDP!" % msg.typestr() + + def sendall(self, data): + ''' Use sendto() on the connectionless UDP socket. + ''' + self.request[1].sendto(data, self.client_address) + + + + +class BaseAuxRequestHandler(CoreRequestHandler): + ''' + This is the superclass for auxiliary handlers in CORE. A concrete auxiliary handler class + must, at a minimum, define the recvmsg(), sendall(), and dispatchreplies() methods. + See SockerServer.BaseRequestHandler for parameter details. + ''' + + def __init__(self, request, client_address, server): + self.msghandler = { + coreapi.CORE_API_NODE_MSG: self.handlenodemsg, + coreapi.CORE_API_LINK_MSG: self.handlelinkmsg, + coreapi.CORE_API_EXEC_MSG: self.handleexecmsg, + coreapi.CORE_API_REG_MSG: self.handleregmsg, + coreapi.CORE_API_CONF_MSG: self.handleconfmsg, + coreapi.CORE_API_FILE_MSG: self.handlefilemsg, + coreapi.CORE_API_IFACE_MSG: self.handleifacemsg, + coreapi.CORE_API_EVENT_MSG: self.handleeventmsg, + coreapi.CORE_API_SESS_MSG: self.handlesessionmsg, + } + self.handlerthreads = [] + self.nodestatusreq = {} + self.master = False + self.session = None + self.verbose = bool(server.mainserver.cfg['verbose'].lower() == "true") + self.debug = bool(server.mainserver.cfg['debug'].lower() == "true") + SocketServer.BaseRequestHandler.__init__(self, request, + client_address, server) + + def setup(self): + ''' New client has connected to the auxiliary server. + ''' + if self.verbose: + self.info("new auxiliary server client: %s:%s" % self.client_address) + + def handle(self): + ''' + The handler main loop + ''' + port = self.request.getpeername()[1] + self.session = self.server.mainserver.getsession(sessionid = port, + useexisting = False) + self.session.connect(self) + while True: + try: + msgs = self.recvmsg() + if msgs: + for msg in msgs: + self.session.broadcast(self, msg) + self.handlemsg(msg) + except EOFError: + break; + except IOError, e: + self.warn("IOError in CoreAuxRequestHandler: %s" % e) + break; + + def finish(self): + ''' + Disconnect the client + ''' + if self.session: + self.session.disconnect(self) + return SocketServer.BaseRequestHandler.finish(self) + + ''' + ======================================================================= + Concrete AuxRequestHandler classes must redefine the following methods + ======================================================================= + ''' + + + def recvmsg(self): + ''' + Receive data from the client in the supported format. Parse, transform to CORE API format and + return transformed messages. + + EXAMPLE: + return self.handler.request.recv(siz) + + ''' + pass + return None + + def dispatchreplies(self, replies, msg): + ''' + Dispatch CORE 'replies' to a previously received message 'msg' from a client. + Replies passed to this method follow the CORE API. This method allows transformation to + the form supported by the auxiliary handler and within the context of 'msg'. + Add transformation and transmission code here. + + EXAMPLE: + transformed_replies = stateful_transform (replies, msg) # stateful_transform method needs to be defined + if transformed_replies: + for reply in transformed_replies: + try: + self.request.sendall(reply) + except Exception, e: + if self.debug: + self.info("-"*60) + traceback.print_exc(file=sys.stdout) + self.info("-"*60) + raise e + + ''' + pass + + + def sendall(self, data): + ''' + CORE calls this method when data needs to be asynchronously sent to a client. The data is + in CORE API format. This method allows transformation to the required format supported by this + handler prior to transmission. + + EXAMPLE: + msgs = self.transform(data) # transform method needs to be defined + if msgs: + for msg in msgs: + try: + self.request.sendall(reply) + except Exception, e: + if self.debug: + self.info("-"*60) + traceback.print_exc(file=sys.stdout) + self.info("-"*60) + raise e + ''' + pass + + + + + + + diff --git a/daemon/sbin/core-daemon b/daemon/sbin/core-daemon index f43c78f1..f9126b16 100755 --- a/daemon/sbin/core-daemon +++ b/daemon/sbin/core-daemon @@ -13,10 +13,10 @@ messages and instantiates emulated nodes and networks within the kernel. Various message handlers are defined and some support for sending messages. ''' -import SocketServer, struct, sys, threading, time, traceback import os, optparse, ConfigParser, gc, shlex, socket, shutil import atexit import signal +import importlib try: from core import pycore @@ -31,11 +31,10 @@ except ImportError: if "/usr/lib64/python2.7/site-packages" in sys.path: sys.path.append("/usr/local/lib64/python2.7/site-packages") from core import pycore +from core.coreserver import * from core.constants import * from core.api import coreapi -from core.coreobj import PyCoreNet -from core.misc.utils import hexdump, daemonize, cmdresult, mutedetach, closeonexec -from core.misc.xmlsession import opensessionxml, savesessionxml +from core.misc.utils import daemonize, closeonexec DEFAULT_MAXFD = 1024 @@ -68,1525 +67,40 @@ coreapi.add_node_class("CORE_NODE_TUNNEL", coreapi.add_node_class("CORE_NODE_EMANE", coreapi.CORE_NODE_EMANE, pycore.nodes.EmaneNode) -class CoreRequestHandler(SocketServer.BaseRequestHandler): - ''' The SocketServer class uses the RequestHandler class for servicing - requests, mainly through the handle() method. The CoreRequestHandler - has the following basic flow: - 1. Client connects and request comes in via handle(). - 2. handle() calls recvmsg() in a loop. - 3. recvmsg() does a recv() call on the socket performs basic - checks that this we received a CoreMessage, returning it. - 4. The message data is queued using queuemsg(). - 5. The handlerthread() thread pops messages from the queue and uses - handlemsg() to invoke the appropriate handler for that message type. - +# +# UDP server startup +# +def startudp(mainserver, server_address): + ''' Start a thread running a UDP server on the same host,port for + connectionless requests. ''' - - maxmsgqueuedtimes = 8 - - def __init__(self, request, client_address, server): - self.done = False - self.msghandler = { - coreapi.CORE_API_NODE_MSG: self.handlenodemsg, - coreapi.CORE_API_LINK_MSG: self.handlelinkmsg, - coreapi.CORE_API_EXEC_MSG: self.handleexecmsg, - coreapi.CORE_API_REG_MSG: self.handleregmsg, - coreapi.CORE_API_CONF_MSG: self.handleconfmsg, - coreapi.CORE_API_FILE_MSG: self.handlefilemsg, - coreapi.CORE_API_IFACE_MSG: self.handleifacemsg, - coreapi.CORE_API_EVENT_MSG: self.handleeventmsg, - coreapi.CORE_API_SESS_MSG: self.handlesessionmsg, - } - self.msgq = [] - self.msgcv = threading.Condition() - self.nodestatusreq = {} - numthreads = int(server.cfg['numthreads']) - if numthreads < 1: - raise ValueError, \ - "invalid number of threads: %s" % numthreads - self.handlerthreads = [] - while numthreads: - t = threading.Thread(target = self.handlerthread) - self.handlerthreads.append(t) - t.start() - numthreads -= 1 - self.master = False - self.verbose = bool(server.cfg['verbose'].lower() == "true") - self.debug = bool(server.cfg['debug'].lower() == "true") - self.session = None - #self.numwlan = 0 - closeonexec(request.fileno()) - SocketServer.BaseRequestHandler.__init__(self, request, - client_address, server) - - def setup(self): - ''' Client has connected, set up a new connection. - ''' - self.info("new TCP connection: %s:%s" % self.client_address) - #self.register() + mainserver.udpserver = CoreUdpServer(server_address, + CoreDatagramRequestHandler, mainserver) + mainserver.udpthread = threading.Thread(target = mainserver.udpserver.start) + mainserver.udpthread.daemon = True + mainserver.udpthread.start() + return mainserver.udpserver - def finish(self): - ''' Client has disconnected, end this request handler and disconnect - from the session. Shutdown sessions that are not running. - ''' - if self.verbose: - self.info("client disconnected: notifying threads") - max_attempts = 5 - timeout = 0.0625 # wait for 1.9375s max - while len(self.msgq) > 0 and max_attempts > 0: - if self.verbose: - self.info("%d messages remain in queue (%d)" % \ - (len(self.msgq), max_attempts)) - max_attempts -= 1 - self.msgcv.acquire() - self.msgcv.notifyAll() # drain msgq before dying - self.msgcv.release() - time.sleep(timeout) # allow time for msg processing - timeout *= 2 # backoff timer - self.msgcv.acquire() - self.done = True - self.msgcv.notifyAll() - self.msgcv.release() - for t in self.handlerthreads: - if self.verbose: - self.info("waiting for thread: %s" % t.getName()) - timeout = 2.0 # seconds - t.join(timeout) - if t.isAlive(): - self.warn("joining %s failed: still alive after %s sec" % - (t.getName(), timeout)) - self.info("connection closed: %s:%s" % self.client_address) - if self.session: - self.session.disconnect(self) - return SocketServer.BaseRequestHandler.finish(self) - - - def info(self, msg): - ''' Utility method for writing output to stdout. - ''' - print msg - sys.stdout.flush() - - - def warn(self, msg): - ''' Utility method for writing output to stderr. - ''' - print >> sys.stderr, msg - sys.stderr.flush() - - def register(self): - ''' Return a Register Message - ''' - self.info("GUI has connected to session %d at %s" % \ - (self.session.sessionid, time.ctime())) - tlvdata = "" - tlvdata += coreapi.CoreRegTlv.pack(coreapi.CORE_TLV_REG_EXECSRV, - "core-daemon") - tlvdata += coreapi.CoreRegTlv.pack(coreapi.CORE_TLV_REG_EMULSRV, - "core-daemon") - tlvdata += self.session.confobjs_to_tlvs() - return coreapi.CoreRegMessage.pack(coreapi.CORE_API_ADD_FLAG, tlvdata) - - def sendall(self, data): - ''' Send raw data to the other end of this TCP connection - using socket's sendall(). - ''' - return self.request.sendall(data) - - def recvmsg(self): - ''' Receive data and return a CORE API message object. - ''' - try: - msghdr = self.request.recv(coreapi.CoreMessage.hdrsiz) - if self.debug and len(msghdr) > 0: - self.info("received message header:\n%s" % hexdump(msghdr)) - except Exception, e: - raise IOError, "error receiving header (%s)" % e - if len(msghdr) != coreapi.CoreMessage.hdrsiz: - if len(msghdr) == 0: - raise EOFError, "client disconnected" - else: - raise IOError, "invalid message header size" - msgtype, msgflags, msglen = coreapi.CoreMessage.unpackhdr(msghdr) - if msglen == 0: - self.warn("received message with no data") - data = "" - while len(data) < msglen: - data += self.request.recv(msglen - len(data)) - if self.debug: - self.info("received message data:\n%s" % hexdump(data)) - if len(data) > msglen: - self.warn("received message length does not match received data " \ - "(%s != %s)" % (len(data), msglen)) - raise IOError - try: - msgcls = coreapi.msg_class(msgtype) - msg = msgcls(msgflags, msghdr, data) - except KeyError: - msg = coreapi.CoreMessage(msgflags, msghdr, data) - msg.msgtype = msgtype - self.warn("unimplemented core message type: %s" % msg.typestr()) - return msg - - - def queuemsg(self, msg): - ''' Queue an API message for later processing. - ''' - if msg.queuedtimes >= self.maxmsgqueuedtimes: - self.warn("dropping message queued %d times: %s" % - (msg.queuedtimes, msg)) - return - if self.debug: - self.info("queueing msg (queuedtimes = %s): type %s" % - (msg.queuedtimes, msg.msgtype)) - msg.queuedtimes += 1 - self.msgcv.acquire() - self.msgq.append(msg) - self.msgcv.notify() - self.msgcv.release() - - def handlerthread(self): - ''' CORE API message handling loop that is spawned for each server - thread; get CORE API messages from the incoming message queue, - and call handlemsg() for processing. - ''' - while not self.done: - # get a coreapi.CoreMessage() from the incoming queue - self.msgcv.acquire() - while not self.msgq: - self.msgcv.wait() - if self.done: - self.msgcv.release() - return - msg = self.msgq.pop(0) - self.msgcv.release() - self.handlemsg(msg) - - - def handlemsg(self, msg): - ''' Handle an incoming message; dispatch based on message type, - optionally sending replies. - ''' - if self.session and self.session.broker.handlemsg(msg): - if self.debug: - self.info("%s forwarding message:\n%s" % - (threading.currentThread().getName(), msg)) - return - - if self.debug: - self.info("%s handling message:\n%s" % - (threading.currentThread().getName(), msg)) - - if msg.msgtype not in self.msghandler: - self.warn("no handler for message type: %s" % - msg.typestr()) - return - msghandler = self.msghandler[msg.msgtype] - - try: - replies = msghandler(msg) - for reply in replies: - if self.debug: - msgtype, msgflags, msglen = \ - coreapi.CoreMessage.unpackhdr(reply) - try: - rmsg = coreapi.msg_class(msgtype)(msgflags, - reply[:coreapi.CoreMessage.hdrsiz], - reply[coreapi.CoreMessage.hdrsiz:]) - except KeyError: - # multiple TLVs of same type cause KeyError exception - rmsg = "CoreMessage (type %d flags %d length %d)" % \ - (msgtype, msgflags, msglen) - self.info("%s: reply msg:\n%s" % - (threading.currentThread().getName(), rmsg)) - try: - self.sendall(reply) - except Exception, e: - self.warn("Error sending reply data: %s" % e) - except Exception, e: - self.warn("%s: exception while handling msg:\n%s\n%s" % - (threading.currentThread().getName(), msg, - traceback.format_exc())) - - def handle(self): - ''' Handle a new connection request from a client. Dispatch to the - recvmsg() method for receiving data into CORE API messages, and - add them to an incoming message queue. - ''' - # use port as session id - port = self.request.getpeername()[1] - self.session = self.server.getsession(sessionid = port, - useexisting = False) - self.session.connect(self) - while True: - try: - msg = self.recvmsg() - except EOFError: - break - except IOError, e: - self.warn("IOError: %s" % e) - break - msg.queuedtimes = 0 - self.queuemsg(msg) - if (msg.msgtype == coreapi.CORE_API_SESS_MSG): - # delay is required for brief connections, allow session joining - time.sleep(0.125) - self.session.broadcast(self, msg) - #self.session.shutdown() - #del self.session - gc.collect() -# print "gc count:", gc.get_count() -# for o in gc.get_objects(): -# if isinstance(o, pycore.PyCoreObj): -# print "XXX XXX XXX PyCoreObj:", o -# for r in gc.get_referrers(o): -# print "XXX XXX XXX referrer:", gc.get_referrers(o) - - - def handlenodemsg(self, msg): - ''' Node Message handler - ''' - replies = [] - if msg.flags & coreapi.CORE_API_ADD_FLAG and \ - msg.flags & coreapi.CORE_API_DEL_FLAG: - self.warn("ignoring invalid message: " - "add and delete flag both set") - return () - nodenum = msg.tlvdata[coreapi.CORE_TLV_NODE_NUMBER] - nodexpos = msg.gettlv(coreapi.CORE_TLV_NODE_XPOS) - nodeypos = msg.gettlv(coreapi.CORE_TLV_NODE_YPOS) - canvas = msg.gettlv(coreapi.CORE_TLV_NODE_CANVAS) - icon = msg.gettlv(coreapi.CORE_TLV_NODE_ICON) - lat = msg.gettlv(coreapi.CORE_TLV_NODE_LAT) - lng = msg.gettlv(coreapi.CORE_TLV_NODE_LONG) - alt = msg.gettlv(coreapi.CORE_TLV_NODE_ALT) - if nodexpos is None and nodeypos is None and \ - lat is not None and lng is not None and alt is not None: - (x, y, z) = self.session.location.getxyz(float(lat), float(lng), - float(alt)) - nodexpos = int(x) - nodeypos = int(y) - # GUI can't handle lat/long, so generate another X/Y position message - tlvdata = "" - tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_NUMBER, - nodenum) - tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_XPOS, - nodexpos) - tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_YPOS, - nodeypos) - self.session.broadcastraw(self, coreapi.CoreNodeMessage.pack(0, tlvdata)) - - if msg.flags & coreapi.CORE_API_ADD_FLAG: - nodetype = msg.tlvdata[coreapi.CORE_TLV_NODE_TYPE] - try: - nodecls = coreapi.node_class(nodetype) - except KeyError: - try: - nodetypestr = " (%s)" % coreapi.node_types[nodetype] - except KeyError: - nodetypestr = "" - self.warn("warning: unimplemented node type: %s%s" % \ - (nodetype, nodetypestr)) - return () - start = False - if self.session.getstate() > coreapi.CORE_EVENT_DEFINITION_STATE: - start = True - - nodename = msg.tlvdata[coreapi.CORE_TLV_NODE_NAME] - model = msg.gettlv(coreapi.CORE_TLV_NODE_MODEL) - clsargs = { 'verbose': self.verbose, 'start': start } - if nodetype == coreapi.CORE_NODE_XEN: - clsargs['model'] = model - if nodetype == coreapi.CORE_NODE_RJ45: - if hasattr(self.session.options, 'enablerj45'): - if self.session.options.enablerj45 == '0': - clsargs['start'] = False - # this instantiates an object of class nodecls, - # creating the node or network - n = self.session.addobj(cls = nodecls, objid = nodenum, - name = nodename, **clsargs) - if nodexpos is not None and nodeypos is not None: - n.setposition(nodexpos, nodeypos, None) - if canvas is not None: - n.canvas = canvas - if icon is not None: - n.icon = icon - opaque = msg.gettlv(coreapi.CORE_TLV_NODE_OPAQUE) - if opaque is not None: - n.opaque = opaque - - # add services to a node, either from its services TLV or - # through the configured defaults for this node type - if nodetype == coreapi.CORE_NODE_DEF or \ - nodetype == coreapi.CORE_NODE_PHYS or \ - nodetype == coreapi.CORE_NODE_XEN: - if model is None: - # TODO: default model from conf file? - model = "router" - n.type = model - services_str = msg.gettlv(coreapi.CORE_TLV_NODE_SERVICES) - self.session.services.addservicestonode(n, model, services_str, - self.verbose) - # boot nodes if they are added after runtime (like - # session.bootnodes()) - if self.session.getstate() == coreapi.CORE_EVENT_RUNTIME_STATE: - if isinstance(n, pycore.nodes.PyCoreNode) and \ - not isinstance(n, pycore.nodes.RJ45Node): - self.session.writeobjs() - self.session.addremovectrlif(node=n, remove=False) - n.boot() - # self.session.updatectrlifhosts() - # n.validate() - if msg.flags & coreapi.CORE_API_STR_FLAG: - self.nodestatusreq[nodenum] = True - self.session.sendnodeemuid(self, nodenum) - - elif msg.flags & coreapi.CORE_API_STR_FLAG: - self.nodestatusreq[nodenum] = True - - elif msg.flags & coreapi.CORE_API_DEL_FLAG: - n = None - try: - n = self.session.obj(nodenum) - except KeyError: - pass - self.session.delobj(nodenum) - - if msg.flags & coreapi.CORE_API_STR_FLAG: - tlvdata = "" - tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_NUMBER, - nodenum) - flags = coreapi.CORE_API_DEL_FLAG | coreapi.CORE_API_LOC_FLAG - replies.append(coreapi.CoreNodeMessage.pack(flags, tlvdata)) - for reply in self.session.checkshutdown(): - replies.append(reply) - # Node modify message (no add/del flag) - else: - n = None - try: - n = self.session.obj(nodenum) - except KeyError: - if self.verbose: - self.warn("ignoring node message: unknown node number %s" \ - % nodenum) - #nodeemuid = msg.gettlv(coreapi.CORE_TLV_NODE_EMUID) - if nodexpos is None or nodeypos is None: - if self.verbose: - self.info("ignoring node message: nothing to do") - else: - if n: - n.setposition(nodexpos, nodeypos, None) - if n: - if canvas is not None: - n.canvas = canvas - if icon is not None: - n.icon = icon - - return replies - - - def handlelinkmsg(self, msg): - ''' Link Message handler - ''' - - nodenum1 = msg.gettlv(coreapi.CORE_TLV_LINK_N1NUMBER) - ifindex1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1NUM) - ipv41 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1IP4) - ipv4mask1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1IP4MASK) - mac1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1MAC) - ipv61 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1IP6) - ipv6mask1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1IP6MASK) - ifname1 = msg.gettlv(coreapi.CORE_TLV_LINK_IF1NAME) - - nodenum2 = msg.gettlv(coreapi.CORE_TLV_LINK_N2NUMBER) - ifindex2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2NUM) - ipv42 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2IP4) - ipv4mask2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2IP4MASK) - mac2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2MAC) - ipv62 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2IP6) - ipv6mask2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2IP6MASK) - ifname2 = msg.gettlv(coreapi.CORE_TLV_LINK_IF2NAME) - - node1 = None - node2 = None - net = None - net2 = None - - uni = msg.gettlv(coreapi.CORE_TLV_LINK_UNI) - if uni is not None and uni == 1: - unidirectional = True - else: - unidirectional = False - - - # one of the nodes may exist on a remote server - if nodenum1 is not None and nodenum2 is not None: - t = self.session.broker.gettunnel(nodenum1, nodenum2) - if isinstance(t, pycore.nodes.PyCoreNet): - net = t - if t.remotenum == nodenum1: - nodenum1 = None - else: - nodenum2 = None - # PhysicalNode connected via GreTap tunnel; uses adoptnetif() below - elif t is not None: - if t.remotenum == nodenum1: - nodenum1 = None - else: - nodenum2 = None - - - if nodenum1 is not None: - try: - n = self.session.obj(nodenum1) - except KeyError: - # XXX wait and queue this message to try again later - # XXX maybe this should be done differently - time.sleep(0.125) - self.queuemsg(msg) - return () - if isinstance(n, pycore.nodes.PyCoreNode): - node1 = n - elif isinstance(n, pycore.nodes.PyCoreNet): - if net is None: - net = n - else: - net2 = n - else: - raise ValueError, "unexpected object class: %s" % n - - if nodenum2 is not None: - try: - n = self.session.obj(nodenum2) - except KeyError: - # XXX wait and queue this message to try again later - # XXX maybe this should be done differently - time.sleep(0.125) - self.queuemsg(msg) - return () - if isinstance(n, pycore.nodes.PyCoreNode): - node2 = n - elif isinstance(n, pycore.nodes.PyCoreNet): - if net is None: - net = n - else: - net2 = n - else: - raise ValueError, "unexpected object class: %s" % n - - link_msg_type = msg.gettlv(coreapi.CORE_TLV_LINK_TYPE) - - if node1: - node1.lock.acquire() - if node2: - node2.lock.acquire() - - try: - if link_msg_type == coreapi.CORE_LINK_WIRELESS: - ''' Wireless link/unlink event - ''' - numwlan = 0 - objs = [node1, node2, net, net2] - objs = filter( lambda(x): x is not None, objs ) - if len(objs) < 2: - raise ValueError, "wireless link/unlink message between unknown objects" - - nets = objs[0].commonnets(objs[1]) - for (netcommon, netif1, netif2) in nets: - if not isinstance(netcommon, pycore.nodes.WlanNode) and \ - not isinstance(netcommon, pycore.nodes.EmaneNode): - continue - if msg.flags & coreapi.CORE_API_ADD_FLAG: - netcommon.link(netif1, netif2) - elif msg.flags & coreapi.CORE_API_DEL_FLAG: - netcommon.unlink(netif1, netif2) - else: - raise ValueError, "invalid flags for wireless link/unlink message" - numwlan += 1 - if numwlan == 0: - raise ValueError, \ - "no common network found for wireless link/unlink" - - elif msg.flags & coreapi.CORE_API_ADD_FLAG: - ''' Add a new link. - ''' - start = False - if self.session.getstate() > coreapi.CORE_EVENT_DEFINITION_STATE: - start = True - - if node1 and node2 and not net: - # a new wired link - net = self.session.addobj(cls = pycore.nodes.PtpNet, - verbose = self.verbose, - start = start) - - bw = msg.gettlv(coreapi.CORE_TLV_LINK_BW) - delay = msg.gettlv(coreapi.CORE_TLV_LINK_DELAY) - loss = msg.gettlv(coreapi.CORE_TLV_LINK_PER) - duplicate = msg.gettlv(coreapi.CORE_TLV_LINK_DUP) - jitter = msg.gettlv(coreapi.CORE_TLV_LINK_JITTER) - key = msg.gettlv(coreapi.CORE_TLV_LINK_KEY) - - netaddrlist = [] - #print " n1=%s n2=%s net=%s net2=%s" % (node1, node2, net, net2) - if node1 and net: - addrlist = [] - if ipv41 is not None and ipv4mask1 is not None: - addrlist.append("%s/%s" % (ipv41, ipv4mask1)) - if ipv61 is not None and ipv6mask1 is not None: - addrlist.append("%s/%s" % (ipv61, ipv6mask1)) - if ipv42 is not None and ipv4mask2 is not None: - netaddrlist.append("%s/%s" % (ipv42, ipv4mask2)) - if ipv62 is not None and ipv6mask2 is not None: - netaddrlist.append("%s/%s" % (ipv62, ipv6mask2)) - ifindex1 = node1.newnetif(net, addrlist = addrlist, - hwaddr = mac1, ifindex = ifindex1, ifname=ifname1) - net.linkconfig(node1.netif(ifindex1, net), bw = bw, - delay = delay, loss = loss, - duplicate = duplicate, jitter = jitter) - if node1 is None and net: - if ipv41 is not None and ipv4mask1 is not None: - netaddrlist.append("%s/%s" % (ipv41, ipv4mask1)) - # don't add this address again if node2 and net - ipv41 = None - if ipv61 is not None and ipv6mask1 is not None: - netaddrlist.append("%s/%s" % (ipv61, ipv6mask1)) - # don't add this address again if node2 and net - ipv61 = None - if node2 and net: - addrlist = [] - if ipv42 is not None and ipv4mask2 is not None: - addrlist.append("%s/%s" % (ipv42, ipv4mask2)) - if ipv62 is not None and ipv6mask2 is not None: - addrlist.append("%s/%s" % (ipv62, ipv6mask2)) - if ipv41 is not None and ipv4mask1 is not None: - netaddrlist.append("%s/%s" % (ipv41, ipv4mask1)) - if ipv61 is not None and ipv6mask1 is not None: - netaddrlist.append("%s/%s" % (ipv61, ipv6mask1)) - ifindex2 = node2.newnetif(net, addrlist = addrlist, - hwaddr = mac2, ifindex = ifindex2, ifname=ifname2) - if not unidirectional: - net.linkconfig(node2.netif(ifindex2, net), bw = bw, - delay = delay, loss = loss, - duplicate = duplicate, jitter = jitter) - if node2 is None and net2: - if ipv42 is not None and ipv4mask2 is not None: - netaddrlist.append("%s/%s" % (ipv42, ipv4mask2)) - if ipv62 is not None and ipv6mask2 is not None: - netaddrlist.append("%s/%s" % (ipv62, ipv6mask2)) - - # tunnel node finalized with this link message - if key and isinstance(net, pycore.nodes.TunnelNode): - net.setkey(key) - if len(netaddrlist) > 0: - net.addrconfig(netaddrlist) - if key and isinstance(net2, pycore.nodes.TunnelNode): - net2.setkey(key) - if len(netaddrlist) > 0: - net2.addrconfig(netaddrlist) - - if net and net2: - # two layer-2 networks linked together - if isinstance(net2, pycore.nodes.RJ45Node): - netif = net2.linknet(net) # RJ45 nodes have different linknet() - else: - netif = net.linknet(net2) - net.linkconfig(netif, bw = bw, delay = delay, loss = loss, - duplicate = duplicate, jitter = jitter) - if not unidirectional: - netif.swapparams('_params_up') - net2.linkconfig(netif, bw = bw, delay = delay, loss = loss, - duplicate = duplicate, jitter = jitter, - devname = netif.name) - netif.swapparams('_params_up') - - - elif net is None and net2 is None and \ - (node1 is None or node2 is None): - # apply address/parameters to PhysicalNodes - fx = (bw, delay, loss, duplicate, jitter) - addrlist = [] - if node1 and isinstance(node1, pycore.pnodes.PhysicalNode): - if ipv41 is not None and ipv4mask1 is not None: - addrlist.append("%s/%s" % (ipv41, ipv4mask1)) - if ipv61 is not None and ipv6mask1 is not None: - addrlist.append("%s/%s" % (ipv61, ipv6mask1)) - node1.adoptnetif(t, ifindex1, mac1, addrlist) - node1.linkconfig(t, bw, delay, loss, duplicate, jitter) - elif node2 and isinstance(node2, pycore.pnodes.PhysicalNode): - if ipv42 is not None and ipv4mask2 is not None: - addrlist.append("%s/%s" % (ipv42, ipv4mask2)) - if ipv62 is not None and ipv6mask2 is not None: - addrlist.append("%s/%s" % (ipv62, ipv6mask2)) - node2.adoptnetif(t, ifindex2, mac2, addrlist) - node2.linkconfig(t, bw, delay, loss, duplicate, jitter) - # delete a link - elif msg.flags & coreapi.CORE_API_DEL_FLAG: - ''' Remove a link. - ''' - if node1 and node2: - # TODO: fix this for the case where ifindex[1,2] are - # not specified - # a wired unlink event, delete the connecting bridge - netif1 = node1.netif(ifindex1) - netif2 = node2.netif(ifindex2) - if netif1 is None and netif2 is None: - nets = node1.commonnets(node2) - for (netcommon, tmp1, tmp2) in nets: - if (net and netcommon == net) or net is None: - netif1 = tmp1 - netif2 = tmp2 - break - if netif1 is None or netif2 is None: - pass - elif netif1.net or netif2.net: - if netif1.net != netif2.net: - if not netif1.up or not netif2.up: - pass - else: - raise ValueError, "no common network found" - net = netif1.net - netif1.detachnet() - netif2.detachnet() - if net.numnetif() == 0: - self.session.delobj(net.objid) - node1.delnetif(ifindex1) - node2.delnetif(ifindex2) - else: - ''' Modify a link. - ''' - bw = msg.gettlv(coreapi.CORE_TLV_LINK_BW) - delay = msg.gettlv(coreapi.CORE_TLV_LINK_DELAY) - loss = msg.gettlv(coreapi.CORE_TLV_LINK_PER) - duplicate = msg.gettlv(coreapi.CORE_TLV_LINK_DUP) - jitter = msg.gettlv(coreapi.CORE_TLV_LINK_JITTER) - numnet = 0 - # TODO: clean up all this logic. Having the add flag or not - # should use the same code block. - if node1 is None and node2 is None: - if net and net2: - # modify link between nets - netif = net.getlinknetif(net2) - upstream = False - if netif is None: - upstream = True - netif = net2.getlinknetif(net) - if netif is None: - raise ValueError, "modify unknown link between nets" - if upstream: - netif.swapparams('_params_up') - net.linkconfig(netif, bw = bw, delay = delay, - loss = loss, duplicate = duplicate, - jitter = jitter, devname = netif.name) - netif.swapparams('_params_up') - else: - net.linkconfig(netif, bw = bw, delay = delay, - loss = loss, duplicate = duplicate, - jitter = jitter) - if not unidirectional: - if upstream: - net2.linkconfig(netif, bw = bw, delay = delay, - loss = loss, - duplicate = duplicate, - jitter = jitter) - else: - netif.swapparams('_params_up') - net2.linkconfig(netif, bw = bw, delay = delay, - loss = loss, - duplicate = duplicate, - jitter = jitter, - devname = netif.name) - netif.swapparams('_params_up') - else: - raise ValueError, "modify link for unknown nodes" - elif node1 is None: - # node1 = layer 2node, node2 = layer3 node - net.linkconfig(node2.netif(ifindex2, net), bw = bw, - delay = delay, loss = loss, - duplicate = duplicate, jitter = jitter) - elif node2 is None: - # node2 = layer 2node, node1 = layer3 node - net.linkconfig(node1.netif(ifindex1, net), bw = bw, - delay = delay, loss = loss, - duplicate = duplicate, jitter = jitter) - else: - nets = node1.commonnets(node2) - for (net, netif1, netif2) in nets: - if ifindex1 is not None and \ - ifindex1 != node1.getifindex(netif1): - continue - net.linkconfig(netif1, bw = bw, delay = delay, - loss = loss, duplicate = duplicate, - jitter = jitter, netif2 = netif2) - if not unidirectional: - net.linkconfig(netif2, bw = bw, delay = delay, - loss = loss, duplicate = duplicate, - jitter = jitter, netif2 = netif1) - numnet += 1 - if numnet == 0: - raise ValueError, "no common network found" - - - finally: - if node1: - node1.lock.release() - if node2: - node2.lock.release() - return () - - def handleexecmsg(self, msg): - ''' Execute Message handler - ''' - nodenum = msg.gettlv(coreapi.CORE_TLV_EXEC_NODE) - execnum = msg.gettlv(coreapi.CORE_TLV_EXEC_NUM) - exectime = msg.gettlv(coreapi.CORE_TLV_EXEC_TIME) - cmd = msg.gettlv(coreapi.CORE_TLV_EXEC_CMD) - - # local flag indicates command executed locally, not on a node - if nodenum is None and not msg.flags & coreapi.CORE_API_LOC_FLAG: - raise ValueError, "Execute Message is missing node number." - if execnum is None: - raise ValueError, "Execute Message is missing execution number." - if exectime is not None: - self.session.addevent(exectime, node=nodenum, name=None, data=cmd) - return () - - try: - n = self.session.obj(nodenum) - except KeyError: - # XXX wait and queue this message to try again later - # XXX maybe this should be done differently - if not msg.flags & coreapi.CORE_API_LOC_FLAG: - time.sleep(0.125) - self.queuemsg(msg) - return () - else: - pass - # build common TLV items for reply - tlvdata = "" - if nodenum is not None: - tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_NODE, - nodenum) - tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_NUM, execnum) - tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_CMD, cmd) - - if msg.flags & coreapi.CORE_API_TTY_FLAG: - if nodenum is None: - raise NotImplementedError - # echo back exec message with cmd for spawning interactive terminal - if cmd == "bash": - cmd = "/bin/bash" - res = n.termcmdstring(cmd) - tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_RESULT, - res) - reply = coreapi.CoreExecMessage.pack(coreapi.CORE_API_TTY_FLAG, - tlvdata) - return (reply, ) - else: - if self.verbose: - self.info("execute message with cmd = '%s'" % cmd) - # execute command and send a response - if msg.flags & coreapi.CORE_API_STR_FLAG or \ - msg.flags & coreapi.CORE_API_TXT_FLAG: - # shlex.split() handles quotes within the string - if msg.flags & coreapi.CORE_API_LOC_FLAG: - status, res = cmdresult(shlex.split(cmd)) - else: - status, res = n.cmdresult(shlex.split(cmd)) - if self.verbose: - self.info("done exec cmd='%s' with status=%d res=(%d bytes)" - % (cmd, status, len(res))) - if msg.flags & coreapi.CORE_API_TXT_FLAG: - tlvdata += coreapi.CoreExecTlv.pack( \ - coreapi.CORE_TLV_EXEC_RESULT, res) - if msg.flags & coreapi.CORE_API_STR_FLAG: - tlvdata += coreapi.CoreExecTlv.pack( \ - coreapi.CORE_TLV_EXEC_STATUS, status) - reply = coreapi.CoreExecMessage.pack(0, tlvdata) - return (reply, ) - # execute the command with no response - else: - if msg.flags & coreapi.CORE_API_LOC_FLAG: - mutedetach(shlex.split(cmd)) - else: - n.cmd(shlex.split(cmd), wait=False) - return () - - - def handleregmsg(self, msg): - ''' Register Message Handler - ''' - replies = [] - # execute a Python script or XML file - ex = msg.gettlv(coreapi.CORE_TLV_REG_EXECSRV) - if ex: - try: - self.info("executing '%s'" % ex) - if isinstance(self.server, CoreUdpServer): - server = self.server.tcpserver - else: - server = self.server - if msg.flags & coreapi.CORE_API_STR_FLAG: - old_session_ids = set(server.getsessionids()) - sys.argv = shlex.split(ex) - filename = sys.argv[0] - if os.path.splitext(filename)[1].lower() == '.xml': - session = server.getsession(useexisting=False) - try: - opensessionxml(session, filename, start=True) - except: - session.shutdown() - server.delsession(session) - raise - else: - t = threading.Thread(target = execfile, - args=(filename, {'__file__': filename, - 'server': server})) - t.daemon = True - t.start() - time.sleep(0.25) # allow time for session creation - if msg.flags & coreapi.CORE_API_STR_FLAG: - new_session_ids = set(server.getsessionids()) - new_sid = new_session_ids.difference(old_session_ids) - try: - sid = new_sid.pop() - self.info("executed '%s' as session %d" % (ex, sid)) - except KeyError: - self.info("executed '%s' with unknown session ID" % ex) - return replies - self.info("checking session %d for RUNTIME state" % sid) - session = self.server.getsession(sessionid=sid, useexisting=True) - retries = 10 - # wait for session to enter RUNTIME state, to prevent GUI from - # connecting while nodes are still being instantiated - while session.getstate() != coreapi.CORE_EVENT_RUNTIME_STATE: - self.info("waiting for session %d to enter RUNTIME state" % sid) - time.sleep(1) - retries -= 1 - if retries <= 0: - self.info("session %d did not enter RUNTIME state" % sid) - return replies - tlvdata = coreapi.CoreRegTlv.pack( \ - coreapi.CORE_TLV_REG_EXECSRV, ex) - tlvdata += coreapi.CoreRegTlv.pack( \ - coreapi.CORE_TLV_REG_SESSION, "%s" % sid) - msg = coreapi.CoreRegMessage.pack(0, tlvdata) - replies.append(msg) - except Exception, e: - self.warn("error executing '%s': %s" % \ - (ex, traceback.format_exc())) - tlvdata = coreapi.CoreExceptionTlv.pack( \ - coreapi.CORE_TLV_EXCP_LEVEL, 2) - tlvdata += coreapi.CoreExceptionTlv.pack( \ - coreapi.CORE_TLV_EXCP_TEXT, str(e)) - msg = coreapi.CoreExceptionMessage.pack(0, tlvdata) - replies.append(msg) - return replies - - gui = msg.gettlv(coreapi.CORE_TLV_REG_GUI) - if gui is None: - self.info("ignoring Register message") - else: - # register capabilities with the GUI - self.master = True - found = self.server.setsessionmaster(self) - replies.append(self.register()) - replies.append(self.server.tosessionmsg()) - return replies - - def handleconfmsg(self, msg): - ''' Configuration Message handler - ''' - nodenum = msg.gettlv(coreapi.CORE_TLV_CONF_NODE) - objname = msg.gettlv(coreapi.CORE_TLV_CONF_OBJ) - if self.verbose: - self.info("Configuration message for %s node %s" % \ - (objname, nodenum)) - # dispatch to any registered callback for this object type - replies = self.session.confobj(objname, self.session, msg) - # config requests usually have a reply with default data - return replies - - def handlefilemsg(self, msg): - ''' File Message handler - ''' - if msg.flags & coreapi.CORE_API_ADD_FLAG: - nodenum = msg.gettlv(coreapi.CORE_TLV_NODE_NUMBER) - filename = msg.gettlv(coreapi.CORE_TLV_FILE_NAME) - type = msg.gettlv(coreapi.CORE_TLV_FILE_TYPE) - srcname = msg.gettlv(coreapi.CORE_TLV_FILE_SRCNAME) - data = msg.gettlv(coreapi.CORE_TLV_FILE_DATA) - cmpdata = msg.gettlv(coreapi.CORE_TLV_FILE_CMPDATA) - - if cmpdata is not None: - self.warn("Compressed file data not implemented for File " \ - "message.") - return () - if srcname is not None and data is not None: - self.warn("ignoring invalid File message: source and data " \ - "TLVs are both present") - return () - - # some File Messages store custom files in services, - # prior to node creation - if type is not None: - if type[:8] == "service:": - self.session.services.setservicefile(nodenum, type, - filename, srcname, data) - return () - elif type[:5] == "hook:": - self.session.sethook(type, filename, srcname, data) - return () - # writing a file to the host - if nodenum is None: - if srcname is not None: - shutil.copy2(srcname, filename) - else: - with open(filename, "w") as f: - f.write(data) - return () - try: - n = self.session.obj(nodenum) - except KeyError: - # XXX wait and queue this message to try again later - # XXX maybe this should be done differently - self.warn("File message for %s for node number %s queued." % \ - (filename, nodenum)) - time.sleep(0.125) - self.queuemsg(msg) - return () - if srcname is not None: - n.addfile(srcname, filename) - elif data is not None: - n.nodefile(filename, data) - else: - raise NotImplementedError - return () - - def handleifacemsg(self, msg): - ''' Interface Message handler - ''' - self.info("ignoring Interface message") - return () - - def handleeventmsg(self, msg): - ''' Event Message handler - ''' - eventtype = msg.gettlv(coreapi.CORE_TLV_EVENT_TYPE) - if eventtype is None: - raise NotImplementedError, "Event message missing event type" - node = msg.gettlv(coreapi.CORE_TLV_EVENT_NODE) - - if self.verbose: - self.info("EVENT %d: %s at %s" % \ - (eventtype, coreapi.event_types[eventtype], time.ctime())) - if eventtype <= coreapi.CORE_EVENT_SHUTDOWN_STATE: - if node is not None: - try: - n = self.session.obj(node) - except KeyError: - raise KeyError, "Event message for unknown node %d" % node - if eventtype == coreapi.CORE_EVENT_INSTANTIATION_STATE: - # configure mobility models for WLAN added during runtime - if isinstance(n, pycore.nodes.WlanNode): - return (self.session.mobility.startup(nodenums=(n.objid,))) - self.warn("dropping unhandled Event message with node number") - return () - self.session.setstate(state=eventtype, info=True, sendevent=False) - - if eventtype == coreapi.CORE_EVENT_DEFINITION_STATE: - # clear all session objects in order to receive new definitions - self.session.delobjs() - self.session.delhooks() - self.session.broker.reset() - elif eventtype == coreapi.CORE_EVENT_CONFIGURATION_STATE: - pass - elif eventtype == coreapi.CORE_EVENT_INSTANTIATION_STATE: - if len(self.handlerthreads) > 1: - # TODO: sync handler threads here before continuing - time.sleep(2.0) # XXX - # done receiving node/link configuration, ready to instantiate - self.session.instantiate(handler=self) - elif eventtype == coreapi.CORE_EVENT_RUNTIME_STATE: - if self.session.master: - self.warn("Unexpected event message: RUNTIME state received " \ - "at session master") - else: - # master event queue is started in session.checkruntime() - self.session.evq.run() - elif eventtype == coreapi.CORE_EVENT_DATACOLLECT_STATE: - self.session.datacollect() - elif eventtype == coreapi.CORE_EVENT_SHUTDOWN_STATE: - if self.session.master: - self.warn("Unexpected event message: SHUTDOWN state received " \ - "at session master") - elif eventtype in (coreapi.CORE_EVENT_START, coreapi.CORE_EVENT_STOP, \ - coreapi.CORE_EVENT_RESTART, \ - coreapi.CORE_EVENT_PAUSE, \ - coreapi.CORE_EVENT_RECONFIGURE): - handled = False - name = msg.gettlv(coreapi.CORE_TLV_EVENT_NAME) - if name: - # TODO: register system for event message handlers, - # like confobjs - if name.startswith("service:"): - self.session.services.handleevent(msg) - handled = True - elif name.startswith("mobility:"): - self.session.mobility.handleevent(msg) - handled = True - else: - pass - if not handled: - self.warn("Unhandled event message: event type %s (%s)" % \ - (eventtype, coreapi.state_name(eventtype))) - elif eventtype == coreapi.CORE_EVENT_FILE_OPEN: - self.session.delobjs() - self.session.delhooks() - self.session.broker.reset() - filename = msg.tlvdata[coreapi.CORE_TLV_EVENT_NAME] - opensessionxml(self.session, filename) - return self.session.sendobjs() - elif eventtype == coreapi.CORE_EVENT_FILE_SAVE: - filename = msg.tlvdata[coreapi.CORE_TLV_EVENT_NAME] - savesessionxml(self.session, filename, self.session.cfg['xmlfilever']) - elif eventtype == coreapi.CORE_EVENT_SCHEDULED: - etime = msg.gettlv(coreapi.CORE_TLV_EVENT_TIME) - node = msg.gettlv(coreapi.CORE_TLV_EVENT_NODE) - name = msg.gettlv(coreapi.CORE_TLV_EVENT_NAME) - data = msg.gettlv(coreapi.CORE_TLV_EVENT_DATA) - if etime is None: - self.warn("Event message scheduled event missing start time") - return () - if msg.flags & coreapi.CORE_API_ADD_FLAG: - self.session.addevent(float(etime), node=node, name=name, - data=data) - else: - raise NotImplementedError - else: - self.warn("Unhandled event message: event type %d" % eventtype) - return () - - def handlesessionmsg(self, msg): - ''' Session Message handler - ''' - replies = [] - sid_str = msg.gettlv(coreapi.CORE_TLV_SESS_NUMBER) - name_str = msg.gettlv(coreapi.CORE_TLV_SESS_NAME) - file_str = msg.gettlv(coreapi.CORE_TLV_SESS_FILE) - nc_str = msg.gettlv(coreapi.CORE_TLV_SESS_NODECOUNT) - thumb = msg.gettlv(coreapi.CORE_TLV_SESS_THUMB) - user = msg.gettlv(coreapi.CORE_TLV_SESS_USER) - sids = coreapi.str_to_list(sid_str) - names = coreapi.str_to_list(name_str) - files = coreapi.str_to_list(file_str) - ncs = coreapi.str_to_list(nc_str) - self.info("SESSION message flags=0x%x sessions=%s" % (msg.flags, sid_str)) - - if msg.flags == 0: - # modify a session - i = 0 - for sid in sids: - sid = int(sid) - if sid == 0: - session = self.session - else: - session = self.server.getsession(sessionid = sid, - useexisting = True) - if session is None: - self.info("session %s not found" % sid) - i += 1 - continue - self.info("request to modify to session %s" % session.sessionid) - if names is not None: - session.name = names[i] - if files is not None: - session.filename = files[i] - if ncs is not None: - session.node_count = ncs[i] - if thumb is not None: - session.setthumbnail(thumb) - if user is not None: - session.setuser(user) - i += 1 - else: - if msg.flags & coreapi.CORE_API_STR_FLAG and not \ - msg.flags & coreapi.CORE_API_ADD_FLAG: - # status request flag: send list of sessions - return (self.server.tosessionmsg(), ) - # handle ADD or DEL flags - for sid in sids: - sid = int(sid) - session = self.server.getsession(sessionid = sid, - useexisting = True) - if session is None: - self.info("session %s not found (flags=0x%x)" % \ - (sid, msg.flags)) - continue - if session.server is None: - # this needs to be set when executing a Python script - session.server = self.server - if msg.flags & coreapi.CORE_API_ADD_FLAG: - # connect to the first session that exists - self.info("request to connect to session %s" % sid) - # this may shutdown the session if no handlers exist - self.session.disconnect(self) - self.session = session - self.session.connect(self) - if user is not None: - self.session.setuser(user) - if msg.flags & coreapi.CORE_API_STR_FLAG: - replies.extend(self.session.sendobjs()) - elif msg.flags & coreapi.CORE_API_DEL_FLAG: - # shut down the specified session(s) - self.info("request to terminate session %s" % sid) - session.setstate(state=coreapi.CORE_EVENT_DATACOLLECT_STATE, - info=True, sendevent=True) - session.setstate(state=coreapi.CORE_EVENT_SHUTDOWN_STATE, - info=True, sendevent=True) - session.shutdown() - else: - self.warn("unhandled session flags for session %s" % sid) - return replies - -class CoreDatagramRequestHandler(CoreRequestHandler): - ''' A child of the CoreRequestHandler class for handling connectionless - UDP messages. No new session is created; messages are handled immediately or - sometimes queued on existing session handlers. +# +# Auxiliary server startup +# +def startaux(mainserver, aux_address, aux_handler): + ''' Start a thread running an auxiliary TCP server on the given address. + This server will communicate with client requests using a handler + using the aux_handler class. The aux_handler can provide an alternative + API to CORE. ''' - - def __init__(self, request, client_address, server): - # TODO: decide which messages cannot be handled with connectionless UDP - self.msghandler = { - coreapi.CORE_API_NODE_MSG: self.handlenodemsg, - coreapi.CORE_API_LINK_MSG: self.handlelinkmsg, - coreapi.CORE_API_EXEC_MSG: self.handleexecmsg, - coreapi.CORE_API_REG_MSG: self.handleregmsg, - coreapi.CORE_API_CONF_MSG: self.handleconfmsg, - coreapi.CORE_API_FILE_MSG: self.handlefilemsg, - coreapi.CORE_API_IFACE_MSG: self.handleifacemsg, - coreapi.CORE_API_EVENT_MSG: self.handleeventmsg, - coreapi.CORE_API_SESS_MSG: self.handlesessionmsg, - } - self.nodestatusreq = {} - self.master = False - self.session = None - self.verbose = bool(server.tcpserver.cfg['verbose'].lower() == "true") - self.debug = bool(server.tcpserver.cfg['debug'].lower() == "true") - SocketServer.BaseRequestHandler.__init__(self, request, - client_address, server) - - def setup(self): - ''' Client has connected, set up a new connection. - ''' - if self.verbose: - self.info("new UDP connection: %s:%s" % self.client_address) - - def handle(self): - msg = self.recvmsg() - - def finish(self): - return SocketServer.BaseRequestHandler.finish(self) - - def recvmsg(self): - ''' Receive data, parse a CoreMessage and queue it onto an existing - session handler's queue, if available. - ''' - data = self.request[0] - socket = self.request[1] - msghdr = data[:coreapi.CoreMessage.hdrsiz] - if len(msghdr) < coreapi.CoreMessage.hdrsiz: - raise IOError, "error receiving header (received %d bytes)" % \ - len(msghdr) - msgtype, msgflags, msglen = coreapi.CoreMessage.unpackhdr(msghdr) - if msglen == 0: - self.warn("received message with no data") - return - if len(data) != coreapi.CoreMessage.hdrsiz + msglen: - self.warn("received message length does not match received data " \ - "(%s != %s)" % \ - (len(data), coreapi.CoreMessage.hdrsiz + msglen)) - raise IOError - elif self.verbose: - self.info("UDP socket received message type=%d len=%d" % \ - (msgtype, msglen)) - try: - msgcls = coreapi.msg_class(msgtype) - msg = msgcls(msgflags, msghdr, data[coreapi.CoreMessage.hdrsiz:]) - except KeyError: - msg = coreapi.CoreMessage(msgflags, msghdr, - data[coreapi.CoreMessage.hdrsiz:]) - msg.msgtype = msgtype - self.warn("unimplemented core message type: %s" % msg.typestr()) - return - sids = msg.sessionnumbers() - msg.queuedtimes = 0 - #self.info("UDP message has session numbers: %s" % sids) - if len(sids) > 0: - for sid in sids: - sess = self.server.tcpserver.getsession(sessionid=sid, - useexisting=True) - if sess: - self.session = sess - sess.broadcast(self, msg) - self.handlemsg(msg) - else: - self.warn("Session %d in %s message not found." % \ - (sid, msg.typestr())) - else: - # no session specified, find an existing one - sess = self.server.tcpserver.getsession(sessionid=0, - useexisting=True) - if sess or msg.msgtype == coreapi.CORE_API_REG_MSG: - self.session = sess - if sess: - sess.broadcast(self, msg) - self.handlemsg(msg) - else: - self.warn("No active session, dropping %s message." % \ - msg.typestr()) - - def queuemsg(self, msg): - ''' UDP handlers are short-lived and do not have message queues. - ''' - raise Exception, "Unable to queue %s message for later processing " \ - "using UDP!" % msg.typestr() - - def sendall(self, data): - ''' Use sendto() on the connectionless UDP socket. - ''' - self.request[1].sendto(data, self.client_address) - - - -class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer): - ''' TCP server class, manages sessions and spawns request handlers for - incoming connections. - ''' - daemon_threads = True - allow_reuse_address = True - servers = set() - - def __init__(self, server_address, RequestHandlerClass, cfg = None): - ''' Server class initialization takes configuration data and calls - the SocketServer constructor - ''' - self.cfg = cfg - self._sessions = {} - self._sessionslock = threading.Lock() - self.newserver(self) - SocketServer.TCPServer.__init__(self, server_address, - RequestHandlerClass) - - @classmethod - def newserver(cls, server): - cls.servers.add(server) - - @classmethod - def delserver(cls, server): - try: - cls.servers.remove(server) - except KeyError: - pass - - def shutdown(self): - for session in self._sessions.values(): - session.shutdown() - if self.cfg['daemonize']: - pidfilename = self.cfg['pidfile'] - try: - os.unlink(pidfilename) - except OSError: - pass - self.delserver(self) - - def addsession(self, session): - ''' Add a session to our dictionary of sessions, ensuring a unique - session number - ''' - self._sessionslock.acquire() - try: - if session.sessionid in self._sessions: - raise KeyError, "non-unique session id %s for %s" % \ - (session.sessionid, session) - self._sessions[session.sessionid] = session - finally: - self._sessionslock.release() - return session - - def delsession(self, session): - ''' Remove a session from our dictionary of sessions. - ''' - with self._sessionslock: - if session.sessionid not in self._sessions: - print "session id %s not found (sessions=%s)" % \ - (session.sessionid, self._sessions.keys()) - else: - del(self._sessions[session.sessionid]) - return session - - def getsessionids(self): - ''' Return a list of active session numbers. - ''' - with self._sessionslock: - sids = self._sessions.keys() - return sids - - def getsession(self, sessionid = None, useexisting = True): - ''' Create a new session or retrieve an existing one from our - dictionary of sessions. When the sessionid=0 and the useexisting - flag is set, return on of the existing sessions. - ''' - if not useexisting: - session = pycore.Session(sessionid, cfg = self.cfg, server = self) - self.addsession(session) - return session - - with self._sessionslock: - # look for the specified session id - if sessionid in self._sessions: - session = self._sessions[sessionid] - else: - session = None - # pick an existing session - if sessionid == 0: - for s in self._sessions.itervalues(): - if s.getstate() == coreapi.CORE_EVENT_RUNTIME_STATE: - if session is None: - session = s - elif s.node_count > session.node_count: - session = s - if session is None: - for s in self._sessions.itervalues(): - session = s - break - return session - - def tosessionmsg(self, flags = 0): - ''' Build CORE API Sessions message based on current session info. - ''' - idlist = [] - namelist = [] - filelist = [] - nclist = [] - datelist = [] - thumblist = [] - num_sessions = 0 - - with self._sessionslock: - for sessionid in self._sessions: - session = self._sessions[sessionid] - # debug: session.dumpsession() - num_sessions += 1 - idlist.append(str(sessionid)) - name = session.name - if name is None: - name = "" - namelist.append(name) - file = session.filename - if file is None: - file = "" - filelist.append(file) - nc = session.node_count - if nc is None: - nc = "" - nclist.append(str(nc)) - datelist.append(time.ctime(session._time)) - thumb = session.thumbnail - if thumb is None: - thumb = "" - thumblist.append(thumb) - sids = "|".join(idlist) - names = "|".join(namelist) - files = "|".join(filelist) - ncs = "|".join(nclist) - dates = "|".join(datelist) - thumbs = "|".join(thumblist) - - if num_sessions > 0: - tlvdata = "" - if len(sids) > 0: - tlvdata += coreapi.CoreSessionTlv.pack( \ - coreapi.CORE_TLV_SESS_NUMBER, sids) - if len(names) > 0: - tlvdata += coreapi.CoreSessionTlv.pack( \ - coreapi.CORE_TLV_SESS_NAME, names) - if len(files) > 0: - tlvdata += coreapi.CoreSessionTlv.pack( \ - coreapi.CORE_TLV_SESS_FILE, files) - if len(ncs) > 0: - tlvdata += coreapi.CoreSessionTlv.pack( \ - coreapi.CORE_TLV_SESS_NODECOUNT, ncs) - if len(dates) > 0: - tlvdata += coreapi.CoreSessionTlv.pack( \ - coreapi.CORE_TLV_SESS_DATE, dates) - if len(thumbs) > 0: - tlvdata += coreapi.CoreSessionTlv.pack( \ - coreapi.CORE_TLV_SESS_THUMB, thumbs) - msg = coreapi.CoreSessionMessage.pack(flags, tlvdata) - else: - msg = None - return(msg) - - def dumpsessions(self): - ''' Debug print all session info. - ''' - print "sessions:" - self._sessionslock.acquire() - try: - for sessionid in self._sessions: - print sessionid, - finally: - self._sessionslock.release() - print "" - sys.stdout.flush() - - def setsessionmaster(self, handler): - ''' Call the setmaster() method for every session. Returns True when - a session having the given handler was updated. - ''' - found = False - self._sessionslock.acquire() - try: - for sessionid in self._sessions: - found = self._sessions[sessionid].setmaster(handler) - if found is True: - break - finally: - self._sessionslock.release() - return found - - def startudp(self, server_address): - ''' Start a thread running a UDP server on the same host,port for - connectionless requests. - ''' - self.udpserver = CoreUdpServer(server_address, - CoreDatagramRequestHandler, self) - self.udpthread = threading.Thread(target = self.udpserver.start) - self.udpthread.daemon = True - self.udpthread.start() - -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, RequestHandlerClass, tcpserver): - ''' Server class initialization takes configuration data and calls - the SocketServer constructor - ''' - self.tcpserver = tcpserver - SocketServer.UDPServer.__init__(self, server_address, - RequestHandlerClass) - - def start(self): - ''' Thread target to run concurrently with the TCP server. - ''' - self.serve_forever() - + handlermodname,dot,handlerclassname = aux_handler.rpartition('.') + handlermod = importlib.import_module(handlermodname) + handlerclass = getattr(handlermod, handlerclassname) + mainserver.auxserver = CoreAuxServer(aux_address, + handlerclass, + mainserver) + mainserver.auxthread = threading.Thread(target = mainserver.auxserver.start) + mainserver.auxthread.daemon = True + mainserver.auxthread.start() + return mainserver.auxserver def banner(): @@ -1607,15 +121,26 @@ def cored(cfg = None): try: server = CoreServer((host, port), CoreRequestHandler, cfg) except Exception, e: - sys.stderr.write("error starting server on: %s:%s\n\t%s\n" % \ + sys.stderr.write("error starting main server on: %s:%s\n\t%s\n" % \ (host, port, e)) sys.stderr.flush() sys.exit(1) closeonexec(server.fileno()) - sys.stdout.write("server started, listening on: %s:%s\n" % (host, port)) + sys.stdout.write("main server started, listening on: %s:%s\n" % (host, port)) sys.stdout.flush() - server.startudp((host,port)) - closeonexec(server.udpserver.fileno()) + + udpserver = startudp(server, (host,port)) + closeonexec(udpserver.fileno()) + + auxreqhandler = cfg['aux_request_handler'] + if auxreqhandler: + try: + handler, auxport = auxreqhandler.rsplit(':') + auxserver = startaux(server, (host,int(auxport)), handler) + closeonexec(auxserver.fileno()) + except Exception as e: + raise ValueError, "invalid auxreqhandler:(%s)\nError: %s" % (auxreqhandler, e) + server.serve_forever() def cleanup(): @@ -1676,7 +201,8 @@ def getMergedConfig(filename): 'daemonize' : 'False', 'debug' : 'False', 'execfile' : None, - } + 'aux_request_handler' : None, + } usagestr = "usage: %prog [-h] [options] [args]\n\n" + \ "CORE daemon v.%s instantiates Linux network namespace " \ From e58de230d20121231cdfb370cb839efbe37037ab Mon Sep 17 00:00:00 2001 From: Rod A Santiago Date: Wed, 14 Sep 2016 18:19:22 -0700 Subject: [PATCH 17/28] packaging: Update RPM spec to include core/coreserver.py --- packaging/rpm/core.spec.in | 1 + 1 file changed, 1 insertion(+) diff --git a/packaging/rpm/core.spec.in b/packaging/rpm/core.spec.in index 7a586ba3..1a2cc9e3 100644 --- a/packaging/rpm/core.spec.in +++ b/packaging/rpm/core.spec.in @@ -377,6 +377,7 @@ fi %{python_sitelib}/core_python-@COREDPY_VERSION@-py%{python_version}.egg-info %{python_sitelib}/core/sdt.py* %{python_sitelib}/core/service.py* +%{python_sitelib}/core/coreserver.py* %dir %{python_sitelib}/core/services %{python_sitelib}/core/services/bird.py* %{python_sitelib}/core/services/__init__.py* From e1aec0ea1d9f41fa1531d115fe013de855df94aa Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Fri, 16 Sep 2016 10:44:48 -0400 Subject: [PATCH 18/28] daemon/examples: Update the distributed netns example. The number of nodes used can depend on a command-line argument, but node one should always exist. --- daemon/examples/netns/distributed.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/examples/netns/distributed.py b/daemon/examples/netns/distributed.py index d5eee2b8..5badf053 100755 --- a/daemon/examples/netns/distributed.py +++ b/daemon/examples/netns/distributed.py @@ -123,7 +123,7 @@ def main(): session.broker.handlerawmsg(msg) # start a shell on node 1 - n[7].term("bash") + n[1].term("bash") # TODO: access to remote nodes is currently limited in this script From bcd749578dee16f5b94d9f39cbad0b750b5d492d Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Thu, 29 Sep 2016 17:26:50 -0400 Subject: [PATCH 19/28] gui: Avoid added an extra newline to hook scripts when saving. --- gui/cfgparse.tcl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/gui/cfgparse.tcl b/gui/cfgparse.tcl index b8b27032..ce84e81e 100644 --- a/gui/cfgparse.tcl +++ b/gui/cfgparse.tcl @@ -198,9 +198,12 @@ proc dumpCfg {method dest} { set state [lindex $hook 1] set script [lindex $hook 2] dumpputs $method $dest "hook $state:$name \{" - foreach line [split $script "\n"] { - dumpputs $method $dest "$line" + # remove the final newline here because dumpputs adds a + # newline automatically + if {[string index $script end] == "\n"} { + set script [string replace $script end end] } + dumpputs $method $dest $script dumpputs $method $dest "\}" dumpputs $method $dest "" } From 31a9f6fb347e0fbf26a1ace29f6661562834950c Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 15:55:30 +0000 Subject: [PATCH 20/28] daemon: Use a set to store broker handlers. --- daemon/core/broker.py | 2 +- daemon/core/emane/emane.py | 2 +- daemon/core/mobility.py | 2 +- daemon/core/sdt.py | 2 +- daemon/core/session.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/daemon/core/broker.py b/daemon/core/broker.py index 7aa2e457..8b949241 100644 --- a/daemon/core/broker.py +++ b/daemon/core/broker.py @@ -54,7 +54,7 @@ class CoreBroker(ConfigurableManager): # list of node numbers that are PhysicalNode nodes self.phys = [] # allows for other message handlers to process API messages (e.g. EMANE) - self.handlers = () + self.handlers = set() # dict with tunnel key to tunnel device mapping self.tunnels = {} self.dorecvloop = False diff --git a/daemon/core/emane/emane.py b/daemon/core/emane/emane.py index 45086a15..fcc299aa 100644 --- a/daemon/core/emane/emane.py +++ b/daemon/core/emane/emane.py @@ -67,7 +67,7 @@ class Emane(ConfigurableManager): self.logversion() # model for global EMANE configuration options self.emane_config = EmaneGlobalModel(session, None, self.verbose) - session.broker.handlers += (self.handledistributed, ) + session.broker.handlers.add(self.handledistributed) self.loadmodels() self.service = None diff --git a/daemon/core/mobility.py b/daemon/core/mobility.py index 2dfb5e8e..3da4dab0 100644 --- a/daemon/core/mobility.py +++ b/daemon/core/mobility.py @@ -33,7 +33,7 @@ class MobilityManager(ConfigurableManager): # dummy node objects for tracking position of nodes on other servers self.phys = {} self.physnets = {} - self.session.broker.handlers += (self.physnodehandlelink, ) + self.session.broker.handlers.add(self.physnodehandlelink) self.register() def startup(self, nodenums=None): diff --git a/daemon/core/sdt.py b/daemon/core/sdt.py index 7f8008d8..2eeb0db2 100644 --- a/daemon/core/sdt.py +++ b/daemon/core/sdt.py @@ -50,7 +50,7 @@ class Sdt(object): # node information for remote nodes not in session._objs # local nodes also appear here since their obj may not exist yet self.remotes = {} - session.broker.handlers += (self.handledistributed, ) + session.broker.handlers.add(self.handledistributed) def is_enabled(self): ''' Check for 'enablesdt' session option. Return False by default if diff --git a/daemon/core/session.py b/daemon/core/session.py index 85fb49d3..8d99f8e7 100644 --- a/daemon/core/session.py +++ b/daemon/core/session.py @@ -1132,7 +1132,7 @@ class SessionConfig(ConfigurableManager, Configurable): def __init__(self, session): ConfigurableManager.__init__(self, session) - session.broker.handlers += (self.handledistributed, ) + session.broker.handlers.add(self.handledistributed) self.reset() def reset(self): From 4662164ecee204261ff766feceaf2e465696889c Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 17:35:18 +0000 Subject: [PATCH 21/28] daemon: Use a set to store broker networks. --- daemon/core/broker.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/daemon/core/broker.py b/daemon/core/broker.py index 8b949241..723561dc 100644 --- a/daemon/core/broker.py +++ b/daemon/core/broker.py @@ -49,8 +49,8 @@ class CoreBroker(ConfigurableManager): # reference counts of nodes on servers self.nodecounts = { } self.bootcount = 0 - # list of node numbers that are link-layer nodes (networks) - self.nets = [] + # set of node numbers that are link-layer nodes (networks) + self.nets = set() # list of node numbers that are PhysicalNode nodes self.phys = [] # allows for other message handlers to process API messages (e.g. EMANE) @@ -98,7 +98,7 @@ class CoreBroker(ConfigurableManager): self.nodecounts.clear() self.bootcount = 0 self.nodemap_lock.release() - del self.nets[:] + self.nets.clear() del self.phys[:] while len(self.tunnels) > 0: (key, gt) = self.tunnels.popitem() @@ -463,8 +463,7 @@ class CoreBroker(ConfigurableManager): def addnet(self, nodenum): ''' Add a node number to the list of link-layer nodes. ''' - if nodenum not in self.nets: - self.nets.append(nodenum) + self.nets.add(nodenum) def addphys(self, nodenum): ''' Add a node number to the list of physical nodes. From 649460529851057b257d2871f3eeb1dd4cc9feb1 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 17:36:16 +0000 Subject: [PATCH 22/28] daemon: Use a set to store broker physical nodes. --- daemon/core/broker.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/daemon/core/broker.py b/daemon/core/broker.py index 723561dc..688e5a54 100644 --- a/daemon/core/broker.py +++ b/daemon/core/broker.py @@ -51,8 +51,8 @@ class CoreBroker(ConfigurableManager): self.bootcount = 0 # set of node numbers that are link-layer nodes (networks) self.nets = set() - # list of node numbers that are PhysicalNode nodes - self.phys = [] + # set of node numbers that are PhysicalNode nodes + self.phys = set() # allows for other message handlers to process API messages (e.g. EMANE) self.handlers = set() # dict with tunnel key to tunnel device mapping @@ -99,7 +99,7 @@ class CoreBroker(ConfigurableManager): self.bootcount = 0 self.nodemap_lock.release() self.nets.clear() - del self.phys[:] + self.phys.clear() while len(self.tunnels) > 0: (key, gt) = self.tunnels.popitem() gt.shutdown() @@ -468,8 +468,7 @@ class CoreBroker(ConfigurableManager): def addphys(self, nodenum): ''' Add a node number to the list of physical nodes. ''' - if nodenum not in self.phys: - self.phys.append(nodenum) + self.phys.add(nodenum) def configure_reset(self, msg): ''' Ignore reset messages, because node delete responses may still From 8479ccd2c563d47386e6cf73c3ffa6bae84d6cfb Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 17:36:30 +0000 Subject: [PATCH 23/28] daemon: Use a set to store the broker node map. --- daemon/core/broker.py | 75 +++++++++++++++++++---------------------- daemon/core/mobility.py | 5 +-- 2 files changed, 37 insertions(+), 43 deletions(-) diff --git a/daemon/core/broker.py b/daemon/core/broker.py index 688e5a54..de06e49a 100644 --- a/daemon/core/broker.py +++ b/daemon/core/broker.py @@ -399,45 +399,41 @@ class CoreBroker(ConfigurableManager): def addnodemap(self, server, nodenum): ''' Record a node number to emulation server mapping. ''' - self.nodemap_lock.acquire() - if nodenum in self.nodemap: - if server in self.nodemap[nodenum]: - self.nodemap_lock.release() - return - self.nodemap[nodenum].append(server) - else: - self.nodemap[nodenum] = [server,] - if server in self.nodecounts: - self.nodecounts[server] += 1 - else: - self.nodecounts[server] = 1 - self.nodemap_lock.release() - + with self.nodemap_lock: + if nodenum in self.nodemap: + if server in self.nodemap[nodenum]: + return + self.nodemap[nodenum].add(server) + else: + self.nodemap[nodenum] = {server} + if server in self.nodecounts: + self.nodecounts[server] += 1 + else: + self.nodecounts[server] = 1 + def delnodemap(self, sock, nodenum): ''' Remove a node number to emulation server mapping. Return the number of nodes left on this server. ''' - self.nodemap_lock.acquire() count = None - if nodenum not in self.nodemap: - self.nodemap_lock.release() - return count - found = False - for server in self.nodemap[nodenum]: - (host, port, srvsock) = self.getserver(server) - if srvsock == sock: - found = True - break - if server in self.nodecounts: - count = self.nodecounts[server] - if found: - self.nodemap[nodenum].remove(server) + with self.nodemap_lock: + if nodenum not in self.nodemap: + return count + found = False + for server in self.nodemap[nodenum]: + (host, port, srvsock) = self.getserver(server) + if srvsock == sock: + found = True + break if server in self.nodecounts: - count -= 1 - self.nodecounts[server] = count - self.nodemap_lock.release() - return count - + count = self.nodecounts[server] + if found: + self.nodemap[nodenum].remove(server) + if server in self.nodecounts: + count -= 1 + self.nodecounts[server] = count + return count + def incrbootcount(self): ''' Count a node that has booted. ''' @@ -450,15 +446,12 @@ class CoreBroker(ConfigurableManager): return self.bootcount def getserversbynode(self, nodenum): - ''' Retrieve a list of emulation servers given a node number. + ''' Retrieve a set of emulation servers given a node number. ''' - self.nodemap_lock.acquire() - if nodenum not in self.nodemap: - self.nodemap_lock.release() - return [] - r = self.nodemap[nodenum] - self.nodemap_lock.release() - return r + with self.nodemap_lock: + if nodenum not in self.nodemap: + return set() + return self.nodemap[nodenum] def addnet(self, nodenum): ''' Add a node number to the list of link-layer nodes. diff --git a/daemon/core/mobility.py b/daemon/core/mobility.py index 3da4dab0..b5a8eb45 100644 --- a/daemon/core/mobility.py +++ b/daemon/core/mobility.py @@ -237,8 +237,9 @@ class MobilityManager(ConfigurableManager): return for nodenum in nodenums: node = self.phys[nodenum] - servers = self.session.broker.getserversbynode(nodenum) - (host, port, sock) = self.session.broker.getserver(servers[0]) + for server in self.session.broker.getserversbynode(nodenum): + break + (host, port, sock) = self.session.broker.getserver(server) netif = self.session.broker.gettunnel(net.objid, IPAddr.toint(host)) node.addnetif(netif, 0) netif.node = node From 42b1771dcbbb94f12637338e67a573ab2be01902 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 17:36:42 +0000 Subject: [PATCH 24/28] daemon: Create a CoreServer class to represent CORE servers. Includes related changes and refactoring. --- daemon/core/broker.py | 475 +++++++++++++++--------------- daemon/core/emane/emane.py | 16 +- daemon/core/misc/xmldeployment.py | 2 +- daemon/core/mobility.py | 4 +- daemon/core/session.py | 6 +- 5 files changed, 243 insertions(+), 260 deletions(-) diff --git a/daemon/core/broker.py b/daemon/core/broker.py index de06e49a..a927c19b 100644 --- a/daemon/core/broker.py +++ b/daemon/core/broker.py @@ -24,7 +24,29 @@ from core.conf import ConfigurableManager if os.uname()[0] == "Linux": from core.netns.vif import GreTap from core.netns.vnet import GreTapBridge - + +class CoreServer(object): + def __init__(self, name, host, port): + self.name = name + self.host = host + self.port = port + self.sock = None + + def connect(self): + assert self.sock is None + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + #sock.setblocking(0) + try: + sock.connect((self.host, self.port)) + except: + sock.close() + raise + self.sock = sock + + def close(self): + if self.sock is not None: + self.sock.close() + self.sock = None class CoreBroker(ConfigurableManager): ''' Member of pycore session class for handling global emulation server @@ -47,7 +69,7 @@ class CoreBroker(ConfigurableManager): # this lock also protects self.nodecounts self.nodemap_lock = threading.Lock() # reference counts of nodes on servers - self.nodecounts = { } + self.nodecounts = {} self.bootcount = 0 # set of node numbers that are link-layer nodes (networks) self.nets = set() @@ -67,21 +89,19 @@ class CoreBroker(ConfigurableManager): ''' self.addnettunnels() self.writeservers() - + def shutdown(self): - ''' Close all active sockets; called when the session enters the + ''' Close all active sockets; called when the session enters the data collect state ''' with self.servers_lock: while len(self.servers) > 0: - (server, v) = self.servers.popitem() - (host, port, sock) = v - if sock is None: - continue - if self.verbose: - self.session.info("closing connection with %s @ %s:%s" % \ - (server, host, port)) - sock.close() + name, server = self.servers.popitem() + if server.sock is not None: + if self.verbose: + self.session.info("closing connection with %s @ %s:%s" % \ + (name, server.host, server.port)) + server.close() self.reset() self.dorecvloop = False if self.recvthread is not None: @@ -92,8 +112,8 @@ class CoreBroker(ConfigurableManager): ''' self.nodemap_lock.acquire() self.nodemap.clear() - for server in self.nodecounts: - if self.nodecounts[server] < 1: + for server, count in self.nodecounts.iteritems(): + if count < 1: self.delserver(server) self.nodecounts.clear() self.bootcount = 0 @@ -128,35 +148,32 @@ class CoreBroker(ConfigurableManager): rlist = [] with self.servers_lock: # build a socket list for select call - for name in self.servers: - (h, p, sock) = self.servers[name] - if sock is not None: - rlist.append(sock.fileno()) + for server in self.servers.itervalues(): + if server.sock is not None: + rlist.append(server.sock) r, w, x = select.select(rlist, [], [], 1.0) - for sockfd in r: - try: - (h, p, sock, name) = self.getserverbysock(sockfd) - except KeyError: + for sock in r: + server = self.getserverbysock(sock) + if server is None: # servers may have changed; loop again - break - rcvlen = self.recv(sock, h) + continue + rcvlen = self.recv(server) if rcvlen == 0: if self.verbose: - self.session.info("connection with %s @ %s:%s" \ - " has closed" % (name, h, p)) - self.servers[name] = (h, p, None) + msg = 'connection with %s @ %s:%s has closed' % \ + (server.name, server.host, server.port) + self.session.info(msg) - - def recv(self, sock, host): + def recv(self, server): ''' Receive data on an emulation server socket and broadcast it to all connected session handlers. Returns the length of data recevied and forwarded. Return value of zero indicates the socket has closed and should be removed from the self.servers dict. ''' - msghdr = sock.recv(coreapi.CoreMessage.hdrsiz) + msghdr = server.sock.recv(coreapi.CoreMessage.hdrsiz) if len(msghdr) == 0: # server disconnected - sock.close() + server.close() return 0 if len(msghdr) != coreapi.CoreMessage.hdrsiz: if self.verbose: @@ -165,20 +182,20 @@ class CoreBroker(ConfigurableManager): return len(msghdr) msgtype, msgflags, msglen = coreapi.CoreMessage.unpackhdr(msghdr) - msgdata = sock.recv(msglen) + msgdata = server.sock.recv(msglen) data = msghdr + msgdata count = None # snoop exec response for remote interactive TTYs if msgtype == coreapi.CORE_API_EXEC_MSG and \ msgflags & coreapi.CORE_API_TTY_FLAG: - data = self.fixupremotetty(msghdr, msgdata, host) + data = self.fixupremotetty(msghdr, msgdata, server.host) elif msgtype == coreapi.CORE_API_NODE_MSG: # snoop node delete response to decrement node counts if msgflags & coreapi.CORE_API_DEL_FLAG: msg = coreapi.CoreNodeMessage(msgflags, msghdr, msgdata) nodenum = msg.gettlv(coreapi.CORE_TLV_NODE_NUMBER) if nodenum is not None: - count = self.delnodemap(sock, nodenum) + count = self.delnodemap(server, nodenum) # snoop node add response to increment booted node count # (only CoreNodes send these response messages) elif msgflags & \ @@ -201,82 +218,74 @@ class CoreBroker(ConfigurableManager): connected to this (host, port), then leave it alone. When host,port is None, do not try to connect. ''' - self.servers_lock.acquire() - if name in self.servers: - (oldhost, oldport, sock) = self.servers[name] - if host == oldhost or port == oldport: - # leave this socket connected - if sock is not None: - self.servers_lock.release() + with self.servers_lock: + server = self.servers.get(name) + if server is not None: + if host == server.host and port == server.port and \ + server.sock is not None: + # leave this socket connected return - if self.verbose and host is not None and sock is not None: - self.session.info("closing connection with %s @ %s:%s" % \ + if self.verbose: + self.session.info('closing connection with %s @ %s:%s' % \ + (name, server.host, server.port)) + server.close() + del self.servers[name] + if self.verbose: + self.session.info('adding server %s @ %s:%s' % \ (name, host, port)) - if sock is not None: - sock.close() - self.servers_lock.release() - if self.verbose and host is not None: - self.session.info("adding server %s @ %s:%s" % (name, host, port)) - if host is None: - sock = None - else: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - #sock.setblocking(0) - #error = sock.connect_ex((host, port)) - try: - sock.connect((host, port)) - self.startrecvloop() - except Exception, e: - self.session.warn("error connecting to server %s:%s:\n\t%s" % \ - (host, port, e)) - sock.close() - sock = None - self.servers_lock.acquire() - self.servers[name] = (host, port, sock) - self.servers_lock.release() - - def delserver(self, name): + server = CoreServer(name, host, port) + if host is not None and port is not None: + try: + server.connect() + except Exception as e: + self.session.warn('error connecting to server %s:%s:\n\t%s' % \ + (host, port, e)) + if server.sock is not None: + self.startrecvloop() + self.servers[name] = server + + def delserver(self, server): ''' Remove a server and hang up any connection. ''' - self.servers_lock.acquire() - if name not in self.servers: - self.servers_lock.release() - return - (host, port, sock) = self.servers.pop(name) - if sock is not None: + with self.servers_lock: + try: + s = self.servers.pop(server.name) + assert s == server + except KeyError: + pass + if server.sock is not None: if self.verbose: self.session.info("closing connection with %s @ %s:%s" % \ - (name, host, port)) - sock.close() - self.servers_lock.release() + (server.name, server.host, server.port)) + server.close() - def getserver(self, name): - ''' Return the (host, port, sock) tuple, or raise a KeyError exception. - ''' - if name not in self.servers: - raise KeyError, "emulation server %s not found" % name - return self.servers[name] - - def getserverbysock(self, sockfd): - ''' Return a (host, port, sock, name) tuple based on socket file - descriptor, or raise a KeyError exception. + def getserverbyname(self, name): + ''' Return the server object having the given name, or None. ''' with self.servers_lock: - for name in self.servers: - (host, port, sock) = self.servers[name] - if sock is None: - continue - if sock.fileno() == sockfd: - return (host, port, sock, name) - raise KeyError, "socket fd %s not found" % sockfd - - def getserverlist(self): - ''' Return the list of server names (keys from self.servers). + return self.servers.get(name) + + def getserverbysock(self, sock): + ''' Return the server object corresponding to the given socket, + or None. ''' with self.servers_lock: - serverlist = sorted(self.servers.keys()) - return serverlist - + for server in self.servers.itervalues(): + if server.sock == sock: + return server + return None + + def getservers(self): + '''Return a list of servers sorted by name.''' + with self.servers_lock: + return sorted(self.servers.values(), key = lambda x: x.name) + + def getservernames(self): + ''' Return a sorted list of server names (keys from self.servers). + ''' + with self.servers_lock: + return sorted(self.servers.keys()) + def tunnelkey(self, n1num, n2num): ''' Compute a 32-bit key used to uniquely identify a GRE tunnel. The hash(n1num), hash(n2num) values are used, so node numbers may be @@ -322,7 +331,7 @@ class CoreBroker(ConfigurableManager): ''' for n in self.nets: self.addnettunnel(n) - + def addnettunnel(self, n): try: net = self.session.obj(n) @@ -335,16 +344,15 @@ class CoreBroker(ConfigurableManager): if hasattr(net, 'serverintf'): if net.serverintf is not None: return None - + servers = self.getserversbynode(n) if len(servers) < 2: return None hosts = [] for server in servers: - (host, port, sock) = self.getserver(server) - if host is None: + if server.host is None: continue - hosts.append(host) + hosts.append(server.host) if len(hosts) == 0: # get IP address from API message sender (master) self.session._handlerslock.acquire() @@ -352,7 +360,7 @@ class CoreBroker(ConfigurableManager): if h.client_address != "": hosts.append(h.client_address[0]) self.session._handlerslock.release() - + r = [] for host in hosts: if self.myip: @@ -370,11 +378,11 @@ class CoreBroker(ConfigurableManager): remoteip=host, key=key) self.tunnels[key] = gt r.append(gt) - # attaching to net will later allow gt to be destroyed + # attaching to net will later allow gt to be destroyed # during net.shutdown() net.attach(gt) return r - + def deltunnel(self, n1num, n2num): ''' Cleanup of the GreTapBridge. ''' @@ -411,7 +419,7 @@ class CoreBroker(ConfigurableManager): else: self.nodecounts[server] = 1 - def delnodemap(self, sock, nodenum): + def delnodemap(self, server, nodenum): ''' Remove a node number to emulation server mapping. Return the number of nodes left on this server. ''' @@ -419,19 +427,11 @@ class CoreBroker(ConfigurableManager): with self.nodemap_lock: if nodenum not in self.nodemap: return count - found = False - for server in self.nodemap[nodenum]: - (host, port, srvsock) = self.getserver(server) - if srvsock == sock: - found = True - break + self.nodemap[nodenum].remove(server) if server in self.nodecounts: count = self.nodecounts[server] - if found: - self.nodemap[nodenum].remove(server) - if server in self.nodecounts: - count -= 1 - self.nodecounts[server] = count + count -= 1 + self.nodecounts[server] = count return count def incrbootcount(self): @@ -507,72 +507,74 @@ class CoreBroker(ConfigurableManager): def handlemsg(self, msg): ''' Handle an API message. Determine whether this needs to be handled - by the local server or forwarded on to another one. + by the local server or forwarded on to another one. Returns True when message does not need to be handled locally, and performs forwarding if required. Returning False indicates this message should be handled locally. ''' - serverlist = [] - handle_locally = False - # Do not forward messages when in definition state + servers = set() + # Do not forward messages when in definition state # (for e.g. configuring services) if self.session.getstate() == coreapi.CORE_EVENT_DEFINITION_STATE: - handle_locally = True - return not handle_locally + return False # Decide whether message should be handled locally or forwarded, or both if msg.msgtype == coreapi.CORE_API_NODE_MSG: - (handle_locally, serverlist) = self.handlenodemsg(msg) + servers = self.handlenodemsg(msg) elif msg.msgtype == coreapi.CORE_API_EVENT_MSG: # broadcast events everywhere - serverlist = self.getserverlist() + servers = self.getservers() elif msg.msgtype == coreapi.CORE_API_CONF_MSG: # broadcast location and services configuration everywhere confobj = msg.gettlv(coreapi.CORE_TLV_CONF_OBJ) if confobj == "location" or confobj == "services" or \ confobj == "session" or confobj == "all": - serverlist = self.getserverlist() + servers = self.getservers() elif msg.msgtype == coreapi.CORE_API_FILE_MSG: # broadcast hook scripts and custom service files everywhere filetype = msg.gettlv(coreapi.CORE_TLV_FILE_TYPE) if filetype is not None and \ (filetype[:5] == "hook:" or filetype[:8] == "service:"): - serverlist = self.getserverlist() + servers = self.getservers() if msg.msgtype == coreapi.CORE_API_LINK_MSG: - # prepare a serverlist from two node numbers in link message - (handle_locally, serverlist, msg) = self.handlelinkmsg(msg) - elif len(serverlist) == 0: + # prepare a server list from two node numbers in link message + servers, msg = self.handlelinkmsg(msg) + elif len(servers) == 0: # check for servers based on node numbers in all messages but link nn = msg.nodenumbers() if len(nn) == 0: return False - serverlist = self.getserversbynode(nn[0]) + servers = self.getserversbynode(nn[0]) - if len(serverlist) == 0: - handle_locally = True - - # allow other handlers to process this message - # (this is used by e.g. EMANE to use the link add message to keep counts - # of interfaces on other servers) + # allow other handlers to process this message (this is used + # by e.g. EMANE to use the link add message to keep counts of + # interfaces on other servers) for handler in self.handlers: handler(msg) # Perform any message forwarding - handle_locally = self.forwardmsg(msg, serverlist, handle_locally) + handle_locally = self.forwardmsg(msg, servers) return not handle_locally - def setupserver(self, server): + def setupserver(self, servername): ''' Send the appropriate API messages for configuring the specified emulation server. ''' - (host, port, sock) = self.getserver(server) - if host is None or sock is None: + server = self.getserverbyname(servername) + if server is None: + msg = 'ignoring unknown server: \'%s\'' % servername + self.session.warn(msg) + return + if server.sock is None or server.host is None or server.port is None: + if self.verbose: + msg = 'ignoring disconnected server: \'%s\'' % servername + self.session.info(msg) return # communicate this session's current state to the server tlvdata = coreapi.CoreEventTlv.pack(coreapi.CORE_TLV_EVENT_TYPE, self.session.getstate()) msg = coreapi.CoreEventMessage.pack(0, tlvdata) - sock.send(msg) + server.sock.send(msg) # send a Configuration message for the broker object and inform the # server of its local name tlvdata = "" @@ -582,11 +584,11 @@ class CoreBroker(ConfigurableManager): tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_DATA_TYPES, (coreapi.CONF_DATA_TYPE_STRING,)) tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_VALUES, - "%s:%s:%s" % (server, host, port)) + "%s:%s:%s" % (server.name, server.host, server.port)) tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_SESSION, "%s" % self.session.sessionid) msg = coreapi.CoreConfMessage.pack(0, tlvdata) - sock.send(msg) + server.sock.send(msg) @staticmethod def fixupremotetty(msghdr, msgdata, host): @@ -617,8 +619,7 @@ class CoreBroker(ConfigurableManager): be forwarded. Also keep track of link-layer nodes and the mapping of nodes to servers. ''' - serverlist = [] - handle_locally = False + servers = set() serverfiletxt = None # snoop Node Message for emulation server TLV and record mapping n = msg.tlvdata[coreapi.CORE_TLV_NODE_NUMBER] @@ -629,22 +630,21 @@ class CoreBroker(ConfigurableManager): nodecls = coreapi.node_class(nodetype) except KeyError: self.session.warn("broker invalid node type %s" % nodetype) - return (False, serverlist) + return servers if nodecls is None: self.session.warn("broker unimplemented node type %s" % nodetype) - return (False, serverlist) + return servers if issubclass(nodecls, PyCoreNet) and \ nodetype != coreapi.CORE_NODE_WLAN: # network node replicated on all servers; could be optimized # don't replicate WLANs, because ebtables rules won't work - serverlist = self.getserverlist() - handle_locally = True + servers = self.getservers() self.addnet(n) - for server in serverlist: + for server in servers: self.addnodemap(server, n) # do not record server name for networks since network # nodes are replicated across all server - return (handle_locally, serverlist) + return servers if issubclass(nodecls, PyCoreNet) and \ nodetype == coreapi.CORE_NODE_WLAN: # special case where remote WLANs not in session._objs, and no @@ -658,26 +658,27 @@ class CoreBroker(ConfigurableManager): if issubclass(nodecls, PhysicalNode): # remember physical nodes self.addphys(n) - + # emulation server TLV specifies server - server = msg.gettlv(coreapi.CORE_TLV_NODE_EMUSRV) + servername = msg.gettlv(coreapi.CORE_TLV_NODE_EMUSRV) + server = self.getserverbyname(servername) if server is not None: self.addnodemap(server, n) - if server not in serverlist: - serverlist.append(server) + if server not in servers: + servers.add(server) if serverfiletxt and self.session.master: self.writenodeserver(serverfiletxt, server) # hook to update coordinates of physical nodes if n in self.phys: self.session.mobility.physnodeupdateposition(msg) - return (handle_locally, serverlist) - + return servers + def handlelinkmsg(self, msg): ''' Determine and return the servers to which this link message should be forwarded. Also build tunnels between different servers or add opaque data to the link message before forwarding. ''' - serverlist = [] + servers = set() handle_locally = False # determine link message destination using non-network nodes @@ -688,71 +689,70 @@ class CoreBroker(ConfigurableManager): # the automatic tunnelling handle_locally = True else: - serverlist = self.getserversbynode(nn[1]) + servers = self.getserversbynode(nn[1]) elif nn[1] in self.nets: - serverlist = self.getserversbynode(nn[0]) + servers = self.getserversbynode(nn[0]) else: - serverset1 = set(self.getserversbynode(nn[0])) - serverset2 = set(self.getserversbynode(nn[1])) + servers1 = self.getserversbynode(nn[0]) + servers2 = self.getserversbynode(nn[1]) # nodes are on two different servers, build tunnels as needed - if serverset1 != serverset2: + if servers1 != servers2: localn = None - if len(serverset1) == 0 or len(serverset2) == 0: + if len(servers1) == 0 or len(servers2) == 0: handle_locally = True - serverlist = list(serverset1 | serverset2) + servers = servers1.union(servers2) host = None # get the IP of remote server and decide which node number # is for a local node - for server in serverlist: - (host, port, sock) = self.getserver(server) + for server in servers: + host = server.host if host is None: - # named server is local + # server is local handle_locally = True - if server in serverset1: + if server in servers1: localn = nn[0] else: localn = nn[1] if handle_locally and localn is None: # having no local node at this point indicates local node is - # the one with the empty serverset - if len(serverset1) == 0: + # the one with the empty server set + if len(servers1) == 0: localn = nn[0] - elif len(serverset2) == 0: + elif len(servers2) == 0: localn = nn[1] if host is None: host = self.getlinkendpoint(msg, localn == nn[0]) if localn is None: - msg = self.addlinkendpoints(msg, serverset1, serverset2) + msg = self.addlinkendpoints(msg, servers1, servers2) elif msg.flags & coreapi.CORE_API_ADD_FLAG: self.addtunnel(host, nn[0], nn[1], localn) elif msg.flags & coreapi.CORE_API_DEL_FLAG: self.deltunnel(nn[0], nn[1]) - handle_locally = False else: - serverlist = list(serverset1 | serverset2) + servers = servers1.union(servers2) - return (handle_locally, serverlist, msg) + return servers, msg - def addlinkendpoints(self, msg, serverset1, serverset2): + def addlinkendpoints(self, msg, servers1, servers2): ''' For a link message that is not handled locally, inform the remote servers of the IP addresses used as tunnel endpoints by adding opaque data to the link message. ''' - ip1 = "" - for server in serverset1: - (host, port, sock) = self.getserver(server) - if host is not None: - ip1 = host - ip2 = "" - for server in serverset2: - (host, port, sock) = self.getserver(server) - if host is not None: - ip2 = host - tlvdata = msg.rawmsg[coreapi.CoreMessage.hdrsiz:] + ip1 = '' + for server in servers1: + if server.host is not None: + ip1 = server.host + break + ip2 = '' + for server in servers2: + if server.host is not None: + ip2 = server.host + break + tlvdata = msg.rawmsg[coreapi.CoreMessage.hdrsiz:] tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_OPAQUE, "%s:%s" % (ip1, ip2)) newraw = coreapi.CoreLinkMessage.pack(msg.flags, tlvdata) - msghdr = newraw[:coreapi.CoreMessage.hdrsiz] + msghdr = newraw[:coreapi.CoreMessage.hdrsiz] return coreapi.CoreLinkMessage(msg.flags, msghdr, tlvdata) def getlinkendpoint(self, msg, first_is_local): @@ -786,59 +786,50 @@ class CoreBroker(ConfigurableManager): msgtype, flags, msglen = coreapi.CoreMessage.unpackhdr(hdr) msgcls = coreapi.msg_class(msgtype) return self.handlemsg(msgcls(flags, hdr, msg[coreapi.CoreMessage.hdrsiz:])) - - def forwardmsg(self, msg, serverlist, handle_locally): - ''' Forward API message to all servers in serverlist; if an empty - host/port is encountered, set the handle_locally flag. Returns the - value of the handle_locally flag, which may be unchanged. + + def forwardmsg(self, msg, servers): + ''' Forward API message to all given servers. + + Return True if an empty host/port is encountered, indicating + the message should be handled locally. ''' - for server in serverlist: - try: - (host, port, sock) = self.getserver(server) - except KeyError: - # server not found, don't handle this message locally - self.session.info("broker could not find server %s, message " \ - "with type %s dropped" % \ - (server, msg.msgtype)) - continue - if host is None and port is None: + handle_locally = len(servers) == 0 + for server in servers: + if server.host is None and server.port is None: # local emulation server, handle this locally handle_locally = True + elif server.sock is None: + self.session.info("server %s @ %s:%s is disconnected" % \ + (server.name, server.host, server.port)) else: - if sock is None: - self.session.info("server %s @ %s:%s is disconnected" % \ - (server, host, port)) - else: - sock.send(msg.rawmsg) + server.sock.send(msg.rawmsg) return handle_locally def writeservers(self): ''' Write the server list to a text file in the session directory upon startup: /tmp/pycore.nnnnn/servers ''' + servers = self.getservers() filename = os.path.join(self.session.sessiondir, "servers") + master = self.session_id_master + if master is None: + master = self.session.sessionid try: - f = open(filename, "w") - master = self.session_id_master - if master is None: - master = self.session.sessionid - f.write("master=%s\n" % master) - self.servers_lock.acquire() - for name in sorted(self.servers.keys()): - if name == "localhost": - continue - (host, port, sock) = self.servers[name] - try: - (lhost, lport) = sock.getsockname() - except: - lhost, lport = None, None - f.write("%s %s %s %s %s\n" % (name, host, port, lhost, lport)) - f.close() - except Exception, e: - self.session.warn("Error writing server list to the file: %s\n%s" \ - % (filename, e)) - finally: - self.servers_lock.release() + with open(filename, 'w') as f: + f.write("master=%s\n" % master) + for server in servers: + if server.name == "localhost": + continue + try: + (lhost, lport) = server.sock.getsockname() + except: + lhost, lport = None, None + f.write('%s %s %s %s %s\n' % (server.name, server.host, + server.port, lhost, lport)) + except Exception as e: + msg = 'Error writing server list to the file: \'%s\'\n%s' % \ + (filename, e) + self.session.warn(msg) def writenodeserver(self, nodestr, server): ''' Creates a /tmp/pycore.nnnnn/nX.conf/server file having the node @@ -846,8 +837,7 @@ class CoreBroker(ConfigurableManager): other machines, much like local nodes may be accessed via the VnodeClient class. ''' - (host, port, sock) = self.getserver(server) - serverstr = "%s %s %s" % (server, host, port) + serverstr = "%s %s %s" % (server.name, server.host, server.port) name = nodestr.split()[1] dirname = os.path.join(self.session.sessiondir, name + ".conf") filename = os.path.join(dirname, "server") @@ -857,14 +847,9 @@ class CoreBroker(ConfigurableManager): # directory may already exist from previous distributed run pass try: - f = open(filename, "w") - f.write("%s\n%s\n" % (serverstr, nodestr)) - f.close() - return True - except Exception, e: - msg = "Error writing server file '%s'" % filename - msg += "for node %s:\n%s" % (name, e) + with open(filename, 'w') as f: + f.write('%s\n%s\n' % (serverstr, nodestr)) + except Exception as e: + msg = 'Error writing server file \'%s\' for node %s:\n%s' % \ + (filename, name, e) self.session.warn(msg) - return False - - diff --git a/daemon/core/emane/emane.py b/daemon/core/emane/emane.py index fcc299aa..dce55fcc 100644 --- a/daemon/core/emane/emane.py +++ b/daemon/core/emane/emane.py @@ -456,10 +456,9 @@ class Emane(ConfigurableManager): self._objslock.release() for server in servers: - if server == "localhost": + if server.name == "localhost": continue - (host, port, sock) = self.session.broker.getserver(server) - if sock is None: + if server.sock is None: continue platformid += 1 typeflags = coreapi.CONF_TYPE_FLAGS_UPDATE @@ -467,12 +466,11 @@ class Emane(ConfigurableManager): values[names.index("nem_id_start")] = str(nemid) msg = EmaneGlobalModel.toconfmsg(flags=0, nodenum=None, typeflags=typeflags, values=values) - sock.send(msg) + server.sock.send(msg) # increment nemid for next server by number of interfaces - self._ifccountslock.acquire() - if server in self._ifccounts: - nemid += self._ifccounts[server] - self._ifccountslock.release() + with self._ifccountslock: + if server in self._ifccounts: + nemid += self._ifccounts[server] return False @@ -511,7 +509,7 @@ class Emane(ConfigurableManager): session = self.session if not session.master: return # slave server - servers = session.broker.getserverlist() + servers = session.broker.getservernames() if len(servers) < 2: return # not distributed prefix = session.cfg.get('controlnet') diff --git a/daemon/core/misc/xmldeployment.py b/daemon/core/misc/xmldeployment.py index d34266ff..0544f9f6 100644 --- a/daemon/core/misc/xmldeployment.py +++ b/daemon/core/misc/xmldeployment.py @@ -87,7 +87,7 @@ class CoreDeploymentWriter(object): for n in nodelist: self.add_virtual_host(testhost, n) # TODO: handle other servers - # servers = self.session.broker.getserverlist() + # servers = self.session.broker.getservernames() # servers.remove('localhost') def add_child_element(self, parent, tagName): diff --git a/daemon/core/mobility.py b/daemon/core/mobility.py index b5a8eb45..00a60564 100644 --- a/daemon/core/mobility.py +++ b/daemon/core/mobility.py @@ -239,8 +239,8 @@ class MobilityManager(ConfigurableManager): node = self.phys[nodenum] for server in self.session.broker.getserversbynode(nodenum): break - (host, port, sock) = self.session.broker.getserver(server) - netif = self.session.broker.gettunnel(net.objid, IPAddr.toint(host)) + netif = self.session.broker.gettunnel(net.objid, + IPAddr.toint(server.host)) node.addnetif(netif, 0) netif.node = node (x,y,z) = netif.node.position.get() diff --git a/daemon/core/session.py b/daemon/core/session.py index 8d99f8e7..2e40ab8c 100644 --- a/daemon/core/session.py +++ b/daemon/core/session.py @@ -890,7 +890,7 @@ class Session(object): prefix = prefixes[0] else: # slave servers have their name and localhost in the serverlist - servers = self.broker.getserverlist() + servers = self.broker.getservernames() servers.remove('localhost') prefix = None for server_prefix in prefixes: @@ -927,7 +927,7 @@ class Session(object): # tunnels between controlnets will be built with Broker.addnettunnels() self.broker.addnet(oid) - for server in self.broker.getserverlist(): + for server in self.broker.getservers(): self.broker.addnodemap(server, oid) return ctrlnet @@ -1190,7 +1190,7 @@ class SessionConfig(ConfigurableManager, Configurable): controlnets = value.split() if len(controlnets) < 2: return # multiple controlnet prefixes do not exist - servers = self.session.broker.getserverlist() + servers = self.session.broker.getservernames() if len(servers) < 2: return # not distributed servers.remove("localhost") From d33147154cbee5c5f39a4bb1df4f824cdb36c282 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 17:36:59 +0000 Subject: [PATCH 25/28] daemon: Add an instantiation-complete CORE API event type. Have CORE servers send an instantiation-complete event after a session has completed instantiating. Only enter the runtime state after instantiation-complete events have been received form all servers. --- daemon/core/api/data.py | 1 + daemon/core/broker.py | 28 ++++++++++++++++++++++++++++ daemon/core/session.py | 11 +++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/daemon/core/api/data.py b/daemon/core/api/data.py index 8d889481..45f838da 100644 --- a/daemon/core/api/data.py +++ b/daemon/core/api/data.py @@ -286,6 +286,7 @@ event_types = dict(enumerate([ "CORE_EVENT_FILE_SAVE", "CORE_EVENT_SCHEDULED", "CORE_EVENT_RECONFIGURE", + "CORE_EVENT_INSTANTIATION_COMPLETE", ])) enumdict(event_types) diff --git a/daemon/core/broker.py b/daemon/core/broker.py index a927c19b..2582c3a6 100644 --- a/daemon/core/broker.py +++ b/daemon/core/broker.py @@ -31,6 +31,7 @@ class CoreServer(object): self.host = host self.port = port self.sock = None + self.instantiation_complete = False def connect(self): assert self.sock is None @@ -206,6 +207,13 @@ class CoreBroker(ConfigurableManager): # this allows green link lines for remote WLANs msg = coreapi.CoreLinkMessage(msgflags, msghdr, msgdata) self.session.sdt.handledistributed(msg) + elif msgtype == coreapi.CORE_API_EVENT_MSG: + msg = coreapi.CoreEventMessage(msgflags, msghdr, msgdata) + eventtype = msg.gettlv(coreapi.CORE_TLV_EVENT_TYPE) + if eventtype == coreapi.CORE_EVENT_INSTANTIATION_COMPLETE: + server.instantiation_complete = True + if self.instantiation_complete(): + self.session.checkruntime() self.session.broadcastraw(None, data) if count is not None and count < 1: @@ -213,6 +221,26 @@ class CoreBroker(ConfigurableManager): else: return len(data) + def local_instantiation_complete(self): + '''\ + Set the local server's instantiation-complete status to True. + ''' + with self.servers_lock: + server = self.servers.get('localhost') + if server is not None: + server.instantiation_complete = True + + def instantiation_complete(self): + '''\ + Return True if all servers have completed instantiation, False + otherwise. + ''' + with self.servers_lock: + for server in self.servers.itervalues(): + if not server.instantiation_complete: + return False + return True + def addserver(self, name, host, port): ''' Add a new server, and try to connect to it. If we're already connected to this (host, port), then leave it alone. When host,port diff --git a/daemon/core/session.py b/daemon/core/session.py index 2e40ab8c..ed2c288b 100644 --- a/daemon/core/session.py +++ b/daemon/core/session.py @@ -638,6 +638,13 @@ class Session(object): # allow time for processes to start time.sleep(0.125) self.validatenodes() + self.broker.local_instantiation_complete() + if self.isconnected(): + tlvdata = '' + tlvdata += coreapi.CoreEventTlv.pack(coreapi.CORE_TLV_EVENT_TYPE, + coreapi.CORE_EVENT_INSTANTIATION_COMPLETE) + msg = coreapi.CoreEventMessage.pack(0, tlvdata) + self.broadcastraw(None, msg) # assume either all nodes have booted already, or there are some # nodes on slave servers that will be booted and those servers will # send a node status response message @@ -687,8 +694,8 @@ class Session(object): return # do not have information on all nodes yet # information on all nodes has been received and they have been started # enter the runtime state - # TODO: more sophisticated checks to verify that all nodes and networks - # are running + if not self.broker.instantiation_complete(): + return state = coreapi.CORE_EVENT_RUNTIME_STATE self.evq.run() self.setstate(state, info=True, sendevent=True) From 6d1b5d28a19e805ce1560635c82f6d17280178b2 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 17:37:13 +0000 Subject: [PATCH 26/28] daemon: Remove tracking remote node boot status. The instantiation-complete status of remote servers should indicate when all nodes are running. --- daemon/core/broker.py | 25 ------------------------- daemon/core/session.py | 17 ++--------------- 2 files changed, 2 insertions(+), 40 deletions(-) diff --git a/daemon/core/broker.py b/daemon/core/broker.py index 2582c3a6..3a6254ca 100644 --- a/daemon/core/broker.py +++ b/daemon/core/broker.py @@ -71,7 +71,6 @@ class CoreBroker(ConfigurableManager): self.nodemap_lock = threading.Lock() # reference counts of nodes on servers self.nodecounts = {} - self.bootcount = 0 # set of node numbers that are link-layer nodes (networks) self.nets = set() # set of node numbers that are PhysicalNode nodes @@ -117,7 +116,6 @@ class CoreBroker(ConfigurableManager): if count < 1: self.delserver(server) self.nodecounts.clear() - self.bootcount = 0 self.nodemap_lock.release() self.nets.clear() self.phys.clear() @@ -197,12 +195,6 @@ class CoreBroker(ConfigurableManager): nodenum = msg.gettlv(coreapi.CORE_TLV_NODE_NUMBER) if nodenum is not None: count = self.delnodemap(server, nodenum) - # snoop node add response to increment booted node count - # (only CoreNodes send these response messages) - elif msgflags & \ - (coreapi.CORE_API_ADD_FLAG | coreapi.CORE_API_LOC_FLAG): - self.incrbootcount() - self.session.checkruntime() elif msgtype == coreapi.CORE_API_LINK_MSG: # this allows green link lines for remote WLANs msg = coreapi.CoreLinkMessage(msgflags, msghdr, msgdata) @@ -462,17 +454,6 @@ class CoreBroker(ConfigurableManager): self.nodecounts[server] = count return count - def incrbootcount(self): - ''' Count a node that has booted. - ''' - self.bootcount += 1 - return self.bootcount - - def getbootcount(self): - ''' Return the number of booted nodes. - ''' - return self.bootcount - def getserversbynode(self, nodenum): ''' Retrieve a set of emulation servers given a node number. ''' @@ -673,12 +654,6 @@ class CoreBroker(ConfigurableManager): # do not record server name for networks since network # nodes are replicated across all server return servers - if issubclass(nodecls, PyCoreNet) and \ - nodetype == coreapi.CORE_NODE_WLAN: - # special case where remote WLANs not in session._objs, and no - # node response message received, so they are counted here - if msg.gettlv(coreapi.CORE_TLV_NODE_EMUSRV) is not None: - self.incrbootcount() elif issubclass(nodecls, PyCoreNode): name = msg.gettlv(coreapi.CORE_TLV_NODE_NAME) if name: diff --git a/daemon/core/session.py b/daemon/core/session.py index ed2c288b..f078151c 100644 --- a/daemon/core/session.py +++ b/daemon/core/session.py @@ -647,7 +647,7 @@ class Session(object): self.broadcastraw(None, msg) # assume either all nodes have booted already, or there are some # nodes on slave servers that will be booted and those servers will - # send a node status response message + # send a status response message self.checkruntime() def getnodecount(self): @@ -680,20 +680,7 @@ class Session(object): return if self.getstate() == coreapi.CORE_EVENT_RUNTIME_STATE: return - session_node_count = int(self.node_count) - 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 - # messages, plus any remote WLANs; remote EMANE, hub, switch, etc. - # are already counted in self._objs - nc += self.broker.getbootcount() - self.info("Checking for runtime with %d of %d session nodes" % \ - (nc, session_node_count)) - if nc < session_node_count: - return # do not have information on all nodes yet - # information on all nodes has been received and they have been started - # enter the runtime state + # check if all servers have completed instantiation if not self.broker.instantiation_complete(): return state = coreapi.CORE_EVENT_RUNTIME_STATE From b1beff1eba2cea8ed6f38a4f388275303470c753 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 17:37:27 +0000 Subject: [PATCH 27/28] daemon: Wait longer for EMANE network interfaces to exist. When EMANE is still running. --- daemon/core/emane/emane.py | 16 ++++++++++++++++ daemon/core/netns/vif.py | 14 +++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/daemon/core/emane/emane.py b/daemon/core/emane/emane.py index dce55fcc..41d8a2ca 100644 --- a/daemon/core/emane/emane.py +++ b/daemon/core/emane/emane.py @@ -1153,6 +1153,22 @@ class Emane(ConfigurableManager): self.session.sdt.updatenodegeo(node.objid, lat, long, alt) return True + def emanerunning(self, node): + '''\ + Return True if an EMANE process associated with the given node + is running, False otherwise. + ''' + status = -1 + cmd = ['pkill', '-0', '-x', 'emane'] + try: + if self.version < self.EMANE092: + status = subprocess.call(cmd) + else: + status = node.cmd(cmd, wait=True) + except: + pass + return status == 0 + def emane_version(): 'Return the locally installed EMANE version identifier and string.' cmd = ('emane', '--version') diff --git a/daemon/core/netns/vif.py b/daemon/core/netns/vif.py index 7ceb15a4..5b5be036 100644 --- a/daemon/core/netns/vif.py +++ b/daemon/core/netns/vif.py @@ -116,7 +116,19 @@ class TunTap(PyCoreNetIf): def nodedevexists(): cmd = (IP_BIN, 'link', 'show', self.name) return self.node.cmd(cmd) - self.waitfor(nodedevexists) + count = 0 + while True: + try: + self.waitfor(nodedevexists) + break + except RuntimeError: + # check if this is an EMANE interface; if so, continue + # waiting if EMANE is still running + if count < 5 and isinstance(self.net, EmaneNode) and \ + self.node.session.emane.emanerunning(self.node): + count += 1 + else: + raise def install(self): ''' Install this TAP into its namespace. This is not done from the From 5ec7e39a284548f7bebbf53a24ada35d8c581365 Mon Sep 17 00:00:00 2001 From: Tom Goff Date: Wed, 12 Oct 2016 17:37:40 +0000 Subject: [PATCH 28/28] daemon: Sort EMANE servers so NEM ids are deterministic. --- daemon/core/emane/emane.py | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/core/emane/emane.py b/daemon/core/emane/emane.py index 41d8a2ca..8765a916 100644 --- a/daemon/core/emane/emane.py +++ b/daemon/core/emane/emane.py @@ -455,6 +455,7 @@ class Emane(ConfigurableManager): servers.append(s) self._objslock.release() + servers.sort(key = lambda x: x.name) for server in servers: if server.name == "localhost": continue