merged cleanup branch with master
This commit is contained in:
parent
a4f47a17e3
commit
0a91fe7a3e
28 changed files with 9033 additions and 0 deletions
13
.editorconfig
Normal file
13
.editorconfig
Normal file
|
@ -0,0 +1,13 @@
|
|||
# This file is for unifying the coding style for different editors and IDEs
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.py]
|
||||
indent_style = space
|
||||
indent_size = 4
|
45
daemon/core/api/dataconversion.py
Normal file
45
daemon/core/api/dataconversion.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
"""
|
||||
Converts CORE data objects into legacy API messages.
|
||||
"""
|
||||
|
||||
from core.api import coreapi
|
||||
from core.enumerations import NodeTlvs
|
||||
from core.misc import log
|
||||
from core.misc import structutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
def convert_node(node_data):
|
||||
"""
|
||||
Callback to handle an node broadcast out from a session.
|
||||
|
||||
:param core.data.NodeData node_data: node data to handle
|
||||
:return: packed node message
|
||||
"""
|
||||
logger.debug("converting node data to message: %s", node_data)
|
||||
|
||||
tlv_data = structutils.pack_values(coreapi.CoreNodeTlv, [
|
||||
(NodeTlvs.NUMBER, node_data.id),
|
||||
(NodeTlvs.TYPE, node_data.node_type),
|
||||
(NodeTlvs.NAME, node_data.name),
|
||||
(NodeTlvs.IP_ADDRESS, node_data.ip_address),
|
||||
(NodeTlvs.MAC_ADDRESS, node_data.mac_address),
|
||||
(NodeTlvs.IP6_ADDRESS, node_data.ip6_address),
|
||||
(NodeTlvs.MODEL, node_data.model),
|
||||
(NodeTlvs.EMULATION_ID, node_data.emulation_id),
|
||||
(NodeTlvs.EMULATION_SERVER, node_data.emulation_server),
|
||||
(NodeTlvs.SESSION, node_data.session),
|
||||
(NodeTlvs.X_POSITION, node_data.x_position),
|
||||
(NodeTlvs.Y_POSITION, node_data.y_position),
|
||||
(NodeTlvs.CANVAS, node_data.canvas),
|
||||
(NodeTlvs.NETWORK_ID, node_data.network_id),
|
||||
(NodeTlvs.SERVICES, node_data.services),
|
||||
(NodeTlvs.LATITUDE, node_data.latitude),
|
||||
(NodeTlvs.LONGITUDE, node_data.longitude),
|
||||
(NodeTlvs.ALTITUDE, node_data.altitude),
|
||||
(NodeTlvs.ICON, node_data.icon),
|
||||
(NodeTlvs.OPAQUE, node_data.opaque)
|
||||
])
|
||||
|
||||
return coreapi.CoreNodeMessage.pack(node_data.message_type, tlv_data)
|
1790
daemon/core/corehandlers.py
Normal file
1790
daemon/core/corehandlers.py
Normal file
File diff suppressed because it is too large
Load diff
120
daemon/core/data.py
Normal file
120
daemon/core/data.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
"""
|
||||
CORE data objects.
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
ConfigData = collections.namedtuple("ConfigData", [
|
||||
"message_type",
|
||||
"node",
|
||||
"object",
|
||||
"type",
|
||||
"data_types",
|
||||
"data_values",
|
||||
"captions",
|
||||
"bitmap",
|
||||
"possible_values",
|
||||
"groups",
|
||||
"session",
|
||||
"interface_number",
|
||||
"network_id",
|
||||
"opaque"
|
||||
])
|
||||
ConfigData.__new__.__defaults__ = (None,) * len(ConfigData._fields)
|
||||
|
||||
EventData = collections.namedtuple("EventData", [
|
||||
"node",
|
||||
"event_type",
|
||||
"name",
|
||||
"data",
|
||||
"time",
|
||||
"session"
|
||||
])
|
||||
EventData.__new__.__defaults__ = (None,) * len(EventData._fields)
|
||||
|
||||
ExceptionData = collections.namedtuple("ExceptionData", [
|
||||
"node",
|
||||
"session",
|
||||
"level",
|
||||
"source",
|
||||
"date",
|
||||
"text",
|
||||
"opaque"
|
||||
])
|
||||
ExceptionData.__new__.__defaults__ = (None,) * len(ExceptionData._fields)
|
||||
|
||||
FileData = collections.namedtuple("FileData", [
|
||||
"message_type",
|
||||
"node",
|
||||
"name",
|
||||
"mode",
|
||||
"number",
|
||||
"type",
|
||||
"source",
|
||||
"session",
|
||||
"data",
|
||||
"compressed_data"
|
||||
])
|
||||
FileData.__new__.__defaults__ = (None,) * len(FileData._fields)
|
||||
|
||||
NodeData = collections.namedtuple("NodeData", [
|
||||
"message_type",
|
||||
"id",
|
||||
"node_type",
|
||||
"name",
|
||||
"ip_address",
|
||||
"mac_address",
|
||||
"ip6_address",
|
||||
"model",
|
||||
"emulation_id",
|
||||
"emulation_server",
|
||||
"session",
|
||||
"x_position",
|
||||
"y_position",
|
||||
"canvas",
|
||||
"network_id",
|
||||
"services",
|
||||
"latitude",
|
||||
"longitude",
|
||||
"altitude",
|
||||
"icon",
|
||||
"opaque"
|
||||
])
|
||||
NodeData.__new__.__defaults__ = (None,) * len(NodeData._fields)
|
||||
|
||||
LinkData = collections.namedtuple("LinkData", [
|
||||
"message_type",
|
||||
"node1_id",
|
||||
"node2_id",
|
||||
"delay",
|
||||
"bandwidth",
|
||||
"per",
|
||||
"dup",
|
||||
"jitter",
|
||||
"mer",
|
||||
"burst",
|
||||
"session",
|
||||
"mburst",
|
||||
"link_type",
|
||||
"gui_attributes",
|
||||
"unidirectional",
|
||||
"emulation_id",
|
||||
"network_id",
|
||||
"key",
|
||||
"interface1_id",
|
||||
"interface1_name",
|
||||
"interface1_ip4",
|
||||
"interface1_ip4_mask",
|
||||
"interface1_mac",
|
||||
"interface1_ip6",
|
||||
"interface1_ip6_mask",
|
||||
"interface2_id",
|
||||
"interface2_name",
|
||||
"interface2_ip4",
|
||||
"interface2_ip4_mask",
|
||||
"interface2_mac",
|
||||
"interface2_ip6",
|
||||
"interface2_ip6_mask",
|
||||
"opaque"
|
||||
])
|
||||
LinkData.__new__.__defaults__ = (None,) * len(LinkData._fields)
|
1231
daemon/core/emane/emanemanager.py
Normal file
1231
daemon/core/emane/emanemanager.py
Normal file
File diff suppressed because it is too large
Load diff
204
daemon/core/emane/emanemodel.py
Normal file
204
daemon/core/emane/emanemodel.py
Normal file
|
@ -0,0 +1,204 @@
|
|||
"""
|
||||
Defines Emane Models used within CORE.
|
||||
Copyright (c)2010-2014, 2017 the Boeing Company.
|
||||
"""
|
||||
|
||||
from core import emane
|
||||
from core.misc import log
|
||||
from core.misc import utils
|
||||
from core.mobility import WirelessModel
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class EmaneModel(WirelessModel):
|
||||
"""
|
||||
EMANE models inherit from this parent class, which takes care of
|
||||
handling configuration messages based on the _confmatrix list of
|
||||
configurable parameters. Helper functions also live here.
|
||||
"""
|
||||
_prefix = {'y': 1e-24, # yocto
|
||||
'z': 1e-21, # zepto
|
||||
'a': 1e-18, # atto
|
||||
'f': 1e-15, # femto
|
||||
'p': 1e-12, # pico
|
||||
'n': 1e-9, # nano
|
||||
'u': 1e-6, # micro
|
||||
'm': 1e-3, # mili
|
||||
'c': 1e-2, # centi
|
||||
'd': 1e-1, # deci
|
||||
'k': 1e3, # kilo
|
||||
'M': 1e6, # mega
|
||||
'G': 1e9, # giga
|
||||
'T': 1e12, # tera
|
||||
'P': 1e15, # peta
|
||||
'E': 1e18, # exa
|
||||
'Z': 1e21, # zetta
|
||||
'Y': 1e24, # yotta
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def configure_emane(cls, session, config_data):
|
||||
"""
|
||||
Handle configuration messages for setting up a model.
|
||||
Pass the Emane object as the manager object.
|
||||
|
||||
:param core.session.Session session: session to configure emane
|
||||
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
|
||||
"""
|
||||
return cls.configure(session.emane, config_data)
|
||||
|
||||
@classmethod
|
||||
def emane074_fixup(cls, value, div=1.0):
|
||||
"""
|
||||
Helper for converting 0.8.1 and newer values to EMANE 0.7.4
|
||||
compatible values.
|
||||
NOTE: This should be removed when support for 0.7.4 has been
|
||||
deprecated.
|
||||
"""
|
||||
if div == 0:
|
||||
return "0"
|
||||
if type(value) is not str:
|
||||
return str(value / div)
|
||||
if value.endswith(tuple(cls._prefix.keys())):
|
||||
suffix = value[-1]
|
||||
value = float(value[:-1]) * cls._prefix[suffix]
|
||||
return str(int(value / div))
|
||||
|
||||
def buildnemxmlfiles(self, e, ifc):
|
||||
"""
|
||||
Build the necessary nem, mac, and phy XMLs in the given path.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def buildplatformxmlnementry(self, doc, n, ifc):
|
||||
"""
|
||||
Build the NEM definition that goes into the platform.xml file.
|
||||
This returns an XML element that will be added to the <platform/> element.
|
||||
This default method supports per-interface config
|
||||
(e.g. <nem definition="n2_0_63emane_rfpipe.xml" id="1"> or per-EmaneNode
|
||||
config (e.g. <nem definition="n1emane_rfpipe.xml" id="1">.
|
||||
This can be overriden by a model for NEM flexibility; n is the EmaneNode.
|
||||
"""
|
||||
nem = doc.createElement("nem")
|
||||
nem.setAttribute("name", ifc.localname)
|
||||
# if this netif contains a non-standard (per-interface) config,
|
||||
# then we need to use a more specific xml file here
|
||||
nem.setAttribute("definition", self.nemxmlname(ifc))
|
||||
return nem
|
||||
|
||||
def buildplatformxmltransportentry(self, doc, n, ifc):
|
||||
"""
|
||||
Build the transport definition that goes into the platform.xml file.
|
||||
This returns an XML element that will added to the nem definition.
|
||||
This default method supports raw and virtual transport types, but may be
|
||||
overriden by a model to support the e.g. pluggable virtual transport.
|
||||
n is the EmaneNode.
|
||||
"""
|
||||
ttype = ifc.transport_type
|
||||
if not ttype:
|
||||
logger.info("warning: %s interface type unsupported!" % ifc.name)
|
||||
ttype = "raw"
|
||||
trans = doc.createElement("transport")
|
||||
trans.setAttribute("definition", n.transportxmlname(ttype))
|
||||
if emane.VERSION < emane.EMANE092:
|
||||
trans.setAttribute("group", "1")
|
||||
param = doc.createElement("param")
|
||||
param.setAttribute("name", "device")
|
||||
if ttype == "raw":
|
||||
# raw RJ45 name e.g. 'eth0'
|
||||
param.setAttribute("value", ifc.name)
|
||||
else:
|
||||
# virtual TAP name e.g. 'n3.0.17'
|
||||
param.setAttribute("value", ifc.localname)
|
||||
if emane.VERSION > emane.EMANE091:
|
||||
param.setAttribute("value", ifc.name)
|
||||
|
||||
trans.appendChild(param)
|
||||
return trans
|
||||
|
||||
def basename(self, interface=None):
|
||||
"""
|
||||
Return the string that other names are based on.
|
||||
If a specific config is stored for a node's interface, a unique
|
||||
filename is needed; otherwise the name of the EmaneNode is used.
|
||||
"""
|
||||
emane = self.session.emane
|
||||
name = "n%s" % self.object_id
|
||||
if interface is not None:
|
||||
nodenum = interface.node.objid
|
||||
# Adamson change - use getifcconfig() to get proper result
|
||||
# if emane.getconfig(nodenum, self._name, None)[1] is not None:
|
||||
if emane.getifcconfig(nodenum, self.name, None, interface) is not None:
|
||||
name = interface.localname.replace('.', '_')
|
||||
return "%s%s" % (name, self.name)
|
||||
|
||||
def nemxmlname(self, interface=None):
|
||||
"""
|
||||
Return the string name for the NEM XML file, e.g. 'n3rfpipenem.xml'
|
||||
"""
|
||||
append = ""
|
||||
if emane.VERSION > emane.EMANE091:
|
||||
if interface and interface.transport_type == "raw":
|
||||
append = "_raw"
|
||||
return "%snem%s.xml" % (self.basename(interface), append)
|
||||
|
||||
def shimxmlname(self, ifc=None):
|
||||
"""
|
||||
Return the string name for the SHIM XML file, e.g. 'commeffectshim.xml'
|
||||
"""
|
||||
return "%sshim.xml" % self.basename(ifc)
|
||||
|
||||
def macxmlname(self, ifc=None):
|
||||
"""
|
||||
Return the string name for the MAC XML file, e.g. 'n3rfpipemac.xml'
|
||||
"""
|
||||
return "%smac.xml" % self.basename(ifc)
|
||||
|
||||
def phyxmlname(self, ifc=None):
|
||||
"""
|
||||
Return the string name for the PHY XML file, e.g. 'n3rfpipephy.xml'
|
||||
"""
|
||||
return "%sphy.xml" % self.basename(ifc)
|
||||
|
||||
def update(self, moved, moved_netifs):
|
||||
"""
|
||||
invoked from MobilityModel when nodes are moved; this causes
|
||||
EMANE location events to be generated for the nodes in the moved
|
||||
list, making EmaneModels compatible with Ns2ScriptedMobility
|
||||
"""
|
||||
try:
|
||||
wlan = self.session.get_object(self.object_id)
|
||||
wlan.setnempositions(moved_netifs)
|
||||
except KeyError:
|
||||
logger.exception("error during update")
|
||||
|
||||
def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None, jitter=None, netif2=None):
|
||||
"""
|
||||
Invoked when a Link Message is received. Default is unimplemented.
|
||||
"""
|
||||
warntxt = "EMANE model %s does not support link " % self.name
|
||||
warntxt += "configuration, dropping Link Message"
|
||||
logger.warn(warntxt)
|
||||
|
||||
@staticmethod
|
||||
def valuestrtoparamlist(dom, name, value):
|
||||
"""
|
||||
Helper to convert a parameter to a paramlist.
|
||||
Returns a an XML paramlist, or None if the value does not expand to
|
||||
multiple values.
|
||||
"""
|
||||
try:
|
||||
values = utils.maketuplefromstr(value, str)
|
||||
except SyntaxError:
|
||||
logger.exception("error in value string to param list")
|
||||
return None
|
||||
|
||||
if not hasattr(values, '__iter__'):
|
||||
return None
|
||||
|
||||
if len(values) < 2:
|
||||
return None
|
||||
|
||||
return xmlutils.add_param_list_to_parent(dom, parent=None, name=name, values=values)
|
318
daemon/core/enumerations.py
Normal file
318
daemon/core/enumerations.py
Normal file
|
@ -0,0 +1,318 @@
|
|||
"""
|
||||
Contains all legacy enumerations for interacting with legacy CORE code.
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
CORE_API_VERSION = "1.23"
|
||||
CORE_API_PORT = 4038
|
||||
|
||||
|
||||
class MessageTypes(Enum):
|
||||
"""
|
||||
CORE message types.
|
||||
"""
|
||||
NODE = 0x01
|
||||
LINK = 0x02
|
||||
EXECUTE = 0x03
|
||||
REGISTER = 0x04
|
||||
CONFIG = 0x05
|
||||
FILE = 0x06
|
||||
INTERFACE = 0x07
|
||||
EVENT = 0x08
|
||||
SESSION = 0x09
|
||||
EXCEPTION = 0x0A
|
||||
|
||||
|
||||
class MessageFlags(Enum):
|
||||
"""
|
||||
CORE message flags.
|
||||
"""
|
||||
ADD = 0x01
|
||||
DELETE = 0x02
|
||||
CRI = 0x04
|
||||
LOCAL = 0x08
|
||||
STRING = 0x10
|
||||
TEXT = 0x20
|
||||
TTY = 0x40
|
||||
|
||||
|
||||
class NodeTlvs(Enum):
|
||||
"""
|
||||
Node type, length, value enumerations.
|
||||
"""
|
||||
NUMBER = 0x01
|
||||
TYPE = 0x02
|
||||
NAME = 0x03
|
||||
IP_ADDRESS = 0x04
|
||||
MAC_ADDRESS = 0x05
|
||||
IP6_ADDRESS = 0x06
|
||||
MODEL = 0x07
|
||||
EMULATION_SERVER = 0x08
|
||||
SESSION = 0x0A
|
||||
X_POSITION = 0x20
|
||||
Y_POSITION = 0x21
|
||||
CANVAS = 0x22
|
||||
EMULATION_ID = 0x23
|
||||
NETWORK_ID = 0x24
|
||||
SERVICES = 0x25
|
||||
LATITUDE = 0x30
|
||||
LONGITUDE = 0x31
|
||||
ALTITUDE = 0x32
|
||||
ICON = 0x42
|
||||
OPAQUE = 0x50
|
||||
|
||||
|
||||
class NodeTypes(Enum):
|
||||
"""
|
||||
Node types.
|
||||
"""
|
||||
DEFAULT = 0
|
||||
PHYSICAL = 1
|
||||
XEN = 2
|
||||
TBD = 3
|
||||
SWITCH = 4
|
||||
HUB = 5
|
||||
WIRELESS_LAN = 6
|
||||
RJ45 = 7
|
||||
TUNNEL = 8
|
||||
KTUNNEL = 9
|
||||
EMANE = 10
|
||||
TAP_BRIDGE = 11
|
||||
PEER_TO_PEER = 12
|
||||
CONTROL_NET = 13
|
||||
EMANE_NET = 14
|
||||
|
||||
|
||||
class Rj45Models(Enum):
|
||||
"""
|
||||
RJ45 model types.
|
||||
"""
|
||||
LINKED = 0
|
||||
WIRELESS = 1
|
||||
INSTALLED = 2
|
||||
|
||||
|
||||
# Link Message TLV Types
|
||||
class LinkTlvs(Enum):
|
||||
"""
|
||||
Link type, length, value enumerations.
|
||||
"""
|
||||
N1_NUMBER = 0x01
|
||||
N2_NUMBER = 0x02
|
||||
DELAY = 0x03
|
||||
BANDWIDTH = 0x04
|
||||
PER = 0x05
|
||||
DUP = 0x06
|
||||
JITTER = 0x07
|
||||
MER = 0x08
|
||||
BURST = 0x09
|
||||
SESSION = 0x0A
|
||||
MBURST = 0x10
|
||||
TYPE = 0x20
|
||||
GUI_ATTRIBUTES = 0x21
|
||||
UNIDIRECTIONAL = 0x22
|
||||
EMULATION_ID = 0x23
|
||||
NETWORK_ID = 0x24
|
||||
KEY = 0x25
|
||||
INTERFACE1_NUMBER = 0x30
|
||||
INTERFACE1_IP4 = 0x31
|
||||
INTERFACE1_IP4_MASK = 0x32
|
||||
INTERFACE1_MAC = 0x33
|
||||
INTERFACE1_IP6 = 0x34
|
||||
INTERFACE1_IP6_MASK = 0x35
|
||||
INTERFACE2_NUMBER = 0x36
|
||||
INTERFACE2_IP4 = 0x37
|
||||
INTERFACE2_IP4_MASK = 0x38
|
||||
INTERFACE2_MAC = 0x39
|
||||
INTERFACE2_IP6 = 0x40
|
||||
INTERFACE2_IP6_MASK = 0x41
|
||||
INTERFACE1_NAME = 0x42
|
||||
INTERFACE2_NAME = 0x43
|
||||
OPAQUE = 0x50
|
||||
|
||||
|
||||
class LinkTypes(Enum):
|
||||
"""
|
||||
Link types.
|
||||
"""
|
||||
WIRELESS = 0
|
||||
WIRED = 1
|
||||
|
||||
|
||||
class ExecuteTlvs(Enum):
|
||||
"""
|
||||
Execute type, length, value enumerations.
|
||||
"""
|
||||
NODE = 0x01
|
||||
NUMBER = 0x02
|
||||
TIME = 0x03
|
||||
COMMAND = 0x04
|
||||
RESULT = 0x05
|
||||
STATUS = 0x06
|
||||
SESSION = 0x0A
|
||||
|
||||
|
||||
class RegisterTlvs(Enum):
|
||||
"""
|
||||
Register type, length, value enumerations.
|
||||
"""
|
||||
WIRELESS = 0x01
|
||||
MOBILITY = 0x02
|
||||
UTILITY = 0x03
|
||||
EXECUTE_SERVER = 0x04
|
||||
GUI = 0x05
|
||||
EMULATION_SERVER = 0x06
|
||||
SESSION = 0x0A
|
||||
|
||||
|
||||
class ConfigTlvs(Enum):
|
||||
"""
|
||||
Configuration type, length, value enumerations.
|
||||
"""
|
||||
NODE = 0x01
|
||||
OBJECT = 0x02
|
||||
TYPE = 0x03
|
||||
DATA_TYPES = 0x04
|
||||
VALUES = 0x05
|
||||
CAPTIONS = 0x06
|
||||
BITMAP = 0x07
|
||||
POSSIBLE_VALUES = 0x08
|
||||
GROUPS = 0x09
|
||||
SESSION = 0x0A
|
||||
INTERFACE_NUMBER = 0x0B
|
||||
NETWORK_ID = 0x24
|
||||
OPAQUE = 0x50
|
||||
|
||||
|
||||
class ConfigFlags(Enum):
|
||||
"""
|
||||
Configuration flags.
|
||||
"""
|
||||
NONE = 0x00
|
||||
REQUEST = 0x01
|
||||
UPDATE = 0x02
|
||||
RESET = 0x03
|
||||
|
||||
|
||||
class ConfigDataTypes(Enum):
|
||||
"""
|
||||
Configuration data types.
|
||||
"""
|
||||
UINT8 = 0x01
|
||||
UINT16 = 0x02
|
||||
UINT32 = 0x03
|
||||
UINT64 = 0x04
|
||||
INT8 = 0x05
|
||||
INT16 = 0x06
|
||||
INT32 = 0x07
|
||||
INT64 = 0x08
|
||||
FLOAT = 0x09
|
||||
STRING = 0x0A
|
||||
BOOL = 0x0B
|
||||
|
||||
|
||||
class FileTlvs(Enum):
|
||||
"""
|
||||
File type, length, value enumerations.
|
||||
"""
|
||||
NODE = 0x01
|
||||
NAME = 0x02
|
||||
MODE = 0x03
|
||||
NUMBER = 0x04
|
||||
TYPE = 0x05
|
||||
SOURCE_NAME = 0x06
|
||||
SESSION = 0x0A
|
||||
DATA = 0x10
|
||||
COMPRESSED_DATA = 0x11
|
||||
|
||||
|
||||
class InterfaceTlvs(Enum):
|
||||
"""
|
||||
Interface type, length, value enumerations.
|
||||
"""
|
||||
NODE = 0x01
|
||||
NUMBER = 0x02
|
||||
NAME = 0x03
|
||||
IP_ADDRESS = 0x04
|
||||
MASK = 0x05
|
||||
MAC_ADDRESS = 0x06
|
||||
IP6_ADDRESS = 0x07
|
||||
IP6_MASK = 0x08
|
||||
TYPE = 0x09
|
||||
SESSION = 0x0A
|
||||
STATE = 0x0B
|
||||
EMULATION_ID = 0x23
|
||||
NETWORK_ID = 0x24
|
||||
|
||||
|
||||
class EventTlvs(Enum):
|
||||
"""
|
||||
Event type, length, value enumerations.
|
||||
"""
|
||||
NODE = 0x01
|
||||
TYPE = 0x02
|
||||
NAME = 0x03
|
||||
DATA = 0x04
|
||||
TIME = 0x05
|
||||
SESSION = 0x0A
|
||||
|
||||
|
||||
class EventTypes(Enum):
|
||||
"""
|
||||
Event types.
|
||||
"""
|
||||
NONE = 0
|
||||
DEFINITION_STATE = 1
|
||||
CONFIGURATION_STATE = 2
|
||||
INSTANTIATION_STATE = 3
|
||||
RUNTIME_STATE = 4
|
||||
DATACOLLECT_STATE = 5
|
||||
SHUTDOWN_STATE = 6
|
||||
START = 7
|
||||
STOP = 8
|
||||
PAUSE = 9
|
||||
RESTART = 10
|
||||
FILE_OPEN = 11
|
||||
FILE_SAVE = 12
|
||||
SCHEDULED = 13
|
||||
RECONFIGURE = 14
|
||||
INSTANTIATION_COMPLETE = 15
|
||||
|
||||
|
||||
class SessionTlvs(Enum):
|
||||
"""
|
||||
Session type, length, value enumerations.
|
||||
"""
|
||||
NUMBER = 0x01
|
||||
NAME = 0x02
|
||||
FILE = 0x03
|
||||
NODE_COUNT = 0x04
|
||||
DATE = 0x05
|
||||
THUMB = 0x06
|
||||
USER = 0x07
|
||||
OPAQUE = 0x0A
|
||||
|
||||
|
||||
class ExceptionTlvs(Enum):
|
||||
"""
|
||||
Exception type, length, value enumerations.
|
||||
"""
|
||||
NODE = 0x01
|
||||
SESSION = 0x02
|
||||
LEVEL = 0x03
|
||||
SOURCE = 0x04
|
||||
DATE = 0x05
|
||||
TEXT = 0x06
|
||||
OPAQUE = 0x0A
|
||||
|
||||
|
||||
class ExceptionLevels(Enum):
|
||||
"""
|
||||
Exception levels.
|
||||
"""
|
||||
NONE = 0
|
||||
FATAL = 1
|
||||
ERROR = 2
|
||||
WARNING = 3
|
||||
NOTICE = 4
|
449
daemon/core/misc/ipaddress.py
Normal file
449
daemon/core/misc/ipaddress.py
Normal file
|
@ -0,0 +1,449 @@
|
|||
"""
|
||||
Helper objects for dealing with IPv4/v6 addresses.
|
||||
"""
|
||||
|
||||
import random
|
||||
import socket
|
||||
import struct
|
||||
from socket import AF_INET
|
||||
from socket import AF_INET6
|
||||
|
||||
from core.misc import log
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class MacAddress(object):
|
||||
"""
|
||||
Provides mac address utilities for use within core.
|
||||
"""
|
||||
|
||||
def __init__(self, address):
|
||||
"""
|
||||
Creates a MacAddress instance.
|
||||
|
||||
:param str address: mac address
|
||||
"""
|
||||
self.addr = address
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Create a string representation of a MacAddress.
|
||||
|
||||
:return: string representation
|
||||
:rtype: str
|
||||
"""
|
||||
return ":".join(map(lambda x: "%02x" % ord(x), self.addr))
|
||||
|
||||
def to_link_local(self):
|
||||
"""
|
||||
Convert the MAC address to a IPv6 link-local address, using EUI 48
|
||||
to EUI 64 conversion process per RFC 5342.
|
||||
|
||||
:return: ip address object
|
||||
:rtype: IpAddress
|
||||
"""
|
||||
if not self.addr:
|
||||
return IpAddress.from_string("::")
|
||||
tmp = struct.unpack("!Q", '\x00\x00' + self.addr)[0]
|
||||
nic = long(tmp) & 0x000000FFFFFFL
|
||||
oui = long(tmp) & 0xFFFFFF000000L
|
||||
# toggle U/L bit
|
||||
oui ^= 0x020000000000L
|
||||
# append EUI-48 octets
|
||||
oui = (oui << 16) | 0xFFFE000000L
|
||||
return IpAddress(AF_INET6, struct.pack("!QQ", 0xfe80 << 48, oui | nic))
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, s):
|
||||
"""
|
||||
Create a mac address object from a string.
|
||||
|
||||
:param s: string representation of a mac address
|
||||
:return: mac address class
|
||||
:rtype: MacAddress
|
||||
"""
|
||||
addr = "".join(map(lambda x: chr(int(x, 16)), s.split(":")))
|
||||
return cls(addr)
|
||||
|
||||
@classmethod
|
||||
def random(cls):
|
||||
"""
|
||||
Create a random mac address.
|
||||
|
||||
:return: random mac address
|
||||
:rtype: MacAddress
|
||||
"""
|
||||
tmp = random.randint(0, 0xFFFFFF)
|
||||
# use the Xen OID 00:16:3E
|
||||
tmp |= 0x00163E << 24
|
||||
tmpbytes = struct.pack("!Q", tmp)
|
||||
return cls(tmpbytes[2:])
|
||||
|
||||
|
||||
class IpAddress(object):
|
||||
"""
|
||||
Provides ip utilities and functionality for use within core.
|
||||
"""
|
||||
|
||||
def __init__(self, af, address):
|
||||
"""
|
||||
Create a IpAddress instance.
|
||||
|
||||
:param int af: address family
|
||||
:param str address: ip address
|
||||
:return:
|
||||
"""
|
||||
# check if (af, addr) is valid
|
||||
if not socket.inet_ntop(af, address):
|
||||
raise ValueError("invalid af/addr")
|
||||
self.af = af
|
||||
self.addr = address
|
||||
|
||||
def is_ipv4(self):
|
||||
"""
|
||||
Checks if this is an ipv4 address.
|
||||
|
||||
:return: True if ipv4 address, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
return self.af == AF_INET
|
||||
|
||||
def is_ipv6(self):
|
||||
"""
|
||||
Checks if this is an ipv6 address.
|
||||
|
||||
:return: True if ipv6 address, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
return self.af == AF_INET6
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
Create a string representation of this address.
|
||||
|
||||
:return: string representation of address
|
||||
:rtype: str
|
||||
"""
|
||||
return socket.inet_ntop(self.af, self.addr)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Checks for equality with another ip address.
|
||||
|
||||
:param IpAddress other: other ip address to check equality with
|
||||
:return: True is the other IpAddress is equal, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
if not isinstance(other, IpAddress):
|
||||
return False
|
||||
elif self is other:
|
||||
return True
|
||||
else:
|
||||
return other.af == self.af and other.addr == self.addr
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Add value to ip addresses.
|
||||
|
||||
:param int other: value to add to ip address
|
||||
:return: added together ip address instance
|
||||
:rtype: IpAddress
|
||||
"""
|
||||
try:
|
||||
carry = int(other)
|
||||
except ValueError:
|
||||
logger.exception("error during addition")
|
||||
return NotImplemented
|
||||
|
||||
tmp = map(lambda x: ord(x), self.addr)
|
||||
for i in xrange(len(tmp) - 1, -1, -1):
|
||||
x = tmp[i] + carry
|
||||
tmp[i] = x & 0xff
|
||||
carry = x >> 8
|
||||
if carry == 0:
|
||||
break
|
||||
addr = "".join(map(lambda x: chr(x), tmp))
|
||||
return self.__class__(self.af, addr)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""
|
||||
Subtract value from ip address.
|
||||
|
||||
:param int other: value to subtract from ip address
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
tmp = -int(other)
|
||||
except ValueError:
|
||||
logger.exception("error during subtraction")
|
||||
return NotImplemented
|
||||
|
||||
return self.__add__(tmp)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, s):
|
||||
"""
|
||||
Create a ip address from a string representation.
|
||||
|
||||
:param s: string representation to create ip address from
|
||||
:return: ip address instance
|
||||
:rtype: IpAddress
|
||||
"""
|
||||
for af in AF_INET, AF_INET6:
|
||||
return cls(af, socket.inet_pton(af, s))
|
||||
|
||||
@staticmethod
|
||||
def to_int(s):
|
||||
"""
|
||||
Convert IPv4 string to integer
|
||||
|
||||
:param s: string to convert to 32-bit integer
|
||||
:return: integer value
|
||||
:rtype: int
|
||||
"""
|
||||
bin = socket.inet_pton(AF_INET, s)
|
||||
return struct.unpack('!I', bin)[0]
|
||||
|
||||
|
||||
class IpPrefix(object):
|
||||
"""
|
||||
Provides ip address generation and prefix utilities.
|
||||
"""
|
||||
|
||||
def __init__(self, af, prefixstr):
|
||||
"""
|
||||
Create a IpPrefix instance.
|
||||
|
||||
:param int af: address family for ip prefix
|
||||
:param prefixstr: ip prefix string
|
||||
"""
|
||||
# prefixstr format: address/prefixlen
|
||||
tmp = prefixstr.split("/")
|
||||
if len(tmp) > 2:
|
||||
raise ValueError("invalid prefix: '%s'" % prefixstr)
|
||||
self.af = af
|
||||
if self.af == AF_INET:
|
||||
self.addrlen = 32
|
||||
elif self.af == AF_INET6:
|
||||
self.addrlen = 128
|
||||
else:
|
||||
raise ValueError("invalid address family: '%s'" % self.af)
|
||||
if len(tmp) == 2:
|
||||
self.prefixlen = int(tmp[1])
|
||||
else:
|
||||
self.prefixlen = self.addrlen
|
||||
self.prefix = socket.inet_pton(self.af, tmp[0])
|
||||
if self.addrlen > self.prefixlen:
|
||||
addrbits = self.addrlen - self.prefixlen
|
||||
netmask = ((1L << self.prefixlen) - 1) << addrbits
|
||||
prefix = ""
|
||||
for i in xrange(-1, -(addrbits >> 3) - 2, -1):
|
||||
prefix = chr(ord(self.prefix[i]) & (netmask & 0xff)) + prefix
|
||||
netmask >>= 8
|
||||
self.prefix = self.prefix[:i] + prefix
|
||||
|
||||
def __str__(self):
|
||||
"""
|
||||
String representation of an ip prefix.
|
||||
|
||||
:return: string representation
|
||||
:rtype: str
|
||||
"""
|
||||
return "%s/%s" % (socket.inet_ntop(self.af, self.prefix), self.prefixlen)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
Compare equality with another ip prefix.
|
||||
|
||||
:param IpPrefix other: other ip prefix to compare with
|
||||
:return: True is equal, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
if not isinstance(other, IpPrefix):
|
||||
return False
|
||||
elif self is other:
|
||||
return True
|
||||
else:
|
||||
return other.af == self.af and other.prefixlen == self.prefixlen and other.prefix == self.prefix
|
||||
|
||||
def __add__(self, other):
|
||||
"""
|
||||
Add a value to this ip prefix.
|
||||
|
||||
:param int other: value to add
|
||||
:return: added ip prefix instance
|
||||
:rtype: IpPrefix
|
||||
"""
|
||||
try:
|
||||
tmp = int(other)
|
||||
except ValueError:
|
||||
logger.exception("error during addition")
|
||||
return NotImplemented
|
||||
|
||||
a = IpAddress(self.af, self.prefix) + (tmp << (self.addrlen - self.prefixlen))
|
||||
prefixstr = "%s/%s" % (a, self.prefixlen)
|
||||
if self.__class__ == IpPrefix:
|
||||
return self.__class__(self.af, prefixstr)
|
||||
else:
|
||||
return self.__class__(prefixstr)
|
||||
|
||||
def __sub__(self, other):
|
||||
"""
|
||||
Subtract value from this ip prefix.
|
||||
|
||||
:param int other: value to subtract
|
||||
:return: subtracted ip prefix instance
|
||||
:rtype: IpPrefix
|
||||
"""
|
||||
try:
|
||||
tmp = -int(other)
|
||||
except ValueError:
|
||||
logger.exception("error during subtraction")
|
||||
return NotImplemented
|
||||
|
||||
return self.__add__(tmp)
|
||||
|
||||
def addr(self, hostid):
|
||||
"""
|
||||
Create an ip address for a given host id.
|
||||
|
||||
:param hostid: host id for an ip address
|
||||
:return: ip address
|
||||
:rtype: IpAddress
|
||||
"""
|
||||
tmp = int(hostid)
|
||||
if tmp in [-1, 0, 1] and self.addrlen == self.prefixlen:
|
||||
return IpAddress(self.af, self.prefix)
|
||||
|
||||
if tmp == 0 or tmp > (1 << (self.addrlen - self.prefixlen)) - 1 or (
|
||||
self.af == AF_INET and tmp == (1 << (self.addrlen - self.prefixlen)) - 1):
|
||||
raise ValueError("invalid hostid for prefix %s: %s" % (self, hostid))
|
||||
|
||||
addr = ""
|
||||
prefix_endpoint = -1
|
||||
for i in xrange(-1, -(self.addrlen >> 3) - 1, -1):
|
||||
prefix_endpoint = i
|
||||
addr = chr(ord(self.prefix[i]) | (tmp & 0xff)) + addr
|
||||
tmp >>= 8
|
||||
if not tmp:
|
||||
break
|
||||
addr = self.prefix[:prefix_endpoint] + addr
|
||||
return IpAddress(self.af, addr)
|
||||
|
||||
def min_addr(self):
|
||||
"""
|
||||
Return the minimum ip address for this prefix.
|
||||
|
||||
:return: minimum ip address
|
||||
:rtype: IpAddress
|
||||
"""
|
||||
return self.addr(1)
|
||||
|
||||
def max_addr(self):
|
||||
"""
|
||||
Return the maximum ip address for this prefix.
|
||||
|
||||
:return: maximum ip address
|
||||
:rtype: IpAddress
|
||||
"""
|
||||
if self.af == AF_INET:
|
||||
return self.addr((1 << (self.addrlen - self.prefixlen)) - 2)
|
||||
else:
|
||||
return self.addr((1 << (self.addrlen - self.prefixlen)) - 1)
|
||||
|
||||
def num_addr(self):
|
||||
"""
|
||||
Retrieve the number of ip addresses for this prefix.
|
||||
|
||||
:return: maximum number of ip addresses
|
||||
:rtype: int
|
||||
"""
|
||||
return max(0, (1 << (self.addrlen - self.prefixlen)) - 2)
|
||||
|
||||
def prefix_str(self):
|
||||
"""
|
||||
Retrieve the prefix string for this ip address.
|
||||
|
||||
:return: prefix string
|
||||
:rtype: str
|
||||
"""
|
||||
return "%s" % socket.inet_ntop(self.af, self.prefix)
|
||||
|
||||
def netmask_str(self):
|
||||
"""
|
||||
Retrieve the netmask string for this ip address.
|
||||
|
||||
:return: netmask string
|
||||
:rtype: str
|
||||
"""
|
||||
addrbits = self.addrlen - self.prefixlen
|
||||
netmask = ((1L << self.prefixlen) - 1) << addrbits
|
||||
netmaskbytes = struct.pack("!L", netmask)
|
||||
return IpAddress(af=AF_INET, address=netmaskbytes).__str__()
|
||||
|
||||
|
||||
class Ipv4Prefix(IpPrefix):
|
||||
"""
|
||||
Provides an ipv4 specific class for ip prefixes.
|
||||
"""
|
||||
|
||||
def __init__(self, prefixstr):
|
||||
"""
|
||||
Create a Ipv4Prefix instance.
|
||||
|
||||
:param str prefixstr: ip prefix
|
||||
"""
|
||||
IpPrefix.__init__(self, AF_INET, prefixstr)
|
||||
|
||||
|
||||
class Ipv6Prefix(IpPrefix):
|
||||
"""
|
||||
Provides an ipv6 specific class for ip prefixes.
|
||||
"""
|
||||
|
||||
def __init__(self, prefixstr):
|
||||
"""
|
||||
Create a Ipv6Prefix instance.
|
||||
|
||||
:param str prefixstr: ip prefix
|
||||
"""
|
||||
IpPrefix.__init__(self, AF_INET6, prefixstr)
|
||||
|
||||
|
||||
def is_ip_address(af, addrstr):
|
||||
"""
|
||||
Check if ip address string is a valid ip address.
|
||||
|
||||
:param int af: address family
|
||||
:param str addrstr: ip address string
|
||||
:return: True if a valid ip address, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
try:
|
||||
socket.inet_pton(af, addrstr)
|
||||
return True
|
||||
except IOError:
|
||||
return False
|
||||
|
||||
|
||||
def is_ipv4_address(addrstr):
|
||||
"""
|
||||
Check if ipv4 address string is a valid ipv4 address.
|
||||
|
||||
:param str addrstr: ipv4 address string
|
||||
:return: True if a valid ipv4 address, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
return is_ip_address(AF_INET, addrstr)
|
||||
|
||||
|
||||
def is_ipv6_address(addrstr):
|
||||
"""
|
||||
Check if ipv6 address string is a valid ipv6 address.
|
||||
|
||||
:param str addrstr: ipv6 address string
|
||||
:return: True if a valid ipv6 address, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
return is_ip_address(AF_INET6, addrstr)
|
35
daemon/core/misc/log.py
Normal file
35
daemon/core/misc/log.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
"""
|
||||
Convenience methods to setup logging.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
_LOG_LEVEL = logging.INFO
|
||||
_LOG_FORMAT = "%(levelname)-7s %(asctime)s %(name)-15s %(funcName)-15s %(lineno)-4d: %(message)s"
|
||||
_INITIAL = True
|
||||
|
||||
|
||||
def setup(level=_LOG_LEVEL, log_format=_LOG_FORMAT):
|
||||
"""
|
||||
Configure a logging with a basic configuration, output to console.
|
||||
|
||||
:param logging.LEVEL level: level for logger, defaults to module defined format
|
||||
:param int log_format: format for logger, default to DEBUG
|
||||
:return: nothing
|
||||
"""
|
||||
logging.basicConfig(level=level, format=log_format)
|
||||
|
||||
|
||||
def get_logger(name):
|
||||
"""
|
||||
Retrieve a logger for logging.
|
||||
|
||||
:param str name: name for logger to retrieve
|
||||
:return: logging.logger
|
||||
"""
|
||||
global _INITIAL
|
||||
if _INITIAL:
|
||||
setup()
|
||||
_INITIAL = False
|
||||
|
||||
return logging.getLogger(name)
|
50
daemon/core/misc/nodemaps.py
Normal file
50
daemon/core/misc/nodemaps.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
"""
|
||||
Provides default node maps that can be used to run core with.
|
||||
"""
|
||||
|
||||
from core.emane.nodes import EmaneNet
|
||||
from core.emane.nodes import EmaneNode
|
||||
from core.enumerations import NodeTypes
|
||||
from core.netns import nodes
|
||||
from core.netns import openvswitch
|
||||
from core.netns.vnet import GreTapBridge
|
||||
from core.phys import pnodes
|
||||
from core.xen import xen
|
||||
|
||||
# legacy core nodes, that leverage linux bridges
|
||||
CLASSIC_NODES = {
|
||||
NodeTypes.DEFAULT: nodes.CoreNode,
|
||||
NodeTypes.PHYSICAL: pnodes.PhysicalNode,
|
||||
NodeTypes.XEN: xen.XenNode,
|
||||
NodeTypes.TBD: None,
|
||||
NodeTypes.SWITCH: nodes.SwitchNode,
|
||||
NodeTypes.HUB: nodes.HubNode,
|
||||
NodeTypes.WIRELESS_LAN: nodes.WlanNode,
|
||||
NodeTypes.RJ45: nodes.RJ45Node,
|
||||
NodeTypes.TUNNEL: nodes.TunnelNode,
|
||||
NodeTypes.KTUNNEL: None,
|
||||
NodeTypes.EMANE: EmaneNode,
|
||||
NodeTypes.EMANE_NET: EmaneNet,
|
||||
NodeTypes.TAP_BRIDGE: GreTapBridge,
|
||||
NodeTypes.PEER_TO_PEER: nodes.PtpNet,
|
||||
NodeTypes.CONTROL_NET: nodes.CtrlNet
|
||||
}
|
||||
|
||||
# ovs nodes, that depend on ovs to leverage ovs based bridges
|
||||
OVS_NODES = {
|
||||
NodeTypes.DEFAULT: nodes.CoreNode,
|
||||
NodeTypes.PHYSICAL: pnodes.PhysicalNode,
|
||||
NodeTypes.XEN: xen.XenNode,
|
||||
NodeTypes.TBD: None,
|
||||
NodeTypes.SWITCH: openvswitch.OvsSwitchNode,
|
||||
NodeTypes.HUB: openvswitch.OvsHubNode,
|
||||
NodeTypes.WIRELESS_LAN: openvswitch.OvsWlanNode,
|
||||
NodeTypes.RJ45: nodes.RJ45Node,
|
||||
NodeTypes.TUNNEL: openvswitch.OvsTunnelNode,
|
||||
NodeTypes.KTUNNEL: None,
|
||||
NodeTypes.EMANE: EmaneNode,
|
||||
NodeTypes.EMANE_NET: EmaneNet,
|
||||
NodeTypes.TAP_BRIDGE: openvswitch.OvsGreTapBridge,
|
||||
NodeTypes.PEER_TO_PEER: openvswitch.OvsPtpNet,
|
||||
NodeTypes.CONTROL_NET: openvswitch.OvsCtrlNet
|
||||
}
|
68
daemon/core/misc/nodeutils.py
Normal file
68
daemon/core/misc/nodeutils.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
"""
|
||||
Serves as a global point for storing and retrieving node types needed during simulation.
|
||||
"""
|
||||
|
||||
import pprint
|
||||
|
||||
from core.misc import log
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
_NODE_MAP = None
|
||||
|
||||
|
||||
def _convert_map(x, y):
|
||||
"""
|
||||
Convenience method to create a human readable version of the node map to log.
|
||||
|
||||
:param dict x: dictionary to reduce node items into
|
||||
:param tuple y: current node item
|
||||
:return:
|
||||
"""
|
||||
x[y[0].name] = y[1]
|
||||
return x
|
||||
|
||||
|
||||
def set_node_map(node_map):
|
||||
"""
|
||||
Set the global node map that proides a consistent way to retrieve differently configured nodes.
|
||||
|
||||
:param dict node_map: node map to set to
|
||||
:return: nothing
|
||||
"""
|
||||
global _NODE_MAP
|
||||
print_map = reduce(lambda x, y: _convert_map(x, y), node_map.items(), {})
|
||||
logger.info("setting node class map: \n%s", pprint.pformat(print_map, indent=4))
|
||||
_NODE_MAP = node_map
|
||||
|
||||
|
||||
def get_node_class(node_type):
|
||||
"""
|
||||
Retrieve the node class for a given node type.
|
||||
|
||||
:param int node_type: node type to retrieve class for
|
||||
:return: node class
|
||||
"""
|
||||
global _NODE_MAP
|
||||
return _NODE_MAP[node_type]
|
||||
|
||||
|
||||
def is_node(obj, node_types):
|
||||
"""
|
||||
Validates if an object is one of the provided node types.
|
||||
|
||||
:param obj: object to check type for
|
||||
:param int|tuple|list node_types: node type(s) to check against
|
||||
:return: True if the object is one of the node types, False otherwise
|
||||
:rtype: bool
|
||||
"""
|
||||
type_classes = []
|
||||
if isinstance(node_types, (tuple, list)):
|
||||
for node_type in node_types:
|
||||
type_class = get_node_class(node_type)
|
||||
type_classes.append(type_class)
|
||||
else:
|
||||
type_class = get_node_class(node_types)
|
||||
type_classes.append(type_class)
|
||||
|
||||
return isinstance(obj, tuple(type_classes))
|
48
daemon/core/misc/structutils.py
Normal file
48
daemon/core/misc/structutils.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
"""
|
||||
Utilities for working with python struct data.
|
||||
"""
|
||||
|
||||
from core.misc import log
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
def pack_values(clazz, packers):
|
||||
"""
|
||||
Pack values for a given legacy class.
|
||||
|
||||
:param class clazz: class that will provide a pack method
|
||||
:param list packers: a list of tuples that are used to pack values and transform them
|
||||
:return: packed data string of all values
|
||||
"""
|
||||
|
||||
# iterate through tuples of values to pack
|
||||
data = ""
|
||||
for packer in packers:
|
||||
# check if a transformer was provided for valid values
|
||||
transformer = None
|
||||
if len(packer) == 2:
|
||||
tlv_type, value = packer
|
||||
elif len(packer) == 3:
|
||||
tlv_type, value, transformer = packer
|
||||
else:
|
||||
raise RuntimeError("packer had more than 3 arguments")
|
||||
|
||||
# convert unicode to normal str for packing
|
||||
if isinstance(value, unicode):
|
||||
value = str(value)
|
||||
|
||||
# only pack actual values and avoid packing empty strings
|
||||
# protobuf defaults to empty strings and does no imply a value to set
|
||||
if value is None or (isinstance(value, str) and not value):
|
||||
continue
|
||||
|
||||
# transform values as needed
|
||||
if transformer:
|
||||
value = transformer(value)
|
||||
|
||||
# pack and add to existing data
|
||||
logger.info("packing: %s - %s", tlv_type, value)
|
||||
data += clazz.pack(tlv_type.value, value)
|
||||
|
||||
return data
|
741
daemon/core/netns/openvswitch.py
Normal file
741
daemon/core/netns/openvswitch.py
Normal file
|
@ -0,0 +1,741 @@
|
|||
"""
|
||||
TODO: probably goes away, or implement the usage of "unshare", or docker formal.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import subprocess
|
||||
import threading
|
||||
from socket import AF_INET
|
||||
from socket import AF_INET6
|
||||
|
||||
from core import constants
|
||||
from core.coreobj import PyCoreNet
|
||||
from core.data import LinkData
|
||||
from core.enumerations import LinkTypes
|
||||
from core.enumerations import NodeTypes
|
||||
from core.enumerations import RegisterTlvs
|
||||
from core.misc import ipaddress
|
||||
from core.misc import log
|
||||
from core.misc import utils
|
||||
from core.netns.vif import GreTap
|
||||
from core.netns.vif import VEth
|
||||
from core.netns.vnet import EbtablesQueue
|
||||
from core.netns.vnet import GreTapBridge
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
# a global object because all WLANs share the same queue
|
||||
# cannot have multiple threads invoking the ebtables commnd
|
||||
ebtables_queue = EbtablesQueue()
|
||||
|
||||
ebtables_lock = threading.Lock()
|
||||
|
||||
utils.check_executables([
|
||||
constants.IP_BIN,
|
||||
constants.EBTABLES_BIN,
|
||||
constants.TC_BIN
|
||||
])
|
||||
|
||||
|
||||
def ebtables_commands(call, commands):
|
||||
ebtables_lock.acquire()
|
||||
try:
|
||||
for command in commands:
|
||||
call(command)
|
||||
finally:
|
||||
ebtables_lock.release()
|
||||
|
||||
|
||||
class OvsNet(PyCoreNet):
|
||||
"""
|
||||
Used to be LxBrNet.
|
||||
|
||||
Base class for providing Openvswitch functionality to objects that create bridges.
|
||||
"""
|
||||
|
||||
policy = "DROP"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True, policy=None):
|
||||
"""
|
||||
Creates an OvsNet instance.
|
||||
|
||||
:param core.session.Session session: session this object is a part of
|
||||
:param objid:
|
||||
:param name:
|
||||
:param start:
|
||||
:param policy:
|
||||
:return:
|
||||
"""
|
||||
|
||||
PyCoreNet.__init__(self, session, objid, name, start)
|
||||
|
||||
if policy:
|
||||
self.policy = policy
|
||||
else:
|
||||
self.policy = self.__class__.policy
|
||||
|
||||
session_id = self.session.short_session_id()
|
||||
self.bridge_name = "b.%s.%s" % (str(self.objid), session_id)
|
||||
self.up = False
|
||||
|
||||
if start:
|
||||
self.startup()
|
||||
ebtables_queue.startupdateloop(self)
|
||||
|
||||
def startup(self):
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "add-br", self.bridge_name])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error adding bridge")
|
||||
|
||||
try:
|
||||
# turn off spanning tree protocol and forwarding delay
|
||||
# TODO: appears stp and rstp are off by default, make sure this always holds true
|
||||
# TODO: apears ovs only supports rstp forward delay and again it's off by default
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.bridge_name, "up"])
|
||||
|
||||
# create a new ebtables chain for this bridge
|
||||
ebtables_commands(subprocess.check_call, [
|
||||
[constants.EBTABLES_BIN, "-N", self.bridge_name, "-P", self.policy],
|
||||
[constants.EBTABLES_BIN, "-A", "FORWARD", "--logical-in", self.bridge_name, "-j", self.bridge_name]
|
||||
])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error setting bridge parameters")
|
||||
|
||||
self.up = True
|
||||
|
||||
def shutdown(self):
|
||||
if not self.up:
|
||||
logger.info("exiting shutdown, object is not up")
|
||||
return
|
||||
|
||||
ebtables_queue.stopupdateloop(self)
|
||||
|
||||
utils.mutecall([constants.IP_BIN, "link", "set", self.bridge_name, "down"])
|
||||
utils.mutecall([constants.OVS_BIN, "del-br", self.bridge_name])
|
||||
|
||||
ebtables_commands(utils.mutecall, [
|
||||
[constants.EBTABLES_BIN, "-D", "FORWARD", "--logical-in", self.bridge_name, "-j", self.bridge_name],
|
||||
[constants.EBTABLES_BIN, "-X", self.bridge_name]
|
||||
])
|
||||
|
||||
for interface in self.netifs():
|
||||
# removes veth pairs used for bridge-to-bridge connections
|
||||
interface.shutdown()
|
||||
|
||||
self._netif.clear()
|
||||
self._linked.clear()
|
||||
del self.session
|
||||
self.up = False
|
||||
|
||||
def attach(self, interface):
|
||||
if self.up:
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "add-port", self.bridge_name, interface.localname])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", interface.localname, "up"])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error joining interface %s to bridge %s", interface.localname, self.bridge_name)
|
||||
return
|
||||
|
||||
PyCoreNet.attach(self, interface)
|
||||
|
||||
def detach(self, interface):
|
||||
if self.up:
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "del-port", self.bridge_name, interface.localname])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error removing interface %s from bridge %s", interface.localname, self.bridge_name)
|
||||
return
|
||||
|
||||
PyCoreNet.detach(self, interface)
|
||||
|
||||
def linked(self, interface_one, interface_two):
|
||||
# check if the network interfaces are attached to this network
|
||||
if self._netif[interface_one.netifi] != interface_one:
|
||||
raise ValueError("inconsistency for interface %s" % interface_one.name)
|
||||
|
||||
if self._netif[interface_two.netifi] != interface_two:
|
||||
raise ValueError("inconsistency for interface %s" % interface_two.name)
|
||||
|
||||
try:
|
||||
linked = self._linked[interface_one][interface_two]
|
||||
except KeyError:
|
||||
if self.policy == "ACCEPT":
|
||||
linked = True
|
||||
elif self.policy == "DROP":
|
||||
linked = False
|
||||
else:
|
||||
raise ValueError("unknown policy: %s" % self.policy)
|
||||
|
||||
self._linked[interface_one][interface_two] = linked
|
||||
|
||||
return linked
|
||||
|
||||
def unlink(self, interface_one, interface_two):
|
||||
"""
|
||||
Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
|
||||
filtering rules.
|
||||
"""
|
||||
with self._linked_lock:
|
||||
if not self.linked(interface_one, interface_two):
|
||||
return
|
||||
|
||||
self._linked[interface_one][interface_two] = False
|
||||
|
||||
ebtables_queue.ebchange(self)
|
||||
|
||||
def link(self, interface_one, interface_two):
|
||||
"""
|
||||
Link two PyCoreNetIfs together, resulting in adding or removing
|
||||
ebtables filtering rules.
|
||||
"""
|
||||
with self._linked_lock:
|
||||
if self.linked(interface_one, interface_two):
|
||||
return
|
||||
|
||||
self._linked[interface_one][interface_two] = True
|
||||
|
||||
ebtables_queue.ebchange(self)
|
||||
|
||||
def linkconfig(self, interface, bw=None, delay=None, loss=None, duplicate=None,
|
||||
jitter=None, netif2=None, devname=None):
|
||||
"""
|
||||
Configure link parameters by applying tc queuing disciplines on the
|
||||
interface.
|
||||
"""
|
||||
if not devname:
|
||||
devname = interface.localname
|
||||
|
||||
tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname]
|
||||
parent = ["root"]
|
||||
|
||||
# attempt to set bandwidth and update as needed if value changed
|
||||
bandwidth_changed = interface.setparam("bw", bw)
|
||||
if bandwidth_changed:
|
||||
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
|
||||
if bw > 0:
|
||||
if self.up:
|
||||
burst = max(2 * interface.mtu, bw / 1000)
|
||||
limit = 0xffff # max IP payload
|
||||
tbf = ["tbf", "rate", str(bw), "burst", str(burst), "limit", str(limit)]
|
||||
logger.info("linkconfig: %s" % [tc + parent + ["handle", "1:"] + tbf])
|
||||
subprocess.check_call(tc + parent + ["handle", "1:"] + tbf)
|
||||
interface.setparam("has_tbf", True)
|
||||
elif interface.getparam("has_tbf") and bw <= 0:
|
||||
tcd = [] + tc
|
||||
tcd[2] = "delete"
|
||||
|
||||
if self.up:
|
||||
subprocess.check_call(tcd + parent)
|
||||
|
||||
interface.setparam("has_tbf", False)
|
||||
# removing the parent removes the child
|
||||
interface.setparam("has_netem", False)
|
||||
|
||||
if interface.getparam("has_tbf"):
|
||||
parent = ["parent", "1:1"]
|
||||
|
||||
netem = ["netem"]
|
||||
delay_changed = interface.setparam("delay", delay)
|
||||
|
||||
if loss is not None:
|
||||
loss = float(loss)
|
||||
loss_changed = interface.setparam("loss", loss)
|
||||
|
||||
if duplicate is not None:
|
||||
duplicate = float(duplicate)
|
||||
duplicate_changed = interface.setparam("duplicate", duplicate)
|
||||
jitter_changed = interface.setparam("jitter", jitter)
|
||||
|
||||
# if nothing changed return
|
||||
if not any([bandwidth_changed, delay_changed, loss_changed, duplicate_changed, jitter_changed]):
|
||||
return
|
||||
|
||||
# jitter and delay use the same delay statement
|
||||
if delay is not None:
|
||||
netem += ["delay", "%sus" % delay]
|
||||
else:
|
||||
netem += ["delay", "0us"]
|
||||
|
||||
if jitter is not None:
|
||||
netem += ["%sus" % jitter, "25%"]
|
||||
|
||||
if loss is not None:
|
||||
netem += ["loss", "%s%%" % min(loss, 100)]
|
||||
|
||||
if duplicate is not None:
|
||||
netem += ["duplicate", "%s%%" % min(duplicate, 100)]
|
||||
|
||||
if delay <= 0 and jitter <= 0 and loss <= 0 and duplicate <= 0:
|
||||
# possibly remove netem if it exists and parent queue wasn"t removed
|
||||
if not interface.getparam("has_netem"):
|
||||
return
|
||||
|
||||
tc[2] = "delete"
|
||||
|
||||
if self.up:
|
||||
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"]],))
|
||||
subprocess.check_call(tc + parent + ["handle", "10:"])
|
||||
interface.setparam("has_netem", False)
|
||||
elif len(netem) > 1:
|
||||
if self.up:
|
||||
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem],))
|
||||
subprocess.check_call(tc + parent + ["handle", "10:"] + netem)
|
||||
interface.setparam("has_netem", True)
|
||||
|
||||
def linknet(self, network):
|
||||
"""
|
||||
Link this bridge with another by creating a veth pair and installing
|
||||
each device into each bridge.
|
||||
"""
|
||||
session_id = self.session.short_session_id()
|
||||
|
||||
try:
|
||||
self_objid = "%x" % self.objid
|
||||
except TypeError:
|
||||
self_objid = "%s" % self.objid
|
||||
|
||||
try:
|
||||
net_objid = "%x" % network.objid
|
||||
except TypeError:
|
||||
net_objid = "%s" % network.objid
|
||||
|
||||
localname = "veth%s.%s.%s" % (self_objid, net_objid, session_id)
|
||||
|
||||
if len(localname) >= 16:
|
||||
raise ValueError("interface local name %s too long" % localname)
|
||||
|
||||
name = "veth%s.%s.%s" % (net_objid, self_objid, session_id)
|
||||
if len(name) >= 16:
|
||||
raise ValueError("interface name %s too long" % name)
|
||||
|
||||
interface = VEth(node=None, name=name, localname=localname, mtu=1500, net=self, start=self.up)
|
||||
self.attach(interface)
|
||||
if network.up:
|
||||
# this is similar to net.attach() but uses netif.name instead
|
||||
# of localname
|
||||
subprocess.check_call([constants.OVS_BIN, "add-port", network.brname, interface.name])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", interface.name, "up"])
|
||||
|
||||
# TODO: is there a native method for this? see if this causes issues
|
||||
# i = network.newifindex()
|
||||
# network._netif[i] = interface
|
||||
# with network._linked_lock:
|
||||
# network._linked[interface] = {}
|
||||
# this method call is equal to the above, with a interface.netifi = call
|
||||
network.attach(interface)
|
||||
|
||||
interface.net = self
|
||||
interface.othernet = network
|
||||
return interface
|
||||
|
||||
def getlinknetif(self, network):
|
||||
"""
|
||||
Return the interface of that links this net with another net
|
||||
(that were linked using linknet()).
|
||||
"""
|
||||
for interface in self.netifs():
|
||||
if hasattr(interface, "othernet") and interface.othernet == network:
|
||||
return interface
|
||||
|
||||
return None
|
||||
|
||||
def addrconfig(self, addresses):
|
||||
"""
|
||||
Set addresses on the bridge.
|
||||
"""
|
||||
if not self.up:
|
||||
return
|
||||
|
||||
for address in addresses:
|
||||
try:
|
||||
subprocess.check_call([constants.IP_BIN, "addr", "add", str(address), "dev", self.bridge_name])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error adding IP address")
|
||||
|
||||
|
||||
class OvsCtrlNet(OvsNet):
|
||||
policy = "ACCEPT"
|
||||
CTRLIF_IDX_BASE = 99 # base control interface index
|
||||
DEFAULT_PREFIX_LIST = [
|
||||
"172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
|
||||
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
|
||||
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
|
||||
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24"
|
||||
]
|
||||
|
||||
def __init__(self, session, objid="ctrlnet", name=None, prefix=None, hostid=None,
|
||||
start=True, assign_address=True, updown_script=None, serverintf=None):
|
||||
OvsNet.__init__(self, session, objid=objid, name=name, start=start)
|
||||
self.prefix = ipaddress.Ipv4Prefix(prefix)
|
||||
self.hostid = hostid
|
||||
self.assign_address = assign_address
|
||||
self.updown_script = updown_script
|
||||
self.serverintf = serverintf
|
||||
|
||||
def startup(self):
|
||||
if self.detectoldbridge():
|
||||
return
|
||||
|
||||
OvsNet.startup(self)
|
||||
if self.hostid:
|
||||
addr = self.prefix.addr(self.hostid)
|
||||
else:
|
||||
addr = self.prefix.max_addr()
|
||||
|
||||
message = "Added control network bridge: %s %s" % (self.bridge_name, self.prefix)
|
||||
addresses = ["%s/%s" % (addr, self.prefix.prefixlen)]
|
||||
if self.assign_address:
|
||||
self.addrconfig(addresses=addresses)
|
||||
message += " address %s" % addr
|
||||
logger.info(message)
|
||||
|
||||
if self.updown_script:
|
||||
logger.info("interface %s updown script %s startup called" % (self.bridge_name, self.updown_script))
|
||||
subprocess.check_call([self.updown_script, self.bridge_name, "startup"])
|
||||
|
||||
if self.serverintf:
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "add-port", self.bridge_name, self.serverintf])
|
||||
subprocess.check_call([constants.IP_BIN, "link", "set", self.serverintf, "up"])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("error joining server interface %s to controlnet bridge %s",
|
||||
self.serverintf, self.bridge_name)
|
||||
|
||||
def detectoldbridge(self):
|
||||
"""
|
||||
Occassionally, control net bridges from previously closed sessions are not cleaned up.
|
||||
Check if there are old control net bridges and delete them
|
||||
"""
|
||||
|
||||
status, output = utils.cmdresult([constants.OVS_BIN, "list-br"])
|
||||
output = output.strip()
|
||||
if output:
|
||||
for line in output.split("\n"):
|
||||
bride_name = line.split(".")
|
||||
if bride_name[0] == "b" and bride_name[1] == self.objid:
|
||||
logger.error("older session may still be running with conflicting id for bridge: %s", line)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def shutdown(self):
|
||||
if self.serverintf:
|
||||
try:
|
||||
subprocess.check_call([constants.OVS_BIN, "del-port", self.bridge_name, self.serverintf])
|
||||
except subprocess.CalledProcessError:
|
||||
logger.exception("Error deleting server interface %s to controlnet bridge %s",
|
||||
self.serverintf, self.bridge_name)
|
||||
|
||||
if self.updown_script:
|
||||
logger.info("interface %s updown script '%s shutdown' called", self.bridge_name, self.updown_script)
|
||||
subprocess.check_call([self.updown_script, self.bridge_name, "shutdown"])
|
||||
|
||||
OvsNet.shutdown(self)
|
||||
|
||||
def all_link_data(self, flags):
|
||||
"""
|
||||
Do not include CtrlNet in link messages describing this session.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class OvsPtpNet(OvsNet):
|
||||
policy = "ACCEPT"
|
||||
|
||||
def attach(self, interface):
|
||||
if len(self._netif) >= 2:
|
||||
raise ValueError("point-to-point links support at most 2 network interfaces")
|
||||
OvsNet.attach(self, interface)
|
||||
|
||||
def data(self, message_type):
|
||||
"""
|
||||
Do not generate a Node Message for point-to-point links. They are
|
||||
built using a link message instead.
|
||||
"""
|
||||
pass
|
||||
|
||||
def all_link_data(self, flags):
|
||||
"""
|
||||
Build CORE API TLVs for a point-to-point link. One Link message describes this network.
|
||||
"""
|
||||
|
||||
all_links = []
|
||||
|
||||
if len(self._netif) != 2:
|
||||
return all_links
|
||||
|
||||
if1, if2 = self._netif.items()
|
||||
if1 = if1[1]
|
||||
if2 = if2[1]
|
||||
|
||||
unidirectional = 0
|
||||
if if1.getparams() != if2.getparams():
|
||||
unidirectional = 1
|
||||
|
||||
interface1_ip4 = None
|
||||
interface1_ip4_mask = None
|
||||
interface1_ip6 = None
|
||||
interface1_ip6_mask = None
|
||||
for address in if1.addrlist:
|
||||
ip, sep, mask = address.partition('/')
|
||||
mask = int(mask)
|
||||
if ipaddress.is_ipv4_address(ip):
|
||||
family = AF_INET
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface1_ip4_mask = mask
|
||||
else:
|
||||
family = AF_INET6
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface1_ip6_mask = mask
|
||||
|
||||
interface2_ip4 = None
|
||||
interface2_ip4_mask = None
|
||||
interface2_ip6 = None
|
||||
interface2_ip6_mask = None
|
||||
for address in if2.addrlist:
|
||||
ip, sep, mask = address.partition('/')
|
||||
mask = int(mask)
|
||||
if ipaddress.is_ipv4_address(ip):
|
||||
family = AF_INET
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface2_ip4_mask = mask
|
||||
else:
|
||||
family = AF_INET6
|
||||
ipl = socket.inet_pton(family, ip)
|
||||
interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
|
||||
interface2_ip6_mask = mask
|
||||
|
||||
# TODO: not currently used
|
||||
# loss=netif.getparam('loss')
|
||||
link_data = LinkData(
|
||||
message_type=flags,
|
||||
node1_id=if1.node.objid,
|
||||
node2_id=if2.node.objid,
|
||||
link_type=self.linktype,
|
||||
unidirectional=unidirectional,
|
||||
delay=if1.getparam("delay"),
|
||||
bandwidth=if1.getparam("bw"),
|
||||
dup=if1.getparam("duplicate"),
|
||||
jitter=if1.getparam("jitter"),
|
||||
interface1_id=if1.node.getifindex(if1),
|
||||
interface1_mac=if1.hwaddr,
|
||||
interface1_ip4=interface1_ip4,
|
||||
interface1_ip4_mask=interface1_ip4_mask,
|
||||
interface1_ip6=interface1_ip6,
|
||||
interface1_ip6_mask=interface1_ip6_mask,
|
||||
interface2_id=if2.node.getifindex(if2),
|
||||
interface2_mac=if2.hwaddr,
|
||||
interface2_ip4=interface2_ip4,
|
||||
interface2_ip4_mask=interface2_ip4_mask,
|
||||
interface2_ip6=interface2_ip6,
|
||||
interface2_ip6_mask=interface2_ip6_mask,
|
||||
)
|
||||
|
||||
all_links.append(link_data)
|
||||
|
||||
# build a 2nd link message for the upstream link parameters
|
||||
# (swap if1 and if2)
|
||||
if unidirectional:
|
||||
link_data = LinkData(
|
||||
message_type=0,
|
||||
node1_id=if2.node.objid,
|
||||
node2_id=if1.node.objid,
|
||||
delay=if1.getparam("delay"),
|
||||
bandwidth=if1.getparam("bw"),
|
||||
dup=if1.getparam("duplicate"),
|
||||
jitter=if1.getparam("jitter"),
|
||||
unidirectional=1,
|
||||
interface1_id=if2.node.getifindex(if2),
|
||||
interface2_id=if1.node.getifindex(if1)
|
||||
)
|
||||
all_links.append(link_data)
|
||||
|
||||
return all_links
|
||||
|
||||
|
||||
class OvsSwitchNode(OvsNet):
|
||||
apitype = NodeTypes.SWITCH.value
|
||||
policy = "ACCEPT"
|
||||
type = "lanswitch"
|
||||
|
||||
|
||||
class OvsHubNode(OvsNet):
|
||||
apitype = NodeTypes.HUB.value
|
||||
policy = "ACCEPT"
|
||||
type = "hub"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True):
|
||||
"""
|
||||
the Hub node forwards packets to all bridge ports by turning off
|
||||
the MAC address learning
|
||||
"""
|
||||
OvsNet.__init__(self, session, objid, name, start)
|
||||
|
||||
if start:
|
||||
# TODO: verify that the below flow accomplishes what is desired for a "HUB"
|
||||
# TODO: replace "brctl setageing 0"
|
||||
subprocess.check_call([constants.OVS_FLOW_BIN, "add-flow", self.bridge_name, "action=flood"])
|
||||
|
||||
|
||||
class OvsWlanNode(OvsNet):
|
||||
apitype = NodeTypes.WIRELESS_LAN.value
|
||||
linktype = LinkTypes.WIRELESS.value
|
||||
policy = "DROP"
|
||||
type = "wlan"
|
||||
|
||||
def __init__(self, session, objid=None, name=None, start=True, policy=None):
|
||||
OvsNet.__init__(self, session, objid, name, start, policy)
|
||||
|
||||
# wireless model such as basic range
|
||||
self.model = None
|
||||
# mobility model such as scripted
|
||||
self.mobility = None
|
||||
|
||||
def attach(self, interface):
|
||||
OvsNet.attach(self, interface)
|
||||
|
||||
if self.model:
|
||||
interface.poshook = self.model.position_callback
|
||||
|
||||
if interface.node is None:
|
||||
return
|
||||
|
||||
x, y, z = interface.node.position.get()
|
||||
# invokes any netif.poshook
|
||||
interface.setposition(x, y, z)
|
||||
# self.model.setlinkparams()
|
||||
|
||||
def setmodel(self, model, config):
|
||||
"""
|
||||
Mobility and wireless model.
|
||||
"""
|
||||
logger.info("adding model %s", model.name)
|
||||
|
||||
if model.type == RegisterTlvs.WIRELESS.value:
|
||||
self.model = model(session=self.session, object_id=self.objid, values=config)
|
||||
if self.model.position_callback:
|
||||
for interface in self.netifs():
|
||||
interface.poshook = self.model.position_callback
|
||||
if interface.node is not None:
|
||||
x, y, z = interface.node.position.get()
|
||||
interface.poshook(interface, x, y, z)
|
||||
self.model.setlinkparams()
|
||||
elif model.type == RegisterTlvs.MOBILITY.value:
|
||||
self.mobility = model(session=self.session, object_id=self.objid, values=config)
|
||||
|
||||
def updatemodel(self, model_name, values):
|
||||
"""
|
||||
Allow for model updates during runtime (similar to setmodel().)
|
||||
"""
|
||||
logger.info("updating model %s", model_name)
|
||||
if self.model is None or self.model.name != model_name:
|
||||
logger.info(
|
||||
"failure to update model, model doesn't exist or invalid name: model(%s) - name(%s)",
|
||||
self.model, model_name
|
||||
)
|
||||
return
|
||||
|
||||
model = self.model
|
||||
if model.type == RegisterTlvs.WIRELESS.value:
|
||||
if not model.updateconfig(values):
|
||||
return
|
||||
if self.model.position_callback:
|
||||
for interface in self.netifs():
|
||||
interface.poshook = self.model.position_callback
|
||||
if interface.node is not None:
|
||||
x, y, z = interface.node.position.get()
|
||||
interface.poshook(interface, x, y, z)
|
||||
self.model.setlinkparams()
|
||||
|
||||
def all_link_data(self, flags):
|
||||
all_links = OvsNet.all_link_data(self, flags)
|
||||
|
||||
if self.model:
|
||||
all_links.extend(self.model.all_link_data(flags))
|
||||
|
||||
return all_links
|
||||
|
||||
|
||||
class OvsTunnelNode(GreTapBridge):
|
||||
apitype = NodeTypes.TUNNEL.value
|
||||
policy = "ACCEPT"
|
||||
type = "tunnel"
|
||||
|
||||
|
||||
class OvsGreTapBridge(OvsNet):
|
||||
"""
|
||||
A network consisting of a bridge with a gretap device for tunneling to
|
||||
another system.
|
||||
"""
|
||||
|
||||
def __init__(self, session, remoteip=None, objid=None, name=None, policy="ACCEPT",
|
||||
localip=None, ttl=255, key=None, start=True):
|
||||
OvsNet.__init__(self, session=session, objid=objid, name=name, policy=policy, start=False)
|
||||
self.grekey = key
|
||||
if self.grekey is None:
|
||||
self.grekey = self.session.session_id ^ self.objid
|
||||
|
||||
self.localnum = None
|
||||
self.remotenum = None
|
||||
self.remoteip = remoteip
|
||||
self.localip = localip
|
||||
self.ttl = ttl
|
||||
|
||||
if remoteip is None:
|
||||
self.gretap = None
|
||||
else:
|
||||
self.gretap = GreTap(node=self, name=None, session=session, remoteip=remoteip,
|
||||
objid=None, localip=localip, ttl=ttl, key=self.grekey)
|
||||
if start:
|
||||
self.startup()
|
||||
|
||||
def startup(self):
|
||||
"""
|
||||
Creates a bridge and adds the gretap device to it.
|
||||
"""
|
||||
OvsNet.startup(self)
|
||||
|
||||
if self.gretap:
|
||||
self.attach(self.gretap)
|
||||
|
||||
def shutdown(self):
|
||||
"""
|
||||
Detach the gretap device and remove the bridge.
|
||||
"""
|
||||
if self.gretap:
|
||||
self.detach(self.gretap)
|
||||
self.gretap.shutdown()
|
||||
self.gretap = None
|
||||
|
||||
OvsNet.shutdown(self)
|
||||
|
||||
def addrconfig(self, addresses):
|
||||
"""
|
||||
Set the remote tunnel endpoint. This is a one-time method for
|
||||
creating the GreTap device, which requires the remoteip at startup.
|
||||
The 1st address in the provided list is remoteip, 2nd optionally
|
||||
specifies localip.
|
||||
"""
|
||||
if self.gretap:
|
||||
raise ValueError("gretap already exists for %s" % self.name)
|
||||
|
||||
remoteip = addresses[0].split('/')[0]
|
||||
localip = None
|
||||
|
||||
if len(addresses) > 1:
|
||||
localip = addresses[1].split('/')[0]
|
||||
|
||||
self.gretap = GreTap(session=self.session, remoteip=remoteip, objid=None, name=None,
|
||||
localip=localip, ttl=self.ttl, key=self.grekey)
|
||||
self.attach(self.gretap)
|
||||
|
||||
def setkey(self, key):
|
||||
"""
|
||||
Set the GRE key used for the GreTap device. This needs to be set
|
||||
prior to instantiating the GreTap device (before addrconfig).
|
||||
"""
|
||||
self.grekey = key
|
0
daemon/core/xml/__init__.py
Normal file
0
daemon/core/xml/__init__.py
Normal file
207
daemon/core/xml/xmldeployment.py
Normal file
207
daemon/core/xml/xmldeployment.py
Normal file
|
@ -0,0 +1,207 @@
|
|||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
|
||||
from core import constants
|
||||
from core import emane
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import ipaddress
|
||||
from core.misc import log
|
||||
from core.misc import nodeutils
|
||||
from core.netns import nodes
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class CoreDeploymentWriter(object):
|
||||
def __init__(self, dom, root, session):
|
||||
self.dom = dom
|
||||
self.root = root
|
||||
self.session = session
|
||||
self.hostname = socket.gethostname()
|
||||
if emane.VERSION < emane.EMANE092:
|
||||
self.transport = None
|
||||
self.platform = None
|
||||
|
||||
@staticmethod
|
||||
def get_ipv4_addresses(hostname):
|
||||
if hostname == 'localhost':
|
||||
addr_list = []
|
||||
cmd = (constants.IP_BIN, '-o', '-f', 'inet', 'addr', 'show')
|
||||
output = subprocess.check_output(cmd)
|
||||
for line in output.split(os.linesep):
|
||||
split = line.split()
|
||||
if not split:
|
||||
continue
|
||||
addr = split[3]
|
||||
if not addr.startswith('127.'):
|
||||
addr_list.append(addr)
|
||||
return addr_list
|
||||
else:
|
||||
# TODO: handle other hosts
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def get_interface_names(hostname):
|
||||
"""
|
||||
Uses same methodology of get_ipv4_addresses() to get
|
||||
parallel list of interface names to go with ...
|
||||
"""
|
||||
if hostname == 'localhost':
|
||||
iface_list = []
|
||||
cmd = (constants.IP_BIN, '-o', '-f', 'inet', 'addr', 'show')
|
||||
output = subprocess.check_output(cmd)
|
||||
for line in output.split(os.linesep):
|
||||
split = line.split()
|
||||
if not split:
|
||||
continue
|
||||
interface_name = split[1]
|
||||
addr = split[3]
|
||||
if not addr.startswith('127.'):
|
||||
iface_list.append(interface_name)
|
||||
return iface_list
|
||||
else:
|
||||
# TODO: handle other hosts
|
||||
raise NotImplementedError
|
||||
|
||||
@staticmethod
|
||||
def find_device(scenario, name):
|
||||
tag_name = ('device', 'host', 'router')
|
||||
for d in xmlutils.iter_descendants_with_attribute(scenario, tag_name, 'name', name):
|
||||
return d
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def find_interface(device, name):
|
||||
for i in xmlutils.iter_descendants_with_attribute(device, 'interface', 'name', name):
|
||||
return i
|
||||
return None
|
||||
|
||||
def add_deployment(self):
|
||||
testbed = self.dom.createElement('container')
|
||||
testbed.setAttribute('name', 'TestBed')
|
||||
testbed.setAttribute('id', 'TestBed')
|
||||
self.root.base_element.appendChild(testbed)
|
||||
nodelist = []
|
||||
for obj in self.session.objects.itervalues():
|
||||
if isinstance(obj, nodes.PyCoreNode):
|
||||
nodelist.append(obj)
|
||||
name = self.hostname
|
||||
ipv4_addresses = self.get_ipv4_addresses('localhost')
|
||||
iface_names = self.get_interface_names('localhost')
|
||||
testhost = self.add_physical_host(testbed, name, ipv4_addresses, iface_names)
|
||||
for n in nodelist:
|
||||
self.add_virtual_host(testhost, n)
|
||||
# TODO: handle other servers
|
||||
# servers = self.session.broker.getservernames()
|
||||
# servers.remove('localhost')
|
||||
|
||||
def add_child_element(self, parent, tag_name):
|
||||
el = self.dom.createElement(tag_name)
|
||||
parent.appendChild(el)
|
||||
return el
|
||||
|
||||
def add_child_element_with_nameattr(self, parent, tag_name, name, setid=True):
|
||||
el = self.add_child_element(parent, tag_name)
|
||||
el.setAttribute('name', name)
|
||||
if setid:
|
||||
el.setAttribute('id', '%s/%s' % (parent.getAttribute('id'), name))
|
||||
return el
|
||||
|
||||
def add_address(self, parent, address_type, address_str, address_iface=None):
|
||||
el = self.add_child_element(parent, 'address')
|
||||
el.setAttribute('type', address_type)
|
||||
if address_iface is not None:
|
||||
el.setAttribute('iface', address_iface)
|
||||
el.appendChild(self.dom.createTextNode(address_str))
|
||||
return el
|
||||
|
||||
def add_type(self, parent, type_str):
|
||||
el = self.add_child_element(parent, 'type')
|
||||
el.appendChild(self.dom.createTextNode(type_str))
|
||||
return el
|
||||
|
||||
def add_platform(self, parent, name):
|
||||
el = self.add_child_element_with_nameattr(parent, 'emanePlatform', name)
|
||||
return el
|
||||
|
||||
def add_transport(self, parent, name):
|
||||
el = self.add_child_element_with_nameattr(parent, 'transport', name)
|
||||
return el
|
||||
|
||||
def add_nem(self, parent, name):
|
||||
el = self.add_child_element_with_nameattr(parent, 'nem', name)
|
||||
return el
|
||||
|
||||
def add_parameter(self, parent, name, val):
|
||||
el = self.add_child_element_with_nameattr(parent, 'parameter', name, False)
|
||||
el.appendChild(self.dom.createTextNode(val))
|
||||
return el
|
||||
|
||||
def add_mapping(self, parent, maptype, mapref):
|
||||
el = self.add_child_element(parent, 'mapping')
|
||||
el.setAttribute('type', maptype)
|
||||
el.setAttribute('ref', mapref)
|
||||
return el
|
||||
|
||||
def add_host(self, parent, name):
|
||||
el = self.add_child_element_with_nameattr(parent, 'testHost', name)
|
||||
return el
|
||||
|
||||
def add_physical_host(self, parent, name, ipv4_addresses, iface_names):
|
||||
el = self.add_host(parent, name)
|
||||
self.add_type(el, 'physical')
|
||||
for i in range(0, len(ipv4_addresses)):
|
||||
addr = ipv4_addresses[i]
|
||||
if iface_names:
|
||||
interface_name = iface_names[i]
|
||||
else:
|
||||
interface_name = None
|
||||
self.add_address(el, 'IPv4', addr, interface_name)
|
||||
return el
|
||||
|
||||
def add_virtual_host(self, parent, obj):
|
||||
assert isinstance(obj, nodes.PyCoreNode)
|
||||
el = self.add_host(parent, obj.name)
|
||||
device = self.find_device(self.root.base_element, obj.name)
|
||||
if device is None:
|
||||
logger.warn('corresponding XML device not found for %s' % obj.name)
|
||||
return
|
||||
self.add_mapping(device, 'testHost', el.getAttribute('id'))
|
||||
self.add_type(el, 'virtual')
|
||||
for netif in obj.netifs():
|
||||
for address in netif.addrlist:
|
||||
addr, slash, prefixlen = address.partition('/')
|
||||
if ipaddress.is_ipv4_address(addr):
|
||||
addr_type = 'IPv4'
|
||||
elif ipaddress.is_ipv6_address(addr):
|
||||
addr_type = 'IPv6'
|
||||
else:
|
||||
raise NotImplementedError
|
||||
self.add_address(el, addr_type, address, netif.name)
|
||||
if nodeutils.is_node(netif.net, NodeTypes.EMANE):
|
||||
nem = self.add_emane_interface(parent, el, netif)
|
||||
interface = self.find_interface(device, netif.name)
|
||||
self.add_mapping(interface, 'nem', nem.getAttribute('id'))
|
||||
return el
|
||||
|
||||
def add_emane_interface(self, physical_host, virtual_host, netif, platform_name='p1', transport_name='t1'):
|
||||
nemid = netif.net.nemidmap[netif]
|
||||
if emane.VERSION < emane.EMANE092:
|
||||
if self.platform is None:
|
||||
self.platform = \
|
||||
self.add_platform(physical_host, name=platform_name)
|
||||
platform = self.platform
|
||||
if self.transport is None:
|
||||
self.transport = \
|
||||
self.add_transport(physical_host, name=transport_name)
|
||||
transport = self.transport
|
||||
else:
|
||||
platform = self.add_platform(virtual_host, name=platform_name)
|
||||
transport = self.add_transport(virtual_host, name=transport_name)
|
||||
nem_name = 'nem%s' % nemid
|
||||
nem = self.add_nem(platform, nem_name)
|
||||
self.add_parameter(nem, 'nemid', str(nemid))
|
||||
self.add_mapping(transport, 'nem', nem.getAttribute('id'))
|
||||
return nem
|
46
daemon/core/xml/xmlparser.py
Normal file
46
daemon/core/xml/xmlparser.py
Normal file
|
@ -0,0 +1,46 @@
|
|||
from xml.dom.minidom import parse
|
||||
|
||||
from core.xml.xmlparser0 import CoreDocumentParser0
|
||||
from core.xml.xmlparser1 import CoreDocumentParser1
|
||||
from core.xml.xmlutils import get_first_child_by_tag_name
|
||||
|
||||
|
||||
class CoreVersionParser(object):
|
||||
"""
|
||||
Helper class to check the version of Network Plan document. This
|
||||
simply looks for a "Scenario" element; when present, this
|
||||
indicates a 0.0 version document. The dom member is set in order
|
||||
to prevent parsing a file twice (it can be passed to the
|
||||
appropriate CoreDocumentParser class.)
|
||||
"""
|
||||
|
||||
DEFAULT_SCENARIO_VERSION = '1.0'
|
||||
|
||||
def __init__(self, filename, options):
|
||||
if 'dom' in options:
|
||||
self.dom = options['dom']
|
||||
else:
|
||||
self.dom = parse(filename)
|
||||
scenario = get_first_child_by_tag_name(self.dom, 'scenario')
|
||||
if scenario:
|
||||
version = scenario.getAttribute('version')
|
||||
if not version:
|
||||
version = self.DEFAULT_SCENARIO_VERSION
|
||||
self.version = version
|
||||
elif get_first_child_by_tag_name(self.dom, 'Scenario'):
|
||||
self.version = '0.0'
|
||||
else:
|
||||
self.version = 'unknown'
|
||||
|
||||
|
||||
def core_document_parser(session, filename, options):
|
||||
vp = CoreVersionParser(filename, options)
|
||||
if 'dom' not in options:
|
||||
options['dom'] = vp.dom
|
||||
if vp.version == '0.0':
|
||||
doc = CoreDocumentParser0(session, filename, options)
|
||||
elif vp.version == '1.0':
|
||||
doc = CoreDocumentParser1(session, filename, options)
|
||||
else:
|
||||
raise ValueError, 'unsupported document version: %s' % vp.version
|
||||
return doc
|
410
daemon/core/xml/xmlparser0.py
Normal file
410
daemon/core/xml/xmlparser0.py
Normal file
|
@ -0,0 +1,410 @@
|
|||
from xml.dom.minidom import parse
|
||||
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import log
|
||||
from core.misc import nodeutils
|
||||
from core.service import ServiceManager
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class CoreDocumentParser0(object):
|
||||
def __init__(self, session, filename, options):
|
||||
self.session = session
|
||||
self.filename = filename
|
||||
if 'dom' in options:
|
||||
# this prevents parsing twice when detecting file versions
|
||||
self.dom = options['dom']
|
||||
else:
|
||||
self.dom = parse(filename)
|
||||
self.start = options['start']
|
||||
self.nodecls = options['nodecls']
|
||||
|
||||
self.np = xmlutils.get_one_element(self.dom, "NetworkPlan")
|
||||
if self.np is None:
|
||||
raise ValueError, "missing NetworkPlan!"
|
||||
self.mp = xmlutils.get_one_element(self.dom, "MotionPlan")
|
||||
self.sp = xmlutils.get_one_element(self.dom, "ServicePlan")
|
||||
self.meta = xmlutils.get_one_element(self.dom, "CoreMetaData")
|
||||
|
||||
self.coords = self.getmotiondict(self.mp)
|
||||
# link parameters parsed in parsenets(), applied in parsenodes()
|
||||
self.linkparams = {}
|
||||
|
||||
self.parsedefaultservices()
|
||||
self.parseorigin()
|
||||
self.parsenets()
|
||||
self.parsenodes()
|
||||
self.parseservices()
|
||||
self.parsemeta()
|
||||
|
||||
def getmotiondict(self, mp):
|
||||
"""
|
||||
Parse a MotionPlan into a dict with node names for keys and coordinates
|
||||
for values.
|
||||
"""
|
||||
if mp is None:
|
||||
return {}
|
||||
coords = {}
|
||||
for node in mp.getElementsByTagName("Node"):
|
||||
nodename = str(node.getAttribute("name"))
|
||||
if nodename == '':
|
||||
continue
|
||||
for m in node.getElementsByTagName("motion"):
|
||||
if m.getAttribute("type") != "stationary":
|
||||
continue
|
||||
point = m.getElementsByTagName("point")
|
||||
if len(point) == 0:
|
||||
continue
|
||||
txt = point[0].firstChild
|
||||
if txt is None:
|
||||
continue
|
||||
xyz = map(int, txt.nodeValue.split(','))
|
||||
z = None
|
||||
x, y = xyz[0:2]
|
||||
if len(xyz) == 3:
|
||||
z = xyz[2]
|
||||
coords[nodename] = (x, y, z)
|
||||
return coords
|
||||
|
||||
@staticmethod
|
||||
def getcommonattributes(obj):
|
||||
"""
|
||||
Helper to return tuple of attributes common to nodes and nets.
|
||||
"""
|
||||
id = int(obj.getAttribute("id"))
|
||||
name = str(obj.getAttribute("name"))
|
||||
type = str(obj.getAttribute("type"))
|
||||
return id, name, type
|
||||
|
||||
def parsenets(self):
|
||||
linkednets = []
|
||||
for net in self.np.getElementsByTagName("NetworkDefinition"):
|
||||
id, name, type = self.getcommonattributes(net)
|
||||
nodecls = xmlutils.xml_type_to_node_class(self.session, type)
|
||||
if not nodecls:
|
||||
logger.warn("skipping unknown network node '%s' type '%s'", name, type)
|
||||
continue
|
||||
n = self.session.add_object(cls=nodecls, objid=id, name=name, start=self.start)
|
||||
if name in self.coords:
|
||||
x, y, z = self.coords[name]
|
||||
n.setposition(x, y, z)
|
||||
xmlutils.get_params_set_attrs(net, ("icon", "canvas", "opaque"), n)
|
||||
if hasattr(n, "canvas") and n.canvas is not None:
|
||||
n.canvas = int(n.canvas)
|
||||
# links between two nets (e.g. switch-switch)
|
||||
for ifc in net.getElementsByTagName("interface"):
|
||||
netid = str(ifc.getAttribute("net"))
|
||||
ifcname = str(ifc.getAttribute("name"))
|
||||
linkednets.append((n, netid, ifcname))
|
||||
self.parsemodels(net, n)
|
||||
# link networks together now that they all have been parsed
|
||||
for n, netid, ifcname in linkednets:
|
||||
try:
|
||||
n2 = n.session.get_object_by_name(netid)
|
||||
except KeyError:
|
||||
logger.warn("skipping net %s interface: unknown net %s", n.name, netid)
|
||||
continue
|
||||
upstream = False
|
||||
netif = n.getlinknetif(n2)
|
||||
if netif is None:
|
||||
netif = n2.linknet(n)
|
||||
else:
|
||||
netif.swapparams('_params_up')
|
||||
upstream = True
|
||||
key = (n2.name, ifcname)
|
||||
if key in self.linkparams:
|
||||
for k, v in self.linkparams[key]:
|
||||
netif.setparam(k, v)
|
||||
if upstream:
|
||||
netif.swapparams('_params_up')
|
||||
|
||||
def parsenodes(self):
|
||||
for node in self.np.getElementsByTagName("Node"):
|
||||
id, name, type = self.getcommonattributes(node)
|
||||
if type == "rj45":
|
||||
nodecls = nodeutils.get_node_class(NodeTypes.RJ45)
|
||||
else:
|
||||
nodecls = self.nodecls
|
||||
n = self.session.add_object(cls=nodecls, objid=id, name=name, start=self.start)
|
||||
if name in self.coords:
|
||||
x, y, z = self.coords[name]
|
||||
n.setposition(x, y, z)
|
||||
n.type = type
|
||||
xmlutils.get_params_set_attrs(node, ("icon", "canvas", "opaque"), n)
|
||||
if hasattr(n, "canvas") and n.canvas is not None:
|
||||
n.canvas = int(n.canvas)
|
||||
for ifc in node.getElementsByTagName("interface"):
|
||||
self.parseinterface(n, ifc)
|
||||
|
||||
def parseinterface(self, n, ifc):
|
||||
"""
|
||||
Parse a interface block such as:
|
||||
<interface name="eth0" net="37278">
|
||||
<address type="mac">00:00:00:aa:00:01</address>
|
||||
<address>10.0.0.2/24</address>
|
||||
<address>2001::2/64</address>
|
||||
</interface>
|
||||
"""
|
||||
name = str(ifc.getAttribute("name"))
|
||||
netid = str(ifc.getAttribute("net"))
|
||||
hwaddr = None
|
||||
addrlist = []
|
||||
try:
|
||||
net = n.session.get_object_by_name(netid)
|
||||
except KeyError:
|
||||
logger.warn("skipping node %s interface %s: unknown net %s", n.name, name, netid)
|
||||
return
|
||||
for addr in ifc.getElementsByTagName("address"):
|
||||
addrstr = xmlutils.get_text_child(addr)
|
||||
if addrstr is None:
|
||||
continue
|
||||
if addr.getAttribute("type") == "mac":
|
||||
hwaddr = addrstr
|
||||
else:
|
||||
addrlist.append(addrstr)
|
||||
i = n.newnetif(net, addrlist=addrlist, hwaddr=hwaddr, ifindex=None, ifname=name)
|
||||
for model in ifc.getElementsByTagName("model"):
|
||||
self.parsemodel(model, n, n.objid)
|
||||
key = (n.name, name)
|
||||
if key in self.linkparams:
|
||||
netif = n.netif(i)
|
||||
for k, v in self.linkparams[key]:
|
||||
netif.setparam(k, v)
|
||||
|
||||
def parsemodels(self, dom, obj):
|
||||
"""
|
||||
Mobility/wireless model config is stored in a ConfigurableManager's
|
||||
config dict.
|
||||
"""
|
||||
nodenum = int(dom.getAttribute("id"))
|
||||
for model in dom.getElementsByTagName("model"):
|
||||
self.parsemodel(model, obj, nodenum)
|
||||
|
||||
def parsemodel(self, model, obj, nodenum):
|
||||
"""
|
||||
Mobility/wireless model config is stored in a ConfigurableManager's
|
||||
config dict.
|
||||
"""
|
||||
name = model.getAttribute("name")
|
||||
if name == '':
|
||||
return
|
||||
type = model.getAttribute("type")
|
||||
# convert child text nodes into key=value pairs
|
||||
kvs = xmlutils.get_text_elements_to_list(model)
|
||||
|
||||
mgr = self.session.mobility
|
||||
# TODO: the session.confobj() mechanism could be more generic;
|
||||
# it only allows registering Conf Message callbacks, but here
|
||||
# we want access to the ConfigurableManager, not the callback
|
||||
if name[:5] == "emane":
|
||||
mgr = self.session.emane
|
||||
elif name[:5] == "netem":
|
||||
mgr = None
|
||||
self.parsenetem(model, obj, kvs)
|
||||
|
||||
elif name[:3] == "xen":
|
||||
mgr = self.session.xen
|
||||
# TODO: assign other config managers here
|
||||
if mgr:
|
||||
mgr.setconfig_keyvalues(nodenum, name, kvs)
|
||||
|
||||
def parsenetem(self, model, obj, kvs):
|
||||
"""
|
||||
Determine interface and invoke setparam() using the parsed
|
||||
(key, value) pairs.
|
||||
"""
|
||||
ifname = model.getAttribute("netif")
|
||||
peer = model.getAttribute("peer")
|
||||
key = (peer, ifname)
|
||||
# nodes and interfaces do not exist yet, at this point of the parsing,
|
||||
# save (key, value) pairs for later
|
||||
try:
|
||||
# kvs = map(lambda(k, v): (int(v)), kvs)
|
||||
kvs = map(self.numericvalue, kvs)
|
||||
except ValueError:
|
||||
logger.warn("error parsing link parameters for '%s' on '%s'", ifname, peer)
|
||||
self.linkparams[key] = kvs
|
||||
|
||||
@staticmethod
|
||||
def numericvalue(keyvalue):
|
||||
(key, value) = keyvalue
|
||||
if '.' in str(value):
|
||||
value = float(value)
|
||||
else:
|
||||
value = int(value)
|
||||
return key, value
|
||||
|
||||
def parseorigin(self):
|
||||
"""
|
||||
Parse any origin tag from the Mobility Plan and set the CoreLocation
|
||||
reference point appropriately.
|
||||
"""
|
||||
origin = xmlutils.get_one_element(self.mp, "origin")
|
||||
if not origin:
|
||||
return
|
||||
location = self.session.location
|
||||
geo = []
|
||||
attrs = ("lat", "lon", "alt")
|
||||
for i in xrange(3):
|
||||
a = origin.getAttribute(attrs[i])
|
||||
if a is not None:
|
||||
a = float(a)
|
||||
geo.append(a)
|
||||
location.setrefgeo(geo[0], geo[1], geo[2])
|
||||
scale = origin.getAttribute("scale100")
|
||||
if scale is not None:
|
||||
location.refscale = float(scale)
|
||||
point = xmlutils.get_one_element(origin, "point")
|
||||
if point is not None and point.firstChild is not None:
|
||||
xyz = point.firstChild.nodeValue.split(',')
|
||||
if len(xyz) == 2:
|
||||
xyz.append('0.0')
|
||||
if len(xyz) == 3:
|
||||
xyz = map(lambda (x): float(x), xyz)
|
||||
location.refxyz = (xyz[0], xyz[1], xyz[2])
|
||||
|
||||
def parsedefaultservices(self):
|
||||
"""
|
||||
Prior to parsing nodes, use session.services manager to store
|
||||
default services for node types
|
||||
"""
|
||||
for node in self.sp.getElementsByTagName("Node"):
|
||||
type = node.getAttribute("type")
|
||||
if type == '':
|
||||
continue # node-specific service config
|
||||
services = []
|
||||
for service in node.getElementsByTagName("Service"):
|
||||
services.append(str(service.getAttribute("name")))
|
||||
self.session.services.defaultservices[type] = services
|
||||
logger.info("default services for type %s set to %s" % (type, services))
|
||||
|
||||
def parseservices(self):
|
||||
"""
|
||||
After node objects exist, parse service customizations and add them
|
||||
to the nodes.
|
||||
"""
|
||||
svclists = {}
|
||||
# parse services and store configs into session.services.configs
|
||||
for node in self.sp.getElementsByTagName("Node"):
|
||||
name = node.getAttribute("name")
|
||||
if name == '':
|
||||
continue # node type without name
|
||||
n = self.session.get_object_by_name(name)
|
||||
if n is None:
|
||||
logger.warn("skipping service config for unknown node '%s'" % name)
|
||||
continue
|
||||
for service in node.getElementsByTagName("Service"):
|
||||
svcname = service.getAttribute("name")
|
||||
if self.parseservice(service, n):
|
||||
if n.objid in svclists:
|
||||
svclists[n.objid] += "|" + svcname
|
||||
else:
|
||||
svclists[n.objid] = svcname
|
||||
# nodes in NetworkPlan but not in ServicePlan use the
|
||||
# default services for their type
|
||||
for node in self.np.getElementsByTagName("Node"):
|
||||
id, name, type = self.getcommonattributes(node)
|
||||
if id in svclists:
|
||||
continue # custom config exists
|
||||
else:
|
||||
svclists[int(id)] = None # use defaults
|
||||
|
||||
# associate nodes with services
|
||||
for objid in sorted(svclists.keys()):
|
||||
n = self.session.get_object(objid)
|
||||
self.session.services.addservicestonode(node=n, nodetype=n.type, services_str=svclists[objid])
|
||||
|
||||
def parseservice(self, service, n):
|
||||
"""
|
||||
Use session.services manager to store service customizations before
|
||||
they are added to a node.
|
||||
"""
|
||||
name = service.getAttribute("name")
|
||||
svc = ServiceManager.get(name)
|
||||
if svc is None:
|
||||
return False
|
||||
values = []
|
||||
startup_idx = service.getAttribute("startup_idx")
|
||||
if startup_idx is not None:
|
||||
values.append("startidx=%s" % startup_idx)
|
||||
startup_time = service.getAttribute("start_time")
|
||||
if startup_time is not None:
|
||||
values.append("starttime=%s" % startup_time)
|
||||
dirs = []
|
||||
for dir in service.getElementsByTagName("Directory"):
|
||||
dirname = dir.getAttribute("name")
|
||||
dirs.append(dirname)
|
||||
if len(dirs):
|
||||
values.append("dirs=%s" % dirs)
|
||||
|
||||
startup = []
|
||||
shutdown = []
|
||||
validate = []
|
||||
for cmd in service.getElementsByTagName("Command"):
|
||||
type = cmd.getAttribute("type")
|
||||
cmdstr = xmlutils.get_text_child(cmd)
|
||||
if cmdstr is None:
|
||||
continue
|
||||
if type == "start":
|
||||
startup.append(cmdstr)
|
||||
elif type == "stop":
|
||||
shutdown.append(cmdstr)
|
||||
elif type == "validate":
|
||||
validate.append(cmdstr)
|
||||
if len(startup):
|
||||
values.append("cmdup=%s" % startup)
|
||||
if len(shutdown):
|
||||
values.append("cmddown=%s" % shutdown)
|
||||
if len(validate):
|
||||
values.append("cmdval=%s" % validate)
|
||||
|
||||
files = []
|
||||
for file in service.getElementsByTagName("File"):
|
||||
filename = file.getAttribute("name")
|
||||
files.append(filename)
|
||||
data = xmlutils.get_text_child(file)
|
||||
typestr = "service:%s:%s" % (name, filename)
|
||||
self.session.services.setservicefile(nodenum=n.objid, type=typestr,
|
||||
filename=filename,
|
||||
srcname=None, data=data)
|
||||
if len(files):
|
||||
values.append("files=%s" % files)
|
||||
if not bool(service.getAttribute("custom")):
|
||||
return True
|
||||
self.session.services.setcustomservice(n.objid, svc, values)
|
||||
return True
|
||||
|
||||
def parsehooks(self, hooks):
|
||||
''' Parse hook scripts from XML into session._hooks.
|
||||
'''
|
||||
for hook in hooks.getElementsByTagName("Hook"):
|
||||
filename = hook.getAttribute("name")
|
||||
state = hook.getAttribute("state")
|
||||
data = xmlutils.get_text_child(hook)
|
||||
if data is None:
|
||||
data = "" # allow for empty file
|
||||
type = "hook:%s" % state
|
||||
self.session.set_hook(type, file_name=filename, source_name=None, data=data)
|
||||
|
||||
def parsemeta(self):
|
||||
opt = xmlutils.get_one_element(self.meta, "SessionOptions")
|
||||
if opt:
|
||||
for param in opt.getElementsByTagName("param"):
|
||||
k = str(param.getAttribute("name"))
|
||||
v = str(param.getAttribute("value"))
|
||||
if v == '':
|
||||
v = xmlutils.get_text_child(param) # allow attribute/text for newlines
|
||||
setattr(self.session.options, k, v)
|
||||
hooks = xmlutils.get_one_element(self.meta, "Hooks")
|
||||
if hooks:
|
||||
self.parsehooks(hooks)
|
||||
meta = xmlutils.get_one_element(self.meta, "MetaData")
|
||||
if meta:
|
||||
for param in meta.getElementsByTagName("param"):
|
||||
k = str(param.getAttribute("name"))
|
||||
v = str(param.getAttribute("value"))
|
||||
if v == '':
|
||||
v = xmlutils.get_text_child(param)
|
||||
self.session.metadata.add_item(k, v)
|
876
daemon/core/xml/xmlparser1.py
Normal file
876
daemon/core/xml/xmlparser1.py
Normal file
|
@ -0,0 +1,876 @@
|
|||
import random
|
||||
from xml.dom.minidom import Node
|
||||
from xml.dom.minidom import parse
|
||||
|
||||
from core import constants
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import log
|
||||
from core.misc import nodeutils
|
||||
from core.misc.ipaddress import MacAddress
|
||||
from core.service import ServiceManager
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class CoreDocumentParser1(object):
|
||||
layer2_device_types = 'hub', 'switch'
|
||||
layer3_device_types = 'host', 'router'
|
||||
device_types = layer2_device_types + layer3_device_types
|
||||
|
||||
# TODO: support CORE interface classes:
|
||||
# RJ45Node
|
||||
# TunnelNode
|
||||
|
||||
def __init__(self, session, filename, options):
|
||||
"""
|
||||
|
||||
:param core.session.Session session:
|
||||
:param filename:
|
||||
:param options:
|
||||
:return:
|
||||
"""
|
||||
self.session = session
|
||||
self.filename = filename
|
||||
if 'dom' in options:
|
||||
# this prevents parsing twice when detecting file versions
|
||||
self.dom = options['dom']
|
||||
else:
|
||||
self.dom = parse(filename)
|
||||
self.start = options['start']
|
||||
self.nodecls = options['nodecls']
|
||||
self.scenario = self.get_scenario(self.dom)
|
||||
self.location_refgeo_set = False
|
||||
self.location_refxyz_set = False
|
||||
# saved link parameters saved when parsing networks and applied later
|
||||
self.link_params = {}
|
||||
# map from id-string to objid, for files having node names but
|
||||
# not node numbers
|
||||
self.objidmap = {}
|
||||
self.objids = set()
|
||||
self.default_services = {}
|
||||
if self.scenario:
|
||||
self.parse_scenario()
|
||||
|
||||
@staticmethod
|
||||
def get_scenario(dom):
|
||||
scenario = xmlutils.get_first_child_by_tag_name(dom, 'scenario')
|
||||
if not scenario:
|
||||
raise ValueError, 'no scenario element found'
|
||||
version = scenario.getAttribute('version')
|
||||
if version and version != '1.0':
|
||||
raise ValueError, \
|
||||
'unsupported scenario version found: \'%s\'' % version
|
||||
return scenario
|
||||
|
||||
def parse_scenario(self):
|
||||
self.parse_default_services()
|
||||
self.parse_session_config()
|
||||
self.parse_network_plan()
|
||||
|
||||
def assign_id(self, idstr, idval):
|
||||
if idstr in self.objidmap:
|
||||
assert self.objidmap[idstr] == idval and idval in self.objids
|
||||
return
|
||||
self.objidmap[idstr] = idval
|
||||
self.objids.add(idval)
|
||||
|
||||
def rand_id(self):
|
||||
while True:
|
||||
x = random.randint(0, 0xffff)
|
||||
if x not in self.objids:
|
||||
return x
|
||||
|
||||
def get_id(self, idstr):
|
||||
"""
|
||||
Get a, possibly new, object id (node number) corresponding to
|
||||
the given XML string id.
|
||||
"""
|
||||
if not idstr:
|
||||
idn = self.rand_id()
|
||||
self.objids.add(idn)
|
||||
return idn
|
||||
elif idstr in self.objidmap:
|
||||
return self.objidmap[idstr]
|
||||
else:
|
||||
try:
|
||||
idn = int(idstr)
|
||||
except ValueError:
|
||||
idn = self.rand_id()
|
||||
self.assign_id(idstr, idn)
|
||||
return idn
|
||||
|
||||
def get_common_attributes(self, node):
|
||||
"""
|
||||
Return id, name attributes for the given XML element. These
|
||||
attributes are common to nodes and networks.
|
||||
"""
|
||||
idstr = node.getAttribute('id')
|
||||
# use an explicit set COREID if it exists
|
||||
coreid = self.find_core_id(node)
|
||||
|
||||
if coreid:
|
||||
idn = int(coreid)
|
||||
if idstr:
|
||||
self.assign_id(idstr, idn)
|
||||
else:
|
||||
idn = self.get_id(idstr)
|
||||
|
||||
# TODO: consider supporting unicode; for now convert to an
|
||||
# ascii string
|
||||
namestr = str(node.getAttribute('name'))
|
||||
return idn, namestr
|
||||
|
||||
def iter_network_member_devices(self, element):
|
||||
# element can be a network or a channel
|
||||
for interface in xmlutils.iter_children_with_attribute(element, 'member', 'type', 'interface'):
|
||||
if_id = xmlutils.get_child_text_trim(interface)
|
||||
assert if_id # XXX for testing
|
||||
if not if_id:
|
||||
continue
|
||||
device, if_name = self.find_device_with_interface(if_id)
|
||||
assert device, 'no device for if_id: %s' % if_id # XXX for testing
|
||||
if device:
|
||||
yield device, if_name
|
||||
|
||||
def network_class(self, network, network_type):
|
||||
"""
|
||||
Return the corresponding CORE network class for the given
|
||||
network/network_type.
|
||||
"""
|
||||
if network_type in ['ethernet', 'satcom']:
|
||||
return nodeutils.get_node_class(NodeTypes.PEER_TO_PEER)
|
||||
elif network_type == 'wireless':
|
||||
channel = xmlutils.get_first_child_by_tag_name(network, 'channel')
|
||||
if channel:
|
||||
# use an explicit CORE type if it exists
|
||||
coretype = xmlutils.get_first_child_text_trim_with_attribute(channel, 'type', 'domain', 'CORE')
|
||||
if coretype:
|
||||
if coretype == 'basic_range':
|
||||
return nodeutils.get_node_class(NodeTypes.WIRELESS_LAN)
|
||||
elif coretype.startswith('emane'):
|
||||
return nodeutils.get_node_class(NodeTypes.EMANE)
|
||||
else:
|
||||
logger.warn('unknown network type: \'%s\'', coretype)
|
||||
return xmlutils.xml_type_to_node_class(self.session, coretype)
|
||||
return nodeutils.get_node_class(NodeTypes.WIRELESS_LAN)
|
||||
logger.warn('unknown network type: \'%s\'', network_type)
|
||||
return None
|
||||
|
||||
def create_core_object(self, objcls, objid, objname, element, node_type):
|
||||
obj = self.session.add_object(cls=objcls, objid=objid, name=objname, start=self.start)
|
||||
logger.info('added object objid=%s name=%s cls=%s' % (objid, objname, objcls))
|
||||
self.set_object_position(obj, element)
|
||||
self.set_object_presentation(obj, element, node_type)
|
||||
return obj
|
||||
|
||||
def get_core_object(self, idstr):
|
||||
if idstr and idstr in self.objidmap:
|
||||
objid = self.objidmap[idstr]
|
||||
return self.session.get_object(objid)
|
||||
return None
|
||||
|
||||
def parse_network_plan(self):
|
||||
# parse the scenario in the following order:
|
||||
# 1. layer-2 devices
|
||||
# 2. other networks (ptp/wlan)
|
||||
# 3. layer-3 devices
|
||||
self.parse_layer2_devices()
|
||||
self.parse_networks()
|
||||
self.parse_layer3_devices()
|
||||
|
||||
def set_ethernet_link_parameters(self, channel, link_params, mobility_model_name, mobility_params):
|
||||
# save link parameters for later use, indexed by the tuple
|
||||
# (device_id, interface_name)
|
||||
for dev, if_name in self.iter_network_member_devices(channel):
|
||||
if self.device_type(dev) in self.device_types:
|
||||
dev_id = dev.getAttribute('id')
|
||||
key = (dev_id, if_name)
|
||||
self.link_params[key] = link_params
|
||||
if mobility_model_name or mobility_params:
|
||||
raise NotImplementedError
|
||||
|
||||
def set_wireless_link_parameters(self, channel, link_params, mobility_model_name, mobility_params):
|
||||
network = self.find_channel_network(channel)
|
||||
network_id = network.getAttribute('id')
|
||||
if network_id in self.objidmap:
|
||||
nodenum = self.objidmap[network_id]
|
||||
else:
|
||||
logger.warn('unknown network: %s', network.toxml('utf-8'))
|
||||
assert False # XXX for testing
|
||||
model_name = xmlutils.get_first_child_text_trim_with_attribute(channel, 'type', 'domain', 'CORE')
|
||||
if not model_name:
|
||||
model_name = 'basic_range'
|
||||
if model_name == 'basic_range':
|
||||
mgr = self.session.mobility
|
||||
elif model_name.startswith('emane'):
|
||||
mgr = self.session.emane
|
||||
elif model_name.startswith('xen'):
|
||||
mgr = self.session.xen
|
||||
else:
|
||||
# TODO: any other config managers?
|
||||
raise NotImplementedError
|
||||
mgr.setconfig_keyvalues(nodenum, model_name, link_params.items())
|
||||
if mobility_model_name and mobility_params:
|
||||
mgr.setconfig_keyvalues(nodenum, mobility_model_name, mobility_params.items())
|
||||
|
||||
def link_layer2_devices(self, device1, ifname1, device2, ifname2):
|
||||
"""
|
||||
Link two layer-2 devices together.
|
||||
"""
|
||||
devid1 = device1.getAttribute('id')
|
||||
dev1 = self.get_core_object(devid1)
|
||||
devid2 = device2.getAttribute('id')
|
||||
dev2 = self.get_core_object(devid2)
|
||||
assert dev1 and dev2 # XXX for testing
|
||||
if dev1 and dev2:
|
||||
# TODO: review this
|
||||
if nodeutils.is_node(dev2, NodeTypes.RJ45):
|
||||
# RJ45 nodes have different linknet()
|
||||
netif = dev2.linknet(dev1)
|
||||
else:
|
||||
netif = dev1.linknet(dev2)
|
||||
self.set_wired_link_parameters(dev1, netif, devid1, ifname1)
|
||||
|
||||
@classmethod
|
||||
def parse_xml_value(cls, valtext):
|
||||
if not valtext:
|
||||
return None
|
||||
try:
|
||||
if not valtext.translate(None, '0123456789'):
|
||||
val = int(valtext)
|
||||
else:
|
||||
val = float(valtext)
|
||||
except ValueError:
|
||||
val = str(valtext)
|
||||
return val
|
||||
|
||||
@classmethod
|
||||
def parse_parameter_children(cls, parent):
|
||||
params = {}
|
||||
for parameter in xmlutils.iter_children_with_name(parent, 'parameter'):
|
||||
param_name = parameter.getAttribute('name')
|
||||
assert param_name # XXX for testing
|
||||
if not param_name:
|
||||
continue
|
||||
# TODO: consider supporting unicode; for now convert
|
||||
# to an ascii string
|
||||
param_name = str(param_name)
|
||||
param_val = cls.parse_xml_value(xmlutils.get_child_text_trim(parameter))
|
||||
# TODO: check if the name already exists?
|
||||
if param_name and param_val:
|
||||
params[param_name] = param_val
|
||||
return params
|
||||
|
||||
def parse_network_channel(self, channel):
|
||||
element = self.search_for_element(channel, 'type', lambda x: not x.hasAttributes())
|
||||
channel_type = xmlutils.get_child_text_trim(element)
|
||||
link_params = self.parse_parameter_children(channel)
|
||||
|
||||
mobility = xmlutils.get_first_child_by_tag_name(channel, 'CORE:mobility')
|
||||
if mobility:
|
||||
mobility_model_name = xmlutils.get_first_child_text_trim_by_tag_name(mobility, 'type')
|
||||
mobility_params = self.parse_parameter_children(mobility)
|
||||
else:
|
||||
mobility_model_name = None
|
||||
mobility_params = None
|
||||
if channel_type == 'wireless':
|
||||
self.set_wireless_link_parameters(channel, link_params, mobility_model_name, mobility_params)
|
||||
elif channel_type == 'ethernet':
|
||||
# TODO: maybe this can be done in the loop below to avoid
|
||||
# iterating through channel members multiple times
|
||||
self.set_ethernet_link_parameters(channel, link_params, mobility_model_name, mobility_params)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
layer2_device = []
|
||||
for dev, if_name in self.iter_network_member_devices(channel):
|
||||
if self.device_type(dev) in self.layer2_device_types:
|
||||
layer2_device.append((dev, if_name))
|
||||
assert len(layer2_device) <= 2
|
||||
if len(layer2_device) == 2:
|
||||
self.link_layer2_devices(layer2_device[0][0], layer2_device[0][1],
|
||||
layer2_device[1][0], layer2_device[1][1])
|
||||
|
||||
def parse_network(self, network):
|
||||
"""
|
||||
Each network element should have an 'id' and 'name' attribute
|
||||
and include the following child elements:
|
||||
|
||||
type (one)
|
||||
member (zero or more with type="interface" or type="channel")
|
||||
channel (zero or more)
|
||||
"""
|
||||
layer2_members = set()
|
||||
layer3_members = 0
|
||||
for dev, if_name in self.iter_network_member_devices(network):
|
||||
if not dev:
|
||||
continue
|
||||
devtype = self.device_type(dev)
|
||||
if devtype in self.layer2_device_types:
|
||||
layer2_members.add(dev)
|
||||
elif devtype in self.layer3_device_types:
|
||||
layer3_members += 1
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if len(layer2_members) == 0:
|
||||
net_type = xmlutils.get_first_child_text_trim_by_tag_name(network, 'type')
|
||||
if not net_type:
|
||||
logger.warn('no network type found for network: \'%s\'', network.toxml('utf-8'))
|
||||
assert False # XXX for testing
|
||||
net_cls = self.network_class(network, net_type)
|
||||
objid, net_name = self.get_common_attributes(network)
|
||||
logger.info('parsing network: name=%s id=%s' % (net_name, objid))
|
||||
if objid in self.session.objects:
|
||||
return
|
||||
n = self.create_core_object(net_cls, objid, net_name, network, None)
|
||||
|
||||
# handle channel parameters
|
||||
for channel in xmlutils.iter_children_with_name(network, 'channel'):
|
||||
self.parse_network_channel(channel)
|
||||
|
||||
def parse_networks(self):
|
||||
"""
|
||||
Parse all 'network' elements.
|
||||
"""
|
||||
for network in xmlutils.iter_descendants_with_name(self.scenario, 'network'):
|
||||
self.parse_network(network)
|
||||
|
||||
def parse_addresses(self, interface):
|
||||
mac = []
|
||||
ipv4 = []
|
||||
ipv6 = []
|
||||
hostname = []
|
||||
for address in xmlutils.iter_children_with_name(interface, 'address'):
|
||||
addr_type = address.getAttribute('type')
|
||||
if not addr_type:
|
||||
msg = 'no type attribute found for address ' \
|
||||
'in interface: \'%s\'' % interface.toxml('utf-8')
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
addr_text = xmlutils.get_child_text_trim(address)
|
||||
if not addr_text:
|
||||
msg = 'no text found for address ' \
|
||||
'in interface: \'%s\'' % interface.toxml('utf-8')
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
if addr_type == 'mac':
|
||||
mac.append(addr_text)
|
||||
elif addr_type == 'IPv4':
|
||||
ipv4.append(addr_text)
|
||||
elif addr_type == 'IPv6':
|
||||
ipv6.append(addr_text)
|
||||
elif addr_type == 'hostname':
|
||||
hostname.append(addr_text)
|
||||
else:
|
||||
msg = 'skipping unknown address type \'%s\' in ' \
|
||||
'interface: \'%s\'' % (addr_type, interface.toxml('utf-8'))
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
return mac, ipv4, ipv6, hostname
|
||||
|
||||
def parse_interface(self, node, device_id, interface):
|
||||
"""
|
||||
Each interface can have multiple 'address' elements.
|
||||
"""
|
||||
if_name = interface.getAttribute('name')
|
||||
network = self.find_interface_network_object(interface)
|
||||
if not network:
|
||||
msg = 'skipping node \'%s\' interface \'%s\': ' \
|
||||
'unknown network' % (node.name, if_name)
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
mac, ipv4, ipv6, hostname = self.parse_addresses(interface)
|
||||
if mac:
|
||||
hwaddr = MacAddress.from_string(mac[0])
|
||||
else:
|
||||
hwaddr = None
|
||||
ifindex = node.newnetif(network, addrlist=ipv4 + ipv6, hwaddr=hwaddr, ifindex=None, ifname=if_name)
|
||||
# TODO: 'hostname' addresses are unused
|
||||
msg = 'node \'%s\' interface \'%s\' connected ' \
|
||||
'to network \'%s\'' % (node.name, if_name, network.name)
|
||||
logger.info(msg)
|
||||
# set link parameters for wired links
|
||||
if nodeutils.is_node(network, (NodeTypes.HUB, NodeTypes.PEER_TO_PEER, NodeTypes.SWITCH)):
|
||||
netif = node.netif(ifindex)
|
||||
self.set_wired_link_parameters(network, netif, device_id)
|
||||
|
||||
def set_wired_link_parameters(self, network, netif, device_id, netif_name=None):
|
||||
if netif_name is None:
|
||||
netif_name = netif.name
|
||||
key = (device_id, netif_name)
|
||||
if key in self.link_params:
|
||||
link_params = self.link_params[key]
|
||||
if self.start:
|
||||
bw = link_params.get('bw')
|
||||
delay = link_params.get('delay')
|
||||
loss = link_params.get('loss')
|
||||
duplicate = link_params.get('duplicate')
|
||||
jitter = link_params.get('jitter')
|
||||
network.linkconfig(netif, bw=bw, delay=delay, loss=loss, duplicate=duplicate, jitter=jitter)
|
||||
else:
|
||||
for k, v in link_params.iteritems():
|
||||
netif.setparam(k, v)
|
||||
|
||||
@staticmethod
|
||||
def search_for_element(node, tag_name, match=None):
|
||||
"""
|
||||
Search the given node and all ancestors for an element named
|
||||
tagName that satisfies the given matching function.
|
||||
"""
|
||||
while True:
|
||||
for child in xmlutils.iter_children(node, Node.ELEMENT_NODE):
|
||||
if child.tagName == tag_name and (match is None or match(child)):
|
||||
return child
|
||||
node = node.parentNode
|
||||
if not node:
|
||||
break
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_core_id(cls, node):
|
||||
def match(x):
|
||||
domain = x.getAttribute('domain')
|
||||
return domain == 'COREID'
|
||||
|
||||
alias = cls.search_for_element(node, 'alias', match)
|
||||
if alias:
|
||||
return xmlutils.get_child_text_trim(alias)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def find_point(cls, node):
|
||||
return cls.search_for_element(node, 'point')
|
||||
|
||||
@staticmethod
|
||||
def find_channel_network(channel):
|
||||
p = channel.parentNode
|
||||
if p and p.tagName == 'network':
|
||||
return p
|
||||
return None
|
||||
|
||||
def find_interface_network_object(self, interface):
|
||||
network_id = xmlutils.get_first_child_text_trim_with_attribute(interface, 'member', 'type', 'network')
|
||||
if not network_id:
|
||||
# support legacy notation: <interface net="netid" ...
|
||||
network_id = interface.getAttribute('net')
|
||||
obj = self.get_core_object(network_id)
|
||||
if obj:
|
||||
# the network_id should exist for ptp or wlan/emane networks
|
||||
return obj
|
||||
# the network should correspond to a layer-2 device if the
|
||||
# network_id does not exist
|
||||
channel_id = xmlutils.get_first_child_text_trim_with_attribute(interface, 'member', 'type', 'channel')
|
||||
if not network_id or not channel_id:
|
||||
return None
|
||||
network = xmlutils.get_first_child_with_attribute(self.scenario, 'network', 'id', network_id)
|
||||
if not network:
|
||||
return None
|
||||
channel = xmlutils.get_first_child_with_attribute(network, 'channel', 'id', channel_id)
|
||||
if not channel:
|
||||
return None
|
||||
device = None
|
||||
for dev, if_name in self.iter_network_member_devices(channel):
|
||||
if self.device_type(dev) in self.layer2_device_types:
|
||||
assert not device # XXX
|
||||
device = dev
|
||||
if device:
|
||||
obj = self.get_core_object(device.getAttribute('id'))
|
||||
if obj:
|
||||
return obj
|
||||
return None
|
||||
|
||||
def set_object_position_pixel(self, obj, point):
|
||||
x = float(point.getAttribute('x'))
|
||||
y = float(point.getAttribute('y'))
|
||||
z = point.getAttribute('z')
|
||||
if z:
|
||||
z = float(z)
|
||||
else:
|
||||
z = 0.0
|
||||
# TODO: zMode is unused
|
||||
# z_mode = point.getAttribute('zMode'))
|
||||
if x < 0.0:
|
||||
logger.warn('limiting negative x position of \'%s\' to zero: %s' % (obj.name, x))
|
||||
x = 0.0
|
||||
if y < 0.0:
|
||||
logger.warn('limiting negative y position of \'%s\' to zero: %s' % (obj.name, y))
|
||||
y = 0.0
|
||||
obj.setposition(x, y, z)
|
||||
|
||||
def set_object_position_gps(self, obj, point):
|
||||
lat = float(point.getAttribute('lat'))
|
||||
lon = float(point.getAttribute('lon'))
|
||||
zalt = point.getAttribute('z')
|
||||
if zalt:
|
||||
zalt = float(zalt)
|
||||
else:
|
||||
zalt = 0.0
|
||||
# TODO: zMode is unused
|
||||
# z_mode = point.getAttribute('zMode'))
|
||||
if not self.location_refgeo_set:
|
||||
# for x,y,z conversion, we need a reasonable refpt; this
|
||||
# picks the first coordinates as the origin
|
||||
self.session.location.setrefgeo(lat, lon, zalt)
|
||||
self.location_refgeo_set = True
|
||||
x, y, z = self.session.location.getxyz(lat, lon, zalt)
|
||||
if x < 0.0:
|
||||
logger.warn('limiting negative x position of \'%s\' to zero: %s' % (obj.name, x))
|
||||
x = 0.0
|
||||
if y < 0.0:
|
||||
logger.warn('limiting negative y position of \'%s\' to zero: %s' % (obj.name, y))
|
||||
y = 0.0
|
||||
obj.setposition(x, y, z)
|
||||
|
||||
def set_object_position_cartesian(self, obj, point):
|
||||
# TODO: review this
|
||||
xm = float(point.getAttribute('x'))
|
||||
ym = float(point.getAttribute('y'))
|
||||
zm = point.getAttribute('z')
|
||||
if zm:
|
||||
zm = float(zm)
|
||||
else:
|
||||
zm = 0.0
|
||||
# TODO: zMode is unused
|
||||
# z_mode = point.getAttribute('zMode'))
|
||||
if not self.location_refxyz_set:
|
||||
self.session.location.refxyz = xm, ym, zm
|
||||
self.location_refxyz_set = True
|
||||
# need to convert meters to pixels
|
||||
x = self.session.location.m2px(xm) + self.session.location.refxyz[0]
|
||||
y = self.session.location.m2px(ym) + self.session.location.refxyz[1]
|
||||
z = self.session.location.m2px(zm) + self.session.location.refxyz[2]
|
||||
if x < 0.0:
|
||||
logger.warn('limiting negative x position of \'%s\' to zero: %s' % (obj.name, x))
|
||||
x = 0.0
|
||||
if y < 0.0:
|
||||
logger.warn('limiting negative y position of \'%s\' to zero: %s' % (obj.name, y))
|
||||
y = 0.0
|
||||
obj.setposition(x, y, z)
|
||||
|
||||
def set_object_position(self, obj, element):
|
||||
"""
|
||||
Set the x,y,x position of obj from the point associated with
|
||||
the given element.
|
||||
"""
|
||||
point = self.find_point(element)
|
||||
if not point:
|
||||
return False
|
||||
point_type = point.getAttribute('type')
|
||||
if not point_type:
|
||||
msg = 'no type attribute found for point: \'%s\'' % \
|
||||
point.toxml('utf-8')
|
||||
logger.warn(msg)
|
||||
assert False # XXX for testing
|
||||
elif point_type == 'pixel':
|
||||
self.set_object_position_pixel(obj, point)
|
||||
elif point_type == 'gps':
|
||||
self.set_object_position_gps(obj, point)
|
||||
elif point_type == 'cart':
|
||||
self.set_object_position_cartesian(obj, point)
|
||||
else:
|
||||
logger.warn("skipping unknown point type: '%s'" % point_type)
|
||||
assert False # XXX for testing
|
||||
|
||||
logger.info('set position of %s from point element: \'%s\'', obj.name, point.toxml('utf-8'))
|
||||
return True
|
||||
|
||||
def parse_device_service(self, service, node):
|
||||
name = service.getAttribute('name')
|
||||
session_service = ServiceManager.get(name)
|
||||
if not session_service:
|
||||
assert False # XXX for testing
|
||||
values = []
|
||||
startup_idx = service.getAttribute('startup_idx')
|
||||
if startup_idx:
|
||||
values.append('startidx=%s' % startup_idx)
|
||||
startup_time = service.getAttribute('start_time')
|
||||
if startup_time:
|
||||
values.append('starttime=%s' % startup_time)
|
||||
dirs = []
|
||||
for directory in xmlutils.iter_children_with_name(service, 'directory'):
|
||||
dirname = directory.getAttribute('name')
|
||||
dirs.append(str(dirname))
|
||||
if dirs:
|
||||
values.append("dirs=%s" % dirs)
|
||||
startup = []
|
||||
shutdown = []
|
||||
validate = []
|
||||
for command in xmlutils.iter_children_with_name(service, 'command'):
|
||||
command_type = command.getAttribute('type')
|
||||
command_text = xmlutils.get_child_text_trim(command)
|
||||
if not command_text:
|
||||
continue
|
||||
if command_type == 'start':
|
||||
startup.append(str(command_text))
|
||||
elif command_type == 'stop':
|
||||
shutdown.append(str(command_text))
|
||||
elif command_type == 'validate':
|
||||
validate.append(str(command_text))
|
||||
if startup:
|
||||
values.append('cmdup=%s' % startup)
|
||||
if shutdown:
|
||||
values.append('cmddown=%s' % shutdown)
|
||||
if validate:
|
||||
values.append('cmdval=%s' % validate)
|
||||
filenames = []
|
||||
files = []
|
||||
for f in xmlutils.iter_children_with_name(service, 'file'):
|
||||
filename = f.getAttribute('name')
|
||||
if not filename:
|
||||
continue
|
||||
filenames.append(filename)
|
||||
data = xmlutils.get_child_text_trim(f)
|
||||
if data:
|
||||
data = str(data)
|
||||
else:
|
||||
data = None
|
||||
typestr = 'service:%s:%s' % (name, filename)
|
||||
files.append((typestr, filename, data))
|
||||
if filenames:
|
||||
values.append('files=%s' % filenames)
|
||||
custom = service.getAttribute('custom')
|
||||
if custom and custom.lower() == 'true':
|
||||
self.session.services.setcustomservice(node.objid, session_service, values)
|
||||
# NOTE: if a custom service is used, setservicefile() must be
|
||||
# called after the custom service exists
|
||||
for typestr, filename, data in files:
|
||||
self.session.services.setservicefile(
|
||||
nodenum=node.objid,
|
||||
type=typestr,
|
||||
filename=filename,
|
||||
srcname=None,
|
||||
data=data
|
||||
)
|
||||
return str(name)
|
||||
|
||||
def parse_device_services(self, services, node):
|
||||
"""
|
||||
Use session.services manager to store service customizations
|
||||
before they are added to a node.
|
||||
"""
|
||||
service_names = []
|
||||
for service in xmlutils.iter_children_with_name(services, 'service'):
|
||||
name = self.parse_device_service(service, node)
|
||||
if name:
|
||||
service_names.append(name)
|
||||
return '|'.join(service_names)
|
||||
|
||||
def add_device_services(self, node, device, node_type):
|
||||
"""
|
||||
Add services to the given node.
|
||||
"""
|
||||
services = xmlutils.get_first_child_by_tag_name(device, 'CORE:services')
|
||||
if services:
|
||||
services_str = self.parse_device_services(services, node)
|
||||
logger.info('services for node \'%s\': %s' % (node.name, services_str))
|
||||
elif node_type in self.default_services:
|
||||
services_str = None # default services will be added
|
||||
else:
|
||||
return
|
||||
self.session.services.addservicestonode(
|
||||
node=node,
|
||||
nodetype=node_type,
|
||||
services_str=services_str
|
||||
)
|
||||
|
||||
def set_object_presentation(self, obj, element, node_type):
|
||||
# defaults from the CORE GUI
|
||||
default_icons = {
|
||||
'router': 'router.gif',
|
||||
'host': 'host.gif',
|
||||
'PC': 'pc.gif',
|
||||
'mdr': 'mdr.gif',
|
||||
# 'prouter': 'router_green.gif',
|
||||
# 'xen': 'xen.gif'
|
||||
}
|
||||
icon_set = False
|
||||
for child in xmlutils.iter_children_with_name(element, 'CORE:presentation'):
|
||||
canvas = child.getAttribute('canvas')
|
||||
if canvas:
|
||||
obj.canvas = int(canvas)
|
||||
icon = child.getAttribute('icon')
|
||||
if icon:
|
||||
icon = str(icon).replace("$CORE_DATA_DIR",
|
||||
constants.CORE_DATA_DIR)
|
||||
obj.icon = icon
|
||||
icon_set = True
|
||||
if not icon_set and node_type in default_icons:
|
||||
obj.icon = default_icons[node_type]
|
||||
|
||||
def device_type(self, device):
|
||||
if device.tagName in self.device_types:
|
||||
return device.tagName
|
||||
return None
|
||||
|
||||
def core_node_type(self, device):
|
||||
# use an explicit CORE type if it exists
|
||||
coretype = xmlutils.get_first_child_text_trim_with_attribute(device, 'type', 'domain', 'CORE')
|
||||
if coretype:
|
||||
return coretype
|
||||
return self.device_type(device)
|
||||
|
||||
def find_device_with_interface(self, interface_id):
|
||||
# TODO: suport generic 'device' elements
|
||||
for device in xmlutils.iter_descendants_with_name(self.scenario, self.device_types):
|
||||
interface = xmlutils.get_first_child_with_attribute(device, 'interface', 'id', interface_id)
|
||||
if interface:
|
||||
if_name = interface.getAttribute('name')
|
||||
return device, if_name
|
||||
return None, None
|
||||
|
||||
def parse_layer2_device(self, device):
|
||||
objid, device_name = self.get_common_attributes(device)
|
||||
logger.info('parsing layer-2 device: name=%s id=%s' % (device_name, objid))
|
||||
|
||||
try:
|
||||
return self.session.get_object(objid)
|
||||
except KeyError:
|
||||
logger.exception("error geting object: %s", objid)
|
||||
|
||||
device_type = self.device_type(device)
|
||||
if device_type == 'hub':
|
||||
device_class = nodeutils.get_node_class(NodeTypes.HUB)
|
||||
elif device_type == 'switch':
|
||||
device_class = nodeutils.get_node_class(NodeTypes.SWITCH)
|
||||
else:
|
||||
logger.warn('unknown layer-2 device type: \'%s\'' % device_type)
|
||||
assert False # XXX for testing
|
||||
|
||||
n = self.create_core_object(device_class, objid, device_name, device, None)
|
||||
return n
|
||||
|
||||
def parse_layer3_device(self, device):
|
||||
objid, device_name = self.get_common_attributes(device)
|
||||
logger.info('parsing layer-3 device: name=%s id=%s', device_name, objid)
|
||||
|
||||
try:
|
||||
return self.session.get_object(objid)
|
||||
except KeyError:
|
||||
logger.exception("error getting session object: %s", objid)
|
||||
|
||||
device_cls = self.nodecls
|
||||
core_node_type = self.core_node_type(device)
|
||||
n = self.create_core_object(device_cls, objid, device_name, device, core_node_type)
|
||||
n.type = core_node_type
|
||||
self.add_device_services(n, device, core_node_type)
|
||||
for interface in xmlutils.iter_children_with_name(device, 'interface'):
|
||||
self.parse_interface(n, device.getAttribute('id'), interface)
|
||||
return n
|
||||
|
||||
def parse_layer2_devices(self):
|
||||
"""
|
||||
Parse all layer-2 device elements. A device can be: 'switch',
|
||||
'hub'.
|
||||
"""
|
||||
# TODO: suport generic 'device' elements
|
||||
for device in xmlutils.iter_descendants_with_name(self.scenario, self.layer2_device_types):
|
||||
self.parse_layer2_device(device)
|
||||
|
||||
def parse_layer3_devices(self):
|
||||
"""
|
||||
Parse all layer-3 device elements. A device can be: 'host',
|
||||
'router'.
|
||||
"""
|
||||
# TODO: suport generic 'device' elements
|
||||
for device in xmlutils.iter_descendants_with_name(self.scenario, self.layer3_device_types):
|
||||
self.parse_layer3_device(device)
|
||||
|
||||
def parse_session_origin(self, session_config):
|
||||
"""
|
||||
Parse the first origin tag and set the CoreLocation reference
|
||||
point appropriately.
|
||||
"""
|
||||
# defaults from the CORE GUI
|
||||
self.session.location.setrefgeo(47.5791667, -122.132322, 2.0)
|
||||
self.session.location.refscale = 150.0
|
||||
origin = xmlutils.get_first_child_by_tag_name(session_config, 'origin')
|
||||
if not origin:
|
||||
return
|
||||
lat = origin.getAttribute('lat')
|
||||
lon = origin.getAttribute('lon')
|
||||
alt = origin.getAttribute('alt')
|
||||
if lat and lon and alt:
|
||||
self.session.location.setrefgeo(float(lat), float(lon), float(alt))
|
||||
self.location_refgeo_set = True
|
||||
scale100 = origin.getAttribute("scale100")
|
||||
if scale100:
|
||||
self.session.location.refscale = float(scale100)
|
||||
point = xmlutils.get_first_child_text_trim_by_tag_name(origin, 'point')
|
||||
if point:
|
||||
xyz = point.split(',')
|
||||
if len(xyz) == 2:
|
||||
xyz.append('0.0')
|
||||
if len(xyz) == 3:
|
||||
self.session.location.refxyz = (float(xyz[0]), float(xyz[1]), float(xyz[2]))
|
||||
self.location_refxyz_set = True
|
||||
|
||||
def parse_session_options(self, session_config):
|
||||
options = xmlutils.get_first_child_by_tag_name(session_config, 'options')
|
||||
if not options:
|
||||
return
|
||||
params = self.parse_parameter_children(options)
|
||||
for name, value in params.iteritems():
|
||||
if name and value:
|
||||
setattr(self.session.options, str(name), str(value))
|
||||
|
||||
def parse_session_hooks(self, session_config):
|
||||
"""
|
||||
Parse hook scripts.
|
||||
"""
|
||||
hooks = xmlutils.get_first_child_by_tag_name(session_config, 'hooks')
|
||||
if not hooks:
|
||||
return
|
||||
for hook in xmlutils.iter_children_with_name(hooks, 'hook'):
|
||||
filename = hook.getAttribute('name')
|
||||
state = hook.getAttribute('state')
|
||||
data = xmlutils.get_child_text_trim(hook)
|
||||
if data is None:
|
||||
data = '' # allow for empty file
|
||||
hook_type = "hook:%s" % state
|
||||
self.session.set_hook(hook_type, file_name=str(filename), source_name=None, data=str(data))
|
||||
|
||||
def parse_session_metadata(self, session_config):
|
||||
metadata = xmlutils.get_first_child_by_tag_name(session_config, 'metadata')
|
||||
if not metadata:
|
||||
return
|
||||
params = self.parse_parameter_children(metadata)
|
||||
for name, value in params.iteritems():
|
||||
if name and value:
|
||||
self.session.metadata.add_item(str(name), str(value))
|
||||
|
||||
def parse_session_config(self):
|
||||
session_config = xmlutils.get_first_child_by_tag_name(self.scenario, 'CORE:sessionconfig')
|
||||
if not session_config:
|
||||
return
|
||||
self.parse_session_origin(session_config)
|
||||
self.parse_session_options(session_config)
|
||||
self.parse_session_hooks(session_config)
|
||||
self.parse_session_metadata(session_config)
|
||||
|
||||
def parse_default_services(self):
|
||||
# defaults from the CORE GUI
|
||||
self.default_services = {
|
||||
'router': ['zebra', 'OSPFv2', 'OSPFv3', 'IPForward'],
|
||||
'host': ['DefaultRoute', 'SSH'],
|
||||
'PC': ['DefaultRoute', ],
|
||||
'mdr': ['zebra', 'OSPFv3MDR', 'IPForward'],
|
||||
}
|
||||
default_services = xmlutils.get_first_child_by_tag_name(self.scenario, 'CORE:defaultservices')
|
||||
if not default_services:
|
||||
return
|
||||
for device in xmlutils.iter_children_with_name(default_services, 'device'):
|
||||
device_type = device.getAttribute('type')
|
||||
if not device_type:
|
||||
logger.warn('parse_default_services: no type attribute found for device')
|
||||
continue
|
||||
services = []
|
||||
for service in xmlutils.iter_children_with_name(device, 'service'):
|
||||
name = service.getAttribute('name')
|
||||
if name:
|
||||
services.append(str(name))
|
||||
self.default_services[device_type] = services
|
||||
# store default services for the session
|
||||
for t, s in self.default_services.iteritems():
|
||||
self.session.services.defaultservices[t] = s
|
||||
logger.info('default services for node type \'%s\' set to: %s' % (t, s))
|
36
daemon/core/xml/xmlsession.py
Normal file
36
daemon/core/xml/xmlsession.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
"""
|
||||
Helpers for loading and saving XML files. savesessionxml(session, filename) is
|
||||
the main public interface here.
|
||||
"""
|
||||
|
||||
import os.path
|
||||
|
||||
from core.enumerations import NodeTypes
|
||||
from core.misc import nodeutils
|
||||
from core.xml.xmlparser import core_document_parser
|
||||
from core.xml.xmlwriter import core_document_writer
|
||||
|
||||
|
||||
def open_session_xml(session, filename, start=False, nodecls=None):
|
||||
"""
|
||||
Import a session from the EmulationScript XML format.
|
||||
"""
|
||||
|
||||
# set default node class when one is not provided
|
||||
if not nodecls:
|
||||
nodecls = nodeutils.get_node_class(NodeTypes.DEFAULT)
|
||||
|
||||
options = {'start': start, 'nodecls': nodecls}
|
||||
doc = core_document_parser(session, filename, options)
|
||||
if start:
|
||||
session.name = os.path.basename(filename)
|
||||
session.filename = filename
|
||||
session.instantiate()
|
||||
|
||||
|
||||
def save_session_xml(session, filename, version):
|
||||
"""
|
||||
Export a session to the EmulationScript XML format.
|
||||
"""
|
||||
doc = core_document_writer(session, version)
|
||||
doc.writexml(filename)
|
338
daemon/core/xml/xmlutils.py
Normal file
338
daemon/core/xml/xmlutils.py
Normal file
|
@ -0,0 +1,338 @@
|
|||
from xml.dom.minidom import Node
|
||||
|
||||
from core.misc import log
|
||||
from core.netns import nodes
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
def add_elements_from_list(dom, parent, iterable, name, attr_name):
|
||||
"""
|
||||
XML helper to iterate through a list and add items to parent using tags
|
||||
of the given name and the item value as an attribute named attr_name.
|
||||
Example: addelementsfromlist(dom, parent, ('a','b','c'), "letter", "value")
|
||||
<parent>
|
||||
<letter value="a"/>
|
||||
<letter value="b"/>
|
||||
<letter value="c"/>
|
||||
</parent>
|
||||
"""
|
||||
for item in iterable:
|
||||
element = dom.createElement(name)
|
||||
element.setAttribute(attr_name, item)
|
||||
parent.appendChild(element)
|
||||
|
||||
|
||||
def add_text_elements_from_list(dom, parent, iterable, name, attrs):
|
||||
"""
|
||||
XML helper to iterate through a list and add items to parent using tags
|
||||
of the given name, attributes specified in the attrs tuple, and having the
|
||||
text of the item within the tags.
|
||||
Example: addtextelementsfromlist(dom, parent, ('a','b','c'), "letter",
|
||||
(('show','True'),))
|
||||
<parent>
|
||||
<letter show="True">a</letter>
|
||||
<letter show="True">b</letter>
|
||||
<letter show="True">c</letter>
|
||||
</parent>
|
||||
"""
|
||||
for item in iterable:
|
||||
element = dom.createElement(name)
|
||||
for k, v in attrs:
|
||||
element.setAttribute(k, v)
|
||||
parent.appendChild(element)
|
||||
txt = dom.createTextNode(item)
|
||||
element.appendChild(txt)
|
||||
|
||||
|
||||
def add_text_elements_from_tuples(dom, parent, iterable, attrs=()):
|
||||
"""
|
||||
XML helper to iterate through a list of tuples and add items to
|
||||
parent using tags named for the first tuple element,
|
||||
attributes specified in the attrs tuple, and having the
|
||||
text of second tuple element.
|
||||
Example: addtextelementsfromtuples(dom, parent,
|
||||
(('first','a'),('second','b'),('third','c')),
|
||||
(('show','True'),))
|
||||
<parent>
|
||||
<first show="True">a</first>
|
||||
<second show="True">b</second>
|
||||
<third show="True">c</third>
|
||||
</parent>
|
||||
"""
|
||||
for name, value in iterable:
|
||||
element = dom.createElement(name)
|
||||
for k, v in attrs:
|
||||
element.setAttribute(k, v)
|
||||
parent.appendChild(element)
|
||||
txt = dom.createTextNode(value)
|
||||
element.appendChild(txt)
|
||||
|
||||
|
||||
def get_text_elements_to_list(parent):
|
||||
"""
|
||||
XML helper to parse child text nodes from the given parent and return
|
||||
a list of (key, value) tuples.
|
||||
"""
|
||||
r = []
|
||||
for n in parent.childNodes:
|
||||
if n.nodeType != Node.ELEMENT_NODE:
|
||||
continue
|
||||
k = str(n.nodeName)
|
||||
v = '' # sometimes want None here?
|
||||
for c in n.childNodes:
|
||||
if c.nodeType != Node.TEXT_NODE:
|
||||
continue
|
||||
v = str(c.nodeValue)
|
||||
break
|
||||
r.append((k, v))
|
||||
return r
|
||||
|
||||
|
||||
def add_param_to_parent(dom, parent, name, value):
|
||||
"""
|
||||
XML helper to add a <param name="name" value="value"/> tag to the parent
|
||||
element, when value is not None.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
p = dom.createElement("param")
|
||||
parent.appendChild(p)
|
||||
p.setAttribute("name", name)
|
||||
p.setAttribute("value", "%s" % value)
|
||||
return p
|
||||
|
||||
|
||||
def add_text_param_to_parent(dom, parent, name, value):
|
||||
"""
|
||||
XML helper to add a <param name="name">value</param> tag to the parent
|
||||
element, when value is not None.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
p = dom.createElement("param")
|
||||
parent.appendChild(p)
|
||||
p.setAttribute("name", name)
|
||||
txt = dom.createTextNode(value)
|
||||
p.appendChild(txt)
|
||||
return p
|
||||
|
||||
|
||||
def add_param_list_to_parent(dom, parent, name, values):
|
||||
"""
|
||||
XML helper to return a parameter list and optionally add it to the
|
||||
parent element:
|
||||
<paramlist name="name">
|
||||
<item value="123">
|
||||
<item value="456">
|
||||
</paramlist>
|
||||
"""
|
||||
if values is None:
|
||||
return None
|
||||
p = dom.createElement("paramlist")
|
||||
if parent:
|
||||
parent.appendChild(p)
|
||||
p.setAttribute("name", name)
|
||||
for v in values:
|
||||
item = dom.createElement("item")
|
||||
item.setAttribute("value", str(v))
|
||||
p.appendChild(item)
|
||||
return p
|
||||
|
||||
|
||||
def get_one_element(dom, name):
|
||||
e = dom.getElementsByTagName(name)
|
||||
if len(e) == 0:
|
||||
return None
|
||||
return e[0]
|
||||
|
||||
|
||||
def iter_descendants(dom, max_depth=0):
|
||||
"""
|
||||
Iterate over all descendant element nodes in breadth first order.
|
||||
Only consider nodes up to max_depth deep when max_depth is greater
|
||||
than zero.
|
||||
"""
|
||||
nodes = [dom]
|
||||
depth = 0
|
||||
current_depth_nodes = 1
|
||||
next_depth_nodes = 0
|
||||
while nodes:
|
||||
n = nodes.pop(0)
|
||||
for child in n.childNodes:
|
||||
if child.nodeType == Node.ELEMENT_NODE:
|
||||
yield child
|
||||
nodes.append(child)
|
||||
next_depth_nodes += 1
|
||||
current_depth_nodes -= 1
|
||||
if current_depth_nodes == 0:
|
||||
depth += 1
|
||||
if max_depth > 0 and depth == max_depth:
|
||||
return
|
||||
current_depth_nodes = next_depth_nodes
|
||||
next_depth_nodes = 0
|
||||
|
||||
|
||||
def iter_matching_descendants(dom, match_function, max_depth=0):
|
||||
"""
|
||||
Iterate over descendant elements where matchFunction(descendant)
|
||||
returns true. Only consider nodes up to max_depth deep when
|
||||
max_depth is greater than zero.
|
||||
"""
|
||||
for d in iter_descendants(dom, max_depth):
|
||||
if match_function(d):
|
||||
yield d
|
||||
|
||||
|
||||
def iter_descendants_with_name(dom, tag_name, max_depth=0):
|
||||
"""
|
||||
Iterate over descendant elements whose name is contained in
|
||||
tagName (or is named tagName if tagName is a string). Only
|
||||
consider nodes up to max_depth deep when max_depth is greater than
|
||||
zero.
|
||||
"""
|
||||
if isinstance(tag_name, basestring):
|
||||
tag_name = (tag_name,)
|
||||
|
||||
def match(d):
|
||||
return d.tagName in tag_name
|
||||
|
||||
return iter_matching_descendants(dom, match, max_depth)
|
||||
|
||||
|
||||
def iter_descendants_with_attribute(dom, tag_name, attr_name, attr_value, max_depth=0):
|
||||
"""
|
||||
Iterate over descendant elements whose name is contained in
|
||||
tagName (or is named tagName if tagName is a string) and have an
|
||||
attribute named attrName with value attrValue. Only consider
|
||||
nodes up to max_depth deep when max_depth is greater than zero.
|
||||
"""
|
||||
if isinstance(tag_name, basestring):
|
||||
tag_name = (tag_name,)
|
||||
|
||||
def match(d):
|
||||
return d.tagName in tag_name and \
|
||||
d.getAttribute(attr_name) == attr_value
|
||||
|
||||
return iter_matching_descendants(dom, match, max_depth)
|
||||
|
||||
|
||||
def iter_children(dom, node_type):
|
||||
"""
|
||||
Iterate over all child elements of the given type.
|
||||
"""
|
||||
for child in dom.childNodes:
|
||||
if child.nodeType == node_type:
|
||||
yield child
|
||||
|
||||
|
||||
def get_text_child(dom):
|
||||
"""
|
||||
Return the text node of the given element.
|
||||
"""
|
||||
for child in iter_children(dom, Node.TEXT_NODE):
|
||||
return str(child.nodeValue)
|
||||
return None
|
||||
|
||||
|
||||
def get_child_text_trim(dom):
|
||||
text = get_text_child(dom)
|
||||
if text:
|
||||
text = text.strip()
|
||||
return text
|
||||
|
||||
|
||||
def get_params_set_attrs(dom, param_names, target):
|
||||
"""
|
||||
XML helper to get <param name="name" value="value"/> tags and set
|
||||
the attribute in the target object. String type is used. Target object
|
||||
attribute is unchanged if the XML attribute is not present.
|
||||
"""
|
||||
params = dom.getElementsByTagName("param")
|
||||
for param in params:
|
||||
param_name = param.getAttribute("name")
|
||||
value = param.getAttribute("value")
|
||||
if value is None:
|
||||
continue # never reached?
|
||||
if param_name in param_names:
|
||||
setattr(target, param_name, str(value))
|
||||
|
||||
|
||||
def xml_type_to_node_class(session, type):
|
||||
"""
|
||||
Helper to convert from a type string to a class name in nodes.*.
|
||||
"""
|
||||
if hasattr(nodes, type):
|
||||
# TODO: remove and use a mapping to known nodes
|
||||
logger.error("using eval to retrieve node type: %s", type)
|
||||
return eval("nodes.%s" % type)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def iter_children_with_name(dom, tag_name):
|
||||
return iter_descendants_with_name(dom, tag_name, 1)
|
||||
|
||||
|
||||
def iter_children_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
return iter_descendants_with_attribute(dom, tag_name, attr_name, attr_value, 1)
|
||||
|
||||
|
||||
def get_first_child_by_tag_name(dom, tag_name):
|
||||
"""
|
||||
Return the first child element whose name is contained in tagName
|
||||
(or is named tagName if tagName is a string).
|
||||
"""
|
||||
for child in iter_children_with_name(dom, tag_name):
|
||||
return child
|
||||
return None
|
||||
|
||||
|
||||
def get_first_child_text_by_tag_name(dom, tag_name):
|
||||
"""
|
||||
Return the corresponding text of the first child element whose
|
||||
name is contained in tagName (or is named tagName if tagName is a
|
||||
string).
|
||||
"""
|
||||
child = get_first_child_by_tag_name(dom, tag_name)
|
||||
if child:
|
||||
return get_text_child(child)
|
||||
return None
|
||||
|
||||
|
||||
def get_first_child_text_trim_by_tag_name(dom, tag_name):
|
||||
text = get_first_child_text_by_tag_name(dom, tag_name)
|
||||
if text:
|
||||
text = text.strip()
|
||||
return text
|
||||
|
||||
|
||||
def get_first_child_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
"""
|
||||
Return the first child element whose name is contained in tagName
|
||||
(or is named tagName if tagName is a string) that has an attribute
|
||||
named attrName with value attrValue.
|
||||
"""
|
||||
for child in \
|
||||
iter_children_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
return child
|
||||
return None
|
||||
|
||||
|
||||
def get_first_child_text_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
"""
|
||||
Return the corresponding text of the first child element whose
|
||||
name is contained in tagName (or is named tagName if tagName is a
|
||||
string) that has an attribute named attrName with value attrValue.
|
||||
"""
|
||||
child = get_first_child_with_attribute(dom, tag_name, attr_name, attr_value)
|
||||
if child:
|
||||
return get_text_child(child)
|
||||
return None
|
||||
|
||||
|
||||
def get_first_child_text_trim_with_attribute(dom, tag_name, attr_name, attr_value):
|
||||
text = get_first_child_text_with_attribute(dom, tag_name, attr_name, attr_value)
|
||||
if text:
|
||||
text = text.strip()
|
||||
return text
|
12
daemon/core/xml/xmlwriter.py
Normal file
12
daemon/core/xml/xmlwriter.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from core.xml.xmlwriter0 import CoreDocumentWriter0
|
||||
from core.xml.xmlwriter1 import CoreDocumentWriter1
|
||||
|
||||
|
||||
def core_document_writer(session, version):
|
||||
if version == '0.0':
|
||||
doc = CoreDocumentWriter0(session)
|
||||
elif version == '1.0':
|
||||
doc = CoreDocumentWriter1(session)
|
||||
else:
|
||||
raise ValueError('unsupported document version: %s' % version)
|
||||
return doc
|
389
daemon/core/xml/xmlwriter0.py
Normal file
389
daemon/core/xml/xmlwriter0.py
Normal file
|
@ -0,0 +1,389 @@
|
|||
import os
|
||||
from xml.dom.minidom import Document
|
||||
|
||||
import pwd
|
||||
|
||||
from core.coreobj import PyCoreNet
|
||||
from core.coreobj import PyCoreNode
|
||||
from core.enumerations import RegisterTlvs
|
||||
from core.misc import log
|
||||
from core.xml import xmlutils
|
||||
|
||||
logger = log.get_logger(__name__)
|
||||
|
||||
|
||||
class CoreDocumentWriter0(Document):
|
||||
"""
|
||||
Utility class for writing a CoreSession to XML. The init method builds
|
||||
an xml.dom.minidom.Document, and the writexml() method saves the XML file.
|
||||
"""
|
||||
|
||||
def __init__(self, session):
|
||||
"""
|
||||
Create an empty Scenario XML Document, then populate it with
|
||||
objects from the given session.
|
||||
"""
|
||||
Document.__init__(self)
|
||||
self.session = session
|
||||
self.scenario = self.createElement("Scenario")
|
||||
self.np = self.createElement("NetworkPlan")
|
||||
self.mp = self.createElement("MotionPlan")
|
||||
self.sp = self.createElement("ServicePlan")
|
||||
self.meta = self.createElement("CoreMetaData")
|
||||
|
||||
self.appendChild(self.scenario)
|
||||
self.scenario.appendChild(self.np)
|
||||
self.scenario.appendChild(self.mp)
|
||||
self.scenario.appendChild(self.sp)
|
||||
self.scenario.appendChild(self.meta)
|
||||
|
||||
self.populatefromsession()
|
||||
|
||||
def populatefromsession(self):
|
||||
self.session.emane.setup() # not during runtime?
|
||||
self.addorigin()
|
||||
self.adddefaultservices()
|
||||
self.addnets()
|
||||
self.addnodes()
|
||||
self.addmetadata()
|
||||
|
||||
def writexml(self, filename):
|
||||
logger.info("saving session XML file %s", filename)
|
||||
f = open(filename, "w")
|
||||
Document.writexml(self, writer=f, indent="", addindent=" ", newl="\n", encoding="UTF-8")
|
||||
f.close()
|
||||
if self.session.user is not None:
|
||||
uid = pwd.getpwnam(self.session.user).pw_uid
|
||||
gid = os.stat(self.session.sessiondir).st_gid
|
||||
os.chown(filename, uid, gid)
|
||||
|
||||
def addnets(self):
|
||||
"""
|
||||
Add PyCoreNet objects as NetworkDefinition XML elements.
|
||||
"""
|
||||
with self.session._objects_lock:
|
||||
for net in self.session.objects.itervalues():
|
||||
if not isinstance(net, PyCoreNet):
|
||||
continue
|
||||
self.addnet(net)
|
||||
|
||||
def addnet(self, net):
|
||||
"""
|
||||
Add one PyCoreNet object as a NetworkDefinition XML element.
|
||||
"""
|
||||
n = self.createElement("NetworkDefinition")
|
||||
self.np.appendChild(n)
|
||||
n.setAttribute("name", net.name)
|
||||
# could use net.brname
|
||||
n.setAttribute("id", "%s" % net.objid)
|
||||
n.setAttribute("type", "%s" % net.__class__.__name__)
|
||||
self.addnetinterfaces(n, net)
|
||||
# key used with tunnel node
|
||||
if hasattr(net, 'grekey') and net.grekey is not None:
|
||||
n.setAttribute("key", "%s" % net.grekey)
|
||||
# link parameters
|
||||
for netif in net.netifs(sort=True):
|
||||
self.addnetem(n, netif)
|
||||
# wireless/mobility models
|
||||
modelconfigs = net.session.mobility.getmodels(net)
|
||||
modelconfigs += net.session.emane.getmodels(net)
|
||||
self.addmodels(n, modelconfigs)
|
||||
self.addposition(net)
|
||||
|
||||
def addnetem(self, n, netif):
|
||||
"""
|
||||
Similar to addmodels(); used for writing netem link effects
|
||||
parameters. TODO: Interface parameters should be moved to the model
|
||||
construct, then this separate method shouldn't be required.
|
||||
"""
|
||||
params = netif.getparams()
|
||||
if len(params) == 0:
|
||||
return
|
||||
model = self.createElement("model")
|
||||
model.setAttribute("name", "netem")
|
||||
model.setAttribute("netif", netif.name)
|
||||
if hasattr(netif, "node") and netif.node is not None:
|
||||
model.setAttribute("peer", netif.node.name)
|
||||
# link between switches uses one veth interface
|
||||
elif hasattr(netif, "othernet") and netif.othernet is not None:
|
||||
if netif.othernet.name == n.getAttribute("name"):
|
||||
model.setAttribute("peer", netif.net.name)
|
||||
else:
|
||||
model.setAttribute("peer", netif.othernet.name)
|
||||
model.setAttribute("netif", netif.localname)
|
||||
# hack used for upstream parameters for link between switches
|
||||
# (see LxBrNet.linknet())
|
||||
if netif.othernet.objid == int(n.getAttribute("id")):
|
||||
netif.swapparams('_params_up')
|
||||
params = netif.getparams()
|
||||
netif.swapparams('_params_up')
|
||||
has_params = False
|
||||
for k, v in params:
|
||||
# default netem parameters are 0 or None
|
||||
if v is None or v == 0:
|
||||
continue
|
||||
if k == "has_netem" or k == "has_tbf":
|
||||
continue
|
||||
key = self.createElement(k)
|
||||
key.appendChild(self.createTextNode("%s" % v))
|
||||
model.appendChild(key)
|
||||
has_params = True
|
||||
if has_params:
|
||||
n.appendChild(model)
|
||||
|
||||
def addmodels(self, n, configs):
|
||||
"""
|
||||
Add models from a list of model-class, config values tuples.
|
||||
"""
|
||||
for m, conf in configs:
|
||||
model = self.createElement("model")
|
||||
n.appendChild(model)
|
||||
model.setAttribute("name", m._name)
|
||||
type = "wireless"
|
||||
if m._type == RegisterTlvs.MOBILITY.value:
|
||||
type = "mobility"
|
||||
model.setAttribute("type", type)
|
||||
for i, k in enumerate(m.getnames()):
|
||||
key = self.createElement(k)
|
||||
value = conf[i]
|
||||
if value is None:
|
||||
value = ""
|
||||
key.appendChild(self.createTextNode("%s" % value))
|
||||
model.appendChild(key)
|
||||
|
||||
def addnodes(self):
|
||||
"""
|
||||
Add PyCoreNode objects as node XML elements.
|
||||
"""
|
||||
with self.session._objects_lock:
|
||||
for node in self.session.objects.itervalues():
|
||||
if not isinstance(node, PyCoreNode):
|
||||
continue
|
||||
self.addnode(node)
|
||||
|
||||
def addnode(self, node):
|
||||
"""
|
||||
Add a PyCoreNode object as node XML elements.
|
||||
"""
|
||||
n = self.createElement("Node")
|
||||
self.np.appendChild(n)
|
||||
n.setAttribute("name", node.name)
|
||||
n.setAttribute("id", "%s" % node.nodeid())
|
||||
if node.type:
|
||||
n.setAttribute("type", node.type)
|
||||
self.addinterfaces(n, node)
|
||||
self.addposition(node)
|
||||
xmlutils.add_param_to_parent(self, n, "icon", node.icon)
|
||||
xmlutils.add_param_to_parent(self, n, "canvas", node.canvas)
|
||||
self.addservices(node)
|
||||
|
||||
def addinterfaces(self, n, node):
|
||||
"""
|
||||
Add PyCoreNetIfs to node XML elements.
|
||||
"""
|
||||
for ifc in node.netifs(sort=True):
|
||||
i = self.createElement("interface")
|
||||
n.appendChild(i)
|
||||
i.setAttribute("name", ifc.name)
|
||||
netmodel = None
|
||||
if ifc.net:
|
||||
i.setAttribute("net", ifc.net.name)
|
||||
if hasattr(ifc.net, "model"):
|
||||
netmodel = ifc.net.model
|
||||
if ifc.mtu and ifc.mtu != 1500:
|
||||
i.setAttribute("mtu", "%s" % ifc.mtu)
|
||||
# could use ifc.params, transport_type
|
||||
self.addaddresses(i, ifc)
|
||||
# per-interface models
|
||||
if netmodel and netmodel._name[:6] == "emane_":
|
||||
cfg = self.session.emane.getifcconfig(node.objid, netmodel._name,
|
||||
None, ifc)
|
||||
if cfg:
|
||||
self.addmodels(i, ((netmodel, cfg),))
|
||||
|
||||
def addnetinterfaces(self, n, net):
|
||||
"""
|
||||
Similar to addinterfaces(), but only adds interface elements to the
|
||||
supplied XML node that would not otherwise appear in the Node elements.
|
||||
These are any interfaces that link two switches/hubs together.
|
||||
"""
|
||||
for ifc in net.netifs(sort=True):
|
||||
if not hasattr(ifc, "othernet") or not ifc.othernet:
|
||||
continue
|
||||
i = self.createElement("interface")
|
||||
n.appendChild(i)
|
||||
if net.objid == ifc.net.objid:
|
||||
i.setAttribute("name", ifc.localname)
|
||||
i.setAttribute("net", ifc.othernet.name)
|
||||
else:
|
||||
i.setAttribute("name", ifc.name)
|
||||
i.setAttribute("net", ifc.net.name)
|
||||
|
||||
def addposition(self, node):
|
||||
"""
|
||||
Add object coordinates as location XML element.
|
||||
"""
|
||||
(x, y, z) = node.position.get()
|
||||
if x is None or y is None:
|
||||
return
|
||||
# <Node name="n1">
|
||||
mpn = self.createElement("Node")
|
||||
mpn.setAttribute("name", node.name)
|
||||
self.mp.appendChild(mpn)
|
||||
|
||||
# <motion type="stationary">
|
||||
motion = self.createElement("motion")
|
||||
motion.setAttribute("type", "stationary")
|
||||
mpn.appendChild(motion)
|
||||
|
||||
# <point>$X$,$Y$,$Z$</point>
|
||||
pt = self.createElement("point")
|
||||
motion.appendChild(pt)
|
||||
coordstxt = "%s,%s" % (x, y)
|
||||
if z:
|
||||
coordstxt += ",%s" % z
|
||||
coords = self.createTextNode(coordstxt)
|
||||
pt.appendChild(coords)
|
||||
|
||||
def addorigin(self):
|
||||
"""
|
||||
Add origin to Motion Plan using canvas reference point.
|
||||
The CoreLocation class maintains this reference point.
|
||||
"""
|
||||
refgeo = self.session.location.refgeo
|
||||
origin = self.createElement("origin")
|
||||
attrs = ("lat", "lon", "alt")
|
||||
have_origin = False
|
||||
for i in xrange(3):
|
||||
if refgeo[i] is not None:
|
||||
origin.setAttribute(attrs[i], str(refgeo[i]))
|
||||
have_origin = True
|
||||
if not have_origin:
|
||||
return
|
||||
if self.session.location.refscale != 1.0: # 100 pixels = refscale m
|
||||
origin.setAttribute("scale100", str(self.session.location.refscale))
|
||||
if self.session.location.refxyz != (0.0, 0.0, 0.0):
|
||||
pt = self.createElement("point")
|
||||
origin.appendChild(pt)
|
||||
x, y, z = self.session.location.refxyz
|
||||
coordstxt = "%s,%s" % (x, y)
|
||||
if z:
|
||||
coordstxt += ",%s" % z
|
||||
coords = self.createTextNode(coordstxt)
|
||||
pt.appendChild(coords)
|
||||
|
||||
self.mp.appendChild(origin)
|
||||
|
||||
def adddefaultservices(self):
|
||||
"""
|
||||
Add default services and node types to the ServicePlan.
|
||||
"""
|
||||
for type in self.session.services.defaultservices:
|
||||
defaults = self.session.services.getdefaultservices(type)
|
||||
spn = self.createElement("Node")
|
||||
spn.setAttribute("type", type)
|
||||
self.sp.appendChild(spn)
|
||||
for svc in defaults:
|
||||
s = self.createElement("Service")
|
||||
spn.appendChild(s)
|
||||
s.setAttribute("name", str(svc._name))
|
||||
|
||||
def addservices(self, node):
|
||||
"""
|
||||
Add services and their customizations to the ServicePlan.
|
||||
"""
|
||||
if len(node.services) == 0:
|
||||
return
|
||||
defaults = self.session.services.getdefaultservices(node.type)
|
||||
if node.services == defaults:
|
||||
return
|
||||
spn = self.createElement("Node")
|
||||
spn.setAttribute("name", node.name)
|
||||
self.sp.appendChild(spn)
|
||||
|
||||
for svc in node.services:
|
||||
s = self.createElement("Service")
|
||||
spn.appendChild(s)
|
||||
s.setAttribute("name", str(svc._name))
|
||||
s.setAttribute("startup_idx", str(svc._startindex))
|
||||
if svc._starttime != "":
|
||||
s.setAttribute("start_time", str(svc._starttime))
|
||||
# only record service names if not a customized service
|
||||
if not svc._custom:
|
||||
continue
|
||||
s.setAttribute("custom", str(svc._custom))
|
||||
xmlutils.add_elements_from_list(self, s, svc._dirs, "Directory", "name")
|
||||
|
||||
for fn in svc._configs:
|
||||
if len(fn) == 0:
|
||||
continue
|
||||
f = self.createElement("File")
|
||||
f.setAttribute("name", fn)
|
||||
# all file names are added to determine when a file has been deleted
|
||||
s.appendChild(f)
|
||||
data = self.session.services.getservicefiledata(svc, fn)
|
||||
if data is None:
|
||||
# this includes only customized file contents and skips
|
||||
# the auto-generated files
|
||||
continue
|
||||
txt = self.createTextNode(data)
|
||||
f.appendChild(txt)
|
||||
|
||||
xmlutils.add_text_elements_from_list(self, s, svc._startup, "Command", (("type", "start"),))
|
||||
xmlutils.add_text_elements_from_list(self, s, svc._shutdown, "Command", (("type", "stop"),))
|
||||
xmlutils.add_text_elements_from_list(self, s, svc._validate, "Command", (("type", "validate"),))
|
||||
|
||||
def addaddresses(self, i, netif):
|
||||
"""
|
||||
Add MAC and IP addresses to interface XML elements.
|
||||
"""
|
||||
if netif.hwaddr:
|
||||
h = self.createElement("address")
|
||||
i.appendChild(h)
|
||||
h.setAttribute("type", "mac")
|
||||
htxt = self.createTextNode("%s" % netif.hwaddr)
|
||||
h.appendChild(htxt)
|
||||
for addr in netif.addrlist:
|
||||
a = self.createElement("address")
|
||||
i.appendChild(a)
|
||||
# a.setAttribute("type", )
|
||||
atxt = self.createTextNode("%s" % addr)
|
||||
a.appendChild(atxt)
|
||||
|
||||
def addhooks(self):
|
||||
"""
|
||||
Add hook script XML elements to the metadata tag.
|
||||
"""
|
||||
hooks = self.createElement("Hooks")
|
||||
for state in sorted(self.session._hooks.keys()):
|
||||
for filename, data in self.session._hooks[state]:
|
||||
hook = self.createElement("Hook")
|
||||
hook.setAttribute("name", filename)
|
||||
hook.setAttribute("state", str(state))
|
||||
txt = self.createTextNode(data)
|
||||
hook.appendChild(txt)
|
||||
hooks.appendChild(hook)
|
||||
if hooks.hasChildNodes():
|
||||
self.meta.appendChild(hooks)
|
||||
|
||||
def addmetadata(self):
|
||||
"""
|
||||
Add CORE-specific session meta-data XML elements.
|
||||
"""
|
||||
# options
|
||||
options = self.createElement("SessionOptions")
|
||||
defaults = self.session.options.getdefaultvalues()
|
||||
for i, (k, v) in enumerate(self.session.options.getkeyvaluelist()):
|
||||
if str(v) != str(defaults[i]):
|
||||
xmlutils.add_text_param_to_parent(self, options, k, v)
|
||||
# addparamtoparent(self, options, k, v)
|
||||
if options.hasChildNodes():
|
||||
self.meta.appendChild(options)
|
||||
# hook scripts
|
||||
self.addhooks()
|
||||
# meta
|
||||
meta = self.createElement("MetaData")
|
||||
self.meta.appendChild(meta)
|
||||
for k, v in self.session.metadata.items():
|
||||
xmlutils.add_text_param_to_parent(self, meta, k, v)
|
||||
# addparamtoparent(self, meta, k, v)
|
1018
daemon/core/xml/xmlwriter1.py
Normal file
1018
daemon/core/xml/xmlwriter1.py
Normal file
File diff suppressed because it is too large
Load diff
8
daemon/requirements.txt
Normal file
8
daemon/requirements.txt
Normal file
|
@ -0,0 +1,8 @@
|
|||
enum34==1.1.6
|
||||
grpcio==1.0.0
|
||||
grpcio-tools==1.0.0
|
||||
pycco==0.5.1
|
||||
sphinx==1.4.8
|
||||
sphinx_rtd_theme==0.1.9
|
||||
pytest==3.0.7
|
||||
pytest-runner==2.11.1
|
2
daemon/setup.cfg
Normal file
2
daemon/setup.cfg
Normal file
|
@ -0,0 +1,2 @@
|
|||
[aliases]
|
||||
test=pytest
|
133
daemon/tests/conftest.py
Normal file
133
daemon/tests/conftest.py
Normal file
|
@ -0,0 +1,133 @@
|
|||
"""
|
||||
Unit test fixture module.
|
||||
"""
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from core.session import Session
|
||||
from core.misc import ipaddress
|
||||
from core.misc import nodemaps
|
||||
from core.misc import nodeutils
|
||||
from core.netns import nodes
|
||||
|
||||
|
||||
class Core(object):
|
||||
def __init__(self, session, ip_prefix):
|
||||
self.session = session
|
||||
self.ip_prefix = ip_prefix
|
||||
self.current_ip = 1
|
||||
self.nodes = {}
|
||||
self.node_ips = {}
|
||||
|
||||
def create_node(self, name):
|
||||
node = self.session.add_object(cls=nodes.CoreNode, name=name)
|
||||
self.nodes[name] = node
|
||||
|
||||
def add_interface(self, network, name):
|
||||
node_ip = self.ip_prefix.addr(self.current_ip)
|
||||
self.current_ip += 1
|
||||
self.node_ips[name] = node_ip
|
||||
node = self.nodes[name]
|
||||
interface_id = node.newnetif(network, ["%s/%s" % (node_ip, self.ip_prefix.prefixlen)])
|
||||
return node.netif(interface_id)
|
||||
|
||||
def get_node(self, name):
|
||||
"""
|
||||
Retrieve node from current session.
|
||||
|
||||
:param str name: name of node to retrieve
|
||||
:return: core node
|
||||
:rtype: core.netns.nodes.CoreNode
|
||||
"""
|
||||
return self.nodes[name]
|
||||
|
||||
def get_ip(self, name):
|
||||
return self.node_ips[name]
|
||||
|
||||
def link(self, network, from_interface, to_interface):
|
||||
network.link(from_interface, to_interface)
|
||||
|
||||
def configure_link(self, network, interface_one, interface_two, values, unidirectional=False):
|
||||
network.linkconfig(netif=interface_one, netif2=interface_two, **values)
|
||||
|
||||
if not unidirectional:
|
||||
network.linkconfig(netif=interface_two, netif2=interface_one, **values)
|
||||
|
||||
def ping(self, from_name, to_name):
|
||||
from_node = self.nodes[from_name]
|
||||
to_ip = str(self.get_ip(to_name))
|
||||
return from_node.cmd(["ping", "-c", "3", to_ip])
|
||||
|
||||
def ping_output(self, from_name, to_name):
|
||||
from_node = self.nodes[from_name]
|
||||
to_ip = str(self.get_ip(to_name))
|
||||
vcmd, stdin, stdout, stderr = from_node.popen(["ping", "-i", "0.05", "-c", "3", to_ip])
|
||||
return stdout.read().strip()
|
||||
|
||||
def iping(self, from_name, to_name):
|
||||
from_node = self.nodes[from_name]
|
||||
to_ip = str(self.get_ip(to_name))
|
||||
from_node.icmd(["ping", "-i", "0.01", "-c", "10", to_ip])
|
||||
|
||||
def iperf(self, from_name, to_name):
|
||||
from_node = self.nodes[from_name]
|
||||
to_node = self.nodes[to_name]
|
||||
to_ip = str(self.get_ip(to_name))
|
||||
|
||||
# run iperf server, run client, kill iperf server
|
||||
vcmd, stdin, stdout, stderr = to_node.popen(["iperf", "-s", "-u", "-y", "C"])
|
||||
from_node.cmd(["iperf", "-u", "-t", "5", "-c", to_ip])
|
||||
to_node.cmd(["killall", "-9", "iperf"])
|
||||
|
||||
return stdout.read().strip()
|
||||
|
||||
def assert_nodes(self):
|
||||
for node in self.nodes.itervalues():
|
||||
assert os.path.exists(node.nodedir)
|
||||
|
||||
def create_link_network(self):
|
||||
# create switch
|
||||
ptp_node = self.session.add_object(cls=nodes.PtpNet)
|
||||
|
||||
# create nodes
|
||||
self.create_node("n1")
|
||||
self.create_node("n2")
|
||||
|
||||
# add interfaces
|
||||
interface_one = self.add_interface(ptp_node, "n1")
|
||||
interface_two = self.add_interface(ptp_node, "n2")
|
||||
|
||||
# instantiate session
|
||||
self.session.instantiate()
|
||||
|
||||
# assert node directories created
|
||||
self.assert_nodes()
|
||||
|
||||
return ptp_node, interface_one, interface_two
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def session():
|
||||
# configure default nodes
|
||||
node_map = nodemaps.CLASSIC_NODES
|
||||
nodeutils.set_node_map(node_map)
|
||||
|
||||
# create and return session
|
||||
session_fixture = Session(1, persistent=True)
|
||||
assert os.path.exists(session_fixture.session_dir)
|
||||
yield session_fixture
|
||||
|
||||
# cleanup
|
||||
print "shutting down session"
|
||||
session_fixture.shutdown()
|
||||
assert not os.path.exists(session_fixture.session_dir)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def ip_prefix():
|
||||
return ipaddress.Ipv4Prefix("10.83.0.0/16")
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def core(session, ip_prefix):
|
||||
return Core(session, ip_prefix)
|
326
daemon/tests/test_core.py
Normal file
326
daemon/tests/test_core.py
Normal file
|
@ -0,0 +1,326 @@
|
|||
"""
|
||||
Unit tests for testing with a CORE switch.
|
||||
"""
|
||||
|
||||
import time
|
||||
|
||||
from core.mobility import BasicRangeModel
|
||||
from core.netns import nodes
|
||||
from core.phys.pnodes import PhysicalNode
|
||||
|
||||
|
||||
class TestCore:
|
||||
def test_physical(self, core):
|
||||
"""
|
||||
Test physical node network.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create switch node
|
||||
switch_node = core.session.add_object(cls=nodes.SwitchNode)
|
||||
|
||||
# create a physical node
|
||||
physical_node = core.session.add_object(cls=PhysicalNode, name="p1")
|
||||
core.nodes[physical_node.name] = physical_node
|
||||
|
||||
# create regular node
|
||||
core.create_node("n1")
|
||||
|
||||
# add interface
|
||||
core.add_interface(switch_node, "n1")
|
||||
core.add_interface(switch_node, "p1")
|
||||
|
||||
# instantiate session
|
||||
core.session.instantiate()
|
||||
|
||||
# assert node directories created
|
||||
core.assert_nodes()
|
||||
|
||||
# ping n2 from n1 and assert success
|
||||
status = core.ping("n1", "p1")
|
||||
assert not status
|
||||
|
||||
def test_ptp(self, core):
|
||||
"""
|
||||
Test ptp node network.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create ptp
|
||||
ptp_node = core.session.add_object(cls=nodes.PtpNet)
|
||||
|
||||
# create nodes
|
||||
core.create_node("n1")
|
||||
core.create_node("n2")
|
||||
|
||||
# add interfaces
|
||||
core.add_interface(ptp_node, "n1")
|
||||
core.add_interface(ptp_node, "n2")
|
||||
|
||||
# instantiate session
|
||||
core.session.instantiate()
|
||||
|
||||
# assert node directories created
|
||||
core.assert_nodes()
|
||||
|
||||
# ping n2 from n1 and assert success
|
||||
status = core.ping("n1", "n2")
|
||||
assert not status
|
||||
|
||||
def test_hub(self, core):
|
||||
"""
|
||||
Test basic hub network.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create hub
|
||||
hub_node = core.session.add_object(cls=nodes.HubNode)
|
||||
|
||||
# create nodes
|
||||
core.create_node("n1")
|
||||
core.create_node("n2")
|
||||
|
||||
# add interfaces
|
||||
core.add_interface(hub_node, "n1")
|
||||
core.add_interface(hub_node, "n2")
|
||||
|
||||
# instantiate session
|
||||
core.session.instantiate()
|
||||
|
||||
# assert node directories created
|
||||
core.assert_nodes()
|
||||
|
||||
# ping n2 from n1 and assert success
|
||||
status = core.ping("n1", "n2")
|
||||
assert not status
|
||||
|
||||
def test_switch(self, core):
|
||||
"""
|
||||
Test basic switch network.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create switch
|
||||
switch_node = core.session.add_object(cls=nodes.SwitchNode)
|
||||
|
||||
# create nodes
|
||||
core.create_node("n1")
|
||||
core.create_node("n2")
|
||||
|
||||
# add interfaces
|
||||
core.add_interface(switch_node, "n1")
|
||||
core.add_interface(switch_node, "n2")
|
||||
|
||||
# instantiate session
|
||||
core.session.instantiate()
|
||||
|
||||
# assert node directories created
|
||||
core.assert_nodes()
|
||||
|
||||
# ping n2 from n1 and assert success
|
||||
status = core.ping("n1", "n2")
|
||||
assert not status
|
||||
|
||||
def test_wlan_basic_range_good(self, core):
|
||||
"""
|
||||
Test basic wlan network.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create wlan
|
||||
wlan_node = core.session.add_object(cls=nodes.WlanNode)
|
||||
values = BasicRangeModel.getdefaultvalues()
|
||||
wlan_node.setmodel(BasicRangeModel, values)
|
||||
|
||||
# create nodes
|
||||
core.create_node("n1")
|
||||
core.create_node("n2")
|
||||
|
||||
# add interfaces
|
||||
interface_one = core.add_interface(wlan_node, "n1")
|
||||
interface_two = core.add_interface(wlan_node, "n2")
|
||||
|
||||
# link nodes in wlan
|
||||
core.link(wlan_node, interface_one, interface_two)
|
||||
|
||||
# mark node position as together
|
||||
core.get_node("n1").setposition(0, 0)
|
||||
core.get_node("n2").setposition(0, 0)
|
||||
|
||||
# instantiate session
|
||||
core.session.instantiate()
|
||||
|
||||
# assert node directories created
|
||||
core.assert_nodes()
|
||||
|
||||
# ping n2 from n1 and assert success
|
||||
status = core.ping("n1", "n2")
|
||||
assert not status
|
||||
|
||||
def test_wlan_basic_range_bad(self, core):
|
||||
"""
|
||||
Test basic wlan network with leveraging basic range model.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create wlan
|
||||
wlan_node = core.session.add_object(cls=nodes.WlanNode)
|
||||
values = BasicRangeModel.getdefaultvalues()
|
||||
wlan_node.setmodel(BasicRangeModel, values)
|
||||
|
||||
# create nodes
|
||||
core.create_node("n1")
|
||||
core.create_node("n2")
|
||||
|
||||
# add interfaces
|
||||
interface_one = core.add_interface(wlan_node, "n1")
|
||||
interface_two = core.add_interface(wlan_node, "n2")
|
||||
|
||||
# link nodes in wlan
|
||||
core.link(wlan_node, interface_one, interface_two)
|
||||
|
||||
# move nodes out of range, default range check is 275
|
||||
core.get_node("n1").setposition(0, 0)
|
||||
core.get_node("n2").setposition(500, 500)
|
||||
|
||||
# instantiate session
|
||||
core.session.instantiate()
|
||||
|
||||
# assert node directories created
|
||||
core.assert_nodes()
|
||||
|
||||
# ping n2 from n1 and assert failure
|
||||
time.sleep(1)
|
||||
status = core.ping("n1", "n2")
|
||||
assert status
|
||||
|
||||
def test_link_bandwidth(self, core):
|
||||
"""
|
||||
Test ptp node network with modifying link bandwidth.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create link network
|
||||
ptp_node, interface_one, interface_two = core.create_link_network()
|
||||
|
||||
# output csv index
|
||||
bandwidth_index = 8
|
||||
|
||||
# run iperf, validate normal bandwidth
|
||||
stdout = core.iperf("n1", "n2")
|
||||
assert stdout
|
||||
value = int(stdout.split(',')[bandwidth_index])
|
||||
assert 900000 <= value <= 1100000
|
||||
|
||||
# change bandwidth in bits per second
|
||||
bandwidth = 500000
|
||||
core.configure_link(ptp_node, interface_one, interface_two, {
|
||||
"bw": bandwidth
|
||||
})
|
||||
|
||||
# run iperf again
|
||||
stdout = core.iperf("n1", "n2")
|
||||
assert stdout
|
||||
value = int(stdout.split(',')[bandwidth_index])
|
||||
assert 400000 <= value <= 600000
|
||||
|
||||
def test_link_loss(self, core):
|
||||
"""
|
||||
Test ptp node network with modifying link packet loss.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create link network
|
||||
ptp_node, interface_one, interface_two = core.create_link_network()
|
||||
|
||||
# output csv index
|
||||
loss_index = -2
|
||||
|
||||
# run iperf, validate normal bandwidth
|
||||
stdout = core.iperf("n1", "n2")
|
||||
assert stdout
|
||||
value = float(stdout.split(',')[loss_index])
|
||||
assert 0 <= value <= 0.5
|
||||
|
||||
# change bandwidth in bits per second
|
||||
loss = 50
|
||||
core.configure_link(ptp_node, interface_one, interface_two, {
|
||||
"loss": loss
|
||||
})
|
||||
|
||||
# run iperf again
|
||||
stdout = core.iperf("n1", "n2")
|
||||
assert stdout
|
||||
value = float(stdout.split(',')[loss_index])
|
||||
assert 45 <= value <= 55
|
||||
|
||||
def test_link_delay(self, core):
|
||||
"""
|
||||
Test ptp node network with modifying link packet delay.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create link network
|
||||
ptp_node, interface_one, interface_two = core.create_link_network()
|
||||
|
||||
# run ping for delay information
|
||||
stdout = core.ping_output("n1", "n2")
|
||||
assert stdout
|
||||
rtt_line = stdout.split("\n")[-1]
|
||||
rtt_values = rtt_line.split("=")[1].split("ms")[0].strip()
|
||||
rtt_avg = float(rtt_values.split("/")[2])
|
||||
assert 0 <= rtt_avg <= 0.1
|
||||
|
||||
# change delay in microseconds
|
||||
delay = 1000000
|
||||
core.configure_link(ptp_node, interface_one, interface_two, {
|
||||
"delay": delay
|
||||
})
|
||||
|
||||
# run ping for delay information again
|
||||
stdout = core.ping_output("n1", "n2")
|
||||
assert stdout
|
||||
rtt_line = stdout.split("\n")[-1]
|
||||
rtt_values = rtt_line.split("=")[1].split("ms")[0].strip()
|
||||
rtt_avg = float(rtt_values.split("/")[2])
|
||||
assert 1800 <= rtt_avg <= 2200
|
||||
|
||||
def test_link_jitter(self, core):
|
||||
"""
|
||||
Test ptp node network with modifying link packet jitter.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
# create link network
|
||||
ptp_node, interface_one, interface_two = core.create_link_network()
|
||||
|
||||
# output csv index
|
||||
jitter_index = 9
|
||||
|
||||
# run iperf
|
||||
stdout = core.iperf("n1", "n2")
|
||||
assert stdout
|
||||
value = float(stdout.split(",")[jitter_index])
|
||||
assert -0.5 <= value <= 0.05
|
||||
|
||||
# change jitter in microseconds
|
||||
jitter = 1000000
|
||||
core.configure_link(ptp_node, interface_one, interface_two, {
|
||||
"jitter": jitter
|
||||
})
|
||||
|
||||
# run iperf again
|
||||
stdout = core.iperf("n1", "n2")
|
||||
assert stdout
|
||||
value = float(stdout.split(",")[jitter_index])
|
||||
assert 200 <= value <= 500
|
120
daemon/tests/test_gui.py
Normal file
120
daemon/tests/test_gui.py
Normal file
|
@ -0,0 +1,120 @@
|
|||
"""
|
||||
Unit tests for testing with a CORE switch.
|
||||
"""
|
||||
from core.api import coreapi, dataconversion
|
||||
from core.api.coreapi import CoreExecuteTlv
|
||||
from core.enumerations import CORE_API_PORT, EventTypes, EventTlvs, MessageFlags, LinkTlvs, LinkTypes, ExecuteTlvs, \
|
||||
MessageTypes
|
||||
from core.misc import ipaddress
|
||||
from core.netns.nodes import SwitchNode, CoreNode
|
||||
|
||||
|
||||
def cmd(node, exec_cmd):
|
||||
"""
|
||||
Convenience method for sending commands to a node using the legacy API.
|
||||
|
||||
:param node: The node the command should be issued too
|
||||
:param exec_cmd: A string with the command to be run
|
||||
:return: Returns the result of the command
|
||||
"""
|
||||
# Set up the command api message
|
||||
tlv_data = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.objid)
|
||||
tlv_data += CoreExecuteTlv.pack(ExecuteTlvs.NUMBER.value, 1)
|
||||
tlv_data += CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, exec_cmd)
|
||||
message = coreapi.CoreExecMessage.pack(MessageFlags.STRING.value | MessageFlags.TEXT.value, tlv_data)
|
||||
node.session.broker.handlerawmsg(message)
|
||||
|
||||
# Now wait for the response
|
||||
server = node.session.broker.servers["localhost"]
|
||||
server.sock.settimeout(50.0)
|
||||
|
||||
# receive messages until we get our execute response
|
||||
result = None
|
||||
while True:
|
||||
message_header = server.sock.recv(coreapi.CoreMessage.header_len)
|
||||
message_type, message_flags, message_length = coreapi.CoreMessage.unpack_header(message_header)
|
||||
message_data = server.sock.recv(message_length)
|
||||
|
||||
# If we get the right response return the results
|
||||
print "received response message: %s" % MessageTypes(message_type)
|
||||
if message_type == MessageTypes.EXECUTE.value:
|
||||
message = coreapi.CoreExecMessage(message_flags, message_header, message_data)
|
||||
result = message.get_tlv(ExecuteTlvs.RESULT.value)
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class TestGui:
|
||||
def test_broker(self, core):
|
||||
"""
|
||||
Test session broker creation.
|
||||
|
||||
:param conftest.Core core: core fixture to test with
|
||||
"""
|
||||
|
||||
prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
|
||||
daemon = "localhost"
|
||||
|
||||
# add server
|
||||
core.session.broker.addserver(daemon, "127.0.0.1", CORE_API_PORT)
|
||||
|
||||
# setup server
|
||||
core.session.broker.setupserver(daemon)
|
||||
|
||||
# do not want the recvloop running as we will deal ourselves
|
||||
core.session.broker.dorecvloop = False
|
||||
|
||||
# have broker handle a configuration state change
|
||||
core.session.set_state(EventTypes.CONFIGURATION_STATE.value)
|
||||
tlv_data = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value, EventTypes.CONFIGURATION_STATE.value)
|
||||
raw_event_message = coreapi.CoreEventMessage.pack(0, tlv_data)
|
||||
core.session.broker.handlerawmsg(raw_event_message)
|
||||
|
||||
# create a switch node
|
||||
switch = core.session.add_object(cls=SwitchNode, name="switch", start=False)
|
||||
switch.setposition(x=80, y=50)
|
||||
switch.server = daemon
|
||||
|
||||
# retrieve switch data representation, create a switch message for broker to handle
|
||||
switch_data = switch.data(MessageFlags.ADD.value)
|
||||
switch_message = dataconversion.convert_node(switch_data)
|
||||
core.session.broker.handlerawmsg(switch_message)
|
||||
|
||||
# create node one
|
||||
core.create_node("n1")
|
||||
node_one = core.get_node("n1")
|
||||
node_one.server = daemon
|
||||
|
||||
# create node two
|
||||
core.create_node("n2")
|
||||
node_two = core.get_node("n2")
|
||||
node_two.server = daemon
|
||||
|
||||
# create node messages for the broker to handle
|
||||
for node in [node_one, node_two]:
|
||||
node_data = node.data(MessageFlags.ADD.value)
|
||||
node_message = dataconversion.convert_node(node_data)
|
||||
core.session.broker.handlerawmsg(node_message)
|
||||
|
||||
# create links to switch from nodes for broker to handle
|
||||
for index, node in enumerate([node_one, node_two], start=1):
|
||||
tlv_data = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.objid)
|
||||
tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, node.objid)
|
||||
tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value)
|
||||
tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, 0)
|
||||
ip4_address = prefix.addr(index)
|
||||
tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4.value, ip4_address)
|
||||
tlv_data += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4_MASK.value, prefix.prefixlen)
|
||||
raw_link_message = coreapi.CoreLinkMessage.pack(MessageFlags.ADD.value, tlv_data)
|
||||
core.session.broker.handlerawmsg(raw_link_message)
|
||||
|
||||
# change session to instantiation state
|
||||
tlv_data = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value, EventTypes.INSTANTIATION_STATE.value)
|
||||
raw_event_message = coreapi.CoreEventMessage.pack(0, tlv_data)
|
||||
core.session.broker.handlerawmsg(raw_event_message)
|
||||
|
||||
# Get the ip or last node and ping it from the first
|
||||
print "pinging from the first to the last node"
|
||||
pingip = cmd(node_one, "ip -4 -o addr show dev eth0").split()[3].split("/")[0]
|
||||
print cmd(node_two, "ping -c 5 " + pingip)
|
Loading…
Reference in a new issue