merged cleanup branch with master

This commit is contained in:
Rod A Santiago 2017-06-19 18:09:28 -07:00
parent 0a91fe7a3e
commit 55a6e2dcef
81 changed files with 11596 additions and 15021 deletions

4
.gitignore vendored
View file

@ -16,3 +16,7 @@ configure
core-*.tar.gz core-*.tar.gz
debian debian
stamp-h1 stamp-h1
# intelli
*.iml
.idea

View file

@ -38,6 +38,9 @@ To build this software you should use:
make make
sudo make install sudo make install
Note: You may need to pass the proxy settings to sudo make install:
sudo make install HTTP_PROXY=<proxy>
Here is what is installed with 'make install': Here is what is installed with 'make install':
/usr/local/bin/core-gui /usr/local/bin/core-gui

View file

@ -1,23 +1,17 @@
# Copyright (c)2010-2012 the Boeing Company. # Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution. # See the LICENSE file included in this distribution.
"""core """
core
Top-level Python package containing CORE components. Top-level Python package containing CORE components.
See http://www.nrl.navy.mil/itd/ncs/products/core and See http://www.nrl.navy.mil/itd/ncs/products/core for more information on CORE.
http://code.google.com/p/coreemu/ for more information on CORE.
Pieces can be imported individually, for example Pieces can be imported individually, for example
import core.netns.vnode from core.netns import vnode
or everything listed in __all__ can be imported using
from core import *
""" """
__all__ = []
# Automatically import all add-ons listed in addons.__all__ # Automatically import all add-ons listed in addons.__all__
from addons import * from addons import *

View file

@ -1,6 +1,6 @@
"""Optional add-ons
Add on files can be put in this directory. Everything listed in
__all__ is automatically loaded by the main core module.
""" """
Optional add ons can be put in this directory. Everything listed in __all__ is automatically
loaded by the main core module.
"""
__all__ = [] __all__ = []

View file

@ -0,0 +1,3 @@
"""
Contains code specific to the legacy TCP API for interacting with the TCL based GUI.
"""

File diff suppressed because it is too large Load diff

View file

@ -1,333 +0,0 @@
#
# CORE
# Copyright (c)2010-2013 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Tom Goff <thomas.goff@boeing.com>
#
'''
data.py: constant definitions for the CORE API, enumerating the
different message and TLV types (these constants are also found in coreapi.h)
'''
def enumdict(d):
for k, v in d.iteritems():
exec "%s = %s" % (v, k) in globals()
# Constants
CORE_API_VER = "1.23"
CORE_API_PORT = 4038
# Message types
message_types = {
0x01: "CORE_API_NODE_MSG",
0x02: "CORE_API_LINK_MSG",
0x03: "CORE_API_EXEC_MSG",
0x04: "CORE_API_REG_MSG",
0x05: "CORE_API_CONF_MSG",
0x06: "CORE_API_FILE_MSG",
0x07: "CORE_API_IFACE_MSG",
0x08: "CORE_API_EVENT_MSG",
0x09: "CORE_API_SESS_MSG",
0x0A: "CORE_API_EXCP_MSG",
0x0B: "CORE_API_MSG_MAX",
}
enumdict(message_types)
# Generic Message Flags
message_flags = {
0x01: "CORE_API_ADD_FLAG",
0x02: "CORE_API_DEL_FLAG",
0x04: "CORE_API_CRI_FLAG",
0x08: "CORE_API_LOC_FLAG",
0x10: "CORE_API_STR_FLAG",
0x20: "CORE_API_TXT_FLAG",
0x40: "CORE_API_TTY_FLAG",
}
enumdict(message_flags)
# Node Message TLV Types
node_tlvs = {
0x01: "CORE_TLV_NODE_NUMBER",
0x02: "CORE_TLV_NODE_TYPE",
0x03: "CORE_TLV_NODE_NAME",
0x04: "CORE_TLV_NODE_IPADDR",
0x05: "CORE_TLV_NODE_MACADDR",
0x06: "CORE_TLV_NODE_IP6ADDR",
0x07: "CORE_TLV_NODE_MODEL",
0x08: "CORE_TLV_NODE_EMUSRV",
0x0A: "CORE_TLV_NODE_SESSION",
0x20: "CORE_TLV_NODE_XPOS",
0x21: "CORE_TLV_NODE_YPOS",
0x22: "CORE_TLV_NODE_CANVAS",
0x23: "CORE_TLV_NODE_EMUID",
0x24: "CORE_TLV_NODE_NETID",
0x25: "CORE_TLV_NODE_SERVICES",
0x30: "CORE_TLV_NODE_LAT",
0x31: "CORE_TLV_NODE_LONG",
0x32: "CORE_TLV_NODE_ALT",
0x42: "CORE_TLV_NODE_ICON",
0x50: "CORE_TLV_NODE_OPAQUE",
}
enumdict(node_tlvs)
node_types = dict(enumerate([
"CORE_NODE_DEF",
"CORE_NODE_PHYS",
"CORE_NODE_XEN",
"CORE_NODE_TBD",
"CORE_NODE_SWITCH",
"CORE_NODE_HUB",
"CORE_NODE_WLAN",
"CORE_NODE_RJ45",
"CORE_NODE_TUNNEL",
"CORE_NODE_KTUNNEL",
"CORE_NODE_EMANE",
]))
enumdict(node_types)
rj45_models = dict(enumerate([
"RJ45_MODEL_LINKED",
"RJ45_MODEL_WIRELESS",
"RJ45_MODEL_INSTALLED",
]))
enumdict(rj45_models)
# Link Message TLV Types
link_tlvs = {
0x01: "CORE_TLV_LINK_N1NUMBER",
0x02: "CORE_TLV_LINK_N2NUMBER",
0x03: "CORE_TLV_LINK_DELAY",
0x04: "CORE_TLV_LINK_BW",
0x05: "CORE_TLV_LINK_PER",
0x06: "CORE_TLV_LINK_DUP",
0x07: "CORE_TLV_LINK_JITTER",
0x08: "CORE_TLV_LINK_MER",
0x09: "CORE_TLV_LINK_BURST",
CORE_TLV_NODE_SESSION: "CORE_TLV_LINK_SESSION",
0x10: "CORE_TLV_LINK_MBURST",
0x20: "CORE_TLV_LINK_TYPE",
0x21: "CORE_TLV_LINK_GUIATTR",
0x22: "CORE_TLV_LINK_UNI",
0x23: "CORE_TLV_LINK_EMUID",
0x24: "CORE_TLV_LINK_NETID",
0x25: "CORE_TLV_LINK_KEY",
0x30: "CORE_TLV_LINK_IF1NUM",
0x31: "CORE_TLV_LINK_IF1IP4",
0x32: "CORE_TLV_LINK_IF1IP4MASK",
0x33: "CORE_TLV_LINK_IF1MAC",
0x34: "CORE_TLV_LINK_IF1IP6",
0x35: "CORE_TLV_LINK_IF1IP6MASK",
0x36: "CORE_TLV_LINK_IF2NUM",
0x37: "CORE_TLV_LINK_IF2IP4",
0x38: "CORE_TLV_LINK_IF2IP4MASK",
0x39: "CORE_TLV_LINK_IF2MAC",
0x40: "CORE_TLV_LINK_IF2IP6",
0x41: "CORE_TLV_LINK_IF2IP6MASK",
0x42: "CORE_TLV_LINK_IF1NAME",
0x43: "CORE_TLV_LINK_IF2NAME",
0x50: "CORE_TLV_LINK_OPAQUE",
}
enumdict(link_tlvs)
link_types = dict(enumerate([
"CORE_LINK_WIRELESS",
"CORE_LINK_WIRED",
]))
enumdict(link_types)
# Execute Message TLV Types
exec_tlvs = {
0x01: "CORE_TLV_EXEC_NODE",
0x02: "CORE_TLV_EXEC_NUM",
0x03: "CORE_TLV_EXEC_TIME",
0x04: "CORE_TLV_EXEC_CMD",
0x05: "CORE_TLV_EXEC_RESULT",
0x06: "CORE_TLV_EXEC_STATUS",
CORE_TLV_NODE_SESSION: "CORE_TLV_EXEC_SESSION",
}
enumdict(exec_tlvs)
# Register Message TLV Types
reg_tlvs = {
0x01: "CORE_TLV_REG_WIRELESS",
0x02: "CORE_TLV_REG_MOBILITY",
0x03: "CORE_TLV_REG_UTILITY",
0x04: "CORE_TLV_REG_EXECSRV",
0x05: "CORE_TLV_REG_GUI",
0x06: "CORE_TLV_REG_EMULSRV",
CORE_TLV_NODE_SESSION: "CORE_TLV_REG_SESSION",
}
enumdict(reg_tlvs)
# Configuration Message TLV Types
conf_tlvs = {
0x01: "CORE_TLV_CONF_NODE",
0x02: "CORE_TLV_CONF_OBJ",
0x03: "CORE_TLV_CONF_TYPE",
0x04: "CORE_TLV_CONF_DATA_TYPES",
0x05: "CORE_TLV_CONF_VALUES",
0x06: "CORE_TLV_CONF_CAPTIONS",
0x07: "CORE_TLV_CONF_BITMAP",
0x08: "CORE_TLV_CONF_POSSIBLE_VALUES",
0x09: "CORE_TLV_CONF_GROUPS",
CORE_TLV_NODE_SESSION: "CORE_TLV_CONF_SESSION",
0x0B: "CORE_TLV_CONF_IFNUM",
CORE_TLV_NODE_NETID: "CORE_TLV_CONF_NETID",
0x50: "CORE_TLV_CONF_OPAQUE",
}
enumdict(conf_tlvs)
conf_flags = {
0x00: "CONF_TYPE_FLAGS_NONE",
0x01: "CONF_TYPE_FLAGS_REQUEST",
0x02: "CONF_TYPE_FLAGS_UPDATE",
0x03: "CONF_TYPE_FLAGS_RESET",
}
enumdict(conf_flags)
conf_data_types = {
0x01: "CONF_DATA_TYPE_UINT8",
0x02: "CONF_DATA_TYPE_UINT16",
0x03: "CONF_DATA_TYPE_UINT32",
0x04: "CONF_DATA_TYPE_UINT64",
0x05: "CONF_DATA_TYPE_INT8",
0x06: "CONF_DATA_TYPE_INT16",
0x07: "CONF_DATA_TYPE_INT32",
0x08: "CONF_DATA_TYPE_INT64",
0x09: "CONF_DATA_TYPE_FLOAT",
0x0A: "CONF_DATA_TYPE_STRING",
0x0B: "CONF_DATA_TYPE_BOOL",
}
enumdict(conf_data_types)
# File Message TLV Types
file_tlvs = {
0x01: "CORE_TLV_FILE_NODE",
0x02: "CORE_TLV_FILE_NAME",
0x03: "CORE_TLV_FILE_MODE",
0x04: "CORE_TLV_FILE_NUM",
0x05: "CORE_TLV_FILE_TYPE",
0x06: "CORE_TLV_FILE_SRCNAME",
CORE_TLV_NODE_SESSION: "CORE_TLV_FILE_SESSION",
0x10: "CORE_TLV_FILE_DATA",
0x11: "CORE_TLV_FILE_CMPDATA",
}
enumdict(file_tlvs)
# Interface Message TLV Types
iface_tlvs = {
0x01: "CORE_TLV_IFACE_NODE",
0x02: "CORE_TLV_IFACE_NUM",
0x03: "CORE_TLV_IFACE_NAME",
0x04: "CORE_TLV_IFACE_IPADDR",
0x05: "CORE_TLV_IFACE_MASK",
0x06: "CORE_TLV_IFACE_MACADDR",
0x07: "CORE_TLV_IFACE_IP6ADDR",
0x08: "CORE_TLV_IFACE_IP6MASK",
0x09: "CORE_TLV_IFACE_TYPE",
CORE_TLV_NODE_SESSION: "CORE_TLV_IFACE_SESSION",
0x0B: "CORE_TLV_IFACE_STATE",
CORE_TLV_NODE_EMUID: "CORE_TLV_IFACE_EMUID",
CORE_TLV_NODE_NETID: "CORE_TLV_IFACE_NETID",
}
enumdict(iface_tlvs)
# Event Message TLV Types
event_tlvs = {
0x01: "CORE_TLV_EVENT_NODE",
0x02: "CORE_TLV_EVENT_TYPE",
0x03: "CORE_TLV_EVENT_NAME",
0x04: "CORE_TLV_EVENT_DATA",
0x05: "CORE_TLV_EVENT_TIME",
CORE_TLV_NODE_SESSION: "CORE_TLV_EVENT_SESSION",
}
enumdict(event_tlvs)
event_types = dict(enumerate([
"CORE_EVENT_NONE",
"CORE_EVENT_DEFINITION_STATE",
"CORE_EVENT_CONFIGURATION_STATE",
"CORE_EVENT_INSTANTIATION_STATE",
"CORE_EVENT_RUNTIME_STATE",
"CORE_EVENT_DATACOLLECT_STATE",
"CORE_EVENT_SHUTDOWN_STATE",
"CORE_EVENT_START",
"CORE_EVENT_STOP",
"CORE_EVENT_PAUSE",
"CORE_EVENT_RESTART",
"CORE_EVENT_FILE_OPEN",
"CORE_EVENT_FILE_SAVE",
"CORE_EVENT_SCHEDULED",
"CORE_EVENT_RECONFIGURE",
"CORE_EVENT_INSTANTIATION_COMPLETE",
]))
enumdict(event_types)
# Session Message TLV Types
session_tlvs = {
0x01: "CORE_TLV_SESS_NUMBER",
0x02: "CORE_TLV_SESS_NAME",
0x03: "CORE_TLV_SESS_FILE",
0x04: "CORE_TLV_SESS_NODECOUNT",
0x05: "CORE_TLV_SESS_DATE",
0x06: "CORE_TLV_SESS_THUMB",
0x07: "CORE_TLV_SESS_USER",
0x0A: "CORE_TLV_SESS_OPAQUE",
}
enumdict(session_tlvs)
# Exception Message TLV Types
exception_tlvs = {
0x01: "CORE_TLV_EXCP_NODE",
0x02: "CORE_TLV_EXCP_SESSION",
0x03: "CORE_TLV_EXCP_LEVEL",
0x04: "CORE_TLV_EXCP_SOURCE",
0x05: "CORE_TLV_EXCP_DATE",
0x06: "CORE_TLV_EXCP_TEXT",
0x0A: "CORE_TLV_EXCP_OPAQUE",
}
enumdict(exception_tlvs)
exception_levels = dict(enumerate([
"CORE_EXCP_LEVEL_NONE",
"CORE_EXCP_LEVEL_FATAL",
"CORE_EXCP_LEVEL_ERROR",
"CORE_EXCP_LEVEL_WARNING",
"CORE_EXCP_LEVEL_NOTICE",
]))
enumdict(exception_levels)
del enumdict

File diff suppressed because it is too large Load diff

View file

@ -5,61 +5,69 @@
# #
# authors: core-dev@pf.itd.nrl.navy.mil # authors: core-dev@pf.itd.nrl.navy.mil
# #
'''
"""
netgraph.py: Netgraph helper functions; for now these are wrappers around netgraph.py: Netgraph helper functions; for now these are wrappers around
ngctl commands. ngctl commands.
''' """
import subprocess import subprocess
from core.misc.utils import *
from core.constants import *
checkexec([NGCTL_BIN]) from core import constants
from core.misc import utils
utils.checkexec([constants.NGCTL_BIN])
def createngnode(type, hookstr, name=None): def createngnode(type, hookstr, name=None):
''' Create a new Netgraph node of type and optionally assign name. The """
Create a new Netgraph node of type and optionally assign name. The
hook string hookstr should contain two names. This is a string so hook string hookstr should contain two names. This is a string so
other commands may be inserted after the two names. other commands may be inserted after the two names.
Return the name and netgraph ID of the new node. Return the name and netgraph ID of the new node.
''' """
hook1 = hookstr.split()[0] hook1 = hookstr.split()[0]
ngcmd = "mkpeer %s %s \n show .%s" % (type, hookstr, hook1) ngcmd = "mkpeer %s %s \n show .%s" % (type, hookstr, hook1)
cmd = [NGCTL_BIN, "-f", "-"] cmd = [constants.NGCTL_BIN, "-f", "-"]
cmdid = subprocess.Popen(cmd, stdin = subprocess.PIPE, cmdid = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout = subprocess.PIPE, stdout=subprocess.PIPE,
stderr = subprocess.STDOUT) stderr=subprocess.STDOUT)
result, err = cmdid.communicate(input = ngcmd) # err will always be None # err will always be None
result, err = cmdid.communicate(input=ngcmd)
status = cmdid.wait() status = cmdid.wait()
if status > 0: if status > 0:
raise Exception, "error creating Netgraph node %s (%s): %s" % \ raise Exception("error creating Netgraph node %s (%s): %s" % (type, ngcmd, result))
(type, ngcmd, result)
results = result.split() results = result.split()
ngname = results[1] ngname = results[1]
ngid = results[5] ngid = results[5]
if name: if name:
check_call([NGCTL_BIN, "name", "[0x%s]:" % ngid, name]) utils.check_call([constants.NGCTL_BIN, "name", "[0x%s]:" % ngid, name])
return (ngname, ngid) return ngname, ngid
def destroyngnode(name): def destroyngnode(name):
''' Shutdown a Netgraph node having the given name. """ Shutdown a Netgraph node having the given name.
''' """
check_call([NGCTL_BIN, "shutdown", "%s:" % name]) utils.check_call([constants.NGCTL_BIN, "shutdown", "%s:" % name])
def connectngnodes(name1, name2, hook1, hook2): def connectngnodes(name1, name2, hook1, hook2):
''' Connect two hooks of two Netgraph nodes given by their names. """ Connect two hooks of two Netgraph nodes given by their names.
''' """
node1 = "%s:" % name1 node1 = "%s:" % name1
node2 = "%s:" % name2 node2 = "%s:" % name2
check_call([NGCTL_BIN, "connect", node1, node2, hook1, hook2]) utils.check_call([constants.NGCTL_BIN, "connect", node1, node2, hook1, hook2])
def ngmessage(name, msg): def ngmessage(name, msg):
''' Send a Netgraph message to the node named name. """ Send a Netgraph message to the node named name.
''' """
cmd = [NGCTL_BIN, "msg", "%s:" % name] + msg cmd = [constants.NGCTL_BIN, "msg", "%s:" % name] + msg
check_call(cmd) utils.check_call(cmd)
def ngloadkernelmodule(name): def ngloadkernelmodule(name):
''' Load a kernel module by invoking kldstat. This is needed for the """ Load a kernel module by invoking kldstat. This is needed for the
ng_ether module which automatically creates Netgraph nodes when loaded. ng_ether module which automatically creates Netgraph nodes when loaded.
''' """
mutecall(["kldload", name]) utils.mutecall(["kldload", name])

View file

@ -6,132 +6,133 @@
# author: core-dev@pf.itd.nrl.navy.mil # author: core-dev@pf.itd.nrl.navy.mil
# #
''' """
nodes.py: definition of CoreNode classes and other node classes that inherit nodes.py: definition of CoreNode classes and other node classes that inherit
from the CoreNode, implementing specific node types. from the CoreNode, implementing specific node types.
''' """
from vnode import * import socket
from vnet import *
from core.constants import * from core import constants
from core.misc.ipaddr import *
from core.api import coreapi from core.api import coreapi
from core.bsd.netgraph import connectngnodes
from core.bsd.netgraph import ngloadkernelmodule from core.bsd.netgraph import ngloadkernelmodule
from core.bsd.vnet import NetgraphNet
from core.bsd.vnet import NetgraphPipeNet
from core.bsd.vnode import JailNode
from core.enumerations import LinkTlvs
from core.enumerations import LinkTypes
from core.enumerations import NodeTypes
from core.enumerations import RegisterTlvs
from core.misc import ipaddress
from core.misc import utils
utils.checkexec([constants.IFCONFIG_BIN])
checkexec([IFCONFIG_BIN])
class CoreNode(JailNode): class CoreNode(JailNode):
apitype = coreapi.CORE_NODE_DEF apitype = NodeTypes.DEFAULT.value
class PtpNet(NetgraphPipeNet): class PtpNet(NetgraphPipeNet):
def tonodemsg(self, flags): def tonodemsg(self, flags):
''' Do not generate a Node Message for point-to-point links. They are """ Do not generate a Node Message for point-to-point links. They are
built using a link message instead. built using a link message instead.
''' """
pass pass
def tolinkmsgs(self, flags): def tolinkmsgs(self, flags):
''' Build CORE API TLVs for a point-to-point link. One Link message """ Build CORE API TLVs for a point-to-point link. One Link message
describes this network. describes this network.
''' """
tlvdata = "" tlvdata = ""
if len(self._netif) != 2: if len(self._netif) != 2:
return tlvdata return tlvdata
(if1, if2) = self._netif.items() (if1, if2) = self._netif.items()
if1 = if1[1] if1 = if1[1]
if2 = if2[1] if2 = if2[1]
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, if1.node.objid)
if1.node.objid) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, if2.node.objid)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER, delay = if1.getparam("delay")
if2.node.objid) bw = if1.getparam("bw")
delay = if1.getparam('delay') loss = if1.getparam("loss")
bw = if1.getparam('bw') duplicate = if1.getparam("duplicate")
loss = if1.getparam('loss') jitter = if1.getparam("jitter")
duplicate = if1.getparam('duplicate')
jitter = if1.getparam('jitter')
if delay is not None: if delay is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_DELAY, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DELAY.value, delay)
delay)
if bw is not None: if bw is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_BW, bw) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.BANDWIDTH.value, bw)
if loss is not None: if loss is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_PER, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.PER.value, str(loss))
str(loss))
if duplicate is not None: if duplicate is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_DUP, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DUP.value, str(duplicate))
str(duplicate))
if jitter is not None: if jitter is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_JITTER, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.JITTER.value, jitter)
jitter) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, self.linktype)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_TYPE,
self.linktype)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \ tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE1_NUMBER.value, if1.node.getifindex(if1))
if1.node.getifindex(if1))
if if1.hwaddr: if if1.hwaddr:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1MAC, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE1_MAC.value, if1.hwaddr)
if1.hwaddr)
for addr in if1.addrlist: for addr in if1.addrlist:
(ip, sep, mask) = addr.partition('/') (ip, sep, mask) = addr.partition("/")
mask = int(mask) mask = int(mask)
if isIPv4Address(ip): if ipaddress.is_ipv4_address(ip):
family = AF_INET family = socket.AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP4 tlvtypeip = LinkTlvs.INTERFACE1_IP4.value
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP4MASK tlvtypemask = LinkTlvs.INTERFACE1_IP4_MASK
else: else:
family = AF_INET6 family = socket.AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP6 tlvtypeip = LinkTlvs.INTERFACE1_IP6.value
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP6MASK tlvtypemask = LinkTlvs.INTERFACE1_IP6_MASK.value
ipl = socket.inet_pton(family, ip) ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, ipaddress.IpAddress(af=family, address=ipl))
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask) tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \ tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, if2.node.getifindex(if2))
if2.node.getifindex(if2))
if if2.hwaddr: if if2.hwaddr:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2MAC, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_MAC.value, if2.hwaddr)
if2.hwaddr)
for addr in if2.addrlist: for addr in if2.addrlist:
(ip, sep, mask) = addr.partition('/') (ip, sep, mask) = addr.partition("/")
mask = int(mask) mask = int(mask)
if isIPv4Address(ip): if ipaddress.is_ipv4_address(ip):
family = AF_INET family = socket.AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP4 tlvtypeip = LinkTlvs.INTERFACE2_IP4.value
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP4MASK tlvtypemask = LinkTlvs.INTERFACE2_IP4_MASK
else: else:
family = AF_INET6 family = socket.AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP6 tlvtypeip = LinkTlvs.INTERFACE2_IP6.value
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP6MASK tlvtypemask = LinkTlvs.INTERFACE2_IP6_MASK.value
ipl = socket.inet_pton(family, ip) ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, ipaddress.IpAddress(af=family, address=ipl))
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask) tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata) msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
return [msg,] return [msg, ]
class SwitchNode(NetgraphNet): class SwitchNode(NetgraphNet):
ngtype = "bridge" ngtype = "bridge"
nghooks = "link0 link0\nmsg .link0 setpersistent" nghooks = "link0 link0\nmsg .link0 setpersistent"
apitype = coreapi.CORE_NODE_SWITCH apitype = NodeTypes.SWITCH.value
policy = "ACCEPT" policy = "ACCEPT"
class HubNode(NetgraphNet): class HubNode(NetgraphNet):
ngtype = "hub" ngtype = "hub"
nghooks = "link0 link0\nmsg .link0 setpersistent" nghooks = "link0 link0\nmsg .link0 setpersistent"
apitype = coreapi.CORE_NODE_HUB apitype = NodeTypes.HUB.value
policy = "ACCEPT" policy = "ACCEPT"
class WlanNode(NetgraphNet): class WlanNode(NetgraphNet):
ngtype = "wlan" ngtype = "wlan"
nghooks = "anchor anchor" nghooks = "anchor anchor"
apitype = coreapi.CORE_NODE_WLAN apitype = NodeTypes.WIRELESS_LAN.value
linktype = coreapi.CORE_LINK_WIRELESS linktype = LinkTypes.WIRELESS.value
policy = "DROP" policy = "DROP"
def __init__(self, session, objid = None, name = None, verbose = False, def __init__(self, session, objid=None, name=None, verbose=False,
start = True, policy = None): start=True, policy=None):
NetgraphNet.__init__(self, session, objid, name, verbose, start, policy) NetgraphNet.__init__(self, session, objid, name, verbose, start, policy)
# wireless model such as basic range # wireless model such as basic range
self.model = None self.model = None
@ -144,34 +145,34 @@ class WlanNode(NetgraphNet):
netif.poshook = self.model._positioncallback netif.poshook = self.model._positioncallback
if netif.node is None: if netif.node is None:
return return
(x,y,z) = netif.node.position.get() x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z) netif.poshook(netif, x, y, z)
def setmodel(self, model, config): def setmodel(self, model, config):
''' Mobility and wireless model. """ Mobility and wireless model.
''' """
if (self.verbose): if self.verbose:
self.info("adding model %s" % model._name) self.info("adding model %s" % model._name)
if model._type == coreapi.CORE_TLV_REG_WIRELESS: if model._type == RegisterTlvs.WIRELESS.value:
self.model = model(session=self.session, objid=self.objid, self.model = model(session=self.session, objid=self.objid,
verbose=self.verbose, values=config) verbose=self.verbose, values=config)
if self.model._positioncallback: if self.model._positioncallback:
for netif in self.netifs(): for netif in self.netifs():
netif.poshook = self.model._positioncallback netif.poshook = self.model._positioncallback
if netif.node is not None: if netif.node is not None:
(x,y,z) = netif.node.position.get() (x, y, z) = netif.node.position.get()
netif.poshook(netif, x, y, z) netif.poshook(netif, x, y, z)
self.model.setlinkparams() self.model.setlinkparams()
elif model._type == coreapi.CORE_TLV_REG_MOBILITY: elif model._type == RegisterTlvs.MOBILITY.value:
self.mobility = model(session=self.session, objid=self.objid, self.mobility = model(session=self.session, objid=self.objid,
verbose=self.verbose, values=config) verbose=self.verbose, values=config)
class RJ45Node(NetgraphPipeNet): class RJ45Node(NetgraphPipeNet):
apitype = coreapi.CORE_NODE_RJ45 apitype = NodeTypes.RJ45.value
policy = "ACCEPT" policy = "ACCEPT"
def __init__(self, session, objid, name, verbose, start = True): def __init__(self, session, objid, name, verbose, start=True):
if start: if start:
ngloadkernelmodule("ng_ether") ngloadkernelmodule("ng_ether")
NetgraphPipeNet.__init__(self, session, objid, name, verbose, start) NetgraphPipeNet.__init__(self, session, objid, name, verbose, start)
@ -186,7 +187,7 @@ class RJ45Node(NetgraphPipeNet):
p = "promisc" p = "promisc"
if not promisc: if not promisc:
p = "-" + p p = "-" + p
check_call([IFCONFIG_BIN, self.name, "up", p]) utils.check_call([constants.IFCONFIG_BIN, self.name, "up", p])
def attach(self, netif): def attach(self, netif):
if len(self._netif) > 0: if len(self._netif) > 0:
@ -195,9 +196,9 @@ class RJ45Node(NetgraphPipeNet):
NetgraphPipeNet.attach(self, netif) NetgraphPipeNet.attach(self, netif)
connectngnodes(self.ngname, self.name, self.gethook(), "lower") connectngnodes(self.ngname, self.name, self.gethook(), "lower")
class TunnelNode(NetgraphNet): class TunnelNode(NetgraphNet):
ngtype = "pipe" ngtype = "pipe"
nghooks = "upper lower" nghooks = "upper lower"
apitype = coreapi.CORE_NODE_TUNNEL apitype = NodeTypes.TUNNEL.value
policy = "ACCEPT" policy = "ACCEPT"

View file

@ -5,25 +5,25 @@
# #
# authors: core-dev@pf.itd.nrl.navy.mil # authors: core-dev@pf.itd.nrl.navy.mil
# #
'''
"""
vnet.py: NetgraphNet and NetgraphPipeNet classes that implement virtual networks vnet.py: NetgraphNet and NetgraphPipeNet classes that implement virtual networks
using the FreeBSD Netgraph subsystem. using the FreeBSD Netgraph subsystem.
''' """
import sys, threading from core.bsd.netgraph import connectngnodes
from core.bsd.netgraph import createngnode
from core.bsd.netgraph import destroyngnode
from core.bsd.netgraph import ngmessage
from core.coreobj import PyCoreNet
from core.misc.utils import *
from core.constants import *
from core.coreobj import PyCoreNet, PyCoreObj
from core.bsd.netgraph import *
from core.bsd.vnode import VEth
class NetgraphNet(PyCoreNet): class NetgraphNet(PyCoreNet):
ngtype = None ngtype = None
nghooks = () nghooks = ()
def __init__(self, session, objid = None, name = None, verbose = False, def __init__(self, session, objid=None, name=None, verbose=False,
start = True, policy = None): start=True, policy=None):
PyCoreNet.__init__(self, session, objid, name) PyCoreNet.__init__(self, session, objid, name)
if name is None: if name is None:
name = str(self.objid) name = str(self.objid)
@ -40,8 +40,7 @@ class NetgraphNet(PyCoreNet):
self.startup() self.startup()
def startup(self): def startup(self):
tmp, self.ngid = createngnode(type=self.ngtype, hookstr=self.nghooks, tmp, self.ngid = createngnode(type=self.ngtype, hookstr=self.nghooks, name=self.ngname)
name=self.ngname)
self.up = True self.up = True
def shutdown(self): def shutdown(self):
@ -62,13 +61,12 @@ class NetgraphNet(PyCoreNet):
destroyngnode(self.ngname) destroyngnode(self.ngname)
def attach(self, netif): def attach(self, netif):
''' Attach an interface to this netgraph node. Create a pipe between """ Attach an interface to this netgraph node. Create a pipe between
the interface and the hub/switch/wlan node. the interface and the hub/switch/wlan node.
(Note that the PtpNet subclass overrides this method.) (Note that the PtpNet subclass overrides this method.)
''' """
if self.up: if self.up:
pipe = self.session.addobj(cls = NetgraphPipeNet, pipe = self.session.addobj(cls=NetgraphPipeNet, verbose=self.verbose, start=True)
verbose = self.verbose, start = True)
pipe.attach(netif) pipe.attach(netif)
hook = "link%d" % len(self._netif) hook = "link%d" % len(self._netif)
pipe.attachnet(self, hook) pipe.attachnet(self, hook)
@ -82,14 +80,16 @@ class NetgraphNet(PyCoreNet):
def linked(self, netif1, netif2): def linked(self, netif1, netif2):
# check if the network interfaces are attached to this network # check if the network interfaces are attached to this network
if self._netif[netif1] != netif1: if self._netif[netif1] != netif1:
raise ValueError, "inconsistency for netif %s" % netif1.name raise ValueError("inconsistency for netif %s" % netif1.name)
if self._netif[netif2] != netif2: if self._netif[netif2] != netif2:
raise ValueError, "inconsistency for netif %s" % netif2.name raise ValueError("inconsistency for netif %s" % netif2.name)
try: try:
linked = self._linked[netif1][netif2] linked = self._linked[netif1][netif2]
except KeyError: except KeyError:
linked = False linked = False
self._linked[netif1][netif2] = linked self._linked[netif1][netif2] = linked
return linked return linked
def unlink(self, netif1, netif2): def unlink(self, netif1, netif2):
@ -109,38 +109,37 @@ class NetgraphNet(PyCoreNet):
self._linked[netif1][netif2] = True self._linked[netif1][netif2] = True
def linknet(self, net): def linknet(self, net):
''' Link this bridge with another by creating a veth pair and installing """ Link this bridge with another by creating a veth pair and installing
each device into each bridge. each device into each bridge.
''' """
raise NotImplementedError raise NotImplementedError
def linkconfig(self, netif, bw = None, delay = None, def linkconfig(self, netif, bw=None, delay=None,
loss = None, duplicate = None, jitter = None, netif2=None): loss=None, duplicate=None, jitter=None, netif2=None):
''' Set link effects by modifying the pipe connected to an interface. """ Set link effects by modifying the pipe connected to an interface.
''' """
if not netif.pipe: if not netif.pipe:
self.warn("linkconfig for %s but interface %s has no pipe" % \ self.warn("linkconfig for %s but interface %s has no pipe" % (self.name, netif.name))
(self.name, netif.name))
return return
return netif.pipe.linkconfig(netif, bw, delay, loss, duplicate, jitter, return netif.pipe.linkconfig(netif, bw, delay, loss, duplicate, jitter, netif2)
netif2)
class NetgraphPipeNet(NetgraphNet): class NetgraphPipeNet(NetgraphNet):
ngtype = "pipe" ngtype = "pipe"
nghooks = "upper lower" nghooks = "upper lower"
def __init__(self, session, objid = None, name = None, verbose = False, def __init__(self, session, objid=None, name=None, verbose=False,
start = True, policy = None): start=True, policy=None):
NetgraphNet.__init__(self, session, objid, name, verbose, start, policy) NetgraphNet.__init__(self, session, objid, name, verbose, start, policy)
if start: if start:
# account for Ethernet header # account for Ethernet header
ngmessage(self.ngname, ["setcfg", "{", "header_offset=14", "}"]) ngmessage(self.ngname, ["setcfg", "{", "header_offset=14", "}"])
def attach(self, netif): def attach(self, netif):
''' Attach an interface to this pipe node. """ Attach an interface to this pipe node.
The first interface is connected to the "upper" hook, the second The first interface is connected to the "upper" hook, the second
connected to the "lower" hook. connected to the "lower" hook.
''' """
if len(self._netif) > 1: if len(self._netif) > 1:
raise ValueError, \ raise ValueError, \
"Netgraph pipes support at most 2 network interfaces" "Netgraph pipes support at most 2 network interfaces"
@ -156,61 +155,60 @@ class NetgraphPipeNet(NetgraphNet):
self._linked[netif] = {} self._linked[netif] = {}
def attachnet(self, net, hook): def attachnet(self, net, hook):
''' Attach another NetgraphNet to this pipe node. """ Attach another NetgraphNet to this pipe node.
''' """
localhook = self.gethook() localhook = self.gethook()
connectngnodes(self.ngname, net.ngname, localhook, hook) connectngnodes(self.ngname, net.ngname, localhook, hook)
def gethook(self): def gethook(self):
''' Returns the first hook (e.g. "upper") then the second hook """ Returns the first hook (e.g. "upper") then the second hook
(e.g. "lower") based on the number of connections. (e.g. "lower") based on the number of connections.
''' """
hooks = self.nghooks.split() hooks = self.nghooks.split()
if len(self._netif) == 0: if len(self._netif) == 0:
return hooks[0] return hooks[0]
else: else:
return hooks[1] return hooks[1]
def linkconfig(self, netif, bw = None, delay = None, def linkconfig(self, netif, bw=None, delay=None,
loss = None, duplicate = None, jitter = None, netif2 = None): loss=None, duplicate=None, jitter=None, netif2=None):
''' Set link effects by sending a Netgraph setcfg message to the pipe. """ Set link effects by sending a Netgraph setcfg message to the pipe.
''' """
netif.setparam('bw', bw) netif.setparam("bw", bw)
netif.setparam('delay', delay) netif.setparam("delay", delay)
netif.setparam('loss', loss) netif.setparam("loss", loss)
netif.setparam('duplicate', duplicate) netif.setparam("duplicate", duplicate)
netif.setparam('jitter', jitter) netif.setparam("jitter", jitter)
if not self.up: if not self.up:
return return
params = [] params = []
upstream = [] upstream = []
downstream = [] downstream = []
if bw is not None: if bw is not None:
if str(bw)=="0": if str(bw) == "0":
bw="-1" bw = "-1"
params += ["bandwidth=%s" % bw,] params += ["bandwidth=%s" % bw, ]
if delay is not None: if delay is not None:
if str(delay)=="0": if str(delay) == "0":
delay="-1" delay = "-1"
params += ["delay=%s" % delay,] params += ["delay=%s" % delay, ]
if loss is not None: if loss is not None:
if str(loss)=="0": if str(loss) == "0":
loss="-1" loss = "-1"
upstream += ["BER=%s" % loss,] upstream += ["BER=%s" % loss, ]
downstream += ["BER=%s" % loss,] downstream += ["BER=%s" % loss, ]
if duplicate is not None: if duplicate is not None:
if str(duplicate)=="0": if str(duplicate) == "0":
duplicate="-1" duplicate = "-1"
upstream += ["duplicate=%s" % duplicate,] upstream += ["duplicate=%s" % duplicate, ]
downstream += ["duplicate=%s" % duplicate,] downstream += ["duplicate=%s" % duplicate, ]
if jitter: if jitter:
self.warn("jitter parameter ignored for link %s" % self.name) self.warn("jitter parameter ignored for link %s" % self.name)
if len(params) > 0 or len(upstream) > 0 or len(downstream) > 0: if len(params) > 0 or len(upstream) > 0 or len(downstream) > 0:
setcfg = ["setcfg", "{",] + params setcfg = ["setcfg", "{", ] + params
if len(upstream) > 0: if len(upstream) > 0:
setcfg += ["upstream={",] + upstream + ["}",] setcfg += ["upstream={", ] + upstream + ["}", ]
if len(downstream) > 0: if len(downstream) > 0:
setcfg += ["downstream={",] + downstream + ["}",] setcfg += ["downstream={", ] + downstream + ["}", ]
setcfg += ["}",] setcfg += ["}", ]
ngmessage(self.ngname, setcfg) ngmessage(self.ngname, setcfg)

View file

@ -5,25 +5,30 @@
# #
# authors: core-dev@pf.itd.nrl.navy.mil # authors: core-dev@pf.itd.nrl.navy.mil
# #
'''
"""
vnode.py: SimpleJailNode and JailNode classes that implement the FreeBSD vnode.py: SimpleJailNode and JailNode classes that implement the FreeBSD
jail-based virtual node. jail-based virtual node.
''' """
import os, signal, sys, subprocess, threading, string import os
import random, time import subprocess
from core.misc.utils import * import threading
from core.constants import *
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position from core import constants
from core.emane.nodes import EmaneNode from core.bsd.netgraph import createngnode
from core.bsd.netgraph import * from core.bsd.netgraph import destroyngnode
from core.coreobj import PyCoreNetIf
from core.coreobj import PyCoreNode
from core.misc import utils
utils.checkexec([constants.IFCONFIG_BIN, constants.VIMAGE_BIN])
checkexec([IFCONFIG_BIN, VIMAGE_BIN])
class VEth(PyCoreNetIf): class VEth(PyCoreNetIf):
def __init__(self, node, name, localname, mtu = 1500, net = None, def __init__(self, node, name, localname, mtu=1500, net=None,
start = True): start=True):
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu) PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
# name is the device name (e.g. ngeth0, ngeth1, etc.) before it is # name is the device name (e.g. ngeth0, ngeth1, etc.) before it is
# installed in a node; the Netgraph name is renamed to localname # installed in a node; the Netgraph name is renamed to localname
# e.g. before install: name = ngeth0 localname = n0_0_123 # e.g. before install: name = ngeth0 localname = n0_0_123
@ -45,7 +50,7 @@ class VEth(PyCoreNetIf):
name=self.localname) name=self.localname)
self.name = ngname self.name = ngname
self.ngid = ngid self.ngid = ngid
check_call([IFCONFIG_BIN, ngname, "up"]) utils.check_call([constants.IFCONFIG_BIN, ngname, "up"])
self.up = True self.up = True
def shutdown(self): def shutdown(self):
@ -74,15 +79,18 @@ class VEth(PyCoreNetIf):
def sethwaddr(self, addr): def sethwaddr(self, addr):
self.hwaddr = addr self.hwaddr = addr
class TunTap(PyCoreNetIf): class TunTap(PyCoreNetIf):
'''TUN/TAP virtual device in TAP mode''' """TUN/TAP virtual device in TAP mode"""
def __init__(self, node, name, localname, mtu = None, net = None,
start = True): def __init__(self, node, name, localname, mtu=None, net=None,
start=True):
raise NotImplementedError raise NotImplementedError
class SimpleJailNode(PyCoreNode): class SimpleJailNode(PyCoreNode):
def __init__(self, session, objid = None, name = None, nodedir = None, def __init__(self, session, objid=None, name=None, nodedir=None,
verbose = False): verbose=False):
PyCoreNode.__init__(self, session, objid, name) PyCoreNode.__init__(self, session, objid, name)
self.nodedir = nodedir self.nodedir = nodedir
self.verbose = verbose self.verbose = verbose
@ -94,17 +102,17 @@ class SimpleJailNode(PyCoreNode):
def startup(self): def startup(self):
if self.up: if self.up:
raise Exception, "already up" raise Exception, "already up"
vimg = [VIMAGE_BIN, "-c", self.name] vimg = [constants.VIMAGE_BIN, "-c", self.name]
try: try:
os.spawnlp(os.P_WAIT, VIMAGE_BIN, *vimg) os.spawnlp(os.P_WAIT, constants.VIMAGE_BIN, *vimg)
except OSError: except OSError:
raise Exception, ("vimage command not found while running: %s" % \ raise Exception, ("vimage command not found while running: %s" % \
vimg) vimg)
self.info("bringing up loopback interface") self.info("bringing up loopback interface")
self.cmd([IFCONFIG_BIN, "lo0", "127.0.0.1"]) self.cmd([constants.IFCONFIG_BIN, "lo0", "127.0.0.1"])
self.info("setting hostname: %s" % self.name) self.info("setting hostname: %s" % self.name)
self.cmd(["hostname", self.name]) self.cmd(["hostname", self.name])
self.cmd([SYSCTL_BIN, "vfs.morphing_symlinks=1"]) self.cmd([constants.SYSCTL_BIN, "vfs.morphing_symlinks=1"])
self.up = True self.up = True
def shutdown(self): def shutdown(self):
@ -114,27 +122,26 @@ class SimpleJailNode(PyCoreNode):
netif.shutdown() netif.shutdown()
self._netif.clear() self._netif.clear()
del self.session del self.session
vimg = [VIMAGE_BIN, "-d", self.name] vimg = [constants.VIMAGE_BIN, "-d", self.name]
try: try:
os.spawnlp(os.P_WAIT, VIMAGE_BIN, *vimg) os.spawnlp(os.P_WAIT, constants.VIMAGE_BIN, *vimg)
except OSError: except OSError:
raise Exception, ("vimage command not found while running: %s" % \ raise Exception("vimage command not found while running: %s" % vimg)
vimg)
self.up = False self.up = False
def cmd(self, args, wait = True): def cmd(self, args, wait=True):
if wait: if wait:
mode = os.P_WAIT mode = os.P_WAIT
else: else:
mode = os.P_NOWAIT mode = os.P_NOWAIT
tmp = call([VIMAGE_BIN, self.name] + args, cwd=self.nodedir) tmp = utils.call([constants.VIMAGE_BIN, self.name] + args, cwd=self.nodedir)
if not wait: if not wait:
tmp = None tmp = None
if tmp: if tmp:
self.warn("cmd exited with status %s: %s" % (tmp, str(args))) self.warn("cmd exited with status %s: %s" % (tmp, str(args)))
return tmp return tmp
def cmdresult(self, args, wait = True): def cmdresult(self, args, wait=True):
cmdid, cmdin, cmdout, cmderr = self.popen(args) cmdid, cmdin, cmdout, cmderr = self.popen(args)
result = cmdout.read() result = cmdout.read()
result += cmderr.read() result += cmderr.read()
@ -145,30 +152,30 @@ class SimpleJailNode(PyCoreNode):
status = cmdid.wait() status = cmdid.wait()
else: else:
status = 0 status = 0
return (status, result) return status, result
def popen(self, args): def popen(self, args):
cmd = [VIMAGE_BIN, self.name] cmd = [constants.VIMAGE_BIN, self.name]
cmd.extend(args) cmd.extend(args)
tmp = subprocess.Popen(cmd, stdin = subprocess.PIPE, tmp = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout = subprocess.PIPE, stdout=subprocess.PIPE,
stderr = subprocess.PIPE, cwd=self.nodedir) stderr=subprocess.PIPE, cwd=self.nodedir)
return tmp, tmp.stdin, tmp.stdout, tmp.stderr return tmp, tmp.stdin, tmp.stdout, tmp.stderr
def icmd(self, args): def icmd(self, args):
return os.spawnlp(os.P_WAIT, VIMAGE_BIN, VIMAGE_BIN, self.name, *args) return os.spawnlp(os.P_WAIT, constants.VIMAGE_BIN, constants.VIMAGE_BIN, self.name, *args)
def term(self, sh = "/bin/sh"): def term(self, sh="/bin/sh"):
return os.spawnlp(os.P_WAIT, "xterm", "xterm", "-ut", return os.spawnlp(os.P_WAIT, "xterm", "xterm", "-ut",
"-title", self.name, "-e", VIMAGE_BIN, self.name, sh) "-title", self.name, "-e", constants.VIMAGE_BIN, self.name, sh)
def termcmdstring(self, sh = "/bin/sh"): def termcmdstring(self, sh="/bin/sh"):
''' We add 'sudo' to the command string because the GUI runs as a """ We add "sudo" to the command string because the GUI runs as a
normal user. normal user.
''' """
return "cd %s && sudo %s %s %s" % (self.nodedir, VIMAGE_BIN, self.name, sh) return "cd %s && sudo %s %s %s" % (self.nodedir, constants.VIMAGE_BIN, self.name, sh)
def shcmd(self, cmdstr, sh = "/bin/sh"): def shcmd(self, cmdstr, sh="/bin/sh"):
return self.cmd([sh, "-c", cmdstr]) return self.cmd([sh, "-c", cmdstr])
def boot(self): def boot(self):
@ -180,9 +187,9 @@ class SimpleJailNode(PyCoreNode):
self.addsymlink(path=target, file=None) self.addsymlink(path=target, file=None)
def umount(self, target): def umount(self, target):
self.info("unmounting '%s'" % target) self.info("unmounting %s" % target)
def newveth(self, ifindex = None, ifname = None, net = None): def newveth(self, ifindex=None, ifname=None, net=None):
self.lock.acquire() self.lock.acquire()
try: try:
if ifindex is None: if ifindex is None:
@ -193,13 +200,15 @@ class SimpleJailNode(PyCoreNode):
name = "n%s_%s_%s" % (self.objid, ifindex, sessionid) name = "n%s_%s_%s" % (self.objid, ifindex, sessionid)
localname = name localname = name
ifclass = VEth ifclass = VEth
veth = ifclass(node = self, name = name, localname = localname, veth = ifclass(node=self, name=name, localname=localname,
mtu = 1500, net = net, start = self.up) mtu=1500, net=net, start=self.up)
if self.up: if self.up:
# install into jail # install into jail
check_call([IFCONFIG_BIN, veth.name, "vnet", self.name]) utils.check_call([constants.IFCONFIG_BIN, veth.name, "vnet", self.name])
# rename from "ngeth0" to "eth0" # rename from "ngeth0" to "eth0"
self.cmd([IFCONFIG_BIN, veth.name, "name", ifname]) self.cmd([constants.IFCONFIG_BIN, veth.name, "name", ifname])
veth.name = ifname veth.name = ifname
try: try:
self.addnetif(veth, ifindex) self.addnetif(veth, ifindex)
@ -214,16 +223,16 @@ class SimpleJailNode(PyCoreNode):
def sethwaddr(self, ifindex, addr): def sethwaddr(self, ifindex, addr):
self._netif[ifindex].sethwaddr(addr) self._netif[ifindex].sethwaddr(addr)
if self.up: if self.up:
self.cmd([IFCONFIG_BIN, self.ifname(ifindex), "link", self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), "link",
str(addr)]) str(addr)])
def addaddr(self, ifindex, addr): def addaddr(self, ifindex, addr):
if self.up: if self.up:
if ':' in addr: if ":" in addr:
family = "inet6" family = "inet6"
else: else:
family = "inet" family = "inet"
self.cmd([IFCONFIG_BIN, self.ifname(ifindex), family, "alias", self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), family, "alias",
str(addr)]) str(addr)])
self._netif[ifindex].addaddr(addr) self._netif[ifindex].addaddr(addr)
@ -233,16 +242,17 @@ class SimpleJailNode(PyCoreNode):
except ValueError: except ValueError:
self.warn("trying to delete unknown address: %s" % addr) self.warn("trying to delete unknown address: %s" % addr)
if self.up: if self.up:
if ':' in addr: if ":" in addr:
family = "inet6" family = "inet6"
else: else:
family = "inet" family = "inet"
self.cmd([IFCONFIG_BIN, self.ifname(ifindex), family, "-alias", self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), family, "-alias",
str(addr)]) str(addr)])
valid_deladdrtype = ("inet", "inet6", "inet6link") valid_deladdrtype = ("inet", "inet6", "inet6link")
def delalladdr(self, ifindex, addrtypes = valid_deladdrtype):
addr = self.getaddr(self.ifname(ifindex), rescan = True) def delalladdr(self, ifindex, addrtypes=valid_deladdrtype):
addr = self.getaddr(self.ifname(ifindex), rescan=True)
for t in addrtypes: for t in addrtypes:
if t not in self.valid_deladdrtype: if t not in self.valid_deladdrtype:
raise ValueError, "addr type must be in: " + \ raise ValueError, "addr type must be in: " + \
@ -250,23 +260,22 @@ class SimpleJailNode(PyCoreNode):
for a in addr[t]: for a in addr[t]:
self.deladdr(ifindex, a) self.deladdr(ifindex, a)
# update cached information # update cached information
self.getaddr(self.ifname(ifindex), rescan = True) self.getaddr(self.ifname(ifindex), rescan=True)
def ifup(self, ifindex): def ifup(self, ifindex):
if self.up: if self.up:
self.cmd([IFCONFIG_BIN, self.ifname(ifindex), "up"]) self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), "up"])
def newnetif(self, net = None, addrlist = [], hwaddr = None, def newnetif(self, net=None, addrlist=[], hwaddr=None,
ifindex = None, ifname = None): ifindex=None, ifname=None):
self.lock.acquire() self.lock.acquire()
try: try:
ifindex = self.newveth(ifindex = ifindex, ifname = ifname, ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net)
net = net)
if net is not None: if net is not None:
self.attachnet(ifindex, net) self.attachnet(ifindex, net)
if hwaddr: if hwaddr:
self.sethwaddr(ifindex, hwaddr) self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist): for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr) self.addaddr(ifindex, addr)
self.ifup(ifindex) self.ifup(ifindex)
return ifindex return ifindex
@ -280,18 +289,17 @@ class SimpleJailNode(PyCoreNode):
self._netif[ifindex].detachnet() self._netif[ifindex].detachnet()
def addfile(self, srcname, filename): def addfile(self, srcname, filename):
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % \ shcmd = 'mkdir -p $(dirname "%s") && mv "%s" "%s" && sync' % (filename, srcname, filename)
(filename, srcname, filename)
self.shcmd(shcmd) self.shcmd(shcmd)
def getaddr(self, ifname, rescan = False): def getaddr(self, ifname, rescan=False):
return None return None
#return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan) # return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan)
def addsymlink(self, path, file): def addsymlink(self, path, file):
''' Create a symbolic link from /path/name/file -> """ Create a symbolic link from /path/name/file ->
/tmp/pycore.nnnnn/@.conf/path.name/file /tmp/pycore.nnnnn/@.conf/path.name/file
''' """
dirname = path dirname = path
if dirname and dirname[0] == "/": if dirname and dirname[0] == "/":
dirname = dirname[1:] dirname = dirname[1:]
@ -316,14 +324,14 @@ class SimpleJailNode(PyCoreNode):
self.info("creating symlink %s -> %s" % (pathname, sym)) self.info("creating symlink %s -> %s" % (pathname, sym))
os.symlink(sym, pathname) os.symlink(sym, pathname)
class JailNode(SimpleJailNode):
def __init__(self, session, objid = None, name = None, class JailNode(SimpleJailNode):
nodedir = None, bootsh = "boot.sh", verbose = False, def __init__(self, session, objid=None, name=None,
start = True): nodedir=None, bootsh="boot.sh", verbose=False,
super(JailNode, self).__init__(session = session, objid = objid, start=True):
name = name, nodedir = nodedir, super(JailNode, self).__init__(session=session, objid=objid,
verbose = verbose) name=name, nodedir=nodedir,
verbose=verbose)
self.bootsh = bootsh self.bootsh = bootsh
if not start: if not start:
return return
@ -341,8 +349,8 @@ class JailNode(SimpleJailNode):
self.lock.acquire() self.lock.acquire()
try: try:
super(JailNode, self).startup() super(JailNode, self).startup()
#self.privatedir("/var/run") # self.privatedir("/var/run")
#self.privatedir("/var/log") # self.privatedir("/var/log")
finally: finally:
self.lock.release() self.lock.release()
@ -351,7 +359,7 @@ class JailNode(SimpleJailNode):
return return
self.lock.acquire() self.lock.acquire()
# services are instead stopped when session enters datacollect state # services are instead stopped when session enters datacollect state
#self.session.services.stopnodeservices(self) # self.session.services.stopnodeservices(self)
try: try:
super(JailNode, self).shutdown() super(JailNode, self).shutdown()
finally: finally:
@ -362,7 +370,7 @@ class JailNode(SimpleJailNode):
if path[0] != "/": if path[0] != "/":
raise ValueError, "path not fully qualified: " + path raise ValueError, "path not fully qualified: " + path
hostpath = os.path.join(self.nodedir, hostpath = os.path.join(self.nodedir,
os.path.normpath(path).strip('/').replace('/', '.')) os.path.normpath(path).strip("/").replace("/", "."))
try: try:
os.mkdir(hostpath) os.mkdir(hostpath)
except OSError: except OSError:
@ -371,9 +379,9 @@ class JailNode(SimpleJailNode):
raise Exception, e raise Exception, e
self.mount(hostpath, path) self.mount(hostpath, path)
def opennodefile(self, filename, mode = "w"): def opennodefile(self, filename, mode="w"):
dirname, basename = os.path.split(filename) dirname, basename = os.path.split(filename)
#self.addsymlink(path=dirname, file=basename) # self.addsymlink(path=dirname, file=basename)
if not basename: if not basename:
raise ValueError, "no basename for filename: " + filename raise ValueError, "no basename for filename: " + filename
if dirname and dirname[0] == "/": if dirname and dirname[0] == "/":
@ -381,14 +389,13 @@ class JailNode(SimpleJailNode):
dirname = dirname.replace("/", ".") dirname = dirname.replace("/", ".")
dirname = os.path.join(self.nodedir, dirname) dirname = os.path.join(self.nodedir, dirname)
if not os.path.isdir(dirname): if not os.path.isdir(dirname):
os.makedirs(dirname, mode = 0755) os.makedirs(dirname, mode=0755)
hostfilename = os.path.join(dirname, basename) hostfilename = os.path.join(dirname, basename)
return open(hostfilename, mode) return open(hostfilename, mode)
def nodefile(self, filename, contents, mode = 0644): def nodefile(self, filename, contents, mode=0644):
f = self.opennodefile(filename, "w") f = self.opennodefile(filename, "w")
f.write(contents) f.write(contents)
os.chmod(f.name, mode) os.chmod(f.name, mode)
f.close() f.close()
self.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode)) self.info("created nodefile: %s; mode: 0%o" % (f.name, mode))

View file

@ -1,35 +1,45 @@
# """
# CORE Common support for configurable CORE objects.
# Copyright (c)2012 the Boeing Company. """
# See the LICENSE file included in this distribution.
#
# authors: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
conf.py: common support for configurable objects
'''
import string import string
from core.api import coreapi
from core.data import ConfigData
from core.enumerations import ConfigDataTypes
from core.enumerations import ConfigFlags
from core.misc import log
logger = log.get_logger(__name__)
class ConfigurableManager(object): class ConfigurableManager(object):
''' A generic class for managing Configurables. This class can register """
A generic class for managing Configurables. This class can register
with a session to receive Config Messages for setting some parameters with a session to receive Config Messages for setting some parameters
for itself or for the Configurables that it manages. for itself or for the Configurables that it manages.
''' """
# name corresponds to configuration object field # name corresponds to configuration object field
_name = "" name = ""
# type corresponds with register message types
_type = None
def __init__(self, session=None): # type corresponds with register message types
self.session = session config_type = None
self.session.addconfobj(self._name, self._type, self.configure)
# Configurable key=values, indexed by node number def __init__(self):
"""
Creates a ConfigurableManager instance.
:param core.session.Session session: session this manager is tied to
:return: nothing
"""
# configurable key=values, indexed by node number
self.configs = {} self.configs = {}
# TODO: fix the need for this and isolate to the mobility class that wants it
self._modelclsmap = {}
def configure(self, session, msg): def configure(self, session, config_data):
''' Handle configure messages. The configuration message sent to a """
Handle configure messages. The configuration message sent to a
ConfigurableManager usually is used to: ConfigurableManager usually is used to:
1. Request a list of Configurables (request flag) 1. Request a list of Configurables (request flag)
2. Reset manager and clear configs (reset flag) 2. Reset manager and clear configs (reset flag)
@ -37,45 +47,66 @@ class ConfigurableManager(object):
Configurables Configurables
Returns any reply messages. Returns any reply messages.
'''
objname = msg.gettlv(coreapi.CORE_TLV_CONF_OBJ)
conftype = msg.gettlv(coreapi.CORE_TLV_CONF_TYPE)
if conftype == coreapi.CONF_TYPE_FLAGS_REQUEST:
return self.configure_request(msg)
elif conftype == coreapi.CONF_TYPE_FLAGS_RESET:
if objname == "all" or objname == self._name:
return self.configure_reset(msg)
else:
return self.configure_values(msg,
msg.gettlv(coreapi.CORE_TLV_CONF_VALUES))
def configure_request(self, msg): :param core.session.Session session: CORE session object
''' Request configuration data. :param ConfigData config_data: configuration data for carrying out a configuration
''' :return: response messages
"""
if config_data.type == ConfigFlags.REQUEST.value:
return self.configure_request(config_data)
elif config_data.type == ConfigFlags.RESET.value:
return self.configure_reset(config_data)
else:
return self.configure_values(config_data)
def configure_request(self, config_data):
"""
Request configuration data.
:param ConfigData config_data: configuration data for carrying out a configuration
:return: nothing
"""
return None return None
def configure_reset(self, msg): def configure_reset(self, config_data):
''' By default, resets this manager to clear configs. """
''' By default, resets this manager to clear configs.
:param ConfigData config_data: configuration data for carrying out a configuration
:return: reset response messages, or None
"""
return self.reset() return self.reset()
def configure_values(self, msg, values): def configure_values(self, config_data):
''' Values have been sent to this manager. """
''' Values have been sent to this manager.
:param ConfigData config_data: configuration data for carrying out a configuration
:return: nothing
"""
return None return None
def configure_values_keyvalues(self, msg, values, target, keys): def configure_values_keyvalues(self, config_data, target, keys):
''' Helper that can be used for configure_values for parsing in """
Helper that can be used for configure_values for parsing in
'key=value' strings from a values field. The key name must be 'key=value' strings from a values field. The key name must be
in the keys list, and target.key=value is set. in the keys list, and target.key=value is set.
'''
:param ConfigData config_data: configuration data for carrying out a configuration
:param target: target to set attribute values on
:param keys: list of keys to verify validity
:return: nothing
"""
values = config_data.data_values
if values is None: if values is None:
return None return None
kvs = values.split('|') kvs = values.split('|')
for kv in kvs: for kv in kvs:
try: try:
# key=value key, value = kv.split('=', 1)
(key, value) = kv.split('=', 1)
if value is not None and not value.strip(): if value is not None and not value.strip():
value = None value = None
except ValueError: except ValueError:
@ -83,25 +114,38 @@ class ConfigurableManager(object):
key = keys[kvs.index(kv)] key = keys[kvs.index(kv)]
value = kv value = kv
if key not in keys: if key not in keys:
raise ValueError, "invalid key: %s" % key raise ValueError("invalid key: %s" % key)
if value is not None: if value is not None:
setattr(target, key, value) setattr(target, key, value)
return None return None
def reset(self): def reset(self):
"""
Reset functionality for the configurable class.
:return: nothing
"""
return None return None
def setconfig(self, nodenum, conftype, values): def setconfig(self, nodenum, conftype, values):
''' add configuration values for a node to a dictionary; values are """
Add configuration values for a node to a dictionary; values are
usually received from a Configuration Message, and may refer to a usually received from a Configuration Message, and may refer to a
node for which no object exists yet node for which no object exists yet
'''
:param int nodenum: node id
:param conftype: configuration types
:param values: configuration values
:return: nothing
"""
logger.info("setting config for node(%s): %s - %s", nodenum, conftype, values)
conflist = [] conflist = []
if nodenum in self.configs: if nodenum in self.configs:
oldlist = self.configs[nodenum] oldlist = self.configs[nodenum]
found = False found = False
for (t, v) in oldlist: for t, v in oldlist:
if (t == conftype): if t == conftype:
# replace existing config # replace existing config
found = True found = True
conflist.append((conftype, values)) conflist.append((conftype, values))
@ -114,34 +158,52 @@ class ConfigurableManager(object):
self.configs[nodenum] = conflist self.configs[nodenum] = conflist
def getconfig(self, nodenum, conftype, defaultvalues): def getconfig(self, nodenum, conftype, defaultvalues):
''' get configuration values for a node; if the values don't exist in """
Get configuration values for a node; if the values don't exist in
our dictionary then return the default values supplied our dictionary then return the default values supplied
'''
:param int nodenum: node id
:param conftype: configuration type
:param defaultvalues: default values
:return: configuration type and default values
:type: tuple
"""
logger.info("getting config for node(%s): %s - default(%s)",
nodenum, conftype, defaultvalues)
if nodenum in self.configs: if nodenum in self.configs:
# return configured values # return configured values
conflist = self.configs[nodenum] conflist = self.configs[nodenum]
for (t, v) in conflist: for t, v in conflist:
if (conftype is None) or (t == conftype): if conftype is None or t == conftype:
return (t, v) return t, v
# return default values provided (may be None) # return default values provided (may be None)
return (conftype, defaultvalues) return conftype, defaultvalues
def getallconfigs(self, use_clsmap=True): def getallconfigs(self, use_clsmap=True):
''' Return (nodenum, conftype, values) tuples for all stored configs. """
Return (nodenum, conftype, values) tuples for all stored configs.
Used when reconnecting to a session. Used when reconnecting to a session.
'''
:param bool use_clsmap: should a class map be used, default to True
:return: list of all configurations
:rtype: list
"""
r = [] r = []
for nodenum in self.configs: for nodenum in self.configs:
for (t, v) in self.configs[nodenum]: for t, v in self.configs[nodenum]:
if use_clsmap: if use_clsmap:
t = self._modelclsmap[t] t = self._modelclsmap[t]
r.append( (nodenum, t, v) ) r.append((nodenum, t, v))
return r return r
def clearconfig(self, nodenum): def clearconfig(self, nodenum):
''' remove configuration values for the specified node; """
remove configuration values for the specified node;
when nodenum is None, remove all configuration values when nodenum is None, remove all configuration values
'''
:param int nodenum: node id
:return: nothing
"""
if nodenum is None: if nodenum is None:
self.configs = {} self.configs = {}
return return
@ -149,10 +211,16 @@ class ConfigurableManager(object):
self.configs.pop(nodenum) self.configs.pop(nodenum)
def setconfig_keyvalues(self, nodenum, conftype, keyvalues): def setconfig_keyvalues(self, nodenum, conftype, keyvalues):
''' keyvalues list of tuples """
''' Key values list of tuples for a node.
:param int nodenum: node id
:param conftype: configuration type
:param keyvalues: key valyes
:return: nothing
"""
if conftype not in self._modelclsmap: if conftype not in self._modelclsmap:
self.warn("Unknown model type '%s'" % (conftype)) logger.warn("Unknown model type '%s'" % conftype)
return return
model = self._modelclsmap[conftype] model = self._modelclsmap[conftype]
keys = model.getnames() keys = model.getnames()
@ -160,7 +228,7 @@ class ConfigurableManager(object):
values = list(model.getdefaultvalues()) values = list(model.getdefaultvalues())
for key, value in keyvalues: for key, value in keyvalues:
if key not in keys: if key not in keys:
self.warn("Skipping unknown configuration key for %s: '%s'" % \ logger.warn("Skipping unknown configuration key for %s: '%s'" % \
(conftype, key)) (conftype, key))
continue continue
i = keys.index(key) i = keys.index(key)
@ -168,11 +236,16 @@ class ConfigurableManager(object):
self.setconfig(nodenum, conftype, values) self.setconfig(nodenum, conftype, values)
def getmodels(self, n): def getmodels(self, n):
''' Return a list of model classes and values for a net if one has been """
Return a list of model classes and values for a net if one has been
configured. This is invoked when exporting a session to XML. configured. This is invoked when exporting a session to XML.
This assumes self.configs contains an iterable of (model-names, values) This assumes self.configs contains an iterable of (model-names, values)
and a self._modelclsmapdict exists. and a self._modelclsmapdict exists.
'''
:param n: network node to get models for
:return: list of model and values tuples for the network node
:rtype: list
"""
r = [] r = []
if n.objid in self.configs: if n.objid in self.configs:
v = self.configs[n.objid] v = self.configs[n.objid]
@ -183,92 +256,119 @@ class ConfigurableManager(object):
return r return r
def info(self, msg):
self.session.info(msg)
def warn(self, msg):
self.session.warn(msg)
class Configurable(object): class Configurable(object):
''' A generic class for managing configuration parameters. """
A generic class for managing configuration parameters.
Parameters are sent via Configuration Messages, which allow the GUI Parameters are sent via Configuration Messages, which allow the GUI
to build dynamic dialogs depending on what is being configured. to build dynamic dialogs depending on what is being configured.
''' """
_name = "" name = ""
# Configuration items: # Configuration items:
# ('name', 'type', 'default', 'possible-value-list', 'caption') # ('name', 'type', 'default', 'possible-value-list', 'caption')
_confmatrix = [] config_matrix = []
_confgroups = None config_groups = None
_bitmap = None bitmap = None
def __init__(self, session=None, objid=None): def __init__(self, session=None, object_id=None):
"""
Creates a Configurable instance.
:param core.session.Session session: session for this configurable
:param object_id:
"""
self.session = session self.session = session
self.objid = objid self.object_id = object_id
def reset(self): def reset(self):
"""
Reset method.
:return: nothing
"""
pass pass
def register(self): def register(self):
"""
Register method.
:return: nothing
"""
pass pass
@classmethod @classmethod
def getdefaultvalues(cls): def getdefaultvalues(cls):
return tuple( map(lambda x: x[2], cls._confmatrix) ) """
Retrieve default values from configuration matrix.
:return: tuple of default values
:rtype: tuple
"""
# TODO: why the need for a tuple?
return tuple(map(lambda x: x[2], cls.config_matrix))
@classmethod @classmethod
def getnames(cls): def getnames(cls):
return tuple( map( lambda x: x[0], cls._confmatrix) ) """
Retrieve name values from configuration matrix.
:return: tuple of name values
:rtype: tuple
"""
# TODO: why the need for a tuple?
return tuple(map(lambda x: x[0], cls.config_matrix))
@classmethod @classmethod
def configure(cls, mgr, msg): def configure(cls, manager, config_data):
''' Handle configuration messages for this object. """
''' Handle configuration messages for this object.
:param ConfigurableManager manager: configuration manager
:param config_data: configuration data
:return: configuration data object
:rtype: ConfigData
"""
reply = None reply = None
nodenum = msg.gettlv(coreapi.CORE_TLV_CONF_NODE) node_id = config_data.node
objname = msg.gettlv(coreapi.CORE_TLV_CONF_OBJ) object_name = config_data.object
conftype = msg.gettlv(coreapi.CORE_TLV_CONF_TYPE) config_type = config_data.type
interface_id = config_data.interface_number
values_str = config_data.data_values
ifacenum = msg.gettlv(coreapi.CORE_TLV_CONF_IFNUM) if interface_id is not None:
if ifacenum is not None: node_id = node_id * 1000 + interface_id
nodenum = nodenum*1000 + ifacenum
if mgr.verbose: logger.info("received configure message for %s nodenum:%s", cls.name, str(node_id))
mgr.info("received configure message for %s nodenum:%s" % (cls._name, str(nodenum))) if config_type == ConfigFlags.REQUEST.value:
if conftype == coreapi.CONF_TYPE_FLAGS_REQUEST: logger.info("replying to configure request for %s model", cls.name)
if mgr.verbose:
mgr.info("replying to configure request for %s model" %
cls._name)
# when object name is "all", the reply to this request may be None # when object name is "all", the reply to this request may be None
# if this node has not been configured for this model; otherwise we # if this node has not been configured for this model; otherwise we
# reply with the defaults for this model # reply with the defaults for this model
if objname == "all": if object_name == "all":
defaults = None defaults = None
typeflags = coreapi.CONF_TYPE_FLAGS_UPDATE typeflags = ConfigFlags.UPDATE.value
else: else:
defaults = cls.getdefaultvalues() defaults = cls.getdefaultvalues()
typeflags = coreapi.CONF_TYPE_FLAGS_NONE typeflags = ConfigFlags.coreapi.CONF_TYPE_FLAGS_NONE
values = mgr.getconfig(nodenum, cls._name, defaults)[1] values = manager.getconfig(node_id, cls.name, defaults)[1]
if values is None: if values is None:
# node has no active config for this model (don't send defaults) # node has no active config for this model (don't send defaults)
return None return None
# reply with config options # reply with config options
reply = cls.toconfmsg(0, nodenum, typeflags, values) reply = cls.config_data(0, node_id, typeflags, values)
elif conftype == coreapi.CONF_TYPE_FLAGS_RESET: elif config_type == ConfigFlags.RESET.value:
if objname == "all": if object_name == "all":
mgr.clearconfig(nodenum) manager.clearconfig(node_id)
#elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE: # elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE:
else: else:
# store the configuration values for later use, when the node # store the configuration values for later use, when the node
# object has been created # object has been created
if objname is None: if object_name is None:
mgr.info("no configuration object for node %s" % nodenum) logger.info("no configuration object for node %s", node_id)
return None return None
values_str = msg.gettlv(coreapi.CORE_TLV_CONF_VALUES)
defaults = cls.getdefaultvalues() defaults = cls.getdefaultvalues()
if values_str is None: if values_str is None:
# use default or preconfigured values # use default or preconfigured values
values = mgr.getconfig(nodenum, cls._name, defaults)[1] values = manager.getconfig(node_id, cls.name, defaults)[1]
else: else:
# use new values supplied from the conf message # use new values supplied from the conf message
values = values_str.split('|') values = values_str.split('|')
@ -282,54 +382,55 @@ class Configurable(object):
try: try:
new_values[keys.index(key)] = value new_values[keys.index(key)] = value
except ValueError: except ValueError:
mgr.info("warning: ignoring invalid key '%s'" % key) logger.info("warning: ignoring invalid key '%s'" % key)
values = new_values values = new_values
mgr.setconfig(nodenum, objname, values) manager.setconfig(node_id, object_name, values)
return reply return reply
@classmethod @classmethod
def toconfmsg(cls, flags, nodenum, typeflags, values): def config_data(cls, flags, node_id, type_flags, values):
''' Convert this class to a Config API message. Some TLVs are defined """
Convert this class to a Config API message. Some TLVs are defined
by the class, but node number, conf type flags, and values must by the class, but node number, conf type flags, and values must
be passed in. be passed in.
'''
:param flags: message flags
:param int node_id: node id
:param type_flags: type flags
:param values: values
:return: configuration data object
:rtype: ConfigData
"""
keys = cls.getnames() keys = cls.getnames()
keyvalues = map(lambda a,b: "%s=%s" % (a,b), keys, values) keyvalues = map(lambda a, b: "%s=%s" % (a, b), keys, values)
values_str = string.join(keyvalues, '|') values_str = string.join(keyvalues, '|')
tlvdata = "" datatypes = tuple(map(lambda x: x[1], cls.config_matrix))
if nodenum is not None: captions = reduce(lambda a, b: a + '|' + b, map(lambda x: x[4], cls.config_matrix))
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_NODE, possible_valuess = reduce(lambda a, b: a + '|' + b, map(lambda x: x[3], cls.config_matrix))
nodenum)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OBJ, return ConfigData(
cls._name) message_type=flags,
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_TYPE, node=node_id,
typeflags) object=cls.name,
datatypes = tuple( map(lambda x: x[1], cls._confmatrix) ) type=type_flags,
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_DATA_TYPES, data_types=datatypes,
datatypes) data_values=values_str,
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_VALUES, captions=captions,
values_str) possible_values=possible_valuess,
captions = reduce( lambda a,b: a + '|' + b, \ bitmap=cls.bitmap,
map(lambda x: x[4], cls._confmatrix)) groups=cls.config_groups
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_CAPTIONS, )
captions)
possiblevals = reduce( lambda a,b: a + '|' + b, \
map(lambda x: x[3], cls._confmatrix))
tlvdata += coreapi.CoreConfTlv.pack(
coreapi.CORE_TLV_CONF_POSSIBLE_VALUES, possiblevals)
if cls._bitmap is not None:
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_BITMAP,
cls._bitmap)
if cls._confgroups is not None:
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_GROUPS,
cls._confgroups)
msg = coreapi.CoreConfMessage.pack(flags, tlvdata)
return msg
@staticmethod @staticmethod
def booltooffon(value): def booltooffon(value):
''' Convenience helper turns bool into on (True) or off (False) string. """
''' Convenience helper turns bool into on (True) or off (False) string.
:param str value: value to retrieve on/off value for
:return: on or off string
:rtype: str
"""
if value == "1" or value == "true" or value == "on": if value == "1" or value == "true" or value == "on":
return "on" return "on"
else: else:
@ -337,6 +438,13 @@ class Configurable(object):
@staticmethod @staticmethod
def offontobool(value): def offontobool(value):
"""
Convenience helper for converting an on/off string to a integer.
:param str value: on/off string
:return: on/off integer value
:rtype: int
"""
if type(value) == str: if type(value) == str:
if value.lower() == "on": if value.lower() == "on":
return 1 return 1
@ -346,20 +454,30 @@ class Configurable(object):
@classmethod @classmethod
def valueof(cls, name, values): def valueof(cls, name, values):
''' Helper to return a value by the name defined in confmatrix. """
Checks if it is boolean''' Helper to return a value by the name defined in confmatrix.
Checks if it is boolean
:param str name: name to get value of
:param values: values to get value from
:return: value for name
"""
i = cls.getnames().index(name) i = cls.getnames().index(name)
if cls._confmatrix[i][1] == coreapi.CONF_DATA_TYPE_BOOL and \ if cls.config_matrix[i][1] == ConfigDataTypes.BOOL.value and values[i] != "":
values[i] != "":
return cls.booltooffon(values[i]) return cls.booltooffon(values[i])
else: else:
return values[i] return values[i]
@staticmethod @staticmethod
def haskeyvalues(values): def haskeyvalues(values):
''' Helper to check for list of key=value pairs versus a plain old """
Helper to check for list of key=value pairs versus a plain old
list of values. Returns True if all elements are "key=value". list of values. Returns True if all elements are "key=value".
'''
:param values: items to check for key/value pairs
:return: True if all values are key/value pairs, False otherwise
:rtype: bool
"""
if len(values) == 0: if len(values) == 0:
return False return False
for v in values: for v in values:
@ -368,13 +486,18 @@ class Configurable(object):
return True return True
def getkeyvaluelist(self): def getkeyvaluelist(self):
''' Helper to return a list of (key, value) tuples. Keys come from """
self._confmatrix and values are instance attributes. Helper to return a list of (key, value) tuples. Keys come from
''' configuration matrix and values are instance attributes.
r = []
for k in self.getnames():
if hasattr(self, k):
r.append((k, getattr(self, k)))
return r
:return: tuples of key value pairs
:rtype: list
"""
key_values = []
for name in self.getnames():
if hasattr(self, name):
value = getattr(self, name)
key_values.append((name, value))
return key_values

View file

@ -1,32 +1,50 @@
# """
# CORE Defines the basic objects for CORE emulation: the PyCoreObj base class, along with PyCoreNode,
# Copyright (c)2010-2013 the Boeing Company. PyCoreNet, and PyCoreNetIf.
# See the LICENSE file included in this distribution. """
#
# authors: Tom Goff <thomas.goff@boeing.com> import os
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com> import shutil
# import socket
''' import threading
coreobj.py: defines the basic objects for emulation: the PyCoreObj base class, from socket import AF_INET
along with PyCoreNode, PyCoreNet, and PyCoreNetIf from socket import AF_INET6
'''
import sys, threading, os, shutil
from core.api import coreapi from core.api import coreapi
from core.misc.ipaddr import * from core.data import NodeData, LinkData
from core.enumerations import LinkTlvs
from core.enumerations import LinkTypes
from core.misc import ipaddress
class Position(object): class Position(object):
''' Helper class for Cartesian coordinate position """
''' Helper class for Cartesian coordinate position
def __init__(self, x = None, y = None, z = None): """
self.x = None
self.y = None
self.z = None
self.set(x, y, z)
def set(self, x = None, y = None, z = None): def __init__(self, x=None, y=None, z=None):
''' Returns True if the position has actually changed. """
''' Creates a Position instance.
:param x: x position
:param y: y position
:param z: z position
:return:
"""
self.x = x
self.y = y
self.z = z
def set(self, x=None, y=None, z=None):
"""
Returns True if the position has actually changed.
:param x: x position
:param y: y position
:param z: z position
:return: True if position changed, False otherwise
:rtype: bool
"""
if self.x == x and self.y == y and self.z == z: if self.x == x and self.y == y and self.z == z:
return False return False
self.x = x self.x = x
@ -35,20 +53,36 @@ class Position(object):
return True return True
def get(self): def get(self):
''' Fetch the (x,y,z) position tuple. """
''' Retrieve x,y,z position.
return (self.x, self.y, self.z)
:return: x,y,z position tuple
:rtype: tuple
"""
return self.x, self.y, self.z
class PyCoreObj(object): class PyCoreObj(object):
''' Base class for pycore objects (nodes and nets) """
''' Base class for CORE objects (nodes and networks)
"""
apitype = None apitype = None
def __init__(self, session, objid = None, name = None, verbose = False, # TODO: appears start has no usage, verify and remove
start = True): def __init__(self, session, objid=None, name=None, start=True):
"""
Creates a PyCoreObj instance.
:param core.session.Session session: CORE session object
:param int objid: object id
:param str name: object name
:param bool start: start value
:return:
"""
self.session = session self.session = session
if objid is None: if objid is None:
objid = session.getobjid() objid = session.get_object_id()
self.objid = objid self.objid = objid
if name is None: if name is None:
name = "o%s" % self.objid name = "o%s" % self.objid
@ -59,242 +93,360 @@ class PyCoreObj(object):
self.canvas = None self.canvas = None
self.icon = None self.icon = None
self.opaque = None self.opaque = None
self.verbose = verbose
self.position = Position() self.position = Position()
def startup(self): def startup(self):
''' Each object implements its own startup method. """
''' Each object implements its own startup method.
:return: nothing
"""
raise NotImplementedError raise NotImplementedError
def shutdown(self): def shutdown(self):
''' Each object implements its own shutdown method. """
''' Each object implements its own shutdown method.
:return: nothing
"""
raise NotImplementedError raise NotImplementedError
def setposition(self, x = None, y = None, z = None): def setposition(self, x=None, y=None, z=None):
''' Set the (x,y,z) position of the object. """
''' Set the (x,y,z) position of the object.
return self.position.set(x = x, y = y, z = z)
:param x: x position
:param y: y position
:param z: z position
:return: True if position changed, False otherwise
:rtype: bool
"""
return self.position.set(x=x, y=y, z=z)
def getposition(self): def getposition(self):
''' Return an (x,y,z) tuple representing this object's position. """
''' Return an (x,y,z) tuple representing this object's position.
:return: x,y,z position tuple
:rtype: tuple
"""
return self.position.get() return self.position.get()
def ifname(self, ifindex): def ifname(self, ifindex):
return self.netif(ifindex).name """
Retrieve interface name for index.
:param int ifindex: interface index
:return: interface name
:rtype: str
"""
return self._netif[ifindex].name
def netifs(self, sort=False): def netifs(self, sort=False):
''' Iterate over attached network interfaces. """
''' Retrieve network interfaces, sorted if desired.
:param bool sort: boolean used to determine if interfaces should be sorted
:return: network interfaces
:rtype: list
"""
if sort: if sort:
return map(lambda k: self._netif[k], sorted(self._netif.keys())) return map(lambda k: self._netif[k], sorted(self._netif.keys()))
else: else:
return self._netif.itervalues() return self._netif.itervalues()
def numnetif(self): def numnetif(self):
''' Return the attached interface count. """
''' Return the attached interface count.
:return: number of network interfaces
:rtype: int
"""
return len(self._netif) return len(self._netif)
def getifindex(self, netif): def getifindex(self, netif):
"""
Retrieve index for an interface.
:param PyCoreNetIf netif: interface to get index for
:return: interface index if found, -1 otherwise
:rtype: int
"""
for ifindex in self._netif: for ifindex in self._netif:
if self._netif[ifindex] is netif: if self._netif[ifindex] is netif:
return ifindex return ifindex
return -1 return -1
def newifindex(self): def newifindex(self):
"""
Create a new interface index.
:return: interface index
:rtype: int
"""
while self.ifindex in self._netif: while self.ifindex in self._netif:
self.ifindex += 1 self.ifindex += 1
ifindex = self.ifindex ifindex = self.ifindex
self.ifindex += 1 self.ifindex += 1
return ifindex return ifindex
def tonodemsg(self, flags): def data(self, message_type):
''' Build a CORE API Node Message for this object. Both nodes and """
networks can be represented by a Node Message. Build a data object for this node.
'''
:param message_type: purpose for the data object we are creating
:return: node data object
:rtype: core.data.NodeData
"""
if self.apitype is None: if self.apitype is None:
return None return None
tlvdata = ""
(x, y, z) = self.getposition() x, y, z = self.getposition()
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_NUMBER,
self.objid) model = None
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_TYPE, if hasattr(self, "type"):
self.apitype) model = self.type
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_NAME,
self.name) emulation_server = None
if hasattr(self, "type") and self.type is not None: if hasattr(self, "server"):
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_MODEL, emulation_server = self.server
self.type)
if hasattr(self, "server") and self.server is not None: services = None
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_EMUSRV,
self.server)
if hasattr(self, "services") and len(self.services) != 0: if hasattr(self, "services") and len(self.services) != 0:
nodeservices = [] nodeservices = []
for s in self.services: for s in self.services:
nodeservices.append(s._name) nodeservices.append(s._name)
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_SERVICES, services = "|".join(nodeservices)
"|".join(nodeservices))
node_data = NodeData(
message_type=message_type,
id=self.objid,
node_type=self.apitype,
name=self.name,
emulation_id=self.objid,
canvas=self.canvas,
icon=self.icon,
opaque=self.opaque,
x_position=x,
y_position=y,
model=model,
emulation_server=emulation_server,
services=services
)
if x is not None: return node_data
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_XPOS, x)
if y is not None:
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_YPOS, y)
if self.canvas is not None:
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_CANVAS,
self.canvas)
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_EMUID,
self.objid)
if self.icon is not None:
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_ICON,
self.icon)
if self.opaque is not None:
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_OPAQUE,
self.opaque)
msg = coreapi.CoreNodeMessage.pack(flags, tlvdata)
return msg
def tolinkmsgs(self, flags): def all_link_data(self, flags):
''' Build CORE API Link Messages for this object. There is no default """
Build CORE Link data for this object. There is no default
method for PyCoreObjs as PyCoreNodes do not implement this but method for PyCoreObjs as PyCoreNodes do not implement this but
PyCoreNets do. PyCoreNets do.
'''
:param flags: message flags
:return: list of link data
:rtype: link
"""
return [] return []
def info(self, msg):
''' Utility method for printing informational messages when verbose
is turned on.
'''
if self.verbose:
print "%s: %s" % (self.name, msg)
sys.stdout.flush()
def warn(self, msg):
''' Utility method for printing warning/error messages
'''
print >> sys.stderr, "%s: %s" % (self.name, msg)
sys.stderr.flush()
def exception(self, level, source, text):
''' Generate an Exception Message for this session, providing this
object number.
'''
if self.session:
id = None
if isinstance(self.objid, int):
id = self.objid
elif isinstance(self.objid, str) and self.objid.isdigit():
id = int(self.objid)
self.session.exception(level, source, id, text)
class PyCoreNode(PyCoreObj): class PyCoreNode(PyCoreObj):
''' Base class for nodes """
''' Base class for CORE nodes.
def __init__(self, session, objid = None, name = None, verbose = False, """
start = True):
''' Initialization for node objects. # TODO: start seems like it should go away
''' def __init__(self, session, objid=None, name=None, start=True):
PyCoreObj.__init__(self, session, objid, name, verbose=verbose, """
start=start) Create a PyCoreNode instance.
:param core.session.Session session: CORE session object
:param int objid: object id
:param str name: object name
:param bool start: boolean for starting
"""
PyCoreObj.__init__(self, session, objid, name, start=start)
self.services = [] self.services = []
if not hasattr(self, "type"): if not hasattr(self, "type"):
self.type = None self.type = None
self.nodedir = None self.nodedir = None
self.tmpnodedir = False
# TODO: getter method that should not be needed
def nodeid(self): def nodeid(self):
"""
Retrieve node id.
:return: node id
:rtype: int
"""
return self.objid return self.objid
def addservice(self, service): def addservice(self, service):
"""
Add a services to the service list.
:param core.service.CoreService service: service to add
:return: nothing
"""
if service is not None: if service is not None:
self.services.append(service) self.services.append(service)
def makenodedir(self): def makenodedir(self):
"""
Create the node directory.
:return: nothing
"""
if self.nodedir is None: if self.nodedir is None:
self.nodedir = \ self.nodedir = os.path.join(self.session.session_dir, self.name + ".conf")
os.path.join(self.session.sessiondir, self.name + ".conf")
os.makedirs(self.nodedir) os.makedirs(self.nodedir)
self.tmpnodedir = True self.tmpnodedir = True
else: else:
self.tmpnodedir = False self.tmpnodedir = False
def rmnodedir(self): def rmnodedir(self):
if hasattr(self.session.options, 'preservedir'): """
if self.session.options.preservedir == '1': Remove the node directory, unless preserve directory has been set.
:return: nothing
"""
preserve = getattr(self.session.options, "preservedir", None)
if preserve == "1":
return return
if self.tmpnodedir: if self.tmpnodedir:
shutil.rmtree(self.nodedir, ignore_errors = True) shutil.rmtree(self.nodedir, ignore_errors=True)
def addnetif(self, netif, ifindex): def addnetif(self, netif, ifindex):
"""
Add network interface to node and set the network interface index if successful.
:param PyCoreNetIf netif: network interface to add
:param int ifindex: interface index
:return: nothing
"""
if ifindex in self._netif: if ifindex in self._netif:
raise ValueError, "ifindex %s already exists" % ifindex raise ValueError("ifindex %s already exists" % ifindex)
self._netif[ifindex] = netif self._netif[ifindex] = netif
# TODO: this hould have probably been set ahead, seems bad to me, check for failure and fix
netif.netindex = ifindex netif.netindex = ifindex
def delnetif(self, ifindex): def delnetif(self, ifindex):
"""
Delete a network interface
:param int ifindex: interface index to delete
:return: nothing
"""
if ifindex not in self._netif: if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex raise ValueError("ifindex %s does not exist" % ifindex)
netif = self._netif.pop(ifindex) netif = self._netif.pop(ifindex)
netif.shutdown() netif.shutdown()
del netif del netif
def netif(self, ifindex, net = None): # TODO: net parameter is not used, remove
def netif(self, ifindex, net=None):
"""
Retrieve network interface.
:param int ifindex: index of interface to retrieve
:param PyCoreNetIf net: network node
:return: network interface, or None if not found
:rtype: PyCoreNetIf
"""
if ifindex in self._netif: if ifindex in self._netif:
return self._netif[ifindex] return self._netif[ifindex]
else: else:
return None return None
def attachnet(self, ifindex, net): def attachnet(self, ifindex, net):
"""
Attach a network.
:param int ifindex: interface of index to attach
:param PyCoreNetIf net: network to attach
:return:
"""
if ifindex not in self._netif: if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex raise ValueError("ifindex %s does not exist" % ifindex)
self._netif[ifindex].attachnet(net) self._netif[ifindex].attachnet(net)
def detachnet(self, ifindex): def detachnet(self, ifindex):
"""
Detach network interface.
:param int ifindex: interface index to detach
:return: nothing
"""
if ifindex not in self._netif: if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex raise ValueError("ifindex %s does not exist" % ifindex)
self._netif[ifindex].detachnet() self._netif[ifindex].detachnet()
def setposition(self, x = None, y = None, z = None): def setposition(self, x=None, y=None, z=None):
changed = PyCoreObj.setposition(self, x = x, y = y, z = z) """
if not changed: Set position.
# save extra interface range calculations
return :param x: x position
:param y: y position
:param z: z position
:return: nothing
"""
changed = super(PyCoreNode, self).setposition(x, y, z)
if changed:
for netif in self.netifs(sort=True): for netif in self.netifs(sort=True):
netif.setposition(x, y, z) netif.setposition(x, y, z)
def commonnets(self, obj, want_ctrl=False): def commonnets(self, obj, want_ctrl=False):
''' Given another node or net object, return common networks between """
Given another node or net object, return common networks between
this node and that object. A list of tuples is returned, with each tuple this node and that object. A list of tuples is returned, with each tuple
consisting of (network, interface1, interface2). consisting of (network, interface1, interface2).
'''
r = [] :param obj: object to get common network with
:param want_ctrl: flag set to determine if control network are wanted
:return: tuples of common networks
:rtype: list
"""
common = []
for netif1 in self.netifs(): for netif1 in self.netifs():
if not want_ctrl and hasattr(netif1, 'control'): if not want_ctrl and hasattr(netif1, "control"):
continue continue
for netif2 in obj.netifs(): for netif2 in obj.netifs():
if netif1.net == netif2.net: if netif1.net == netif2.net:
r += (netif1.net, netif1, netif2), common.append((netif1.net, netif1, netif2))
return r
return common
class PyCoreNet(PyCoreObj): class PyCoreNet(PyCoreObj):
''' Base class for networks """
''' Base class for networks
linktype = coreapi.CORE_LINK_WIRED """
linktype = LinkTypes.WIRED.value
def __init__(self, session, objid, name, verbose = False, start = True): # TODO: remove start if appropriate
''' Initialization for network objects. def __init__(self, session, objid, name, start=True):
''' """
PyCoreObj.__init__(self, session, objid, name, verbose=verbose, Create a PyCoreNet instance.
start=start)
:param core.session.Session session: CORE session object
:param int objid: object id
:param str name: object name
:param bool start: should object start
"""
PyCoreObj.__init__(self, session, objid, name, start=start)
self._linked = {} self._linked = {}
self._linked_lock = threading.Lock() self._linked_lock = threading.Lock()
def attach(self, netif): def attach(self, netif):
"""
Attach network interface.
:param PyCoreNetIf netif: network interface to attach
:return: nothing
"""
i = self.newifindex() i = self.newifindex()
self._netif[i] = netif self._netif[i] = netif
netif.netifi = i netif.netifi = i
@ -302,43 +454,53 @@ class PyCoreNet(PyCoreObj):
self._linked[netif] = {} self._linked[netif] = {}
def detach(self, netif): def detach(self, netif):
"""
Detach network interface.
:param PyCoreNetIf netif: network interface to detach
:return: nothing
"""
del self._netif[netif.netifi] del self._netif[netif.netifi]
netif.netifi = None netif.netifi = None
with self._linked_lock: with self._linked_lock:
del self._linked[netif] del self._linked[netif]
# TODO: needs to be abstracted out, seems like it may be ok to remove
def netifparamstolink(self, netif): def netifparamstolink(self, netif):
''' Helper for tolinkmsgs() to build TLVs having link parameters """
from interface parameters. Helper for tolinkmsgs() to build TLVs having link parameters from interface parameters.
'''
:param PyCoreNetIf netif: network interface to retrieve params from
:return: tlv data
"""
delay = netif.getparam("delay")
bw = netif.getparam("bw")
loss = netif.getparam("loss")
duplicate = netif.getparam("duplicate")
jitter = netif.getparam("jitter")
tlvdata = "" tlvdata = ""
delay = netif.getparam('delay')
bw = netif.getparam('bw')
loss = netif.getparam('loss')
duplicate = netif.getparam('duplicate')
jitter = netif.getparam('jitter')
if delay is not None: if delay is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_DELAY, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DELAY.value, delay)
delay)
if bw is not None: if bw is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_BW, bw) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.BANDWIDTH.value, bw)
if loss is not None: if loss is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_PER, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.PER.value, str(loss))
str(loss))
if duplicate is not None: if duplicate is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_DUP, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DUP.value, str(duplicate))
str(duplicate))
if jitter is not None: if jitter is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_JITTER, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.JITTER.value, jitter)
jitter)
return tlvdata return tlvdata
def all_link_data(self, flags):
"""
Build link data objects for this network. Each link object describes a link
between this network and a node.
"""
all_links = []
def tolinkmsgs(self, flags):
''' Build CORE API Link Messages for this network. Each link message
describes a link between this network and a node.
'''
msgs = []
# build a link message from this network node to each node having a # build a link message from this network node to each node having a
# connected interface # connected interface
for netif in self.netifs(sort=True): for netif in self.netifs(sort=True):
@ -359,61 +521,85 @@ class PyCoreNet(PyCoreObj):
if netif.getparams() != upstream_params: if netif.getparams() != upstream_params:
uni = True uni = True
tlvdata = "" unidirectional = 0
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
self.objid)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
otherobj.objid)
tlvdata += self.netifparamstolink(netif)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_TYPE,
self.linktype)
if uni: if uni:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, unidirectional = 1
1)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, interface2_ip4 = None
otherobj.getifindex(netif)) interface2_ip4_mask = None
if netif.hwaddr: interface2_ip6 = None
tlvdata += \ interface2_ip6_mask = None
coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2MAC, for address in netif.addrlist:
netif.hwaddr) ip, sep, mask = address.partition('/')
for addr in netif.addrlist:
(ip, sep, mask) = addr.partition('/')
mask = int(mask) mask = int(mask)
if isIPv4Address(ip): if ipaddress.is_ipv4_address(ip):
family = AF_INET family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP4 ipl = socket.inet_pton(family, ip)
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP4MASK interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip4_mask = mask
else: else:
family = AF_INET6 family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP6MASK
ipl = socket.inet_pton(family, ip) ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, \ interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
IPAddr(af=family, addr=ipl)) interface2_ip6_mask = mask
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
# TODO: not currently used
# loss = netif.getparam('loss')
link_data = LinkData(
message_type=flags,
node1_id=self.objid,
node2_id=otherobj.objid,
link_type=self.linktype,
unidirectional=unidirectional,
interface2_id=otherobj.getifindex(netif),
interface2_mac=netif.hwaddr,
interface2_ip4=interface2_ip4,
interface2_ip4_mask=interface2_ip4_mask,
interface2_ip6=interface2_ip6,
interface2_ip6_mask=interface2_ip6_mask,
delay=netif.getparam("delay"),
bandwidth=netif.getparam("bw"),
dup=netif.getparam("duplicate"),
jitter=netif.getparam("jitter")
)
all_links.append(link_data)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
msgs.append(msg)
if not uni: if not uni:
continue continue
# build a 2nd link message for any upstream link parameters
tlvdata = ""
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
otherobj.objid)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
self.objid)
netif.swapparams('_params_up') netif.swapparams('_params_up')
tlvdata += self.netifparamstolink(netif) link_data = LinkData(
message_type=0,
node1_id=otherobj.objid,
node2_id=self.objid,
unidirectional=1,
delay=netif.getparam("delay"),
bandwidth=netif.getparam("bw"),
dup=netif.getparam("duplicate"),
jitter=netif.getparam("jitter")
)
netif.swapparams('_params_up') netif.swapparams('_params_up')
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1)
msg = coreapi.CoreLinkMessage.pack(0, tlvdata) all_links.append(link_data)
msgs.append(msg)
return msgs return all_links
class PyCoreNetIf(object): class PyCoreNetIf(object):
''' Base class for interfaces. """
''' Base class for network interfaces.
"""
def __init__(self, node, name, mtu): def __init__(self, node, name, mtu):
"""
Creates a PyCoreNetIf instance.
:param node: node for interface
:param str name: interface name
:param mtu: mtu value
"""
self.node = node self.node = node
self.name = name self.name = name
if not isinstance(mtu, (int, long)): if not isinstance(mtu, (int, long)):
@ -428,67 +614,119 @@ class PyCoreNetIf(object):
self.transport_type = None self.transport_type = None
# interface index on the network # interface index on the network
self.netindex = None self.netindex = None
# index used to find flow data
self.flow_id = None
def startup(self): def startup(self):
"""
Startup method for the interface.
:return: nothing
"""
pass pass
def shutdown(self): def shutdown(self):
"""
Shutdown method for the interface.
:return: nothing
"""
pass pass
def attachnet(self, net): def attachnet(self, net):
"""
Attach network.
:param PyCoreNet net: network to attach to
:return:nothing
"""
if self.net: if self.net:
self.detachnet() self.detachnet()
self.net = None self.net = None
net.attach(self) net.attach(self)
self.net = net self.net = net
def detachnet(self): def detachnet(self):
"""
Detach from a network.
:return: nothing
"""
if self.net is not None: if self.net is not None:
self.net.detach(self) self.net.detach(self)
def addaddr(self, addr): def addaddr(self, addr):
"""
Add address.
:param str addr: address to add
:return: nothing
"""
self.addrlist.append(addr) self.addrlist.append(addr)
def deladdr(self, addr): def deladdr(self, addr):
"""
Delete address.
:param str addr: address to delete
:return: nothing
"""
self.addrlist.remove(addr) self.addrlist.remove(addr)
def sethwaddr(self, addr): def sethwaddr(self, addr):
"""
Set hardware address.
:param core.misc.ipaddress.MacAddress addr: hardware address to set to.
:return: nothing
"""
self.hwaddr = addr self.hwaddr = addr
def getparam(self, key): def getparam(self, key):
''' Retrieve a parameter from the _params dict, """
or None if the parameter does not exist. Retrieve a parameter from the, or None if the parameter does not exist.
'''
if key not in self._params: :param key: parameter to get value for
return None :return: parameter value
return self._params[key] """
return self._params.get(key)
def getparams(self): def getparams(self):
''' Return (key, value) pairs from the _params dict. """
''' Return (key, value) pairs for parameters.
r = [] """
parameters = []
for k in sorted(self._params.keys()): for k in sorted(self._params.keys()):
r.append((k, self._params[k])) parameters.append((k, self._params[k]))
return r return parameters
def setparam(self, key, value): def setparam(self, key, value):
''' Set a parameter in the _params dict. """
Returns True if the parameter has changed. Set a parameter value, returns True if the parameter has changed.
'''
if key in self._params: :param key: parameter name to set
if self._params[key] == value: :param value: parameter value
return False :return: True if parameter changed, False otherwise
elif self._params[key] <= 0 and value <= 0: """
# treat None and 0 as unchanged values # treat None and 0 as unchanged values
current_value = self._params.get(key)
if current_value == value or current_value <= 0 and value <= 0:
return False return False
self._params[key] = value self._params[key] = value
return True return True
def swapparams(self, name): def swapparams(self, name):
''' Swap out the _params dict for name. If name does not exist, """
Swap out parameters dict for name. If name does not exist,
intialize it. This is for supporting separate upstream/downstream intialize it. This is for supporting separate upstream/downstream
parameters when two layer-2 nodes are linked together. parameters when two layer-2 nodes are linked together.
'''
:param str name: name of parameter to swap
:return: nothing
"""
tmp = self._params tmp = self._params
if not hasattr(self, name): if not hasattr(self, name):
setattr(self, name, {}) setattr(self, name, {})
@ -496,8 +734,13 @@ class PyCoreNetIf(object):
setattr(self, name, tmp) setattr(self, name, tmp)
def setposition(self, x, y, z): def setposition(self, x, y, z):
''' Dispatch to any position hook (self.poshook) handler. """
''' Dispatch position hook handler.
:param x: x position
:param y: y position
:param z: z position
:return: nothing
"""
if self.poshook is not None: if self.poshook is not None:
self.poshook(self, x, y, z) self.poshook(self, x, y, z)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,54 @@
import subprocess
from core.misc import log
from core.misc import utils
logger = log.get_logger(__name__)
EMANEUNK = 0
EMANE074 = 7
EMANE081 = 8
EMANE091 = 91
EMANE092 = 92
EMANE093 = 93
EMANE101 = 101
VERSION = None
VERSIONSTR = None
def emane_version():
"""
Return the locally installed EMANE version identifier and string.
"""
global VERSION
global VERSIONSTR
cmd = ("emane", "--version")
try:
status, result = utils.cmdresult(cmd)
except (OSError, subprocess.CalledProcessError):
logger.exception("error checking emane version")
status = -1
result = ""
VERSION = EMANEUNK
if status == 0:
if result.startswith("0.7.4"):
VERSION = EMANE074
elif result.startswith("0.8.1"):
VERSION = EMANE081
elif result.startswith("0.9.1"):
VERSION = EMANE091
elif result.startswith("0.9.2"):
VERSION = EMANE092
elif result.startswith("0.9.3"):
VERSION = EMANE093
elif result.startswith("1.0.1"):
VERSION = EMANE101
VERSIONSTR = result.strip()
# set version variables for the Emane class
emane_version()

View file

@ -1,48 +1,38 @@
# """
# CORE
# Copyright (c)2011-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
bypass.py: EMANE Bypass model for CORE bypass.py: EMANE Bypass model for CORE
''' """
import sys from core.emane.emanemodel import EmaneModel
import string from core.enumerations import ConfigDataTypes
from core.api import coreapi
from core.constants import *
from emane import EmaneModel
class EmaneBypassModel(EmaneModel): class EmaneBypassModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False): def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, objid, verbose) EmaneModel.__init__(self, session, object_id)
_name = "emane_bypass" name = "emane_bypass"
_confmatrix = [ config_matrix = [
("none",coreapi.CONF_DATA_TYPE_BOOL, '0', ("none", ConfigDataTypes.BOOL.value, '0',
'True,False','There are no parameters for the bypass model.'), 'True,False', 'There are no parameters for the bypass model.'),
] ]
# value groupings # value groupings
_confgroups = "Bypass Parameters:1-1" config_groups = "Bypass Parameters:1-1"
def buildnemxmlfiles(self, e, ifc): def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem, mac, and phy XMLs in the given path. """
Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_bypassnem.xml, that file also. Otherwise the WLAN-wide nXXemane_bypassnem.xml,
nXXemane_bypassmac.xml, nXXemane_bypassphy.xml are used. nXXemane_bypassmac.xml, nXXemane_bypassphy.xml are used.
''' """
values = e.getifcconfig(self.objid, self._name, values = e.getifcconfig(self.object_id, self.name, self.getdefaultvalues(), ifc)
self.getdefaultvalues(), ifc)
if values is None: if values is None:
return return
nemdoc = e.xmldoc("nem") nemdoc = e.xmldoc("nem")
nem = nemdoc.getElementsByTagName("nem").pop() nem = nemdoc.getElementsByTagName("nem").pop()
nem.setAttribute("name", "BYPASS NEM") nem.setAttribute("name", "BYPASS NEM")
e.appendtransporttonem(nemdoc, nem, self.objid, ifc) e.appendtransporttonem(nemdoc, nem, self.object_id, ifc)
mactag = nemdoc.createElement("mac") mactag = nemdoc.createElement("mac")
mactag.setAttribute("definition", self.macxmlname(ifc)) mactag.setAttribute("definition", self.macxmlname(ifc))
nem.appendChild(mactag) nem.appendChild(mactag)
@ -62,5 +52,3 @@ class EmaneBypassModel(EmaneModel):
phy.setAttribute("name", "BYPASS PHY") phy.setAttribute("name", "BYPASS PHY")
phy.setAttribute("library", "bypassphylayer") phy.setAttribute("library", "bypassphylayer")
e.xmlwrite(phydoc, self.phyxmlname(ifc)) e.xmlwrite(phydoc, self.phyxmlname(ifc))

View file

@ -1,76 +1,70 @@
# """
# CORE
# Copyright (c)2010-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
# Randy Charland <rcharland@ll.mit.edu>
#
'''
commeffect.py: EMANE CommEffect model for CORE commeffect.py: EMANE CommEffect model for CORE
''' """
from core import emane
from core.emane.emanemodel import EmaneModel
from core.enumerations import ConfigDataTypes
from core.misc import log
logger = log.get_logger(__name__)
import sys
import string
try: try:
from emanesh.events import EventService from emanesh.events import EventService
except: except ImportError:
pass logger.error("error importing emanesh")
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
try: try:
import emaneeventservice import emaneeventservice
import emaneeventcommeffect import emaneeventcommeffect
except Exception, e: except ImportError:
pass logger.error("error importing emaneeventservice and emaneeventcommeffect")
class EmaneCommEffectModel(EmaneModel): class EmaneCommEffectModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False): def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, objid, verbose) EmaneModel.__init__(self, session, object_id)
# model name # model name
_name = "emane_commeffect" name = "emane_commeffect"
# CommEffect parameters # CommEffect parameters
_confmatrix_shim_base = [ _confmatrix_shim_base = [
("filterfile", coreapi.CONF_DATA_TYPE_STRING, '', ("filterfile", ConfigDataTypes.STRING.value, '',
'', 'filter file'), '', 'filter file'),
("groupid", coreapi.CONF_DATA_TYPE_UINT32, '0', ("groupid", ConfigDataTypes.UINT32.value, '0',
'', 'NEM Group ID'), '', 'NEM Group ID'),
("enablepromiscuousmode", coreapi.CONF_DATA_TYPE_BOOL, '0', ("enablepromiscuousmode", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable promiscuous mode'), 'On,Off', 'enable promiscuous mode'),
("receivebufferperiod", coreapi.CONF_DATA_TYPE_FLOAT, '1.0', ("receivebufferperiod", ConfigDataTypes.FLOAT.value, '1.0',
'', 'receivebufferperiod'), '', 'receivebufferperiod'),
] ]
_confmatrix_shim_081 = [ _confmatrix_shim_081 = [
("defaultconnectivity", coreapi.CONF_DATA_TYPE_BOOL, '0', ("defaultconnectivity", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'defaultconnectivity'), 'On,Off', 'defaultconnectivity'),
("enabletighttimingmode", coreapi.CONF_DATA_TYPE_BOOL, '0', ("enabletighttimingmode", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable tight timing mode'), 'On,Off', 'enable tight timing mode'),
] ]
_confmatrix_shim_091 = [ _confmatrix_shim_091 = [
("defaultconnectivitymode", coreapi.CONF_DATA_TYPE_BOOL, '0', ("defaultconnectivitymode", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'defaultconnectivity'), 'On,Off', 'defaultconnectivity'),
] ]
if Emane.version >= Emane.EMANE091: if emane.VERSION >= emane.EMANE091:
_confmatrix_shim = _confmatrix_shim_base + _confmatrix_shim_091 _confmatrix_shim = _confmatrix_shim_base + _confmatrix_shim_091
else: else:
_confmatrix_shim = _confmatrix_shim_base + _confmatrix_shim_081 _confmatrix_shim = _confmatrix_shim_base + _confmatrix_shim_081
_confmatrix = _confmatrix_shim config_matrix = _confmatrix_shim
# value groupings # value groupings
_confgroups = "CommEffect SHIM Parameters:1-%d" \ config_groups = "CommEffect SHIM Parameters:1-%d" % len(_confmatrix_shim)
% len(_confmatrix_shim)
def buildnemxmlfiles(self, e, ifc): def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem and commeffect XMLs in the given path. """
Build the necessary nem and commeffect XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide that file also. Otherwise the WLAN-wide
nXXemane_commeffectnem.xml, nXXemane_commeffectshim.xml are used. nXXemane_commeffectnem.xml, nXXemane_commeffectshim.xml are used.
''' """
values = e.getifcconfig(self.objid, self._name, values = e.getifcconfig(self.object_id, self.name, self.getdefaultvalues(), ifc)
self.getdefaultvalues(), ifc)
if values is None: if values is None:
return return
shimdoc = e.xmldoc("shim") shimdoc = e.xmldoc("shim")
@ -83,8 +77,7 @@ class EmaneCommEffectModel(EmaneModel):
shimnames.remove("filterfile") shimnames.remove("filterfile")
# append all shim options (except filterfile) to shimdoc # append all shim options (except filterfile) to shimdoc
map( lambda n: shim.appendChild(e.xmlparam(shimdoc, n, \ map(lambda n: shim.appendChild(e.xmlparam(shimdoc, n, self.valueof(n, values))), shimnames)
self.valueof(n, values))), shimnames)
# empty filterfile is not allowed # empty filterfile is not allowed
ff = self.valueof("filterfile", values) ff = self.valueof("filterfile", values)
if ff.strip() != '': if ff.strip() != '':
@ -95,20 +88,24 @@ class EmaneCommEffectModel(EmaneModel):
nem = nemdoc.getElementsByTagName("nem").pop() nem = nemdoc.getElementsByTagName("nem").pop()
nem.setAttribute("name", "commeffect NEM") nem.setAttribute("name", "commeffect NEM")
nem.setAttribute("type", "unstructured") nem.setAttribute("type", "unstructured")
e.appendtransporttonem(nemdoc, nem, self.objid, ifc) e.appendtransporttonem(nemdoc, nem, self.object_id, ifc)
nem.appendChild(e.xmlshimdefinition(nemdoc, self.shimxmlname(ifc))) nem.appendChild(e.xmlshimdefinition(nemdoc, self.shimxmlname(ifc)))
e.xmlwrite(nemdoc, self.nemxmlname(ifc)) e.xmlwrite(nemdoc, self.nemxmlname(ifc))
def linkconfig(self, netif, bw = None, delay = None, def linkconfig(self, netif, bw=None, delay=None,
loss = None, duplicate = None, jitter = None, netif2 = None): loss=None, duplicate=None, jitter=None, netif2=None):
''' Generate CommEffect events when a Link Message is received having """
Generate CommEffect events when a Link Message is received having
link parameters. link parameters.
''' """
if self.session.emane.version >= self.session.emane.EMANE091: if emane.VERSION >= emane.EMANE091:
raise NotImplementedError, \ raise NotImplementedError, \
"CommEffect linkconfig() not implemented for EMANE 0.9.1+" "CommEffect linkconfig() not implemented for EMANE 0.9.1+"
def z(x): def z(x):
''' Helper to use 0 for None values. ''' """
Helper to use 0 for None values.
"""
if type(x) is str: if type(x) is str:
x = float(x) x = float(x)
if x is None: if x is None:
@ -118,17 +115,16 @@ class EmaneCommEffectModel(EmaneModel):
service = self.session.emane.service service = self.session.emane.service
if service is None: if service is None:
self.session.warn("%s: EMANE event service unavailable" % \ logger.warn("%s: EMANE event service unavailable" % self.name)
self._name)
return return
if netif is None or netif2 is None: if netif is None or netif2 is None:
self.session.warn("%s: missing NEM information" % self._name) logger.warn("%s: missing NEM information" % self.name)
return return
# TODO: batch these into multiple events per transmission # TODO: batch these into multiple events per transmission
# TODO: may want to split out seconds portion of delay and jitter # TODO: may want to split out seconds portion of delay and jitter
event = emaneeventcommeffect.EventCommEffect(1) event = emaneeventcommeffect.EventCommEffect(1)
index = 0 index = 0
e = self.session.obj(self.objid) e = self.session.get_object(self.object_id)
nemid = e.getnemid(netif) nemid = e.getnemid(netif)
nemid2 = e.getnemid(netif2) nemid2 = e.getnemid(netif2)
mbw = bw mbw = bw
@ -139,6 +135,3 @@ class EmaneCommEffectModel(EmaneModel):
emaneeventservice.PLATFORMID_ANY, emaneeventservice.PLATFORMID_ANY,
nemid2, emaneeventservice.COMPONENTID_ANY, nemid2, emaneeventservice.COMPONENTID_ANY,
event.export()) event.export())

File diff suppressed because it is too large Load diff

View file

@ -1,60 +1,56 @@
# """
# CORE
# Copyright (c)2010-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
ieee80211abg.py: EMANE IEEE 802.11abg model for CORE ieee80211abg.py: EMANE IEEE 802.11abg model for CORE
''' """
from core import emane
from core.emane.emanemodel import EmaneModel
from core.emane.universal import EmaneUniversalModel
from core.enumerations import ConfigDataTypes
from core.misc import log
logger = log.get_logger(__name__)
import sys
import string
try: try:
from emanesh.events import EventService from emanesh.events import EventService
except: except:
pass logger.error("error importing emanesh")
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
from universal import EmaneUniversalModel
class EmaneIeee80211abgModel(EmaneModel): class EmaneIeee80211abgModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False): def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, objid, verbose) EmaneModel.__init__(self, session, object_id)
# model name # model name
_name = "emane_ieee80211abg" name = "emane_ieee80211abg"
_80211rates = '1 1 Mbps,2 2 Mbps,3 5.5 Mbps,4 11 Mbps,5 6 Mbps,' + \ _80211rates = '1 1 Mbps,2 2 Mbps,3 5.5 Mbps,4 11 Mbps,5 6 Mbps,' + \
'6 9 Mbps,7 12 Mbps,8 18 Mbps,9 24 Mbps,10 36 Mbps,11 48 Mbps,' + \ '6 9 Mbps,7 12 Mbps,8 18 Mbps,9 24 Mbps,10 36 Mbps,11 48 Mbps,' + \
'12 54 Mbps' '12 54 Mbps'
if Emane.version >= Emane.EMANE091: if emane.VERSION >= emane.EMANE091:
xml_path = '/usr/share/emane/xml/models/mac/ieee80211abg' xml_path = '/usr/share/emane/xml/models/mac/ieee80211abg'
else: else:
xml_path = "/usr/share/emane/models/ieee80211abg/xml" xml_path = "/usr/share/emane/models/ieee80211abg/xml"
# MAC parameters # MAC parameters
_confmatrix_mac_base = [ _confmatrix_mac_base = [
("mode", coreapi.CONF_DATA_TYPE_UINT8, '0', ("mode", ConfigDataTypes.UINT8.value, '0',
'0 802.11b (DSSS only),1 802.11b (DSSS only),' + \ '0 802.11b (DSSS only),1 802.11b (DSSS only),' + \
'2 802.11a or g (OFDM),3 802.11b/g (DSSS and OFDM)', 'mode'), '2 802.11a or g (OFDM),3 802.11b/g (DSSS and OFDM)', 'mode'),
("enablepromiscuousmode", coreapi.CONF_DATA_TYPE_BOOL, '0', ("enablepromiscuousmode", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable promiscuous mode'), 'On,Off', 'enable promiscuous mode'),
("distance", coreapi.CONF_DATA_TYPE_UINT32, '1000', ("distance", ConfigDataTypes.UINT32.value, '1000',
'', 'max distance (m)'), '', 'max distance (m)'),
("unicastrate", coreapi.CONF_DATA_TYPE_UINT8, '4', _80211rates, ("unicastrate", ConfigDataTypes.UINT8.value, '4', _80211rates,
'unicast rate (Mbps)'), 'unicast rate (Mbps)'),
("multicastrate", coreapi.CONF_DATA_TYPE_UINT8, '1', _80211rates, ("multicastrate", ConfigDataTypes.UINT8.value, '1', _80211rates,
'multicast rate (Mbps)'), 'multicast rate (Mbps)'),
("rtsthreshold", coreapi.CONF_DATA_TYPE_UINT16, '0', ("rtsthreshold", ConfigDataTypes.UINT16.value, '0',
'', 'RTS threshold (bytes)'), '', 'RTS threshold (bytes)'),
("pcrcurveuri", coreapi.CONF_DATA_TYPE_STRING, ("pcrcurveuri", ConfigDataTypes.STRING.value,
'%s/ieee80211pcr.xml' % xml_path, '%s/ieee80211pcr.xml' % xml_path,
'', 'SINR/PCR curve file'), '', 'SINR/PCR curve file'),
("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0', ("flowcontrolenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable traffic flow control'), 'On,Off', 'enable traffic flow control'),
("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10', ("flowcontroltokens", ConfigDataTypes.UINT16.value, '10',
'', 'number of flow control tokens'), '', 'number of flow control tokens'),
] ]
# mac parameters introduced in EMANE 0.8.1 # mac parameters introduced in EMANE 0.8.1
@ -62,56 +58,56 @@ class EmaneIeee80211abgModel(EmaneModel):
# EMANE 9.x, but are being preserved for the time being due to space constraints in the # EMANE 9.x, but are being preserved for the time being due to space constraints in the
# CORE GUI. A conversion function (get9xmacparamequivalent) has been defined to support this. # CORE GUI. A conversion function (get9xmacparamequivalent) has been defined to support this.
_confmatrix_mac_extended = [ _confmatrix_mac_extended = [
("wmmenable", coreapi.CONF_DATA_TYPE_BOOL, '0', ("wmmenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'WiFi Multimedia (WMM)'), 'On,Off', 'WiFi Multimedia (WMM)'),
("queuesize", coreapi.CONF_DATA_TYPE_STRING, '0:255 1:255 2:255 3:255', ("queuesize", ConfigDataTypes.STRING.value, '0:255 1:255 2:255 3:255',
'', 'queue size (0-4:size)'), '', 'queue size (0-4:size)'),
("cwmin", coreapi.CONF_DATA_TYPE_STRING, '0:32 1:32 2:16 3:8', ("cwmin", ConfigDataTypes.STRING.value, '0:32 1:32 2:16 3:8',
'', 'min contention window (0-4:minw)'), '', 'min contention window (0-4:minw)'),
("cwmax", coreapi.CONF_DATA_TYPE_STRING, '0:1024 1:1024 2:64 3:16', ("cwmax", ConfigDataTypes.STRING.value, '0:1024 1:1024 2:64 3:16',
'', 'max contention window (0-4:maxw)'), '', 'max contention window (0-4:maxw)'),
("aifs", coreapi.CONF_DATA_TYPE_STRING, '0:2 1:2 2:2 3:1', ("aifs", ConfigDataTypes.STRING.value, '0:2 1:2 2:2 3:1',
'', 'arbitration inter frame space (0-4:aifs)'), '', 'arbitration inter frame space (0-4:aifs)'),
("txop", coreapi.CONF_DATA_TYPE_STRING, '0:0 1:0 2:0 3:0', ("txop", ConfigDataTypes.STRING.value, '0:0 1:0 2:0 3:0',
'', 'txop (0-4:usec)'), '', 'txop (0-4:usec)'),
("retrylimit", coreapi.CONF_DATA_TYPE_STRING, '0:3 1:3 2:3 3:3', ("retrylimit", ConfigDataTypes.STRING.value, '0:3 1:3 2:3 3:3',
'', 'retry limit (0-4:numretries)'), '', 'retry limit (0-4:numretries)'),
] ]
_confmatrix_mac_091 = [ _confmatrix_mac_091 = [
('radiometricenable', coreapi.CONF_DATA_TYPE_BOOL, '0', ('radiometricenable', ConfigDataTypes.BOOL.value, '0',
'On,Off', 'report radio metrics via R2RI'), 'On,Off', 'report radio metrics via R2RI'),
('radiometricreportinterval', coreapi.CONF_DATA_TYPE_FLOAT, '1.0', ('radiometricreportinterval', ConfigDataTypes.FLOAT.value, '1.0',
'', 'R2RI radio metric report interval (sec)'), '', 'R2RI radio metric report interval (sec)'),
('neighbormetricdeletetime', coreapi.CONF_DATA_TYPE_FLOAT, '60.0', ('neighbormetricdeletetime', ConfigDataTypes.FLOAT.value, '60.0',
'', 'R2RI neighbor table inactivity time (sec)'), '', 'R2RI neighbor table inactivity time (sec)'),
] ]
_confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_extended _confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_extended
if Emane.version >= Emane.EMANE091: if emane.VERSION >= emane.EMANE091:
_confmatrix_mac += _confmatrix_mac_091 _confmatrix_mac += _confmatrix_mac_091
# PHY parameters from Universal PHY # PHY parameters from Universal PHY
_confmatrix_phy = EmaneUniversalModel._confmatrix _confmatrix_phy = EmaneUniversalModel.config_matrix
_confmatrix = _confmatrix_mac + _confmatrix_phy config_matrix = _confmatrix_mac + _confmatrix_phy
# value groupings # value groupings
_confgroups = "802.11 MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" \ config_groups = "802.11 MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % (
% (len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(_confmatrix)) len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(config_matrix))
def buildnemxmlfiles(self, e, ifc): def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem, mac, and phy XMLs in the given path. """
Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide that file also. Otherwise the WLAN-wide
nXXemane_ieee80211abgnem.xml, nXXemane_ieee80211abgemac.xml, nXXemane_ieee80211abgnem.xml, nXXemane_ieee80211abgemac.xml,
nXXemane_ieee80211abgphy.xml are used. nXXemane_ieee80211abgphy.xml are used.
''' """
values = e.getifcconfig(self.objid, self._name, values = e.getifcconfig(self.object_id, self.name, self.getdefaultvalues(), ifc)
self.getdefaultvalues(), ifc)
if values is None: if values is None:
return return
nemdoc = e.xmldoc("nem") nemdoc = e.xmldoc("nem")
nem = nemdoc.getElementsByTagName("nem").pop() nem = nemdoc.getElementsByTagName("nem").pop()
nem.setAttribute("name", "ieee80211abg NEM") nem.setAttribute("name", "ieee80211abg NEM")
e.appendtransporttonem(nemdoc, nem, self.objid, ifc) e.appendtransporttonem(nemdoc, nem, self.object_id, ifc)
mactag = nemdoc.createElement("mac") mactag = nemdoc.createElement("mac")
mactag.setAttribute("definition", self.macxmlname(ifc)) mactag.setAttribute("definition", self.macxmlname(ifc))
nem.appendChild(mactag) nem.appendChild(mactag)
@ -130,14 +126,13 @@ class EmaneIeee80211abgModel(EmaneModel):
phynames = names[len(self._confmatrix_mac):] phynames = names[len(self._confmatrix_mac):]
# append all MAC options to macdoc # append all MAC options to macdoc
if Emane.version >= Emane.EMANE091: if emane.VERSION >= emane.EMANE091:
for macname in macnames: for macname in macnames:
mac9xnvpairlist = self.get9xmacparamequivalent(macname, values) mac9xnvpairlist = self.get9xmacparamequivalent(macname, values)
for nvpair in mac9xnvpairlist: for nvpair in mac9xnvpairlist:
mac.appendChild(e.xmlparam(macdoc, nvpair[0], nvpair[1])) mac.appendChild(e.xmlparam(macdoc, nvpair[0], nvpair[1]))
else: else:
map( lambda n: mac.appendChild(e.xmlparam(macdoc, n, \ map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, self.valueof(n, values))), macnames)
self.valueof(n, values))), macnames)
e.xmlwrite(macdoc, self.macxmlname(ifc)) e.xmlwrite(macdoc, self.macxmlname(ifc))
@ -150,24 +145,24 @@ class EmaneIeee80211abgModel(EmaneModel):
# and work with EMANE 9.x onwards. # and work with EMANE 9.x onwards.
# #
def get9xmacparamequivalent(self, macname, values): def get9xmacparamequivalent(self, macname, values):
''' Generate a list of 80211abg mac parameters in 0.9.x layout for a given mac parameter """
Generate a list of 80211abg mac parameters in 0.9.x layout for a given mac parameter
in 8.x layout.For mac category parameters, the list returned will contain the four in 8.x layout.For mac category parameters, the list returned will contain the four
equivalent 9.x parameter and value pairs. Otherwise, the list returned will only equivalent 9.x parameter and value pairs. Otherwise, the list returned will only
contain a single name and value pair. contain a single name and value pair.
''' """
nvpairlist = [] nvpairlist = []
macparmval = self.valueof(macname, values) macparmval = self.valueof(macname, values)
if macname in ["queuesize","aifs","cwmin","cwmax","txop","retrylimit"]: if macname in ["queuesize", "aifs", "cwmin", "cwmax", "txop", "retrylimit"]:
for catval in macparmval.split(): for catval in macparmval.split():
idx_and_val = catval.split(":") idx_and_val = catval.split(":")
idx = int(idx_and_val[0]) idx = int(idx_and_val[0])
val = idx_and_val[1] val = idx_and_val[1]
# aifs and tx are in microseconds. Convert to seconds. # aifs and tx are in microseconds. Convert to seconds.
if macname in ["aifs","txop"]: if macname in ["aifs", "txop"]:
val = "%f" % (float(val)*(1e-6)) val = "%f" % (float(val) * 1e-6)
name9x = "%s%d" % (macname, idx) name9x = "%s%d" % (macname, idx)
nvpairlist.append([name9x, val]) nvpairlist.append([name9x, val])
else: else:
nvpairlist.append([macname, macparmval]) nvpairlist.append([macname, macparmval])
return nvpairlist return nvpairlist

View file

@ -1,69 +1,72 @@
# """
# CORE
# Copyright (c)2010-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
nodes.py: definition of an EmaneNode class for implementing configuration nodes.py: definition of an EmaneNode class for implementing configuration
control of an EMANE emulation. An EmaneNode has several attached NEMs that control of an EMANE emulation. An EmaneNode has several attached NEMs that
share the same MAC+PHY model. share the same MAC+PHY model.
''' """
import sys from os import path
import os.path
from core.api import coreapi from core import emane
from core.coreobj import PyCoreNet from core.coreobj import PyCoreNet
from core.enumerations import LinkTypes
from core.enumerations import NodeTypes
from core.enumerations import RegisterTlvs
from core.misc import log
logger = log.get_logger(__name__)
try: try:
from emanesh.events import EventService from emanesh.events import EventService
from emanesh.events import LocationEvent from emanesh.events import LocationEvent
except Exception, e: except ImportError:
pass logger.error("error loading emanesh")
try: try:
import emaneeventservice import emaneeventservice
import emaneeventlocation import emaneeventlocation
except Exception, e: except ImportError:
''' Don't require all CORE users to have EMANE libeventservice and its """
Don't require all CORE users to have EMANE libeventservice and its
Python bindings installed. Python bindings installed.
''' """
pass logger.error("error loading emaneeventservice and emaneeventlocation")
class EmaneNet(PyCoreNet): class EmaneNet(PyCoreNet):
''' EMANE network base class. """
''' EMANE network base class.
apitype = coreapi.CORE_NODE_EMANE """
linktype = coreapi.CORE_LINK_WIRELESS apitype = NodeTypes.EMANE.value
type = "wlan" # icon used linktype = LinkTypes.WIRELESS.value
# icon used
type = "wlan"
class EmaneNode(EmaneNet): class EmaneNode(EmaneNet):
''' EMANE node contains NEM configuration and causes connected nodes """
EMANE node contains NEM configuration and causes connected nodes
to have TAP interfaces (instead of VEth). These are managed by the to have TAP interfaces (instead of VEth). These are managed by the
Emane controller object that exists in a session. Emane controller object that exists in a session.
''' """
def __init__(self, session, objid = None, name = None, verbose = False,
start = True): def __init__(self, session, objid=None, name=None, start=True):
PyCoreNet.__init__(self, session, objid, name, verbose, start) PyCoreNet.__init__(self, session, objid, name, start)
self.verbose = verbose
self.conf = "" self.conf = ""
self.up = False self.up = False
self.nemidmap = {} self.nemidmap = {}
self.model = None self.model = None
self.mobility = None self.mobility = None
def linkconfig(self, netif, bw = None, delay = None, def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None, jitter=None, netif2=None):
loss = None, duplicate = None, jitter = None, netif2 = None): """
''' The CommEffect model supports link configuration. The CommEffect model supports link configuration.
''' """
if not self.model: if not self.model:
return return
return self.model.linkconfig(netif=netif, bw=bw, delay=delay, loss=loss, return self.model.linkconfig(netif=netif, bw=bw, delay=delay, loss=loss,
duplicate=duplicate, jitter=jitter, netif2=netif2) duplicate=duplicate, jitter=jitter, netif2=netif2)
def config(self, conf): def config(self, conf):
#print "emane", self.name, "got config:", conf
self.conf = conf self.conf = conf
def shutdown(self): def shutdown(self):
@ -76,55 +79,57 @@ class EmaneNode(EmaneNet):
pass pass
def setmodel(self, model, config): def setmodel(self, model, config):
''' set the EmaneModel associated with this node """
''' set the EmaneModel associated with this node
if (self.verbose): """
self.info("adding model %s" % model._name) logger.info("adding model: %s", model.name)
if model._type == coreapi.CORE_TLV_REG_WIRELESS: if model.config_type == RegisterTlvs.WIRELESS.value:
# EmaneModel really uses values from ConfigurableManager # EmaneModel really uses values from ConfigurableManager
# when buildnemxml() is called, not during init() # when buildnemxml() is called, not during init()
self.model = model(session=self.session, objid=self.objid, self.model = model(session=self.session, object_id=self.objid)
verbose=self.verbose) elif model.config_type == RegisterTlvs.MOBILITY.value:
elif model._type == coreapi.CORE_TLV_REG_MOBILITY: self.mobility = model(session=self.session, object_id=self.objid, values=config)
self.mobility = model(session=self.session, objid=self.objid,
verbose=self.verbose, values=config)
def setnemid(self, netif, nemid): def setnemid(self, netif, nemid):
''' Record an interface to numerical ID mapping. The Emane controller """
Record an interface to numerical ID mapping. The Emane controller
object manages and assigns these IDs for all NEMs. object manages and assigns these IDs for all NEMs.
''' """
self.nemidmap[netif] = nemid self.nemidmap[netif] = nemid
def getnemid(self, netif): def getnemid(self, netif):
''' Given an interface, return its numerical ID. """
''' Given an interface, return its numerical ID.
"""
if netif not in self.nemidmap: if netif not in self.nemidmap:
return None return None
else: else:
return self.nemidmap[netif] return self.nemidmap[netif]
def getnemnetif(self, nemid): def getnemnetif(self, nemid):
''' Given a numerical NEM ID, return its interface. This returns the """
Given a numerical NEM ID, return its interface. This returns the
first interface that matches the given NEM ID. first interface that matches the given NEM ID.
''' """
for netif in self.nemidmap: for netif in self.nemidmap:
if self.nemidmap[netif] == nemid: if self.nemidmap[netif] == nemid:
return netif return netif
return None return None
def netifs(self, sort=True): def netifs(self, sort=True):
''' Retrieve list of linked interfaces sorted by node number. """
''' Retrieve list of linked interfaces sorted by node number.
"""
return sorted(self._netif.values(), key=lambda ifc: ifc.node.objid) return sorted(self._netif.values(), key=lambda ifc: ifc.node.objid)
def buildplatformxmlentry(self, doc): def buildplatformxmlentry(self, doc):
''' Return a dictionary of XML elements describing the NEMs """
Return a dictionary of XML elements describing the NEMs
connected to this EmaneNode for inclusion in the platform.xml file. connected to this EmaneNode for inclusion in the platform.xml file.
''' """
ret = {} ret = {}
if self.model is None: if self.model is None:
self.info("warning: EmaneNode %s has no associated model" % \ logger.info("warning: EmaneNode %s has no associated model" % self.name)
self.name)
return ret return ret
for netif in self.netifs(): for netif in self.netifs():
# <nem name="NODE-001" definition="rfpipenem.xml"> # <nem name="NODE-001" definition="rfpipenem.xml">
@ -139,9 +144,9 @@ class EmaneNode(EmaneNet):
return ret return ret
def buildnemxmlfiles(self, emane): def buildnemxmlfiles(self, emane):
''' Let the configured model build the necessary nem, mac, and phy """
XMLs. Let the configured model build the necessary nem, mac, and phy XMLs.
''' """
if self.model is None: if self.model is None:
return return
# build XML for overall network (EmaneNode) configs # build XML for overall network (EmaneNode) configs
@ -166,8 +171,9 @@ class EmaneNode(EmaneNet):
self.buildtransportxml(emane, rtype) self.buildtransportxml(emane, rtype)
def buildtransportxml(self, emane, type): def buildtransportxml(self, emane, type):
''' Write a transport XML file for the Virtual or Raw Transport. """
''' Write a transport XML file for the Virtual or Raw Transport.
"""
transdoc = emane.xmldoc("transport") transdoc = emane.xmldoc("transport")
trans = transdoc.getElementsByTagName("transport").pop() trans = transdoc.getElementsByTagName("transport").pop()
trans.setAttribute("name", "%s Transport" % type.capitalize()) trans.setAttribute("name", "%s Transport" % type.capitalize())
@ -176,7 +182,7 @@ class EmaneNode(EmaneNet):
flowcontrol = False flowcontrol = False
names = self.model.getnames() names = self.model.getnames()
values = emane.getconfig(self.objid, self.model._name, values = emane.getconfig(self.objid, self.model.name,
self.model.getdefaultvalues())[1] self.model.getdefaultvalues())[1]
if "flowcontrolenable" in names and values: if "flowcontrolenable" in names and values:
i = names.index("flowcontrolenable") i = names.index("flowcontrolenable")
@ -184,35 +190,30 @@ class EmaneNode(EmaneNet):
flowcontrol = True flowcontrol = True
if "virtual" in type.lower(): if "virtual" in type.lower():
if os.path.exists("/dev/net/tun_flowctl"): if path.exists("/dev/net/tun_flowctl"):
trans.appendChild(emane.xmlparam(transdoc, "devicepath", trans.appendChild(emane.xmlparam(transdoc, "devicepath", "/dev/net/tun_flowctl"))
"/dev/net/tun_flowctl"))
else: else:
trans.appendChild(emane.xmlparam(transdoc, "devicepath", trans.appendChild(emane.xmlparam(transdoc, "devicepath", "/dev/net/tun"))
"/dev/net/tun"))
if flowcontrol: if flowcontrol:
trans.appendChild(emane.xmlparam(transdoc, "flowcontrolenable", trans.appendChild(emane.xmlparam(transdoc, "flowcontrolenable", "on"))
"on"))
emane.xmlwrite(transdoc, self.transportxmlname(type.lower())) emane.xmlwrite(transdoc, self.transportxmlname(type.lower()))
def transportxmlname(self, type): def transportxmlname(self, type):
''' Return the string name for the Transport XML file, """
e.g. 'n3transvirtual.xml' Return the string name for the Transport XML file, e.g. 'n3transvirtual.xml'
''' """
return "n%strans%s.xml" % (self.objid, type) return "n%strans%s.xml" % (self.objid, type)
def installnetifs(self, do_netns=True): def installnetifs(self, do_netns=True):
''' Install TAP devices into their namespaces. This is done after """
Install TAP devices into their namespaces. This is done after
EMANE daemons have been started, because that is their only chance EMANE daemons have been started, because that is their only chance
to bind to the TAPs. to bind to the TAPs.
''' """
if self.session.emane.genlocationevents() and \ if self.session.emane.genlocationevents() and self.session.emane.service is None:
self.session.emane.service is None:
warntxt = "unable to publish EMANE events because the eventservice " warntxt = "unable to publish EMANE events because the eventservice "
warntxt += "Python bindings failed to load" warntxt += "Python bindings failed to load"
self.session.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.name, logger.error(warntxt)
self.objid, warntxt)
for netif in self.netifs(): for netif in self.netifs():
if do_netns and "virtual" in netif.transport_type.lower(): if do_netns and "virtual" in netif.transport_type.lower():
@ -224,67 +225,69 @@ class EmaneNode(EmaneNet):
# at this point we register location handlers for generating # at this point we register location handlers for generating
# EMANE location events # EMANE location events
netif.poshook = self.setnemposition netif.poshook = self.setnemposition
(x,y,z) = netif.node.position.get() (x, y, z) = netif.node.position.get()
self.setnemposition(netif, x, y, z) self.setnemposition(netif, x, y, z)
def deinstallnetifs(self): def deinstallnetifs(self):
''' Uninstall TAP devices. This invokes their shutdown method for """
Uninstall TAP devices. This invokes their shutdown method for
any required cleanup; the device may be actually removed when any required cleanup; the device may be actually removed when
emanetransportd terminates. emanetransportd terminates.
''' """
for netif in self.netifs(): for netif in self.netifs():
if "virtual" in netif.transport_type.lower(): if "virtual" in netif.transport_type.lower():
netif.shutdown() netif.shutdown()
netif.poshook = None netif.poshook = None
def setnemposition(self, netif, x, y, z): def setnemposition(self, netif, x, y, z):
''' Publish a NEM location change event using the EMANE event service. """
''' Publish a NEM location change event using the EMANE event service.
"""
if self.session.emane.service is None: if self.session.emane.service is None:
if self.verbose: logger.info("position service not available")
self.info("position service not available")
return return
nemid = self.getnemid(netif) nemid = self.getnemid(netif)
ifname = netif.localname ifname = netif.localname
if nemid is None: if nemid is None:
self.info("nemid for %s is unknown" % ifname) logger.info("nemid for %s is unknown" % ifname)
return return
(lat, long, alt) = self.session.location.getgeo(x, y, z) (lat, long, alt) = self.session.location.getgeo(x, y, z)
if self.verbose: logger.info("setnemposition %s (%s) x,y,z=(%d,%d,%s)"
self.info("setnemposition %s (%s) x,y,z=(%d,%d,%s)"
"(%.6f,%.6f,%.6f)" % \ "(%.6f,%.6f,%.6f)" % \
(ifname, nemid, x, y, z, lat, long, alt)) (ifname, nemid, x, y, z, lat, long, alt))
if self.session.emane.version >= self.session.emane.EMANE091: if emane.VERSION >= emane.EMANE091:
event = LocationEvent() event = LocationEvent()
else: else:
event = emaneeventlocation.EventLocation(1) event = emaneeventlocation.EventLocation(1)
# altitude must be an integer or warning is printed # altitude must be an integer or warning is printed
# unused: yaw, pitch, roll, azimuth, elevation, velocity # unused: yaw, pitch, roll, azimuth, elevation, velocity
alt = int(round(alt)) alt = int(round(alt))
if self.session.emane.version >= self.session.emane.EMANE091: if emane.VERSION >= emane.EMANE091:
event.append(nemid, latitude=lat, longitude=long, altitude=alt) event.append(nemid, latitude=lat, longitude=long, altitude=alt)
self.session.emane.service.publish(0, event) self.session.emane.service.publish(0, event)
else: else:
event.set(0, nemid, lat, long, alt) event.set(0, nemid, lat, long, alt)
self.session.emane.service.publish(emaneeventlocation.EVENT_ID, self.session.emane.service.publish(
emaneeventlocation.EVENT_ID,
emaneeventservice.PLATFORMID_ANY, emaneeventservice.PLATFORMID_ANY,
emaneeventservice.NEMID_ANY, emaneeventservice.NEMID_ANY,
emaneeventservice.COMPONENTID_ANY, emaneeventservice.COMPONENTID_ANY,
event.export()) event.export()
)
def setnempositions(self, moved_netifs): def setnempositions(self, moved_netifs):
''' Several NEMs have moved, from e.g. a WaypointMobilityModel """
Several NEMs have moved, from e.g. a WaypointMobilityModel
calculation. Generate an EMANE Location Event having several calculation. Generate an EMANE Location Event having several
entries for each netif that has moved. entries for each netif that has moved.
''' """
if len(moved_netifs) == 0: if len(moved_netifs) == 0:
return return
if self.session.emane.service is None: if self.session.emane.service is None:
if self.verbose: logger.info("position service not available")
self.info("position service not available")
return return
if self.session.emane.version >= self.session.emane.EMANE091: if emane.VERSION >= emane.EMANE091:
event = LocationEvent() event = LocationEvent()
else: else:
event = emaneeventlocation.EventLocation(len(moved_netifs)) event = emaneeventlocation.EventLocation(len(moved_netifs))
@ -293,29 +296,28 @@ class EmaneNode(EmaneNet):
nemid = self.getnemid(netif) nemid = self.getnemid(netif)
ifname = netif.localname ifname = netif.localname
if nemid is None: if nemid is None:
self.info("nemid for %s is unknown" % ifname) logger.info("nemid for %s is unknown" % ifname)
continue continue
(x, y, z) = netif.node.getposition() (x, y, z) = netif.node.getposition()
(lat, long, alt) = self.session.location.getgeo(x, y, z) (lat, long, alt) = self.session.location.getgeo(x, y, z)
if self.verbose: logger.info("setnempositions %d %s (%s) x,y,z=(%d,%d,%s)"
self.info("setnempositions %d %s (%s) x,y,z=(%d,%d,%s)" "(%.6f,%.6f,%.6f)" %
"(%.6f,%.6f,%.6f)" % \
(i, ifname, nemid, x, y, z, lat, long, alt)) (i, ifname, nemid, x, y, z, lat, long, alt))
# altitude must be an integer or warning is printed # altitude must be an integer or warning is printed
alt = int(round(alt)) alt = int(round(alt))
if self.session.emane.version >= self.session.emane.EMANE091: if emane.VERSION >= emane.EMANE091:
event.append(nemid, latitude=lat, longitude=long, altitude=alt) event.append(nemid, latitude=lat, longitude=long, altitude=alt)
else: else:
event.set(i, nemid, lat, long, alt) event.set(i, nemid, lat, long, alt)
i += 1 i += 1
if self.session.emane.version >= self.session.emane.EMANE091: if emane.VERSION >= emane.EMANE091:
self.session.emane.service.publish(0, event) self.session.emane.service.publish(0, event)
else: else:
self.session.emane.service.publish(emaneeventlocation.EVENT_ID, self.session.emane.service.publish(
emaneeventlocation.EVENT_ID,
emaneeventservice.PLATFORMID_ANY, emaneeventservice.PLATFORMID_ANY,
emaneeventservice.NEMID_ANY, emaneeventservice.NEMID_ANY,
emaneeventservice.COMPONENTID_ANY, emaneeventservice.COMPONENTID_ANY,
event.export()) event.export()
)

View file

@ -1,33 +1,28 @@
# """
# CORE
# Copyright (c)2010-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
# Harry Bullen <hbullen@i-a-i.com>
#
'''
rfpipe.py: EMANE RF-PIPE model for CORE rfpipe.py: EMANE RF-PIPE model for CORE
''' """
from core import emane
from core.emane.emanemodel import EmaneModel
from core.emane.universal import EmaneUniversalModel
from core.enumerations import ConfigDataTypes
from core.misc import log
logger = log.get_logger(__name__)
import sys
import string
try: try:
from emanesh.events import EventService from emanesh.events import EventService
except: except ImportError:
pass logger.error("error importing emanesh")
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
from universal import EmaneUniversalModel
class EmaneRfPipeModel(EmaneModel): class EmaneRfPipeModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False): def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, objid, verbose) EmaneModel.__init__(self, session, object_id)
# model name # model name
_name = "emane_rfpipe" name = "emane_rfpipe"
if Emane.version >= Emane.EMANE091: if emane.VERSION >= emane.EMANE091:
xml_path = '/usr/share/emane/xml/models/mac/rfpipe' xml_path = '/usr/share/emane/xml/models/mac/rfpipe'
else: else:
xml_path = "/usr/share/emane/models/rfpipe/xml" xml_path = "/usr/share/emane/models/rfpipe/xml"
@ -36,68 +31,69 @@ class EmaneRfPipeModel(EmaneModel):
# ( 'name', 'type', 'default', 'possible-value-list', 'caption') # ( 'name', 'type', 'default', 'possible-value-list', 'caption')
# MAC parameters # MAC parameters
_confmatrix_mac_base = [ _confmatrix_mac_base = [
("enablepromiscuousmode", coreapi.CONF_DATA_TYPE_BOOL, '0', ("enablepromiscuousmode", ConfigDataTypes.BOOL.value, '0',
'True,False', 'enable promiscuous mode'), 'True,False', 'enable promiscuous mode'),
("datarate", coreapi.CONF_DATA_TYPE_UINT32, '1M', ("datarate", ConfigDataTypes.UINT32.value, '1M',
'', 'data rate (bps)'), '', 'data rate (bps)'),
("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0', ("flowcontrolenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable traffic flow control'), 'On,Off', 'enable traffic flow control'),
("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10', ("flowcontroltokens", ConfigDataTypes.UINT16.value, '10',
'', 'number of flow control tokens'), '', 'number of flow control tokens'),
("pcrcurveuri", coreapi.CONF_DATA_TYPE_STRING, ("pcrcurveuri", ConfigDataTypes.STRING.value,
'%s/rfpipepcr.xml' % xml_path, '%s/rfpipepcr.xml' % xml_path,
'', 'SINR/PCR curve file'), '', 'SINR/PCR curve file'),
] ]
_confmatrix_mac_081 = [ _confmatrix_mac_081 = [
("jitter", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("jitter", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmission jitter (usec)'), '', 'transmission jitter (usec)'),
("delay", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("delay", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmission delay (usec)'), '', 'transmission delay (usec)'),
("transmissioncontrolmap", coreapi.CONF_DATA_TYPE_STRING, '', ("transmissioncontrolmap", ConfigDataTypes.STRING.value, '',
'', 'tx control map (nem:rate:freq:tx_dBm)'), '', 'tx control map (nem:rate:freq:tx_dBm)'),
("enabletighttiming", coreapi.CONF_DATA_TYPE_BOOL, '0', ("enabletighttiming", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable tight timing for pkt delay'), 'On,Off', 'enable tight timing for pkt delay'),
] ]
_confmatrix_mac_091 = [ _confmatrix_mac_091 = [
("jitter", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("jitter", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmission jitter (sec)'), '', 'transmission jitter (sec)'),
("delay", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("delay", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmission delay (sec)'), '', 'transmission delay (sec)'),
('radiometricenable', coreapi.CONF_DATA_TYPE_BOOL, '0', ('radiometricenable', ConfigDataTypes.BOOL.value, '0',
'On,Off', 'report radio metrics via R2RI'), 'On,Off', 'report radio metrics via R2RI'),
('radiometricreportinterval', coreapi.CONF_DATA_TYPE_FLOAT, '1.0', ('radiometricreportinterval', ConfigDataTypes.FLOAT.value, '1.0',
'', 'R2RI radio metric report interval (sec)'), '', 'R2RI radio metric report interval (sec)'),
('neighbormetricdeletetime', coreapi.CONF_DATA_TYPE_FLOAT, '60.0', ('neighbormetricdeletetime', ConfigDataTypes.FLOAT.value, '60.0',
'', 'R2RI neighbor table inactivity time (sec)'), '', 'R2RI neighbor table inactivity time (sec)'),
] ]
if Emane.version >= Emane.EMANE091: if emane.VERSION >= emane.EMANE091:
_confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_091 _confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_091
else: else:
_confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_081 _confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_081
# PHY parameters from Universal PHY # PHY parameters from Universal PHY
_confmatrix_phy = EmaneUniversalModel._confmatrix _confmatrix_phy = EmaneUniversalModel.config_matrix
_confmatrix = _confmatrix_mac + _confmatrix_phy config_matrix = _confmatrix_mac + _confmatrix_phy
# value groupings # value groupings
_confgroups = "RF-PIPE MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" \ config_groups = "RF-PIPE MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % (
% ( len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(_confmatrix)) len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(config_matrix))
def buildnemxmlfiles(self, e, ifc): def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem, mac, and phy XMLs in the given path. """
Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_rfpipenem.xml, that file also. Otherwise the WLAN-wide nXXemane_rfpipenem.xml,
nXXemane_rfpipemac.xml, nXXemane_rfpipephy.xml are used. nXXemane_rfpipemac.xml, nXXemane_rfpipephy.xml are used.
''' """
values = e.getifcconfig(self.objid, self._name, values = e.getifcconfig(self.object_id, self.name,
self.getdefaultvalues(), ifc) self.getdefaultvalues(), ifc)
if values is None: if values is None:
return return
nemdoc = e.xmldoc("nem") nemdoc = e.xmldoc("nem")
nem = nemdoc.getElementsByTagName("nem").pop() nem = nemdoc.getElementsByTagName("nem").pop()
nem.setAttribute("name", "RF-PIPE NEM") nem.setAttribute("name", "RF-PIPE NEM")
e.appendtransporttonem(nemdoc, nem, self.objid, ifc) e.appendtransporttonem(nemdoc, nem, self.object_id, ifc)
mactag = nemdoc.createElement("mac") mactag = nemdoc.createElement("mac")
mactag.setAttribute("definition", self.macxmlname(ifc)) mactag.setAttribute("definition", self.macxmlname(ifc))
nem.appendChild(mactag) nem.appendChild(mactag)
@ -124,10 +120,8 @@ class EmaneRfPipeModel(EmaneModel):
values = list(values) values = list(values)
values[i] = self.emane074_fixup(values[i], 1000) values[i] = self.emane074_fixup(values[i], 1000)
# append MAC options to macdoc # append MAC options to macdoc
map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, \ map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, self.valueof(n, values))), macnames)
self.valueof(n, values))), macnames)
e.xmlwrite(macdoc, self.macxmlname(ifc)) e.xmlwrite(macdoc, self.macxmlname(ifc))
phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames) phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames)
e.xmlwrite(phydoc, self.phyxmlname(ifc)) e.xmlwrite(phydoc, self.phyxmlname(ifc))

View file

@ -1,91 +1,85 @@
"""
#
# CORE
# Copyright (c)2013 Company.
# See the LICENSE file included in this distribution.
#
# author: Name <email@company.com>
#
'''
tdma.py: EMANE TDMA model bindings for CORE tdma.py: EMANE TDMA model bindings for CORE
''' """
from core import emane
from core.emane.emanemodel import EmaneModel
from core.emane.universal import EmaneUniversalModel
from core.enumerations import ConfigDataTypes
from core.misc import log
logger = log.get_logger(__name__)
import sys
import string
try: try:
from emanesh.events import EventService from emanesh.events import EventService
except: except:
pass logger.error("error importing emanesh")
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
from universal import EmaneUniversalModel
class EmaneTdmaModel(EmaneModel): class EmaneTdmaModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False): def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, objid, verbose) EmaneModel.__init__(self, session, object_id)
# model name # model name
_name = "emane_tdma" name = "emane_tdma"
if Emane.version >= Emane.EMANE101: if emane.VERSION >= emane.EMANE101:
xml_path = '/usr/share/emane/xml/models/mac/tdmaeventscheduler' xml_path = '/usr/share/emane/xml/models/mac/tdmaeventscheduler'
else: else:
raise Exception("EMANE TDMA requires EMANE 1.0.1 or greater") raise Exception("EMANE TDMA requires EMANE 1.0.1 or greater")
# MAC parameters # MAC parameters
_confmatrix_mac = [ _confmatrix_mac = [
("enablepromiscuousmode", coreapi.CONF_DATA_TYPE_BOOL, '0', ("enablepromiscuousmode", ConfigDataTypes.BOOL.value, '0',
'True,False', 'enable promiscuous mode'), 'True,False', 'enable promiscuous mode'),
("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0', ("flowcontrolenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable traffic flow control'), 'On,Off', 'enable traffic flow control'),
("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10', ("flowcontroltokens", ConfigDataTypes.UINT16.value, '10',
'', 'number of flow control tokens'), '', 'number of flow control tokens'),
("fragmentcheckthreshold", coreapi.CONF_DATA_TYPE_UINT16, '2', ("fragmentcheckthreshold", ConfigDataTypes.UINT16.value, '2',
'', 'rate in seconds for check if fragment reassembly efforts should be abandoned'), '', 'rate in seconds for check if fragment reassembly efforts should be abandoned'),
("fragmenttimeoutthreshold", coreapi.CONF_DATA_TYPE_UINT16, '5', ("fragmenttimeoutthreshold", ConfigDataTypes.UINT16.value, '5',
'', 'threshold in seconds to wait for another packet fragment for reassembly'), '', 'threshold in seconds to wait for another packet fragment for reassembly'),
('neighbormetricdeletetime', coreapi.CONF_DATA_TYPE_FLOAT, '60.0', ('neighbormetricdeletetime', ConfigDataTypes.FLOAT.value, '60.0',
'', 'neighbor RF reception timeout for removal from neighbor table (sec)'), '', 'neighbor RF reception timeout for removal from neighbor table (sec)'),
('neighbormetricupdateinterval', coreapi.CONF_DATA_TYPE_FLOAT, '1.0', ('neighbormetricupdateinterval', ConfigDataTypes.FLOAT.value, '1.0',
'', 'neighbor table update interval (sec)'), '', 'neighbor table update interval (sec)'),
("pcrcurveuri", coreapi.CONF_DATA_TYPE_STRING, '%s/tdmabasemodelpcr.xml' % xml_path, ("pcrcurveuri", ConfigDataTypes.STRING.value, '%s/tdmabasemodelpcr.xml' % xml_path,
'', 'SINR/PCR curve file'), '', 'SINR/PCR curve file'),
("queue.aggregationenable", coreapi.CONF_DATA_TYPE_BOOL, '1', ("queue.aggregationenable", ConfigDataTypes.BOOL.value, '1',
'On,Off', 'enable transmit packet aggregation'), 'On,Off', 'enable transmit packet aggregation'),
('queue.aggregationslotthreshold', coreapi.CONF_DATA_TYPE_FLOAT, '90.0', ('queue.aggregationslotthreshold', ConfigDataTypes.FLOAT.value, '90.0',
'', 'percentage of a slot that must be filled in order to conclude aggregation'), '', 'percentage of a slot that must be filled in order to conclude aggregation'),
("queue.depth", coreapi.CONF_DATA_TYPE_UINT16, '256', ("queue.depth", ConfigDataTypes.UINT16.value, '256',
'', 'size of the per service class downstream packet queues (packets)'), '', 'size of the per service class downstream packet queues (packets)'),
("queue.fragmentationenable", coreapi.CONF_DATA_TYPE_BOOL, '1', ("queue.fragmentationenable", ConfigDataTypes.BOOL.value, '1',
'On,Off', 'enable packet fragmentation (over multiple slots)'), 'On,Off', 'enable packet fragmentation (over multiple slots)'),
("queue.strictdequeueenable", coreapi.CONF_DATA_TYPE_BOOL, '0', ("queue.strictdequeueenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable strict dequeueing to specified queues only'), 'On,Off', 'enable strict dequeueing to specified queues only'),
] ]
# PHY parameters from Universal PHY # PHY parameters from Universal PHY
_confmatrix_phy = EmaneUniversalModel._confmatrix _confmatrix_phy = EmaneUniversalModel.config_matrix
_confmatrix = _confmatrix_mac + _confmatrix_phy config_matrix = _confmatrix_mac + _confmatrix_phy
# value groupings # value groupings
_confgroups = "TDMA MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % \ config_groups = "TDMA MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % (
(len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(_confmatrix)) len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(config_matrix))
def buildnemxmlfiles(self, e, ifc): def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem, mac, and phy XMLs in the given path. """
Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_tdmanem.xml, that file also. Otherwise the WLAN-wide nXXemane_tdmanem.xml,
nXXemane_tdmamac.xml, nXXemane_tdmaphy.xml are used. nXXemane_tdmamac.xml, nXXemane_tdmaphy.xml are used.
''' """
values = e.getifcconfig(self.objid, self._name, values = e.getifcconfig(self.object_id, self.name, self.getdefaultvalues(), ifc)
self.getdefaultvalues(), ifc)
if values is None: if values is None:
return return
nemdoc = e.xmldoc("nem") nemdoc = e.xmldoc("nem")
nem = nemdoc.getElementsByTagName("nem").pop() nem = nemdoc.getElementsByTagName("nem").pop()
nem.setAttribute("name", "TDMA NEM") nem.setAttribute("name", "TDMA NEM")
e.appendtransporttonem(nemdoc, nem, self.objid, ifc) e.appendtransporttonem(nemdoc, nem, self.object_id, ifc)
mactag = nemdoc.createElement("mac") mactag = nemdoc.createElement("mac")
mactag.setAttribute("definition", self.macxmlname(ifc)) mactag.setAttribute("definition", self.macxmlname(ifc))
nem.appendChild(mactag) nem.appendChild(mactag)
@ -105,10 +99,8 @@ class EmaneTdmaModel(EmaneModel):
mac.setAttribute("name", "TDMA MAC") mac.setAttribute("name", "TDMA MAC")
mac.setAttribute("library", "tdmaeventschedulerradiomodel") mac.setAttribute("library", "tdmaeventschedulerradiomodel")
# append MAC options to macdoc # append MAC options to macdoc
map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, \ map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, self.valueof(n, values))), macnames)
self.valueof(n, values))), macnames)
e.xmlwrite(macdoc, self.macxmlname(ifc)) e.xmlwrite(macdoc, self.macxmlname(ifc))
phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames) phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames)
e.xmlwrite(phydoc, self.phyxmlname(ifc)) e.xmlwrite(phydoc, self.phyxmlname(ifc))

View file

@ -1,98 +1,96 @@
# """
# CORE
# Copyright (c)2010-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
universal.py: EMANE Universal PHY model for CORE. Enumerates configuration items universal.py: EMANE Universal PHY model for CORE. Enumerates configuration items
used for the Universal PHY. used for the Universal PHY.
''' """
from core import emane
from core.emane.emanemodel import EmaneModel
from core.enumerations import ConfigDataTypes
from core.misc import log
logger = log.get_logger(__name__)
import sys
import string
try: try:
from emanesh.events import EventService from emanesh.events import EventService
except: except ImportError:
pass logger.error("error importing emanesh")
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
class EmaneUniversalModel(EmaneModel): class EmaneUniversalModel(EmaneModel):
''' This Univeral PHY model is meant to be imported by other models, """
This Univeral PHY model is meant to be imported by other models,
not instantiated. not instantiated.
''' """
def __init__(self, session, objid = None, verbose = False):
raise SyntaxError
_name = "emane_universal" def __init__(self, session, object_id=None):
raise NotImplemented("Cannot use this class directly")
name = "emane_universal"
_xmlname = "universalphy" _xmlname = "universalphy"
_xmllibrary = "universalphylayer" _xmllibrary = "universalphylayer"
# universal PHY parameters # universal PHY parameters
_confmatrix_base = [ _confmatrix_base = [
("bandwidth", coreapi.CONF_DATA_TYPE_UINT64, '1M', ("bandwidth", ConfigDataTypes.UINT64.value, '1M',
'', 'rf bandwidth (hz)'), '', 'rf bandwidth (hz)'),
("frequency", coreapi.CONF_DATA_TYPE_UINT64, '2.347G', ("frequency", ConfigDataTypes.UINT64.value, '2.347G',
'','frequency (Hz)'), '', 'frequency (Hz)'),
("frequencyofinterest", coreapi.CONF_DATA_TYPE_UINT64, '2.347G', ("frequencyofinterest", ConfigDataTypes.UINT64.value, '2.347G',
'','frequency of interest (Hz)'), '', 'frequency of interest (Hz)'),
("subid", coreapi.CONF_DATA_TYPE_UINT16, '1', ("subid", ConfigDataTypes.UINT16.value, '1',
'','subid'), '', 'subid'),
("systemnoisefigure", coreapi.CONF_DATA_TYPE_FLOAT, '4.0', ("systemnoisefigure", ConfigDataTypes.FLOAT.value, '4.0',
'','system noise figure (dB)'), '', 'system noise figure (dB)'),
("txpower", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("txpower", ConfigDataTypes.FLOAT.value, '0.0',
'','transmit power (dBm)'), '', 'transmit power (dBm)'),
] ]
_confmatrix_081 = [ _confmatrix_081 = [
("antennagain", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("antennagain", ConfigDataTypes.FLOAT.value, '0.0',
'','antenna gain (dBi)'), '', 'antenna gain (dBi)'),
("antennaazimuth", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("antennaazimuth", ConfigDataTypes.FLOAT.value, '0.0',
'','antenna azimuth (deg)'), '', 'antenna azimuth (deg)'),
("antennaelevation", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("antennaelevation", ConfigDataTypes.FLOAT.value, '0.0',
'','antenna elevation (deg)'), '', 'antenna elevation (deg)'),
("antennaprofileid", coreapi.CONF_DATA_TYPE_STRING, '1', ("antennaprofileid", ConfigDataTypes.STRING.value, '1',
'','antenna profile ID'), '', 'antenna profile ID'),
("antennaprofilemanifesturi", coreapi.CONF_DATA_TYPE_STRING, '', ("antennaprofilemanifesturi", ConfigDataTypes.STRING.value, '',
'','antenna profile manifest URI'), '', 'antenna profile manifest URI'),
("antennaprofileenable", coreapi.CONF_DATA_TYPE_BOOL, '0', ("antennaprofileenable", ConfigDataTypes.BOOL.value, '0',
'On,Off','antenna profile mode'), 'On,Off', 'antenna profile mode'),
("defaultconnectivitymode", coreapi.CONF_DATA_TYPE_BOOL, '1', ("defaultconnectivitymode", ConfigDataTypes.BOOL.value, '1',
'On,Off','default connectivity'), 'On,Off', 'default connectivity'),
("frequencyofinterestfilterenable", coreapi.CONF_DATA_TYPE_BOOL, '1', ("frequencyofinterestfilterenable", ConfigDataTypes.BOOL.value, '1',
'On,Off','frequency of interest filter enable'), 'On,Off', 'frequency of interest filter enable'),
("noiseprocessingmode", coreapi.CONF_DATA_TYPE_BOOL, '0', ("noiseprocessingmode", ConfigDataTypes.BOOL.value, '0',
'On,Off','enable noise processing'), 'On,Off', 'enable noise processing'),
("pathlossmode", coreapi.CONF_DATA_TYPE_STRING, '2ray', ("pathlossmode", ConfigDataTypes.STRING.value, '2ray',
'pathloss,2ray,freespace','path loss mode'), 'pathloss,2ray,freespace', 'path loss mode'),
] ]
_confmatrix_091 = [ _confmatrix_091 = [
("fixedantennagain", coreapi.CONF_DATA_TYPE_FLOAT, '0.0', ("fixedantennagain", ConfigDataTypes.FLOAT.value, '0.0',
'','antenna gain (dBi)'), '', 'antenna gain (dBi)'),
("fixedantennagainenable", coreapi.CONF_DATA_TYPE_BOOL, '1', ("fixedantennagainenable", ConfigDataTypes.BOOL.value, '1',
'On,Off','enable fixed antenna gain'), 'On,Off', 'enable fixed antenna gain'),
("noisemode", coreapi.CONF_DATA_TYPE_STRING, 'none', ("noisemode", ConfigDataTypes.STRING.value, 'none',
'none,all,outofband','noise processing mode'), 'none,all,outofband', 'noise processing mode'),
("noisebinsize", coreapi.CONF_DATA_TYPE_UINT64, '20', ("noisebinsize", ConfigDataTypes.UINT64.value, '20',
'','noise bin size in microseconds'), '', 'noise bin size in microseconds'),
("propagationmodel", coreapi.CONF_DATA_TYPE_STRING, '2ray', ("propagationmodel", ConfigDataTypes.STRING.value, '2ray',
'precomputed,2ray,freespace','path loss mode'), 'precomputed,2ray,freespace', 'path loss mode'),
] ]
if Emane.version >= Emane.EMANE091: if emane.VERSION >= emane.EMANE091:
_confmatrix = _confmatrix_base + _confmatrix_091 config_matrix = _confmatrix_base + _confmatrix_091
else: else:
_confmatrix = _confmatrix_base + _confmatrix_081 config_matrix = _confmatrix_base + _confmatrix_081
# old parameters # old parameters
_confmatrix_ver074 = [ _confmatrix_ver074 = [
("antennaazimuthbeamwidth", coreapi.CONF_DATA_TYPE_FLOAT, '360.0', ("antennaazimuthbeamwidth", ConfigDataTypes.FLOAT.value, '360.0',
'','azimith beam width (deg)'), '', 'azimith beam width (deg)'),
("antennaelevationbeamwidth", coreapi.CONF_DATA_TYPE_FLOAT, '180.0', ("antennaelevationbeamwidth", ConfigDataTypes.FLOAT.value, '180.0',
'','elevation beam width (deg)'), '', 'elevation beam width (deg)'),
("antennatype", coreapi.CONF_DATA_TYPE_STRING, 'omnidirectional', ("antennatype", ConfigDataTypes.STRING.value, 'omnidirectional',
'omnidirectional,unidirectional','antenna type'), 'omnidirectional,unidirectional', 'antenna type'),
] ]
# parameters that require unit conversion for 0.7.4 # parameters that require unit conversion for 0.7.4
@ -102,16 +100,15 @@ class EmaneUniversalModel(EmaneModel):
"antennaprofilemanifesturi", "antennaprofilemanifesturi",
"frequencyofinterestfilterenable") "frequencyofinterestfilterenable")
@classmethod @classmethod
def getphydoc(cls, e, mac, values, phynames): def getphydoc(cls, e, mac, values, phynames):
phydoc = e.xmldoc("phy") phydoc = e.xmldoc("phy")
phy = phydoc.getElementsByTagName("phy").pop() phy = phydoc.getElementsByTagName("phy").pop()
phy.setAttribute("name", cls._xmlname) phy.setAttribute("name", cls._xmlname)
if e.version < e.EMANE091: if emane.VERSION < emane.EMANE091:
phy.setAttribute("library", cls._xmllibrary) phy.setAttribute("library", cls._xmllibrary)
# EMANE 0.7.4 suppport - to be removed when 0.7.4 support is deprecated # EMANE 0.7.4 suppport - to be removed when 0.7.4 support is deprecated
if e.version == e.EMANE074: if emane.VERSION == emane.EMANE074:
names = mac.getnames() names = mac.getnames()
values = list(values) values = list(values)
phynames = list(phynames) phynames = list(phynames)
@ -128,7 +125,7 @@ class EmaneUniversalModel(EmaneModel):
phy.appendChild(e.xmlparam(phydoc, old[0], old[2])) phy.appendChild(e.xmlparam(phydoc, old[0], old[2]))
frequencies = None frequencies = None
if e.version >= e.EMANE091: if emane.VERSION >= emane.EMANE091:
name = "frequencyofinterest" name = "frequencyofinterest"
value = mac.valueof(name, values) value = mac.valueof(name, values)
frequencies = cls.valuestrtoparamlist(phydoc, name, value) frequencies = cls.valuestrtoparamlist(phydoc, name, value)
@ -137,10 +134,7 @@ class EmaneUniversalModel(EmaneModel):
phynames.remove("frequencyofinterest") phynames.remove("frequencyofinterest")
# append all PHY options to phydoc # append all PHY options to phydoc
map( lambda n: phy.appendChild(e.xmlparam(phydoc, n, \ map(lambda n: phy.appendChild(e.xmlparam(phydoc, n, mac.valueof(n, values))), phynames)
mac.valueof(n, values))), phynames)
if frequencies: if frequencies:
phy.appendChild(frequencies) phy.appendChild(frequencies)
return phydoc return phydoc

View file

@ -1,42 +1,50 @@
# """
# CORE
# Copyright (c)2010-2013 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
location.py: definition of CoreLocation class that is a member of the location.py: definition of CoreLocation class that is a member of the
Session object. Provides conversions between Cartesian and geographic coordinate Session object. Provides conversions between Cartesian and geographic coordinate
systems. Depends on utm contributed module, from systems. Depends on utm contributed module, from
https://pypi.python.org/pypi/utm (version 0.3.0). https://pypi.python.org/pypi/utm (version 0.3.0).
''' """
from core.conf import ConfigurableManager from core.conf import ConfigurableManager
from core.api import coreapi from core.enumerations import RegisterTlvs
from core.misc import log
from core.misc import utm from core.misc import utm
logger = log.get_logger(__name__)
class CoreLocation(ConfigurableManager): class CoreLocation(ConfigurableManager):
''' Member of session class for handling global location data. This keeps """
Member of session class for handling global location data. This keeps
track of a latitude/longitude/altitude reference point and scale in track of a latitude/longitude/altitude reference point and scale in
order to convert between X,Y and geo coordinates. order to convert between X,Y and geo coordinates.
TODO: this could be updated to use more generic TODO: this could be updated to use more generic
Configurable/ConfigurableManager code like other Session objects Configurable/ConfigurableManager code like other Session objects
''' """
_name = "location" name = "location"
_type = coreapi.CORE_TLV_REG_UTILITY config_type = RegisterTlvs.UTILITY.value
def __init__(self, session): def __init__(self):
ConfigurableManager.__init__(self, session) """
Creates a MobilityManager instance.
:return: nothing
"""
ConfigurableManager.__init__(self)
self.reset() self.reset()
self.zonemap = {} self.zonemap = {}
self.refxyz = (0.0, 0.0, 0.0)
self.refscale = 1.0
self.zoneshifts = {}
self.refgeo = (0.0, 0.0, 0.0)
for n, l in utm.ZONE_LETTERS: for n, l in utm.ZONE_LETTERS:
self.zonemap[l] = n self.zonemap[l] = n
def reset(self): def reset(self):
''' Reset to initial state. """
''' Reset to initial state.
"""
# (x, y, z) coordinates of the point given by self.refgeo # (x, y, z) coordinates of the point given by self.refgeo
self.refxyz = (0.0, 0.0, 0.0) self.refxyz = (0.0, 0.0, 0.0)
# decimal latitude, longitude, and altitude at the point (x, y, z) # decimal latitude, longitude, and altitude at the point (x, y, z)
@ -46,70 +54,97 @@ class CoreLocation(ConfigurableManager):
# cached distance to refpt in other zones # cached distance to refpt in other zones
self.zoneshifts = {} self.zoneshifts = {}
def configure_values(self, msg, values): def configure_values(self, config_data):
''' Receive configuration message for setting the reference point """
Receive configuration message for setting the reference point
and scale. and scale.
'''
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:return: nothing
"""
values = config_data.data_values
if values is None: if values is None:
self.session.info("location data missing") logger.info("location data missing")
return None return None
values = values.split('|') values = values.split('|')
# Cartesian coordinate reference point # Cartesian coordinate reference point
refx,refy = map(lambda x: float(x), values[0:2]) refx, refy = map(lambda x: float(x), values[0:2])
refz = 0.0 refz = 0.0
self.refxyz = (refx, refy, refz) self.refxyz = (refx, refy, refz)
# Geographic reference point # Geographic reference point
lat,long,alt = map(lambda x: float(x), values[2:5]) lat, lon, alt = map(lambda x: float(x), values[2:5])
self.setrefgeo(lat, long, alt) self.setrefgeo(lat, lon, alt)
self.refscale = float(values[5]) self.refscale = float(values[5])
self.session.info("location configured: (%.2f,%.2f,%.2f) = " logger.info("location configured: (%.2f,%.2f,%.2f) = (%.5f,%.5f,%.5f) scale=%.2f" %
"(%.5f,%.5f,%.5f) scale=%.2f" %
(self.refxyz[0], self.refxyz[1], self.refxyz[2], self.refgeo[0], (self.refxyz[0], self.refxyz[1], self.refxyz[2], self.refgeo[0],
self.refgeo[1], self.refgeo[2], self.refscale)) self.refgeo[1], self.refgeo[2], self.refscale))
self.session.info("location configured: UTM(%.5f,%.5f,%.5f)" % logger.info("location configured: UTM(%.5f,%.5f,%.5f)" %
(self.refutm[1], self.refutm[2], self.refutm[3])) (self.refutm[1], self.refutm[2], self.refutm[3]))
def px2m(self, val): def px2m(self, val):
''' Convert the specified value in pixels to meters using the """
Convert the specified value in pixels to meters using the
configured scale. The scale is given as s, where configured scale. The scale is given as s, where
100 pixels = s meters. 100 pixels = s meters.
'''
:param val: value to use in converting to meters
:return: value converted to meters
"""
return (val / 100.0) * self.refscale return (val / 100.0) * self.refscale
def m2px(self, val): def m2px(self, val):
''' Convert the specified value in meters to pixels using the """
Convert the specified value in meters to pixels using the
configured scale. The scale is given as s, where configured scale. The scale is given as s, where
100 pixels = s meters. 100 pixels = s meters.
'''
:param val: value to convert to pixels
:return: value converted to pixels
"""
if self.refscale == 0.0: if self.refscale == 0.0:
return 0.0 return 0.0
return 100.0 * (val / self.refscale) return 100.0 * (val / self.refscale)
def setrefgeo(self, lat, lon, alt): def setrefgeo(self, lat, lon, alt):
''' Record the geographical reference point decimal (lat, lon, alt) """
Record the geographical reference point decimal (lat, lon, alt)
and convert and store its UTM equivalent for later use. and convert and store its UTM equivalent for later use.
'''
:param lat: latitude
:param lon: longitude
:param alt: altitude
:return: nothing
"""
self.refgeo = (lat, lon, alt) self.refgeo = (lat, lon, alt)
# easting, northing, zone # easting, northing, zone
(e, n, zonen, zonel) = utm.from_latlon(lat, lon) e, n, zonen, zonel = utm.from_latlon(lat, lon)
self.refutm = ( (zonen, zonel), e, n, alt) self.refutm = ((zonen, zonel), e, n, alt)
def getgeo(self, x, y, z): def getgeo(self, x, y, z):
''' Given (x, y, z) Cartesian coordinates, convert them to latitude, """
Given (x, y, z) Cartesian coordinates, convert them to latitude,
longitude, and altitude based on the configured reference point longitude, and altitude based on the configured reference point
and scale. and scale.
'''
:param x: x value
:param y: y value
:param z: z value
:return: lat, lon, alt values for provided coordinates
:rtype: tuple
"""
# shift (x,y,z) over to reference point (x,y,z) # shift (x,y,z) over to reference point (x,y,z)
x = x - self.refxyz[0] x -= self.refxyz[0]
y = -(y - self.refxyz[1]) y = -(y - self.refxyz[1])
if z is None: if z is None:
z = self.refxyz[2] z = self.refxyz[2]
else: else:
z = z - self.refxyz[2] z -= self.refxyz[2]
# use UTM coordinates since unit is meters # use UTM coordinates since unit is meters
zone = self.refutm[0] zone = self.refutm[0]
if zone == "": if zone == "":
raise ValueError, "reference point not configured" raise ValueError("reference point not configured")
e = self.refutm[1] + self.px2m(x) e = self.refutm[1] + self.px2m(x)
n = self.refutm[2] + self.px2m(y) n = self.refutm[2] + self.px2m(y)
alt = self.refutm[3] + self.px2m(z) alt = self.refutm[3] + self.px2m(z)
@ -117,23 +152,29 @@ class CoreLocation(ConfigurableManager):
try: try:
lat, lon = utm.to_latlon(e, n, zone[0], zone[1]) lat, lon = utm.to_latlon(e, n, zone[0], zone[1])
except utm.OutOfRangeError: except utm.OutOfRangeError:
self.info("UTM out of range error for e=%s n=%s zone=%s" \ logger.exception("UTM out of range error for n=%s zone=%s xyz=(%s,%s,%s)", n, zone, x, y, z)
"xyz=(%s,%s,%s)" % (e, n, zone, x, y, z)) lat, lon = self.refgeo[:2]
(lat, lon) = self.refgeo[:2] # self.info("getgeo(%s,%s,%s) e=%s n=%s zone=%s lat,lon,alt=" \
#self.info("getgeo(%s,%s,%s) e=%s n=%s zone=%s lat,lon,alt=" \
# "%.3f,%.3f,%.3f" % (x, y, z, e, n, zone, lat, lon, alt)) # "%.3f,%.3f,%.3f" % (x, y, z, e, n, zone, lat, lon, alt))
return (lat, lon, alt) return lat, lon, alt
def getxyz(self, lat, lon, alt): def getxyz(self, lat, lon, alt):
''' Given latitude, longitude, and altitude location data, convert them """
Given latitude, longitude, and altitude location data, convert them
to (x, y, z) Cartesian coordinates based on the configured to (x, y, z) Cartesian coordinates based on the configured
reference point and scale. Lat/lon is converted to UTM meter reference point and scale. Lat/lon is converted to UTM meter
coordinates, UTM zones are accounted for, and the scale turns coordinates, UTM zones are accounted for, and the scale turns
meters to pixels. meters to pixels.
'''
:param lat: latitude
:param lon: longitude
:param alt: altitude
:return: converted x, y, z coordinates
:rtype: tuple
"""
# convert lat/lon to UTM coordinates in meters # convert lat/lon to UTM coordinates in meters
(e, n, zonen, zonel) = utm.from_latlon(lat, lon) e, n, zonen, zonel = utm.from_latlon(lat, lon)
(rlat, rlon, ralt) = self.refgeo rlat, rlon, ralt = self.refgeo
xshift = self.geteastingshift(zonen, zonel) xshift = self.geteastingshift(zonen, zonel)
if xshift is None: if xshift is None:
xm = e - self.refutm[1] xm = e - self.refutm[1]
@ -150,26 +191,35 @@ class CoreLocation(ConfigurableManager):
x = self.m2px(xm) + self.refxyz[0] x = self.m2px(xm) + self.refxyz[0]
y = -(self.m2px(ym) + self.refxyz[1]) y = -(self.m2px(ym) + self.refxyz[1])
z = self.m2px(zm) + self.refxyz[2] z = self.m2px(zm) + self.refxyz[2]
return (x, y, z) return x, y, z
def geteastingshift(self, zonen, zonel): def geteastingshift(self, zonen, zonel):
''' If the lat, lon coordinates being converted are located in a """
If the lat, lon coordinates being converted are located in a
different UTM zone than the canvas reference point, the UTM meters different UTM zone than the canvas reference point, the UTM meters
may need to be shifted. may need to be shifted.
This picks a reference point in the same longitudinal band This picks a reference point in the same longitudinal band
(UTM zone number) as the provided zone, to calculate the shift in (UTM zone number) as the provided zone, to calculate the shift in
meters for the x coordinate. meters for the x coordinate.
'''
rzonen = int(self.refutm[0][0])
if zonen == rzonen:
return None # same zone number, no x shift required
z = (zonen, zonel)
if z in self.zoneshifts and self.zoneshifts[z][0] is not None:
return self.zoneshifts[z][0] # x shift already calculated, cached
(rlat, rlon, ralt) = self.refgeo :param zonen: zonen
lon2 = rlon + 6*(zonen - rzonen) # ea. zone is 6deg band :param zonel: zone1
(e2, n2, zonen2, zonel2) = utm.from_latlon(rlat, lon2) # ignore northing :return: the x shift value
"""
rzonen = int(self.refutm[0][0])
# same zone number, no x shift required
if zonen == rzonen:
return None
z = (zonen, zonel)
# x shift already calculated, cached
if z in self.zoneshifts and self.zoneshifts[z][0] is not None:
return self.zoneshifts[z][0]
rlat, rlon, ralt = self.refgeo
# ea. zone is 6deg band
lon2 = rlon + 6 * (zonen - rzonen)
# ignore northing
e2, n2, zonen2, zonel2 = utm.from_latlon(rlat, lon2)
# NOTE: great circle distance used here, not reference ellipsoid! # NOTE: great circle distance used here, not reference ellipsoid!
xshift = utm.haversine(rlon, rlat, lon2, rlat) - e2 xshift = utm.haversine(rlon, rlat, lon2, rlat) - e2
# cache the return value # cache the return value
@ -180,25 +230,33 @@ class CoreLocation(ConfigurableManager):
return xshift return xshift
def getnorthingshift(self, zonen, zonel): def getnorthingshift(self, zonen, zonel):
''' If the lat, lon coordinates being converted are located in a """
If the lat, lon coordinates being converted are located in a
different UTM zone than the canvas reference point, the UTM meters different UTM zone than the canvas reference point, the UTM meters
may need to be shifted. may need to be shifted.
This picks a reference point in the same latitude band (UTM zone letter) This picks a reference point in the same latitude band (UTM zone letter)
as the provided zone, to calculate the shift in meters for the as the provided zone, to calculate the shift in meters for the
y coordinate. y coordinate.
'''
rzonel = self.refutm[0][1]
if zonel == rzonel:
return None # same zone letter, no y shift required
z = (zonen, zonel)
if z in self.zoneshifts and self.zoneshifts[z][1] is not None:
return self.zoneshifts[z][1] # y shift already calculated, cached
(rlat, rlon, ralt) = self.refgeo :param zonen: zonen
:param zonel: zone1
:return: calculated y shift
"""
rzonel = self.refutm[0][1]
# same zone letter, no y shift required
if zonel == rzonel:
return None
z = (zonen, zonel)
# y shift already calculated, cached
if z in self.zoneshifts and self.zoneshifts[z][1] is not None:
return self.zoneshifts[z][1]
rlat, rlon, ralt = self.refgeo
# zonemap is used to calculate degrees difference between zone letters # zonemap is used to calculate degrees difference between zone letters
latshift = self.zonemap[zonel] - self.zonemap[rzonel] latshift = self.zonemap[zonel] - self.zonemap[rzonel]
lat2 = rlat + latshift # ea. latitude band is 8deg high # ea. latitude band is 8deg high
(e2, n2, zonen2, zonel2) = utm.from_latlon(lat2, rlon) lat2 = rlat + latshift
e2, n2, zonen2, zonel2 = utm.from_latlon(lat2, rlon)
# NOTE: great circle distance used here, not reference ellipsoid # NOTE: great circle distance used here, not reference ellipsoid
yshift = -(utm.haversine(rlon, rlat, rlon, lat2) + n2) yshift = -(utm.haversine(rlon, rlat, rlon, lat2) + n2)
# cache the return value # cache the return value
@ -209,26 +267,32 @@ class CoreLocation(ConfigurableManager):
return yshift return yshift
def getutmzoneshift(self, e, n): def getutmzoneshift(self, e, n):
''' Given UTM easting and northing values, check if they fall outside """
Given UTM easting and northing values, check if they fall outside
the reference point's zone boundary. Return the UTM coordinates in a the reference point's zone boundary. Return the UTM coordinates in a
different zone and the new zone if they do. Zone lettering is only different zone and the new zone if they do. Zone lettering is only
changed when the reference point is in the opposite hemisphere. changed when the reference point is in the opposite hemisphere.
'''
:param e: easting value
:param n: northing value
:return: modified easting, northing, and zone values
:rtype: tuple
"""
zone = self.refutm[0] zone = self.refutm[0]
(rlat, rlon, ralt) = self.refgeo rlat, rlon, ralt = self.refgeo
if e > 834000 or e < 166000: if e > 834000 or e < 166000:
num_zones = (int(e) - 166000) / (utm.R/10) num_zones = (int(e) - 166000) / (utm.R / 10)
# estimate number of zones to shift, E (positive) or W (negative) # estimate number of zones to shift, E (positive) or W (negative)
rlon2 = self.refgeo[1] + (num_zones * 6) rlon2 = self.refgeo[1] + (num_zones * 6)
(e2, n2, zonen2, zonel2) = utm.from_latlon(rlat, rlon2) e2, n2, zonen2, zonel2 = utm.from_latlon(rlat, rlon2)
xshift = utm.haversine(rlon, rlat, rlon2, rlat) xshift = utm.haversine(rlon, rlat, rlon2, rlat)
# after >3 zones away from refpt, the above estimate won't work # after >3 zones away from refpt, the above estimate won't work
# (the above estimate could be improved) # (the above estimate could be improved)
if not 100000 <= (e - xshift) < 1000000: if not 100000 <= (e - xshift) < 1000000:
# move one more zone away # move one more zone away
num_zones = (abs(num_zones)+1) * (abs(num_zones)/num_zones) num_zones = (abs(num_zones) + 1) * (abs(num_zones) / num_zones)
rlon2 = self.refgeo[1] + (num_zones * 6) rlon2 = self.refgeo[1] + (num_zones * 6)
(e2, n2, zonen2, zonel2) = utm.from_latlon(rlat, rlon2) e2, n2, zonen2, zonel2 = utm.from_latlon(rlat, rlon2)
xshift = utm.haversine(rlon, rlat, rlon2, rlat) xshift = utm.haversine(rlon, rlat, rlon2, rlat)
e = e - xshift e = e - xshift
zone = (zonen2, zonel2) zone = (zonen2, zonel2)
@ -240,7 +304,4 @@ class CoreLocation(ConfigurableManager):
# refpt in southern hemisphere and we crossed north of equator # refpt in southern hemisphere and we crossed north of equator
n -= 10000000 n -= 10000000
zone = (zone[0], 'N') zone = (zone[0], 'N')
return (e, n, zone) return e, n, zone

View file

@ -5,9 +5,9 @@
from math import pi, sin, cos, tan, sqrt from math import pi, sin, cos, tan, sqrt
#LatLong- UTM conversion..h # LatLong- UTM conversion..h
#definitions for lat/long to UTM and UTM to lat/lng conversions # definitions for lat/long to UTM and UTM to lat/lng conversions
#include <string.h> # include <string.h>
_deg2rad = pi / 180.0 _deg2rad = pi / 180.0
_rad2deg = 180.0 / pi _rad2deg = 180.0 / pi
@ -16,48 +16,49 @@ _EquatorialRadius = 2
_eccentricitySquared = 3 _eccentricitySquared = 3
_ellipsoid = [ _ellipsoid = [
# id, Ellipsoid name, Equatorial Radius, square of eccentricity # id, Ellipsoid name, Equatorial Radius, square of eccentricity
# first once is a placeholder only, To allow array indices to match id numbers # first once is a placeholder only, To allow array indices to match id numbers
[ -1, "Placeholder", 0, 0], [-1, "Placeholder", 0, 0],
[ 1, "Airy", 6377563, 0.00667054], [1, "Airy", 6377563, 0.00667054],
[ 2, "Australian National", 6378160, 0.006694542], [2, "Australian National", 6378160, 0.006694542],
[ 3, "Bessel 1841", 6377397, 0.006674372], [3, "Bessel 1841", 6377397, 0.006674372],
[ 4, "Bessel 1841 (Nambia] ", 6377484, 0.006674372], [4, "Bessel 1841 (Nambia] ", 6377484, 0.006674372],
[ 5, "Clarke 1866", 6378206, 0.006768658], [5, "Clarke 1866", 6378206, 0.006768658],
[ 6, "Clarke 1880", 6378249, 0.006803511], [6, "Clarke 1880", 6378249, 0.006803511],
[ 7, "Everest", 6377276, 0.006637847], [7, "Everest", 6377276, 0.006637847],
[ 8, "Fischer 1960 (Mercury] ", 6378166, 0.006693422], [8, "Fischer 1960 (Mercury] ", 6378166, 0.006693422],
[ 9, "Fischer 1968", 6378150, 0.006693422], [9, "Fischer 1968", 6378150, 0.006693422],
[ 10, "GRS 1967", 6378160, 0.006694605], [10, "GRS 1967", 6378160, 0.006694605],
[ 11, "GRS 1980", 6378137, 0.00669438], [11, "GRS 1980", 6378137, 0.00669438],
[ 12, "Helmert 1906", 6378200, 0.006693422], [12, "Helmert 1906", 6378200, 0.006693422],
[ 13, "Hough", 6378270, 0.00672267], [13, "Hough", 6378270, 0.00672267],
[ 14, "International", 6378388, 0.00672267], [14, "International", 6378388, 0.00672267],
[ 15, "Krassovsky", 6378245, 0.006693422], [15, "Krassovsky", 6378245, 0.006693422],
[ 16, "Modified Airy", 6377340, 0.00667054], [16, "Modified Airy", 6377340, 0.00667054],
[ 17, "Modified Everest", 6377304, 0.006637847], [17, "Modified Everest", 6377304, 0.006637847],
[ 18, "Modified Fischer 1960", 6378155, 0.006693422], [18, "Modified Fischer 1960", 6378155, 0.006693422],
[ 19, "South American 1969", 6378160, 0.006694542], [19, "South American 1969", 6378160, 0.006694542],
[ 20, "WGS 60", 6378165, 0.006693422], [20, "WGS 60", 6378165, 0.006693422],
[ 21, "WGS 66", 6378145, 0.006694542], [21, "WGS 66", 6378145, 0.006694542],
[ 22, "WGS-72", 6378135, 0.006694318], [22, "WGS-72", 6378135, 0.006694318],
[ 23, "WGS-84", 6378137, 0.00669438] [23, "WGS-84", 6378137, 0.00669438]
] ]
#Reference ellipsoids derived from Peter H. Dana's website-
#http://www.utexas.edu/depts/grg/gcraft/notes/datum/elist.html
#Department of Geography, University of Texas at Austin
#Internet: pdana@mail.utexas.edu
#3/22/95
#Source # Reference ellipsoids derived from Peter H. Dana's website-
#Defense Mapping Agency. 1987b. DMA Technical Report: Supplement to Department of Defense World Geodetic System # http://www.utexas.edu/depts/grg/gcraft/notes/datum/elist.html
#1984 Technical Report. Part I and II. Washington, DC: Defense Mapping Agency # Department of Geography, University of Texas at Austin
# Internet: pdana@mail.utexas.edu
# 3/22/95
#def LLtoUTM(int ReferenceEllipsoid, const double Lat, const double Long, # Source
# Defense Mapping Agency. 1987b. DMA Technical Report: Supplement to Department of Defense World Geodetic System
# 1984 Technical Report. Part I and II. Washington, DC: Defense Mapping Agency
# def LLtoUTM(int ReferenceEllipsoid, const double Lat, const double Long,
# double &UTMNorthing, double &UTMEasting, char* UTMZone) # double &UTMNorthing, double &UTMEasting, char* UTMZone)
def LLtoUTM(ReferenceEllipsoid, Lat, Long, zone = None): def LLtoUTM(ReferenceEllipsoid, Lat, Long, zone=None):
"""converts lat/long to UTM coords. Equations from USGS Bulletin 1532 """converts lat/long to UTM coords. Equations from USGS Bulletin 1532
East Longitudes are positive, West longitudes are negative. East Longitudes are positive, West longitudes are negative.
North latitudes are positive, South latitudes are negative North latitudes are positive, South latitudes are negative
@ -68,14 +69,14 @@ def LLtoUTM(ReferenceEllipsoid, Lat, Long, zone = None):
eccSquared = _ellipsoid[ReferenceEllipsoid][_eccentricitySquared] eccSquared = _ellipsoid[ReferenceEllipsoid][_eccentricitySquared]
k0 = 0.9996 k0 = 0.9996
#Make sure the longitude is between -180.00 .. 179.9 # Make sure the longitude is between -180.00 .. 179.9
LongTemp = (Long+180)-int((Long+180)/360)*360-180 # -180.00 .. 179.9 LongTemp = (Long + 180) - int((Long + 180) / 360) * 360 - 180 # -180.00 .. 179.9
LatRad = Lat*_deg2rad LatRad = Lat * _deg2rad
LongRad = LongTemp*_deg2rad LongRad = LongTemp * _deg2rad
if zone is None: if zone is None:
ZoneNumber = int((LongTemp + 180)/6) + 1 ZoneNumber = int((LongTemp + 180) / 6) + 1
else: else:
ZoneNumber = zone ZoneNumber = zone
@ -84,46 +85,50 @@ def LLtoUTM(ReferenceEllipsoid, Lat, Long, zone = None):
# Special zones for Svalbard # Special zones for Svalbard
if Lat >= 72.0 and Lat < 84.0: if Lat >= 72.0 and Lat < 84.0:
if LongTemp >= 0.0 and LongTemp < 9.0:ZoneNumber = 31 if LongTemp >= 0.0 and LongTemp < 9.0:
elif LongTemp >= 9.0 and LongTemp < 21.0: ZoneNumber = 33 ZoneNumber = 31
elif LongTemp >= 21.0 and LongTemp < 33.0: ZoneNumber = 35 elif LongTemp >= 9.0 and LongTemp < 21.0:
elif LongTemp >= 33.0 and LongTemp < 42.0: ZoneNumber = 37 ZoneNumber = 33
elif LongTemp >= 21.0 and LongTemp < 33.0:
ZoneNumber = 35
elif LongTemp >= 33.0 and LongTemp < 42.0:
ZoneNumber = 37
LongOrigin = (ZoneNumber - 1)*6 - 180 + 3 #+3 puts origin in middle of zone LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3 # +3 puts origin in middle of zone
LongOriginRad = LongOrigin * _deg2rad LongOriginRad = LongOrigin * _deg2rad
#compute the UTM Zone from the latitude and longitude # compute the UTM Zone from the latitude and longitude
UTMZone = "%d%c" % (ZoneNumber, _UTMLetterDesignator(Lat)) UTMZone = "%d%c" % (ZoneNumber, _UTMLetterDesignator(Lat))
eccPrimeSquared = (eccSquared)/(1-eccSquared) eccPrimeSquared = (eccSquared) / (1 - eccSquared)
N = a/sqrt(1-eccSquared*sin(LatRad)*sin(LatRad)) N = a / sqrt(1 - eccSquared * sin(LatRad) * sin(LatRad))
T = tan(LatRad)*tan(LatRad) T = tan(LatRad) * tan(LatRad)
C = eccPrimeSquared*cos(LatRad)*cos(LatRad) C = eccPrimeSquared * cos(LatRad) * cos(LatRad)
A = cos(LatRad)*(LongRad-LongOriginRad) A = cos(LatRad) * (LongRad - LongOriginRad)
M = a*((1 M = a * ((1
- eccSquared/4 - eccSquared / 4
- 3*eccSquared*eccSquared/64 - 3 * eccSquared * eccSquared / 64
- 5*eccSquared*eccSquared*eccSquared/256)*LatRad - 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad
- (3*eccSquared/8 - (3 * eccSquared / 8
+ 3*eccSquared*eccSquared/32 + 3 * eccSquared * eccSquared / 32
+ 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*LatRad) + 45 * eccSquared * eccSquared * eccSquared / 1024) * sin(2 * LatRad)
+ (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*LatRad) + (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * sin(4 * LatRad)
- (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*LatRad)) - (35 * eccSquared * eccSquared * eccSquared / 3072) * sin(6 * LatRad))
UTMEasting = (k0*N*(A+(1-T+C)*A*A*A/6 UTMEasting = (k0 * N * (A + (1 - T + C) * A * A * A / 6
+ (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120) + (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120)
+ 500000.0) + 500000.0)
UTMNorthing = (k0*(M+N*tan(LatRad)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24 UTMNorthing = (k0 * (M + N * tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24
+ (61 + (61
-58*T - 58 * T
+T*T + T * T
+600*C + 600 * C
-330*eccPrimeSquared)*A*A*A*A*A*A/720))) - 330 * eccPrimeSquared) * A * A * A * A * A * A / 720)))
if Lat < 0: if Lat < 0:
UTMNorthing = UTMNorthing + 10000000.0; #10000000 meter offset for southern hemisphere UTMNorthing = UTMNorthing + 10000000.0; # 10000000 meter offset for southern hemisphere
return (UTMZone, UTMEasting, UTMNorthing) return (UTMZone, UTMEasting, UTMNorthing)
@ -132,29 +137,51 @@ def _UTMLetterDesignator(Lat):
latitude returns 'Z' if latitude is outside the UTM limits of 84N to 80S latitude returns 'Z' if latitude is outside the UTM limits of 84N to 80S
Written by Chuck Gantz- chuck.gantz@globalstar.com""" Written by Chuck Gantz- chuck.gantz@globalstar.com"""
if 84 >= Lat >= 72: return 'X' if 84 >= Lat >= 72:
elif 72 > Lat >= 64: return 'W' return 'X'
elif 64 > Lat >= 56: return 'V' elif 72 > Lat >= 64:
elif 56 > Lat >= 48: return 'U' return 'W'
elif 48 > Lat >= 40: return 'T' elif 64 > Lat >= 56:
elif 40 > Lat >= 32: return 'S' return 'V'
elif 32 > Lat >= 24: return 'R' elif 56 > Lat >= 48:
elif 24 > Lat >= 16: return 'Q' return 'U'
elif 16 > Lat >= 8: return 'P' elif 48 > Lat >= 40:
elif 8 > Lat >= 0: return 'N' return 'T'
elif 0 > Lat >= -8: return 'M' elif 40 > Lat >= 32:
elif -8> Lat >= -16: return 'L' return 'S'
elif -16 > Lat >= -24: return 'K' elif 32 > Lat >= 24:
elif -24 > Lat >= -32: return 'J' return 'R'
elif -32 > Lat >= -40: return 'H' elif 24 > Lat >= 16:
elif -40 > Lat >= -48: return 'G' return 'Q'
elif -48 > Lat >= -56: return 'F' elif 16 > Lat >= 8:
elif -56 > Lat >= -64: return 'E' return 'P'
elif -64 > Lat >= -72: return 'D' elif 8 > Lat >= 0:
elif -72 > Lat >= -80: return 'C' return 'N'
else: return 'Z' # if the Latitude is outside the UTM limits elif 0 > Lat >= -8:
return 'M'
elif -8 > Lat >= -16:
return 'L'
elif -16 > Lat >= -24:
return 'K'
elif -24 > Lat >= -32:
return 'J'
elif -32 > Lat >= -40:
return 'H'
elif -40 > Lat >= -48:
return 'G'
elif -48 > Lat >= -56:
return 'F'
elif -56 > Lat >= -64:
return 'E'
elif -64 > Lat >= -72:
return 'D'
elif -72 > Lat >= -80:
return 'C'
else:
return 'Z' # if the Latitude is outside the UTM limits
#void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone,
# void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone,
# double& Lat, double& Long ) # double& Lat, double& Long )
def UTMtoLL(ReferenceEllipsoid, northing, easting, zone): def UTMtoLL(ReferenceEllipsoid, northing, easting, zone):
@ -168,10 +195,10 @@ Converted to Python by Russ Nelson <nelson@crynwr.com>"""
k0 = 0.9996 k0 = 0.9996
a = _ellipsoid[ReferenceEllipsoid][_EquatorialRadius] a = _ellipsoid[ReferenceEllipsoid][_EquatorialRadius]
eccSquared = _ellipsoid[ReferenceEllipsoid][_eccentricitySquared] eccSquared = _ellipsoid[ReferenceEllipsoid][_eccentricitySquared]
e1 = (1-sqrt(1-eccSquared))/(1+sqrt(1-eccSquared)) e1 = (1 - sqrt(1 - eccSquared)) / (1 + sqrt(1 - eccSquared))
#NorthernHemisphere; //1 for northern hemispher, 0 for southern # NorthernHemisphere; //1 for northern hemispher, 0 for southern
x = easting - 500000.0 #remove 500,000 meter offset for longitude x = easting - 500000.0 # remove 500,000 meter offset for longitude
y = northing y = northing
ZoneLetter = zone[-1] ZoneLetter = zone[-1]
@ -182,35 +209,38 @@ Converted to Python by Russ Nelson <nelson@crynwr.com>"""
NorthernHemisphere = 0 # point is in southern hemisphere NorthernHemisphere = 0 # point is in southern hemisphere
y -= 10000000.0 # remove 10,000,000 meter offset used for southern hemisphere y -= 10000000.0 # remove 10,000,000 meter offset used for southern hemisphere
LongOrigin = (ZoneNumber - 1)*6 - 180 + 3 # +3 puts origin in middle of zone LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3 # +3 puts origin in middle of zone
eccPrimeSquared = (eccSquared)/(1-eccSquared) eccPrimeSquared = (eccSquared) / (1 - eccSquared)
M = y / k0 M = y / k0
mu = M/(a*(1-eccSquared/4-3*eccSquared*eccSquared/64-5*eccSquared*eccSquared*eccSquared/256)) mu = M / (
a * (1 - eccSquared / 4 - 3 * eccSquared * eccSquared / 64 - 5 * eccSquared * eccSquared * eccSquared / 256))
phi1Rad = (mu + (3*e1/2-27*e1*e1*e1/32)*sin(2*mu) phi1Rad = (mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * sin(2 * mu)
+ (21*e1*e1/16-55*e1*e1*e1*e1/32)*sin(4*mu) + (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * sin(4 * mu)
+(151*e1*e1*e1/96)*sin(6*mu)) + (151 * e1 * e1 * e1 / 96) * sin(6 * mu))
phi1 = phi1Rad*_rad2deg; phi1 = phi1Rad * _rad2deg;
N1 = a/sqrt(1-eccSquared*sin(phi1Rad)*sin(phi1Rad)) N1 = a / sqrt(1 - eccSquared * sin(phi1Rad) * sin(phi1Rad))
T1 = tan(phi1Rad)*tan(phi1Rad) T1 = tan(phi1Rad) * tan(phi1Rad)
C1 = eccPrimeSquared*cos(phi1Rad)*cos(phi1Rad) C1 = eccPrimeSquared * cos(phi1Rad) * cos(phi1Rad)
R1 = a*(1-eccSquared)/pow(1-eccSquared*sin(phi1Rad)*sin(phi1Rad), 1.5) R1 = a * (1 - eccSquared) / pow(1 - eccSquared * sin(phi1Rad) * sin(phi1Rad), 1.5)
D = x/(N1*k0) D = x / (N1 * k0)
Lat = phi1Rad - (N1*tan(phi1Rad)/R1)*(D*D/2-(5+3*T1+10*C1-4*C1*C1-9*eccPrimeSquared)*D*D*D*D/24 Lat = phi1Rad - (N1 * tan(phi1Rad) / R1) * (
+(61+90*T1+298*C1+45*T1*T1-252*eccPrimeSquared-3*C1*C1)*D*D*D*D*D*D/720) D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24
+ (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720)
Lat = Lat * _rad2deg Lat = Lat * _rad2deg
Long = (D-(1+2*T1+C1)*D*D*D/6+(5-2*C1+28*T1-3*C1*C1+8*eccPrimeSquared+24*T1*T1) Long = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (
*D*D*D*D*D/120)/cos(phi1Rad) 5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1)
* D * D * D * D * D / 120) / cos(phi1Rad)
Long = LongOrigin + Long * _rad2deg Long = LongOrigin + Long * _rad2deg
return (Lat, Long) return (Lat, Long)
if __name__ == '__main__': if __name__ == '__main__':
(z, e, n) = LLtoUTM(23, 45.00, -75.00) (z, e, n) = LLtoUTM(23, 45.00, -75.00)
print z, e, n print z, e, n
print UTMtoLL(23, n, e, z) print UTMtoLL(23, n, e, z)

View file

@ -1,39 +1,58 @@
# """
# CORE
# Copyright (c)2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Tom Goff <thomas.goff@boeing.com>
#
'''
event.py: event loop implementation using a heap queue and threads. event.py: event loop implementation using a heap queue and threads.
''' """
import time
import threading
import heapq import heapq
import threading
import time
class EventLoop(object): from core.misc import log
class Timer(threading.Thread): logger = log.get_logger(__name__)
'''\
class Timer(threading.Thread):
"""
Based on threading.Timer but cancel() returns if the timer was Based on threading.Timer but cancel() returns if the timer was
already running. already running.
''' """
def __init__(self, interval, function, args=[], kwargs={}): def __init__(self, interval, function, args=None, kwargs=None):
super(EventLoop.Timer, self).__init__() """
Create a Timer instance.
:param interval: time interval
:param function: function to call when timer finishes
:param args: function arguments
:param kwargs: function keyword arguments
"""
super(Timer, self).__init__()
self.interval = interval self.interval = interval
self.function = function self.function = function
self.args = args
self.kwargs = kwargs
self.finished = threading.Event() self.finished = threading.Event()
self._running = threading.Lock() self._running = threading.Lock()
# validate arguments were provided
if args:
self.args = args
else:
self.args = []
# validate keyword arguments were provided
if kwargs:
self.kwargs = kwargs
else:
self.kwargs = {}
def cancel(self): def cancel(self):
'''\ """
Stop the timer if it hasn't finished yet. Return False if Stop the timer if it hasn't finished yet. Return False if
the timer was already running. the timer was already running.
'''
:return: True if canceled, False otherwise
:rtype: bool
"""
locked = self._running.acquire(False) locked = self._running.acquire(False)
if locked: if locked:
self.finished.set() self.finished.set()
@ -41,36 +60,82 @@ class EventLoop(object):
return locked return locked
def run(self): def run(self):
"""
Run the timer.
:return: nothing
"""
self.finished.wait(self.interval) self.finished.wait(self.interval)
with self._running: with self._running:
if not self.finished.is_set(): if not self.finished.is_set():
self.function(*self.args, **self.kwargs) self.function(*self.args, **self.kwargs)
self.finished.set() self.finished.set()
class Event(object):
def __init__(self, eventnum, time, func, *args, **kwds): class Event(object):
"""
Provides event objects that can be used within the EventLoop class.
"""
def __init__(self, eventnum, event_time, func, *args, **kwds):
"""
Create an Event instance.
:param eventnum: event number
:param event_time: event time
:param func: event function
:param args: function arguments
:param kwds: function keyword arguments
"""
self.eventnum = eventnum self.eventnum = eventnum
self.time = time self.time = event_time
self.func = func self.func = func
self.args = args self.args = args
self.kwds = kwds self.kwds = kwds
self.canceled = False self.canceled = False
def __cmp__(self, other): def __cmp__(self, other):
"""
Comparison function.
:param Event other: event to compare with
:return: comparison result
:rtype: int
"""
tmp = cmp(self.time, other.time) tmp = cmp(self.time, other.time)
if tmp == 0: if tmp == 0:
tmp = cmp(self.eventnum, other.eventnum) tmp = cmp(self.eventnum, other.eventnum)
return tmp return tmp
def run(self): def run(self):
"""
Run an event.
:return: nothing
"""
if self.canceled: if self.canceled:
return return
self.func(*self.args, **self.kwds) self.func(*self.args, **self.kwds)
def cancel(self): def cancel(self):
self.canceled = True # XXX not thread-safe """
Cancel event.
:return: nothing
"""
# XXX not thread-safe
self.canceled = True
class EventLoop(object):
"""
Provides an event loop for running events.
"""
def __init__(self): def __init__(self):
"""
Creates a EventLoop instance.
"""
self.lock = threading.RLock() self.lock = threading.RLock()
self.queue = [] self.queue = []
self.eventnum = 0 self.eventnum = 0
@ -79,6 +144,11 @@ class EventLoop(object):
self.start = None self.start = None
def __run_events(self): def __run_events(self):
"""
Run events.
:return: nothing
"""
schedule = False schedule = False
while True: while True:
with self.lock: with self.lock:
@ -91,23 +161,34 @@ class EventLoop(object):
event = heapq.heappop(self.queue) event = heapq.heappop(self.queue)
assert event.time <= now assert event.time <= now
event.run() event.run()
with self.lock: with self.lock:
self.timer = None self.timer = None
if schedule: if schedule:
self.__schedule_event() self.__schedule_event()
def __schedule_event(self): def __schedule_event(self):
"""
Schedule event.
:return: nothing
"""
with self.lock: with self.lock:
assert self.running assert self.running
if not self.queue: if not self.queue:
return return
delay = self.queue[0].time - time.time() delay = self.queue[0].time - time.time()
assert self.timer is None assert self.timer is None
self.timer = EventLoop.Timer(delay, self.__run_events) self.timer = Timer(delay, self.__run_events)
self.timer.daemon = True self.timer.daemon = True
self.timer.start() self.timer.start()
def run(self): def run(self):
"""
Start event loop.
:return: nothing
"""
with self.lock: with self.lock:
if self.running: if self.running:
return return
@ -118,6 +199,11 @@ class EventLoop(object):
self.__schedule_event() self.__schedule_event()
def stop(self): def stop(self):
"""
Stop event loop.
:return: nothing
"""
with self.lock: with self.lock:
if not self.running: if not self.running:
return return
@ -130,13 +216,23 @@ class EventLoop(object):
self.start = None self.start = None
def add_event(self, delaysec, func, *args, **kwds): def add_event(self, delaysec, func, *args, **kwds):
"""
Add an event to the event loop.
:param int delaysec: delay in seconds for event
:param func: event function
:param args: event arguments
:param kwds: event keyword arguments
:return: created event
:rtype: Event
"""
with self.lock: with self.lock:
eventnum = self.eventnum eventnum = self.eventnum
self.eventnum += 1 self.eventnum += 1
evtime = float(delaysec) evtime = float(delaysec)
if self.running: if self.running:
evtime += time.time() evtime += time.time()
event = self.Event(eventnum, evtime, func, *args, **kwds) event = Event(eventnum, evtime, func, *args, **kwds)
if self.queue: if self.queue:
prevhead = self.queue[0] prevhead = self.queue[0]
@ -152,12 +248,14 @@ class EventLoop(object):
self.__schedule_event() self.__schedule_event()
return event return event
# TODO: move example to documentation
def example(): def example():
loop = EventLoop() loop = EventLoop()
def msg(arg): def msg(arg):
delta = time.time() - loop.start delta = time.time() - loop.start
print delta, 'arg:', arg logger.debug("%s arg: %s", delta, arg)
def repeat(interval, count): def repeat(interval, count):
count -= 1 count -= 1

View file

@ -1,230 +0,0 @@
#
# CORE
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Tom Goff <thomas.goff@boeing.com>
#
'''
ipaddr.py: helper objects for dealing with IPv4/v6 addresses.
'''
import socket
import struct
import random
AF_INET = socket.AF_INET
AF_INET6 = socket.AF_INET6
class MacAddr(object):
def __init__(self, addr):
self.addr = addr
def __str__(self):
return ":".join(map(lambda x: ("%02x" % ord(x)), self.addr))
def tolinklocal(self):
''' Convert the MAC address to a IPv6 link-local address, using EUI 48
to EUI 64 conversion process per RFC 5342.
'''
if not self.addr:
return IPAddr.fromstring("::")
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 IPAddr(AF_INET6, struct.pack("!QQ", 0xfe80 << 48, oui | nic))
@classmethod
def fromstring(cls, s):
addr = "".join(map(lambda x: chr(int(x, 16)), s.split(":")))
return cls(addr)
@classmethod
def random(cls):
tmp = random.randint(0, 0xFFFFFF)
tmp |= 0x00163E << 24 # use the Xen OID 00:16:3E
tmpbytes = struct.pack("!Q", tmp)
return cls(tmpbytes[2:])
class IPAddr(object):
def __init__(self, af, addr):
# check if (af, addr) is valid
if not socket.inet_ntop(af, addr):
raise ValueError, "invalid af/addr"
self.af = af
self.addr = addr
def isIPv4(self):
return self.af == AF_INET
def isIPv6(self):
return self.af == AF_INET6
def __str__(self):
return socket.inet_ntop(self.af, self.addr)
def __eq__(self, other):
try:
return other.af == self.af and other.addr == self.addr
except:
return False
def __add__(self, other):
try:
carry = int(other)
except:
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):
try:
tmp = -int(other)
except:
return NotImplemented
return self.__add__(tmp)
@classmethod
def fromstring(cls, s):
for af in AF_INET, AF_INET6:
try:
return cls(af, socket.inet_pton(af, s))
except Exception, e:
pass
raise e
@staticmethod
def toint(s):
''' convert IPv4 string to 32-bit integer
'''
bin = socket.inet_pton(AF_INET, s)
return(struct.unpack('!I', bin)[0])
class IPPrefix(object):
def __init__(self, af, prefixstr):
"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):
return "%s/%s" % (socket.inet_ntop(self.af, self.prefix),
self.prefixlen)
def __eq__(self, other):
try:
return other.af == self.af and \
other.prefixlen == self.prefixlen and \
other.prefix == self.prefix
except:
return False
def __add__(self, other):
try:
tmp = int(other)
except:
return NotImplemented
a = IPAddr(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):
try:
tmp = -int(other)
except:
return NotImplemented
return self.__add__(tmp)
def addr(self, hostid):
tmp = int(hostid)
if (tmp == 1 or tmp == 0 or tmp == -1) and self.addrlen == self.prefixlen:
return IPAddr(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 = ""
for i in xrange(-1, -(self.addrlen >> 3) - 1, -1):
addr = chr(ord(self.prefix[i]) | (tmp & 0xff)) + addr
tmp >>= 8
if not tmp:
break
addr = self.prefix[:i] + addr
return IPAddr(self.af, addr)
def minaddr(self):
return self.addr(1)
def maxaddr(self):
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 numaddr(self):
return max(0, (1 << (self.addrlen - self.prefixlen)) - 2)
def prefixstr(self):
return "%s" % socket.inet_ntop(self.af, self.prefix)
def netmaskstr(self):
addrbits = self.addrlen - self.prefixlen
netmask = ((1L << self.prefixlen) - 1) << addrbits
netmaskbytes = struct.pack("!L", netmask)
return IPAddr(af=AF_INET, addr=netmaskbytes).__str__()
class IPv4Prefix(IPPrefix):
def __init__(self, prefixstr):
IPPrefix.__init__(self, AF_INET, prefixstr)
class IPv6Prefix(IPPrefix):
def __init__(self, prefixstr):
IPPrefix.__init__(self, AF_INET6, prefixstr)
def isIPAddress(af, addrstr):
try:
tmp = socket.inet_pton(af, addrstr)
return True
except:
return False
def isIPv4Address(addrstr):
return isIPAddress(AF_INET, addrstr)
def isIPv6Address(addrstr):
return isIPAddress(AF_INET6, addrstr)

View file

@ -5,35 +5,76 @@
# #
# author: Tom Goff <thomas.goff@boeing.com> # author: Tom Goff <thomas.goff@boeing.com>
# #
'''
quagga.py: helper class for generating Quagga configuration.
'''
import os.path """
quagga.py: helper class for generating Quagga configuration.
"""
from string import Template from string import Template
def maketuple(obj): from core.misc import utils
if hasattr(obj, "__iter__"):
return tuple(obj)
def addrstr(x):
if x.find(".") >= 0:
return "ip address %s" % x
elif x.find(":") >= 0:
return "ipv6 address %s" % x
else: else:
return (obj,) raise ValueError("invalid address: %s" % x)
class NetIf(object): class NetIf(object):
def __init__(self, name, addrlist = []): """
Represents a network interface.
"""
def __init__(self, name, addrlist=None):
"""
Create a NetIf instance.
:param str name: interface name
:param addrlist: address list for the interface
"""
self.name = name self.name = name
if addrlist:
self.addrlist = addrlist self.addrlist = addrlist
else:
self.addrlist = []
class Conf(object): class Conf(object):
def __init__(self, **kwds): """
self.kwds = kwds Provides a configuration object.
"""
def __init__(self, **kwargs):
"""
Create a Conf instance.
:param dict kwargs: configuration keyword arguments
"""
self.kwargs = kwargs
def __str__(self): def __str__(self):
tmp = self.template.substitute(**self.kwds) """
if tmp[-1] == '\n': Provides a string representation of a configuration object.
:return: string representation
:rtype: str
"""
# TODO: seems like an error here
tmp = self.template.substitute(**self.kwargs)
if tmp[-1] == "\n":
tmp = tmp[:-1] tmp = tmp[:-1]
return tmp return tmp
class QuaggaOSPF6Interface(Conf): class QuaggaOSPF6Interface(Conf):
"""
Provides quagga ospf6 interface functionality.
"""
AF_IPV6_ID = 0 AF_IPV6_ID = 0
AF_IPV4_ID = 65 AF_IPV4_ID = 65
@ -50,32 +91,40 @@ interface $interface
ipv6 ospf6 lsafullness mincostlsa ipv6 ospf6 lsafullness mincostlsa
""") """)
# ip address $ipaddr/32 # ip address $ipaddr/32
# ipv6 ospf6 simhelloLLtoULRecv :$simhelloport # ipv6 ospf6 simhelloLLtoULRecv :$simhelloport
# !$ipaddr:$simhelloport # !$ipaddr:$simhelloport
def __init__(self, netif, instanceid = AF_IPV4_ID, def __init__(self, netif, instanceid=AF_IPV4_ID, network="manet-designated-router", **kwargs):
network = "manet-designated-router", **kwds): """
Create a QuaggaOSPF6Interface instance.
:param netif: network interface
:param int instanceid: instance id
:param network: network
:param dict kwargs: keyword arguments
"""
self.netif = netif self.netif = netif
def addrstr(x):
if x.find(".") >= 0:
return "ip address %s" % x
elif x.find(":") >= 0:
return "ipv6 address %s" % x
else:
raise Value, "invalid address: %s", x
addr = "\n ".join(map(addrstr, netif.addrlist)) addr = "\n ".join(map(addrstr, netif.addrlist))
self.instanceid = instanceid self.instanceid = instanceid
self.network = network self.network = network
Conf.__init__(self, interface = netif.name, addr = addr, Conf.__init__(self, interface=netif.name, addr=addr,
instanceid = instanceid, network = network, **kwds) instanceid=instanceid, network=network, **kwargs)
def name(self): def name(self):
"""
Retrieve network interface name.
:return: network interface name
:rtype: str
"""
return self.netif.name return self.netif.name
class QuaggaOSPF6(Conf):
class QuaggaOSPF6(Conf):
"""
Provides quagga ospf6 functionality.
"""
template = Template("""\ template = Template("""\
$interfaces $interfaces
! !
@ -85,17 +134,25 @@ router ospf6
$redistribute $redistribute
""") """)
def __init__(self, ospf6ifs, area, routerid, def __init__(self, ospf6ifs, area, routerid, redistribute="! no redistribute"):
redistribute = "! no redistribute"): """
ospf6ifs = maketuple(ospf6ifs) Create a QuaggaOSPF6 instance.
:param list ospf6ifs: ospf6 interfaces
:param area: area
:param routerid: router id
:param str redistribute: redistribute value
"""
ospf6ifs = utils.maketuple(ospf6ifs)
interfaces = "\n!\n".join(map(str, ospf6ifs)) interfaces = "\n!\n".join(map(str, ospf6ifs))
ospfifs = "\n ".join(map(lambda x: "interface %s area %s" % \ ospfifs = "\n ".join(map(lambda x: "interface %s area %s" % (x.name(), area), ospf6ifs))
(x.name(), area), ospf6ifs)) Conf.__init__(self, interfaces=interfaces, routerid=routerid, ospfifs=ospfifs, redistribute=redistribute)
Conf.__init__(self, interfaces = interfaces, routerid = routerid,
ospfifs = ospfifs, redistribute = redistribute)
class QuaggaConf(Conf): class QuaggaConf(Conf):
"""
Provides quagga configuration functionality.
"""
template = Template("""\ template = Template("""\
log file $logfile log file $logfile
$debugs $debugs
@ -105,12 +162,18 @@ $routers
$forwarding $forwarding
""") """)
def __init__(self, routers, logfile, debugs = ()): def __init__(self, routers, logfile, debugs=()):
routers = "\n!\n".join(map(str, maketuple(routers))) """
Create a QuaggaConf instance.
:param list routers: routers
:param str logfile: log file name
:param debugs: debug options
"""
routers = "\n!\n".join(map(str, utils.maketuple(routers)))
if debugs: if debugs:
debugs = "\n".join(maketuple(debugs)) debugs = "\n".join(utils.maketuple(debugs))
else: else:
debugs = "! no debugs" debugs = "! no debugs"
forwarding = "ip forwarding\nipv6 forwarding" forwarding = "ip forwarding\nipv6 forwarding"
Conf.__init__(self, logfile = logfile, debugs = debugs, Conf.__init__(self, logfile=logfile, debugs=debugs, routers=routers, forwarding=forwarding)
routers = routers, forwarding = forwarding)

View file

@ -1,123 +1,235 @@
# """
# CORE Miscellaneous utility functions, wrappers around some subprocess procedures.
# Copyright (c)2010-2012 the Boeing Company. """
# See the LICENSE file included in this distribution.
# import ast
# authors: Tom Goff <thomas.goff@boeing.com> import os
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com> import subprocess
#
'''
utils.py: miscellaneous utility functions, wrappers around some subprocess
procedures.
'''
import subprocess, os, ast
import fcntl import fcntl
import resource
from core.misc import log
logger = log.get_logger(__name__)
def closeonexec(fd): def closeonexec(fd):
"""
Close on execution of a shell process.
:param fd: file descriptor to close
:return: nothing
"""
fdflags = fcntl.fcntl(fd, fcntl.F_GETFD) fdflags = fcntl.fcntl(fd, fcntl.F_GETFD)
fcntl.fcntl(fd, fcntl.F_SETFD, fdflags | fcntl.FD_CLOEXEC) fcntl.fcntl(fd, fcntl.F_SETFD, fdflags | fcntl.FD_CLOEXEC)
def checkexec(execlist):
for bin in execlist: def check_executables(executables):
if which(bin) is None: """
raise EnvironmentError, "executable not found: %s" % bin Check executables, verify they exist and are executable.
:param list[str] executables: executable to check
:return: nothing
:raises EnvironmentError: when an executable doesn't exist or is not executable
"""
for executable in executables:
if not is_exe(executable):
raise EnvironmentError("executable not found: %s" % executable)
def is_exe(file_path):
"""
Check if a given file path exists and is an executable file.
:param str file_path: file path to check
:return: True if the file is considered and executable file, False otherwise
:rtype: bool
"""
return os.path.isfile(file_path) and os.access(file_path, os.X_OK)
def which(program): def which(program):
''' From: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python """
''' From: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
:param str program: program to check for
:return: path if it exists, none otherwise
"""
fpath, fname = os.path.split(program) fpath, fname = os.path.split(program)
if fpath: if fpath:
if is_exe(program): if is_exe(program):
return program return program
else: else:
for path in os.environ["PATH"].split(os.pathsep): for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"') path = path.strip("\"")
exe_file = os.path.join(path, program) exe_file = os.path.join(path, program)
if is_exe(exe_file): if is_exe(exe_file):
return exe_file return exe_file
return None return None
def ensurepath(pathlist): def ensurepath(pathlist):
"""
Checks a list of paths are contained within the environment path, if not add it to the path.
:param list[str] pathlist: list of paths to check
:return: nothing
"""
searchpath = os.environ["PATH"].split(":") searchpath = os.environ["PATH"].split(":")
for p in set(pathlist): for p in set(pathlist):
if p not in searchpath: if p not in searchpath:
os.environ["PATH"] += ":" + p os.environ["PATH"] += ":" + p
def maketuple(obj): def maketuple(obj):
"""
Create a tuple from an object, or return the object itself.
:param obj: object to convert to a tuple
:return: converted tuple or the object itself
:rtype: tuple
"""
if hasattr(obj, "__iter__"): if hasattr(obj, "__iter__"):
return tuple(obj) return tuple(obj)
else: else:
return (obj,) return obj,
# TODO: remove unused parameter type
def maketuplefromstr(s, type): def maketuplefromstr(s, type):
s.replace('\\', '\\\\') """
Create a tuple from a string.
:param str s: string to convert to a tuple
:param type: type of tuple to convert to
:return: tuple from string
:rtype: tuple
"""
s.replace("\\", "\\\\")
return ast.literal_eval(s) return ast.literal_eval(s)
#return tuple(type(i) for i in s[1:-1].split(','))
#r = ()
#for i in s.strip("()").split(','):
# r += (i.strip("' "), )
# chop empty last element from "('a',)" strings
#if r[-1] == '':
# r = r[:-1]
#return r
def call(*args, **kwds):
return subprocess.call(*args, **kwds)
def mutecall(*args, **kwds): def mutecall(*args, **kwargs):
kwds["stdout"] = open(os.devnull, "w") """
kwds["stderr"] = subprocess.STDOUT Run a muted call command.
return call(*args, **kwds)
def check_call(*args, **kwds): :param list args: arguments for the command
return subprocess.check_call(*args, **kwds) :param dict kwargs: keyword arguments for the command
:return: command result
:rtype: int
"""
kwargs["stdout"] = open(os.devnull, "w")
kwargs["stderr"] = subprocess.STDOUT
return subprocess.call(*args, **kwargs)
def mutecheck_call(*args, **kwds):
kwds["stdout"] = open(os.devnull, "w")
kwds["stderr"] = subprocess.STDOUT
return subprocess.check_call(*args, **kwds)
def spawn(*args, **kwds): def mutecheck_call(*args, **kwargs):
return subprocess.Popen(*args, **kwds).pid """
Run a muted check call command.
:param list args: arguments for the command
:param dict kwargs: keyword arguments for the command
:return: command result
:rtype: int
"""
kwargs["stdout"] = open(os.devnull, "w")
kwargs["stderr"] = subprocess.STDOUT
return subprocess.check_call(*args, **kwargs)
def spawn(*args, **kwargs):
"""
Wrapper for running a spawn command and returning the process id.
:param list args: arguments for the command
:param dict kwargs: keyword arguments for the command
:return: process id of the command
:rtype: int
"""
return subprocess.Popen(*args, **kwargs).pid
def mutespawn(*args, **kwargs):
"""
Wrapper for running a muted spawned command.
:param list args: arguments for the command
:param dict kwargs: keyword arguments for the command
:return: process id of the command
:rtype: int
"""
kwargs["stdout"] = open(os.devnull, "w")
kwargs["stderr"] = subprocess.STDOUT
return subprocess.Popen(*args, **kwargs).pid
def mutespawn(*args, **kwds):
kwds["stdout"] = open(os.devnull, "w")
kwds["stderr"] = subprocess.STDOUT
return subprocess.Popen(*args, **kwds).pid
def detachinit(): def detachinit():
"""
Fork a child process and exit.
:return: nothing
"""
if os.fork(): if os.fork():
os._exit(0) # parent exits # parent exits
os._exit(0)
os.setsid() os.setsid()
def detach(*args, **kwds):
kwds["preexec_fn"] = detachinit
return subprocess.Popen(*args, **kwds).pid
def mutedetach(*args, **kwds): def detach(*args, **kwargs):
kwds["preexec_fn"] = detachinit """
kwds["stdout"] = open(os.devnull, "w") Run a detached process by forking it.
kwds["stderr"] = subprocess.STDOUT
return subprocess.Popen(*args, **kwds).pid :param list args: arguments for the command
:param dict kwargs: keyword arguments for the command
:return: process id of the command
:rtype: int
"""
kwargs["preexec_fn"] = detachinit
return subprocess.Popen(*args, **kwargs).pid
def mutedetach(*args, **kwargs):
"""
Run a muted detached process by forking it.
:param list args: arguments for the command
:param dict kwargs: keyword arguments for the command
:return: process id of the command
:rtype: int
"""
kwargs["preexec_fn"] = detachinit
kwargs["stdout"] = open(os.devnull, "w")
kwargs["stderr"] = subprocess.STDOUT
return subprocess.Popen(*args, **kwargs).pid
def cmdresult(args): def cmdresult(args):
''' Execute a command on the host and return a tuple containing the """
exit status and result string. stderr output Execute a command on the host and return a tuple containing the exit status and result string. stderr output
is folded into the stdout result string. is folded into the stdout result string.
'''
cmdid = subprocess.Popen(args, stdin = open(os.devnull, 'r'),
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT)
result, err = cmdid.communicate() # err will always be None
status = cmdid.wait()
return (status, result)
def hexdump(s, bytes_per_word = 2, words_per_line = 8): :param list args: command arguments
:return: command status and stdout
:rtype: tuple[int, str]
"""
cmdid = subprocess.Popen(args, stdin=open(os.devnull, "r"), stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
# err will always be None
result, err = cmdid.communicate()
status = cmdid.wait()
return status, result
def hexdump(s, bytes_per_word=2, words_per_line=8):
"""
Hex dump of a string.
:param str s: string to hex dump
:param bytes_per_word: number of bytes per word
:param words_per_line: number of words per line
:return: hex dump of string
"""
dump = "" dump = ""
count = 0 count = 0
bytes = bytes_per_word * words_per_line bytes = bytes_per_word * words_per_line
@ -132,20 +244,34 @@ def hexdump(s, bytes_per_word = 2, words_per_line = 8):
count += len(line) count += len(line)
return dump[:-1] return dump[:-1]
def filemunge(pathname, header, text): def filemunge(pathname, header, text):
''' Insert text at the end of a file, surrounded by header comments. """
''' Insert text at the end of a file, surrounded by header comments.
filedemunge(pathname, header) # prevent duplicates
f = open(pathname, 'a') :param str pathname: file path to add text to
:param str header: header text comments
:param str text: text to append to file
:return: nothing
"""
# prevent duplicates
filedemunge(pathname, header)
f = open(pathname, "a")
f.write("# BEGIN %s\n" % header) f.write("# BEGIN %s\n" % header)
f.write(text) f.write(text)
f.write("# END %s\n" % header) f.write("# END %s\n" % header)
f.close() f.close()
def filedemunge(pathname, header): def filedemunge(pathname, header):
''' Remove text that was inserted in a file surrounded by header comments. """
''' Remove text that was inserted in a file surrounded by header comments.
f = open(pathname, 'r')
:param str pathname: file path to open for removing a header
:param str header: header text to target for removal
:return: nothing
"""
f = open(pathname, "r")
lines = f.readlines() lines = f.readlines()
f.close() f.close()
start = None start = None
@ -157,66 +283,101 @@ def filedemunge(pathname, header):
end = i + 1 end = i + 1
if start is None or end is None: if start is None or end is None:
return return
f = open(pathname, 'w') f = open(pathname, "w")
lines = lines[:start] + lines[end:] lines = lines[:start] + lines[end:]
f.write("".join(lines)) f.write("".join(lines))
f.close() f.close()
def expandcorepath(pathname, session=None, node=None): def expandcorepath(pathname, session=None, node=None):
''' Expand a file path given session information. """
''' Expand a file path given session information.
:param str pathname: file path to expand
:param core.session.Session session: core session object to expand path with
:param core.netns.LxcNode node: node to expand path with
:return: expanded path
:rtype: str
"""
if session is not None: if session is not None:
pathname = pathname.replace('~', "/home/%s" % session.user) pathname = pathname.replace("~", "/home/%s" % session.user)
pathname = pathname.replace('%SESSION%', str(session.sessionid)) pathname = pathname.replace("%SESSION%", str(session.session_id))
pathname = pathname.replace('%SESSION_DIR%', session.sessiondir) pathname = pathname.replace("%SESSION_DIR%", session.session_dir)
pathname = pathname.replace('%SESSION_USER%', session.user) pathname = pathname.replace("%SESSION_USER%", session.user)
if node is not None: if node is not None:
pathname = pathname.replace('%NODE%', str(node.objid)) pathname = pathname.replace("%NODE%", str(node.objid))
pathname = pathname.replace('%NODENAME%', node.name) pathname = pathname.replace("%NODENAME%", node.name)
return pathname return pathname
def sysctldevname(devname): def sysctldevname(devname):
''' Translate a device name to the name used with sysctl. """
''' Translate a device name to the name used with sysctl.
:param str devname: device name to translate
:return: translated device name
:rtype: str
"""
if devname is None: if devname is None:
return None return None
return devname.replace(".", "/") return devname.replace(".", "/")
def daemonize(rootdir = "/", umask = 0, close_fds = False, dontclose = (),
stdin = os.devnull, stdout = os.devnull, stderr = os.devnull, def daemonize(rootdir="/", umask=0, close_fds=False, dontclose=(),
stdoutmode = 0644, stderrmode = 0644, pidfilename = None, stdin=os.devnull, stdout=os.devnull, stderr=os.devnull,
defaultmaxfd = 1024): stdoutmode=0644, stderrmode=0644, pidfilename=None,
''' Run the background process as a daemon. defaultmaxfd=1024):
''' """
Run the background process as a daemon.
:param str rootdir: root directory for daemon
:param int umask: umask for daemon
:param bool close_fds: flag to close file descriptors
:param dontclose: dont close options
:param stdin: stdin for daemon
:param stdout: stdout for daemon
:param stderr: stderr for daemon
:param int stdoutmode: stdout mode
:param int stderrmode: stderr mode
:param str pidfilename: pid file name
:param int defaultmaxfd: default max file descriptors
:return: nothing
"""
if not hasattr(dontclose, "__contains__"): if not hasattr(dontclose, "__contains__"):
if not isinstance(dontclose, int): if not isinstance(dontclose, int):
raise TypeError, "dontclose must be an integer" raise TypeError("dontclose must be an integer")
dontclose = (int(dontclose),) dontclose = (int(dontclose),)
else: else:
for fd in dontclose: for fd in dontclose:
if not isinstance(fd, int): if not isinstance(fd, int):
raise TypeError, "dontclose must contain only integers" raise TypeError("dontclose must contain only integers")
# redirect stdin # redirect stdin
if stdin: if stdin:
fd = os.open(stdin, os.O_RDONLY) fd = os.open(stdin, os.O_RDONLY)
os.dup2(fd, 0) os.dup2(fd, 0)
os.close(fd) os.close(fd)
# redirect stdout # redirect stdout
if stdout: if stdout:
fd = os.open(stdout, os.O_WRONLY | os.O_CREAT | os.O_APPEND, fd = os.open(stdout, os.O_WRONLY | os.O_CREAT | os.O_APPEND,
stdoutmode) stdoutmode)
os.dup2(fd, 1) os.dup2(fd, 1)
if (stdout == stderr): if stdout == stderr:
os.dup2(1, 2) os.dup2(1, 2)
os.close(fd) os.close(fd)
# redirect stderr # redirect stderr
if stderr and (stderr != stdout): if stderr and (stderr != stdout):
fd = os.open(stderr, os.O_WRONLY | os.O_CREAT | os.O_APPEND, fd = os.open(stderr, os.O_WRONLY | os.O_CREAT | os.O_APPEND,
stderrmode) stderrmode)
os.dup2(fd, 2) os.dup2(fd, 2)
os.close(fd) os.close(fd)
if os.fork(): if os.fork():
os._exit(0) # parent exits # parent exits
os._exit(0)
os.setsid() os.setsid()
pid = os.fork() pid = os.fork()
if pid: if pid:
@ -225,11 +386,14 @@ def daemonize(rootdir = "/", umask = 0, close_fds = False, dontclose = (),
f = open(pidfilename, "w") f = open(pidfilename, "w")
f.write("%s\n" % pid) f.write("%s\n" % pid)
f.close() f.close()
except: except IOError:
pass logger.exception("error writing to file: %s", pidfilename)
os._exit(0) # parent exits # parent exits
os._exit(0)
if rootdir: if rootdir:
os.chdir(rootdir) os.chdir(rootdir)
os.umask(umask) os.umask(umask)
if close_fds: if close_fds:
try: try:
@ -238,38 +402,49 @@ def daemonize(rootdir = "/", umask = 0, close_fds = False, dontclose = (),
raise ValueError raise ValueError
except: except:
maxfd = defaultmaxfd maxfd = defaultmaxfd
for fd in xrange(3, maxfd): for fd in xrange(3, maxfd):
if fd in dontclose: if fd in dontclose:
continue continue
try: try:
os.close(fd) os.close(fd)
except: except IOError:
pass logger.exception("error closing file descriptor")
def readfileintodict(filename, d): def readfileintodict(filename, d):
''' Read key=value pairs from a file, into a dict. """
Skip comments; strip newline characters and spacing. Read key=value pairs from a file, into a dict. Skip comments; strip newline characters and spacing.
'''
with open(filename, 'r') as f: :param str filename: file to read into a dictionary
:param dict d: dictionary to read file into
:return: nothing
"""
with open(filename, "r") as f:
lines = f.readlines() lines = f.readlines()
for l in lines: for l in lines:
if l[:1] == '#': if l[:1] == "#":
continue continue
try: try:
key, value = l.split('=', 1) key, value = l.split("=", 1)
d[key] = value.strip() d[key] = value.strip()
except ValueError: except ValueError:
pass logger.exception("error reading file to dict: %s", filename)
def checkforkernelmodule(name): def checkforkernelmodule(name):
''' Return a string if a Linux kernel module is loaded, None otherwise. """
Return a string if a Linux kernel module is loaded, None otherwise.
The string is the line from /proc/modules containing the module name, The string is the line from /proc/modules containing the module name,
memory size (bytes), number of loaded instances, dependencies, state, memory size (bytes), number of loaded instances, dependencies, state,
and kernel memory offset. and kernel memory offset.
'''
with open('/proc/modules', 'r') as f: :param str name: name of kernel module to check for
:return: kernel module line, None otherwise
:rtype: str
"""
with open("/proc/modules", "r") as f:
for line in f: for line in f:
if line.startswith(name + ' '): if line.startswith(name + " "):
return line.rstrip() return line.rstrip()
return None return None

View file

@ -66,6 +66,7 @@ import math
__all__ = ['to_latlon', 'from_latlon'] __all__ = ['to_latlon', 'from_latlon']
class OutOfRangeError(ValueError): class OutOfRangeError(ValueError):
pass pass
@ -139,7 +140,7 @@ def to_latlon(easting, northing, zone_number, zone_letter):
n = R / ep_sin_sqrt n = R / ep_sin_sqrt
r = (1 - E) / ep_sin r = (1 - E) / ep_sin
c = _E * p_cos**2 c = _E * p_cos ** 2
c2 = c * c c2 = c * c
d = x / (n * K0) d = x / (n * K0)
@ -184,8 +185,8 @@ def from_latlon(latitude, longitude):
zone_letter = latitude_to_zone_letter(latitude) zone_letter = latitude_to_zone_letter(latitude)
n = R / math.sqrt(1 - E * lat_sin**2) n = R / math.sqrt(1 - E * lat_sin ** 2)
c = E_P2 * lat_cos**2 c = E_P2 * lat_cos ** 2
a = lat_cos * (lon_rad - central_lon_rad) a = lat_cos * (lon_rad - central_lon_rad)
a2 = a * a a2 = a * a
@ -204,7 +205,7 @@ def from_latlon(latitude, longitude):
a5 / 120 * (5 - 18 * lat_tan2 + lat_tan4 + 72 * c - 58 * E_P2)) + 500000 a5 / 120 * (5 - 18 * lat_tan2 + lat_tan4 + 72 * c - 58 * E_P2)) + 500000
northing = K0 * (m + n * lat_tan * (a2 / 2 + northing = K0 * (m + n * lat_tan * (a2 / 2 +
a4 / 24 * (5 - lat_tan2 + 9 * c + 4 * c**2) + a4 / 24 * (5 - lat_tan2 + 9 * c + 4 * c ** 2) +
a6 / 720 * (61 - 58 * lat_tan2 + lat_tan4 + 600 * c - 330 * E_P2))) a6 / 720 * (61 - 58 * lat_tan2 + lat_tan4 + 600 * c - 330 * E_P2)))
if latitude < 0: if latitude < 0:
@ -252,8 +253,7 @@ def haversine(lon1, lat1, lon2, lat2):
# haversine formula # haversine formula
dlon = lon2 - lon1 dlon = lon2 - lon1
dlat = lat2 - lat1 dlat = lat2 - lat1
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2 a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
c = 2 * math.asin(math.sqrt(a)) c = 2 * math.asin(math.sqrt(a))
m = 6367000 * c m = 6367000 * c
return m return m

View file

@ -1,205 +0,0 @@
import socket
import subprocess
import os
import xmlutils
from core.netns import nodes
from core.misc import ipaddr
from core import constants
class CoreDeploymentWriter(object):
def __init__(self, dom, root, session):
self.dom = dom
self.root = root
self.session = session
self.hostname = socket.gethostname()
if self.session.emane.version < self.session.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
ifaceName = split[1]
addr = split[3]
if not addr.startswith('127.'):
iface_list.append(ifaceName)
return iface_list
else:
# TODO: handle other hosts
raise NotImplementedError
@staticmethod
def find_device(scenario, name):
tagName = ('device', 'host', 'router')
for d in xmlutils.iterDescendantsWithAttribute(scenario, tagName,
'name', name):
return d
return None
@staticmethod
def find_interface(device, name):
for i in xmlutils.iterDescendantsWithAttribute(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.baseEle.appendChild(testbed)
nodelist = []
for obj in self.session.objs():
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, tagName):
el = self.dom.createElement(tagName)
parent.appendChild(el)
return el
def add_child_element_with_nameattr(self, parent, tagName,
name, setid = True):
el = self.add_child_element(parent, tagName)
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:
ifaceName = iface_names[i]
else:
ifaceName = None
self.add_address(el, 'IPv4', addr, ifaceName)
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.baseEle, obj.name)
if device is None:
self.session.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 ipaddr.isIPv4Address(addr):
addr_type = 'IPv4'
elif ipaddr.isIPv6Address(addr):
addr_type = 'IPv6'
else:
raise NotImplementedError
self.add_address(el, addr_type, address, netif.name)
if isinstance(netif.net, nodes.EmaneNode):
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 self.session.emane.version < self.session.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

View file

@ -1,46 +0,0 @@
# CORE
# Copyright (c) 2014 The Boeing Company.
# See the LICENSE file included in this distribution.
from xml.dom.minidom import parse
from xmlutils import getFirstChildByTagName
from xmlparser0 import CoreDocumentParser0
from xmlparser1 import CoreDocumentParser1
class CoreVersionParser(object):
DEFAULT_SCENARIO_VERSION = '1.0'
'''\
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.)
'''
def __init__(self, filename, options={}):
if 'dom' in options:
self.dom = options['dom']
else:
self.dom = parse(filename)
scenario = getFirstChildByTagName(self.dom, 'scenario')
if scenario:
version = scenario.getAttribute('version')
if not version:
version = self.DEFAULT_SCENARIO_VERSION
self.version = version
elif getFirstChildByTagName(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

View file

@ -1,420 +0,0 @@
#
# CORE
# Copyright (c)2011-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
from core.netns import nodes
from xml.dom.minidom import parse
from xmlutils import *
class CoreDocumentParser0(object):
def __init__(self, session, filename, options):
self.session = session
self.verbose = self.session.getcfgitembool('verbose', False)
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 = getoneelement(self.dom, "NetworkPlan")
if self.np is None:
raise ValueError, "missing NetworkPlan!"
self.mp = getoneelement(self.dom, "MotionPlan")
self.sp = getoneelement(self.dom, "ServicePlan")
self.meta = getoneelement(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 warn(self, msg):
if self.session:
warnstr = "XML parsing '%s':" % (self.filename)
self.session.warn("%s %s" % (warnstr, msg))
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 = xmltypetonodeclass(self.session, type)
if not nodecls:
self.warn("skipping unknown network node '%s' type '%s'" % \
(name, type))
continue
n = self.session.addobj(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)
getparamssetattrs(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.objbyname(netid)
except KeyError:
n.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 = nodes.RJ45Node
else:
nodecls = self.nodecls
n = self.session.addobj(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
getparamssetattrs(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.objbyname(netid)
except KeyError:
n.warn("skipping node %s interface %s: unknown net %s" % \
(n.name, name, netid))
return
for addr in ifc.getElementsByTagName("address"):
addrstr = gettextchild(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 = gettextelementstolist(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:
self.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 = getoneelement(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 = getoneelement(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
self.session.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.objbyname(name)
if n is None:
self.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.obj(objid)
self.session.services.addservicestonode(node=n, nodetype=n.type,
services_str=svclists[objid],
verbose=self.verbose)
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 = self.session.services.getservicebyname(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 = gettextchild(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 = gettextchild(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 = gettextchild(hook)
if data is None:
data = "" # allow for empty file
type = "hook:%s" % state
self.session.sethook(type, filename=filename,
srcname=None, data=data)
def parsemeta(self):
opt = getoneelement(self.meta, "SessionOptions")
if opt:
for param in opt.getElementsByTagName("param"):
k = str(param.getAttribute("name"))
v = str(param.getAttribute("value"))
if v == '':
v = gettextchild(param) # allow attribute/text for newlines
setattr(self.session.options, k, v)
hooks = getoneelement(self.meta, "Hooks")
if hooks:
self.parsehooks(hooks)
meta = getoneelement(self.meta, "MetaData")
if meta:
for param in meta.getElementsByTagName("param"):
k = str(param.getAttribute("name"))
v = str(param.getAttribute("value"))
if v == '':
v = gettextchild(param)
self.session.metadata.additem(k, v)

View file

@ -1,942 +0,0 @@
#
# CORE
# Copyright (c) 2015 the Boeing Company.
# See the LICENSE file included in this distribution.
#
import sys
import random
from core.netns import nodes
from core import constants
from core.misc.ipaddr import MacAddr
from xml.dom.minidom import parse
from xmlutils import *
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):
self.session = session
self.verbose = self.session.getcfgitembool('verbose', False)
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()
def info(self, msg):
s = 'XML parsing \'%s\': %s' % (self.filename, msg)
if self.session:
self.session.info(s)
else:
sys.stdout.write(s + '\n')
def warn(self, msg):
s = 'WARNING XML parsing \'%s\': %s' % (self.filename, msg)
if self.session:
self.session.warn(s)
else:
sys.stderr.write(s + '\n')
@staticmethod
def get_scenario(dom):
scenario = getFirstChildByTagName(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 iterChildrenWithAttribute(element, 'member',
'type', 'interface'):
if_id = getChildTextTrim(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 == 'ethernet':
return nodes.PtpNet
elif network_type == 'satcom':
return nodes.PtpNet
elif network_type == 'wireless':
channel = getFirstChildByTagName(network, 'channel')
if channel:
# use an explicit CORE type if it exists
coretype = getFirstChildTextTrimWithAttribute(channel, 'type',
'domain', 'CORE')
if coretype:
if coretype == 'basic_range':
return nodes.WlanNode
elif coretype.startswith('emane'):
return nodes.EmaneNode
else:
self.warn('unknown network type: \'%s\'' % coretype)
return xmltypetonodeclass(self.session, coretype)
return nodes.WlanNode
self.warn('unknown network type: \'%s\'' % network_type)
return None
def create_core_object(self, objcls, objid, objname, element, node_type):
obj = self.session.addobj(cls = objcls, objid = objid,
name = objname, start = self.start)
if self.verbose:
self.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.obj(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:
self.warn('unknown network: %s' % network.toxml('utf-8'))
assert False # XXX for testing
return
model_name = getFirstChildTextTrimWithAttribute(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 isinstance(dev2, nodes.RJ45Node):
# 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 iterChildrenWithName(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(getChildTextTrim(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 = getChildTextTrim(element)
link_params = self.parse_parameter_children(channel)
mobility = getFirstChildByTagName(channel, 'CORE:mobility')
if mobility:
mobility_model_name = \
getFirstChildTextTrimByTagName(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 = getFirstChildTextTrimByTagName(network, 'type')
if not net_type:
msg = 'no network type found for network: \'%s\'' % \
network.toxml('utf-8')
self.warn(msg)
assert False # XXX for testing
return
net_cls = self.network_class(network, net_type)
objid, net_name = self.get_common_attributes(network)
if self.verbose:
self.info('parsing network: %s %s' % (net_name, objid))
if objid in self.session._objs:
return
n = self.create_core_object(net_cls, objid, net_name,
network, None)
# handle channel parameters
for channel in iterChildrenWithName(network, 'channel'):
self.parse_network_channel(channel)
def parse_networks(self):
'''\
Parse all 'network' elements.
'''
for network in iterDescendantsWithName(self.scenario, 'network'):
self.parse_network(network)
def parse_addresses(self, interface):
mac = []
ipv4 = []
ipv6= []
hostname = []
for address in iterChildrenWithName(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')
self.warn(msg)
assert False # XXX for testing
continue
addr_text = getChildTextTrim(address)
if not addr_text:
msg = 'no text found for address ' \
'in interface: \'%s\'' % interface.toxml('utf-8')
self.warn(msg)
assert False # XXX for testing
continue
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'))
self.warn(msg)
assert False # XXX for testing
continue
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)
self.warn(msg)
assert False # XXX for testing
return
mac, ipv4, ipv6, hostname = self.parse_addresses(interface)
if mac:
hwaddr = MacAddr.fromstring(mac[0])
else:
hwaddr = None
ifindex = node.newnetif(network, addrlist = ipv4 + ipv6,
hwaddr = hwaddr, ifindex = None,
ifname = if_name)
# TODO: 'hostname' addresses are unused
if self.verbose:
msg = 'node \'%s\' interface \'%s\' connected ' \
'to network \'%s\'' % (node.name, if_name, network.name)
self.info(msg)
# set link parameters for wired links
if isinstance(network,
(nodes.HubNode, nodes.PtpNet, nodes.SwitchNode)):
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, tagName, 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 iterChildren(node, Node.ELEMENT_NODE):
if child.tagName == tagName 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 getChildTextTrim(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 = getFirstChildTextTrimWithAttribute(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 = getFirstChildTextTrimWithAttribute(interface, 'member',
'type', 'channel')
if not network_id or not channel_id:
return None
network = getFirstChildWithAttribute(self.scenario, 'network',
'id', network_id)
if not network:
return None
channel = getFirstChildWithAttribute(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:
self.warn('limiting negative x position of \'%s\' to zero: %s' %
(obj.name, x))
x = 0.0
if y < 0.0:
self.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:
self.warn('limiting negative x position of \'%s\' to zero: %s' %
(obj.name, x))
x = 0.0
if y < 0.0:
self.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:
self.warn('limiting negative x position of \'%s\' to zero: %s' %
(obj.name, x))
x = 0.0
if y < 0.0:
self.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')
self.warn(msg)
assert False # XXX for testing
return False
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:
self.warn("skipping unknown point type: '%s'" % point_type)
assert False # XXX for testing
return False
if self.verbose:
msg = 'set position of %s from point element: \'%s\'' % \
(obj.name, point.toxml('utf-8'))
self.info(msg)
return True
def parse_device_service(self, service, node):
name = service.getAttribute('name')
session_service = self.session.services.getservicebyname(name)
if not session_service:
assert False # XXX for testing
return None
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 iterChildrenWithName(service, 'directory'):
dirname = directory.getAttribute('name')
dirs.append(str(dirname))
if dirs:
values.append("dirs=%s" % dirs)
startup = []
shutdown = []
validate = []
for command in iterChildrenWithName(service, 'command'):
command_type = command.getAttribute('type')
command_text = getChildTextTrim(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 iterChildrenWithName(service, 'file'):
filename = f.getAttribute('name')
if not filename:
continue;
filenames.append(filename)
data = getChildTextTrim(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 iterChildrenWithName(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 = getFirstChildByTagName(device, 'CORE:services')
if services:
services_str = self.parse_device_services(services, node)
if self.verbose:
self.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,
verbose = self.verbose)
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 iterChildrenWithName(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 = getFirstChildTextTrimWithAttribute(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 iterDescendantsWithName(self.scenario,
self.device_types):
interface = getFirstChildWithAttribute(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)
if self.verbose:
self.info('parsing layer-2 device: %s %s' % (device_name, objid))
try:
return self.session.obj(objid)
except KeyError:
pass
device_type = self.device_type(device)
if device_type == 'hub':
device_class = nodes.HubNode
elif device_type == 'switch':
device_class = nodes.SwitchNode
else:
self.warn('unknown layer-2 device type: \'%s\'' % device_type)
assert False # XXX for testing
return None
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)
if self.verbose:
self.info('parsing layer-3 device: %s %s' % (device_name, objid))
try:
return self.session.obj(objid)
except KeyError:
pass
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 iterChildrenWithName(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 iterDescendantsWithName(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 iterDescendantsWithName(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 = getFirstChildByTagName(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 = getFirstChildTextTrimByTagName(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 = getFirstChildByTagName(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 = getFirstChildByTagName(session_config, 'hooks')
if not hooks:
return
for hook in iterChildrenWithName(hooks, 'hook'):
filename = hook.getAttribute('name')
state = hook.getAttribute('state')
data = getChildTextTrim(hook)
if data is None:
data = '' # allow for empty file
hook_type = "hook:%s" % state
self.session.sethook(hook_type, filename = str(filename),
srcname = None, data = str(data))
def parse_session_metadata(self, session_config):
metadata = getFirstChildByTagName(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.additem(str(name), str(value))
def parse_session_config(self):
session_config = \
getFirstChildByTagName(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'],
# 'prouter': ['zebra', 'OSPFv2', 'OSPFv3', 'IPForward'],
# 'xen': ['zebra', 'OSPFv2', 'OSPFv3', 'IPForward'],
}
default_services = \
getFirstChildByTagName(self.scenario, 'CORE:defaultservices')
if not default_services:
return
for device in iterChildrenWithName(default_services, 'device'):
device_type = device.getAttribute('type')
if not device_type:
self.warn('parse_default_services: no type attribute ' \
'found for device')
continue
services = []
for service in iterChildrenWithName(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
if self.verbose:
self.info('default services for node type \'%s\' ' \
'set to: %s' % (t, s))

View file

@ -1,34 +0,0 @@
#
# CORE
# Copyright (c)2011-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
Helpers for loading and saving XML files. savesessionxml(session, filename) is
the main public interface here.
'''
import os.path
from core.netns import nodes
from xmlparser import core_document_parser
from xmlwriter import core_document_writer
def opensessionxml(session, filename, start=False, nodecls=nodes.CoreNode):
''' Import a session from the EmulationScript XML format.
'''
options = {'start': start, 'nodecls': nodecls}
doc = core_document_parser(session, filename, options)
if start:
session.name = os.path.basename(filename)
session.filename = filename
session.node_count = str(session.getnodecount())
session.instantiate()
def savesessionxml(session, filename, version):
''' Export a session to the EmulationScript XML format.
'''
doc = core_document_writer(session, version)
doc.writexml(filename)

View file

@ -1,303 +0,0 @@
#
# CORE
# Copyright (c)2011-2013 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
from core.netns import nodes
from xml.dom.minidom import Node
def addelementsfromlist(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 addtextelementsfromlist(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 addtextelementsfromtuples(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 gettextelementstolist(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 addparamtoparent(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 addtextparamtoparent(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 addparamlisttoparent(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 getoneelement(dom, name):
e = dom.getElementsByTagName(name)
if len(e) == 0:
return None
return e[0]
def iterDescendants(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 iterMatchingDescendants(dom, matchFunction, 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 iterDescendants(dom, max_depth):
if matchFunction(d):
yield d
def iterDescendantsWithName(dom, tagName, 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(tagName, basestring):
tagName = (tagName,)
def match(d):
return d.tagName in tagName
return iterMatchingDescendants(dom, match, max_depth)
def iterDescendantsWithAttribute(dom, tagName, attrName, attrValue,
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(tagName, basestring):
tagName = (tagName,)
def match(d):
return d.tagName in tagName and \
d.getAttribute(attrName) == attrValue
return iterMatchingDescendants(dom, match, max_depth)
def iterChildren(dom, nodeType):
'''\
Iterate over all child elements of the given type.
'''
for child in dom.childNodes:
if child.nodeType == nodeType:
yield child
def gettextchild(dom):
'''\
Return the text node of the given element.
'''
for child in iterChildren(dom, Node.TEXT_NODE):
return str(child.nodeValue)
return None
def getChildTextTrim(dom):
text = gettextchild(dom)
if text:
text = text.strip()
return text
def getparamssetattrs(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 xmltypetonodeclass(session, type):
''' Helper to convert from a type string to a class name in nodes.*.
'''
if hasattr(nodes, type):
return eval("nodes.%s" % type)
else:
return None
def iterChildrenWithName(dom, tagName):
return iterDescendantsWithName(dom, tagName, 1)
def iterChildrenWithAttribute(dom, tagName, attrName, attrValue):
return iterDescendantsWithAttribute(dom, tagName, attrName, attrValue, 1)
def getFirstChildByTagName(dom, tagName):
'''\
Return the first child element whose name is contained in tagName
(or is named tagName if tagName is a string).
'''
for child in iterChildrenWithName(dom, tagName):
return child
return None
def getFirstChildTextByTagName(dom, tagName):
'''\
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 = getFirstChildByTagName(dom, tagName)
if child:
return gettextchild(child)
return None
def getFirstChildTextTrimByTagName(dom, tagName):
text = getFirstChildTextByTagName(dom, tagName)
if text:
text = text.strip()
return text
def getFirstChildWithAttribute(dom, tagName, attrName, attrValue):
'''\
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 \
iterChildrenWithAttribute(dom, tagName, attrName, attrValue):
return child
return None
def getFirstChildTextWithAttribute(dom, tagName, attrName, attrValue):
'''\
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 = getFirstChildWithAttribute(dom, tagName, attrName, attrValue)
if child:
return gettextchild(child)
return None
def getFirstChildTextTrimWithAttribute(dom, tagName, attrName, attrValue):
text = getFirstChildTextWithAttribute(dom, tagName, attrName, attrValue)
if text:
text = text.strip()
return text

View file

@ -1,15 +0,0 @@
# CORE
# Copyright (c) 2015 The Boeing Company.
# See the LICENSE file included in this distribution.
from xmlwriter0 import CoreDocumentWriter0
from 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

View file

@ -1,377 +0,0 @@
#
# CORE
# Copyright (c)2011-2013 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
import os
import pwd
from core.netns import nodes
from core.api import coreapi
from xml.dom.minidom import Document
from xmlutils import *
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):
self.session.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._objslock:
for net in self.session.objs():
if not isinstance(net, nodes.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 == coreapi.CORE_TLV_REG_MOBILITY:
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._objslock:
for node in self.session.objs():
if not isinstance(node, nodes.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)
addparamtoparent(self, n, "icon", node.icon)
addparamtoparent(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))
addelementsfromlist(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)
addtextelementsfromlist(self, s, svc._startup, "Command",
(("type","start"),))
addtextelementsfromlist(self, s, svc._shutdown, "Command",
(("type","stop"),))
addtextelementsfromlist(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]):
addtextparamtoparent(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():
addtextparamtoparent(self, meta, k, v)
#addparamtoparent(self, meta, k, v)

View file

@ -1,989 +0,0 @@
#
# CORE
# Copyright (c)2011-2015 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# Created on Dec 18, 2014
#
# @author: santiago
#
import os
import pwd
import collections
from core.netns import nodes
from core.api import coreapi
from core.misc.ipaddr import *
from xml.dom.minidom import Document
from xmlutils import *
from xmldeployment import CoreDeploymentWriter
def enum(**enums):
return type('Enum', (), enums)
class Attrib(object):
''' NMF scenario plan attribute constants
'''
NetType = enum(WIRELESS = 'wireless', ETHERNET = 'ethernet',
PTP_WIRED = 'point-to-point-wired',
PTP_WIRELESS = 'point-to-point-wireless')
MembType = enum(INTERFACE = 'interface', CHANNEL = 'channel',
SWITCH = 'switch', HUB = 'hub', TUNNEL = 'tunnel',
NETWORK = "network")
DevType = enum(HOST = 'host', ROUTER = 'router', SWITCH = 'switch',
HUB = 'hub')
''' Node types in CORE
'''
NodeType = enum(ROUTER = 'router', HOST = 'host', MDR = 'mdr',
PC = 'PC', RJ45 = 'rj45', SWITCH = 'lanswitch',
HUB = 'hub')
Alias = enum(ID = "COREID")
''' A link endpoint in CORE
net: the network that the endpoint belongs to
netif: the network interface at this end
id: the identifier for the endpoint
l2devport: if the other end is a layer 2 device, this is the assigned port in that device
params: link/interface parameters
'''
Endpoint = collections.namedtuple('Endpoint',
['net', 'netif', 'type', 'id', 'l2devport', 'params'])
class CoreDocumentWriter1(Document):
''' Utility class for writing a CoreSession to XML in the NMF scenPlan schema. 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)
session.info('Exporting to NMF XML version 1.0')
with session._objslock:
self.scenarioPlan = ScenarioPlan(self, session)
if session.getstate() == coreapi.CORE_EVENT_RUNTIME_STATE:
deployment = CoreDeploymentWriter(self, self.scenarioPlan,
session)
deployment.add_deployment()
self.scenarioPlan.setAttribute('deployed', 'true')
def writexml(self, filename):
''' Commit to file
'''
self.scenarioPlan.coreSession.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.scenarioPlan.coreSession.user is not None:
uid = pwd.getpwnam(self.scenarioPlan.coreSession.user).pw_uid
gid = os.stat(self.scenarioPlan.coreSession.sessiondir).st_gid
os.chown(filename, uid, gid)
class XmlElement(object):
''' The base class for all XML elements in the scenario plan. Includes
convenience functions.
'''
def __init__(self, document, parent, elementType):
self.document = document
self.parent = parent
self.baseEle = document.createElement("%s" % elementType)
if self.parent is not None:
self.parent.appendChild(self.baseEle)
def createElement(self, elementTag):
return self.document.createElement(elementTag)
def getTagName(self):
return self.baseEle.tagName
def createTextNode(self, nodeTag):
return self.document.createTextNode(nodeTag)
def appendChild(self, child):
if isinstance(child, XmlElement):
self.baseEle.appendChild(child.baseEle)
else:
self.baseEle.appendChild(child)
@staticmethod
def add_parameter(doc, parent, key, value):
if key and value:
parm = doc.createElement("parameter")
parm.setAttribute("name", str(key))
parm.appendChild(doc.createTextNode(str(value)))
parent.appendChild(parm)
def addParameter(self, key, value):
'''
Add a parameter to the xml element
'''
self.add_parameter(self.document, self, key, value)
def setAttribute(self, name, val):
self.baseEle.setAttribute(name, val)
def getAttribute(self, name):
return self.baseEle.getAttribute(name)
class NamedXmlElement(XmlElement):
''' The base class for all "named" xml elements. Named elements are
xml elements in the scenario plan that have an id and a name attribute.
'''
def __init__(self, scenPlan, parent, elementType, elementName):
XmlElement.__init__(self, scenPlan.document, parent, elementType)
self.scenPlan = scenPlan
self.coreSession = scenPlan.coreSession
elementPath = ''
self.id=None
if self.parent is not None and isinstance(self.parent, XmlElement) and self.parent.getTagName() != "scenario":
elementPath="%s/" % self.parent.getAttribute("id")
self.id = "%s%s" % (elementPath,elementName)
self.setAttribute("name", elementName)
self.setAttribute("id", self.id)
def addPoint(self, coreObj):
''' Add position to an object
'''
(x,y,z) = coreObj.position.get()
if x is None or y is None:
return
lat, lon, alt = self.coreSession.location.getgeo(x, y, z)
pt = self.createElement("point")
pt.setAttribute("type", "gps")
pt.setAttribute("lat", "%s" % lat)
pt.setAttribute("lon", "%s" % lon)
if z:
pt.setAttribute("z", "%s" % alt)
self.appendChild(pt)
def createAlias(self, domain, valueStr):
''' Create an alias element for CORE specific information
'''
a = self.createElement("alias")
a.setAttribute("domain", "%s" % domain)
a.appendChild(self.createTextNode(valueStr))
return a
class ScenarioPlan(XmlElement):
''' Container class for ScenarioPlan.
'''
def __init__(self, document, session):
XmlElement.__init__(self, document, parent=document, elementType='scenario')
self.coreSession = session
self.setAttribute('version', '1.0')
self.setAttribute("name", "%s" % session.name)
self.setAttribute('xmlns', 'nmfPlan')
self.setAttribute('xmlns:CORE', 'coreSpecific')
self.setAttribute('compiled', 'true')
self.allChannelMembers = dict()
self.lastNetIdx = 0
self.addNetworks()
self.addDevices()
# XXX Do we need these?
#self.session.emane.setup() # not during runtime?
#self.addorigin()
self.addDefaultServices()
self.addSessionConfiguration()
def addNetworks(self):
''' Add networks in the session to the scenPlan.
'''
for net in self.coreSession.objs():
if not isinstance(net, nodes.PyCoreNet):
continue
if isinstance(net, nodes.CtrlNet):
continue
# Do not add switches and hubs that belong to another network
if isinstance(net, (nodes.SwitchNode, nodes.HubNode)):
if inOtherNetwork(net):
continue
try:
NetworkElement(self, self, net)
except:
if hasattr(net, "name") and net.name:
self.coreSession.warn('Unsupported net: %s' % net.name)
else:
self.coreSession.warn('Unsupported net: %s' % net.__class__.__name__)
def addDevices(self):
''' Add device elements to the scenario plan.
'''
for node in self.coreSession.objs():
if not isinstance(node, (nodes.PyCoreNode)):
continue
try:
DeviceElement(self, self, node)
except:
if hasattr(node, "name") and node.name:
self.coreSession.warn('Unsupported device: %s' % node.name)
else:
self.coreSession.warn('Unsupported device: %s' % node.__class__.__name__)
def addDefaultServices(self):
''' Add default services and node types to the ServicePlan.
'''
defaultservices = self.createElement("CORE:defaultservices")
for type in self.coreSession.services.defaultservices:
defaults = self.coreSession.services.getdefaultservices(type)
spn = self.createElement("device")
spn.setAttribute("type", type)
defaultservices.appendChild(spn)
for svc in defaults:
s = self.createElement("service")
spn.appendChild(s)
s.setAttribute("name", str(svc._name))
if defaultservices.hasChildNodes():
self.appendChild(defaultservices)
def addSessionConfiguration(self):
''' Add CORE-specific session configuration XML elements.
'''
config = self.createElement("CORE:sessionconfig")
# origin: geolocation of cartesian coordinate 0,0,0
refgeo = self.coreSession.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 have_origin:
if self.coreSession.location.refscale != 1.0: # 100 pixels = refscale m
origin.setAttribute("scale100", str(self.coreSession.location.refscale))
if self.coreSession.location.refxyz != (0.0, 0.0, 0.0):
pt = self.createElement("point")
origin.appendChild(pt)
x,y,z = self.coreSession.location.refxyz
coordstxt = "%s,%s" % (x,y)
if z:
coordstxt += ",%s" % z
coords = self.createTextNode(coordstxt)
pt.appendChild(coords)
config.appendChild(origin)
# options
options = self.createElement("options")
defaults = self.coreSession.options.getdefaultvalues()
for i, (k, v) in enumerate(self.coreSession.options.getkeyvaluelist()):
if str(v) != str(defaults[i]):
XmlElement.add_parameter(self.document, options, k, v)
if options.hasChildNodes():
config.appendChild(options)
# hook scripts
hooks = self.createElement("hooks")
for state in sorted(self.coreSession._hooks.keys()):
for (filename, data) in self.coreSession._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():
config.appendChild(hooks)
# metadata
meta = self.createElement("metadata")
for (k, v) in self.coreSession.metadata.items():
XmlElement.add_parameter(self.document, meta, k, v)
if meta.hasChildNodes():
config.appendChild(meta)
if config.hasChildNodes():
self.appendChild(config)
class NetworkElement(NamedXmlElement):
def __init__(self, scenPlan, parent, netObj):
''' Add one PyCoreNet object as one network XML element.
'''
elementName = self.getNetworkName(scenPlan, netObj)
NamedXmlElement.__init__(self, scenPlan, parent, "network", elementName)
self.scenPlan = scenPlan
self.addPoint(netObj)
netType = None
if isinstance(netObj, (nodes.WlanNode, nodes.EmaneNode)):
netType = Attrib.NetType.WIRELESS
elif isinstance(netObj, (nodes.SwitchNode, nodes.HubNode,
nodes.PtpNet, nodes.TunnelNode)):
netType = Attrib.NetType.ETHERNET
else:
netType ="%s" % netObj.__class__.__name__
typeEle = self.createElement("type")
typeEle.appendChild(self.createTextNode(netType))
self.appendChild(typeEle)
# Gather all endpoints belonging to this network
self.endpoints = getEndpoints(netObj)
# Special case for a network of switches and hubs
createAlias = True
self.l2devices = []
if isinstance(netObj, (nodes.SwitchNode, nodes.HubNode)):
createAlias = False
self.appendChild(typeEle)
self.addL2Devices(netObj)
if createAlias:
a = self.createAlias(Attrib.Alias.ID, "%d" % int(netObj.objid))
self.appendChild(a)
# XXXX TODO: Move this to channel?
# key used with tunnel node
if hasattr(netObj, 'grekey') and netObj.grekey is not None:
a = self.createAlias("COREGREKEY", "%s" % netObj.grekey)
self.appendChild(a)
self.addNetMembers(netObj)
self.addChannels(netObj)
presentationEle = self.createElement("CORE:presentation")
addPresentationEle = False
if netObj.icon and not netObj.icon.isspace():
presentationEle.setAttribute("icon", netObj.icon)
addPresentationEle = True
if netObj.canvas:
presentationEle.setAttribute("canvas", str(netObj.canvas))
addPresentationEle = True
if addPresentationEle:
self.appendChild(presentationEle)
def getNetworkName(self, scenPlan, netObj):
''' Determine the name to use for this network element
'''
if isinstance(netObj, (nodes.PtpNet, nodes.TunnelNode)):
name = "net%s" % scenPlan.lastNetIdx
scenPlan.lastNetIdx += 1
elif netObj.name:
name = str(netObj.name) # could use net.brname for bridges?
elif isinstance(netObj, (nodes.SwitchNode, nodes.HubNode)):
name = "lan%s" % netObj.objid
else:
name = ''
return name
def addL2Devices(self, netObj):
''' Add switches and hubs
'''
# Add the netObj as a device
self.l2devices.append(DeviceElement(self.scenPlan, self, netObj))
# Add downstream switches/hubs
l2devs = []
neweps = []
for ep in self.endpoints:
if ep.type and ep.net.objid != netObj.objid:
l2s, eps = getDowmstreamL2Devices(ep.net)
l2devs.extend(l2s)
neweps.extend(eps)
for l2dev in l2devs:
self.l2devices.append(DeviceElement(self.scenPlan, self, l2dev))
self.endpoints.extend(neweps)
# XXX: Optimize later
def addNetMembers(self, netObj):
''' Add members to a network XML element.
'''
for ep in self.endpoints:
if ep.type:
MemberElement(self.scenPlan, self, referencedType=ep.type, referencedId=ep.id)
if ep.l2devport:
MemberElement(self.scenPlan,
self,
referencedType=Attrib.MembType.INTERFACE,
referencedId="%s/%s" % (self.id,ep.l2devport))
# XXX Revisit this
# Create implied members given the network type
if isinstance(netObj, nodes.TunnelNode):
MemberElement(self.scenPlan,
self,
referencedType=Attrib.MembType.TUNNEL,
referencedId="%s/%s" % (netObj.name, netObj.name))
# XXX: Optimize later
def addChannels(self, netObj):
''' Add channels to a network XML element
'''
if isinstance(netObj, (nodes.WlanNode, nodes.EmaneNode)):
modelconfigs = netObj.session.mobility.getmodels(netObj)
modelconfigs += netObj.session.emane.getmodels(netObj)
chan = None
for (model, conf) in modelconfigs:
# Handle mobility parameters below
if model._type == coreapi.CORE_TLV_REG_MOBILITY:
continue
# Create the channel
if chan is None:
name = "wireless"
chan = ChannelElement(self.scenPlan, self, netObj,
channelType=model._name,
channelName=name,
channelDomain="CORE")
# Add wireless model parameters
for i, key in enumerate(model.getnames()):
value = conf[i]
if value is not None:
chan.addParameter(key, model.valueof(key, conf))
for (model, conf) in modelconfigs:
if model._type == coreapi.CORE_TLV_REG_MOBILITY:
# Add wireless mobility parameters
mobility = XmlElement(self.scenPlan, chan, "CORE:mobility")
# Add a type child
typeEle = self.createElement("type")
typeEle.appendChild(self.createTextNode(model._name))
mobility.appendChild(typeEle)
for i, key in enumerate(model.getnames()):
value = conf[i]
if value is not None:
mobility.addParameter(key, value)
# Add members to the channel
if chan is not None:
chan.addChannelMembers(self.endpoints)
self.appendChild(chan.baseEle)
elif isinstance(netObj, nodes.PtpNet) :
if len(self.endpoints) < 2:
if len(self.endpoints) == 1:
self.coreSession.warn('Pt2Pt network with only 1 endpoint: %s' % self.endpoints[0].id)
else:
self.coreSession.warn('Pt2Pt network with no endpoints encountered in %s' % netObj.name)
return
name = "chan%d" % (0)
chan = ChannelElement(self.scenPlan, self, netObj,
channelType=Attrib.NetType.ETHERNET,
channelName=name)
# Add interface parameters
if self.endpoints[0].params != self.endpoints[1].params:
self.coreSession.warn('Pt2Pt Endpoint parameters do not match in %s' % netObj.name)
for key, value in self.endpoints[0].params:
# XXX lifted from original addnetem function. revisit this.
# default netem parameters are 0 or None
if value is None or value == 0:
continue
if key == "has_netem" or key == "has_tbf":
continue
chan.addParameter(key, value)
# Add members to the channel
chan.addChannelMembers(self.endpoints)
self.appendChild(chan)
elif isinstance(netObj, (nodes.SwitchNode,
nodes.HubNode, nodes.TunnelNode)):
cidx=0
channels = []
for ep in self.endpoints:
# Create one channel member per ep
if ep.type:
name = "chan%d" % (cidx)
chan = ChannelElement(self.scenPlan, self, netObj,
channelType=Attrib.NetType.ETHERNET,
channelName=name)
# Add interface parameters
for key, value in ep.params:
# XXX lifted from original addnetem function. revisit this.
# default netem parameters are 0 or None
if value is None or value == 0:
continue
if key == "has_netem" or key == "has_tbf":
continue
chan.addParameter(key, value)
# Add members to the channel
chan.addChannelMembers(ep)
channels.append(chan)
cidx += 1
for chan in channels:
self.appendChild(chan)
class DeviceElement(NamedXmlElement):
''' A device element in the scenario plan.
'''
def __init__(self, scenPlan, parent, devObj):
''' Add a PyCoreNode object as a device element.
'''
devType = None
coreDevType = None
if hasattr(devObj, "type") and devObj.type:
coreDevType = devObj.type
if devObj.type == Attrib.NodeType.ROUTER:
devType = Attrib.DevType.ROUTER
elif devObj.type == Attrib.NodeType.MDR:
devType = Attrib.DevType.ROUTER
elif devObj.type == Attrib.NodeType.HOST:
devType = Attrib.DevType.HOST
elif devObj.type == Attrib.NodeType.PC:
devType = Attrib.DevType.HOST
elif devObj.type == Attrib.NodeType.RJ45:
devType = Attrib.DevType.HOST
nodeId = "EMULATOR-HOST"
elif devObj.type == Attrib.NodeType.HUB:
devType = Attrib.DevType.HUB
elif devObj.type == Attrib.NodeType.SWITCH:
devType = Attrib.DevType.SWITCH
else:
# Default custom types (defined in ~/.core/nodes.conf) to HOST
devType = Attrib.DevType.HOST
if devType is None:
raise Exception
NamedXmlElement.__init__(self, scenPlan, parent, devType, devObj.name)
if coreDevType is not None:
typeEle = self.createElement("type")
typeEle.setAttribute("domain", "CORE")
typeEle.appendChild(self.createTextNode("%s" % coreDevType))
self.appendChild(typeEle)
self.interfaces = []
self.addInterfaces(devObj)
alias = self.createAlias(Attrib.Alias.ID, "%s" % devObj.objid)
self.appendChild(alias)
self.addPoint(devObj)
self.addServices(devObj)
presentationEle = self.createElement("CORE:presentation")
addPresentationEle = False
if devObj.icon and not devObj.icon.isspace():
presentationEle.setAttribute("icon", devObj.icon)
addPresentationEle = True
if devObj.canvas:
presentationEle.setAttribute("canvas", str(devObj.canvas))
addPresentationEle = True
if addPresentationEle:
self.appendChild(presentationEle)
def addInterfaces(self, devObj):
''' Add interfaces to a device element.
'''
idx=0
for ifcObj in devObj.netifs(sort=True):
if ifcObj.net and isinstance(ifcObj.net, nodes.CtrlNet):
continue
if isinstance(devObj, nodes.PyCoreNode):
ifcEle = InterfaceElement(self.scenPlan, self, devObj, ifcObj)
else: # isinstance(node, (nodes.HubNode nodes.SwitchNode)):
ifcEle = InterfaceElement(self.scenPlan, self, devObj, ifcObj, idx)
idx += 1
netmodel = None
if ifcObj.net:
if hasattr(ifcObj.net, "model"):
netmodel = ifcObj.net.model
if ifcObj.mtu and ifcObj.mtu != 1500:
ifcEle.setAttribute("mtu", "%s" % ifcObj.mtu)
# The interfaces returned for Switches and Hubs are the interfaces of the nodes connected to them.
# The addresses are for those interfaces. Don't include them here.
if isinstance(devObj, nodes.PyCoreNode):
# could use ifcObj.params, transport_type
ifcEle.addAddresses(ifcObj)
# per-interface models
# XXX Remove???
if netmodel and netmodel._name[:6] == "emane_":
cfg = self.coreSession.emane.getifcconfig(devObj.objid, netmodel._name,
None, ifcObj)
if cfg:
ifcEle.addModels(((netmodel, cfg),) )
self.interfaces.append(ifcEle)
def addServices(self, devObj):
''' Add services and their customizations to the ServicePlan.
'''
if not hasattr(devObj, "services") :
return
if len(devObj.services) == 0:
return
defaults = self.coreSession.services.getdefaultservices(devObj.type)
if devObj.services == defaults:
return
spn = self.createElement("CORE:services")
spn.setAttribute("name", devObj.name)
self.appendChild(spn)
for svc in devObj.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))
addelementsfromlist(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.coreSession.services.getservicefiledata(svc, fn)
if data is None:
# this includes only customized file contents and skips
# the auto-generated files
continue
txt = self.createTextNode("\n" + data)
f.appendChild(txt)
addtextelementsfromlist(self, s, svc._startup, "command",
(("type","start"),))
addtextelementsfromlist(self, s, svc._shutdown, "command",
(("type","stop"),))
addtextelementsfromlist(self, s, svc._validate, "command",
(("type","validate"),))
class ChannelElement(NamedXmlElement):
''' A channel element in the scenario plan
'''
def __init__(self, scenPlan, parent, netObj, channelType, channelName, channelDomain=None):
NamedXmlElement.__init__(self, scenPlan, parent, "channel", channelName)
'''
Create a channel element and append a member child referencing this channel element
in the parent element.
'''
# Create a member element for this channel in the parent
MemberElement(self.scenPlan,
parent,
referencedType=Attrib.MembType.CHANNEL,
referencedId=self.id)
# Add a type child
typeEle = self.createElement("type")
if channelDomain is not None:
typeEle.setAttribute("domain", "%s" % channelDomain)
typeEle.appendChild(self.createTextNode(channelType))
self.appendChild(typeEle)
def addChannelMembers(self, endpoints):
'''
Add network channel members referencing interfaces in the channel
'''
if isinstance(endpoints, list):
# A list of endpoints is given. Create one channel member per endpoint
idx = 0
for ep in endpoints:
self.addChannelMember(ep.type, ep.id, idx)
idx += 1
else:
# A single endpoint is given. Create one channel member for the endpoint,
# and if the endpoint is associated with a Layer 2 device port, add the
# port as a second member
ep = endpoints
self.addChannelMember(ep.type, ep.id, 0)
if ep.l2devport is not None:
memId = "%s/%s" % (self.parent.getAttribute("id"), ep.l2devport)
self.addChannelMember(ep.type, memId, 1)
def addChannelMember(self, memIfcType, memIfcId, memIdx):
'''
add a member to a given channel
'''
m = MemberElement(self.scenPlan,
self,
referencedType=memIfcType,
referencedId=memIfcId,
index=memIdx)
self.scenPlan.allChannelMembers[memIfcId] = m
class InterfaceElement(NamedXmlElement):
'''
A network interface element
'''
def __init__(self, scenPlan, parent, devObj, ifcObj, ifcIdx=None):
'''
Create a network interface element with references to channel that this
interface is used.
'''
elementName=None
if ifcIdx is not None:
elementName = "e%d" % ifcIdx
else:
elementName = ifcObj.name
NamedXmlElement.__init__(self, scenPlan, parent, "interface", elementName)
self.ifcObj = ifcObj
self.addChannelReference()
def addChannelReference(self):
'''
Add a reference to the channel that uses this interface
'''
try:
cm = self.scenPlan.allChannelMembers[self.id]
if cm is not None:
ch = cm.baseEle.parentNode
if ch is not None:
net = ch.parentNode
if net is not None:
MemberElement(self.scenPlan,
self,
referencedType=Attrib.MembType.CHANNEL,
referencedId=ch.getAttribute("id"),
index=int(cm.getAttribute("index")))
MemberElement(self.scenPlan,
self,
referencedType=Attrib.MembType.NETWORK,
referencedId=net.getAttribute("id"))
except KeyError:
pass # Not an error. This occurs when an interface belongs to a switch or a hub within a network and the channel is yet to be defined
def addAddresses(self, ifcObj):
'''
Add MAC and IP addresses to interface XML elements.
'''
if ifcObj.hwaddr:
h = self.createElement("address")
self.appendChild(h)
h.setAttribute("type", "mac")
htxt = self.createTextNode("%s" % ifcObj.hwaddr)
h.appendChild(htxt)
for addr in ifcObj.addrlist:
a = self.createElement("address")
self.appendChild(a)
(ip, sep, mask) = addr.partition('/')
# mask = int(mask) XXX?
if isIPv4Address(ip):
a.setAttribute("type", "IPv4")
else:
a.setAttribute("type", "IPv6")
# a.setAttribute("type", )
atxt = self.createTextNode("%s" % addr)
a.appendChild(atxt)
# XXX Remove?
def addModels(self, configs):
'''
Add models from a list of model-class, config values tuples.
'''
for (m, conf) in configs:
modelEle = self.createElement("model")
modelEle.setAttribute("name", m._name)
typeStr = "wireless"
if m._type == coreapi.CORE_TLV_REG_MOBILITY:
typeStr = "mobility"
modelEle.setAttribute("type", typeStr)
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))
modelEle.appendChild(key)
self.appendChild(modelEle)
class MemberElement(XmlElement):
'''
Member elements are references to other elements in the network plan elements of the scenario.
They are used in networks to reference channels, in channels to reference interfaces,
and in interfaces to reference networks/channels. Member elements provided allow bi-directional
traversal of network plan components.
'''
def __init__(self, scenPlan, parent, referencedType, referencedId, index=None):
'''
Create a member element
'''
XmlElement.__init__(self, scenPlan.document, parent, "member")
self.setAttribute("type", "%s" % referencedType)
# See'Understanding the Network Modeling Framework document'
if index is not None:
self.setAttribute("index", "%d" % index)
self.appendChild(self.createTextNode("%s" % referencedId))
#
# =======================================================================================
# Helpers
# =======================================================================================
def getEndpoint(netObj, ifcObj):
'''
Create an Endpoint object given the network and the interface of interest
'''
ep = None
l2devport=None
# if ifcObj references an interface of a node and is part of this network
if ifcObj.net.objid == netObj.objid and hasattr(ifcObj,'node') and ifcObj.node:
params = ifcObj.getparams()
if isinstance(ifcObj.net, (nodes.HubNode, nodes.SwitchNode)):
l2devport="%s/e%d" % (ifcObj.net.name, ifcObj.net.getifindex(ifcObj))
ep = Endpoint(netObj,
ifcObj,
type = Attrib.MembType.INTERFACE,
id="%s/%s" % (ifcObj.node.name, ifcObj.name),
l2devport=l2devport,
params=params)
# else if ifcObj references another node and is connected to this network
elif hasattr(ifcObj,"othernet"):
if ifcObj.othernet.objid == netObj.objid:
# #hack used for upstream parameters for link between switches
# #(see LxBrNet.linknet())
ifcObj.swapparams('_params_up')
params = ifcObj.getparams()
ifcObj.swapparams('_params_up')
owner = ifcObj.net
l2devport="%s/e%d" % (ifcObj.othernet.name, ifcObj.othernet.getifindex(ifcObj))
# Create the endpoint.
# XXX the interface index might not match what is shown in the gui. For switches and hubs,
# The gui assigns its index but doesn't pass it to the daemon and vice versa.
# The gui stores it's index in the IMN file, which it reads and writes without daemon intervention.
# Fix this!
ep = Endpoint(owner,
ifcObj,
type = Attrib.MembType.INTERFACE,
id="%s/%s/e%d" % (netObj.name, owner.name, owner.getifindex(ifcObj)),
l2devport=l2devport,
params=params)
# else this node has an interface that belongs to another network
# i.e. a switch/hub interface connected to another switch/hub and CORE has the other switch/hub
# as the containing network
else :
ep = Endpoint(netObj, ifcObj,type=None, id=None, l2devport=None, params=None)
return ep
def getEndpoints(netObj):
'''
Gather all endpoints of the given network
'''
# Get all endpoints
endpoints = []
# XXX TODO: How to represent physical interfaces.
#
# NOTE: The following code works except it would be missing physical (rj45) interfaces from Pt2pt links
# TODO: Fix data in net.netifs to include Pt2Pt physical interfaces
#
# Iterate through all the nodes in the scenario, then iterate through all the interface for each node,
# and check if the interface is connected to this network.
for ifcObj in netObj.netifs(sort=True):
try:
ep = getEndpoint(netObj, ifcObj)
if ep is not None:
endpoints.append(ep)
except Exception:
pass
return endpoints
def getDowmstreamL2Devices(netObj):
'''
Helper function for getting a list of all downstream layer 2 devices from the given netObj
'''
l2devObjs = [netObj]
allendpoints = []
myendpoints = getEndpoints(netObj)
allendpoints.extend(myendpoints)
for ep in myendpoints:
if ep.type and ep.net.objid != netObj.objid:
l2s, eps = getDowmstreamL2Devices(ep.net)
l2devObjs.extend(l2s)
allendpoints.extend(eps)
return l2devObjs, allendpoints
def getAllNetworkInterfaces(session):
'''
Gather all network interfacecs in the session
'''
netifs = []
for node in session.objs():
for netif in node.netifs(sort=True):
if netif not in netifs:
netifs.append(netif)
return netifs
def inOtherNetwork(netObj):
'''
Determine if CORE considers a given network object to be part of another network.
Note: CORE considers layer 2 devices to be their own networks. However, if a l2 device
is connected to another device, it is possible that one of its ports belong to the other
l2 device's network (thus, "othernet").
'''
for netif in netObj.netifs(sort=True):
if hasattr(netif,"othernet"):
if netif.othernet.objid != netObj.objid:
return True
return False

File diff suppressed because it is too large Load diff

View file

@ -1,43 +1,76 @@
# """
# CORE Definition of LxcNode, CoreNode, and other node classes that inherit from the CoreNode,
# Copyright (c)2010-2013 the Boeing Company. implementing specific node types.
# See the LICENSE file included in this distribution. """
#
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
nodes.py: definition of an LxcNode and CoreNode classes, and other node classes
that inherit from the CoreNode, implementing specific node types.
'''
from vnode import * import socket
from vnet import * import subprocess
from core.misc.ipaddr import * import threading
from core.api import coreapi from socket import AF_INET
from socket import AF_INET6
from core import constants
from core.coreobj import PyCoreNetIf
from core.coreobj import PyCoreNode from core.coreobj import PyCoreNode
from core.coreobj import PyCoreObj
from core.data import LinkData
from core.enumerations import LinkTypes
from core.enumerations import NodeTypes
from core.enumerations import RegisterTlvs
from core.misc import ipaddress
from core.misc import log
from core.misc import utils
from core.netns.vnet import GreTapBridge
from core.netns.vnet import LxBrNet
from core.netns.vnode import LxcNode
logger = log.get_logger(__name__)
class CtrlNet(LxBrNet): class CtrlNet(LxBrNet):
"""
Control network functionality.
"""
policy = "ACCEPT" policy = "ACCEPT"
CTRLIF_IDX_BASE = 99 # base control interface index # 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", CTRLIF_IDX_BASE = 99
DEFAULT_PREFIX_LIST = [
"172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24", "172.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.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"] "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, def __init__(self, session, objid="ctrlnet", name=None, prefix=None,
verbose = False, prefix = None, hostid=None, start=True, assign_address=True,
hostid = None, start = True, assign_address = True, updown_script=None, serverintf=None):
updown_script = None, serverintf = None): """
self.prefix = IPv4Prefix(prefix) Creates a CtrlNet instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node namee
:param prefix: control network ipv4 prefix
:param hostid: host id
:param bool start: start flag
:param str assign_address: assigned address
:param str updown_script: updown script
:param serverintf: server interface
:return:
"""
self.prefix = ipaddress.Ipv4Prefix(prefix)
self.hostid = hostid self.hostid = hostid
self.assign_address = assign_address self.assign_address = assign_address
self.updown_script = updown_script self.updown_script = updown_script
self.serverintf = serverintf self.serverintf = serverintf
LxBrNet.__init__(self, session, objid = objid, name = name, LxBrNet.__init__(self, session, objid=objid, name=name, start=start)
verbose = verbose, start = start)
def startup(self): def startup(self):
"""
Startup functionality for the control network.
:return: nothing
"""
if self.detectoldbridge(): if self.detectoldbridge():
return return
@ -45,36 +78,37 @@ class CtrlNet(LxBrNet):
if self.hostid: if self.hostid:
addr = self.prefix.addr(self.hostid) addr = self.prefix.addr(self.hostid)
else: else:
addr = self.prefix.maxaddr() addr = self.prefix.max_addr()
msg = "Added control network bridge: %s %s" % \ msg = "Added control network bridge: %s %s" % \
(self.brname, self.prefix) (self.brname, self.prefix)
addrlist = ["%s/%s" % (addr, self.prefix.prefixlen)] addrlist = ["%s/%s" % (addr, self.prefix.prefixlen)]
if self.assign_address: if self.assign_address:
self.addrconfig(addrlist = addrlist) self.addrconfig(addrlist=addrlist)
msg += " address %s" % addr msg += " address %s" % addr
self.session.info(msg) logger.info(msg)
if self.updown_script is not None: if self.updown_script is not None:
self.info("interface %s updown script '%s startup' called" % \ logger.info("interface %s updown script '%s startup' called" % \
(self.brname, self.updown_script)) (self.brname, self.updown_script))
check_call([self.updown_script, self.brname, "startup"]) subprocess.check_call([self.updown_script, self.brname, "startup"])
if self.serverintf is not None: if self.serverintf is not None:
try: try:
check_call([BRCTL_BIN, "addif", self.brname, self.serverintf]) subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, self.serverintf])
check_call([IP_BIN, "link", "set", self.serverintf, "up"]) subprocess.check_call([constants.IP_BIN, "link", "set", self.serverintf, "up"])
except Exception, e: except subprocess.CalledProcessError:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, self.brname, logger.exception("Error joining server interface %s to controlnet bridge %s",
"Error joining server interface %s to controlnet bridge %s: %s" % \ self.serverintf, self.brname)
(self.serverintf, self.brname, e))
def detectoldbridge(self): def detectoldbridge(self):
''' Occassionally, control net bridges from previously closed sessions are not cleaned up. """
Occassionally, control net bridges from previously closed sessions are not cleaned up.
Check if there are old control net bridges and delete them Check if there are old control net bridges and delete them
'''
retstat, retstr = cmdresult([BRCTL_BIN,'show']) :return: True if an old bridge was detected, False otherwise
:rtype: bool
"""
retstat, retstr = utils.cmdresult([constants.BRCTL_BIN, 'show'])
if retstat != 0: if retstat != 0:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, None, logger.error("Unable to retrieve list of installed bridges")
"Unable to retrieve list of installed bridges")
lines = retstr.split('\n') lines = retstr.split('\n')
for line in lines[1:]: for line in lines[1:]:
cols = line.split('\t') cols = line.split('\t')
@ -82,248 +116,346 @@ class CtrlNet(LxBrNet):
flds = cols[0].split('.') flds = cols[0].split('.')
if len(flds) == 3: if len(flds) == 3:
if flds[0] == 'b' and flds[1] == self.objid: if flds[0] == 'b' and flds[1] == self.objid:
self.session.exception(coreapi.CORE_EXCP_LEVEL_FATAL, "CtrlNet.startup()", None, logger.error(
"Error: An active control net bridge (%s) found. "\ "Error: An active control net bridge (%s) found. " \
"An older session might still be running. " \ "An older session might still be running. " \
"Stop all sessions and, if needed, delete %s to continue." % \ "Stop all sessions and, if needed, delete %s to continue." % \
(oldbr, oldbr)) (oldbr, oldbr)
)
return True return True
''' """
# Do this if we want to delete the old bridge # Do this if we want to delete the old bridge
self.warn("Warning: Old %s bridge found: %s" % (self.objid, oldbr)) logger.warn("Warning: Old %s bridge found: %s" % (self.objid, oldbr))
try: try:
check_call([BRCTL_BIN, 'delbr', oldbr]) check_call([BRCTL_BIN, 'delbr', oldbr])
except Exception, e: except subprocess.CalledProcessError as e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, oldbr, logger.exception("Error deleting old bridge %s", oldbr, e)
"Error deleting old bridge %s" % oldbr) logger.info("Deleted %s", oldbr)
self.info("Deleted %s" % oldbr) """
'''
return False return False
def shutdown(self): def shutdown(self):
"""
Control network shutdown.
:return: nothing
"""
if self.serverintf is not None: if self.serverintf is not None:
try: try:
check_call([BRCTL_BIN, "delif", self.brname, self.serverintf]) subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, self.serverintf])
except Exception, e: except subprocess.CalledProcessError:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname, logger.exception("Error deleting server interface %s to controlnet bridge %s",
"Error deleting server interface %s to controlnet bridge %s: %s" % \ self.serverintf, self.brname)
(self.serverintf, self.brname, e))
if self.updown_script is not None: if self.updown_script is not None:
self.info("interface %s updown script '%s shutdown' called" % \ logger.info("interface %s updown script '%s shutdown' called" % (self.brname, self.updown_script))
(self.brname, self.updown_script)) subprocess.check_call([self.updown_script, self.brname, "shutdown"])
check_call([self.updown_script, self.brname, "shutdown"])
LxBrNet.shutdown(self) LxBrNet.shutdown(self)
def tolinkmsgs(self, flags): def all_link_data(self, flags):
''' Do not include CtrlNet in link messages describing this session. """
''' Do not include CtrlNet in link messages describing this session.
:return: nothing
"""
return [] return []
class CoreNode(LxcNode): class CoreNode(LxcNode):
apitype = coreapi.CORE_NODE_DEF """
Basic core node class for nodes to extend.
"""
apitype = NodeTypes.DEFAULT.value
class PtpNet(LxBrNet): class PtpNet(LxBrNet):
"""
Peer to peer network node.
"""
policy = "ACCEPT" policy = "ACCEPT"
def attach(self, netif): def attach(self, netif):
if len(self._netif) > 1: """
raise ValueError, \ Attach a network interface, but limit attachment to two interfaces.
"Point-to-point links support at most 2 network interfaces"
:param core.coreobj.PyCoreNetIf netif: network interface
:return: nothing
"""
if len(self._netif) >= 2:
raise ValueError("Point-to-point links support at most 2 network interfaces")
LxBrNet.attach(self, netif) LxBrNet.attach(self, netif)
def tonodemsg(self, flags): def data(self, message_type):
''' Do not generate a Node Message for point-to-point links. They are """
Do not generate a Node Message for point-to-point links. They are
built using a link message instead. built using a link message instead.
'''
:return: nothing
"""
pass pass
def tolinkmsgs(self, flags): def all_link_data(self, flags):
''' Build CORE API TLVs for a point-to-point link. One Link message """
Build CORE API TLVs for a point-to-point link. One Link message
describes this network. describes this network.
'''
tlvdata = "" :return: all link data
:rtype: list[LinkData]
"""
all_links = []
if len(self._netif) != 2: if len(self._netif) != 2:
return tlvdata return all_links
(if1, if2) = self._netif.items()
if1, if2 = self._netif.items()
if1 = if1[1] if1 = if1[1]
if2 = if2[1] if2 = if2[1]
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
if1.node.objid) unidirectional = 0
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
if2.node.objid)
uni = False
if if1.getparams() != if2.getparams(): if if1.getparams() != if2.getparams():
uni = True unidirectional = 1
tlvdata += self.netifparamstolink(if1)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_TYPE,
self.linktype)
if uni:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \ interface1_ip4 = None
if1.node.getifindex(if1)) interface1_ip4_mask = None
if if1.hwaddr: interface1_ip6 = None
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1MAC, interface1_ip6_mask = None
if1.hwaddr) for address in if1.addrlist:
for addr in if1.addrlist: ip, sep, mask = address.partition('/')
(ip, sep, mask) = addr.partition('/')
mask = int(mask) mask = int(mask)
if isIPv4Address(ip): if ipaddress.is_ipv4_address(ip):
family = AF_INET family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP4 ipl = socket.inet_pton(family, ip)
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP4MASK interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface1_ip4_mask = mask
else: else:
family = AF_INET6 family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP6MASK
ipl = socket.inet_pton(family, ip) ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl)
IPAddr(af=family, addr=ipl)) interface1_ip6_mask = mask
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \ interface2_ip4 = None
if2.node.getifindex(if2)) interface2_ip4_mask = None
if if2.hwaddr: interface2_ip6 = None
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2MAC, interface2_ip6_mask = None
if2.hwaddr) for address in if2.addrlist:
for addr in if2.addrlist: ip, sep, mask = address.partition('/')
(ip, sep, mask) = addr.partition('/')
mask = int(mask) mask = int(mask)
if isIPv4Address(ip): if ipaddress.is_ipv4_address(ip):
family = AF_INET family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP4 ipl = socket.inet_pton(family, ip)
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP4MASK interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip4_mask = mask
else: else:
family = AF_INET6 family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP6MASK
ipl = socket.inet_pton(family, ip) ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
IPAddr(af=family, addr=ipl)) interface2_ip6_mask = mask
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata) # TODO: not currently used
if not uni: # loss=netif.getparam('loss')
return [msg,] 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 # build a 2nd link message for the upstream link parameters
# (swap if1 and if2) # (swap if1 and if2)
tlvdata = "" if unidirectional:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER, link_data = LinkData(
if2.node.objid) message_type=0,
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER, node1_id=if2.node.objid,
if1.node.objid) node2_id=if1.node.objid,
tlvdata += self.netifparamstolink(if2) delay=if1.getparam("delay"),
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1) bandwidth=if1.getparam("bw"),
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \ dup=if1.getparam("duplicate"),
if2.node.getifindex(if2)) jitter=if1.getparam("jitter"),
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \ unidirectional=1,
if1.node.getifindex(if1)) interface1_id=if2.node.getifindex(if2),
msg2 = coreapi.CoreLinkMessage.pack(0, tlvdata) interface2_id=if1.node.getifindex(if1)
return [msg, msg2] )
all_links.append(link_data)
return all_links
class SwitchNode(LxBrNet): class SwitchNode(LxBrNet):
apitype = coreapi.CORE_NODE_SWITCH """
Provides switch functionality within a core node.
"""
apitype = NodeTypes.SWITCH.value
policy = "ACCEPT" policy = "ACCEPT"
type = "lanswitch" type = "lanswitch"
class HubNode(LxBrNet): class HubNode(LxBrNet):
apitype = coreapi.CORE_NODE_HUB """
Provides hub functionality within a core node, forwards packets to all bridge
ports by turning off MAC address learning.
"""
apitype = NodeTypes.HUB.value
policy = "ACCEPT" policy = "ACCEPT"
type = "hub" type = "hub"
def __init__(self, session, objid = None, name = None, verbose = False, def __init__(self, session, objid=None, name=None, start=True):
start = True): """
''' the Hub node forwards packets to all bridge ports by turning off Creates a HubNode instance.
the MAC address learning
''' :param core.session.Session session: core session instance
LxBrNet.__init__(self, session, objid, name, verbose, start) :param int objid: node id
:param str name: node namee
:param bool start: start flag
"""
LxBrNet.__init__(self, session, objid, name, start)
if start: if start:
check_call([BRCTL_BIN, "setageing", self.brname, "0"]) subprocess.check_call([constants.BRCTL_BIN, "setageing", self.brname, "0"])
class WlanNode(LxBrNet): class WlanNode(LxBrNet):
apitype = coreapi.CORE_NODE_WLAN """
linktype = coreapi.CORE_LINK_WIRELESS Provides wireless lan functionality within a core node.
"""
apitype = NodeTypes.WIRELESS_LAN.value
linktype = LinkTypes.WIRELESS.value
policy = "DROP" policy = "DROP"
type = "wlan" type = "wlan"
def __init__(self, session, objid = None, name = None, verbose = False, def __init__(self, session, objid=None, name=None, start=True, policy=None):
start = True, policy = None): """
LxBrNet.__init__(self, session, objid, name, verbose, start, policy) Create a WlanNode instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node name
:param bool start: start flag
:param policy: wlan policy
"""
LxBrNet.__init__(self, session, objid, name, start, policy)
# wireless model such as basic range # wireless model such as basic range
self.model = None self.model = None
# mobility model such as scripted # mobility model such as scripted
self.mobility = None self.mobility = None
def attach(self, netif): def attach(self, netif):
"""
Attach a network interface.
:param core.coreobj.PyCoreNetIf netif: network interface
:return: nothing
"""
LxBrNet.attach(self, netif) LxBrNet.attach(self, netif)
if self.model: if self.model:
netif.poshook = self.model._positioncallback netif.poshook = self.model.position_callback
if netif.node is None: if netif.node is None:
return return
(x,y,z) = netif.node.position.get() x, y, z = netif.node.position.get()
# invokes any netif.poshook # invokes any netif.poshook
netif.setposition(x, y, z) netif.setposition(x, y, z)
#self.model.setlinkparams() # self.model.setlinkparams()
def setmodel(self, model, config): def setmodel(self, model, config):
''' Mobility and wireless model. """
''' Sets the mobility and wireless model.
if (self.verbose):
self.info("adding model %s" % model._name) :param core.mobility.WirelessModel.cls model: wireless model to set to
if model._type == coreapi.CORE_TLV_REG_WIRELESS: :param config: model configuration
self.model = model(session=self.session, objid=self.objid, :return: nothing
verbose=self.verbose, values=config) """
if self.model._positioncallback: logger.info("adding model %s" % model.name)
if model.config_type == RegisterTlvs.WIRELESS.value:
self.model = model(session=self.session, object_id=self.objid, values=config)
if self.model.position_callback:
for netif in self.netifs(): for netif in self.netifs():
netif.poshook = self.model._positioncallback netif.poshook = self.model.position_callback
if netif.node is not None: if netif.node is not None:
(x,y,z) = netif.node.position.get() x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z) netif.poshook(netif, x, y, z)
self.model.setlinkparams() self.model.setlinkparams()
elif model._type == coreapi.CORE_TLV_REG_MOBILITY: elif model.config_type == RegisterTlvs.MOBILITY.value:
self.mobility = model(session=self.session, objid=self.objid, self.mobility = model(session=self.session, object_id=self.objid, values=config)
verbose=self.verbose, values=config)
def updatemodel(self, model_name, values): def updatemodel(self, model_name, values):
''' Allow for model updates during runtime (similar to setmodel().) """
''' Allow for model updates during runtime (similar to setmodel().)
if (self.verbose):
self.info("updating model %s" % model_name) :param model_name: model name to update
if self.model is None or self.model._name != model_name: :param values: values to update model with
:return: nothing
"""
logger.info("updating model %s" % model_name)
if self.model is None or self.model.name != model_name:
return return
model = self.model model = self.model
if model._type == coreapi.CORE_TLV_REG_WIRELESS: if model.config_type == RegisterTlvs.WIRELESS.value:
if not model.updateconfig(values): if not model.updateconfig(values):
return return
if self.model._positioncallback: if self.model.position_callback:
for netif in self.netifs(): for netif in self.netifs():
netif.poshook = self.model._positioncallback netif.poshook = self.model.position_callback
if netif.node is not None: if netif.node is not None:
(x,y,z) = netif.node.position.get() (x, y, z) = netif.node.position.get()
netif.poshook(netif, x, y, z) netif.poshook(netif, x, y, z)
self.model.setlinkparams() self.model.setlinkparams()
def tolinkmsgs(self, flags): def all_link_data(self, flags):
msgs = LxBrNet.tolinkmsgs(self, flags) """
Retrieve all link data.
:param flags: link flags
:return: all link data
:rtype: list[LinkData]
"""
all_links = LxBrNet.all_link_data(self, flags)
if self.model: if self.model:
msgs += self.model.tolinkmsgs(flags) all_links.extend(self.model.all_link_data(flags))
return msgs
return all_links
class RJ45Node(PyCoreNode, PyCoreNetIf): class RJ45Node(PyCoreNode, PyCoreNetIf):
''' RJ45Node is a physical interface on the host linked to the emulated """
RJ45Node is a physical interface on the host linked to the emulated
network. network.
''' """
apitype = coreapi.CORE_NODE_RJ45 apitype = NodeTypes.RJ45.value
type = "rj45" type = "rj45"
def __init__(self, session, objid = None, name = None, mtu = 1500, def __init__(self, session, objid=None, name=None, mtu=1500, start=True):
verbose = False, start = True): """
PyCoreNode.__init__(self, session, objid, name, verbose=verbose, Create an RJ45Node instance.
start=start)
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node name
:param mtu: rj45 mtu
:param bool start: start flag
:return:
"""
PyCoreNode.__init__(self, session, objid, name, start=start)
# this initializes net, params, poshook # this initializes net, params, poshook
PyCoreNetIf.__init__(self, node=self, name=name, mtu = mtu) PyCoreNetIf.__init__(self, node=self, name=name, mtu=mtu)
self.up = False self.up = False
self.lock = threading.RLock() self.lock = threading.RLock()
self.ifindex = None self.ifindex = None
@ -334,65 +466,101 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
self.startup() self.startup()
def startup(self): def startup(self):
''' Set the interface in the up state. """
''' Set the interface in the up state.
:return: nothing
"""
# interface will also be marked up during net.attach() # interface will also be marked up during net.attach()
self.savestate() self.savestate()
try: try:
check_call([IP_BIN, "link", "set", self.localname, "up"]) subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
except:
self.warn("Failed to run command: %s link set %s up" % \
(IP_BIN, self.localname))
return
self.up = True self.up = True
except subprocess.CalledProcessError:
logger.exception("failed to run command: %s link set %s up", constants.IP_BIN, self.localname)
def shutdown(self): def shutdown(self):
''' Bring the interface down. Remove any addresses and queuing """
Bring the interface down. Remove any addresses and queuing
disciplines. disciplines.
'''
:return: nothing
"""
if not self.up: if not self.up:
return return
check_call([IP_BIN, "link", "set", self.localname, "down"]) subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "down"])
check_call([IP_BIN, "addr", "flush", "dev", self.localname]) subprocess.check_call([constants.IP_BIN, "addr", "flush", "dev", self.localname])
mutecall([TC_BIN, "qdisc", "del", "dev", self.localname, "root"]) utils.mutecall([constants.TC_BIN, "qdisc", "del", "dev", self.localname, "root"])
self.up = False self.up = False
self.restorestate() self.restorestate()
# TODO: issue in that both classes inherited from provide the same method with different signatures
def attachnet(self, net): def attachnet(self, net):
"""
Attach a network.
:param core.coreobj.PyCoreNet net: network to attach
:return: nothing
"""
PyCoreNetIf.attachnet(self, net) PyCoreNetIf.attachnet(self, net)
def detachnet(self): def detachnet(self):
"""
Detach a network.
:return: nothing
"""
PyCoreNetIf.detachnet(self) PyCoreNetIf.detachnet(self)
def newnetif(self, net = None, addrlist = [], hwaddr = None, # TODO: parameters are not used
ifindex = None, ifname = None): def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
''' This is called when linking with another node. Since this node """
This is called when linking with another node. Since this node
represents an interface, we do not create another object here, represents an interface, we do not create another object here,
but attach ourselves to the given network. but attach ourselves to the given network.
'''
self.lock.acquire() :param core.coreobj.PyCoreNet net: new network instance
try: :param list[str] addrlist: address list
:param str hwaddr: hardware address
:param int ifindex: interface index
:param str ifname: interface name
:return:
"""
with self.lock:
if ifindex is None: if ifindex is None:
ifindex = 0 ifindex = 0
if self.net is not None: if self.net is not None:
raise ValueError, \ raise ValueError("RJ45 nodes support at most 1 network interface")
"RJ45 nodes support at most 1 network interface"
self._netif[ifindex] = self self._netif[ifindex] = self
self.node = self # PyCoreNetIf.node is self # PyCoreNetIf.node is self
self.node = self
self.ifindex = ifindex self.ifindex = ifindex
if net is not None: if net is not None:
self.attachnet(net) self.attachnet(net)
for addr in maketuple(addrlist):
if addrlist:
for addr in utils.maketuple(addrlist):
self.addaddr(addr) self.addaddr(addr)
return ifindex return ifindex
finally:
self.lock.release()
def delnetif(self, ifindex): def delnetif(self, ifindex):
"""
Delete a network interface.
:param int ifindex: interface index to delete
:return: nothing
"""
if ifindex is None: if ifindex is None:
ifindex = 0 ifindex = 0
if ifindex not in self._netif: if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex raise ValueError, "ifindex %s does not exist" % ifindex
self._netif.pop(ifindex) self._netif.pop(ifindex)
if ifindex == self.ifindex: if ifindex == self.ifindex:
self.shutdown() self.shutdown()
@ -400,46 +568,78 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
raise ValueError, "ifindex %s does not exist" % ifindex raise ValueError, "ifindex %s does not exist" % ifindex
def netif(self, ifindex, net=None): def netif(self, ifindex, net=None):
''' This object is considered the network interface, so we only """
This object is considered the network interface, so we only
return self here. This keeps the RJ45Node compatible with return self here. This keeps the RJ45Node compatible with
real nodes. real nodes.
'''
:param int ifindex: interface index to retrieve
:param net: network to retrieve
:return: a network interface
:rtype: core.coreobj.PyCoreNetIf
"""
if net is not None and net == self.net: if net is not None and net == self.net:
return self return self
if ifindex is None: if ifindex is None:
ifindex = 0 ifindex = 0
if ifindex == self.ifindex: if ifindex == self.ifindex:
return self return self
return None return None
def getifindex(self, netif): def getifindex(self, netif):
"""
Retrieve network interface index.
:param core.coreobj.PyCoreNetIf netif: network interface to retrieve index for
:return: interface index, None otherwise
:rtype: int
"""
if netif != self: if netif != self:
return None return None
return self.ifindex return self.ifindex
def addaddr(self, addr): def addaddr(self, addr):
"""
Add address to to network interface.
:param str addr: address to add
:return: nothing
"""
if self.up: if self.up:
check_call([IP_BIN, "addr", "add", str(addr), "dev", self.name]) subprocess.check_call([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name])
PyCoreNetIf.addaddr(self, addr) PyCoreNetIf.addaddr(self, addr)
def deladdr(self, addr): def deladdr(self, addr):
"""
Delete address from network interface.
:param str addr: address to delete
:return: nothing
"""
if self.up: if self.up:
check_call([IP_BIN, "addr", "del", str(addr), "dev", self.name]) subprocess.check_call([constants.IP_BIN, "addr", "del", str(addr), "dev", self.name])
PyCoreNetIf.deladdr(self, addr) PyCoreNetIf.deladdr(self, addr)
def savestate(self): def savestate(self):
''' Save the addresses and other interface state before using the """
Save the addresses and other interface state before using the
interface for emulation purposes. TODO: save/restore the PROMISC flag interface for emulation purposes. TODO: save/restore the PROMISC flag
'''
:return: nothing
"""
self.old_up = False self.old_up = False
self.old_addrs = [] self.old_addrs = []
cmd = [IP_BIN, "addr", "show", "dev", self.localname] cmd = [constants.IP_BIN, "addr", "show", "dev", self.localname]
try: try:
tmp = subprocess.Popen(cmd, stdout = subprocess.PIPE) tmp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
except OSError: except OSError:
self.warn("Failed to run %s command: %s" % (IP_BIN, cmd)) logger.exception("Failed to run %s command: %s", constants.IP_BIN, cmd)
if tmp.wait(): if tmp.wait():
self.warn("Command failed: %s" % cmd) logger.warn("Command failed: %s", cmd)
return return
lines = tmp.stdout.read() lines = tmp.stdout.read()
tmp.stdout.close() tmp.stdout.close()
@ -459,31 +659,34 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
self.old_addrs.append((items[1], None)) self.old_addrs.append((items[1], None))
def restorestate(self): def restorestate(self):
''' Restore the addresses and other interface state after using it. """
''' Restore the addresses and other interface state after using it.
:return: nothing
"""
for addr in self.old_addrs: for addr in self.old_addrs:
if addr[1] is None: if addr[1] is None:
check_call([IP_BIN, "addr", "add", addr[0], "dev", subprocess.check_call([constants.IP_BIN, "addr", "add", addr[0], "dev", self.localname])
self.localname])
else: else:
check_call([IP_BIN, "addr", "add", addr[0], "brd", addr[1], subprocess.check_call([constants.IP_BIN, "addr", "add", addr[0], "brd", addr[1], "dev", self.localname])
"dev", self.localname])
if self.old_up: if self.old_up:
check_call([IP_BIN, "link", "set", self.localname, "up"]) subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
def setposition(self, x=None, y=None, z=None): def setposition(self, x=None, y=None, z=None):
''' Use setposition() from both parent classes. """
''' Use setposition() from both parent classes.
:return: nothing
"""
PyCoreObj.setposition(self, x, y, z) PyCoreObj.setposition(self, x, y, z)
# invoke any poshook # invoke any poshook
PyCoreNetIf.setposition(self, x, y, z) PyCoreNetIf.setposition(self, x, y, z)
class TunnelNode(GreTapBridge): class TunnelNode(GreTapBridge):
apitype = coreapi.CORE_NODE_TUNNEL """
Provides tunnel functionality in a core node.
"""
apitype = NodeTypes.TUNNEL.value
policy = "ACCEPT" policy = "ACCEPT"
type = "tunnel" type = "tunnel"

View file

@ -1,58 +1,91 @@
# """
# CORE virtual ethernet classes that implement the interfaces available under Linux.
# Copyright (c)2011-2014 the Boeing Company. """
# See the LICENSE file included in this distribution.
#
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
vif.py: PyCoreNetIf classes that implement the interfaces available
under Linux.
'''
import os, signal, shutil, sys, subprocess, vnodeclient, threading, string import subprocess
import random, time import time
from core.api import coreapi
from core.misc.utils import * from core import constants
from core.constants import * from core.coreobj import PyCoreNetIf
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position from core.enumerations import NodeTypes
from core.emane.nodes import EmaneNode from core.misc import log
from core.misc import nodeutils
from core.misc import utils
logger = log.get_logger(__name__)
utils.check_executables([constants.IP_BIN])
checkexec([IP_BIN])
class VEth(PyCoreNetIf): class VEth(PyCoreNetIf):
def __init__(self, node, name, localname, mtu = 1500, net = None, """
start = True): Provides virtual ethernet functionality for core nodes.
"""
# TODO: network is not used, why was it needed?
def __init__(self, node, name, localname, mtu=1500, net=None, start=True):
"""
Creates a VEth instance.
:param core.netns.nodes.CoreNode node: related core node
:param str name: interface name
:param str localname: interface local name
:param mtu: interface mtu
:param net: network
:param bool start: start flag
:return:
"""
# note that net arg is ignored # note that net arg is ignored
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu) PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.localname = localname self.localname = localname
self.up = False self.up = False
if start: if start:
self.startup() self.startup()
def startup(self): def startup(self):
check_call([IP_BIN, "link", "add", "name", self.localname, """
Interface startup logic.
:return: nothing
"""
subprocess.check_call([constants.IP_BIN, "link", "add", "name", self.localname,
"type", "veth", "peer", "name", self.name]) "type", "veth", "peer", "name", self.name])
check_call([IP_BIN, "link", "set", self.localname, "up"]) subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
self.up = True self.up = True
def shutdown(self): def shutdown(self):
"""
Interface shutdown logic.
:return: nothing
"""
if not self.up: if not self.up:
return return
if self.node: if self.node:
self.node.cmd([IP_BIN, "-6", "addr", "flush", "dev", self.name]) self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
if self.localname: if self.localname:
mutedetach([IP_BIN, "link", "delete", self.localname]) utils.mutedetach([constants.IP_BIN, "link", "delete", self.localname])
self.up = False self.up = False
class TunTap(PyCoreNetIf): class TunTap(PyCoreNetIf):
''' TUN/TAP virtual device in TAP mode """
''' TUN/TAP virtual device in TAP mode
def __init__(self, node, name, localname, mtu = 1500, net = None, """
start = True):
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu) # TODO: network is not used, why was it needed?
def __init__(self, node, name, localname, mtu=1500, net=None, start=True):
"""
Create a TunTap instance.
:param core.netns.nodes.CoreNode node: related core node
:param str name: interface name
:param str localname: local interface name
:param mtu: interface mtu
:param net: related network
:param bool start: start flag
"""
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.localname = localname self.localname = localname
self.up = False self.up = False
self.transport_type = "virtual" self.transport_type = "virtual"
@ -60,26 +93,41 @@ class TunTap(PyCoreNetIf):
self.startup() self.startup()
def startup(self): def startup(self):
"""
Startup logic for a tunnel tap.
:return: nothing
"""
# TODO: more sophisticated TAP creation here # TODO: more sophisticated TAP creation here
# Debian does not support -p (tap) option, RedHat does. # Debian does not support -p (tap) option, RedHat does.
# For now, this is disabled to allow the TAP to be created by another # For now, this is disabled to allow the TAP to be created by another
# system (e.g. EMANE's emanetransportd) # system (e.g. EMANE's emanetransportd)
#check_call(["tunctl", "-t", self.name]) # check_call(["tunctl", "-t", self.name])
# self.install() # self.install()
self.up = True self.up = True
def shutdown(self): def shutdown(self):
"""
Shutdown functionality for a tunnel tap.
:return: nothing
"""
if not self.up: if not self.up:
return return
self.node.cmd([IP_BIN, "-6", "addr", "flush", "dev", self.name]) self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
#if self.name: # if self.name:
# mutedetach(["tunctl", "-d", self.localname]) # mutedetach(["tunctl", "-d", self.localname])
self.up = False self.up = False
def waitfor(self, func, attempts = 10, maxretrydelay = 0.25): def waitfor(self, func, attempts=10, maxretrydelay=0.25):
'''\ """
Wait for func() to return zero with exponential backoff Wait for func() to return zero with exponential backoff.
'''
:param func: function to wait for a result of zero
:param int attempts: number of attempts to wait for a zero result
:param float maxretrydelay: maximum retry delay
:return: nothing
"""
delay = 0.01 delay = 0.01
for i in xrange(1, attempts + 1): for i in xrange(1, attempts + 1):
r = func() r = func()
@ -88,91 +136,123 @@ class TunTap(PyCoreNetIf):
msg = 'attempt %s failed with nonzero exit status %s' % (i, r) msg = 'attempt %s failed with nonzero exit status %s' % (i, r)
if i < attempts + 1: if i < attempts + 1:
msg += ', retrying...' msg += ', retrying...'
self.node.info(msg) logger.info(msg)
time.sleep(delay) time.sleep(delay)
delay = delay + delay delay = delay + delay
if delay > maxretrydelay: if delay > maxretrydelay:
delay = maxretrydelay delay = maxretrydelay
else: else:
msg += ', giving up' msg += ', giving up'
self.node.info(msg) logger.info(msg)
raise RuntimeError, 'command failed after %s attempts' % attempts
raise RuntimeError('command failed after %s attempts' % attempts)
def waitfordevicelocal(self): def waitfordevicelocal(self):
'''\ """
Check for presence of a local device - tap device may not Check for presence of a local device - tap device may not
appear right away waits appear right away waits
'''
:return: wait for device local response
:rtype: int
"""
def localdevexists(): def localdevexists():
cmd = (IP_BIN, 'link', 'show', self.localname) cmd = (constants.IP_BIN, 'link', 'show', self.localname)
return mutecall(cmd) return utils.mutecall(cmd)
self.waitfor(localdevexists) self.waitfor(localdevexists)
def waitfordevicenode(self): def waitfordevicenode(self):
'''\ """
Check for presence of a node device - tap device may not Check for presence of a node device - tap device may not appear right away waits.
appear right away waits
''' :return: nothing
"""
def nodedevexists(): def nodedevexists():
cmd = (IP_BIN, 'link', 'show', self.name) cmd = (constants.IP_BIN, 'link', 'show', self.name)
return self.node.cmd(cmd) return self.node.cmd(cmd)
count = 0 count = 0
while True: while True:
try: try:
self.waitfor(nodedevexists) self.waitfor(nodedevexists)
break break
except RuntimeError: except RuntimeError as e:
# check if this is an EMANE interface; if so, continue # check if this is an EMANE interface; if so, continue
# waiting if EMANE is still running # waiting if EMANE is still running
if count < 5 and isinstance(self.net, EmaneNode) and \ # TODO: remove emane code
if count < 5 and nodeutils.is_node(self.net, NodeTypes.EMANE) and \
self.node.session.emane.emanerunning(self.node): self.node.session.emane.emanerunning(self.node):
count += 1 count += 1
else: else:
raise raise e
def install(self): def install(self):
''' Install this TAP into its namespace. This is not done from the """
Install this TAP into its namespace. This is not done from the
startup() method but called at a later time when a userspace startup() method but called at a later time when a userspace
program (running on the host) has had a chance to open the socket program (running on the host) has had a chance to open the socket
end of the TAP. end of the TAP.
'''
:return: nothing
"""
self.waitfordevicelocal() self.waitfordevicelocal()
netns = str(self.node.pid) netns = str(self.node.pid)
try: try:
check_call([IP_BIN, "link", "set", self.localname, "netns", netns]) subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "netns", netns])
except Exception, e: except subprocess.CalledProcessError:
msg = "error installing TAP interface %s, command:" % self.localname msg = "error installing TAP interface %s, command:" % self.localname
msg += "ip link set %s netns %s" % (self.localname, netns) msg += "ip link set %s netns %s" % (self.localname, netns)
self.node.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.localname, msg) logger.exception(msg)
self.node.warn(msg)
return return
self.node.cmd([IP_BIN, "link", "set", self.localname,
"name", self.name]) self.node.cmd([constants.IP_BIN, "link", "set", self.localname, "name", self.name])
self.node.cmd([IP_BIN, "link", "set", self.name, "up"]) self.node.cmd([constants.IP_BIN, "link", "set", self.name, "up"])
def setaddrs(self): def setaddrs(self):
''' Set interface addresses based on self.addrlist. """
''' Set interface addresses based on self.addrlist.
:return: nothing
"""
self.waitfordevicenode() self.waitfordevicenode()
for addr in self.addrlist: for addr in self.addrlist:
self.node.cmd([IP_BIN, "addr", "add", str(addr), self.node.cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name])
"dev", self.name])
class GreTap(PyCoreNetIf): class GreTap(PyCoreNetIf):
''' GRE TAP device for tunneling between emulation servers. """
GRE TAP device for tunneling between emulation servers.
Uses the "gretap" tunnel device type from Linux which is a GRE device Uses the "gretap" tunnel device type from Linux which is a GRE device
having a MAC address. The MAC address is required for bridging. having a MAC address. The MAC address is required for bridging.
''' """
def __init__(self, node = None, name = None, session = None, mtu = 1458,
remoteip = None, objid = None, localip = None, ttl = 255, def __init__(self, node=None, name=None, session=None, mtu=1458,
key = None, start = True): remoteip=None, objid=None, localip=None, ttl=255,
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu) key=None, start=True):
"""
Creates a GreTap instance.
:param core.netns.nodes.CoreNode node: related core node
:param str name: interface name
:param core.session.Session session: core session instance
:param mtu: interface mtu
:param str remoteip: remote address
:param int objid: object id
:param str localip: local address
:param ttl: ttl value
:param key: gre tap key
:param bool start: start flag
"""
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.session = session self.session = session
if objid is None: if objid is None:
# from PyCoreObj # from PyCoreObj
objid = (((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff) objid = ((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff
self.objid = objid self.objid = objid
sessionid = self.session.shortsessionid() sessionid = self.session.short_session_id()
# interface name on the local host machine # interface name on the local host machine
self.localname = "gt.%s.%s" % (self.objid, sessionid) self.localname = "gt.%s.%s" % (self.objid, sessionid)
self.transport_type = "raw" self.transport_type = "raw"
@ -190,21 +270,39 @@ class GreTap(PyCoreNetIf):
cmd += ("ttl", str(ttl)) cmd += ("ttl", str(ttl))
if key: if key:
cmd += ("key", str(key)) cmd += ("key", str(key))
check_call(cmd) subprocess.check_call(cmd)
cmd = ("ip", "link", "set", self.localname, "up") cmd = ("ip", "link", "set", self.localname, "up")
check_call(cmd) subprocess.check_call(cmd)
self.up = True self.up = True
def shutdown(self): def shutdown(self):
"""
Shutdown logic for a GreTap.
:return: nothing
"""
if self.localname: if self.localname:
cmd = ("ip", "link", "set", self.localname, "down") cmd = ("ip", "link", "set", self.localname, "down")
check_call(cmd) subprocess.check_call(cmd)
cmd = ("ip", "link", "del", self.localname) cmd = ("ip", "link", "del", self.localname)
check_call(cmd) subprocess.check_call(cmd)
self.localname = None self.localname = None
def tonodemsg(self, flags): def data(self, message_type):
"""
Data for a gre tap.
:param message_type: message type for data
:return: None
"""
return None return None
def tolinkmsgs(self, flags): def all_link_data(self, flags):
"""
Retrieve link data.
:param flags: link flags
:return: link data
:rtype: list[core.data.LinkData]
"""
return [] return []

View file

@ -1,42 +1,48 @@
# """
# CORE PyCoreNet and LxBrNet classes that implement virtual networks using
# Copyright (c)2010-2016 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
vnet.py: PyCoreNet and LxBrNet classes that implement virtual networks using
Linux Ethernet bridging and ebtables rules. Linux Ethernet bridging and ebtables rules.
''' """
import os, sys, threading, time, subprocess import os
import subprocess
import threading
import time
from core.api import coreapi from core import constants
from core.misc.utils import * from core.coreobj import PyCoreNet
from core.constants import * from core.misc import log
from core.coreobj import PyCoreNet, PyCoreObj from core.misc import utils
from core.netns.vif import VEth, GreTap from core.netns.vif import GreTap
from core.netns.vif import VEth
checkexec([BRCTL_BIN, IP_BIN, EBTABLES_BIN, TC_BIN]) logger = log.get_logger(__name__)
utils.check_executables([
constants.BRCTL_BIN,
constants.IP_BIN,
constants.EBTABLES_BIN,
constants.TC_BIN
])
ebtables_lock = threading.Lock() ebtables_lock = threading.Lock()
class EbtablesQueue(object): class EbtablesQueue(object):
''' Helper class for queuing up ebtables commands into rate-limited """
Helper class for queuing up ebtables commands into rate-limited
atomic commits. This improves performance and reliability when there are atomic commits. This improves performance and reliability when there are
many WLAN link updates. many WLAN link updates.
''' """
# update rate is every 300ms # update rate is every 300ms
rate = 0.3 rate = 0.3
# ebtables # ebtables
atomic_file = "/tmp/pycore.ebtables.atomic" atomic_file = "/tmp/pycore.ebtables.atomic"
def __init__(self): def __init__(self):
''' Initialize the helper class, but don't start the update thread """
Initialize the helper class, but don't start the update thread
until a WLAN is instantiated. until a WLAN is instantiated.
''' """
self.doupdateloop = False self.doupdateloop = False
self.updatethread = None self.updatethread = None
# this lock protects cmds and updates lists # this lock protects cmds and updates lists
@ -50,26 +56,33 @@ class EbtablesQueue(object):
self.last_update_time = {} self.last_update_time = {}
def startupdateloop(self, wlan): def startupdateloop(self, wlan):
''' Kick off the update loop; only needs to be invoked once. """
''' Kick off the update loop; only needs to be invoked once.
:return: nothing
"""
self.updatelock.acquire() self.updatelock.acquire()
self.last_update_time[wlan] = time.time() self.last_update_time[wlan] = time.time()
self.updatelock.release() self.updatelock.release()
if self.doupdateloop: if self.doupdateloop:
return return
self.doupdateloop = True self.doupdateloop = True
self.updatethread = threading.Thread(target = self.updateloop) self.updatethread = threading.Thread(target=self.updateloop)
self.updatethread.daemon = True self.updatethread.daemon = True
self.updatethread.start() self.updatethread.start()
def stopupdateloop(self, wlan): def stopupdateloop(self, wlan):
''' Kill the update loop thread if there are no more WLANs using it. """
''' Kill the update loop thread if there are no more WLANs using it.
:return: nothing
"""
self.updatelock.acquire() self.updatelock.acquire()
try: try:
del self.last_update_time[wlan] del self.last_update_time[wlan]
except KeyError: except KeyError:
pass logger.exception("error deleting last update time for wlan: %s", wlan)
self.updatelock.release() self.updatelock.release()
if len(self.last_update_time) > 0: if len(self.last_update_time) > 0:
return return
@ -79,42 +92,60 @@ class EbtablesQueue(object):
self.updatethread = None self.updatethread = None
def ebatomiccmd(self, cmd): def ebatomiccmd(self, cmd):
''' Helper for building ebtables atomic file command list. """
''' Helper for building ebtables atomic file command list.
r = [EBTABLES_BIN, "--atomic-file", self.atomic_file]
:param list[str] cmd: ebtable command
:return: ebtable atomic command
:rtype: list[str]
"""
r = [constants.EBTABLES_BIN, "--atomic-file", self.atomic_file]
if cmd: if cmd:
r.extend(cmd) r.extend(cmd)
return r return r
def lastupdate(self, wlan): def lastupdate(self, wlan):
''' Return the time elapsed since this WLAN was last updated. """
''' Return the time elapsed since this WLAN was last updated.
:param wlan: wlan entity
:return: elpased time
:rtype: float
"""
try: try:
elapsed = time.time() - self.last_update_time[wlan] elapsed = time.time() - self.last_update_time[wlan]
except KeyError: except KeyError:
self.last_update_time[wlan] = time.time() self.last_update_time[wlan] = time.time()
elapsed = 0.0 elapsed = 0.0
return elapsed return elapsed
def updated(self, wlan): def updated(self, wlan):
''' Keep track of when this WLAN was last updated. """
''' Keep track of when this WLAN was last updated.
:param wlan: wlan entity
:return: nothing
"""
self.last_update_time[wlan] = time.time() self.last_update_time[wlan] = time.time()
self.updates.remove(wlan) self.updates.remove(wlan)
def updateloop(self): def updateloop(self):
''' Thread target that looks for WLANs needing update, and """
Thread target that looks for WLANs needing update, and
rate limits the amount of ebtables activity. Only one userspace program rate limits the amount of ebtables activity. Only one userspace program
should use ebtables at any given time, or results can be unpredictable. should use ebtables at any given time, or results can be unpredictable.
'''
:return: nothing
"""
while self.doupdateloop: while self.doupdateloop:
self.updatelock.acquire() self.updatelock.acquire()
for wlan in self.updates: for wlan in self.updates:
''' """
Check if wlan is from a previously closed session. Because of the Check if wlan is from a previously closed session. Because of the
rate limiting scheme employed here, this may happen if a new session rate limiting scheme employed here, this may happen if a new session
is started soon after closing a previous session. is started soon after closing a previous session.
''' """
try: try:
wlan.session wlan.session
except: except:
@ -123,60 +154,68 @@ class EbtablesQueue(object):
continue continue
if self.lastupdate(wlan) > self.rate: if self.lastupdate(wlan) > self.rate:
self.buildcmds(wlan) self.buildcmds(wlan)
#print "ebtables commit %d rules" % len(self.cmds) # print "ebtables commit %d rules" % len(self.cmds)
self.ebcommit(wlan) self.ebcommit(wlan)
self.updated(wlan) self.updated(wlan)
self.updatelock.release() self.updatelock.release()
time.sleep(self.rate) time.sleep(self.rate)
def ebcommit(self, wlan): def ebcommit(self, wlan):
''' Perform ebtables atomic commit using commands built in the """
self.cmds list. Perform ebtables atomic commit using commands built in the self.cmds list.
'''
:return: nothing
"""
# save kernel ebtables snapshot to a file # save kernel ebtables snapshot to a file
cmd = self.ebatomiccmd(["--atomic-save",]) cmd = self.ebatomiccmd(["--atomic-save", ])
try: try:
check_call(cmd) subprocess.check_call(cmd)
except Exception, e: except subprocess.CalledProcessError:
self.eberror(wlan, "atomic-save (%s)" % cmd, e) logger.exception("atomic-save (%s)", cmd)
# no atomic file, exit # no atomic file, exit
return return
# modify the table file using queued ebtables commands # modify the table file using queued ebtables commands
for c in self.cmds: for c in self.cmds:
cmd = self.ebatomiccmd(c) cmd = self.ebatomiccmd(c)
try: try:
check_call(cmd) subprocess.check_call(cmd)
except Exception, e: except subprocess.CalledProcessError:
self.eberror(wlan, "cmd=%s" % cmd, e) logger.exception("cmd=%s", cmd)
pass
self.cmds = [] self.cmds = []
# commit the table file to the kernel # commit the table file to the kernel
cmd = self.ebatomiccmd(["--atomic-commit",]) cmd = self.ebatomiccmd(["--atomic-commit", ])
try: try:
check_call(cmd) subprocess.check_call(cmd)
os.unlink(self.atomic_file) os.unlink(self.atomic_file)
except Exception, e: except OSError:
self.eberror(wlan, "atomic-commit (%s)" % cmd, e) logger.exception("atomic-commit (%s)", cmd)
def ebchange(self, wlan): def ebchange(self, wlan):
''' Flag a change to the given WLAN's _linked dict, so the ebtables """
Flag a change to the given WLAN's _linked dict, so the ebtables
chain will be rebuilt at the next interval. chain will be rebuilt at the next interval.
'''
:return: nothing
"""
self.updatelock.acquire() self.updatelock.acquire()
if wlan not in self.updates: if wlan not in self.updates:
self.updates.append(wlan) self.updates.append(wlan)
self.updatelock.release() self.updatelock.release()
def buildcmds(self, wlan): def buildcmds(self, wlan):
''' Inspect a _linked dict from a wlan, and rebuild the ebtables chain """
for that WLAN. Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN.
'''
:return: nothing
"""
wlan._linked_lock.acquire() wlan._linked_lock.acquire()
# flush the chain # flush the chain
self.cmds.extend([["-F", wlan.brname],]) self.cmds.extend([["-F", wlan.brname], ])
# rebuild the chain # rebuild the chain
for (netif1, v) in wlan._linked.items(): for netif1, v in wlan._linked.items():
for (netif2, linked) in v.items(): for netif2, linked in v.items():
if wlan.policy == "DROP" and linked: if wlan.policy == "DROP" and linked:
self.cmds.extend([["-A", wlan.brname, "-i", netif1.localname, self.cmds.extend([["-A", wlan.brname, "-i", netif1.localname,
"-o", netif2.localname, "-j", "ACCEPT"], "-o", netif2.localname, "-j", "ACCEPT"],
@ -189,40 +228,48 @@ class EbtablesQueue(object):
"-i", netif2.localname, "-j", "DROP"]]) "-i", netif2.localname, "-j", "DROP"]])
wlan._linked_lock.release() wlan._linked_lock.release()
def eberror(self, wlan, source, error):
''' Log an ebtables command error and send an exception.
'''
if not wlan:
return
wlan.exception(coreapi.CORE_EXCP_LEVEL_ERROR, wlan.brname,
"ebtables command error: %s\n%s\n" % (source, error))
# a global object because all WLANs share the same queue # a global object because all WLANs share the same queue
# cannot have multiple threads invoking the ebtables commnd # cannot have multiple threads invoking the ebtables commnd
ebq = EbtablesQueue() ebq = EbtablesQueue()
def ebtablescmds(call, cmds): def ebtablescmds(call, cmds):
ebtables_lock.acquire() """
try: Run ebtable commands.
:param func call: function to call commands
:param list cmds: commands to call
:return: nothing
"""
with ebtables_lock:
for cmd in cmds: for cmd in cmds:
call(cmd) call(cmd)
finally:
ebtables_lock.release()
class LxBrNet(PyCoreNet): class LxBrNet(PyCoreNet):
"""
Provides linux bridge network functionlity for core nodes.
"""
policy = "DROP" policy = "DROP"
def __init__(self, session, objid = None, name = None, verbose = False, def __init__(self, session, objid=None, name=None, start=True, policy=None):
start = True, policy = None): """
PyCoreNet.__init__(self, session, objid, name, verbose, start) Creates a LxBrNet instance.
:param core.session.Session session: core session instance
:param int objid: object id
:param str name: object name
:param bool start: start flag
:param policy: network policy
"""
PyCoreNet.__init__(self, session, objid, name, start)
if name is None: if name is None:
name = str(self.objid) name = str(self.objid)
if policy is not None: if policy is not None:
self.policy = policy self.policy = policy
self.name = name self.name = name
sessionid = self.session.shortsessionid() sessionid = self.session.short_session_id()
self.brname = "b.%s.%s" % (str(self.objid), sessionid) self.brname = "b.%s.%s" % (str(self.objid), sessionid)
self.up = False self.up = False
if start: if start:
@ -230,42 +277,50 @@ class LxBrNet(PyCoreNet):
ebq.startupdateloop(self) ebq.startupdateloop(self)
def startup(self): def startup(self):
"""
Linux bridge starup logic.
:return: nothing
"""
try: try:
check_call([BRCTL_BIN, "addbr", self.brname]) subprocess.check_call([constants.BRCTL_BIN, "addbr", self.brname])
except Exception, e: except subprocess.CalledProcessError:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, self.brname, logger.exception("Error adding bridge")
"Error adding bridge: %s" % e)
try: try:
# turn off spanning tree protocol and forwarding delay # turn off spanning tree protocol and forwarding delay
check_call([BRCTL_BIN, "stp", self.brname, "off"]) subprocess.check_call([constants.BRCTL_BIN, "stp", self.brname, "off"])
check_call([BRCTL_BIN, "setfd", self.brname, "0"]) subprocess.check_call([constants.BRCTL_BIN, "setfd", self.brname, "0"])
check_call([IP_BIN, "link", "set", self.brname, "up"]) subprocess.check_call([constants.IP_BIN, "link", "set", self.brname, "up"])
# create a new ebtables chain for this bridge # create a new ebtables chain for this bridge
ebtablescmds(check_call, [ ebtablescmds(subprocess.check_call, [
[EBTABLES_BIN, "-N", self.brname, "-P", self.policy], [constants.EBTABLES_BIN, "-N", self.brname, "-P", self.policy],
[EBTABLES_BIN, "-A", "FORWARD", [constants.EBTABLES_BIN, "-A", "FORWARD", "--logical-in", self.brname, "-j", self.brname]
"--logical-in", self.brname, "-j", self.brname]]) ])
# turn off multicast snooping so mcast forwarding occurs w/o IGMP joins # turn off multicast snooping so mcast forwarding occurs w/o IGMP joins
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % \ snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % self.brname
self.brname
if os.path.exists(snoop): if os.path.exists(snoop):
open(snoop, "w").write('0') open(snoop, "w").write('0')
except Exception, e: except subprocess.CalledProcessError:
self.exception(coreapi.CORE_EXCP_LEVEL_WARNING, self.brname, logger.exception("Error setting bridge parameters")
"Error setting bridge parameters: %s" % e)
self.up = True self.up = True
def shutdown(self): def shutdown(self):
"""
Linux bridge shutdown logic.
:return: nothing
"""
if not self.up: if not self.up:
return return
ebq.stopupdateloop(self) ebq.stopupdateloop(self)
mutecall([IP_BIN, "link", "set", self.brname, "down"]) utils.mutecall([constants.IP_BIN, "link", "set", self.brname, "down"])
mutecall([BRCTL_BIN, "delbr", self.brname]) utils.mutecall([constants.BRCTL_BIN, "delbr", self.brname])
ebtablescmds(mutecall, [ ebtablescmds(utils.mutecall, [
[EBTABLES_BIN, "-D", "FORWARD", [constants.EBTABLES_BIN, "-D", "FORWARD",
"--logical-in", self.brname, "-j", self.brname], "--logical-in", self.brname, "-j", self.brname],
[EBTABLES_BIN, "-X", self.brname]]) [constants.EBTABLES_BIN, "-X", self.brname]])
for netif in self.netifs(): for netif in self.netifs():
# removes veth pairs used for bridge-to-bridge connections # removes veth pairs used for bridge-to-bridge connections
netif.shutdown() netif.shutdown()
@ -275,34 +330,52 @@ class LxBrNet(PyCoreNet):
self.up = False self.up = False
def attach(self, netif): def attach(self, netif):
"""
Attach a network interface.
:param core.netns.vif.VEth netif: network interface to attach
:return: nothing
"""
if self.up: if self.up:
try: try:
check_call([BRCTL_BIN, "addif", self.brname, netif.localname]) subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, netif.localname])
check_call([IP_BIN, "link", "set", netif.localname, "up"]) subprocess.check_call([constants.IP_BIN, "link", "set", netif.localname, "up"])
except Exception, e: except subprocess.CalledProcessError:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname, logger.exception("Error joining interface %s to bridge %s", netif.localname, self.brname)
"Error joining interface %s to bridge %s: %s" % \
(netif.localname, self.brname, e))
return return
PyCoreNet.attach(self, netif) PyCoreNet.attach(self, netif)
def detach(self, netif): def detach(self, netif):
"""
Detach a network interface.
:param core.netns.vif.Veth netif: network interface to detach
:return: nothing
"""
if self.up: if self.up:
try: try:
check_call([BRCTL_BIN, "delif", self.brname, netif.localname]) subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, netif.localname])
except Exception, e: except subprocess.CalledProcessError:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname, logger.exception("Error removing interface %s from bridge %s", netif.localname, self.brname)
"Error removing interface %s from bridge %s: %s" % \
(netif.localname, self.brname, e))
return return
PyCoreNet.detach(self, netif) PyCoreNet.detach(self, netif)
def linked(self, netif1, netif2): def linked(self, netif1, netif2):
"""
Determine if the provided network interfaces are linked.
:param core.netns.vif.Veth netif1: interface one
:param core.netns.vif.Veth netif2: interface two
:return: True if interfaces are linked, False otherwise
:rtype: bool
"""
# check if the network interfaces are attached to this network # check if the network interfaces are attached to this network
if self._netif[netif1.netifi] != netif1: if self._netif[netif1.netifi] != netif1:
raise ValueError, "inconsistency for netif %s" % netif1.name raise ValueError("inconsistency for netif %s" % netif1.name)
if self._netif[netif2.netifi] != netif2: if self._netif[netif2.netifi] != netif2:
raise ValueError, "inconsistency for netif %s" % netif2.name raise ValueError("inconsistency for netif %s" % netif2.name)
try: try:
linked = self._linked[netif1][netif2] linked = self._linked[netif1][netif2]
except KeyError: except KeyError:
@ -311,14 +384,20 @@ class LxBrNet(PyCoreNet):
elif self.policy == "DROP": elif self.policy == "DROP":
linked = False linked = False
else: else:
raise Exception, "unknown policy: %s" % self.policy raise Exception("unknown policy: %s" % self.policy)
self._linked[netif1][netif2] = linked self._linked[netif1][netif2] = linked
return linked return linked
def unlink(self, netif1, netif2): def unlink(self, netif1, netif2):
''' Unlink two PyCoreNetIfs, resulting in adding or removing ebtables """
Unlink two PyCoreNetIfs, resulting in adding or removing ebtables
filtering rules. filtering rules.
'''
:param core.netns.vif.Veth netif1: interface one
:param core.netns.vif.Veth netif2: interface two
:return: nothing
"""
self._linked_lock.acquire() self._linked_lock.acquire()
if not self.linked(netif1, netif2): if not self.linked(netif1, netif2):
self._linked_lock.release() self._linked_lock.release()
@ -328,9 +407,14 @@ class LxBrNet(PyCoreNet):
ebq.ebchange(self) ebq.ebchange(self)
def link(self, netif1, netif2): def link(self, netif1, netif2):
''' Link two PyCoreNetIfs together, resulting in adding or removing """
Link two PyCoreNetIfs together, resulting in adding or removing
ebtables filtering rules. ebtables filtering rules.
'''
:param core.netns.vif.Veth netif1: interface one
:param core.netns.vif.Veth netif2: interface two
:return: nothing
"""
self._linked_lock.acquire() self._linked_lock.acquire()
if self.linked(netif1, netif2): if self.linked(netif1, netif2):
self._linked_lock.release() self._linked_lock.release()
@ -339,37 +423,45 @@ class LxBrNet(PyCoreNet):
self._linked_lock.release() self._linked_lock.release()
ebq.ebchange(self) ebq.ebchange(self)
def linkconfig(self, netif, bw = None, delay = None, def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None,
loss = None, duplicate = None, jitter = None, netif2 = None, jitter=None, netif2=None, devname=None):
devname = None): """
''' Configure link parameters by applying tc queuing disciplines on the Configure link parameters by applying tc queuing disciplines on the interface.
interface.
''' :param core.netns.vif.Veth netif: interface one
:param bw: bandwidth to set to
:param delay: packet delay to set to
:param loss: packet loss to set to
:param duplicate: duplicate percentage to set to
:param jitter: jitter to set to
:param core.netns.vif.Veth netif2: interface two
:param devname: device name
:return: nothing
"""
if devname is None: if devname is None:
devname = netif.localname devname = netif.localname
tc = [TC_BIN, "qdisc", "replace", "dev", devname] tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname]
parent = ["root"] parent = ["root"]
changed = False changed = False
if netif.setparam('bw', bw): if netif.setparam('bw', bw):
# from tc-tbf(8): minimum value for burst is rate / kernel_hz # from tc-tbf(8): minimum value for burst is rate / kernel_hz
if bw is not None: if bw is not None:
burst = max(2 * netif.mtu, bw / 1000) burst = max(2 * netif.mtu, bw / 1000)
limit = 0xffff # max IP payload # max IP payload
limit = 0xffff
tbf = ["tbf", "rate", str(bw), tbf = ["tbf", "rate", str(bw),
"burst", str(burst), "limit", str(limit)] "burst", str(burst), "limit", str(limit)]
if bw > 0: if bw > 0:
if self.up: if self.up:
if (self.verbose): logger.info("linkconfig: %s" % ([tc + parent + ["handle", "1:"] + tbf],))
self.info("linkconfig: %s" % \ subprocess.check_call(tc + parent + ["handle", "1:"] + tbf)
([tc + parent + ["handle", "1:"] + tbf],))
check_call(tc + parent + ["handle", "1:"] + tbf)
netif.setparam('has_tbf', True) netif.setparam('has_tbf', True)
changed = True changed = True
elif netif.getparam('has_tbf') and bw <= 0: elif netif.getparam('has_tbf') and bw <= 0:
tcd = [] + tc tcd = [] + tc
tcd[2] = "delete" tcd[2] = "delete"
if self.up: if self.up:
check_call(tcd + parent) subprocess.check_call(tcd + parent)
netif.setparam('has_tbf', False) netif.setparam('has_tbf', False)
# removing the parent removes the child # removing the parent removes the child
netif.setparam('has_netem', False) netif.setparam('has_netem', False)
@ -406,47 +498,47 @@ class LxBrNet(PyCoreNet):
return return
tc[2] = "delete" tc[2] = "delete"
if self.up: if self.up:
if self.verbose: logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"]],))
self.info("linkconfig: %s" % \ subprocess.check_call(tc + parent + ["handle", "10:"])
([tc + parent + ["handle", "10:"]],))
check_call(tc + parent + ["handle", "10:"])
netif.setparam('has_netem', False) netif.setparam('has_netem', False)
elif len(netem) > 1: elif len(netem) > 1:
if self.up: if self.up:
if self.verbose: logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem],))
self.info("linkconfig: %s" % \ subprocess.check_call(tc + parent + ["handle", "10:"] + netem)
([tc + parent + ["handle", "10:"] + netem],))
check_call(tc + parent + ["handle", "10:"] + netem)
netif.setparam('has_netem', True) netif.setparam('has_netem', True)
def linknet(self, net): def linknet(self, net):
''' Link this bridge with another by creating a veth pair and installing """
Link this bridge with another by creating a veth pair and installing
each device into each bridge. each device into each bridge.
'''
sessionid = self.session.shortsessionid() :param core.netns.vnet.LxBrNet net: network to link with
:return: created interface
:rtype: Veth
"""
sessionid = self.session.short_session_id()
try: try:
self_objid = '%x' % self.objid self_objid = "%x" % self.objid
except TypeError: except TypeError:
self_objid = '%s' % self.objid self_objid = "%s" % self.objid
try: try:
net_objid = '%x' % net.objid net_objid = "%x" % net.objid
except TypeError: except TypeError:
net_objid = '%s' % net.objid net_objid = "%s" % net.objid
localname = 'veth%s.%s.%s' % (self_objid, net_objid, sessionid) localname = "veth%s.%s.%s" % (self_objid, net_objid, sessionid)
if len(localname) >= 16: if len(localname) >= 16:
raise ValueError, "interface local name '%s' too long" % \ raise ValueError("interface local name %s too long" % localname)
localname name = "veth%s.%s.%s" % (net_objid, self_objid, sessionid)
name = 'veth%s.%s.%s' % (net_objid, self_objid, sessionid)
if len(name) >= 16: if len(name) >= 16:
raise ValueError, "interface name '%s' too long" % name raise ValueError("interface name %s too long" % name)
netif = VEth(node = None, name = name, localname = localname, netif = VEth(node=None, name=name, localname=localname,
mtu = 1500, net = self, start = self.up) mtu=1500, net=self, start=self.up)
self.attach(netif) self.attach(netif)
if net.up: if net.up:
# this is similar to net.attach() but uses netif.name instead # this is similar to net.attach() but uses netif.name instead
# of localname # of localname
check_call([BRCTL_BIN, "addif", net.brname, netif.name]) subprocess.check_call([constants.BRCTL_BIN, "addif", net.brname, netif.name])
check_call([IP_BIN, "link", "set", netif.name, "up"]) subprocess.check_call([constants.IP_BIN, "link", "set", netif.name, "up"])
i = net.newifindex() i = net.newifindex()
net._netif[i] = netif net._netif[i] = netif
with net._linked_lock: with net._linked_lock:
@ -456,36 +548,58 @@ class LxBrNet(PyCoreNet):
return netif return netif
def getlinknetif(self, net): def getlinknetif(self, net):
''' Return the interface of that links this net with another net """
Return the interface of that links this net with another net
(that were linked using linknet()). (that were linked using linknet()).
'''
:param core.netns.vnet.LxBrNet net: interface to get link for
:return: interface the provided network is linked to
:rtype: core.netns.vnet.LxBrNet
"""
for netif in self.netifs(): for netif in self.netifs():
if hasattr(netif, 'othernet') and netif.othernet == net: if hasattr(netif, "othernet") and netif.othernet == net:
return netif return netif
return None return None
def addrconfig(self, addrlist): def addrconfig(self, addrlist):
''' Set addresses on the bridge. """
''' Set addresses on the bridge.
:param list[str] addrlist: address list
:return: nothing
"""
if not self.up: if not self.up:
return return
for addr in addrlist: for addr in addrlist:
try: try:
check_call([IP_BIN, "addr", "add", str(addr), "dev", self.brname]) subprocess.check_call([constants.IP_BIN, "addr", "add", str(addr), "dev", self.brname])
except Exception, e: except subprocess.CalledProcessError:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname, logger.exception("Error adding IP address")
"Error adding IP address: %s" % e)
class GreTapBridge(LxBrNet): class GreTapBridge(LxBrNet):
''' A network consisting of a bridge with a gretap device for tunneling to """
A network consisting of a bridge with a gretap device for tunneling to
another system. another system.
''' """
def __init__(self, session, remoteip = None, objid = None, name = None,
policy = "ACCEPT", localip = None, ttl = 255, key = None, def __init__(self, session, remoteip=None, objid=None, name=None,
verbose = False, start = True): policy="ACCEPT", localip=None, ttl=255, key=None, start=True):
LxBrNet.__init__(self, session = session, objid = objid, """
name = name, verbose = verbose, policy = policy, Create a GreTapBridge instance.
start = False)
:param core.session.Session session: core session instance
:param str remoteip: remote address
:param int objid: object id
:param str name: object name
:param policy: network policy
:param str localip: local address
:param ttl: ttl value
:param key: gre tap key
:param bool start: start flag
:return:
"""
LxBrNet.__init__(self, session=session, objid=objid, name=name, policy=policy, start=False)
self.grekey = key self.grekey = key
if self.grekey is None: if self.grekey is None:
self.grekey = self.session.sessionid ^ self.objid self.grekey = self.session.sessionid ^ self.objid
@ -497,22 +611,27 @@ class GreTapBridge(LxBrNet):
if remoteip is None: if remoteip is None:
self.gretap = None self.gretap = None
else: else:
self.gretap = GreTap(node = self, name = None, session = session, self.gretap = GreTap(node=self, name=None, session=session, remoteip=remoteip,
remoteip = remoteip, objid = None, localip = localip, ttl = ttl, objid=None, localip=localip, ttl=ttl, key=self.grekey)
key = self.grekey)
if start: if start:
self.startup() self.startup()
def startup(self): def startup(self):
''' Creates a bridge and adds the gretap device to it. """
''' Creates a bridge and adds the gretap device to it.
:return: nothing
"""
LxBrNet.startup(self) LxBrNet.startup(self)
if self.gretap: if self.gretap:
self.attach(self.gretap) self.attach(self.gretap)
def shutdown(self): def shutdown(self):
''' Detach the gretap device and remove the bridge. """
''' Detach the gretap device and remove the bridge.
:return: nothing
"""
if self.gretap: if self.gretap:
self.detach(self.gretap) self.detach(self.gretap)
self.gretap.shutdown() self.gretap.shutdown()
@ -520,24 +639,31 @@ class GreTapBridge(LxBrNet):
LxBrNet.shutdown(self) LxBrNet.shutdown(self)
def addrconfig(self, addrlist): def addrconfig(self, addrlist):
''' Set the remote tunnel endpoint. This is a one-time method for """
Set the remote tunnel endpoint. This is a one-time method for
creating the GreTap device, which requires the remoteip at startup. creating the GreTap device, which requires the remoteip at startup.
The 1st address in the provided list is remoteip, 2nd optionally The 1st address in the provided list is remoteip, 2nd optionally
specifies localip. specifies localip.
'''
:param list addrlist: address list
:return: nothing
"""
if self.gretap: if self.gretap:
raise ValueError, "gretap already exists for %s" % self.name raise ValueError("gretap already exists for %s" % self.name)
remoteip = addrlist[0].split('/')[0] remoteip = addrlist[0].split('/')[0]
localip = None localip = None
if len(addrlist) > 1: if len(addrlist) > 1:
localip = addrlist[1].split('/')[0] localip = addrlist[1].split('/')[0]
self.gretap = GreTap(session = self.session, remoteip = remoteip, self.gretap = GreTap(session=self.session, remoteip=remoteip, objid=None, name=None,
objid = None, name = None, localip=localip, ttl=self.ttl, key=self.grekey)
localip = localip, ttl = self.ttl, key = self.grekey)
self.attach(self.gretap) self.attach(self.gretap)
def setkey(self, key): def setkey(self, key):
''' Set the GRE key used for the GreTap device. This needs to be set """
Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig). prior to instantiating the GreTap device (before addrconfig).
'''
:param key: gre key
:return: nothing
"""
self.grekey = key self.grekey = key

View file

@ -1,35 +1,50 @@
# """
# CORE PyCoreNode and LxcNode classes that implement the network namespac virtual node.
# Copyright (c)2010-2012 the Boeing Company. """
# See the LICENSE file included in this distribution.
#
# authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
vnode.py: PyCoreNode and LxcNode classes that implement the network namespace
virtual node.
'''
import os, signal, sys, subprocess, vnodeclient, threading, string, shutil import os
import random, time import random
from core.api import coreapi import shutil
from core.misc.utils import * import signal
from core.constants import * import string
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position import subprocess
from core.netns.vif import VEth, TunTap import threading
from core.emane.nodes import EmaneNode
from core import constants
from core.coreobj import PyCoreNetIf
from core.coreobj import PyCoreNode
from core.enumerations import NodeTypes
from core.misc import log
from core.misc import nodeutils
from core.misc import utils
from core.netns import vnodeclient
from core.netns.vif import TunTap
from core.netns.vif import VEth
logger = log.get_logger(__name__)
utils.check_executables([constants.IP_BIN])
checkexec([IP_BIN])
class SimpleLxcNode(PyCoreNode): class SimpleLxcNode(PyCoreNode):
def __init__(self, session, objid = None, name = None, nodedir = None, """
verbose = False, start = True): Provides simple lxc functionality for core nodes.
PyCoreNode.__init__(self, session, objid, name, verbose=verbose, """
start=start) valid_deladdrtype = ("inet", "inet6", "inet6link")
def __init__(self, session, objid=None, name=None, nodedir=None, start=True):
"""
Create a SimpleLxcNode instance.
:param core.session.Session session: core session instance
:param int objid: object id
:param str name: object name
:param str nodedir: node directory
:param bool start: start flag
"""
PyCoreNode.__init__(self, session, objid, name, start=start)
self.nodedir = nodedir self.nodedir = nodedir
self.ctrlchnlname = \ self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name))
os.path.abspath(os.path.join(self.session.sessiondir, self.name))
self.vnodeclient = None self.vnodeclient = None
self.pid = None self.pid = None
self.up = False self.up = False
@ -37,234 +52,411 @@ class SimpleLxcNode(PyCoreNode):
self._mounts = [] self._mounts = []
def alive(self): def alive(self):
"""
Check if the node is alive.
:return: True if node is alive, False otherwise
:rtype: bool
"""
try: try:
os.kill(self.pid, 0) os.kill(self.pid, 0)
except OSError: except OSError:
return False return False
return True return True
def startup(self): def startup(self):
''' Start a new namespace node by invoking the vnoded process that """
Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set allocates a new namespace. Bring up the loopback device and set
the hostname. the hostname.
'''
:return: nothing
"""
if self.up: if self.up:
raise Exception, "already up" raise Exception("already up")
vnoded = ["%s/vnoded" % CORE_SBIN_DIR, "-v", "-c", self.ctrlchnlname, vnoded = ["%s/vnoded" % constants.CORE_SBIN_DIR, "-v", "-c", self.ctrlchnlname,
"-l", self.ctrlchnlname + ".log", "-l", self.ctrlchnlname + ".log",
"-p", self.ctrlchnlname + ".pid"] "-p", self.ctrlchnlname + ".pid"]
if self.nodedir: if self.nodedir:
vnoded += ["-C", self.nodedir] vnoded += ["-C", self.nodedir]
env = self.session.getenviron(state=False) env = self.session.get_environment(state=False)
env['NODE_NUMBER'] = str(self.objid) env['NODE_NUMBER'] = str(self.objid)
env['NODE_NAME'] = str(self.name) env['NODE_NAME'] = str(self.name)
try: try:
tmp = subprocess.Popen(vnoded, stdout = subprocess.PIPE, env = env) tmp = subprocess.Popen(vnoded, stdout=subprocess.PIPE, env=env)
except OSError, e: except OSError:
msg = "error running vnoded command: %s (%s)" % (vnoded, e) msg = "error running vnoded command: %s" % vnoded
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, logger.exception("SimpleLxcNode.startup(): %s", msg)
"SimpleLxcNode.startup()", msg) raise Exception(msg)
raise Exception, msg
try: try:
self.pid = int(tmp.stdout.read()) self.pid = int(tmp.stdout.read())
tmp.stdout.close() tmp.stdout.close()
except Exception: except ValueError:
msg = "vnoded failed to create a namespace; " msg = "vnoded failed to create a namespace; "
msg += "check kernel support and user priveleges" msg += "check kernel support and user priveleges"
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, logger.exception("SimpleLxcNode.startup(): %s", msg)
"SimpleLxcNode.startup()", msg)
if tmp.wait(): if tmp.wait():
raise Exception, ("command failed: %s" % vnoded) raise Exception("command failed: %s" % vnoded)
self.vnodeclient = vnodeclient.VnodeClient(self.name,
self.ctrlchnlname) self.vnodeclient = vnodeclient.VnodeClient(self.name, self.ctrlchnlname)
self.info("bringing up loopback interface") logger.info("bringing up loopback interface")
self.cmd([IP_BIN, "link", "set", "lo", "up"]) self.cmd([constants.IP_BIN, "link", "set", "lo", "up"])
self.info("setting hostname: %s" % self.name) logger.info("setting hostname: %s" % self.name)
self.cmd(["hostname", self.name]) self.cmd(["hostname", self.name])
self.up = True self.up = True
def shutdown(self): def shutdown(self):
"""
Shutdown logic for simple lxc nodes.
:return: nothing
"""
# nothing to do if node is not up
if not self.up: if not self.up:
return return
# unmount all targets
while self._mounts: while self._mounts:
source, target = self._mounts.pop(-1) source, target = self._mounts.pop(-1)
self.umount(target) self.umount(target)
# shutdown all interfaces
for netif in self.netifs(): for netif in self.netifs():
netif.shutdown() netif.shutdown()
# attempt to kill node process and wait for termination of children
try: try:
os.kill(self.pid, signal.SIGTERM) os.kill(self.pid, signal.SIGTERM)
os.waitpid(self.pid, 0) os.waitpid(self.pid, 0)
except OSError: except OSError:
pass logger.exception("error killing process")
# remove node directory if present
try: try:
if os.path.exists(self.ctrlchnlname):
os.unlink(self.ctrlchnlname) os.unlink(self.ctrlchnlname)
except OSError: except OSError:
pass logger.exception("error removing file")
# clear interface data, close client, and mark self and not up
self._netif.clear() self._netif.clear()
self.vnodeclient.close() self.vnodeclient.close()
self.up = False self.up = False
def cmd(self, args, wait = True): # TODO: potentially remove all these wrapper methods, just make use of object itself.
def cmd(self, args, wait=True):
"""
Wrapper around vnodeclient cmd.
:param args: arguments for ocmmand
:param wait: wait or not
:return:
"""
return self.vnodeclient.cmd(args, wait) return self.vnodeclient.cmd(args, wait)
def cmdresult(self, args): def cmdresult(self, args):
"""
Wrapper around vnodeclient cmdresult.
:param args: arguments for ocmmand
:return:
"""
return self.vnodeclient.cmdresult(args) return self.vnodeclient.cmdresult(args)
def popen(self, args): def popen(self, args):
"""
Wrapper around vnodeclient popen.
:param args: arguments for ocmmand
:return:
"""
return self.vnodeclient.popen(args) return self.vnodeclient.popen(args)
def icmd(self, args): def icmd(self, args):
"""
Wrapper around vnodeclient icmd.
:param args: arguments for ocmmand
:return:
"""
return self.vnodeclient.icmd(args) return self.vnodeclient.icmd(args)
def redircmd(self, infd, outfd, errfd, args, wait = True): def redircmd(self, infd, outfd, errfd, args, wait=True):
"""
Wrapper around vnodeclient redircmd.
:param infd: input file descriptor
:param outfd: output file descriptor
:param errfd: err file descriptor
:param args: command arguments
:param wait: wait or not
:return:
"""
return self.vnodeclient.redircmd(infd, outfd, errfd, args, wait) return self.vnodeclient.redircmd(infd, outfd, errfd, args, wait)
def term(self, sh = "/bin/sh"): def term(self, sh="/bin/sh"):
return self.vnodeclient.term(sh = sh) """
Wrapper around vnodeclient term.
def termcmdstring(self, sh = "/bin/sh"): :param sh: shell to create terminal for
return self.vnodeclient.termcmdstring(sh = sh) :return:
"""
return self.vnodeclient.term(sh=sh)
def shcmd(self, cmdstr, sh = "/bin/sh"): def termcmdstring(self, sh="/bin/sh"):
return self.vnodeclient.shcmd(cmdstr, sh = sh) """
Wrapper around vnodeclient termcmdstring.
:param sh: shell to run command in
:return:
"""
return self.vnodeclient.termcmdstring(sh=sh)
def shcmd(self, cmdstr, sh="/bin/sh"):
"""
Wrapper around vnodeclient shcmd.
:param str cmdstr: command string
:param sh: shell to run command in
:return:
"""
return self.vnodeclient.shcmd(cmdstr, sh=sh)
def boot(self): def boot(self):
"""
Boot logic.
:return: nothing
"""
pass pass
def mount(self, source, target): def mount(self, source, target):
"""
Create and mount a directory.
:param str source: source directory to mount
:param str target: target directory to create
:return: nothing
"""
source = os.path.abspath(source) source = os.path.abspath(source)
self.info("mounting %s at %s" % (source, target)) logger.info("mounting %s at %s" % (source, target))
try: try:
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % \ shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % (
(target, MOUNT_BIN, source, target) target, constants.MOUNT_BIN, source, target)
self.shcmd(shcmd) self.shcmd(shcmd)
self._mounts.append((source, target)) self._mounts.append((source, target))
except: except IOError:
self.warn("mounting failed for %s at %s" % (source, target)) logger.exception("mounting failed for %s at %s", source, target)
def umount(self, target): def umount(self, target):
self.info("unmounting '%s'" % target) """
Unmount a target directory.
:param str target: target directory to unmount
:return: nothing
"""
logger.info("unmounting '%s'" % target)
try: try:
self.cmd([UMOUNT_BIN, "-n", "-l", target]) self.cmd([constants.UMOUNT_BIN, "-n", "-l", target])
except: except IOError:
self.warn("unmounting failed for %s" % target) logger.exception("unmounting failed for %s" % target)
def newifindex(self): def newifindex(self):
with self.lock: """
return PyCoreNode.newifindex(self) Retrieve a new interface index.
def newveth(self, ifindex = None, ifname = None, net = None): :return: new interface index
:rtype: int
"""
with self.lock:
return super(SimpleLxcNode, self).newifindex()
def newveth(self, ifindex=None, ifname=None, net=None):
"""
Create a new interface.
:param int ifindex: index for the new interface
:param str ifname: name for the new interface
:param net: network to associate interface with
:return: nothing
"""
self.lock.acquire() self.lock.acquire()
try: try:
if ifindex is None: if ifindex is None:
ifindex = self.newifindex() ifindex = self.newifindex()
if ifname is None: if ifname is None:
ifname = "eth%d" % ifindex ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
try: try:
suffix = '%x.%s.%s' % (self.objid, ifindex, sessionid) suffix = '%x.%s.%s' % (self.objid, ifindex, sessionid)
except TypeError: except TypeError:
suffix = '%s.%s.%s' % (self.objid, ifindex, sessionid) suffix = '%s.%s.%s' % (self.objid, ifindex, sessionid)
localname = 'veth' + suffix localname = 'veth' + suffix
if len(localname) >= 16: if len(localname) >= 16:
raise ValueError, "interface local name '%s' too long" % \ raise ValueError("interface local name '%s' too long" % localname)
localname
name = localname + 'p' name = localname + 'p'
if len(name) >= 16: if len(name) >= 16:
raise ValueError, "interface name '%s' too long" % name raise ValueError, "interface name '%s' too long" % name
ifclass = VEth veth = VEth(node=self, name=name, localname=localname, mtu=1500, net=net, start=self.up)
veth = ifclass(node = self, name = name, localname = localname,
mtu = 1500, net = net, start = self.up)
if self.up: if self.up:
check_call([IP_BIN, "link", "set", veth.name, subprocess.check_call([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)])
"netns", str(self.pid)]) self.cmd([constants.IP_BIN, "link", "set", veth.name, "name", ifname])
self.cmd([IP_BIN, "link", "set", veth.name, "name", ifname])
veth.name = ifname veth.name = ifname
# retrieve interface information
result, output = self.cmdresult(["ip", "link", "show", veth.name])
logger.info("interface command output: %s", output)
output = output.split("\n")
veth.flow_id = int(output[0].strip().split(":")[0]) + 1
logger.info("interface flow index: %s - %s", veth.name, veth.flow_id)
veth.hwaddr = output[1].strip().split()[1]
logger.info("interface mac: %s - %s", veth.name, veth.hwaddr)
try: try:
self.addnetif(veth, ifindex) self.addnetif(veth, ifindex)
except: except:
veth.shutdown() veth.shutdown()
del veth del veth
raise raise
return ifindex return ifindex
finally: finally:
self.lock.release() self.lock.release()
def newtuntap(self, ifindex = None, ifname = None, net = None): def newtuntap(self, ifindex=None, ifname=None, net=None):
"""
Create a new tunnel tap.
:param int ifindex: interface index
:param str ifname: interface name
:param net: network to associate with
:return: interface index
:rtype: int
"""
self.lock.acquire() self.lock.acquire()
try: try:
if ifindex is None: if ifindex is None:
ifindex = self.newifindex() ifindex = self.newifindex()
if ifname is None: if ifname is None:
ifname = "eth%d" % ifindex ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid() sessionid = self.session.short_session_id()
localname = "tap%s.%s.%s" % (self.objid, ifindex, sessionid) localname = "tap%s.%s.%s" % (self.objid, ifindex, sessionid)
name = ifname name = ifname
ifclass = TunTap ifclass = TunTap
tuntap = ifclass(node = self, name = name, localname = localname, tuntap = ifclass(node=self, name=name, localname=localname,
mtu = 1500, net = net, start = self.up) mtu=1500, net=net, start=self.up)
try: try:
self.addnetif(tuntap, ifindex) self.addnetif(tuntap, ifindex)
except: except Exception as e:
tuntap.shutdown() tuntap.shutdown()
del tuntap del tuntap
raise raise e
return ifindex return ifindex
finally: finally:
self.lock.release() self.lock.release()
def sethwaddr(self, ifindex, addr): def sethwaddr(self, ifindex, addr):
"""
Set hardware addres for an interface.
:param int ifindex: index of interface to set hardware address for
:param core.misc.ipaddress.MacAddress addr: hardware address to set
:return: mothing
"""
self._netif[ifindex].sethwaddr(addr) self._netif[ifindex].sethwaddr(addr)
if self.up: if self.up:
(status, result) = self.cmdresult([IP_BIN, "link", "set", "dev", (status, result) = self.cmdresult([constants.IP_BIN, "link", "set", "dev",
self.ifname(ifindex), "address", str(addr)]) self.ifname(ifindex), "address", str(addr)])
if status: if status:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, logger.error("error setting MAC address %s", str(addr))
"SimpleLxcNode.sethwaddr()",
"error setting MAC address %s" % str(addr))
def addaddr(self, ifindex, addr): def addaddr(self, ifindex, addr):
"""
Add interface address.
:param int ifindex: index of interface to add address to
:param str addr: address to add to interface
:return: nothing
"""
if self.up: if self.up:
if ":" in str(addr): # check if addr is ipv6 if ":" in str(addr): # check if addr is ipv6
self.cmd([IP_BIN, "addr", "add", str(addr), self.cmd([constants.IP_BIN, "addr", "add", str(addr),
"dev", self.ifname(ifindex)]) "dev", self.ifname(ifindex)])
else: else:
self.cmd([IP_BIN, "addr", "add", str(addr), "broadcast", "+", self.cmd([constants.IP_BIN, "addr", "add", str(addr), "broadcast", "+",
"dev", self.ifname(ifindex)]) "dev", self.ifname(ifindex)])
self._netif[ifindex].addaddr(addr) self._netif[ifindex].addaddr(addr)
def deladdr(self, ifindex, addr): def deladdr(self, ifindex, addr):
"""
Delete address from an interface.
:param int ifindex: index of interface to delete address from
:param str addr: address to delete from interface
:return: nothing
"""
try: try:
self._netif[ifindex].deladdr(addr) self._netif[ifindex].deladdr(addr)
except ValueError: except ValueError:
self.warn("trying to delete unknown address: %s" % addr) logger.exception("trying to delete unknown address: %s" % addr)
if self.up:
self.cmd([IP_BIN, "addr", "del", str(addr),
"dev", self.ifname(ifindex)])
valid_deladdrtype = ("inet", "inet6", "inet6link") if self.up:
def delalladdr(self, ifindex, addrtypes = valid_deladdrtype): self.cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)])
addr = self.getaddr(self.ifname(ifindex), rescan = True)
def delalladdr(self, ifindex, addrtypes=valid_deladdrtype):
"""
Delete all addresses from an interface.
:param int ifindex: index of interface to delete all addresses from
:param tuple addrtypes: address types to delete
:return: nothing
"""
addr = self.getaddr(self.ifname(ifindex), rescan=True)
for t in addrtypes: for t in addrtypes:
if t not in self.valid_deladdrtype: if t not in self.valid_deladdrtype:
raise ValueError, "addr type must be in: " + \ raise ValueError("addr type must be in: " + " ".join(self.valid_deladdrtype))
" ".join(self.valid_deladdrtype)
for a in addr[t]: for a in addr[t]:
self.deladdr(ifindex, a) self.deladdr(ifindex, a)
# update cached information # update cached information
self.getaddr(self.ifname(ifindex), rescan = True) self.getaddr(self.ifname(ifindex), rescan=True)
def ifup(self, ifindex): def ifup(self, ifindex):
if self.up: """
self.cmd([IP_BIN, "link", "set", self.ifname(ifindex), "up"]) Bring an interface up.
def newnetif(self, net = None, addrlist = [], hwaddr = None, :param int ifindex: index of interface to bring up
ifindex = None, ifname = None): :return: nothing
"""
if self.up:
self.cmd([constants.IP_BIN, "link", "set", self.ifname(ifindex), "up"])
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
"""
Create a new network interface.
:param net: network to associate with
:param list addrlist: addresses to add on the interface
:param core.misc.ipaddress.MacAddress hwaddr: hardware address to set for interface
:param int ifindex: index of interface to create
:param str ifname: name for interface
:return: interface index
:rtype: int
"""
self.lock.acquire() self.lock.acquire()
try: try:
if isinstance(net, EmaneNode): # TODO: see if you can move this to emane specific code
ifindex = self.newtuntap(ifindex = ifindex, ifname = ifname, if nodeutils.is_node(net, NodeTypes.EMANE):
net = net) ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname, net=net)
# TUN/TAP is not ready for addressing yet; the device may # TUN/TAP is not ready for addressing yet; the device may
# take some time to appear, and installing it into a # take some time to appear, and installing it into a
# namespace after it has been bound removes addressing; # namespace after it has been bound removes addressing;
@ -272,143 +464,235 @@ class SimpleLxcNode(PyCoreNode):
self.attachnet(ifindex, net) self.attachnet(ifindex, net)
netif = self.netif(ifindex) netif = self.netif(ifindex)
netif.sethwaddr(hwaddr) netif.sethwaddr(hwaddr)
for addr in maketuple(addrlist): for addr in utils.maketuple(addrlist):
netif.addaddr(addr) netif.addaddr(addr)
return ifindex return ifindex
else: else:
ifindex = self.newveth(ifindex = ifindex, ifname = ifname, ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net)
net = net)
if net is not None: if net is not None:
self.attachnet(ifindex, net) self.attachnet(ifindex, net)
if hwaddr: if hwaddr:
self.sethwaddr(ifindex, hwaddr) self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist):
if addrlist:
for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr) self.addaddr(ifindex, addr)
self.ifup(ifindex) self.ifup(ifindex)
return ifindex return ifindex
finally: finally:
self.lock.release() self.lock.release()
def connectnode(self, ifname, othernode, otherifname): def connectnode(self, ifname, othernode, otherifname):
"""
Connect a node.
:param str ifname: name of interface to connect
:param core.netns.nodes.LxcNode othernode: node to connect to
:param str otherifname: interface name to connect to
:return: nothing
"""
tmplen = 8 tmplen = 8
tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase) tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase)
for x in xrange(tmplen)]) for x in xrange(tmplen)])
tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase) tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase)
for x in xrange(tmplen)]) for x in xrange(tmplen)])
check_call([IP_BIN, "link", "add", "name", tmp1, subprocess.check_call([constants.IP_BIN, "link", "add", "name", tmp1,
"type", "veth", "peer", "name", tmp2]) "type", "veth", "peer", "name", tmp2])
check_call([IP_BIN, "link", "set", tmp1, "netns", str(self.pid)]) subprocess.call([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
self.cmd([IP_BIN, "link", "set", tmp1, "name", ifname]) self.cmd([constants.IP_BIN, "link", "set", tmp1, "name", ifname])
self.addnetif(PyCoreNetIf(self, ifname), self.newifindex()) self.addnetif(PyCoreNetIf(self, ifname), self.newifindex())
check_call([IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)]) subprocess.check_call([constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)])
othernode.cmd([IP_BIN, "link", "set", tmp2, "name", otherifname]) othernode.cmd([constants.IP_BIN, "link", "set", tmp2, "name", otherifname])
othernode.addnetif(PyCoreNetIf(othernode, otherifname), othernode.addnetif(PyCoreNetIf(othernode, otherifname),
othernode.newifindex()) othernode.newifindex())
def addfile(self, srcname, filename): def addfile(self, srcname, filename):
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % \ """
(filename, srcname, filename) Add a file.
:param str srcname: source file name
:param str filename: file name to add
:return: nothing
"""
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % (filename, srcname, filename)
self.shcmd(shcmd) self.shcmd(shcmd)
def getaddr(self, ifname, rescan = False): def getaddr(self, ifname, rescan=False):
return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan) """
Wrapper around vnodeclient getaddr.
def netifstats(self, ifname = None): :param str ifname: interface name to get address for
return self.vnodeclient.netifstats(ifname = ifname) :param bool rescan: rescan flag
:return:
"""
return self.vnodeclient.getaddr(ifname=ifname, rescan=rescan)
def netifstats(self, ifname=None):
"""
Wrapper around vnodeclient netifstate.
:param str ifname: interface name to get state for
:return:
"""
return self.vnodeclient.netifstats(ifname=ifname)
class LxcNode(SimpleLxcNode): class LxcNode(SimpleLxcNode):
def __init__(self, session, objid = None, name = None, """
nodedir = None, bootsh = "boot.sh", verbose = False, Provides lcx node functionality for core nodes.
start = True): """
super(LxcNode, self).__init__(session = session, objid = objid,
name = name, nodedir = nodedir, def __init__(self, session, objid=None, name=None,
verbose = verbose, start = start) nodedir=None, bootsh="boot.sh", start=True):
"""
Create a LxcNode instance.
:param core.session.Session session: core session instance
:param int objid: object id
:param str name: object name
:param str nodedir: node directory
:param bootsh: boot shell
:param bool start: start flag
"""
super(LxcNode, self).__init__(session=session, objid=objid,
name=name, nodedir=nodedir, start=start)
self.bootsh = bootsh self.bootsh = bootsh
if start: if start:
self.startup() self.startup()
def boot(self): def boot(self):
"""
Boot the node.
:return: nothing
"""
self.session.services.bootnodeservices(self) self.session.services.bootnodeservices(self)
def validate(self): def validate(self):
"""
Validate the node.
:return: nothing
"""
self.session.services.validatenodeservices(self) self.session.services.validatenodeservices(self)
def startup(self): def startup(self):
"""
Startup logic for the node.
:return: nothing
"""
self.lock.acquire() self.lock.acquire()
try: try:
self.makenodedir() self.makenodedir()
super(LxcNode, self).startup() super(LxcNode, self).startup()
self.privatedir("/var/run") self.privatedir("/var/run")
self.privatedir("/var/log") self.privatedir("/var/log")
except OSError, e: except OSError:
self.warn("Error with LxcNode.startup(): %s" % e) logger.exception("error during LxcNode.startup()")
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
"LxcNode.startup()", "%s" % e)
finally: finally:
self.lock.release() self.lock.release()
def shutdown(self): def shutdown(self):
"""
Shutdown logic for the node.
:return: nothing
"""
if not self.up: if not self.up:
return return
self.lock.acquire() self.lock.acquire()
# services are instead stopped when session enters datacollect state # services are instead stopped when session enters datacollect state
#self.session.services.stopnodeservices(self) # self.session.services.stopnodeservices(self)
try: try:
super(LxcNode, self).shutdown() super(LxcNode, self).shutdown()
except:
logger.exception("error during shutdown")
finally: finally:
self.rmnodedir() self.rmnodedir()
self.lock.release() self.lock.release()
def privatedir(self, path): def privatedir(self, path):
"""
Create a private directory.
:param str path: path to create
:return: nothing
"""
if path[0] != "/": if path[0] != "/":
raise ValueError, "path not fully qualified: " + path raise ValueError("path not fully qualified: %s" % path)
hostpath = os.path.join(self.nodedir, hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.'))
os.path.normpath(path).strip('/').replace('/', '.'))
try: try:
os.mkdir(hostpath) os.mkdir(hostpath)
except OSError: except OSError:
pass logger.exception("error creating directory: %s", hostpath)
except Exception, e:
raise Exception, e
self.mount(hostpath, path) self.mount(hostpath, path)
def hostfilename(self, filename): def hostfilename(self, filename):
''' Return the name of a node's file on the host filesystem. """
''' Return the name of a node's file on the host filesystem.
:param str filename: host file name
:return: path to file
"""
dirname, basename = os.path.split(filename) dirname, basename = os.path.split(filename)
if not basename: if not basename:
raise ValueError, "no basename for filename: " + filename raise ValueError("no basename for filename: " + filename)
if dirname and dirname[0] == "/": if dirname and dirname[0] == "/":
dirname = dirname[1:] dirname = dirname[1:]
dirname = dirname.replace("/", ".") dirname = dirname.replace("/", ".")
dirname = os.path.join(self.nodedir, dirname) dirname = os.path.join(self.nodedir, dirname)
return os.path.join(dirname, basename) return os.path.join(dirname, basename)
def opennodefile(self, filename, mode = "w"): def opennodefile(self, filename, mode="w"):
"""
Open a node file, within it's directory.
:param str filename: file name to open
:param str mode: mode to open file in
:return: open file
:rtype: file
"""
hostfilename = self.hostfilename(filename) hostfilename = self.hostfilename(filename)
dirname, basename = os.path.split(hostfilename) dirname, basename = os.path.split(hostfilename)
if not os.path.isdir(dirname): if not os.path.isdir(dirname):
os.makedirs(dirname, mode = 0755) os.makedirs(dirname, mode=0755)
return open(hostfilename, mode) return open(hostfilename, mode)
def nodefile(self, filename, contents, mode = 0644): def nodefile(self, filename, contents, mode=0644):
"""
Create a node file with a given mode.
:param str filename: name of file to create
:param contents: contents of file
:param int mode: mode for file
:return: nothing
"""
f = self.opennodefile(filename, "w") f = self.opennodefile(filename, "w")
f.write(contents) f.write(contents)
os.chmod(f.name, mode) os.chmod(f.name, mode)
f.close() f.close()
self.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode)) logger.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode))
def nodefilecopy(self, filename, srcfilename, mode = None): def nodefilecopy(self, filename, srcfilename, mode=None):
''' Copy a file to a node, following symlinks and preserving metadata. """
Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified. Change file mode if specified.
'''
:param str filename: file name to copy file to
:param str srcfilename: file to copy
:param int mode: mode to copy to
:return: nothing
"""
hostfilename = self.hostfilename(filename) hostfilename = self.hostfilename(filename)
shutil.copy2(srcfilename, hostfilename) shutil.copy2(srcfilename, hostfilename)
if mode is not None: if mode is not None:
os.chmod(hostfilename, mode) os.chmod(hostfilename, mode)
self.info("copied nodefile: '%s'; mode: %s" % (hostfilename, mode)) logger.info("copied nodefile: '%s'; mode: %s" % (hostfilename, mode))

View file

@ -1,19 +1,17 @@
# """
# CORE
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Tom Goff <thomas.goff@boeing.com>
#
'''
vnodeclient.py: implementation of the VnodeClient class for issuing commands vnodeclient.py: implementation of the VnodeClient class for issuing commands
over a control channel to the vnoded process running in a network namespace. over a control channel to the vnoded process running in a network namespace.
The control channel can be accessed via calls to the vcmd Python module or The control channel can be accessed via calls to the vcmd Python module or
by invoking the vcmd shell command. by invoking the vcmd shell command.
''' """
import os, stat, sys import os
from core.constants import * import stat
from core import constants
from core.misc import log
logger = log.get_logger(__name__)
USE_VCMD_MODULE = True USE_VCMD_MODULE = True
@ -22,10 +20,21 @@ if USE_VCMD_MODULE:
else: else:
import subprocess import subprocess
VCMD = os.path.join(CORE_SBIN_DIR, "vcmd") VCMD = os.path.join(constants.CORE_SBIN_DIR, "vcmd")
class VnodeClient(object): class VnodeClient(object):
"""
Provides client functionality for interacting with a virtual node.
"""
def __init__(self, name, ctrlchnlname): def __init__(self, name, ctrlchnlname):
"""
Create a VnodeClient instance.
:param str name: name for client
:param str ctrlchnlname: control channel name
"""
self.name = name self.name = name
self.ctrlchnlname = ctrlchnlname self.ctrlchnlname = ctrlchnlname
if USE_VCMD_MODULE: if USE_VCMD_MODULE:
@ -34,25 +43,39 @@ class VnodeClient(object):
self.cmdchnl = None self.cmdchnl = None
self._addr = {} self._addr = {}
def warn(self, msg):
print >> sys.stderr, "%s: %s" % (self.name, msg)
def connected(self): def connected(self):
"""
Check if node is connected or not.
:return: True if connected, False otherwise
:rtype: bool
"""
if USE_VCMD_MODULE: if USE_VCMD_MODULE:
return self.cmdchnl.connected() return self.cmdchnl.connected()
else: else:
return True return True
def close(self): def close(self):
"""
Close the client connection.
:return: nothing
"""
if USE_VCMD_MODULE: if USE_VCMD_MODULE:
self.cmdchnl.close() self.cmdchnl.close()
def cmd(self, args, wait = True): def cmd(self, args, wait=True):
''' Execute a command on a node and return the status (return code). """
''' Execute a command on a node and return the status (return code).
:param list args: command arguments
:param bool wait: wait for command to end or not
:return: command status
:rtype: int
"""
if USE_VCMD_MODULE: if USE_VCMD_MODULE:
if not self.cmdchnl.connected(): if not self.cmdchnl.connected():
raise ValueError, "self.cmdchnl not connected" raise ValueError("self.cmdchnl not connected")
tmp = self.cmdchnl.qcmd(args) tmp = self.cmdchnl.qcmd(args)
if not wait: if not wait:
return tmp return tmp
@ -62,19 +85,25 @@ class VnodeClient(object):
mode = os.P_WAIT mode = os.P_WAIT
else: else:
mode = os.P_NOWAIT mode = os.P_NOWAIT
tmp = os.spawnlp(mode, VCMD, VCMD, "-c", tmp = os.spawnlp(mode, VCMD, VCMD, "-c", self.ctrlchnlname, "-q", "--", *args)
self.ctrlchnlname, "-q", "--", *args)
if not wait: if not wait:
return tmp return tmp
if tmp: if tmp:
self.warn("cmd exited with status %s: %s" % (tmp, str(args))) logger.warn("cmd exited with status %s: %s" % (tmp, str(args)))
return tmp return tmp
def cmdresult(self, args): def cmdresult(self, args):
''' Execute a command on a node and return a tuple containing the """
Execute a command on a node and return a tuple containing the
exit status and result string. stderr output exit status and result string. stderr output
is folded into the stdout result string. is folded into the stdout result string.
'''
:param list args: command arguments
:return: command status and combined stdout and stderr output
:rtype: tuple[int, str]
"""
cmdid, cmdin, cmdout, cmderr = self.popen(args) cmdid, cmdin, cmdout, cmderr = self.popen(args)
result = cmdout.read() result = cmdout.read()
result += cmderr.read() result += cmderr.read()
@ -82,43 +111,69 @@ class VnodeClient(object):
cmdout.close() cmdout.close()
cmderr.close() cmderr.close()
status = cmdid.wait() status = cmdid.wait()
return (status, result) return status, result
def popen(self, args): def popen(self, args):
"""
Execute a popen command against the node.
:param list args: command arguments
:return: popen object, stdin, stdout, and stderr
:rtype: tuple
"""
if USE_VCMD_MODULE: if USE_VCMD_MODULE:
if not self.cmdchnl.connected(): if not self.cmdchnl.connected():
raise ValueError, "self.cmdchnl not connected" raise ValueError("self.cmdchnl not connected")
return self.cmdchnl.popen(args) return self.cmdchnl.popen(args)
else: else:
cmd = [VCMD, "-c", self.ctrlchnlname, "--"] cmd = [VCMD, "-c", self.ctrlchnlname, "--"]
cmd.extend(args) cmd.extend(args)
tmp = subprocess.Popen(cmd, stdin = subprocess.PIPE, tmp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
return tmp, tmp.stdin, tmp.stdout, tmp.stderr return tmp, tmp.stdin, tmp.stdout, tmp.stderr
def icmd(self, args): def icmd(self, args):
return os.spawnlp(os.P_WAIT, VCMD, VCMD, "-c", self.ctrlchnlname, """
"--", *args) Execute an icmd against a node.
def redircmd(self, infd, outfd, errfd, args, wait = True): :param list args: command arguments
''' :return: command result
:rtype: int
"""
return os.spawnlp(os.P_WAIT, VCMD, VCMD, "-c", self.ctrlchnlname, "--", *args)
def redircmd(self, infd, outfd, errfd, args, wait=True):
"""
Execute a command on a node with standard input, output, and Execute a command on a node with standard input, output, and
error redirected according to the given file descriptors. error redirected according to the given file descriptors.
'''
:param infd: stdin file descriptor
:param outfd: stdout file descriptor
:param errfd: stderr file descriptor
:param list args: command arguments
:param bool wait: wait flag
:return: command status
:rtype: int
"""
if not USE_VCMD_MODULE: if not USE_VCMD_MODULE:
raise NotImplementedError raise NotImplementedError
if not self.cmdchnl.connected(): if not self.cmdchnl.connected():
raise ValueError, "self.cmdchnl not connected" raise ValueError("self.cmdchnl not connected")
tmp = self.cmdchnl.redircmd(infd, outfd, errfd, args) tmp = self.cmdchnl.redircmd(infd, outfd, errfd, args)
if not wait: if not wait:
return tmp return tmp
tmp = tmp.wait() tmp = tmp.wait()
if tmp: if tmp:
self.warn("cmd exited with status %s: %s" % (tmp, str(args))) logger.warn("cmd exited with status %s: %s" % (tmp, str(args)))
return tmp return tmp
def term(self, sh = "/bin/sh"): def term(self, sh="/bin/sh"):
"""
Open a terminal on a node.
:param str sh: shell to open terminal with
:return: terminal command result
:rtype: int
"""
cmd = ("xterm", "-ut", "-title", self.name, "-e", cmd = ("xterm", "-ut", "-title", self.name, "-e",
VCMD, "-c", self.ctrlchnlname, "--", sh) VCMD, "-c", self.ctrlchnlname, "--", sh)
if "SUDO_USER" in os.environ: if "SUDO_USER" in os.environ:
@ -127,19 +182,42 @@ class VnodeClient(object):
os.environ["SUDO_USER"]) os.environ["SUDO_USER"])
return os.spawnvp(os.P_NOWAIT, cmd[0], cmd) return os.spawnvp(os.P_NOWAIT, cmd[0], cmd)
def termcmdstring(self, sh = "/bin/sh"): def termcmdstring(self, sh="/bin/sh"):
"""
Create a terminal command string.
:param str sh: shell to execute command in
:return: str
"""
return "%s -c %s -- %s" % (VCMD, self.ctrlchnlname, sh) return "%s -c %s -- %s" % (VCMD, self.ctrlchnlname, sh)
def shcmd(self, cmdstr, sh = "/bin/sh"): def shcmd(self, cmdstr, sh="/bin/sh"):
"""
Execute a shell command.
:param str cmdstr: command string
:param str sh: shell to run command in
:return: command result
:rtype: int
"""
return self.cmd([sh, "-c", cmdstr]) return self.cmd([sh, "-c", cmdstr])
def getaddr(self, ifname, rescan = False): def getaddr(self, ifname, rescan=False):
"""
Get address for interface on node.
:param str ifname: interface name to get address for
:param bool rescan: rescan flag
:return: interface information
:rtype: dict
"""
if ifname in self._addr and not rescan: if ifname in self._addr and not rescan:
return self._addr[ifname] return self._addr[ifname]
tmp = {"ether": [], "inet": [], "inet6": [], "inet6link": []} tmp = {"ether": [], "inet": [], "inet6": [], "inet6link": []}
cmd = [IP_BIN, "addr", "show", "dev", ifname] cmd = [constants.IP_BIN, "addr", "show", "dev", ifname]
cmdid, cmdin, cmdout, cmderr = self.popen(cmd) cmdid, cmdin, cmdout, cmderr = self.popen(cmd)
cmdin.close() cmdin.close()
for line in cmdout: for line in cmdout:
line = line.strip().split() line = line.strip().split()
if line[0] == "link/ether": if line[0] == "link/ether":
@ -152,21 +230,27 @@ class VnodeClient(object):
elif line[3] == "link": elif line[3] == "link":
tmp["inet6link"].append(line[1]) tmp["inet6link"].append(line[1])
else: else:
self.warn("unknown scope: %s" % line[3]) logger.warn("unknown scope: %s" % line[3])
else:
pass
err = cmderr.read() err = cmderr.read()
cmdout.close() cmdout.close()
cmderr.close() cmderr.close()
status = cmdid.wait() status = cmdid.wait()
if status: if status:
self.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd)) logger.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
if err: if err:
self.warn("error output: %s" % err) logger.warn("error output: %s" % err)
self._addr[ifname] = tmp self._addr[ifname] = tmp
return tmp return tmp
def netifstats(self, ifname = None): def netifstats(self, ifname=None):
"""
Retrieve network interface state.
:param str ifname: name of interface to get state for
:return: interface state information
:rtype: dict
"""
stats = {} stats = {}
cmd = ["cat", "/proc/net/dev"] cmd = ["cat", "/proc/net/dev"]
cmdid, cmdin, cmdout, cmderr = self.popen(cmd) cmdid, cmdin, cmdout, cmderr = self.popen(cmd)
@ -195,34 +279,47 @@ class VnodeClient(object):
cmderr.close() cmderr.close()
status = cmdid.wait() status = cmdid.wait()
if status: if status:
self.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd)) logger.warn("nonzero exist status (%s) for cmd: %s" % (status, cmd))
if err: if err:
self.warn("error output: %s" % err) logger.warn("error output: %s" % err)
if ifname is not None: if ifname is not None:
return stats[ifname] return stats[ifname]
else: else:
return stats return stats
def createclients(sessiondir, clientcls = VnodeClient,
cmdchnlfilterfunc = None): def createclients(sessiondir, clientcls=VnodeClient, cmdchnlfilterfunc=None):
direntries = map(lambda x: os.path.join(sessiondir, x), """
os.listdir(sessiondir)) Create clients
:param str sessiondir: session directory to create clients
:param class clientcls: class to create clients from
:param func cmdchnlfilterfunc: command channel filter function
:return: list of created clients
:rtype: list
"""
direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir))
cmdchnls = filter(lambda x: stat.S_ISSOCK(os.stat(x).st_mode), direntries) cmdchnls = filter(lambda x: stat.S_ISSOCK(os.stat(x).st_mode), direntries)
if cmdchnlfilterfunc: if cmdchnlfilterfunc:
cmdchnls = filter(cmdchnlfilterfunc, cmdchnls) cmdchnls = filter(cmdchnlfilterfunc, cmdchnls)
cmdchnls.sort() cmdchnls.sort()
return map(lambda x: clientcls(os.path.basename(x), x), cmdchnls) return map(lambda x: clientcls(os.path.basename(x), x), cmdchnls)
def createremoteclients(sessiondir, clientcls = VnodeClient,
filterfunc = None): def createremoteclients(sessiondir, clientcls=VnodeClient, filterfunc=None):
''' Creates remote VnodeClients, for nodes emulated on other machines. The """
Creates remote VnodeClients, for nodes emulated on other machines. The
session.Broker writes a n1.conf/server file having the server's info. session.Broker writes a n1.conf/server file having the server's info.
'''
direntries = map(lambda x: os.path.join(sessiondir, x), :param str sessiondir: session directory to create clients
os.listdir(sessiondir)) :param class clientcls: class to create clients from
:param func filterfunc: filter function
:return: list of remove clients
:rtype: list
"""
direntries = map(lambda x: os.path.join(sessiondir, x), os.listdir(sessiondir))
nodedirs = filter(lambda x: stat.S_ISDIR(os.stat(x).st_mode), direntries) nodedirs = filter(lambda x: stat.S_ISDIR(os.stat(x).st_mode), direntries)
nodedirs = filter(lambda x: os.path.exists(os.path.join(x, "server")), nodedirs = filter(lambda x: os.path.exists(os.path.join(x, "server")), nodedirs)
nodedirs)
if filterfunc: if filterfunc:
nodedirs = filter(filterfunc, nodedirs) nodedirs = filter(filterfunc, nodedirs)
nodedirs.sort() nodedirs.sort()

View file

@ -1,32 +1,24 @@
# """
# CORE PhysicalNode class for including real systems in the emulated network.
# Copyright (c)2011-2012 the Boeing Company. """
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
''' PhysicalNode class for including real systems in the emulated network.
'''
import os, threading, subprocess
from core.misc.ipaddr import * import os
from core.misc.utils import * import subprocess
from core.constants import * import threading
from core.api import coreapi
from core.coreobj import PyCoreNode, PyCoreNetIf from core import constants
from core.emane.nodes import EmaneNode from core.coreobj import PyCoreNode
if os.uname()[0] == "Linux": from core.misc import log
from core.netns.vnet import LxBrNet from core.misc import utils
from core.netns.vif import GreTap from core.netns.vnet import GreTap
elif os.uname()[0] == "FreeBSD": from core.netns.vnet import LxBrNet
from core.bsd.vnet import NetgraphNet
logger = log.get_logger(__name__)
class PhysicalNode(PyCoreNode): class PhysicalNode(PyCoreNode):
def __init__(self, session, objid = None, name = None, def __init__(self, session, objid=None, name=None, nodedir=None, start=True):
nodedir = None, verbose = False, start = True): PyCoreNode.__init__(self, session, objid, name, start=start)
PyCoreNode.__init__(self, session, objid, name, verbose=verbose,
start=start)
self.nodedir = nodedir self.nodedir = nodedir
self.up = start self.up = start
self.lock = threading.RLock() self.lock = threading.RLock()
@ -44,11 +36,10 @@ class PhysicalNode(PyCoreNode):
self.lock.acquire() self.lock.acquire()
try: try:
self.makenodedir() self.makenodedir()
#self.privatedir("/var/run") # self.privatedir("/var/run")
#self.privatedir("/var/log") # self.privatedir("/var/log")
except OSError, e: except OSError:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, logger.exception("startup error")
"PhysicalNode.startup()", e)
finally: finally:
self.lock.release() self.lock.release()
@ -64,16 +55,17 @@ class PhysicalNode(PyCoreNode):
self.rmnodedir() self.rmnodedir()
self.lock.release() self.lock.release()
def termcmdstring(self, sh="/bin/sh"):
def termcmdstring(self, sh = "/bin/sh"): """
''' The broker will add the appropriate SSH command to open a terminal The broker will add the appropriate SSH command to open a terminal
on this physical node. on this physical node.
''' """
return sh return sh
def cmd(self, args, wait = True): def cmd(self, args, wait=True):
''' run a command on the physical node """
''' run a command on the physical node
"""
os.chdir(self.nodedir) os.chdir(self.nodedir)
try: try:
if wait: if wait:
@ -82,109 +74,105 @@ class PhysicalNode(PyCoreNode):
else: else:
# os.spawnlp(os.P_NOWAIT, args) # os.spawnlp(os.P_NOWAIT, args)
subprocess.Popen(args) subprocess.Popen(args)
except CalledProcessError, e: except subprocess.CalledProcessError:
self.warn("cmd exited with status %s: %s" % (e, str(args))) logger.exception("cmd exited with status: %s", str(args))
def cmdresult(self, args): def cmdresult(self, args):
''' run a command on the physical node and get the result """
''' run a command on the physical node and get the result
"""
os.chdir(self.nodedir) os.chdir(self.nodedir)
# in Python 2.7 we can use subprocess.check_output() here # in Python 2.7 we can use subprocess.check_output() here
tmp = subprocess.Popen(args, stdin = open(os.devnull, 'r'), tmp = subprocess.Popen(args, stdin=open(os.devnull, 'r'),
stdout = subprocess.PIPE, stdout=subprocess.PIPE,
stderr = subprocess.STDOUT) stderr=subprocess.STDOUT)
result, err = tmp.communicate() # err will always be None # err will always be None
result, err = tmp.communicate()
status = tmp.wait() status = tmp.wait()
return (status, result) return status, result
def shcmd(self, cmdstr, sh = "/bin/sh"): def shcmd(self, cmdstr, sh="/bin/sh"):
return self.cmd([sh, "-c", cmdstr]) return self.cmd([sh, "-c", cmdstr])
def sethwaddr(self, ifindex, addr): def sethwaddr(self, ifindex, addr):
''' same as SimpleLxcNode.sethwaddr() """
''' same as SimpleLxcNode.sethwaddr()
"""
self._netif[ifindex].sethwaddr(addr) self._netif[ifindex].sethwaddr(addr)
ifname = self.ifname(ifindex) ifname = self.ifname(ifindex)
if self.up: if self.up:
(status, result) = self.cmdresult([IP_BIN, "link", "set", "dev", (status, result) = self.cmdresult(
ifname, "address", str(addr)]) [constants.IP_BIN, "link", "set", "dev", ifname, "address", str(addr)])
if status: if status:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, logger.error("error setting MAC address %s", str(addr))
"PhysicalNode.sethwaddr()",
"error setting MAC address %s" % str(addr))
def addaddr(self, ifindex, addr): def addaddr(self, ifindex, addr):
''' same as SimpleLxcNode.addaddr() """
''' same as SimpleLxcNode.addaddr()
"""
if self.up: if self.up:
self.cmd([IP_BIN, "addr", "add", str(addr), self.cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.ifname(ifindex)])
"dev", self.ifname(ifindex)])
self._netif[ifindex].addaddr(addr) self._netif[ifindex].addaddr(addr)
def deladdr(self, ifindex, addr): def deladdr(self, ifindex, addr):
''' same as SimpleLxcNode.deladdr() """
''' same as SimpleLxcNode.deladdr()
"""
try: try:
self._netif[ifindex].deladdr(addr) self._netif[ifindex].deladdr(addr)
except ValueError: except ValueError:
self.warn("trying to delete unknown address: %s" % addr) logger.exception("trying to delete unknown address: %s", addr)
if self.up: if self.up:
self.cmd([IP_BIN, "addr", "del", str(addr), self.cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)])
"dev", self.ifname(ifindex)])
def adoptnetif(self, netif, ifindex, hwaddr, addrlist): def adoptnetif(self, netif, ifindex, hwaddr, addrlist):
''' The broker builds a GreTap tunnel device to this physical node. """
The broker builds a GreTap tunnel device to this physical node.
When a link message is received linking this node to another part of When a link message is received linking this node to another part of
the emulation, no new interface is created; instead, adopt the the emulation, no new interface is created; instead, adopt the
GreTap netif as the node interface. GreTap netif as the node interface.
''' """
netif.name = "gt%d" % ifindex netif.name = "gt%d" % ifindex
netif.node = self netif.node = self
self.addnetif(netif, ifindex) self.addnetif(netif, ifindex)
# use a more reasonable name, e.g. "gt0" instead of "gt.56286.150" # use a more reasonable name, e.g. "gt0" instead of "gt.56286.150"
if self.up: if self.up:
self.cmd([IP_BIN, "link", "set", "dev", netif.localname, "down"]) self.cmd([constants.IP_BIN, "link", "set", "dev", netif.localname, "down"])
self.cmd([IP_BIN, "link", "set", netif.localname, "name", netif.name]) self.cmd([constants.IP_BIN, "link", "set", netif.localname, "name", netif.name])
netif.localname = netif.name netif.localname = netif.name
if hwaddr: if hwaddr:
self.sethwaddr(ifindex, hwaddr) self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist): for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr) self.addaddr(ifindex, addr)
if self.up: if self.up:
self.cmd([IP_BIN, "link", "set", "dev", netif.localname, "up"]) self.cmd([constants.IP_BIN, "link", "set", "dev", netif.localname, "up"])
def linkconfig(self, netif, bw = None, delay = None, def linkconfig(self, netif, bw=None, delay=None,
loss = None, duplicate = None, jitter = None, netif2 = None): loss=None, duplicate=None, jitter=None, netif2=None):
''' Apply tc queing disciplines using LxBrNet.linkconfig() """
''' Apply tc queing disciplines using LxBrNet.linkconfig()
if os.uname()[0] == "Linux": """
netcls = LxBrNet
elif os.uname()[0] == "FreeBSD":
netcls = NetgraphNet
else:
raise NotImplementedError, "unsupported platform"
# borrow the tc qdisc commands from LxBrNet.linkconfig() # borrow the tc qdisc commands from LxBrNet.linkconfig()
tmp = netcls(session=self.session, start=False) linux_bridge = LxBrNet(session=self.session, start=False)
tmp.up = True linux_bridge.up = True
tmp.linkconfig(netif, bw=bw, delay=delay, loss=loss, linux_bridge.linkconfig(netif, bw=bw, delay=delay, loss=loss, duplicate=duplicate,
duplicate=duplicate, jitter=jitter, netif2=netif2) jitter=jitter, netif2=netif2)
del tmp del linux_bridge
def newifindex(self): def newifindex(self):
self.lock.acquire() with self.lock:
try:
while self.ifindex in self._netif: while self.ifindex in self._netif:
self.ifindex += 1 self.ifindex += 1
ifindex = self.ifindex ifindex = self.ifindex
self.ifindex += 1 self.ifindex += 1
return ifindex return ifindex
finally:
self.lock.release()
def newnetif(self, net = None, addrlist = [], hwaddr = None, def newnetif(self, net=None, addrlist=[], hwaddr=None, ifindex=None, ifname=None):
ifindex = None, ifname = None):
if self.up and net is None: if self.up and net is None:
raise NotImplementedError raise NotImplementedError
if ifindex is None: if ifindex is None:
ifindex = self.newifindex() ifindex = self.newifindex()
@ -193,8 +181,7 @@ class PhysicalNode(PyCoreNode):
# tunnel to net not built yet, so build it now and adopt it # tunnel to net not built yet, so build it now and adopt it
gt = self.session.broker.addnettunnel(net.objid) gt = self.session.broker.addnettunnel(net.objid)
if gt is None or len(gt) != 1: if gt is None or len(gt) != 1:
self.session.warn("Error building tunnel from PhysicalNode." raise ValueError("error building tunnel from adding a new network interface: %s" % gt)
"newnetif()")
gt = gt[0] gt = gt[0]
net.detach(gt) net.detach(gt)
self.adoptnetif(gt, ifindex, hwaddr, addrlist) self.adoptnetif(gt, ifindex, hwaddr, addrlist)
@ -203,46 +190,43 @@ class PhysicalNode(PyCoreNode):
# this is reached when configuring services (self.up=False) # this is reached when configuring services (self.up=False)
if ifname is None: if ifname is None:
ifname = "gt%d" % ifindex ifname = "gt%d" % ifindex
netif = GreTap(node = self, name = ifname, session = self.session,
start = False) netif = GreTap(node=self, name=ifname, session=self.session, start=False)
self.adoptnetif(netif, ifindex, hwaddr, addrlist) self.adoptnetif(netif, ifindex, hwaddr, addrlist)
return ifindex return ifindex
def privatedir(self, path): def privatedir(self, path):
if path[0] != "/": if path[0] != "/":
raise ValueError, "path not fully qualified: " + path raise ValueError, "path not fully qualified: " + path
hostpath = os.path.join(self.nodedir, hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.'))
os.path.normpath(path).strip('/').replace('/', '.'))
try: try:
os.mkdir(hostpath) os.mkdir(hostpath)
except OSError: except OSError:
pass logger.exception("error creating directory: %s", hostpath)
except Exception, e:
raise Exception, e
self.mount(hostpath, path) self.mount(hostpath, path)
def mount(self, source, target): def mount(self, source, target):
source = os.path.abspath(source) source = os.path.abspath(source)
self.info("mounting %s at %s" % (source, target)) logger.info("mounting %s at %s" % (source, target))
try: try:
os.makedirs(target) os.makedirs(target)
except OSError: self.cmd([constants.MOUNT_BIN, "--bind", source, target])
pass
try:
self.cmd([MOUNT_BIN, "--bind", source, target])
self._mounts.append((source, target)) self._mounts.append((source, target))
except OSError:
logger.exception("error making directories")
except: except:
self.warn("mounting failed for %s at %s" % (source, target)) logger.exception("mounting failed for %s at %s", source, target)
def umount(self, target): def umount(self, target):
self.info("unmounting '%s'" % target) logger.info("unmounting '%s'" % target)
try: try:
self.cmd([UMOUNT_BIN, "-l", target]) self.cmd([constants.UMOUNT_BIN, "-l", target])
except: except:
self.warn("unmounting failed for %s" % target) logger.exception("unmounting failed for %s", target)
def opennodefile(self, filename, mode = "w"): def opennodefile(self, filename, mode="w"):
dirname, basename = os.path.split(filename) dirname, basename = os.path.split(filename)
if not basename: if not basename:
raise ValueError, "no basename for filename: " + filename raise ValueError, "no basename for filename: " + filename
@ -251,15 +235,13 @@ class PhysicalNode(PyCoreNode):
dirname = dirname.replace("/", ".") dirname = dirname.replace("/", ".")
dirname = os.path.join(self.nodedir, dirname) dirname = os.path.join(self.nodedir, dirname)
if not os.path.isdir(dirname): if not os.path.isdir(dirname):
os.makedirs(dirname, mode = 0755) os.makedirs(dirname, mode=0755)
hostfilename = os.path.join(dirname, basename) hostfilename = os.path.join(dirname, basename)
return open(hostfilename, mode) return open(hostfilename, mode)
def nodefile(self, filename, contents, mode = 0644): def nodefile(self, filename, contents, mode=0644):
f = self.opennodefile(filename, "w") f = self.opennodefile(filename, "w")
f.write(contents) f.write(contents)
os.chmod(f.name, mode) os.chmod(f.name, mode)
f.close() f.close()
self.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode)) logger.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode))

View file

@ -1,27 +0,0 @@
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
"""
This is a convenience module that imports a set of platform-dependent
defaults.
"""
from misc.utils import ensurepath
ensurepath(["/sbin", "/bin", "/usr/sbin", "/usr/bin"])
del ensurepath
from session import Session
import os
if os.uname()[0] == "Linux":
from netns import nodes
try:
from xen import xen
except ImportError:
#print "Xen support disabled."
pass
elif os.uname()[0] == "FreeBSD":
from bsd import nodes
from phys import pnodes
del os

View file

@ -1,74 +1,102 @@
# """
# CORE
# Copyright (c)2012-2013 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
sdt.py: Scripted Display Tool (SDT3D) helper sdt.py: Scripted Display Tool (SDT3D) helper
''' """
from core.constants import *
from core.api import coreapi
from coreobj import PyCoreNet, PyCoreObj
from core.netns import nodes
from urlparse import urlparse
import socket import socket
from urlparse import urlparse
from core import constants
from core.api import coreapi
from core.coreobj import PyCoreNet
from core.coreobj import PyCoreObj
from core.enumerations import EventTypes
from core.enumerations import LinkTlvs
from core.enumerations import LinkTypes
from core.enumerations import MessageFlags
from core.enumerations import MessageTypes
from core.enumerations import NodeTlvs
from core.enumerations import NodeTypes
from core.misc import log
from core.misc import nodeutils
logger = log.get_logger(__name__)
# TODO: A named tuple may be more appropriate, than abusing a class dict like this
class Bunch(object):
"""
Helper class for recording a collection of attributes.
"""
def __init__(self, **kwds):
"""
Create a Bunch instance.
:param dict kwds: keyword arguments
:return:
"""
self.__dict__.update(kwds)
class Sdt(object): class Sdt(object):
''' Helper class for exporting session objects to NRL's SDT3D. """
Helper class for exporting session objects to NRL"s SDT3D.
The connect() method initializes the display, and can be invoked The connect() method initializes the display, and can be invoked
when a node position or link has changed. when a node position or link has changed.
''' """
DEFAULT_SDT_URL = "tcp://127.0.0.1:50000/" DEFAULT_SDT_URL = "tcp://127.0.0.1:50000/"
# default altitude (in meters) for flyto view # default altitude (in meters) for flyto view
DEFAULT_ALT = 2500 DEFAULT_ALT = 2500
# TODO: read in user's nodes.conf here; below are default node types # TODO: read in user"s nodes.conf here; below are default node types from the GUI
# from the GUI DEFAULT_SPRITES = [
DEFAULT_SPRITES = [('router', 'router.gif'), ('host', 'host.gif'), ("router", "router.gif"), ("host", "host.gif"),
('PC', 'pc.gif'), ('mdr', 'mdr.gif'), ("PC", "pc.gif"), ("mdr", "mdr.gif"),
('prouter', 'router_green.gif'), ('xen', 'xen.gif'), ("prouter", "router_green.gif"), ("xen", "xen.gif"),
('hub', 'hub.gif'), ('lanswitch','lanswitch.gif'), ("hub", "hub.gif"), ("lanswitch", "lanswitch.gif"),
('wlan', 'wlan.gif'), ('rj45','rj45.gif'), ("wlan", "wlan.gif"), ("rj45", "rj45.gif"),
('tunnel','tunnel.gif'), ("tunnel", "tunnel.gif"),
] ]
class Bunch:
''' Helper class for recording a collection of attributes.
'''
def __init__(self, **kwds):
self.__dict__.update(kwds)
def __init__(self, session): def __init__(self, session):
"""
Creates a Sdt instance.
:param core.session.Session session: session this manager is tied to
"""
self.session = session self.session = session
self.sock = None self.sock = None
self.connected = False self.connected = False
self.showerror = True self.showerror = True
self.url = self.DEFAULT_SDT_URL self.url = self.DEFAULT_SDT_URL
self.verbose = self.session.getcfgitembool('verbose', False)
# node information for remote nodes not in session._objs # node information for remote nodes not in session._objs
# local nodes also appear here since their obj may not exist yet # local nodes also appear here since their obj may not exist yet
self.remotes = {} self.remotes = {}
session.broker.handlers.add(self.handledistributed) session.broker.handlers.add(self.handledistributed)
def is_enabled(self): def is_enabled(self):
''' Check for 'enablesdt' session option. Return False by default if """
Check for "enablesdt" session option. Return False by default if
the option is missing. the option is missing.
'''
if not hasattr(self.session.options, 'enablesdt'): :return: True if enabled, False otherwise
:rtype: bool
"""
if not hasattr(self.session.options, "enablesdt"):
return False return False
enabled = self.session.options.enablesdt enabled = self.session.options.enablesdt
if enabled in ('1', 'true', 1, True): if enabled in ("1", "true", 1, True):
return True return True
return False return False
def seturl(self): def seturl(self):
''' Read 'sdturl' from session options, or use the default value. """
Read "sdturl" from session options, or use the default value.
Set self.url, self.address, self.protocol Set self.url, self.address, self.protocol
'''
:return: nothing
"""
url = None url = None
if hasattr(self.session.options,'sdturl'): if hasattr(self.session.options, "sdturl"):
if self.session.options.sdturl != "": if self.session.options.sdturl != "":
url = self.session.options.sdturl url = self.session.options.sdturl
if url is None or url == "": if url is None or url == "":
@ -78,147 +106,193 @@ class Sdt(object):
self.protocol = self.url.scheme self.protocol = self.url.scheme
def connect(self, flags=0): def connect(self, flags=0):
''' Connect to the SDT address/port if enabled. """
''' Connect to the SDT address/port if enabled.
:return: True if connected, False otherwise
:rtype: bool
"""
if not self.is_enabled(): if not self.is_enabled():
return False return False
if self.connected: if self.connected:
return True return True
if self.session.getstate() == coreapi.CORE_EVENT_SHUTDOWN_STATE: if self.session.state == EventTypes.SHUTDOWN_STATE.value:
return False return False
self.seturl() self.seturl()
if self.showerror: logger.info("connecting to SDT at %s://%s" % (self.protocol, self.address))
self.session.info("connecting to SDT at %s://%s" \
% (self.protocol, self.address))
if self.sock is None: if self.sock is None:
try: try:
if (self.protocol.lower() == 'udp'): if self.protocol.lower() == "udp":
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.connect(self.address) self.sock.connect(self.address)
else: else:
# Default to tcp # Default to tcp
self.sock = socket.create_connection(self.address, 5) self.sock = socket.create_connection(self.address, 5)
except Exception, e: except IOError:
if self.showerror: logger.exception("SDT socket connect error")
self.session.warn("SDT socket connect error: %s" % e)
self.showerror = False
return False return False
if not self.initialize(): if not self.initialize():
return False return False
self.connected = True self.connected = True
# refresh all objects in SDT3D when connecting after session start # refresh all objects in SDT3D when connecting after session start
if not flags & coreapi.CORE_API_ADD_FLAG: if not flags & MessageFlags.ADD.value and not self.sendobjs():
if not self.sendobjs():
return False return False
return True return True
def initialize(self): def initialize(self):
''' Load icon sprites, and fly to the reference point location on """
Load icon sprites, and fly to the reference point location on
the virtual globe. the virtual globe.
'''
if not self.cmd('path "%s/icons/normal"' % CORE_DATA_DIR): :return: initialize command status
:rtype: bool
"""
if not self.cmd("path \"%s/icons/normal\"" % constants.CORE_DATA_DIR):
return False return False
# send node type to icon mappings # send node type to icon mappings
for (type, icon) in self.DEFAULT_SPRITES: for type, icon in self.DEFAULT_SPRITES:
if not self.cmd('sprite %s image %s' % (type, icon)): if not self.cmd("sprite %s image %s" % (type, icon)):
return False return False
(lat, long) = self.session.location.refgeo[:2] (lat, long) = self.session.location.refgeo[:2]
return self.cmd('flyto %.6f,%.6f,%d' % (long, lat, self.DEFAULT_ALT)) return self.cmd("flyto %.6f,%.6f,%d" % (long, lat, self.DEFAULT_ALT))
def disconnect(self): def disconnect(self):
"""
Disconnect from SDT.
:return: nothing
"""
if self.sock:
try: try:
self.sock.close() self.sock.close()
except: except IOError:
pass logger.error("error closing socket")
finally:
self.sock = None self.sock = None
self.connected = False self.connected = False
def shutdown(self): def shutdown(self):
''' Invoked from Session.shutdown() and Session.checkshutdown(). """
''' Invoked from Session.shutdown() and Session.checkshutdown().
self.cmd('clear all')
:return: nothing
"""
self.cmd("clear all")
self.disconnect() self.disconnect()
self.showerror = True self.showerror = True
def cmd(self, cmdstr): def cmd(self, cmdstr):
''' Send an SDT command over a UDP socket. socket.sendall() is used """
Send an SDT command over a UDP socket. socket.sendall() is used
as opposed to socket.sendto() because an exception is raised when as opposed to socket.sendto() because an exception is raised when
there is no socket listener. there is no socket listener.
'''
:param str cmdstr: command to send
:return: True if command was successful, False otherwise
:rtype: bool
"""
if self.sock is None: if self.sock is None:
return False return False
try: try:
if self.verbose: logger.info("sdt: %s" % cmdstr)
self.session.info("sdt: %s" % cmdstr)
self.sock.sendall("%s\n" % cmdstr) self.sock.sendall("%s\n" % cmdstr)
return True return True
except Exception, e: except IOError:
if self.showerror: logger.exception("SDT connection error")
self.session.warn("SDT connection error: %s" % e)
self.showerror = False
self.sock = None self.sock = None
self.connected = False self.connected = False
return False return False
def updatenode(self, nodenum, flags, x, y, z, def updatenode(self, nodenum, flags, x, y, z, name=None, type=None, icon=None):
name=None, type=None, icon=None): """
''' Node is updated from a Node Message or mobility script. Node is updated from a Node Message or mobility script.
'''
:param int nodenum: node id to update
:param flags: update flags
:param x: x position
:param y: y position
:param z: z position
:param str name: node name
:param type: node type
:param icon: node icon
:return: nothing
"""
if not self.connect(): if not self.connect():
return return
if flags & coreapi.CORE_API_DEL_FLAG: if flags & MessageFlags.DELETE.value:
self.cmd('delete node,%d' % nodenum) self.cmd("delete node,%d" % nodenum)
return return
if x is None or y is None: if x is None or y is None:
return return
(lat, long, alt) = self.session.location.getgeo(x, y, z) lat, long, alt = self.session.location.getgeo(x, y, z)
pos = "pos %.6f,%.6f,%.6f" % (long, lat, alt) pos = "pos %.6f,%.6f,%.6f" % (long, lat, alt)
if flags & coreapi.CORE_API_ADD_FLAG: if flags & MessageFlags.ADD.value:
if icon is not None: if icon is not None:
type = name type = name
icon = icon.replace("$CORE_DATA_DIR", CORE_DATA_DIR) icon = icon.replace("$CORE_DATA_DIR", constants.CORE_DATA_DIR)
icon = icon.replace("$CORE_CONF_DIR", CORE_CONF_DIR) icon = icon.replace("$CORE_CONF_DIR", constants.CORE_CONF_DIR)
self.cmd('sprite %s image %s' % (type, icon)) self.cmd("sprite %s image %s" % (type, icon))
self.cmd('node %d type %s label on,"%s" %s' % \ self.cmd("node %d type %s label on,\"%s\" %s" % (nodenum, type, name, pos))
(nodenum, type, name, pos))
else: else:
self.cmd('node %d %s' % (nodenum, pos)) self.cmd("node %d %s" % (nodenum, pos))
def updatenodegeo(self, nodenum, lat, long, alt): def updatenodegeo(self, nodenum, lat, long, alt):
''' Node is updated upon receiving an EMANE Location Event. """
TODO: received Node Message with lat/long/alt. Node is updated upon receiving an EMANE Location Event.
'''
:param int nodenum: node id to update geospatial for
:param lat: latitude
:param long: longitude
:param alt: altitude
:return: nothing
"""
# TODO: received Node Message with lat/long/alt.
if not self.connect(): if not self.connect():
return return
pos = "pos %.6f,%.6f,%.6f" % (long, lat, alt) pos = "pos %.6f,%.6f,%.6f" % (long, lat, alt)
self.cmd('node %d %s' % (nodenum, pos)) self.cmd("node %d %s" % (nodenum, pos))
def updatelink(self, node1num, node2num, flags, wireless=False): def updatelink(self, node1num, node2num, flags, wireless=False):
''' Link is updated from a Link Message or by a wireless model. """
''' Link is updated from a Link Message or by a wireless model.
:param int node1num: node one id
:param int node2num: node two id
:param flags: link flags
:param bool wireless: flag to check if wireless or not
:return: nothing
"""
if node1num is None or node2num is None: if node1num is None or node2num is None:
return return
if not self.connect(): if not self.connect():
return return
if flags & coreapi.CORE_API_DEL_FLAG: if flags & MessageFlags.DELETE.value:
self.cmd('delete link,%s,%s' % (node1num, node2num)) self.cmd("delete link,%s,%s" % (node1num, node2num))
elif flags & coreapi.CORE_API_ADD_FLAG: elif flags & MessageFlags.ADD.value:
attr = "" attr = ""
if wireless: if wireless:
attr = " line green,2" attr = " line green,2"
else: else:
attr = " line red,2" attr = " line red,2"
self.cmd('link %s,%s%s' % (node1num, node2num, attr)) self.cmd("link %s,%s%s" % (node1num, node2num, attr))
def sendobjs(self): def sendobjs(self):
''' Session has already started, and the SDT3D GUI later connects. """
Session has already started, and the SDT3D GUI later connects.
Send all node and link objects for display. Otherwise, nodes and Send all node and link objects for display. Otherwise, nodes and
links will only be drawn when they have been updated (e.g. moved). links will only be drawn when they have been updated (e.g. moved).
'''
:return: nothing
"""
nets = [] nets = []
with self.session._objslock: with self.session._objects_lock:
for obj in self.session.objs(): for obj in self.session.objects.itervalues():
if isinstance(obj, PyCoreNet): if isinstance(obj, PyCoreNet):
nets.append(obj) nets.append(obj)
if not isinstance(obj, PyCoreObj): if not isinstance(obj, PyCoreObj):
@ -226,88 +300,96 @@ class Sdt(object):
(x, y, z) = obj.getposition() (x, y, z) = obj.getposition()
if x is None or y is None: if x is None or y is None:
continue continue
self.updatenode(obj.objid, coreapi.CORE_API_ADD_FLAG, x, y, z, self.updatenode(obj.objid, MessageFlags.ADD.value, x, y, z,
obj.name, obj.type, obj.icon) obj.name, obj.type, obj.icon)
for nodenum in sorted(self.remotes.keys()): for nodenum in sorted(self.remotes.keys()):
r = self.remotes[nodenum] r = self.remotes[nodenum]
(x, y, z) = r.pos x, y, z = r.pos
self.updatenode(nodenum, coreapi.CORE_API_ADD_FLAG, x, y, z, self.updatenode(nodenum, MessageFlags.ADD.value, x, y, z,
r.name, r.type, r.icon) r.name, r.type, r.icon)
for net in nets: for net in nets:
# use tolinkmsgs() to handle various types of links # use tolinkmsgs() to handle various types of links
msgs = net.tolinkmsgs(flags = coreapi.CORE_API_ADD_FLAG) messages = net.all_link_data(flags=MessageFlags.ADD.value)
for msg in msgs: for message in messages:
msghdr = msg[:coreapi.CoreMessage.hdrsiz] msghdr = message[:coreapi.CoreMessage.header_len]
flags = coreapi.CoreMessage.unpackhdr(msghdr)[1] flags = coreapi.CoreMessage.unpack_header(msghdr)[1]
m = coreapi.CoreLinkMessage(flags, msghdr, m = coreapi.CoreLinkMessage(flags, msghdr, message[coreapi.CoreMessage.header_len:])
msg[coreapi.CoreMessage.hdrsiz:]) n1num = m.get_tlv(LinkTlvs.N1_NUMBER.value)
n1num = m.gettlv(coreapi.CORE_TLV_LINK_N1NUMBER) n2num = m.get_tlv(LinkTlvs.N2_NUMBER.value)
n2num = m.gettlv(coreapi.CORE_TLV_LINK_N2NUMBER) link_msg_type = m.get_tlv(LinkTlvs.TYPE.value)
link_msg_type = m.gettlv(coreapi.CORE_TLV_LINK_TYPE) if nodeutils.is_node(net, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
if isinstance(net, nodes.WlanNode) or \ if n1num == net.objid:
isinstance(net, nodes.EmaneNode):
if (n1num == net.objid):
continue continue
wl = (link_msg_type == coreapi.CORE_LINK_WIRELESS) wl = link_msg_type == LinkTypes.WIRELESS.value
self.updatelink(n1num, n2num, coreapi.CORE_API_ADD_FLAG, wl) self.updatelink(n1num, n2num, MessageFlags.ADD.value, wl)
for n1num in sorted(self.remotes.keys()): for n1num in sorted(self.remotes.keys()):
r = self.remotes[n1num] r = self.remotes[n1num]
for (n2num, wl) in r.links: for n2num, wl in r.links:
self.updatelink(n1num, n2num, coreapi.CORE_API_ADD_FLAG, wl) self.updatelink(n1num, n2num, MessageFlags.ADD.value, wl)
def handledistributed(self, msg): # TODO: remove the need for this
''' Broker handler for processing CORE API messages as they are def handledistributed(self, message):
"""
Broker handler for processing CORE API messages as they are
received. This is used to snoop the Node messages and update received. This is used to snoop the Node messages and update
node positions. node positions.
'''
if msg.msgtype == coreapi.CORE_API_LINK_MSG:
return self.handlelinkmsg(msg)
elif msg.msgtype == coreapi.CORE_API_NODE_MSG:
return self.handlenodemsg(msg)
:param message: message to handle
:return: replies
"""
if message.message_type == MessageTypes.LINK.value:
return self.handlelinkmsg(message)
elif message.message_type == MessageTypes.NODE.value:
return self.handlenodemsg(message)
# TODO: remove the need for this
def handlenodemsg(self, msg): def handlenodemsg(self, msg):
''' Process a Node Message to add/delete or move a node on """
Process a Node Message to add/delete or move a node on
the SDT display. Node properties are found in session._objs or the SDT display. Node properties are found in session._objs or
self.remotes for remote nodes (or those not yet instantiated). self.remotes for remote nodes (or those not yet instantiated).
'''
:param msg: node message to handle
:return: nothing
"""
# for distributed sessions to work properly, the SDT option should be # for distributed sessions to work properly, the SDT option should be
# enabled prior to starting the session # enabled prior to starting the session
if not self.is_enabled(): if not self.is_enabled():
return False return False
# node.(objid, type, icon, name) are used. # node.(objid, type, icon, name) are used.
nodenum = msg.gettlv(coreapi.CORE_TLV_NODE_NUMBER) nodenum = msg.get_tlv(NodeTlvs.NUMBER.value)
if not nodenum: if not nodenum:
return return
x = msg.gettlv(coreapi.CORE_TLV_NODE_XPOS) x = msg.get_tlv(NodeTlvs.X_POSITION.value)
y = msg.gettlv(coreapi.CORE_TLV_NODE_YPOS) y = msg.get_tlv(NodeTlvs.Y_POSITION.value)
z = None z = None
name = msg.gettlv(coreapi.CORE_TLV_NODE_NAME) name = msg.get_tlv(NodeTlvs.NAME.value)
nodetype = msg.gettlv(coreapi.CORE_TLV_NODE_TYPE) nodetype = msg.get_tlv(NodeTlvs.TYPE.value)
model = msg.gettlv(coreapi.CORE_TLV_NODE_MODEL) model = msg.get_tlv(NodeTlvs.MODEL.value)
icon = msg.gettlv(coreapi.CORE_TLV_NODE_ICON) icon = msg.get_tlv(NodeTlvs.ICON.value)
net = False net = False
if nodetype == coreapi.CORE_NODE_DEF or \ if nodetype == NodeTypes.DEFAULT.value or \
nodetype == coreapi.CORE_NODE_PHYS or \ nodetype == NodeTypes.PHYSICAL.value or \
nodetype == coreapi.CORE_NODE_XEN: nodetype == NodeTypes.XEN.value:
if model is None: if model is None:
model = "router" model = "router"
type = model type = model
elif nodetype != None: elif nodetype is not None:
type = coreapi.node_class(nodetype).type type = nodeutils.get_node_class(NodeTypes(nodetype)).type
net = True net = True
else: else:
type = None type = None
try: try:
node = self.session.obj(nodenum) node = self.session.get_object(nodenum)
except KeyError: except KeyError:
node = None node = None
if node: if node:
self.updatenode(node.objid, msg.flags, x, y, z, self.updatenode(node.objid, msg.flags, x, y, z, node.name, node.type, node.icon)
node.name, node.type, node.icon)
else: else:
if nodenum in self.remotes: if nodenum in self.remotes:
remote = self.remotes[nodenum] remote = self.remotes[nodenum]
@ -318,29 +400,33 @@ class Sdt(object):
if icon is None: if icon is None:
icon = remote.icon icon = remote.icon
else: else:
remote = self.Bunch(objid=nodenum, type=type, icon=icon, remote = Bunch(objid=nodenum, type=type, icon=icon, name=name, net=net, links=set())
name=name, net=net, links=set())
self.remotes[nodenum] = remote self.remotes[nodenum] = remote
remote.pos = (x, y, z) remote.pos = (x, y, z)
self.updatenode(nodenum, msg.flags, x, y, z, name, type, icon) self.updatenode(nodenum, msg.flags, x, y, z, name, type, icon)
# TODO: remove the need for this
def handlelinkmsg(self, msg): def handlelinkmsg(self, msg):
''' Process a Link Message to add/remove links on the SDT display. """
Process a Link Message to add/remove links on the SDT display.
Links are recorded in the remotes[nodenum1].links set for updating Links are recorded in the remotes[nodenum1].links set for updating
the SDT display at a later time. the SDT display at a later time.
'''
:param msg: link message to handle
:return: nothing
"""
if not self.is_enabled(): if not self.is_enabled():
return False return False
nodenum1 = msg.gettlv(coreapi.CORE_TLV_LINK_N1NUMBER) nodenum1 = msg.get_tlv(LinkTlvs.N1_NUMBER.value)
nodenum2 = msg.gettlv(coreapi.CORE_TLV_LINK_N2NUMBER) nodenum2 = msg.get_tlv(LinkTlvs.N2_NUMBER.value)
link_msg_type = msg.gettlv(coreapi.CORE_TLV_LINK_TYPE) link_msg_type = msg.get_tlv(LinkTlvs.TYPE.value)
# this filters out links to WLAN and EMANE nodes which are not drawn # this filters out links to WLAN and EMANE nodes which are not drawn
if self.wlancheck(nodenum1): if self.wlancheck(nodenum1):
return return
wl = (link_msg_type == coreapi.CORE_LINK_WIRELESS) wl = link_msg_type == LinkTypes.WIRELESS.value
if nodenum1 in self.remotes: if nodenum1 in self.remotes:
r = self.remotes[nodenum1] r = self.remotes[nodenum1]
if msg.flags & coreapi.CORE_API_DEL_FLAG: if msg.flags & MessageFlags.DELETE.value:
if (nodenum2, wl) in r.links: if (nodenum2, wl) in r.links:
r.links.remove((nodenum2, wl)) r.links.remove((nodenum2, wl))
else: else:
@ -348,18 +434,22 @@ class Sdt(object):
self.updatelink(nodenum1, nodenum2, msg.flags, wireless=wl) self.updatelink(nodenum1, nodenum2, msg.flags, wireless=wl)
def wlancheck(self, nodenum): def wlancheck(self, nodenum):
''' Helper returns True if a node number corresponds to a WlanNode """
or EmaneNode. Helper returns True if a node number corresponds to a WlanNode or EmaneNode.
'''
:param int nodenum: node id to check
:return: True if node is wlan or emane, False otherwise
:rtype: bool
"""
if nodenum in self.remotes: if nodenum in self.remotes:
type = self.remotes[nodenum].type type = self.remotes[nodenum].type
if type in ("wlan", "emane"): if type in ("wlan", "emane"):
return True return True
else: else:
try: try:
n = self.session.obj(nodenum) n = self.session.get_object(nodenum)
except KeyError: except KeyError:
return False return False
if isinstance(n, (nodes.WlanNode, nodes.EmaneNode)): if nodeutils.is_node(n, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
return True return True
return False return False

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
"""Services """
Services
Services available to nodes can be put in this directory. Everything listed in Services available to nodes can be put in this directory. Everything listed in
__all__ is automatically loaded by the main core module. __all__ is automatically loaded by the main core module.
""" """
__all__ = ["quagga", "nrl", "xorp", "bird", "utility", "security", "ucarp", "dockersvc", 'startup']

View file

@ -1,24 +1,15 @@
# """
# CORE
# Copyright (c)2012 Jean-Tiare Le Bigot.
# See the LICENSE file included in this distribution.
#
# authors: Jean-Tiare Le Bigot <admin@jtlebi.fr>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
bird.py: defines routing services provided by the BIRD Internet Routing Daemon. bird.py: defines routing services provided by the BIRD Internet Routing Daemon.
''' """
import os from core.service import CoreService
from core.service import ServiceManager
from core.service import CoreService, addservice
from core.misc.ipaddr import IPv4Prefix
from core.constants import *
class Bird(CoreService): class Bird(CoreService):
''' Bird router support """
''' Bird router support
"""
_name = "bird" _name = "bird"
_group = "BIRD" _group = "BIRD"
_depends = () _depends = ()
@ -26,13 +17,14 @@ class Bird(CoreService):
_configs = ("/etc/bird/bird.conf",) _configs = ("/etc/bird/bird.conf",)
_startindex = 35 _startindex = 35
_startup = ("bird -c %s" % (_configs[0]),) _startup = ("bird -c %s" % (_configs[0]),)
_shutdown = ("killall bird", ) _shutdown = ("killall bird",)
_validate = ("pidof bird", ) _validate = ("pidof bird",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Return the bird.conf file contents. """
''' Return the bird.conf file contents.
"""
if filename == cls._configs[0]: if filename == cls._configs[0]:
return cls.generateBirdConf(node, services) return cls.generateBirdConf(node, services)
else: else:
@ -40,23 +32,25 @@ class Bird(CoreService):
@staticmethod @staticmethod
def routerid(node): def routerid(node):
''' Helper to return the first IPv4 address of a node as its router ID. """
''' Helper to return the first IPv4 address of a node as its router ID.
"""
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control == True:
continue continue
for a in ifc.addrlist: for a in ifc.addrlist:
if a.find(".") >= 0: if a.find(".") >= 0:
return a .split('/') [0] return a.split('/')[0]
#raise ValueError, "no IPv4 address found for router ID" # raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0" return "0.0.0.0"
@classmethod @classmethod
def generateBirdConf(cls, node, services): def generateBirdConf(cls, node, services):
''' Returns configuration file text. Other services that depend on bird """
Returns configuration file text. Other services that depend on bird
will have generatebirdifcconfig() and generatebirdconfig() will have generatebirdifcconfig() and generatebirdconfig()
hooks that are invoked here. hooks that are invoked here.
''' """
cfg = """\ cfg = """\
/* Main configuration file for BIRD. This is ony a template, /* Main configuration file for BIRD. This is ony a template,
* you will *need* to customize it according to your needs * you will *need* to customize it according to your needs
@ -90,14 +84,16 @@ protocol device {
return cfg return cfg
class BirdService(CoreService): class BirdService(CoreService):
''' Parent class for Bird services. Defines properties and methods """
Parent class for Bird services. Defines properties and methods
common to Bird's routing daemons. common to Bird's routing daemons.
''' """
_name = "BirdDaemon" _name = "BirdDaemon"
_group = "BIRD" _group = "BIRD"
_depends = ("bird", ) _depends = ("bird",)
_dirs = () _dirs = ()
_configs = () _configs = ()
_startindex = 40 _startindex = 40
@ -118,14 +114,17 @@ class BirdService(CoreService):
cfg = "" cfg = ""
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: continue if hasattr(ifc, 'control') and ifc.control == True:
cfg += ' interface "%s";\n'% ifc.name continue
cfg += ' interface "%s";\n' % ifc.name
return cfg return cfg
class BirdBgp(BirdService): class BirdBgp(BirdService):
'''BGP BIRD Service (configuration generation)''' """
BGP BIRD Service (configuration generation)
"""
_name = "BIRD_BGP" _name = "BIRD_BGP"
_custom_needed = True _custom_needed = True
@ -152,8 +151,11 @@ protocol bgp {
""" """
class BirdOspf(BirdService): class BirdOspf(BirdService):
'''OSPF BIRD Service (configuration generation)''' """
OSPF BIRD Service (configuration generation)
"""
_name = "BIRD_OSPFv2" _name = "BIRD_OSPFv2"
@ -176,7 +178,9 @@ class BirdOspf(BirdService):
class BirdRadv(BirdService): class BirdRadv(BirdService):
'''RADV BIRD Service (configuration generation)''' """
RADV BIRD Service (configuration generation)
"""
_name = "BIRD_RADV" _name = "BIRD_RADV"
@ -202,7 +206,9 @@ class BirdRadv(BirdService):
class BirdRip(BirdService): class BirdRip(BirdService):
'''RIP BIRD Service (configuration generation)''' """
RIP BIRD Service (configuration generation)
"""
_name = "BIRD_RIP" _name = "BIRD_RIP"
@ -222,7 +228,9 @@ class BirdRip(BirdService):
class BirdStatic(BirdService): class BirdStatic(BirdService):
'''Static Bird Service (configuration generation)''' """
Static Bird Service (configuration generation)
"""
_name = "BIRD_static" _name = "BIRD_static"
_custom_needed = True _custom_needed = True
@ -240,10 +248,11 @@ class BirdStatic(BirdService):
return cfg return cfg
# Register all protocols def load_services():
addservice(Bird) # Register all protocols
addservice(BirdOspf) ServiceManager.add(Bird)
addservice(BirdBgp) ServiceManager.add(BirdOspf)
#addservice(BirdRadv) # untested ServiceManager.add(BirdBgp)
addservice(BirdRip) # ServiceManager.add(BirdRadv) # untested
addservice(BirdStatic) ServiceManager.add(BirdRip)
ServiceManager.add(BirdStatic)

View file

@ -1,12 +1,5 @@
# """
# CORE Docker service allows running docker containers within CORE nodes.
# Copyright (c)2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# authors: Stuart Marsden
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
''' Docker service allows running docker containers within CORE nodes.
The running of Docker within a CORE node allows for additional extensibility to The running of Docker within a CORE node allows for additional extensibility to
the CORE services. This allows network applications and protocols to be easily the CORE services. This allows network applications and protocols to be easily
@ -102,39 +95,42 @@ Limitations:
for most due to contention. The service just does a hackish wait for 1 second for most due to contention. The service just does a hackish wait for 1 second
and retry. This means all the docker containers can take a while to come up and retry. This means all the docker containers can take a while to come up
depending on how many nodes you have. depending on how many nodes you have.
"""
''' from core.misc import log
from core.service import CoreService
from core.service import ServiceManager
logger = log.get_logger(__name__)
import os
import sys
try: try:
from docker import Client from docker import Client
except Exception: except ImportError:
pass logger.error("failure to import docker")
from core.service import CoreService, addservice
from core.misc.ipaddr import IPv4Prefix, IPv6Prefix
class DockerService(CoreService): class DockerService(CoreService):
''' This is a service which will allow running docker containers in a CORE """
This is a service which will allow running docker containers in a CORE
node. node.
''' """
_name = "Docker" _name = "Docker"
_group = "Docker" _group = "Docker"
_depends = () _depends = ()
_dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',) _dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',)
_configs = ('docker.sh', ) _configs = ('docker.sh',)
_startindex = 50 _startindex = 50
_startup = ('sh docker.sh',) _startup = ('sh docker.sh',)
_shutdown = ('service docker stop', ) _shutdown = ('service docker stop',)
# Container image to start # Container image to start
_image = "" _image = ""
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Returns a string having contents of a docker.sh script that """
Returns a string having contents of a docker.sh script that
can be modified to start a specific docker image. can be modified to start a specific docker image.
''' """
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# auto-generated by Docker (docker.py)\n" cfg += "# auto-generated by Docker (docker.py)\n"
# Docker likes to think it has DNS set up or it complains. # Docker likes to think it has DNS set up or it complains.
@ -156,27 +152,30 @@ until [ $result -eq 0 ]; do
# this is to alleviate contention to docker's SQLite database # this is to alleviate contention to docker's SQLite database
sleep 0.3 sleep 0.3
done done
""" % (cls._image, ) """ % (cls._image,)
return cfg return cfg
addservice(DockerService)
# This auto-loads Docker images having a :core tag, adding them to the list def load_services():
# of services under the "Docker" group. ServiceManager.add(DockerService)
if 'Client' in globals():
# This auto-loads Docker images having a :core tag, adding them to the list
# of services under the "Docker" group.
# TODO: change this logic, should be a proper configurable, or docker needs to be a required library
# TODO: also should make this call possible real time for reloading removing "magic" auto loading on import
if 'Client' in globals():
client = Client(version='1.10') client = Client(version='1.10')
images = client.images() images = client.images()
del client del client
else: else:
images = [] images = []
for image in images: for image in images:
if u'<none>' in image['RepoTags'][0]: if u'<none>' in image['RepoTags'][0]:
continue continue
for repo in image['RepoTags']: for repo in image['RepoTags']:
if u':core' not in repo: if u':core' not in repo:
continue continue
dockerid = repo.encode('ascii','ignore').split(':')[0] dockerid = repo.encode('ascii', 'ignore').split(':')[0]
SubClass = type('SubClass', (DockerService,), sub_class = type('SubClass', (DockerService,), {'_name': dockerid, '_image': dockerid})
{'_name': dockerid, '_image': dockerid}) ServiceManager.add(sub_class)
addservice(SubClass) del images
del images

View file

@ -1,24 +1,19 @@
# """
# CORE
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
nrl.py: defines services provided by NRL protolib tools hosted here: nrl.py: defines services provided by NRL protolib tools hosted here:
http://www.nrl.navy.mil/itd/ncs/products http://www.nrl.navy.mil/itd/ncs/products
''' """
from core.misc import utils
from core.misc.ipaddress import Ipv4Prefix
from core.service import CoreService
from core.service import ServiceManager
from core.service import CoreService, addservice
from core.misc.ipaddr import IPv4Prefix, IPv6Prefix
from core.misc.utils import *
from core.constants import *
class NrlService(CoreService): class NrlService(CoreService):
''' Parent class for NRL services. Defines properties and methods """
Parent class for NRL services. Defines properties and methods
common to NRL's routing daemons. common to NRL's routing daemons.
''' """""
_name = "Protean" _name = "Protean"
_group = "ProtoSvc" _group = "ProtoSvc"
_depends = () _depends = ()
@ -34,57 +29,60 @@ class NrlService(CoreService):
@staticmethod @staticmethod
def firstipv4prefix(node, prefixlen=24): def firstipv4prefix(node, prefixlen=24):
''' Similar to QuaggaService.routerid(). Helper to return the first IPv4 """
Similar to QuaggaService.routerid(). Helper to return the first IPv4
prefix of a node, using the supplied prefix length. This ignores the prefix of a node, using the supplied prefix length. This ignores the
interface's prefix length, so e.g. '/32' can turn into '/24'. interface's prefix length, so e.g. '/32' can turn into '/24'.
''' """
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control == True:
continue continue
for a in ifc.addrlist: for a in ifc.addrlist:
if a.find(".") >= 0: if a.find(".") >= 0:
addr = a.split('/')[0] addr = a.split('/')[0]
pre = IPv4Prefix("%s/%s" % (addr, prefixlen)) pre = Ipv4Prefix("%s/%s" % (addr, prefixlen))
return str(pre) return str(pre)
#raise ValueError, "no IPv4 address found" # raise ValueError, "no IPv4 address found"
return "0.0.0.0/%s" % prefixlen return "0.0.0.0/%s" % prefixlen
class MgenSinkService(NrlService): class MgenSinkService(NrlService):
_name = "MGEN_Sink" _name = "MGEN_Sink"
_configs = ("sink.mgen", ) _configs = ("sink.mgen",)
_startindex = 5 _startindex = 5
_startup = ("mgen input sink.mgen", ) _startup = ("mgen input sink.mgen",)
_validate = ("pidof mgen", ) _validate = ("pidof mgen",)
_shutdown = ("killall mgen", ) _shutdown = ("killall mgen",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
cfg = "0.0 LISTEN UDP 5000\n" cfg = "0.0 LISTEN UDP 5000\n"
for ifc in node.netifs(): for ifc in node.netifs():
name = sysctldevname(ifc.name) name = utils.sysctldevname(ifc.name)
cfg += "0.0 Join 224.225.1.2 INTERFACE %s\n" % name cfg += "0.0 Join 224.225.1.2 INTERFACE %s\n" % name
return cfg return cfg
@classmethod @classmethod
def getstartup(cls, node, services): def getstartup(cls, node, services):
cmd =cls._startup[0] cmd = cls._startup[0]
cmd += " output /tmp/mgen_%s.log" % node.name cmd += " output /tmp/mgen_%s.log" % node.name
return (cmd, ) return cmd,
addservice(MgenSinkService)
class NrlNhdp(NrlService): class NrlNhdp(NrlService):
''' NeighborHood Discovery Protocol for MANET networks. """
''' NeighborHood Discovery Protocol for MANET networks.
"""
_name = "NHDP" _name = "NHDP"
_startup = ("nrlnhdp", ) _startup = ("nrlnhdp",)
_shutdown = ("killall nrlnhdp", ) _shutdown = ("killall nrlnhdp",)
_validate = ("pidof nrlnhdp", ) _validate = ("pidof nrlnhdp",)
@classmethod @classmethod
def getstartup(cls, node, services): def getstartup(cls, node, services):
''' Generate the appropriate command-line based on node interfaces. """
''' Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0] cmd = cls._startup[0]
cmd += " -l /var/log/nrlnhdp.log" cmd += " -l /var/log/nrlnhdp.log"
cmd += " -rpipe %s_nhdp" % node.name cmd += " -rpipe %s_nhdp" % node.name
@ -101,33 +99,33 @@ class NrlNhdp(NrlService):
cmd += " -i " cmd += " -i "
cmd += " -i ".join(interfacenames) cmd += " -i ".join(interfacenames)
return (cmd, ) return cmd,
addservice(NrlNhdp)
class NrlSmf(NrlService): class NrlSmf(NrlService):
''' Simplified Multicast Forwarding for MANET networks. """
''' Simplified Multicast Forwarding for MANET networks.
"""
_name = "SMF" _name = "SMF"
_startup = ("sh startsmf.sh", ) _startup = ("sh startsmf.sh",)
_shutdown = ("killall nrlsmf", ) _shutdown = ("killall nrlsmf",)
_validate = ("pidof nrlsmf", ) _validate = ("pidof nrlsmf",)
_configs = ("startsmf.sh", ) _configs = ("startsmf.sh",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate a startup script for SMF. Because nrlsmf does not """
Generate a startup script for SMF. Because nrlsmf does not
daemonize, it can cause problems in some situations when launched daemonize, it can cause problems in some situations when launched
directly using vcmd. directly using vcmd.
''' """
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# auto-generated by nrl.py:NrlSmf.generateconfig()\n" cfg += "# auto-generated by nrl.py:NrlSmf.generateconfig()\n"
comments = "" comments = ""
cmd = "nrlsmf instance %s_smf" % (node.name) cmd = "nrlsmf instance %s_smf" % node.name
servicenames = map(lambda x: x._name, services) servicenames = map(lambda x: x._name, services)
netifs = filter(lambda x: not getattr(x, 'control', False), \ netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
node.netifs())
if len(netifs) == 0: if len(netifs) == 0:
return () return ()
@ -154,20 +152,21 @@ class NrlSmf(NrlService):
cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n"
return cfg return cfg
addservice(NrlSmf)
class NrlOlsr(NrlService): class NrlOlsr(NrlService):
''' Optimized Link State Routing protocol for MANET networks. """
''' Optimized Link State Routing protocol for MANET networks.
"""
_name = "OLSR" _name = "OLSR"
_startup = ("nrlolsrd", ) _startup = ("nrlolsrd",)
_shutdown = ("killall nrlolsrd", ) _shutdown = ("killall nrlolsrd",)
_validate = ("pidof nrlolsrd", ) _validate = ("pidof nrlolsrd",)
@classmethod @classmethod
def getstartup(cls, node, services): def getstartup(cls, node, services):
''' Generate the appropriate command-line based on node interfaces. """
''' Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0] cmd = cls._startup[0]
# are multiple interfaces supported? No. # are multiple interfaces supported? No.
netifs = list(node.netifs()) netifs = list(node.netifs())
@ -184,22 +183,23 @@ class NrlOlsr(NrlService):
if "zebra" in servicenames: if "zebra" in servicenames:
cmd += " -z" cmd += " -z"
return (cmd, ) return cmd,
addservice(NrlOlsr)
class NrlOlsrv2(NrlService): class NrlOlsrv2(NrlService):
''' Optimized Link State Routing protocol version 2 for MANET networks. """
''' Optimized Link State Routing protocol version 2 for MANET networks.
"""
_name = "OLSRv2" _name = "OLSRv2"
_startup = ("nrlolsrv2", ) _startup = ("nrlolsrv2",)
_shutdown = ("killall nrlolsrv2", ) _shutdown = ("killall nrlolsrv2",)
_validate = ("pidof nrlolsrv2", ) _validate = ("pidof nrlolsrv2",)
@classmethod @classmethod
def getstartup(cls, node, services): def getstartup(cls, node, services):
''' Generate the appropriate command-line based on node interfaces. """
''' Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0] cmd = cls._startup[0]
cmd += " -l /var/log/nrlolsrv2.log" cmd += " -l /var/log/nrlolsrv2.log"
cmd += " -rpipe %s_olsrv2" % node.name cmd += " -rpipe %s_olsrv2" % node.name
@ -211,44 +211,45 @@ class NrlOlsrv2(NrlService):
cmd += " -p olsr" cmd += " -p olsr"
netifs = filter(lambda x: not getattr(x, 'control', False), \ netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
node.netifs())
if len(netifs) > 0: if len(netifs) > 0:
interfacenames = map(lambda x: x.name, netifs) interfacenames = map(lambda x: x.name, netifs)
cmd += " -i " cmd += " -i "
cmd += " -i ".join(interfacenames) cmd += " -i ".join(interfacenames)
return (cmd, ) return cmd,
addservice(NrlOlsrv2)
class OlsrOrg(NrlService): class OlsrOrg(NrlService):
''' Optimized Link State Routing protocol from olsr.org for MANET networks. """
''' Optimized Link State Routing protocol from olsr.org for MANET networks.
"""
_name = "OLSRORG" _name = "OLSRORG"
_configs = ("/etc/olsrd/olsrd.conf",) _configs = ("/etc/olsrd/olsrd.conf",)
_dirs = ("/etc/olsrd",) _dirs = ("/etc/olsrd",)
_startup = ("olsrd", ) _startup = ("olsrd",)
_shutdown = ("killall olsrd", ) _shutdown = ("killall olsrd",)
_validate = ("pidof olsrd", ) _validate = ("pidof olsrd",)
@classmethod @classmethod
def getstartup(cls, node, services): def getstartup(cls, node, services):
''' Generate the appropriate command-line based on node interfaces. """
''' Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0] cmd = cls._startup[0]
netifs = filter(lambda x: not getattr(x, 'control', False), \ netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
node.netifs())
if len(netifs) > 0: if len(netifs) > 0:
interfacenames = map(lambda x: x.name, netifs) interfacenames = map(lambda x: x.name, netifs)
cmd += " -i " cmd += " -i "
cmd += " -i ".join(interfacenames) cmd += " -i ".join(interfacenames)
return (cmd, ) return cmd,
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate a default olsrd config file to use the broadcast address of 255.255.255.255. """
''' Generate a default olsrd config file to use the broadcast address of 255.255.255.255.
"""
cfg = """\ cfg = """\
# #
# OLSR.org routing daemon config file # OLSR.org routing daemon config file
@ -566,11 +567,11 @@ InterfaceDefaults {
""" """
return cfg return cfg
addservice(OlsrOrg)
class MgenActor(NrlService): class MgenActor(NrlService):
''' ZpcMgenActor. """
''' ZpcMgenActor.
"""
# a unique name is required, without spaces # a unique name is required, without spaces
_name = "MgenActor" _name = "MgenActor"
@ -582,53 +583,53 @@ class MgenActor(NrlService):
_dirs = () _dirs = ()
# generated files (without a full path this file goes in the node's dir, # generated files (without a full path this file goes in the node's dir,
# e.g. /tmp/pycore.12345/n1.conf/) # e.g. /tmp/pycore.12345/n1.conf/)
_configs = ('start_mgen_actor.sh', ) _configs = ('start_mgen_actor.sh',)
# this controls the starting order vs other enabled services # this controls the starting order vs other enabled services
_startindex = 50 _startindex = 50
# list of startup commands, also may be generated during startup # list of startup commands, also may be generated during startup
_startup = ("sh start_mgen_actor.sh", ) _startup = ("sh start_mgen_actor.sh",)
# list of validation commands # list of validation commands
_validate = ("pidof mgen", ) _validate = ("pidof mgen",)
# list of shutdown commands # list of shutdown commands
_shutdown = ("killall mgen", ) _shutdown = ("killall mgen",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate a startup script for MgenActor. Because mgenActor does not """
Generate a startup script for MgenActor. Because mgenActor does not
daemonize, it can cause problems in some situations when launched daemonize, it can cause problems in some situations when launched
directly using vcmd. directly using vcmd.
''' """
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n" cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n"
comments = "" comments = ""
cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % (node.name) cmd = "mgenBasicActor.py -n %s -a 0.0.0.0" % node.name
servicenames = map(lambda x: x._name, services) servicenames = map(lambda x: x._name, services)
netifs = filter(lambda x: not getattr(x, 'control', False), \ netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
node.netifs())
if len(netifs) == 0: if len(netifs) == 0:
return () return ()
cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n" cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n"
return cfg return cfg
# this line is required to add the above class to the list of available services
addservice(MgenActor)
class Arouted(NrlService): class Arouted(NrlService):
''' Adaptive Routing """
''' Adaptive Routing
"""
_name = "arouted" _name = "arouted"
_configs = ("startarouted.sh", ) _configs = ("startarouted.sh",)
_startindex = NrlService._startindex + 10 _startindex = NrlService._startindex + 10
_startup = ("sh startarouted.sh", ) _startup = ("sh startarouted.sh",)
_shutdown = ("pkill arouted", ) _shutdown = ("pkill arouted",)
_validate = ("pidof arouted", ) _validate = ("pidof arouted",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Return the Quagga.conf or quaggaboot.sh file contents. """
''' Return the Quagga.conf or quaggaboot.sh file contents.
"""
cfg = """ cfg = """
#!/bin/sh #!/bin/sh
for f in "/tmp/%s_smf"; do for f in "/tmp/%s_smf"; do
@ -643,12 +644,23 @@ for f in "/tmp/%s_smf"; do
done done
done done
""" % (node.name) """ % node.name
cfg += "ip route add %s dev lo\n" % cls.firstipv4prefix(node, 24) cfg += "ip route add %s dev lo\n" % cls.firstipv4prefix(node, 24)
cfg += "arouted instance %s_smf tap %s_tap" % (node.name, node.name) cfg += "arouted instance %s_smf tap %s_tap" % (node.name, node.name)
cfg += " stability 10" # seconds to consider a new route valid # seconds to consider a new route valid
cfg += " stability 10"
cfg += " 2>&1 > /var/log/arouted.log &\n\n" cfg += " 2>&1 > /var/log/arouted.log &\n\n"
return cfg return cfg
# experimental
#addservice(Arouted) def load_services():
ServiceManager.add(MgenSinkService)
ServiceManager.add(NrlNhdp)
ServiceManager.add(NrlSmf)
ServiceManager.add(NrlOlsr)
ServiceManager.add(NrlOlsrv2)
ServiceManager.add(OlsrOrg)
# this line is required to add the above class to the list of available services
ServiceManager.add(MgenActor)
# experimental
# ServiceManager.add(Arouted)

View file

@ -1,42 +1,33 @@
# """
# CORE
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
quagga.py: defines routing services provided by Quagga. quagga.py: defines routing services provided by Quagga.
''' """
import os import os
if os.uname()[0] == "Linux": from core import constants
from core.netns import nodes from core.enumerations import LinkTypes, NodeTypes
elif os.uname()[0] == "FreeBSD": from core.misc import ipaddress
from core.bsd import nodes from core.misc import nodeutils
from core.service import CoreService, addservice from core.service import CoreService
from core.misc.ipaddr import IPv4Prefix, isIPv4Address, isIPv6Address from core.service import ServiceManager
from core.api import coreapi
from core.constants import *
class Zebra(CoreService): class Zebra(CoreService):
'''
'''
_name = "zebra" _name = "zebra"
_group = "Quagga" _group = "Quagga"
_dirs = ("/usr/local/etc/quagga", "/var/run/quagga") _dirs = ("/usr/local/etc/quagga", "/var/run/quagga")
_configs = ("/usr/local/etc/quagga/Quagga.conf", _configs = ("/usr/local/etc/quagga/Quagga.conf",
"quaggaboot.sh","/usr/local/etc/quagga/vtysh.conf") "quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf")
_startindex = 35 _startindex = 35
_startup = ("sh quaggaboot.sh zebra",) _startup = ("sh quaggaboot.sh zebra",)
_shutdown = ("killall zebra", ) _shutdown = ("killall zebra",)
_validate = ("pidof zebra", ) _validate = ("pidof zebra",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Return the Quagga.conf or quaggaboot.sh file contents. """
''' Return the Quagga.conf or quaggaboot.sh file contents.
"""
if filename == cls._configs[0]: if filename == cls._configs[0]:
return cls.generateQuaggaConf(node, services) return cls.generateQuaggaConf(node, services)
elif filename == cls._configs[1]: elif filename == cls._configs[1]:
@ -48,16 +39,18 @@ class Zebra(CoreService):
@classmethod @classmethod
def generateVtyshConf(cls, node, services): def generateVtyshConf(cls, node, services):
''' Returns configuration file text. """
''' Returns configuration file text.
"""
return "service integrated-vtysh-config\n" return "service integrated-vtysh-config\n"
@classmethod @classmethod
def generateQuaggaConf(cls, node, services): def generateQuaggaConf(cls, node, services):
''' Returns configuration file text. Other services that depend on zebra """
Returns configuration file text. Other services that depend on zebra
will have generatequaggaifcconfig() and generatequaggaconfig() will have generatequaggaifcconfig() and generatequaggaconfig()
hooks that are invoked here. hooks that are invoked here.
''' """
# we could verify here that filename == Quagga.conf # we could verify here that filename == Quagga.conf
cfg = "" cfg = ""
for ifc in node.netifs(): for ifc in node.netifs():
@ -85,15 +78,13 @@ class Zebra(CoreService):
cfgv4 += ifccfg cfgv4 += ifccfg
if want_ipv4: if want_ipv4:
ipv4list = filter(lambda x: isIPv4Address(x.split('/')[0]), ipv4list = filter(lambda x: ipaddress.is_ipv4_address(x.split('/')[0]), ifc.addrlist)
ifc.addrlist)
cfg += " " cfg += " "
cfg += "\n ".join(map(cls.addrstr, ipv4list)) cfg += "\n ".join(map(cls.addrstr, ipv4list))
cfg += "\n" cfg += "\n"
cfg += cfgv4 cfg += cfgv4
if want_ipv6: if want_ipv6:
ipv6list = filter(lambda x: isIPv6Address(x.split('/')[0]), ipv6list = filter(lambda x: ipaddress.is_ipv6_address(x.split('/')[0]), ifc.addrlist)
ifc.addrlist)
cfg += " " cfg += " "
cfg += "\n ".join(map(cls.addrstr, ipv6list)) cfg += "\n ".join(map(cls.addrstr, ipv6list))
cfg += "\n" cfg += "\n"
@ -108,22 +99,24 @@ class Zebra(CoreService):
@staticmethod @staticmethod
def addrstr(x): def addrstr(x):
''' helper for mapping IP addresses to zebra config statements """
''' helper for mapping IP addresses to zebra config statements
"""
if x.find(".") >= 0: if x.find(".") >= 0:
return "ip address %s" % x return "ip address %s" % x
elif x.find(":") >= 0: elif x.find(":") >= 0:
return "ipv6 address %s" % x return "ipv6 address %s" % x
else: else:
raise Value, "invalid address: %s", x raise ValueError("invalid address: %s", x)
@classmethod @classmethod
def generateQuaggaBoot(cls, node, services): def generateQuaggaBoot(cls, node, services):
''' Generate a shell script used to boot the Quagga daemons. """
''' Generate a shell script used to boot the Quagga daemons.
"""
try: try:
quagga_bin_search = node.session.cfg['quagga_bin_search'] quagga_bin_search = node.session.config['quagga_bin_search']
quagga_sbin_search = node.session.cfg['quagga_sbin_search'] quagga_sbin_search = node.session.config['quagga_sbin_search']
except KeyError: except KeyError:
quagga_bin_search = '"/usr/local/bin /usr/bin /usr/lib/quagga"' quagga_bin_search = '"/usr/local/bin /usr/bin /usr/lib/quagga"'
quagga_sbin_search = '"/usr/local/sbin /usr/sbin /usr/lib/quagga"' quagga_sbin_search = '"/usr/local/sbin /usr/sbin /usr/lib/quagga"'
@ -220,18 +213,17 @@ if [ "$1" != "zebra" ]; then
fi fi
confcheck confcheck
bootquagga bootquagga
""" % (cls._configs[0], quagga_sbin_search, quagga_bin_search, \ """ % (cls._configs[0], quagga_sbin_search, quagga_bin_search, constants.QUAGGA_STATE_DIR)
QUAGGA_STATE_DIR)
addservice(Zebra)
class QuaggaService(CoreService): class QuaggaService(CoreService):
''' Parent class for Quagga services. Defines properties and methods """
Parent class for Quagga services. Defines properties and methods
common to Quagga's routing daemons. common to Quagga's routing daemons.
''' """
_name = "QuaggaDaemon" _name = "QuaggaDaemon"
_group = "Quagga" _group = "Quagga"
_depends = ("zebra", ) _depends = ("zebra",)
_dirs = () _dirs = ()
_configs = () _configs = ()
_startindex = 40 _startindex = 40
@ -244,27 +236,29 @@ class QuaggaService(CoreService):
@staticmethod @staticmethod
def routerid(node): def routerid(node):
''' Helper to return the first IPv4 address of a node as its router ID. """
''' Helper to return the first IPv4 address of a node as its router ID.
"""
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control == True:
continue continue
for a in ifc.addrlist: for a in ifc.addrlist:
if a.find(".") >= 0: if a.find(".") >= 0:
return a .split('/') [0] return a.split('/')[0]
#raise ValueError, "no IPv4 address found for router ID" # raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0" return "0.0.0.0"
@staticmethod @staticmethod
def rj45check(ifc): def rj45check(ifc):
''' Helper to detect whether interface is connected an external RJ45 """
Helper to detect whether interface is connected an external RJ45
link. link.
''' """
if ifc.net: if ifc.net:
for peerifc in ifc.net.netifs(): for peerifc in ifc.net.netifs():
if peerifc == ifc: if peerifc == ifc:
continue continue
if isinstance(peerifc, nodes.RJ45Node): if nodeutils.is_node(peerifc, NodeTypes.RJ45):
return True return True
return False return False
@ -281,12 +275,12 @@ class QuaggaService(CoreService):
return "" return ""
class Ospfv2(QuaggaService): class Ospfv2(QuaggaService):
''' The OSPFv2 service provides IPv4 routing for wired networks. It does """
The OSPFv2 service provides IPv4 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the not build its own configuration file but has hooks for adding to the
unified Quagga.conf file. unified Quagga.conf file.
''' """
_name = "OSPFv2" _name = "OSPFv2"
_startup = () _startup = ()
_shutdown = ("killall ospfd", ) _shutdown = ("killall ospfd", )
@ -295,10 +289,11 @@ class Ospfv2(QuaggaService):
@staticmethod @staticmethod
def mtucheck(ifc): def mtucheck(ifc):
''' Helper to detect MTU mismatch and add the appropriate OSPF """
Helper to detect MTU mismatch and add the appropriate OSPF
mtu-ignore command. This is needed when e.g. a node is linked via a mtu-ignore command. This is needed when e.g. a node is linked via a
GreTap device. GreTap device.
''' """
if ifc.mtu != 1500: if ifc.mtu != 1500:
# a workaround for PhysicalNode GreTap, which has no knowledge of # a workaround for PhysicalNode GreTap, which has no knowledge of
# the other nodes/nets # the other nodes/nets
@ -312,10 +307,11 @@ class Ospfv2(QuaggaService):
@staticmethod @staticmethod
def ptpcheck(ifc): def ptpcheck(ifc):
''' Helper to detect whether interface is connected to a notional """
Helper to detect whether interface is connected to a notional
point-to-point link. point-to-point link.
''' """
if isinstance(ifc.net, nodes.PtpNet): if nodeutils.is_node(ifc.net, NodeTypes.PEER_TO_PEER):
return " ip ospf network point-to-point\n" return " ip ospf network point-to-point\n"
return "" return ""
@ -326,12 +322,12 @@ class Ospfv2(QuaggaService):
cfg += " router-id %s\n" % rtrid cfg += " router-id %s\n" % rtrid
# network 10.0.0.0/24 area 0 # network 10.0.0.0/24 area 0
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
for a in ifc.addrlist: for a in ifc.addrlist:
if a.find(".") < 0: if a.find(".") < 0:
continue continue
net = IPv4Prefix(a) net = ipaddress.Ipv4Prefix(a)
cfg += " network %s area 0\n" % net cfg += " network %s area 0\n" % net
cfg += "!\n" cfg += "!\n"
return cfg return cfg
@ -339,25 +335,27 @@ class Ospfv2(QuaggaService):
@classmethod @classmethod
def generatequaggaifcconfig(cls, node, ifc): def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc) return cls.mtucheck(ifc)
#cfg = cls.mtucheck(ifc) # cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers # external RJ45 connections will use default OSPF timers
#if cls.rj45check(ifc): # if cls.rj45check(ifc):
# return cfg # return cfg
#cfg += cls.ptpcheck(ifc) # cfg += cls.ptpcheck(ifc)
# return cfg + """\
#return cfg + """\
# ip ospf hello-interval 2 # ip ospf hello-interval 2
# ip ospf dead-interval 6 # ip ospf dead-interval 6
# ip ospf retransmit-interval 5 # ip ospf retransmit-interval 5
#""" # """
addservice(Ospfv2)
class Ospfv3(QuaggaService): class Ospfv3(QuaggaService):
''' The OSPFv3 service provides IPv6 routing for wired networks. It does """
The OSPFv3 service provides IPv6 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the not build its own configuration file but has hooks for adding to the
unified Quagga.conf file. unified Quagga.conf file.
''' """
_name = "OSPFv3" _name = "OSPFv3"
_startup = () _startup = ()
_shutdown = ("killall ospf6d", ) _shutdown = ("killall ospf6d", )
@ -367,9 +365,10 @@ class Ospfv3(QuaggaService):
@staticmethod @staticmethod
def minmtu(ifc): def minmtu(ifc):
''' Helper to discover the minimum MTU of interfaces linked with the """
Helper to discover the minimum MTU of interfaces linked with the
given interface. given interface.
''' """
mtu = ifc.mtu mtu = ifc.mtu
if not ifc.net: if not ifc.net:
return mtu return mtu
@ -380,10 +379,11 @@ class Ospfv3(QuaggaService):
@classmethod @classmethod
def mtucheck(cls, ifc): def mtucheck(cls, ifc):
''' Helper to detect MTU mismatch and add the appropriate OSPFv3 """
Helper to detect MTU mismatch and add the appropriate OSPFv3
ifmtu command. This is needed when e.g. a node is linked via a ifmtu command. This is needed when e.g. a node is linked via a
GreTap device. GreTap device.
''' """
minmtu = cls.minmtu(ifc) minmtu = cls.minmtu(ifc)
if minmtu < ifc.mtu: if minmtu < ifc.mtu:
return " ipv6 ospf6 ifmtu %d\n" % minmtu return " ipv6 ospf6 ifmtu %d\n" % minmtu
@ -392,10 +392,11 @@ class Ospfv3(QuaggaService):
@staticmethod @staticmethod
def ptpcheck(ifc): def ptpcheck(ifc):
''' Helper to detect whether interface is connected to a notional """
Helper to detect whether interface is connected to a notional
point-to-point link. point-to-point link.
''' """
if isinstance(ifc.net, nodes.PtpNet): if nodeutils.is_node(ifc.net, NodeTypes.PEER_TO_PEER):
return " ipv6 ospf6 network point-to-point\n" return " ipv6 ospf6 network point-to-point\n"
return "" return ""
@ -405,7 +406,7 @@ class Ospfv3(QuaggaService):
rtrid = cls.routerid(node) rtrid = cls.routerid(node)
cfg += " router-id %s\n" % rtrid cfg += " router-id %s\n" % rtrid
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += " interface %s area 0.0.0.0\n" % ifc.name cfg += " interface %s area 0.0.0.0\n" % ifc.name
cfg += "!\n" cfg += "!\n"
@ -414,26 +415,28 @@ class Ospfv3(QuaggaService):
@classmethod @classmethod
def generatequaggaifcconfig(cls, node, ifc): def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc) return cls.mtucheck(ifc)
#cfg = cls.mtucheck(ifc) # cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers # external RJ45 connections will use default OSPF timers
#if cls.rj45check(ifc): # if cls.rj45check(ifc):
# return cfg # return cfg
#cfg += cls.ptpcheck(ifc) # cfg += cls.ptpcheck(ifc)
# return cfg + """\
#return cfg + """\
# ipv6 ospf6 hello-interval 2 # ipv6 ospf6 hello-interval 2
# ipv6 ospf6 dead-interval 6 # ipv6 ospf6 dead-interval 6
# ipv6 ospf6 retransmit-interval 5 # ipv6 ospf6 retransmit-interval 5
#""" # """
addservice(Ospfv3)
class Ospfv3mdr(Ospfv3): class Ospfv3mdr(Ospfv3):
''' The OSPFv3 MANET Designated Router (MDR) service provides IPv6 """
The OSPFv3 MANET Designated Router (MDR) service provides IPv6
routing for wireless networks. It does not build its own routing for wireless networks. It does not build its own
configuration file but has hooks for adding to the configuration file but has hooks for adding to the
unified Quagga.conf file. unified Quagga.conf file.
''' """
_name = "OSPFv3MDR" _name = "OSPFv3MDR"
_ipv4_routing = True _ipv4_routing = True
@ -441,8 +444,7 @@ class Ospfv3mdr(Ospfv3):
def generatequaggaifcconfig(cls, node, ifc): def generatequaggaifcconfig(cls, node, ifc):
cfg = cls.mtucheck(ifc) cfg = cls.mtucheck(ifc)
cfg += " ipv6 ospf6 instance-id 65\n" cfg += " ipv6 ospf6 instance-id 65\n"
if ifc.net is not None and \ if ifc.net is not None and nodeutils.is_node(ifc.net, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
isinstance(ifc.net, (nodes.WlanNode, nodes.EmaneNode)):
return cfg + """\ return cfg + """\
ipv6 ospf6 hello-interval 2 ipv6 ospf6 hello-interval 2
ipv6 ospf6 dead-interval 6 ipv6 ospf6 dead-interval 6
@ -455,13 +457,13 @@ class Ospfv3mdr(Ospfv3):
else: else:
return cfg return cfg
addservice(Ospfv3mdr)
class Bgp(QuaggaService): class Bgp(QuaggaService):
'''' The BGP service provides interdomain routing. """
The BGP service provides interdomain routing.
Peers must be manually configured, with a full mesh for those Peers must be manually configured, with a full mesh for those
having the same AS number. having the same AS number.
''' """
_name = "BGP" _name = "BGP"
_startup = () _startup = ()
_shutdown = ("killall bgpd", ) _shutdown = ("killall bgpd", )
@ -482,11 +484,11 @@ class Bgp(QuaggaService):
cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n" cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n"
return cfg return cfg
addservice(Bgp)
class Rip(QuaggaService): class Rip(QuaggaService):
''' The RIP service provides IPv4 routing for wired networks. """
''' The RIP service provides IPv4 routing for wired networks.
"""
_name = "RIP" _name = "RIP"
_startup = () _startup = ()
_shutdown = ("killall ripd", ) _shutdown = ("killall ripd", )
@ -505,11 +507,11 @@ router rip
""" """
return cfg return cfg
addservice(Rip)
class Ripng(QuaggaService): class Ripng(QuaggaService):
''' The RIP NG service provides IPv6 routing for wired networks. """
''' The RIP NG service provides IPv6 routing for wired networks.
"""
_name = "RIPNG" _name = "RIPNG"
_startup = () _startup = ()
_shutdown = ("killall ripngd", ) _shutdown = ("killall ripngd", )
@ -528,12 +530,12 @@ router ripng
""" """
return cfg return cfg
addservice(Ripng)
class Babel(QuaggaService): class Babel(QuaggaService):
''' The Babel service provides a loop-avoiding distance-vector routing """
The Babel service provides a loop-avoiding distance-vector routing
protocol for IPv6 and IPv4 with fast convergence properties. protocol for IPv6 and IPv4 with fast convergence properties.
''' """
_name = "Babel" _name = "Babel"
_startup = () _startup = ()
_shutdown = ("killall babeld", ) _shutdown = ("killall babeld", )
@ -544,7 +546,7 @@ class Babel(QuaggaService):
def generatequaggaconfig(cls, node): def generatequaggaconfig(cls, node):
cfg = "router babel\n" cfg = "router babel\n"
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += " network %s\n" % ifc.name cfg += " network %s\n" % ifc.name
cfg += " redistribute static\n redistribute connected\n" cfg += " redistribute static\n redistribute connected\n"
@ -553,17 +555,16 @@ class Babel(QuaggaService):
@classmethod @classmethod
def generatequaggaifcconfig(cls, node, ifc): def generatequaggaifcconfig(cls, node, ifc):
type = "wired" type = "wired"
if ifc.net and ifc.net.linktype == coreapi.CORE_LINK_WIRELESS: if ifc.net and ifc.net.linktype == LinkTypes.WIRELESS.value:
return " babel wireless\n no babel split-horizon\n" return " babel wireless\n no babel split-horizon\n"
else: else:
return " babel wired\n babel split-horizon\n" return " babel wired\n babel split-horizon\n"
addservice(Babel)
class Xpimd(QuaggaService): class Xpimd(QuaggaService):
'''\ """
PIM multicast routing based on XORP. PIM multicast routing based on XORP.
''' """
_name = 'Xpimd' _name = 'Xpimd'
_startup = () _startup = ()
_shutdown = ('killall xpimd', ) _shutdown = ('killall xpimd', )
@ -590,4 +591,14 @@ class Xpimd(QuaggaService):
def generatequaggaifcconfig(cls, node, ifc): def generatequaggaifcconfig(cls, node, ifc):
return ' ip mfea\n ip igmp\n ip pim\n' return ' ip mfea\n ip igmp\n ip pim\n'
addservice(Xpimd)
def load_services():
ServiceManager.add(Zebra)
ServiceManager.add(Ospfv2)
ServiceManager.add(Ospfv3)
ServiceManager.add(Ospfv3mdr)
ServiceManager.add(Bgp)
ServiceManager.add(Rip)
ServiceManager.add(Ripng)
ServiceManager.add(Babel)
ServiceManager.add(Xpimd)

View file

@ -1,83 +1,75 @@
# """
# CORE - define security services : vpnclient, vpnserver, ipsec and firewall
#
# Copyright (c)2011-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
'''
security.py: defines security services (vpnclient, vpnserver, ipsec and security.py: defines security services (vpnclient, vpnserver, ipsec and
firewall) firewall)
''' """
import os from core import constants
from core.misc import log
from core.service import CoreService
from core.service import ServiceManager
logger = log.get_logger(__name__)
from core.service import CoreService, addservice
from core.constants import *
class VPNClient(CoreService): class VPNClient(CoreService):
'''
'''
_name = "VPNClient" _name = "VPNClient"
_group = "Security" _group = "Security"
_configs = ('vpnclient.sh', ) _configs = ('vpnclient.sh',)
_startindex = 60 _startindex = 60
_startup = ('sh vpnclient.sh',) _startup = ('sh vpnclient.sh',)
_shutdown = ("killall openvpn",) _shutdown = ("killall openvpn",)
_validate = ("pidof openvpn", ) _validate = ("pidof openvpn",)
_custom_needed = True _custom_needed = True
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Return the client.conf and vpnclient.sh file contents to """
''' Return the client.conf and vpnclient.sh file contents to
"""
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# custom VPN Client configuration for service (security.py)\n" cfg += "# custom VPN Client configuration for service (security.py)\n"
fname = "%s/examples/services/sampleVPNClient" % CORE_DATA_DIR fname = "%s/examples/services/sampleVPNClient" % constants.CORE_DATA_DIR
try: try:
cfg += open(fname, "rb").read() cfg += open(fname, "rb").read()
except e: except IOError:
print "Error opening VPN client configuration template (%s): %s" % \ logger.exception("Error opening VPN client configuration template (%s)", fname)
(fname, e)
return cfg return cfg
# this line is required to add the above class to the list of available services
addservice(VPNClient)
class VPNServer(CoreService): class VPNServer(CoreService):
'''
'''
_name = "VPNServer" _name = "VPNServer"
_group = "Security" _group = "Security"
_configs = ('vpnserver.sh', ) _configs = ('vpnserver.sh',)
_startindex = 50 _startindex = 50
_startup = ('sh vpnserver.sh',) _startup = ('sh vpnserver.sh',)
_shutdown = ("killall openvpn",) _shutdown = ("killall openvpn",)
_validate = ("pidof openvpn", ) _validate = ("pidof openvpn",)
_custom_needed = True _custom_needed = True
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Return the sample server.conf and vpnserver.sh file contents to """
Return the sample server.conf and vpnserver.sh file contents to
GUI for user customization. GUI for user customization.
''' """
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# custom VPN Server Configuration for service (security.py)\n" cfg += "# custom VPN Server Configuration for service (security.py)\n"
fname = "%s/examples/services/sampleVPNServer" % CORE_DATA_DIR fname = "%s/examples/services/sampleVPNServer" % constants.CORE_DATA_DIR
try: try:
cfg += open(fname, "rb").read() cfg += open(fname, "rb").read()
except e: except IOError:
print "Error opening VPN server configuration template (%s): %s" % \ logger.exception("Error opening VPN server configuration template (%s)", fname)
(fname, e)
return cfg return cfg
addservice(VPNServer)
class IPsec(CoreService): class IPsec(CoreService):
'''
'''
_name = "IPsec" _name = "IPsec"
_group = "Security" _group = "Security"
_configs = ('ipsec.sh', ) _configs = ('ipsec.sh',)
_startindex = 60 _startindex = 60
_startup = ('sh ipsec.sh',) _startup = ('sh ipsec.sh',)
_shutdown = ("killall racoon",) _shutdown = ("killall racoon",)
@ -85,45 +77,51 @@ class IPsec(CoreService):
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Return the ipsec.conf and racoon.conf file contents to """
Return the ipsec.conf and racoon.conf file contents to
GUI for user customization. GUI for user customization.
''' """
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# set up static tunnel mode security assocation for service " cfg += "# set up static tunnel mode security assocation for service "
cfg += "(security.py)\n" cfg += "(security.py)\n"
fname = "%s/examples/services/sampleIPsec" % CORE_DATA_DIR fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR
try: try:
cfg += open(fname, "rb").read() cfg += open(fname, "rb").read()
except e: except IOError:
print "Error opening IPsec configuration template (%s): %s" % \ logger.exception("Error opening IPsec configuration template (%s)", fname)
(fname, e)
return cfg return cfg
addservice(IPsec)
class Firewall(CoreService): class Firewall(CoreService):
'''
'''
_name = "Firewall" _name = "Firewall"
_group = "Security" _group = "Security"
_configs = ('firewall.sh', ) _configs = ('firewall.sh',)
_startindex = 20 _startindex = 20
_startup = ('sh firewall.sh',) _startup = ('sh firewall.sh',)
_custom_needed = True _custom_needed = True
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Return the firewall rule examples to GUI for user customization. """
''' Return the firewall rule examples to GUI for user customization.
"""
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# custom node firewall rules for service (security.py)\n" cfg += "# custom node firewall rules for service (security.py)\n"
fname = "%s/examples/services/sampleFirewall" % CORE_DATA_DIR fname = "%s/examples/services/sampleFirewall" % constants.CORE_DATA_DIR
try: try:
cfg += open(fname, "rb").read() cfg += open(fname, "rb").read()
except e: except IOError:
print "Error opening Firewall configuration template (%s): %s" % \ logger.exception("Error opening Firewall configuration template (%s)", fname)
(fname, e)
return cfg return cfg
addservice(Firewall)
def load_services():
# this line is required to add the above class to the list of available services
ServiceManager.add(VPNClient)
ServiceManager.add(VPNServer)
ServiceManager.add(IPsec)
ServiceManager.add(Firewall)

View file

@ -1,23 +1,27 @@
from core.service import CoreService, addservice
from sys import maxint
from inspect import isclass from inspect import isclass
from sys import maxint
from core.service import CoreService
from core.service import ServiceManager
class Startup(CoreService): class Startup(CoreService):
'A CORE service to start other services in order, serially' """
A CORE service to start other services in order, serially
"""
_name = 'startup' _name = 'startup'
_group = 'Utility' _group = 'Utility'
_depends = () _depends = ()
_dirs = () _dirs = ()
_configs = ('startup.sh', ) _configs = ('startup.sh',)
_startindex = maxint _startindex = maxint
_startup = ('sh startup.sh', ) _startup = ('sh startup.sh',)
_shutdown = () _shutdown = ()
_validate = () _validate = ()
@staticmethod @staticmethod
def isStartupService(s): def is_startup_service(s):
return isinstance(s, Startup) or \ return isinstance(s, Startup) or (isclass(s) and issubclass(s, Startup))
(isclass(s) and issubclass(s, Startup))
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
@ -26,12 +30,14 @@ class Startup(CoreService):
script = '#!/bin/sh\n' \ script = '#!/bin/sh\n' \
'# auto-generated by Startup (startup.py)\n\n' \ '# auto-generated by Startup (startup.py)\n\n' \
'exec > startup.log 2>&1\n\n' 'exec > startup.log 2>&1\n\n'
for s in sorted(services, key = lambda x: x._startindex): for s in sorted(services, key=lambda x: x._startindex):
if cls.isStartupService(s) or len(str(s._starttime)) > 0: if cls.is_startup_service(s) or len(str(s._starttime)) > 0:
continue continue
start = '\n'.join(s.getstartup(node, services)) start = '\n'.join(s.getstartup(node, services))
if start: if start:
script += start + '\n' script += start + '\n'
return script return script
addservice(Startup)
def load_services():
ServiceManager.add(Startup)

View file

@ -1,41 +1,30 @@
# """
# CORE configuration for UCARP
# Copyright (c) 2012 Jonathan deBoer
# See the LICENSE file included in this distribution.
#
#
# author: Jonathan deBoer <jdccdevel@gmail.com>
#
'''
ucarp.py: defines high-availability IP address controlled by ucarp ucarp.py: defines high-availability IP address controlled by ucarp
''' """
import os from core.service import CoreService
from core.service import ServiceManager
from core.service import CoreService, addservice UCARP_ETC = "/usr/local/etc/ucarp"
from core.misc.ipaddr import IPv4Prefix
from core.constants import *
UCARP_ETC="/usr/local/etc/ucarp"
class Ucarp(CoreService): class Ucarp(CoreService):
'''
'''
_name = "ucarp" _name = "ucarp"
_group = "Utility" _group = "Utility"
_depends = ( ) _depends = ( )
_dirs = (UCARP_ETC, ) _dirs = (UCARP_ETC,)
_configs = (UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh",) _configs = (
UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh",)
_startindex = 65 _startindex = 65
_startup = ("sh ucarpboot.sh",) _startup = ("sh ucarpboot.sh",)
_shutdown = ("killall ucarp", ) _shutdown = ("killall ucarp",)
_validate = ("pidof ucarp", ) _validate = ("pidof ucarp",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Return the default file contents """
''' Return the default file contents
"""
if filename == cls._configs[0]: if filename == cls._configs[0]:
return cls.generateUcarpConf(node, services) return cls.generateUcarpConf(node, services)
elif filename == cls._configs[1]: elif filename == cls._configs[1]:
@ -49,12 +38,14 @@ class Ucarp(CoreService):
@classmethod @classmethod
def generateUcarpConf(cls, node, services): def generateUcarpConf(cls, node, services):
''' Returns configuration file text. """
''' Returns configuration file text.
"""
try: try:
ucarp_bin = node.session.cfg['ucarp_bin'] ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError: except KeyError:
ucarp_bin = "/usr/sbin/ucarp" ucarp_bin = "/usr/sbin/ucarp"
return """\ return """\
#!/bin/sh #!/bin/sh
# Location of UCARP executable # Location of UCARP executable
@ -116,8 +107,10 @@ ${UCARP_EXEC} -B ${UCARP_OPTS}
@classmethod @classmethod
def generateUcarpBoot(cls, node, services): def generateUcarpBoot(cls, node, services):
''' Generate a shell script used to boot the Ucarp daemons. """
''' Generate a shell script used to boot the Ucarp daemons.
"""
try: try:
ucarp_bin = node.session.cfg['ucarp_bin'] ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError: except KeyError:
@ -132,16 +125,18 @@ chmod a+x ${UCARP_CFGDIR}/*.sh
# Start the default ucarp daemon configuration # Start the default ucarp daemon configuration
${UCARP_CFGDIR}/default.sh ${UCARP_CFGDIR}/default.sh
""" % (UCARP_ETC) """ % UCARP_ETC
@classmethod @classmethod
def generateVipUp(cls, node, services): def generateVipUp(cls, node, services):
''' Generate a shell script used to start the virtual ip """
''' Generate a shell script used to start the virtual ip
"""
try: try:
ucarp_bin = node.session.cfg['ucarp_bin'] ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError: except KeyError:
ucarp_bin = "/usr/sbin/ucarp" ucarp_bin = "/usr/sbin/ucarp"
return """\ return """\
#!/bin/bash #!/bin/bash
@ -161,8 +156,9 @@ fi
@classmethod @classmethod
def generateVipDown(cls, node, services): def generateVipDown(cls, node, services):
''' Generate a shell script used to stop the virtual ip """
''' Generate a shell script used to stop the virtual ip
"""
try: try:
ucarp_bin = node.session.cfg['ucarp_bin'] ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError: except KeyError:
@ -185,5 +181,5 @@ fi
""" """
addservice(Ucarp) def load_services():
ServiceManager.add(Ucarp)

View file

@ -1,24 +1,22 @@
# """
# CORE
# Copyright (c)2010-2014 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
utility.py: defines miscellaneous utility services. utility.py: defines miscellaneous utility services.
''' """
import os import os
import subprocess
from core import constants
from core.misc import utils
from core.misc.ipaddress import Ipv4Prefix
from core.misc.ipaddress import Ipv6Prefix
from core.service import CoreService
from core.service import ServiceManager
from core.service import CoreService, addservice
from core.misc.ipaddr import IPv4Prefix, IPv6Prefix
from core.misc.utils import *
from core.constants import *
class UtilService(CoreService): class UtilService(CoreService):
''' Parent class for utility services. """
''' Parent class for utility services.
"""
_name = "UtilityProcess" _name = "UtilityProcess"
_group = "Utility" _group = "Utility"
_depends = () _depends = ()
@ -32,11 +30,12 @@ class UtilService(CoreService):
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
return "" return ""
class IPForwardService(UtilService): class IPForwardService(UtilService):
_name = "IPForward" _name = "IPForward"
_configs = ("ipforward.sh", ) _configs = ("ipforward.sh",)
_startindex = 5 _startindex = 5
_startup = ("sh ipforward.sh", ) _startup = ("sh ipforward.sh",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
@ -60,13 +59,13 @@ class IPForwardService(UtilService):
%(sysctl)s -w net.ipv4.conf.default.send_redirects=0 %(sysctl)s -w net.ipv4.conf.default.send_redirects=0
%(sysctl)s -w net.ipv4.conf.all.rp_filter=0 %(sysctl)s -w net.ipv4.conf.all.rp_filter=0
%(sysctl)s -w net.ipv4.conf.default.rp_filter=0 %(sysctl)s -w net.ipv4.conf.default.rp_filter=0
""" % {'sysctl': SYSCTL_BIN} """ % {'sysctl': constants.SYSCTL_BIN}
for ifc in node.netifs(): for ifc in node.netifs():
name = sysctldevname(ifc.name) name = utils.sysctldevname(ifc.name)
cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (SYSCTL_BIN, name) cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (constants.SYSCTL_BIN, name)
cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % \ cfg += "%s -w net.ipv4.conf.%s.send_redirects=0\n" % \
(SYSCTL_BIN, name) (constants.SYSCTL_BIN, name)
cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (SYSCTL_BIN, name) cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (constants.SYSCTL_BIN, name)
return cfg return cfg
@classmethod @classmethod
@ -78,9 +77,8 @@ class IPForwardService(UtilService):
%s -w net.inet6.ip6.forwarding=1 %s -w net.inet6.ip6.forwarding=1
%s -w net.inet.icmp.bmcastecho=1 %s -w net.inet.icmp.bmcastecho=1
%s -w net.inet.icmp.icmplim=0 %s -w net.inet.icmp.icmplim=0
""" % (SYSCTL_BIN, SYSCTL_BIN, SYSCTL_BIN, SYSCTL_BIN) """ % (constants.SYSCTL_BIN, constants.SYSCTL_BIN, constants.SYSCTL_BIN, constants.SYSCTL_BIN)
addservice(IPForwardService)
class DefaultRouteService(UtilService): class DefaultRouteService(UtilService):
_name = "DefaultRoute" _name = "DefaultRoute"
@ -92,7 +90,7 @@ class DefaultRouteService(UtilService):
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# auto-generated by DefaultRoute service (utility.py)\n" cfg += "# auto-generated by DefaultRoute service (utility.py)\n"
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += "\n".join(map(cls.addrstr, ifc.addrlist)) cfg += "\n".join(map(cls.addrstr, ifc.addrlist))
cfg += "\n" cfg += "\n"
@ -101,12 +99,12 @@ class DefaultRouteService(UtilService):
@staticmethod @staticmethod
def addrstr(x): def addrstr(x):
if x.find(":") >= 0: if x.find(":") >= 0:
net = IPv6Prefix(x) net = Ipv6Prefix(x)
fam = "inet6 ::" fam = "inet6 ::"
else: else:
net = IPv4Prefix(x) net = Ipv4Prefix(x)
fam = "inet 0.0.0.0" fam = "inet 0.0.0.0"
if net.maxaddr() == net.minaddr(): if net.max_addr() == net.min_addr():
return "" return ""
else: else:
if os.uname()[0] == "Linux": if os.uname()[0] == "Linux":
@ -115,9 +113,8 @@ class DefaultRouteService(UtilService):
rtcmd = "route add -%s" % fam rtcmd = "route add -%s" % fam
else: else:
raise Exception, "unknown platform" raise Exception, "unknown platform"
return "%s %s" % (rtcmd, net.minaddr()) return "%s %s" % (rtcmd, net.min_addr())
addservice(DefaultRouteService)
class DefaultMulticastRouteService(UtilService): class DefaultMulticastRouteService(UtilService):
_name = "DefaultMulticastRoute" _name = "DefaultMulticastRoute"
@ -145,7 +142,6 @@ class DefaultMulticastRouteService(UtilService):
break break
return cfg return cfg
addservice(DefaultMulticastRouteService)
class StaticRouteService(UtilService): class StaticRouteService(UtilService):
_name = "StaticRoute" _name = "StaticRoute"
@ -160,7 +156,7 @@ class StaticRouteService(UtilService):
cfg += "# NOTE: this service must be customized to be of any use\n" cfg += "# NOTE: this service must be customized to be of any use\n"
cfg += "# Below are samples that you can uncomment and edit.\n#\n" cfg += "# Below are samples that you can uncomment and edit.\n#\n"
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += "\n".join(map(cls.routestr, ifc.addrlist)) cfg += "\n".join(map(cls.routestr, ifc.addrlist))
cfg += "\n" cfg += "\n"
@ -169,14 +165,14 @@ class StaticRouteService(UtilService):
@staticmethod @staticmethod
def routestr(x): def routestr(x):
if x.find(":") >= 0: if x.find(":") >= 0:
net = IPv6Prefix(x) net = Ipv6Prefix(x)
fam = "inet6" fam = "inet6"
dst = "3ffe:4::/64" dst = "3ffe:4::/64"
else: else:
net = IPv4Prefix(x) net = Ipv4Prefix(x)
fam = "inet" fam = "inet"
dst = "10.9.8.0/24" dst = "10.9.8.0/24"
if net.maxaddr() == net.minaddr(): if net.max_addr() == net.min_addr():
return "" return ""
else: else:
if os.uname()[0] == "Linux": if os.uname()[0] == "Linux":
@ -185,9 +181,8 @@ class StaticRouteService(UtilService):
rtcmd = "#/sbin/route add -%s %s" % (fam, dst) rtcmd = "#/sbin/route add -%s %s" % (fam, dst)
else: else:
raise Exception, "unknown platform" raise Exception, "unknown platform"
return "%s %s" % (rtcmd, net.minaddr()) return "%s %s" % (rtcmd, net.min_addr())
addservice(StaticRouteService)
class SshService(UtilService): class SshService(UtilService):
_name = "SSH" _name = "SSH"
@ -203,9 +198,10 @@ class SshService(UtilService):
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Use a startup script for launching sshd in order to wait for host """
Use a startup script for launching sshd in order to wait for host
key generation. key generation.
''' """
if os.uname()[0] == "FreeBSD": if os.uname()[0] == "FreeBSD":
sshcfgdir = node.nodedir sshcfgdir = node.nodedir
sshstatedir = node.nodedir sshstatedir = node.nodedir
@ -264,7 +260,6 @@ UsePAM yes
UseDNS no UseDNS no
""" % (sshcfgdir, sshstatedir, sshlibdir) """ % (sshcfgdir, sshstatedir, sshlibdir)
addservice(SshService)
class DhcpService(UtilService): class DhcpService(UtilService):
_name = "DHCP" _name = "DHCP"
@ -276,9 +271,10 @@ class DhcpService(UtilService):
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate a dhcpd config file using the network address of """
Generate a dhcpd config file using the network address of
each interface. each interface.
''' """
cfg = """\ cfg = """\
# auto-generated by DHCP service (utility.py) # auto-generated by DHCP service (utility.py)
# NOTE: move these option lines into the desired pool { } block(s) below # NOTE: move these option lines into the desired pool { } block(s) below
@ -294,7 +290,7 @@ max-lease-time 7200;
ddns-update-style none; ddns-update-style none;
""" """
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += "\n".join(map(cls.subnetentry, ifc.addrlist)) cfg += "\n".join(map(cls.subnetentry, ifc.addrlist))
cfg += "\n" cfg += "\n"
@ -302,17 +298,18 @@ ddns-update-style none;
@staticmethod @staticmethod
def subnetentry(x): def subnetentry(x):
''' Generate a subnet declaration block given an IPv4 prefix string """
Generate a subnet declaration block given an IPv4 prefix string
for inclusion in the dhcpd3 config file. for inclusion in the dhcpd3 config file.
''' """
if x.find(":") >= 0: if x.find(":") >= 0:
return "" return ""
else: else:
addr = x.split("/")[0] addr = x.split("/")[0]
net = IPv4Prefix(x) net = Ipv4Prefix(x)
# divide the address space in half # divide the address space in half
rangelow = net.addr(net.numaddr() / 2) rangelow = net.addr(net.num_addr() / 2)
rangehigh = net.maxaddr() rangehigh = net.max_addr()
return """ return """
subnet %s netmask %s { subnet %s netmask %s {
pool { pool {
@ -321,13 +318,13 @@ subnet %s netmask %s {
option routers %s; option routers %s;
} }
} }
""" % (net.prefixstr(), net.netmaskstr(), rangelow, rangehigh, addr) """ % (net.prefix_str(), net.netmask_str(), rangelow, rangehigh, addr)
addservice(DhcpService)
class DhcpClientService(UtilService): class DhcpClientService(UtilService):
''' Use a DHCP client for all interfaces for addressing. """
''' Use a DHCP client for all interfaces for addressing.
"""
_name = "DHCPClient" _name = "DHCPClient"
_configs = ("startdhcpclient.sh",) _configs = ("startdhcpclient.sh",)
_startup = ("sh startdhcpclient.sh",) _startup = ("sh startdhcpclient.sh",)
@ -336,8 +333,9 @@ class DhcpClientService(UtilService):
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate a script to invoke dhclient on all interfaces. """
''' Generate a script to invoke dhclient on all interfaces.
"""
cfg = "#!/bin/sh\n" cfg = "#!/bin/sh\n"
cfg += "# auto-generated by DHCPClient service (utility.py)\n" cfg += "# auto-generated by DHCPClient service (utility.py)\n"
cfg += "# uncomment this mkdir line and symlink line to enable client-" cfg += "# uncomment this mkdir line and symlink line to enable client-"
@ -353,11 +351,11 @@ class DhcpClientService(UtilService):
cfg += " -lf /var/run/dhclient-%s.lease %s\n" % (ifc.name, ifc.name) cfg += " -lf /var/run/dhclient-%s.lease %s\n" % (ifc.name, ifc.name)
return cfg return cfg
addservice(DhcpClientService)
class FtpService(UtilService): class FtpService(UtilService):
''' Start a vsftpd server. """
''' Start a vsftpd server.
"""
_name = "FTP" _name = "FTP"
_configs = ("vsftpd.conf",) _configs = ("vsftpd.conf",)
_dirs = ("/var/run/vsftpd/empty", "/var/ftp",) _dirs = ("/var/run/vsftpd/empty", "/var/ftp",)
@ -367,8 +365,9 @@ class FtpService(UtilService):
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate a vsftpd.conf configuration file. """
''' Generate a vsftpd.conf configuration file.
"""
return """\ return """\
# vsftpd.conf auto-generated by FTP service (utility.py) # vsftpd.conf auto-generated by FTP service (utility.py)
listen=YES listen=YES
@ -384,16 +383,16 @@ secure_chroot_dir=/var/run/vsftpd/empty
anon_root=/var/ftp anon_root=/var/ftp
""" """
addservice(FtpService)
class HttpService(UtilService): class HttpService(UtilService):
''' Start an apache server. """
''' Start an apache server.
"""
_name = "HTTP" _name = "HTTP"
_configs = ("/etc/apache2/apache2.conf", "/etc/apache2/envvars", _configs = ("/etc/apache2/apache2.conf", "/etc/apache2/envvars",
"/var/www/index.html",) "/var/www/index.html",)
_dirs = ("/etc/apache2", "/var/run/apache2", "/var/log/apache2", _dirs = ("/etc/apache2", "/var/run/apache2", "/var/log/apache2",
"/run/lock", "/var/lock/apache2", "/var/www", ) "/run/lock", "/var/lock/apache2", "/var/www",)
_startup = ("chown www-data /var/lock/apache2", "apache2ctl start",) _startup = ("chown www-data /var/lock/apache2", "apache2ctl start",)
_shutdown = ("apache2ctl stop",) _shutdown = ("apache2ctl stop",)
_validate = ("pidof apache2",) _validate = ("pidof apache2",)
@ -402,8 +401,9 @@ class HttpService(UtilService):
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate an apache2.conf configuration file. """
''' Generate an apache2.conf configuration file.
"""
if filename == cls._configs[0]: if filename == cls._configs[0]:
return cls.generateapache2conf(node, filename, services) return cls.generateapache2conf(node, filename, services)
elif filename == cls._configs[1]: elif filename == cls._configs[1]:
@ -415,43 +415,45 @@ class HttpService(UtilService):
@classmethod @classmethod
def detectversionfromcmd(cls): def detectversionfromcmd(cls):
''' Detect the apache2 version using the 'a2query' command. """
''' Detect the apache2 version using the 'a2query' command.
"""
try: try:
status, result = cmdresult(['a2query', '-v']) status, result = utils.cmdresult(['a2query', '-v'])
except Exception: except subprocess.CalledProcessError:
status = -1 status = -1
if status == 0 and result[:3] == '2.4': if status == 0 and result[:3] == '2.4':
return cls.APACHEVER24 return cls.APACHEVER24
return cls.APACHEVER22
return cls.APACHEVER22
@classmethod @classmethod
def generateapache2conf(cls, node, filename, services): def generateapache2conf(cls, node, filename, services):
lockstr = { cls.APACHEVER22: lockstr = {cls.APACHEVER22:
'LockFile ${APACHE_LOCK_DIR}/accept.lock\n', 'LockFile ${APACHE_LOCK_DIR}/accept.lock\n',
cls.APACHEVER24: cls.APACHEVER24:
'Mutex file:${APACHE_LOCK_DIR} default\n', } 'Mutex file:${APACHE_LOCK_DIR} default\n', }
mpmstr = { cls.APACHEVER22: '', cls.APACHEVER24: mpmstr = {cls.APACHEVER22: '', cls.APACHEVER24:
'LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n', } 'LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n', }
permstr = { cls.APACHEVER22: permstr = {cls.APACHEVER22:
' Order allow,deny\n Deny from all\n Satisfy all\n', ' Order allow,deny\n Deny from all\n Satisfy all\n',
cls.APACHEVER24: cls.APACHEVER24:
' Require all denied\n', } ' Require all denied\n', }
authstr = { cls.APACHEVER22: authstr = {cls.APACHEVER22:
'LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n', 'LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n',
cls.APACHEVER24: cls.APACHEVER24:
'LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n', } 'LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n', }
permstr2 = { cls.APACHEVER22: permstr2 = {cls.APACHEVER22:
'\t\tOrder allow,deny\n\t\tallow from all\n', '\t\tOrder allow,deny\n\t\tallow from all\n',
cls.APACHEVER24: cls.APACHEVER24:
'\t\tRequire all granted\n', } '\t\tRequire all granted\n', }
version = cls.detectversionfromcmd() version = cls.detectversionfromcmd()
cfg ="# apache2.conf generated by utility.py:HttpService\n" cfg = "# apache2.conf generated by utility.py:HttpService\n"
cfg += lockstr[version] cfg += lockstr[version]
cfg += """\ cfg += """\
PidFile ${APACHE_PID_FILE} PidFile ${APACHE_PID_FILE}
@ -593,13 +595,13 @@ export LANG
body += "<li>%s - %s</li>\n" % (ifc.name, ifc.addrlist) body += "<li>%s - %s</li>\n" % (ifc.name, ifc.addrlist)
return "<html><body>%s</body></html>" % body return "<html><body>%s</body></html>" % body
addservice(HttpService)
class PcapService(UtilService): class PcapService(UtilService):
''' Pcap service for logging packets. """
''' Pcap service for logging packets.
"""
_name = "pcap" _name = "pcap"
_configs = ("pcap.sh", ) _configs = ("pcap.sh",)
_dirs = () _dirs = ()
_startindex = 1 _startindex = 1
_startup = ("sh pcap.sh start",) _startup = ("sh pcap.sh start",)
@ -609,8 +611,9 @@ class PcapService(UtilService):
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate a startpcap.sh traffic logging script. """
''' Generate a startpcap.sh traffic logging script.
"""
cfg = """ cfg = """
#!/bin/sh #!/bin/sh
# set tcpdump options here (see 'man tcpdump' for help) # set tcpdump options here (see 'man tcpdump' for help)
@ -635,7 +638,6 @@ fi;
""" """
return cfg return cfg
addservice(PcapService)
class RadvdService(UtilService): class RadvdService(UtilService):
_name = "radvd" _name = "radvd"
@ -647,9 +649,10 @@ class RadvdService(UtilService):
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Generate a RADVD router advertisement daemon config file """
Generate a RADVD router advertisement daemon config file
using the network address of each interface. using the network address of each interface.
''' """
cfg = "# auto-generated by RADVD service (utility.py)\n" cfg = "# auto-generated by RADVD service (utility.py)\n"
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control == True:
@ -682,25 +685,26 @@ interface %s
@staticmethod @staticmethod
def subnetentry(x): def subnetentry(x):
''' Generate a subnet declaration block given an IPv6 prefix string """
Generate a subnet declaration block given an IPv6 prefix string
for inclusion in the RADVD config file. for inclusion in the RADVD config file.
''' """
if x.find(":") >= 0: if x.find(":") >= 0:
net = IPv6Prefix(x) net = Ipv6Prefix(x)
return str(net) return str(net)
else: else:
return "" return ""
addservice(RadvdService)
class AtdService(UtilService): class AtdService(UtilService):
''' Atd service for scheduling at jobs """
''' Atd service for scheduling at jobs
"""
_name = "atd" _name = "atd"
_configs = ("startatd.sh",) _configs = ("startatd.sh",)
_dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool") _dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool")
_startup = ("sh startatd.sh", ) _startup = ("sh startatd.sh",)
_shutdown = ("pkill atd", ) _shutdown = ("pkill atd",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
@ -712,13 +716,27 @@ chmod -R 700 /var/spool/cron/*
atd atd
""" """
addservice(AtdService)
class UserDefinedService(UtilService): class UserDefinedService(UtilService):
''' Dummy service allowing customization of anything. """
''' Dummy service allowing customization of anything.
"""
_name = "UserDefined" _name = "UserDefined"
_startindex = 50 _startindex = 50
_meta = "Customize this service to do anything upon startup." _meta = "Customize this service to do anything upon startup."
addservice(UserDefinedService)
def load_services():
ServiceManager.add(IPForwardService)
ServiceManager.add(DefaultRouteService)
ServiceManager.add(DefaultMulticastRouteService)
ServiceManager.add(StaticRouteService)
ServiceManager.add(SshService)
ServiceManager.add(DhcpService)
ServiceManager.add(DhcpClientService)
ServiceManager.add(FtpService)
ServiceManager.add(HttpService)
ServiceManager.add(PcapService)
ServiceManager.add(RadvdService)
ServiceManager.add(AtdService)
ServiceManager.add(UserDefinedService)

View file

@ -1,24 +1,19 @@
# """
# CORE
# Copyright (c)2011-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
xorp.py: defines routing services provided by the XORP routing suite. xorp.py: defines routing services provided by the XORP routing suite.
''' """
import os from core.misc import log
from core.service import CoreService
from core.service import ServiceManager
logger = log.get_logger(__name__)
from core.service import CoreService, addservice
from core.misc.ipaddr import IPv4Prefix
from core.constants import *
class XorpRtrmgr(CoreService): class XorpRtrmgr(CoreService):
''' XORP router manager service builds a config.boot file based on other """
XORP router manager service builds a config.boot file based on other
enabled XORP services, and launches necessary daemons upon startup. enabled XORP services, and launches necessary daemons upon startup.
''' """
_name = "xorp_rtrmgr" _name = "xorp_rtrmgr"
_group = "XORP" _group = "XORP"
_depends = () _depends = ()
@ -26,15 +21,16 @@ class XorpRtrmgr(CoreService):
_configs = ("/etc/xorp/config.boot",) _configs = ("/etc/xorp/config.boot",)
_startindex = 35 _startindex = 35
_startup = ("xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (_configs[0], _name, _name),) _startup = ("xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (_configs[0], _name, _name),)
_shutdown = ("killall xorp_rtrmgr", ) _shutdown = ("killall xorp_rtrmgr",)
_validate = ("pidof xorp_rtrmgr", ) _validate = ("pidof xorp_rtrmgr",)
@classmethod @classmethod
def generateconfig(cls, node, filename, services): def generateconfig(cls, node, filename, services):
''' Returns config.boot configuration file text. Other services that """
Returns config.boot configuration file text. Other services that
depend on this will have generatexorpconfig() hooks that are depend on this will have generatexorpconfig() hooks that are
invoked here. Filename currently ignored. invoked here. Filename currently ignored.
''' """
cfg = "interfaces {\n" cfg = "interfaces {\n"
for ifc in node.netifs(): for ifc in node.netifs():
cfg += " interface %s {\n" % ifc.name cfg += " interface %s {\n" % ifc.name
@ -50,40 +46,40 @@ class XorpRtrmgr(CoreService):
s._depends.index(cls._name) s._depends.index(cls._name)
cfg += s.generatexorpconfig(node) cfg += s.generatexorpconfig(node)
except ValueError: except ValueError:
pass logger.exception("error getting value from service: %s", cls._name)
return cfg return cfg
@staticmethod @staticmethod
def addrstr(x): def addrstr(x):
''' helper for mapping IP addresses to XORP config statements """
''' helper for mapping IP addresses to XORP config statements
try: """
(addr, plen) = x.split("/") addr, plen = x.split("/")
except Exception:
raise ValueError, "invalid address"
cfg = "\t address %s {\n" % addr cfg = "\t address %s {\n" % addr
cfg += "\t\tprefix-length: %s\n" % plen cfg += "\t\tprefix-length: %s\n" % plen
cfg +="\t }\n" cfg += "\t }\n"
return cfg return cfg
@staticmethod @staticmethod
def lladdrstr(ifc): def lladdrstr(ifc):
''' helper for adding link-local address entries (required by OSPFv3) """
''' helper for adding link-local address entries (required by OSPFv3)
"""
cfg = "\t address %s {\n" % ifc.hwaddr.tolinklocal() cfg = "\t address %s {\n" % ifc.hwaddr.tolinklocal()
cfg += "\t\tprefix-length: 64\n" cfg += "\t\tprefix-length: 64\n"
cfg += "\t }\n" cfg += "\t }\n"
return cfg return cfg
addservice(XorpRtrmgr)
class XorpService(CoreService): class XorpService(CoreService):
''' Parent class for XORP services. Defines properties and methods """
Parent class for XORP services. Defines properties and methods
common to XORP's routing daemons. common to XORP's routing daemons.
''' """
_name = "XorpDaemon" _name = "XorpDaemon"
_group = "XORP" _group = "XORP"
_depends = ("xorp_rtrmgr", ) _depends = ("xorp_rtrmgr",)
_dirs = () _dirs = ()
_configs = () _configs = ()
_startindex = 40 _startindex = 40
@ -93,8 +89,9 @@ class XorpService(CoreService):
@staticmethod @staticmethod
def fea(forwarding): def fea(forwarding):
''' Helper to add a forwarding engine entry to the config file. """
''' Helper to add a forwarding engine entry to the config file.
"""
cfg = "fea {\n" cfg = "fea {\n"
cfg += " %s {\n" % forwarding cfg += " %s {\n" % forwarding
cfg += "\tdisable:false\n" cfg += "\tdisable:false\n"
@ -104,11 +101,12 @@ class XorpService(CoreService):
@staticmethod @staticmethod
def mfea(forwarding, ifcs): def mfea(forwarding, ifcs):
''' Helper to add a multicast forwarding engine entry to the config file. """
''' Helper to add a multicast forwarding engine entry to the config file.
"""
names = [] names = []
for ifc in ifcs: for ifc in ifcs:
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
names.append(ifc.name) names.append(ifc.name)
names.append("register_vif") names.append("register_vif")
@ -125,11 +123,11 @@ class XorpService(CoreService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
@staticmethod @staticmethod
def policyexportconnected(): def policyexportconnected():
''' Helper to add a policy statement for exporting connected routes. """
''' Helper to add a policy statement for exporting connected routes.
"""
cfg = "policy {\n" cfg = "policy {\n"
cfg += " policy-statement export-connected {\n" cfg += " policy-statement export-connected {\n"
cfg += "\tterm 100 {\n" cfg += "\tterm 100 {\n"
@ -143,15 +141,16 @@ class XorpService(CoreService):
@staticmethod @staticmethod
def routerid(node): def routerid(node):
''' Helper to return the first IPv4 address of a node as its router ID. """
''' Helper to return the first IPv4 address of a node as its router ID.
"""
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
for a in ifc.addrlist: for a in ifc.addrlist:
if a.find(".") >= 0: if a.find(".") >= 0:
return a.split('/')[0] return a.split('/')[0]
#raise ValueError, "no IPv4 address found for router ID" # raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0" return "0.0.0.0"
@classmethod @classmethod
@ -162,11 +161,13 @@ class XorpService(CoreService):
def generatexorpconfig(cls, node): def generatexorpconfig(cls, node):
return "" return ""
class XorpOspfv2(XorpService): class XorpOspfv2(XorpService):
''' The OSPFv2 service provides IPv4 routing for wired networks. It does """
The OSPFv2 service provides IPv4 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the not build its own configuration file but has hooks for adding to the
unified XORP configuration file. unified XORP configuration file.
''' """
_name = "XORP_OSPFv2" _name = "XORP_OSPFv2"
@classmethod @classmethod
@ -178,7 +179,7 @@ class XorpOspfv2(XorpService):
cfg += "\trouter-id: %s\n" % rtrid cfg += "\trouter-id: %s\n" % rtrid
cfg += "\tarea 0.0.0.0 {\n" cfg += "\tarea 0.0.0.0 {\n"
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += "\t interface %s {\n" % ifc.name cfg += "\t interface %s {\n" % ifc.name
cfg += "\t\tvif %s {\n" % ifc.name cfg += "\t\tvif %s {\n" % ifc.name
@ -195,13 +196,13 @@ class XorpOspfv2(XorpService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
addservice(XorpOspfv2)
class XorpOspfv3(XorpService): class XorpOspfv3(XorpService):
''' The OSPFv3 service provides IPv6 routing. It does """
The OSPFv3 service provides IPv6 routing. It does
not build its own configuration file but has hooks for adding to the not build its own configuration file but has hooks for adding to the
unified XORP configuration file. unified XORP configuration file.
''' """
_name = "XORP_OSPFv3" _name = "XORP_OSPFv3"
@classmethod @classmethod
@ -213,7 +214,7 @@ class XorpOspfv3(XorpService):
cfg += "\trouter-id: %s\n" % rtrid cfg += "\trouter-id: %s\n" % rtrid
cfg += "\tarea 0.0.0.0 {\n" cfg += "\tarea 0.0.0.0 {\n"
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += "\t interface %s {\n" % ifc.name cfg += "\t interface %s {\n" % ifc.name
cfg += "\t\tvif %s {\n" % ifc.name cfg += "\t\tvif %s {\n" % ifc.name
@ -224,11 +225,11 @@ class XorpOspfv3(XorpService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
addservice(XorpOspfv3)
class XorpBgp(XorpService): class XorpBgp(XorpService):
''' IPv4 inter-domain routing. AS numbers and peers must be customized. """
''' IPv4 inter-domain routing. AS numbers and peers must be customized.
"""
_name = "XORP_BGP" _name = "XORP_BGP"
_custom_needed = True _custom_needed = True
@ -253,11 +254,12 @@ class XorpBgp(XorpService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
addservice(XorpBgp)
class XorpRip(XorpService): class XorpRip(XorpService):
''' RIP IPv4 unicast routing. """
''' RIP IPv4 unicast routing.
"""
_name = "XORP_RIP" _name = "XORP_RIP"
@classmethod @classmethod
@ -268,7 +270,7 @@ class XorpRip(XorpService):
cfg += " rip {\n" cfg += " rip {\n"
cfg += "\texport: \"export-connected\"\n" cfg += "\texport: \"export-connected\"\n"
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += "\tinterface %s {\n" % ifc.name cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name cfg += "\t vif %s {\n" % ifc.name
@ -285,11 +287,11 @@ class XorpRip(XorpService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
addservice(XorpRip)
class XorpRipng(XorpService): class XorpRipng(XorpService):
''' RIP NG IPv6 unicast routing. """
''' RIP NG IPv6 unicast routing.
"""
_name = "XORP_RIPNG" _name = "XORP_RIPNG"
@classmethod @classmethod
@ -300,17 +302,17 @@ class XorpRipng(XorpService):
cfg += " ripng {\n" cfg += " ripng {\n"
cfg += "\texport: \"export-connected\"\n" cfg += "\texport: \"export-connected\"\n"
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += "\tinterface %s {\n" % ifc.name cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name cfg += "\t vif %s {\n" % ifc.name
# for a in ifc.addrlist: # for a in ifc.addrlist:
# if a.find(":") < 0: # if a.find(":") < 0:
# continue # continue
# addr = a.split("/")[0] # addr = a.split("/")[0]
# cfg += "\t\taddress %s {\n" % addr # cfg += "\t\taddress %s {\n" % addr
# cfg += "\t\t disable: false\n" # cfg += "\t\t disable: false\n"
# cfg += "\t\t}\n" # cfg += "\t\t}\n"
cfg += "\t\taddress %s {\n" % ifc.hwaddr.tolinklocal() cfg += "\t\taddress %s {\n" % ifc.hwaddr.tolinklocal()
cfg += "\t\t disable: false\n" cfg += "\t\t disable: false\n"
cfg += "\t\t}\n" cfg += "\t\t}\n"
@ -320,11 +322,11 @@ class XorpRipng(XorpService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
addservice(XorpRipng)
class XorpPimSm4(XorpService): class XorpPimSm4(XorpService):
''' PIM Sparse Mode IPv4 multicast routing. """
''' PIM Sparse Mode IPv4 multicast routing.
"""
_name = "XORP_PIMSM4" _name = "XORP_PIMSM4"
@classmethod @classmethod
@ -335,7 +337,7 @@ class XorpPimSm4(XorpService):
cfg += " igmp {\n" cfg += " igmp {\n"
names = [] names = []
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
names.append(ifc.name) names.append(ifc.name)
cfg += "\tinterface %s {\n" % ifc.name cfg += "\tinterface %s {\n" % ifc.name
@ -379,11 +381,11 @@ class XorpPimSm4(XorpService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
addservice(XorpPimSm4)
class XorpPimSm6(XorpService): class XorpPimSm6(XorpService):
''' PIM Sparse Mode IPv6 multicast routing. """
''' PIM Sparse Mode IPv6 multicast routing.
"""
_name = "XORP_PIMSM6" _name = "XORP_PIMSM6"
@classmethod @classmethod
@ -394,7 +396,7 @@ class XorpPimSm6(XorpService):
cfg += " mld {\n" cfg += " mld {\n"
names = [] names = []
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
names.append(ifc.name) names.append(ifc.name)
cfg += "\tinterface %s {\n" % ifc.name cfg += "\tinterface %s {\n" % ifc.name
@ -438,11 +440,11 @@ class XorpPimSm6(XorpService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
addservice(XorpPimSm6)
class XorpOlsr(XorpService): class XorpOlsr(XorpService):
''' OLSR IPv4 unicast MANET routing. """
''' OLSR IPv4 unicast MANET routing.
"""
_name = "XORP_OLSR" _name = "XORP_OLSR"
@classmethod @classmethod
@ -453,7 +455,7 @@ class XorpOlsr(XorpService):
cfg += " olsr4 {\n" cfg += " olsr4 {\n"
cfg += "\tmain-address: %s\n" % rtrid cfg += "\tmain-address: %s\n" % rtrid
for ifc in node.netifs(): for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control is True:
continue continue
cfg += "\tinterface %s {\n" % ifc.name cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name cfg += "\t vif %s {\n" % ifc.name
@ -469,4 +471,14 @@ class XorpOlsr(XorpService):
cfg += "}\n" cfg += "}\n"
return cfg return cfg
addservice(XorpOlsr)
def load_services():
ServiceManager.add(XorpRtrmgr)
ServiceManager.add(XorpOspfv2)
ServiceManager.add(XorpOspfv3)
ServiceManager.add(XorpBgp)
ServiceManager.add(XorpRip)
ServiceManager.add(XorpRipng)
ServiceManager.add(XorpPimSm4)
ServiceManager.add(XorpPimSm6)
ServiceManager.add(XorpOlsr)

File diff suppressed because it is too large Load diff

View file

@ -1,51 +1,43 @@
# """
# CORE
# Copyright (c)2011-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
'''
xen.py: implementation of the XenNode and XenVEth classes that support xen.py: implementation of the XenNode and XenVEth classes that support
generating Xen domUs based on an ISO image and persistent configuration area generating Xen domUs based on an ISO image and persistent configuration area
''' """
from core.netns.vnet import * import base64
import os
import shutil
import string
import subprocess
import sys
import threading
import crypt
from core import constants
from core.coreobj import PyCoreNetIf
from core.coreobj import PyCoreNode
from core.enumerations import NodeTypes
from core.misc import log
from core.misc import nodeutils
from core.misc import utils
from core.netns.vnode import LxcNode from core.netns.vnode import LxcNode
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf
from core.misc.ipaddr import * logger = log.get_logger(__name__)
from core.misc.utils import *
from core.constants import *
from core.api import coreapi
from core.netns.vif import TunTap
from core.emane.nodes import EmaneNode
try: try:
import parted import parted
except ImportError, e: except ImportError:
#print "Failed to load parted Python module required by Xen support." logger.error("failed to import parted for xen nodes")
#print "Error was:", e
raise ImportError
import base64
import crypt
import subprocess
try: try:
import fsimage import fsimage
except ImportError, e: except ImportError:
# fix for fsimage under Ubuntu # fix for fsimage under Ubuntu
sys.path.append("/usr/lib/xen-default/lib/python") sys.path.append("/usr/lib/xen-default/lib/python")
try: try:
import fsimage import fsimage
except ImportError, e: except ImportError:
#print "Failed to load fsimage Python module required by Xen support." logger.error("failed to import fsimage for xen nodes")
#print "Error was:", e
raise ImportError
import os
import time
import shutil
import string
# XXX move these out to config file # XXX move these out to config file
AWK_PATH = "/bin/awk" AWK_PATH = "/bin/awk"
@ -60,11 +52,12 @@ SED_PATH = "/bin/sed"
XM_PATH = "/usr/sbin/xm" XM_PATH = "/usr/sbin/xm"
UDEVADM_PATH = "/sbin/udevadm" UDEVADM_PATH = "/sbin/udevadm"
class XenVEth(PyCoreNetIf): class XenVEth(PyCoreNetIf):
def __init__(self, node, name, localname, mtu = 1500, net = None, def __init__(self, node, name, localname, mtu=1500, net=None,
start = True, hwaddr = None): start=True, hwaddr=None):
# note that net arg is ignored # note that net arg is ignored
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu) PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.localname = localname self.localname = localname
self.up = False self.up = False
self.hwaddr = hwaddr self.hwaddr = hwaddr
@ -76,8 +69,8 @@ class XenVEth(PyCoreNetIf):
'vifname=%s' % self.localname, 'script=vif-core'] 'vifname=%s' % self.localname, 'script=vif-core']
if self.hwaddr is not None: if self.hwaddr is not None:
cmd.append('mac=%s' % self.hwaddr) cmd.append('mac=%s' % self.hwaddr)
check_call(cmd) subprocess.check_call(cmd)
check_call([IP_BIN, "link", "set", self.localname, "up"]) subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
self.up = True self.up = True
def shutdown(self): def shutdown(self):
@ -87,28 +80,28 @@ class XenVEth(PyCoreNetIf):
if self.hwaddr is not None: if self.hwaddr is not None:
pass pass
# this should be doable, but some argument isn't a string # this should be doable, but some argument isn't a string
#check_call([XM_PATH, 'network-detach', self.node.vmname, # check_call([XM_PATH, 'network-detach', self.node.vmname,
# self.hwaddr]) # self.hwaddr])
self.up = False self.up = False
class XenNode(PyCoreNode): class XenNode(PyCoreNode):
apitype = coreapi.CORE_NODE_XEN apitype = NodeTypes.XEN.value
FilesToIgnore = frozenset([ FilesToIgnore = frozenset([
#'ipforward.sh', # 'ipforward.sh',
'quaggaboot.sh', 'quaggaboot.sh',
]) ])
FilesRedirection = { FilesRedirection = {
'ipforward.sh' : '/core-tmp/ipforward.sh', 'ipforward.sh': '/core-tmp/ipforward.sh',
} }
CmdsToIgnore = frozenset([ CmdsToIgnore = frozenset([
#'sh ipforward.sh', # 'sh ipforward.sh',
#'sh quaggaboot.sh zebra', # 'sh quaggaboot.sh zebra',
#'sh quaggaboot.sh ospfd', # 'sh quaggaboot.sh ospfd',
#'sh quaggaboot.sh ospf6d', # 'sh quaggaboot.sh ospf6d',
'killall zebra', 'killall zebra',
'killall ospfd', 'killall ospfd',
'killall ospf6d', 'killall ospf6d',
@ -116,43 +109,39 @@ class XenNode(PyCoreNode):
]) ])
def RedirCmd_ipforward(self): def RedirCmd_ipforward(self):
sysctlFile = open(os.path.join(self.mountdir, self.etcdir, sysctlFile = open(os.path.join(self.mountdir, self.etcdir, 'sysctl.conf'), 'a')
'sysctl.conf'), 'a') p1 = subprocess.Popen([AWK_PATH, '/^\/sbin\/sysctl -w/ {print $NF}',
p1 = subprocess.Popen([AWK_PATH, os.path.join(self.nodedir, 'core-tmp/ipforward.sh')], stdout=sysctlFile)
'/^\/sbin\/sysctl -w/ {print $NF}',
os.path.join(self.nodedir,
'core-tmp/ipforward.sh') ],
stdout=sysctlFile)
p1.wait() p1.wait()
sysctlFile.close() sysctlFile.close()
def RedirCmd_zebra(self): def RedirCmd_zebra(self):
check_call([SED_PATH, '-i', '-e', 's/^zebra=no/zebra=yes/', subprocess.check_call([SED_PATH, '-i', '-e', 's/^zebra=no/zebra=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')]) os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
def RedirCmd_ospfd(self): def RedirCmd_ospfd(self):
check_call([SED_PATH, '-i', '-e', 's/^ospfd=no/ospfd=yes/', subprocess.check_call([SED_PATH, '-i', '-e', 's/^ospfd=no/ospfd=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')]) os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
def RedirCmd_ospf6d(self): def RedirCmd_ospf6d(self):
check_call([SED_PATH, '-i', '-e', subprocess.check_call([SED_PATH, '-i', '-e',
's/^ospf6d=no/ospf6d=yes/', 's/^ospf6d=no/ospf6d=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')]) os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
CmdsRedirection = { CmdsRedirection = {
'sh ipforward.sh' : RedirCmd_ipforward, 'sh ipforward.sh': RedirCmd_ipforward,
'sh quaggaboot.sh zebra' : RedirCmd_zebra, 'sh quaggaboot.sh zebra': RedirCmd_zebra,
'sh quaggaboot.sh ospfd' : RedirCmd_ospfd, 'sh quaggaboot.sh ospfd': RedirCmd_ospfd,
'sh quaggaboot.sh ospf6d' : RedirCmd_ospf6d, 'sh quaggaboot.sh ospf6d': RedirCmd_ospf6d,
} }
# CoreNode: no __init__, take from LxcNode & SimpleLxcNode # CoreNode: no __init__, take from LxcNode & SimpleLxcNode
def __init__(self, session, objid = None, name = None, def __init__(self, session, objid=None, name=None,
nodedir = None, bootsh = "boot.sh", verbose = False, nodedir=None, bootsh="boot.sh", start=True, model=None,
start = True, model = None, vgname=None, ramsize=None, disksize=None,
vgname = None, ramsize = None, disksize = None, isofile=None):
isofile = None):
# SimpleLxcNode initialization # SimpleLxcNode initialization
PyCoreNode.__init__(self, session = session, objid = objid, name = name, PyCoreNode.__init__(self, session=session, objid=objid, name=name)
verbose = verbose)
self.nodedir = nodedir self.nodedir = nodedir
self.model = model self.model = model
# indicates startup() has been invoked and disk has been initialized # indicates startup() has been invoked and disk has been initialized
@ -185,31 +174,30 @@ class XenNode(PyCoreNode):
# LxcNode initialization # LxcNode initialization
# self.makenodedir() # self.makenodedir()
if self.nodedir is None: if self.nodedir is None:
self.nodedir = \ self.nodedir = os.path.join(session.sessiondir, self.name + ".conf")
os.path.join(session.sessiondir, self.name + ".conf")
self.mountdir = self.nodedir + self.getconfigitem('mount_path') self.mountdir = self.nodedir + self.getconfigitem('mount_path')
if not os.path.isdir(self.mountdir): if not os.path.isdir(self.mountdir):
os.makedirs(self.mountdir) os.makedirs(self.mountdir)
self.tmpnodedir = True self.tmpnodedir = True
else: else:
raise Exception, "Xen PVM node requires a temporary nodedir" raise Exception("Xen PVM node requires a temporary nodedir")
self.tmpnodedir = False self.tmpnodedir = False
self.bootsh = bootsh self.bootsh = bootsh
if start: if start:
self.startup() self.startup()
def getconfigitem(self, name, default=None): def getconfigitem(self, name, default=None):
''' Configuration items come from the xen.conf file and/or input from """
Configuration items come from the xen.conf file and/or input from
the GUI, and are stored in the session using the XenConfigManager the GUI, and are stored in the session using the XenConfigManager
object. self.model is used to identify particular profiles object. self.model is used to identify particular profiles
associated with a node type in the GUI. associated with a node type in the GUI.
''' """
return self.session.xen.getconfigitem(name=name, model=self.model, return self.session.xen.getconfigitem(name=name, model=self.model, node=self, value=default)
node=self, value=default)
# from class LxcNode (also SimpleLxcNode) # from class LxcNode (also SimpleLxcNode)
def startup(self): def startup(self):
self.warn("XEN PVM startup() called: preparing disk for %s" % self.name) logger.warn("XEN PVM startup() called: preparing disk for %s" % self.name)
self.lock.acquire() self.lock.acquire()
try: try:
if self.up: if self.up:
@ -217,10 +205,10 @@ class XenNode(PyCoreNode):
self.createlogicalvolume() self.createlogicalvolume()
self.createpartitions() self.createpartitions()
persistdev = self.createfilesystems() persistdev = self.createfilesystems()
check_call([MOUNT_BIN, '-t', 'ext4', persistdev, self.mountdir]) subprocess.check_call([constants.MOUNT_BIN, '-t', 'ext4', persistdev, self.mountdir])
self.untarpersistent(tarname=self.getconfigitem('persist_tar_iso'), self.untarpersistent(tarname=self.getconfigitem('persist_tar_iso'),
iso=True) iso=True)
self.setrootpassword(pw = self.getconfigitem('root_password')) self.setrootpassword(pw=self.getconfigitem('root_password'))
self.sethostname(old='UBASE', new=self.name) self.sethostname(old='UBASE', new=self.name)
self.setupssh(keypath=self.getconfigitem('ssh_key_path')) self.setupssh(keypath=self.getconfigitem('ssh_key_path'))
self.createvm() self.createvm()
@ -230,11 +218,11 @@ class XenNode(PyCoreNode):
# from class LxcNode (also SimpleLxcNode) # from class LxcNode (also SimpleLxcNode)
def boot(self): def boot(self):
self.warn("XEN PVM boot() called") logger.warn("XEN PVM boot() called")
self.lock.acquire() self.lock.acquire()
if not self.up: if not self.up:
raise Exception, "Can't boot VM without initialized disk" raise Exception("Can't boot VM without initialized disk")
if self.booted: if self.booted:
self.lock.release() self.lock.release()
@ -246,18 +234,17 @@ class XenNode(PyCoreNode):
self.untarpersistent(tarname=tarname, iso=False) self.untarpersistent(tarname=tarname, iso=False)
try: try:
check_call([UMOUNT_BIN, self.mountdir]) subprocess.check_call([constants.UMOUNT_BIN, self.mountdir])
self.unmount_all(self.mountdir) self.unmount_all(self.mountdir)
check_call([UDEVADM_PATH, 'settle']) subprocess.check_call([UDEVADM_PATH, 'settle'])
check_call([KPARTX_PATH, '-d', self.lvpath]) subprocess.check_call([KPARTX_PATH, '-d', self.lvpath])
#time.sleep(5) # time.sleep(5)
#time.sleep(1) # time.sleep(1)
# unpause VM # unpause VM
if self.verbose: logger.warn("XEN PVM boot() unpause domU %s" % self.vmname)
self.warn("XEN PVM boot() unpause domU %s" % self.vmname) utils.mutecheck_call([XM_PATH, 'unpause', self.vmname])
mutecheck_call([XM_PATH, 'unpause', self.vmname])
self.booted = True self.booted = True
finally: finally:
@ -268,7 +255,7 @@ class XenNode(PyCoreNode):
# from class LxcNode (also SimpleLxcNode) # from class LxcNode (also SimpleLxcNode)
def shutdown(self): def shutdown(self):
self.warn("XEN PVM shutdown() called") logger.warn("XEN PVM shutdown() called")
if not self.up: if not self.up:
return return
self.lock.acquire() self.lock.acquire()
@ -281,27 +268,25 @@ class XenNode(PyCoreNode):
try: try:
# RJE XXX what to do here # RJE XXX what to do here
if self.booted: if self.booted:
mutecheck_call([XM_PATH, 'destroy', self.vmname]) utils.mutecheck_call([XM_PATH, 'destroy', self.vmname])
self.booted = False self.booted = False
except OSError: except (OSError, subprocess.CalledProcessError):
pass
except subprocess.CalledProcessError:
# ignore this error too, the VM may have exited already # ignore this error too, the VM may have exited already
pass logger.exception("error during shutdown")
# discard LVM volume # discard LVM volume
lvmRemoveCount = 0 lvmRemoveCount = 0
while os.path.exists(self.lvpath): while os.path.exists(self.lvpath):
try: try:
check_call([UDEVADM_PATH, 'settle']) subprocess.check_call([UDEVADM_PATH, 'settle'])
mutecall([LVCHANGE_PATH, '-an', self.lvpath]) utils.mutecall([LVCHANGE_PATH, '-an', self.lvpath])
lvmRemoveCount += 1 lvmRemoveCount += 1
mutecall([LVREMOVE_PATH, '-f', self.lvpath]) utils.mutecall([LVREMOVE_PATH, '-f', self.lvpath])
except OSError: except OSError:
pass logger.exception("error during shutdown")
if (lvmRemoveCount > 1):
self.warn("XEN PVM shutdown() required %d lvremove " \ if lvmRemoveCount > 1:
"executions." % lvmRemoveCount) logger.warn("XEN PVM shutdown() required %d lvremove executions." % lvmRemoveCount)
self._netif.clear() self._netif.clear()
del self.session del self.session
@ -313,78 +298,82 @@ class XenNode(PyCoreNode):
self.lock.release() self.lock.release()
def createlogicalvolume(self): def createlogicalvolume(self):
''' Create a logical volume for this Xen domU. Called from startup(). """
''' Create a logical volume for this Xen domU. Called from startup().
"""
if os.path.exists(self.lvpath): if os.path.exists(self.lvpath):
raise Exception, "LVM volume already exists" raise Exception, "LVM volume already exists"
mutecheck_call([LVCREATE_PATH, '--size', self.disksize, utils.mutecheck_call([LVCREATE_PATH, '--size', self.disksize,
'--name', self.lvname, self.vgname]) '--name', self.lvname, self.vgname])
def createpartitions(self): def createpartitions(self):
''' Partition the LVM volume into persistent and swap partitions """
Partition the LVM volume into persistent and swap partitions
using the parted module. using the parted module.
''' """
dev = parted.Device(path=self.lvpath) dev = parted.Device(path=self.lvpath)
dev.removeFromCache() dev.removeFromCache()
disk = parted.freshDisk(dev, 'msdos') disk = parted.freshDisk(dev, 'msdos')
constraint = parted.Constraint(device=dev) constraint = parted.Constraint(device=dev)
persist_size = int(0.75 * constraint.maxSize); persist_size = int(0.75 * constraint.maxSize)
self.createpartition(device=dev, disk=disk, start=1, self.createpartition(device=dev, disk=disk, start=1,
end=(persist_size - 1) , type="ext4") end=persist_size - 1, type="ext4")
self.createpartition(device=dev, disk=disk, start=persist_size, self.createpartition(device=dev, disk=disk, start=persist_size,
end=(constraint.maxSize - 1) , type="linux-swap(v1)") end=constraint.maxSize - 1, type="linux-swap(v1)")
disk.commit() disk.commit()
def createpartition(self, device, disk, start, end, type): def createpartition(self, device, disk, start, end, type):
''' Create a single partition of the specified type and size and add """
Create a single partition of the specified type and size and add
it to the disk object, using the parted module. it to the disk object, using the parted module.
''' """
geo = parted.Geometry(device=device, start=start, end=end) geo = parted.Geometry(device=device, start=start, end=end)
fs = parted.FileSystem(type=type, geometry=geo) fs = parted.FileSystem(type=type, geometry=geo)
part = parted.Partition(disk=disk, fs=fs, type=parted.PARTITION_NORMAL, part = parted.Partition(disk=disk, fs=fs, type=parted.PARTITION_NORMAL, geometry=geo)
geometry=geo)
constraint = parted.Constraint(exactGeom=geo) constraint = parted.Constraint(exactGeom=geo)
disk.addPartition(partition=part, constraint=constraint) disk.addPartition(partition=part, constraint=constraint)
def createfilesystems(self): def createfilesystems(self):
''' Make an ext4 filesystem and swap space. Return the device name for """
Make an ext4 filesystem and swap space. Return the device name for
the persistent partition so we can mount it. the persistent partition so we can mount it.
''' """
output = subprocess.Popen([KPARTX_PATH, '-l', self.lvpath], output = subprocess.Popen([KPARTX_PATH, '-l', self.lvpath],
stdout=subprocess.PIPE).communicate()[0] stdout=subprocess.PIPE).communicate()[0]
lines = output.splitlines() lines = output.splitlines()
persistdev = '/dev/mapper/' + lines[0].strip().split(' ')[0].strip() persistdev = '/dev/mapper/' + lines[0].strip().split(' ')[0].strip()
swapdev = '/dev/mapper/' + lines[1].strip().split(' ')[0].strip() swapdev = '/dev/mapper/' + lines[1].strip().split(' ')[0].strip()
check_call([KPARTX_PATH, '-a', self.lvpath]) subprocess.check_call([KPARTX_PATH, '-a', self.lvpath])
mutecheck_call([MKFSEXT4_PATH, '-L', 'persist', persistdev]) utils.mutecheck_call([MKFSEXT4_PATH, '-L', 'persist', persistdev])
mutecheck_call([MKSWAP_PATH, '-f', '-L', 'swap', swapdev]) utils.mutecheck_call([MKSWAP_PATH, '-f', '-L', 'swap', swapdev])
return persistdev return persistdev
def untarpersistent(self, tarname, iso): def untarpersistent(self, tarname, iso):
''' Unpack a persistent template tar file to the mounted mount dir. """
Unpack a persistent template tar file to the mounted mount dir.
Uses fsimage library to read from an ISO file. Uses fsimage library to read from an ISO file.
''' """
tarname = tarname.replace('%h', self.name) # filename may use hostname tarname = tarname.replace('%h', self.name) # filename may use hostname
if iso: if iso:
try: try:
fs = fsimage.open(self.isofile, 0) fs = fsimage.open(self.isofile, 0)
except IOError, e: except IOError:
self.warn("Failed to open ISO file: %s (%s)" % (self.isofile,e)) logger.exception("Failed to open ISO file: %s", self.isofile)
return return
try: try:
tardata = fs.open_file(tarname).read(); tardata = fs.open_file(tarname).read()
except IOError, e: except IOError:
self.warn("Failed to open tar file: %s (%s)" % (tarname, e)) logger.exception("Failed to open tar file: %s", tarname)
return return
finally: finally:
del fs; del fs
else: else:
try: try:
f = open(tarname) f = open(tarname)
tardata = f.read() tardata = f.read()
f.close() f.close()
except IOError, e: except IOError:
self.warn("Failed to open tar file: %s (%s)" % (tarname, e)) logger.exception("Failed to open tar file: %s", tarname)
return return
p = subprocess.Popen([TAR_PATH, '-C', self.mountdir, '--numeric-owner', p = subprocess.Popen([TAR_PATH, '-C', self.mountdir, '--numeric-owner',
'-xf', '-'], stdin=subprocess.PIPE) '-xf', '-'], stdin=subprocess.PIPE)
@ -392,38 +381,41 @@ class XenNode(PyCoreNode):
p.wait() p.wait()
def setrootpassword(self, pw): def setrootpassword(self, pw):
''' Set the root password by updating the shadow password file that """
Set the root password by updating the shadow password file that
is on the filesystem mounted in the temporary area. is on the filesystem mounted in the temporary area.
''' """
saltedpw = crypt.crypt(pw, '$6$'+base64.b64encode(os.urandom(12))) saltedpw = crypt.crypt(pw, '$6$' + base64.b64encode(os.urandom(12)))
check_call([SED_PATH, '-i', '-e', subprocess.check_call([SED_PATH, '-i', '-e',
'/^root:/s_^root:\([^:]*\):_root:' + saltedpw + ':_', '/^root:/s_^root:\([^:]*\):_root:' + saltedpw + ':_',
os.path.join(self.mountdir, self.etcdir, 'shadow')]) os.path.join(self.mountdir, self.etcdir, 'shadow')])
def sethostname(self, old, new): def sethostname(self, old, new):
''' Set the hostname by updating the hostname and hosts files that """
Set the hostname by updating the hostname and hosts files that
reside on the filesystem mounted in the temporary area. reside on the filesystem mounted in the temporary area.
''' """
check_call([SED_PATH, '-i', '-e', 's/%s/%s/' % (old, new), subprocess.check_call([SED_PATH, '-i', '-e', 's/%s/%s/' % (old, new),
os.path.join(self.mountdir, self.etcdir, 'hostname')]) os.path.join(self.mountdir, self.etcdir, 'hostname')])
check_call([SED_PATH, '-i', '-e', 's/%s/%s/' % (old, new), subprocess.check_call([SED_PATH, '-i', '-e', 's/%s/%s/' % (old, new),
os.path.join(self.mountdir, self.etcdir, 'hosts')]) os.path.join(self.mountdir, self.etcdir, 'hosts')])
def setupssh(self, keypath): def setupssh(self, keypath):
''' Configure SSH access by installing host keys and a system-wide """
Configure SSH access by installing host keys and a system-wide
authorized_keys file. authorized_keys file.
''' """
sshdcfg = os.path.join(self.mountdir, self.etcdir, 'ssh/sshd_config') sshdcfg = os.path.join(self.mountdir, self.etcdir, 'ssh/sshd_config')
check_call([SED_PATH, '-i', '-e', subprocess.check_call([SED_PATH, '-i', '-e',
's/PermitRootLogin no/PermitRootLogin yes/', sshdcfg]) 's/PermitRootLogin no/PermitRootLogin yes/', sshdcfg])
sshdir = os.path.join(self.getconfigitem('mount_path'), self.etcdir, sshdir = os.path.join(self.getconfigitem('mount_path'), self.etcdir,
'ssh') 'ssh')
sshdir = sshdir.replace('/','\\/') # backslash slashes for use in sed sshdir = sshdir.replace('/', '\\/') # backslash slashes for use in sed
check_call([SED_PATH, '-i', '-e', subprocess.check_call([SED_PATH, '-i', '-e',
's/#AuthorizedKeysFile %h\/.ssh\/authorized_keys/' + \ 's/#AuthorizedKeysFile %h\/.ssh\/authorized_keys/' + \
'AuthorizedKeysFile ' + sshdir + '\/authorized_keys/', 'AuthorizedKeysFile ' + sshdir + '\/authorized_keys/',
sshdcfg]) sshdcfg])
for f in ('ssh_host_rsa_key','ssh_host_rsa_key.pub','authorized_keys'): for f in 'ssh_host_rsa_key', 'ssh_host_rsa_key.pub', 'authorized_keys':
src = os.path.join(keypath, f) src = os.path.join(keypath, f)
dst = os.path.join(self.mountdir, self.etcdir, 'ssh', f) dst = os.path.join(self.mountdir, self.etcdir, 'ssh', f)
shutil.copy(src, dst) shutil.copy(src, dst)
@ -431,10 +423,11 @@ class XenNode(PyCoreNode):
os.chmod(dst, 0600) os.chmod(dst, 0600)
def createvm(self): def createvm(self):
''' Instantiate a *paused* domU VM """
Instantiate a *paused* domU VM
Instantiate it now, so we can add network interfaces, Instantiate it now, so we can add network interfaces,
pause it so we can have the filesystem open for configuration. pause it so we can have the filesystem open for configuration.
''' """
args = [XM_PATH, 'create', os.devnull, '--paused'] args = [XM_PATH, 'create', os.devnull, '--paused']
args.extend(['name=' + self.vmname, 'memory=' + str(self.ramsize)]) args.extend(['name=' + self.vmname, 'memory=' + str(self.ramsize)])
args.append('disk=tap:aio:' + self.isofile + ',hda,r') args.append('disk=tap:aio:' + self.isofile + ',hda,r')
@ -445,63 +438,61 @@ class XenNode(PyCoreNode):
for action in ('poweroff', 'reboot', 'suspend', 'crash', 'halt'): for action in ('poweroff', 'reboot', 'suspend', 'crash', 'halt'):
args.append('on_%s=destroy' % action) args.append('on_%s=destroy' % action)
args.append('extra=' + self.getconfigitem('xm_create_extra')) args.append('extra=' + self.getconfigitem('xm_create_extra'))
mutecheck_call(args) utils.mutecheck_call(args)
# from class LxcNode # from class LxcNode
def privatedir(self, path): def privatedir(self, path):
#self.warn("XEN PVM privatedir() called") # self.warn("XEN PVM privatedir() called")
# Do nothing, Xen PVM nodes are fully private # Do nothing, Xen PVM nodes are fully private
pass pass
# from class LxcNode # from class LxcNode
def opennodefile(self, filename, mode = "w"): def opennodefile(self, filename, mode="w"):
self.warn("XEN PVM opennodefile() called") logger.warn("XEN PVM opennodefile() called")
raise Exception, "Can't open VM file with opennodefile()" raise Exception("Can't open VM file with opennodefile()")
# from class LxcNode # from class LxcNode
# open a file on a paused Xen node # open a file on a paused Xen node
def openpausednodefile(self, filename, mode = "w"): def openpausednodefile(self, filename, mode="w"):
dirname, basename = os.path.split(filename) dirname, basename = os.path.split(filename)
if not basename: if not basename:
raise ValueError, "no basename for filename: " + filename raise ValueError, "no basename for filename: " + filename
if dirname and dirname[0] == "/": if dirname and dirname[0] == "/":
dirname = dirname[1:] dirname = dirname[1:]
#dirname = dirname.replace("/", ".") # dirname = dirname.replace("/", ".")
dirname = os.path.join(self.nodedir, dirname) dirname = os.path.join(self.nodedir, dirname)
if not os.path.isdir(dirname): if not os.path.isdir(dirname):
os.makedirs(dirname, mode = 0755) os.makedirs(dirname, mode=0755)
hostfilename = os.path.join(dirname, basename) hostfilename = os.path.join(dirname, basename)
return open(hostfilename, mode) return open(hostfilename, mode)
# from class LxcNode # from class LxcNode
def nodefile(self, filename, contents, mode = 0644): def nodefile(self, filename, contents, mode=0644):
if filename in self.FilesToIgnore: if filename in self.FilesToIgnore:
#self.warn("XEN PVM nodefile(filename=%s) ignored" % [filename]) # self.warn("XEN PVM nodefile(filename=%s) ignored" % [filename])
return return
if filename in self.FilesRedirection: if filename in self.FilesRedirection:
redirFilename = self.FilesRedirection[filename] redirFilename = self.FilesRedirection[filename]
self.warn("XEN PVM nodefile(filename=%s) redirected to %s" % (filename, redirFilename)) logger.warn("XEN PVM nodefile(filename=%s) redirected to %s" % (filename, redirFilename))
filename = redirFilename filename = redirFilename
self.warn("XEN PVM nodefile(filename=%s) called" % [filename]) logger.warn("XEN PVM nodefile(filename=%s) called" % [filename])
self.lock.acquire() self.lock.acquire()
if not self.up: if not self.up:
self.lock.release() self.lock.release()
raise Exception, "Can't access VM file as VM disk isn't ready" raise Exception("Can't access VM file as VM disk isn't ready")
return
if self.booted: if self.booted:
self.lock.release() self.lock.release()
raise Exception, "Can't access VM file as VM is already running" raise Exception("Can't access VM file as VM is already running")
return
try: try:
f = self.openpausednodefile(filename, "w") f = self.openpausednodefile(filename, "w")
f.write(contents) f.write(contents)
os.chmod(f.name, mode) os.chmod(f.name, mode)
f.close() f.close()
self.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode)) logger.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode))
finally: finally:
self.lock.release() self.lock.release()
@ -510,45 +501,46 @@ class XenNode(PyCoreNode):
# is VM running? # is VM running?
return False # XXX return False # XXX
def cmd(self, args, wait = True): def cmd(self, args, wait=True):
cmdAsString = string.join(args, ' ') cmdAsString = string.join(args, ' ')
if cmdAsString in self.CmdsToIgnore: if cmdAsString in self.CmdsToIgnore:
#self.warn("XEN PVM cmd(args=[%s]) called and ignored" % cmdAsString) # self.warn("XEN PVM cmd(args=[%s]) called and ignored" % cmdAsString)
return 0 return 0
if cmdAsString in self.CmdsRedirection: if cmdAsString in self.CmdsRedirection:
self.CmdsRedirection[cmdAsString](self) self.CmdsRedirection[cmdAsString](self)
return 0 return 0
self.warn("XEN PVM cmd(args=[%s]) called, but not yet implemented" % cmdAsString) logger("XEN PVM cmd(args=[%s]) called, but not yet implemented" % cmdAsString)
return 0 return 0
def cmdresult(self, args): def cmdresult(self, args):
cmdAsString = string.join(args, ' ') cmdAsString = string.join(args, ' ')
if cmdAsString in self.CmdsToIgnore: if cmdAsString in self.CmdsToIgnore:
#self.warn("XEN PVM cmd(args=[%s]) called and ignored" % cmdAsString) # self.warn("XEN PVM cmd(args=[%s]) called and ignored" % cmdAsString)
return (0, "") return 0, ""
self.warn("XEN PVM cmdresult(args=[%s]) called, but not yet implemented" % cmdAsString) logger.warn("XEN PVM cmdresult(args=[%s]) called, but not yet implemented" % cmdAsString)
return (0, "") return 0, ""
def popen(self, args): def popen(self, args):
cmdAsString = string.join(args, ' ') cmdAsString = string.join(args, ' ')
self.warn("XEN PVM popen(args=[%s]) called, but not yet implemented" % cmdAsString) logger.warn("XEN PVM popen(args=[%s]) called, but not yet implemented" % cmdAsString)
return return
def icmd(self, args): def icmd(self, args):
cmdAsString = string.join(args, ' ') cmdAsString = string.join(args, ' ')
self.warn("XEN PVM icmd(args=[%s]) called, but not yet implemented" % cmdAsString) logger.warn("XEN PVM icmd(args=[%s]) called, but not yet implemented" % cmdAsString)
return return
def term(self, sh = "/bin/sh"): def term(self, sh="/bin/sh"):
self.warn("XEN PVM term() called, but not yet implemented") logger.warn("XEN PVM term() called, but not yet implemented")
return return
def termcmdstring(self, sh = "/bin/sh"): def termcmdstring(self, sh="/bin/sh"):
''' We may add 'sudo' to the command string because the GUI runs as a """
We may add 'sudo' to the command string because the GUI runs as a
normal user. Use SSH if control interface is available, otherwise normal user. Use SSH if control interface is available, otherwise
use Xen console with a keymapping for easy login. use Xen console with a keymapping for easy login.
''' """
controlifc = None controlifc = None
for ifc in self.netifs(): for ifc in self.netifs():
if hasattr(ifc, 'control') and ifc.control == True: if hasattr(ifc, 'control') and ifc.control == True:
@ -561,32 +553,21 @@ class XenNode(PyCoreNode):
cmd += "-e ssh root@%s" % controlip cmd += "-e ssh root@%s" % controlip
return cmd return cmd
# otherwise use 'xm console' # otherwise use 'xm console'
#pw = self.getconfigitem('root_password') # pw = self.getconfigitem('root_password')
#cmd += "-xrm 'XTerm*VT100.translations: #override <Key>F1: " # cmd += "-xrm 'XTerm*VT100.translations: #override <Key>F1: "
#cmd += "string(\"root\\n\") \\n <Key>F2: string(\"%s\\n\")' " % pw # cmd += "string(\"root\\n\") \\n <Key>F2: string(\"%s\\n\")' " % pw
cmd += "-e sudo %s console %s" % (XM_PATH, self.vmname) cmd += "-e sudo %s console %s" % (XM_PATH, self.vmname)
return cmd return cmd
def shcmd(self, cmdstr, sh = "/bin/sh"): def shcmd(self, cmdstr, sh="/bin/sh"):
self.warn("XEN PVM shcmd(args=[%s]) called, but not yet implemented" % cmdstr) logger("XEN PVM shcmd(args=[%s]) called, but not yet implemented" % cmdstr)
return return
# from class SimpleLxcNode
def info(self, msg):
if self.verbose:
print "%s: %s" % (self.name, msg)
sys.stdout.flush()
# from class SimpleLxcNode
def warn(self, msg):
print >> sys.stderr, "%s: %s" % (self.name, msg)
sys.stderr.flush()
def mount(self, source, target): def mount(self, source, target):
self.warn("XEN PVM Nodes can't bind-mount filesystems") logger.warn("XEN PVM Nodes can't bind-mount filesystems")
def umount(self, target): def umount(self, target):
self.warn("XEN PVM Nodes can't bind-mount filesystems") logger.warn("XEN PVM Nodes can't bind-mount filesystems")
def newifindex(self): def newifindex(self):
self.lock.acquire() self.lock.acquire()
@ -606,15 +587,15 @@ class XenNode(PyCoreNode):
return -1 return -1
def addnetif(self, netif, ifindex): def addnetif(self, netif, ifindex):
self.warn("XEN PVM addnetif() called") logger.warn("XEN PVM addnetif() called")
PyCoreNode.addnetif(self, netif, ifindex) PyCoreNode.addnetif(self, netif, ifindex)
def delnetif(self, ifindex): def delnetif(self, ifindex):
self.warn("XEN PVM delnetif() called") logger.warn("XEN PVM delnetif() called")
PyCoreNode.delnetif(self, ifindex) PyCoreNode.delnetif(self, ifindex)
def newveth(self, ifindex = None, ifname = None, net = None, hwaddr = None): def newveth(self, ifindex=None, ifname=None, net=None, hwaddr=None):
self.warn("XEN PVM newveth(ifindex=%s, ifname=%s) called" % logger.warn("XEN PVM newveth(ifindex=%s, ifname=%s) called" %
(ifindex, ifname)) (ifindex, ifname))
self.lock.acquire() self.lock.acquire()
@ -623,12 +604,12 @@ class XenNode(PyCoreNode):
ifindex = self.newifindex() ifindex = self.newifindex()
if ifname is None: if ifname is None:
ifname = "eth%d" % ifindex ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid() sessionid = self.session.short_session_id()
name = "n%s.%s.%s" % (self.objid, ifindex, sessionid) name = "n%s.%s.%s" % (self.objid, ifindex, sessionid)
localname = "n%s.%s.%s" % (self.objid, ifname, sessionid) localname = "n%s.%s.%s" % (self.objid, ifname, sessionid)
ifclass = XenVEth ifclass = XenVEth
veth = ifclass(node = self, name = name, localname = localname, veth = ifclass(node=self, name=name, localname=localname,
mtu = 1500, net = net, hwaddr = hwaddr) mtu=1500, net=net, hwaddr=hwaddr)
veth.name = ifname veth.name = ifname
try: try:
@ -641,14 +622,14 @@ class XenNode(PyCoreNode):
finally: finally:
self.lock.release() self.lock.release()
def newtuntap(self, ifindex = None, ifname = None, net = None): def newtuntap(self, ifindex=None, ifname=None, net=None):
self.warn("XEN PVM newtuntap() called but not implemented") logger.warn("XEN PVM newtuntap() called but not implemented")
def sethwaddr(self, ifindex, addr): def sethwaddr(self, ifindex, addr):
self._netif[ifindex].sethwaddr(addr) self._netif[ifindex].sethwaddr(addr)
if self.up: if self.up:
pass pass
#self.cmd([IP_BIN, "link", "set", "dev", self.ifname(ifindex), # self.cmd([IP_BIN, "link", "set", "dev", self.ifname(ifindex),
# "address", str(addr)]) # "address", str(addr)])
def addaddr(self, ifindex, addr): def addaddr(self, ifindex, addr):
@ -662,15 +643,17 @@ class XenNode(PyCoreNode):
try: try:
self._netif[ifindex].deladdr(addr) self._netif[ifindex].deladdr(addr)
except ValueError: except ValueError:
self.warn("trying to delete unknown address: %s" % addr) logger.exception("trying to delete unknown address: %s", addr)
if self.up: if self.up:
pass pass
# self.cmd([IP_BIN, "addr", "del", str(addr), # self.cmd([IP_BIN, "addr", "del", str(addr),
# "dev", self.ifname(ifindex)]) # "dev", self.ifname(ifindex)])
valid_deladdrtype = ("inet", "inet6", "inet6link") valid_deladdrtype = ("inet", "inet6", "inet6link")
def delalladdr(self, ifindex, addrtypes = valid_deladdrtype):
addr = self.getaddr(self.ifname(ifindex), rescan = True) def delalladdr(self, ifindex, addrtypes=valid_deladdrtype):
addr = self.getaddr(self.ifname(ifindex), rescan=True)
for t in addrtypes: for t in addrtypes:
if t not in self.valid_deladdrtype: if t not in self.valid_deladdrtype:
raise ValueError, "addr type must be in: " + \ raise ValueError, "addr type must be in: " + \
@ -678,33 +661,31 @@ class XenNode(PyCoreNode):
for a in addr[t]: for a in addr[t]:
self.deladdr(ifindex, a) self.deladdr(ifindex, a)
# update cached information # update cached information
self.getaddr(self.ifname(ifindex), rescan = True) self.getaddr(self.ifname(ifindex), rescan=True)
# Xen PVM relies on boot process to bring up links # Xen PVM relies on boot process to bring up links
#def ifup(self, ifindex): # def ifup(self, ifindex):
# if self.up: # if self.up:
# self.cmd([IP_BIN, "link", "set", self.ifname(ifindex), "up"]) # self.cmd([IP_BIN, "link", "set", self.ifname(ifindex), "up"])
def newnetif(self, net = None, addrlist = [], hwaddr = None, def newnetif(self, net=None, addrlist=[], hwaddr=None,
ifindex = None, ifname = None): ifindex=None, ifname=None):
self.warn("XEN PVM newnetif(ifindex=%s, ifname=%s) called" % logger.warn("XEN PVM newnetif(ifindex=%s, ifname=%s) called" %
(ifindex, ifname)) (ifindex, ifname))
self.lock.acquire() self.lock.acquire()
if not self.up: if not self.up:
self.lock.release() self.lock.release()
raise Exception, "Can't access add veth as VM disk isn't ready" raise Exception("Can't access add veth as VM disk isn't ready")
return
if self.booted: if self.booted:
self.lock.release() self.lock.release()
raise Exception, "Can't access add veth as VM is already running" raise Exception("Can't access add veth as VM is already running")
return
try: try:
if isinstance(net, EmaneNode): if nodeutils.is_node(net, NodeTypes.EMANE):
raise Exception, "Xen PVM doesn't yet support Emane nets" raise Exception("Xen PVM doesn't yet support Emane nets")
# ifindex = self.newtuntap(ifindex = ifindex, ifname = ifname, # ifindex = self.newtuntap(ifindex = ifindex, ifname = ifname,
# net = net) # net = net)
@ -719,8 +700,8 @@ class XenNode(PyCoreNode):
# netif.addaddr(addr) # netif.addaddr(addr)
# return ifindex # return ifindex
else: else:
ifindex = self.newveth(ifindex = ifindex, ifname = ifname, ifindex = self.newveth(ifindex=ifindex, ifname=ifname,
net = net, hwaddr = hwaddr) net=net, hwaddr=hwaddr)
if net is not None: if net is not None:
self.attachnet(ifindex, net) self.attachnet(ifindex, net)
@ -728,24 +709,27 @@ class XenNode(PyCoreNode):
self.etcdir, self.etcdir,
'udev/rules.d/70-persistent-net.rules') 'udev/rules.d/70-persistent-net.rules')
f = self.openpausednodefile(rulefile, "a") f = self.openpausednodefile(rulefile, "a")
f.write('\n# Xen PVM virtual interface #%s %s with MAC address %s\n' % (ifindex, self.ifname(ifindex), hwaddr)) f.write(
'\n# Xen PVM virtual interface #%s %s with MAC address %s\n' % (ifindex, self.ifname(ifindex), hwaddr))
# Using MAC address as we're now loading PVM net driver "early" # Using MAC address as we're now loading PVM net driver "early"
# OLD: Would like to use MAC address, but udev isn't working with paravirtualized NICs. Perhaps the "set hw address" isn't triggering a rescan. # OLD: Would like to use MAC address, but udev isn't working with paravirtualized NICs. Perhaps the "set hw address" isn't triggering a rescan.
f.write('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="%s", KERNEL=="eth*", NAME="%s"\n' % (hwaddr, self.ifname(ifindex))) f.write(
#f.write('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", DEVPATH=="/devices/vif-%s/?*", KERNEL=="eth*", NAME="%s"\n' % (ifindex, self.ifname(ifindex))) 'SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="%s", KERNEL=="eth*", NAME="%s"\n' % (
hwaddr, self.ifname(ifindex)))
# f.write('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", DEVPATH=="/devices/vif-%s/?*", KERNEL=="eth*", NAME="%s"\n' % (ifindex, self.ifname(ifindex)))
f.close() f.close()
if hwaddr: if hwaddr:
self.sethwaddr(ifindex, hwaddr) self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist): for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr) self.addaddr(ifindex, addr)
#self.ifup(ifindex) # self.ifup(ifindex)
return ifindex return ifindex
finally: finally:
self.lock.release() self.lock.release()
def connectnode(self, ifname, othernode, otherifname): def connectnode(self, ifname, othernode, otherifname):
self.warn("XEN PVM connectnode() called") logger.warn("XEN PVM connectnode() called")
# tmplen = 8 # tmplen = 8
# tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase) # tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase)
@ -768,21 +752,19 @@ class XenNode(PyCoreNode):
self.lock.acquire() self.lock.acquire()
if not self.up: if not self.up:
self.lock.release() self.lock.release()
raise Exception, "Can't access VM file as VM disk isn't ready" raise Exception("Can't access VM file as VM disk isn't ready")
return
if self.booted: if self.booted:
self.lock.release() self.lock.release()
raise Exception, "Can't access VM file as VM is already running" raise Exception("Can't access VM file as VM is already running")
return
if filename in self.FilesToIgnore: if filename in self.FilesToIgnore:
#self.warn("XEN PVM addfile(filename=%s) ignored" % [filename]) # self.warn("XEN PVM addfile(filename=%s) ignored" % [filename])
return return
if filename in self.FilesRedirection: if filename in self.FilesRedirection:
redirFilename = self.FilesRedirection[filename] redirFilename = self.FilesRedirection[filename]
self.warn("XEN PVM addfile(filename=%s) redirected to %s" % (filename, redirFilename)) logger.warn("XEN PVM addfile(filename=%s) redirected to %s" % (filename, redirFilename))
filename = redirFilename filename = redirFilename
try: try:
@ -794,24 +776,24 @@ class XenNode(PyCoreNode):
fout.write(contents) fout.write(contents)
os.chmod(fout.name, mode) os.chmod(fout.name, mode)
fout.close() fout.close()
self.info("created nodefile: '%s'; mode: 0%o" % (fout.name, mode)) logger.info("created nodefile: '%s'; mode: 0%o" % (fout.name, mode))
finally: finally:
self.lock.release() self.lock.release()
self.warn("XEN PVM addfile(filename=%s) called" % [filename]) logger.warn("XEN PVM addfile(filename=%s) called" % [filename])
#shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % \ # shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % \
# (filename, srcname, filename) # (filename, srcname, filename)
#self.shcmd(shcmd) # self.shcmd(shcmd)
def unmount_all(self, path): def unmount_all(self, path):
''' Namespaces inherit the host mounts, so we need to ensure that all """
Namespaces inherit the host mounts, so we need to ensure that all
namespaces have unmounted our temporary mount area so that the namespaces have unmounted our temporary mount area so that the
kpartx command will succeed. kpartx command will succeed.
''' """
# Session.bootnodes() already has self.session._objslock # Session.bootnodes() already has self.session._objslock
for o in self.session.objs(): for o in self.session.objects.itervalues():
if not isinstance(o, LxcNode): if not isinstance(o, LxcNode):
continue continue
o.umount(path) o.umount(path)

View file

@ -1,11 +1,4 @@
# """
# CORE
# Copyright (c)2011-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
#
'''
xenconfig.py: Implementation of the XenConfigManager class for managing xenconfig.py: Implementation of the XenConfigManager class for managing
configurable items for XenNodes. configurable items for XenNodes.
@ -17,72 +10,93 @@ Node type config: XenConfigManager.configs[0] = (type='mytype', values)
All nodes of this type have this config. All nodes of this type have this config.
Node-specific config: XenConfigManager.configs[nodenumber] = (type, values) Node-specific config: XenConfigManager.configs[nodenumber] = (type, values)
The node having this specific number has this config. The node having this specific number has this config.
''' """
import sys, os, threading, subprocess, time, string
import ConfigParser import ConfigParser
from xml.dom.minidom import parseString, Document import os
from core.constants import * import string
from core import constants
from core.api import coreapi from core.api import coreapi
from core.conf import ConfigurableManager, Configurable from core.conf import Configurable
from core.conf import ConfigurableManager
from core.enumerations import ConfigDataTypes
from core.enumerations import ConfigFlags
from core.enumerations import ConfigTlvs
from core.enumerations import RegisterTlvs
from core.misc import log
logger = log.get_logger(__name__)
class XenConfigManager(ConfigurableManager): class XenConfigManager(ConfigurableManager):
''' Xen controller object. Lives in a Session instance and is used for """
Xen controller object. Lives in a Session instance and is used for
building Xen profiles. building Xen profiles.
''' """
_name = "xen" name = "xen"
_type = coreapi.CORE_TLV_REG_EMULSRV config_type = RegisterTlvs.EMULATION_SERVER.value
def __init__(self, session): def __init__(self, session):
ConfigurableManager.__init__(self, session) """
self.verbose = self.session.getcfgitembool('verbose', False) Creates a XenConfigManager instance.
self.default_config = XenDefaultConfig(session, objid=None)
:param core.session.Session session: session this manager is tied to
:return: nothing
"""
ConfigurableManager.__init__(self)
self.default_config = XenDefaultConfig(session, object_id=None)
self.loadconfigfile() self.loadconfigfile()
def setconfig(self, nodenum, conftype, values): def setconfig(self, nodenum, conftype, values):
''' add configuration values for a node to a dictionary; values are """
add configuration values for a node to a dictionary; values are
usually received from a Configuration Message, and may refer to a usually received from a Configuration Message, and may refer to a
node for which no object exists yet node for which no object exists yet
''' """
if nodenum is None: if nodenum is None:
nodenum = 0 # used for storing the global default config nodenum = 0 # used for storing the global default config
return ConfigurableManager.setconfig(self, nodenum, conftype, values) return ConfigurableManager.setconfig(self, nodenum, conftype, values)
def getconfig(self, nodenum, conftype, defaultvalues): def getconfig(self, nodenum, conftype, defaultvalues):
''' get configuration values for a node; if the values don't exist in """
get configuration values for a node; if the values don't exist in
our dictionary then return the default values supplied; if conftype our dictionary then return the default values supplied; if conftype
is None then we return a match on any conftype. is None then we return a match on any conftype.
''' """
if nodenum is None: if nodenum is None:
nodenum = 0 # used for storing the global default config nodenum = 0 # used for storing the global default config
return ConfigurableManager.getconfig(self, nodenum, conftype, return ConfigurableManager.getconfig(self, nodenum, conftype, defaultvalues)
defaultvalues)
def clearconfig(self, nodenum): def clearconfig(self, nodenum):
''' remove configuration values for a node """
''' remove configuration values for a node
"""
ConfigurableManager.clearconfig(self, nodenum) ConfigurableManager.clearconfig(self, nodenum)
if 0 in self.configs: if 0 in self.configs:
self.configs.pop(0) self.configs.pop(0)
def configure(self, session, msg): def configure(self, session, config_data):
''' Handle configuration messages for global Xen config. """
''' Handle configuration messages for global Xen config.
return self.default_config.configure(self, msg)
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
"""
return self.default_config.configure(self, config_data)
def loadconfigfile(self, filename=None): def loadconfigfile(self, filename=None):
''' Load defaults from the /etc/core/xen.conf file into dict object. """
''' Load defaults from the /etc/core/xen.conf file into dict object.
"""
if filename is None: if filename is None:
filename = os.path.join(CORE_CONF_DIR, 'xen.conf') filename = os.path.join(constants.CORE_CONF_DIR, 'xen.conf')
cfg = ConfigParser.SafeConfigParser() cfg = ConfigParser.SafeConfigParser()
if filename not in cfg.read(filename): if filename not in cfg.read(filename):
self.session.warn("unable to read Xen config file: %s" % filename) logger.warn("unable to read Xen config file: %s" % filename)
return return
section = "xen" section = "xen"
if not cfg.has_section(section): if not cfg.has_section(section):
self.session.warn("%s is missing a xen section!" % filename) logger.warn("%s is missing a xen section!" % filename)
return return
self.configfile = dict(cfg.items(section)) self.configfile = dict(cfg.items(section))
# populate default config items from config file entries # populate default config items from config file entries
@ -92,13 +106,14 @@ class XenConfigManager(ConfigurableManager):
if names[i] in self.configfile: if names[i] in self.configfile:
vals[i] = self.configfile[names[i]] vals[i] = self.configfile[names[i]]
# this sets XenConfigManager.configs[0] = (type='xen', vals) # this sets XenConfigManager.configs[0] = (type='xen', vals)
self.setconfig(None, self.default_config._name, vals) self.setconfig(None, self.default_config.name, vals)
def getconfigitem(self, name, model=None, node=None, value=None): def getconfigitem(self, name, model=None, node=None, value=None):
''' Get a config item of the given name, first looking for node-specific """
Get a config item of the given name, first looking for node-specific
configuration, then model specific, and finally global defaults. configuration, then model specific, and finally global defaults.
If a value is supplied, it will override any stored config. If a value is supplied, it will override any stored config.
''' """
if value is not None: if value is not None:
return value return value
n = None n = None
@ -112,7 +127,7 @@ class XenConfigManager(ConfigurableManager):
if v is None: if v is None:
# get item from default config for the machine type # get item from default config for the machine type
(t, v) = self.getconfig(nodenum=None, (t, v) = self.getconfig(nodenum=None,
conftype=self.default_config._name, conftype=self.default_config.name,
defaultvalues=None) defaultvalues=None)
confignames = self.default_config.getnames() confignames = self.default_config.getnames()
@ -124,142 +139,136 @@ class XenConfigManager(ConfigurableManager):
if name in self.configfile: if name in self.configfile:
return self.configfile[name] return self.configfile[name]
else: else:
#self.warn("missing config item '%s'" % name) # logger.warn("missing config item '%s'" % name)
return None return None
class XenConfig(Configurable): class XenConfig(Configurable):
''' Manage Xen configuration profiles. """
''' Manage Xen configuration profiles.
"""
@classmethod @classmethod
def configure(cls, xen, msg): def configure(cls, xen, config_data):
''' Handle configuration messages for setting up a model. """
Handle configuration messages for setting up a model.
Similar to Configurable.configure(), but considers opaque data Similar to Configurable.configure(), but considers opaque data
for indicating node types. for indicating node types.
'''
reply = None
nodenum = msg.gettlv(coreapi.CORE_TLV_CONF_NODE)
objname = msg.gettlv(coreapi.CORE_TLV_CONF_OBJ)
conftype = msg.gettlv(coreapi.CORE_TLV_CONF_TYPE)
opaque = msg.gettlv(coreapi.CORE_TLV_CONF_OPAQUE)
nodetype = objname :param xen: xen instance to configure
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
"""
reply = None
node_id = config_data.node
object_name = config_data.object
config_type = config_data.type
opaque = config_data.opaque
values_str = config_data.data_values
nodetype = object_name
if opaque is not None: if opaque is not None:
opaque_items = opaque.split(':') opaque_items = opaque.split(':')
if len(opaque_items) != 2: if len(opaque_items) != 2:
xen.warn("xen config: invalid opaque data in conf message") logger.warn("xen config: invalid opaque data in conf message")
return None return None
nodetype = opaque_items[1] nodetype = opaque_items[1]
if xen.verbose: logger.info("received configure message for %s", nodetype)
xen.info("received configure message for %s" % nodetype) if config_type == ConfigFlags.REQUEST.value:
if conftype == coreapi.CONF_TYPE_FLAGS_REQUEST: logger.info("replying to configure request for %s " % nodetype)
if xen.verbose:
xen.info("replying to configure request for %s " % nodetype)
# when object name is "all", the reply to this request may be None # when object name is "all", the reply to this request may be None
# if this node has not been configured for this model; otherwise we # if this node has not been configured for this model; otherwise we
# reply with the defaults for this model # reply with the defaults for this model
if objname == "all": if object_name == "all":
typeflags = coreapi.CONF_TYPE_FLAGS_UPDATE typeflags = ConfigFlags.UPDATE.value
else: else:
typeflags = coreapi.CONF_TYPE_FLAGS_NONE typeflags = ConfigFlags.NONE.value
values = xen.getconfig(nodenum, nodetype, defaultvalues=None)[1] values = xen.getconfig(node_id, nodetype, defaultvalues=None)[1]
if values is None: if values is None:
# get defaults from default "xen" config which includes # get defaults from default "xen" config which includes
# settings from both cls._confdefaultvalues and xen.conf # settings from both cls._confdefaultvalues and xen.conf
defaults = cls.getdefaultvalues() defaults = cls.getdefaultvalues()
values = xen.getconfig(nodenum, cls._name, defaults)[1] values = xen.getconfig(node_id, cls.name, defaults)[1]
if values is None: if values is None:
return None return None
# reply with config options # reply with config options
if nodenum is None: if node_id is None:
nodenum = 0 node_id = 0
reply = cls.toconfmsg(0, nodenum, typeflags, nodetype, values) reply = cls.config_data(0, node_id, typeflags, nodetype, values)
elif conftype == coreapi.CONF_TYPE_FLAGS_RESET: elif config_type == ConfigFlags.RESET.value:
if objname == "all": if object_name == "all":
xen.clearconfig(nodenum) xen.clearconfig(node_id)
#elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE: # elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE:
else: else:
# store the configuration values for later use, when the XenNode # store the configuration values for later use, when the XenNode
# object has been created # object has been created
if objname is None: if object_name is None:
xen.info("no configuration object for node %s" % nodenum) logger.info("no configuration object for node %s" % node_id)
return None return None
values_str = msg.gettlv(coreapi.CORE_TLV_CONF_VALUES)
if values_str is None: if values_str is None:
# use default or preconfigured values # use default or preconfigured values
defaults = cls.getdefaultvalues() defaults = cls.getdefaultvalues()
values = xen.getconfig(nodenum, cls._name, defaults)[1] values = xen.getconfig(node_id, cls.name, defaults)[1]
else: else:
# use new values supplied from the conf message # use new values supplied from the conf message
values = values_str.split('|') values = values_str.split('|')
xen.setconfig(nodenum, nodetype, values) xen.setconfig(node_id, nodetype, values)
return reply return reply
@classmethod @classmethod
def toconfmsg(cls, flags, nodenum, typeflags, nodetype, values): def config_data(cls, flags, node_id, type_flags, nodetype, values):
''' Convert this class to a Config API message. Some TLVs are defined """
Convert this class to a Config API message. Some TLVs are defined
by the class, but node number, conf type flags, and values must by the class, but node number, conf type flags, and values must
be passed in. be passed in.
''' """
values_str = string.join(values, '|') values_str = string.join(values, '|')
tlvdata = "" tlvdata = ""
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_NODE, nodenum) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.NODE.value, node_id)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OBJ, tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.OBJECT.value, cls.name)
cls._name) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.TYPE.value, type_flags)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_TYPE, datatypes = tuple(map(lambda x: x[1], cls.config_matrix))
typeflags) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.DATA_TYPES.value, datatypes)
datatypes = tuple( map(lambda x: x[1], cls._confmatrix) ) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.VALUES.value, values_str)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_DATA_TYPES, captions = reduce(lambda a, b: a + '|' + b, map(lambda x: x[4], cls.config_matrix))
datatypes) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.CAPTIONS, captions)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_VALUES, possiblevals = reduce(lambda a, b: a + '|' + b, map(lambda x: x[3], cls.config_matrix))
values_str) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.POSSIBLE_VALUES.value, possiblevals)
captions = reduce( lambda a,b: a + '|' + b, \ if cls.bitmap is not None:
map(lambda x: x[4], cls._confmatrix)) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.BITMAP.value, cls.bitmap)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_CAPTIONS, if cls.config_groups is not None:
captions) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.GROUPS.value, cls.config_groups)
possiblevals = reduce( lambda a,b: a + '|' + b, \ opaque = "%s:%s" % (cls.name, nodetype)
map(lambda x: x[3], cls._confmatrix)) tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.OPAQUE.value, opaque)
tlvdata += coreapi.CoreConfTlv.pack(
coreapi.CORE_TLV_CONF_POSSIBLE_VALUES, possiblevals)
if cls._bitmap is not None:
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_BITMAP,
cls._bitmap)
if cls._confgroups is not None:
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_GROUPS,
cls._confgroups)
opaque = "%s:%s" % (cls._name, nodetype)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OPAQUE,
opaque)
msg = coreapi.CoreConfMessage.pack(flags, tlvdata) msg = coreapi.CoreConfMessage.pack(flags, tlvdata)
return msg return msg
class XenDefaultConfig(XenConfig): class XenDefaultConfig(XenConfig):
''' Global default Xen configuration options. """
''' Global default Xen configuration options.
_name = "xen" """
name = "xen"
# Configuration items: # Configuration items:
# ('name', 'type', 'default', 'possible-value-list', 'caption') # ('name', 'type', 'default', 'possible-value-list', 'caption')
_confmatrix = [ config_matrix = [
('ram_size', coreapi.CONF_DATA_TYPE_STRING, '256', '', ('ram_size', ConfigDataTypes.STRING.value, '256', '',
'ram size (MB)'), 'ram size (MB)'),
('disk_size', coreapi.CONF_DATA_TYPE_STRING, '256M', '', ('disk_size', ConfigDataTypes.STRING.value, '256M', '',
'disk size (use K/M/G suffix)'), 'disk size (use K/M/G suffix)'),
('iso_file', coreapi.CONF_DATA_TYPE_STRING, '', '', ('iso_file', ConfigDataTypes.STRING.value, '', '',
'iso file'), 'iso file'),
('mount_path', coreapi.CONF_DATA_TYPE_STRING, '', '', ('mount_path', ConfigDataTypes.STRING.value, '', '',
'mount path'), 'mount path'),
('etc_path', coreapi.CONF_DATA_TYPE_STRING, '', '', ('etc_path', ConfigDataTypes.STRING.value, '', '',
'etc path'), 'etc path'),
('persist_tar_iso', coreapi.CONF_DATA_TYPE_STRING, '', '', ('persist_tar_iso', ConfigDataTypes.STRING.value, '', '',
'iso persist tar file'), 'iso persist tar file'),
('persist_tar', coreapi.CONF_DATA_TYPE_STRING, '', '', ('persist_tar', ConfigDataTypes.STRING.value, '', '',
'persist tar file'), 'persist tar file'),
('root_password', coreapi.CONF_DATA_TYPE_STRING, 'password', '', ('root_password', ConfigDataTypes.STRING.value, 'password', '',
'root password'), 'root password'),
] ]
_confgroups = "domU properties:1-%d" % len(_confmatrix) config_groups = "domU properties:1-%d" % len(config_matrix)

View file

@ -29,12 +29,12 @@ quagga_sbin_search = "/usr/local/sbin /usr/sbin /usr/lib/quagga"
# #
# #
# uncomment and edit to establish a distributed control backchannel # uncomment and edit to establish a distributed control backchannel
#controlnet = core1:172.16.1.0/24 core2:172.16.2.0/24 core3:172.16.3.0/24 core4 :172.16.4.0/24 core5:172.16.5.0/24 #controlnet = core1:172.16.1.0/24 core:172.16.2.0/24 core3:172.16.3.0/24 core4 :172.16.4.0/24 core5:172.16.5.0/24
# uncomment and edit to establish distributed auxiliary control channels. # uncomment and edit to establish distributed auxiliary control channels.
#controlnet1 = core1:172.17.1.0/24 core2:172.17.2.0/24 core3:172.17.3.0/24 core4 :172.17.4.0/24 core5:172.17.5.0/24 #controlnet1 = core1:172.17.1.0/24 core:172.17.2.0/24 core3:172.17.3.0/24 core4 :172.17.4.0/24 core5:172.17.5.0/24
#controlnet2 = core1:172.18.1.0/24 core2:172.18.2.0/24 core3:172.18.3.0/24 core4 :172.18.4.0/24 core5:172.18.5.0/24 #controlnet2 = core1:172.18.1.0/24 core:172.18.2.0/24 core3:172.18.3.0/24 core4 :172.18.4.0/24 core5:172.18.5.0/24
#controlnet3 = core1:172.19.1.0/24 core2:172.19.2.0/24 core3:172.19.3.0/24 core4 :172.19.4.0/24 core5:172.19.5.0/24 #controlnet3 = core1:172.19.1.0/24 core:172.19.2.0/24 core3:172.19.3.0/24 core4 :172.19.4.0/24 core5:172.19.5.0/24
# uncomment and edit to assign host interfaces to auxilary control channels # uncomment and edit to assign host interfaces to auxilary control channels
# for use in connecting with other servers in a distributed environments. # for use in connecting with other servers in a distributed environments.
@ -62,4 +62,4 @@ emane_models = RfPipe, Ieee80211abg, CommEffect, Bypass, Tdma
#emane_log_level = 2 #emane_log_level = 2
emane_realtime = True emane_realtime = True
aux_request_handler = core.addons.api2handler.CoreApi2RequestHandler:12222 #aux_request_handler = core.addons.api2handler.CoreApi2RequestHandler:12222

View file

@ -8,65 +8,83 @@
# #
import optparse, sys, os, datetime, time import datetime
import optparse
import sys
import time
from core import pycore from core.misc import ipaddress, nodeutils
from core.misc import ipaddr from core.misc import nodemaps
from core.misc.utils import mutecall
from core.mobility import BasicRangeModel from core.mobility import BasicRangeModel
from core.netns.nodes import WlanNode
from core.netns.vnet import EbtablesQueue from core.netns.vnet import EbtablesQueue
from core.netns.vnode import LxcNode
from core.session import Session
# node list - global so you can play using 'python -i' # node list - global so you can play using 'python -i'
# e.g. >>> n[0].session.shutdown() # e.g. >>> n[0].session.shutdown()
n = [] n = []
def test(options): def test(options):
prefix = ipaddr.IPv4Prefix("10.83.0.0/16") prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
session = pycore.Session(persistent = True) session = Session(1, persistent=True)
if options.enablesdt: if options.enablesdt:
session.location.setrefgeo(47.57917,-122.13232,50.0) # GUI default # GUI default
session.location.setrefgeo(47.57917, -122.13232, 50.0)
session.location.refscale = 100.0 session.location.refscale = 100.0
session.options.enablesdt = True session.options.enablesdt = True
session.options.sdturl = options.sdturl session.options.sdturl = options.sdturl
wlanid = options.numnodes + 1 wlanid = options.numnodes + 1
net = session.addobj(cls = pycore.nodes.WlanNode, name = "wlan%d" % wlanid, net = session.add_object(
objid = wlanid, verbose = True) cls=WlanNode,
name="wlan%d" % wlanid,
objid=wlanid
)
values = list(BasicRangeModel.getdefaultvalues()) values = list(BasicRangeModel.getdefaultvalues())
#values[0] = 5000000 # 5000km range # values[0] = 5000000 # 5000km range
net.setmodel(BasicRangeModel, values) net.setmodel(BasicRangeModel, values)
for i in xrange(1, options.numnodes + 1): for i in xrange(1, options.numnodes + 1):
tmp = session.addobj(cls = pycore.nodes.LxcNode, name = "n%d" % i, node = session.add_object(cls=LxcNode, name="n%d" % i, objid=i)
objid = i) address = "%s/%s" % (prefix.addr(i), prefix.prefixlen)
tmp.newnetif(net, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) print "setting node address: %s - %s" % (node.objid, address)
node.newnetif(net, [address])
# set increasing Z coordinates # set increasing Z coordinates
tmp.setposition(10, 10, 100*i) node.setposition(10, 10, 100)
n.append(tmp) n.append(node)
# example setting node n2 to a high altitude # example setting node n2 to a high altitude
#n[1].setposition(10, 10, 2000000) # 2000km # n[1].setposition(10, 10, 2000000) # 2000km
#session.sdt.updatenode(n[1].objid, 0, 10, 10, 2000000) # session.sdt.updatenode(n[1].objid, 0, 10, 10, 2000000)
# launches terminal for the first node
# n[0].term("bash")
n[0].icmd(["ping", "-c", "5", "127.0.0.1"])
n[0].term("bash")
# wait for rate seconds to allow ebtables commands to commit # wait for rate seconds to allow ebtables commands to commit
time.sleep(EbtablesQueue.rate) time.sleep(EbtablesQueue.rate)
#session.shutdown()
raw_input("press enter to exit")
session.shutdown()
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes = 2, enablesdt = False, parser.set_defaults(numnodes=2, enablesdt=False, sdturl="tcp://127.0.0.1:50000/")
sdturl = "tcp://127.0.0.1:50000/") parser.add_option(
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, "-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes to test; default = %s" % help="number of nodes to test; default = %s" % parser.defaults["numnodes"]
parser.defaults["numnodes"]) )
parser.add_option("-s", "--sdt", dest = "enablesdt", action = "store_true", parser.add_option("-s", "--sdt", dest="enablesdt", action="store_true", help="enable SDT output")
help = "enable SDT output") parser.add_option(
parser.add_option("-u", "--sdturl", dest = "sdturl", type = "string", "-u", "--sdturl", dest="sdturl", type="string",
help = "URL for SDT connection, default = %s" % \ help="URL for SDT connection, default = %s" % parser.defaults["sdturl"]
parser.defaults["sdturl"]) )
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -86,8 +104,12 @@ def main():
test(options) test(options)
print >> sys.stderr, \ print >> sys.stderr, "elapsed time: %s" % (datetime.datetime.now() - start)
"elapsed time: %s" % (datetime.datetime.now() - start)
if __name__ == "__main__": if __name__ == "__main__":
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main() main()

View file

@ -5,71 +5,86 @@
# A distributed example where CORE API messaging is used to create a session # A distributed example where CORE API messaging is used to create a session
# on a daemon server. The daemon server defaults to 127.0.0.1:4038 # on a daemon server. The daemon server defaults to 127.0.0.1:4038
# to target a remote machine specify '-d <ip address>' parameter, it needs to be # to target a remote machine specify "-d <ip address>" parameter, it needs to be
# running the daemon with listenaddr=0.0.0.0 in the core.conf file. # running the daemon with listenaddr=0.0.0.0 in the core.conf file.
# This script creates no nodes locally and therefore can be run as an # This script creates no nodes locally and therefore can be run as an
# unprivileged user. # unprivileged user.
import sys, datetime, optparse, time import datetime
import optparse
import sys
from core import pycore
from core.misc import ipaddr
from core.constants import *
from core.api import coreapi from core.api import coreapi
from core.api import dataconversion
from core.api.coreapi import CoreExecuteTlv
from core.enumerations import CORE_API_PORT
from core.enumerations import EventTlvs
from core.enumerations import EventTypes
from core.enumerations import ExecuteTlvs
from core.enumerations import LinkTlvs
from core.enumerations import LinkTypes
from core.enumerations import MessageFlags
from core.enumerations import MessageTypes
from core.misc import ipaddress, nodeutils, nodemaps
from core.netns import nodes
# declare classes for use with Broker # declare classes for use with Broker
import select
coreapi.add_node_class("CORE_NODE_DEF", from core.session import Session
coreapi.CORE_NODE_DEF, pycore.nodes.CoreNode)
coreapi.add_node_class("CORE_NODE_SWITCH",
coreapi.CORE_NODE_SWITCH, pycore.nodes.SwitchNode)
# node list (count from 1) # node list (count from 1)
n = [None] n = [None]
exec_num = 1 exec_num = 1
def cmd(node, exec_cmd): def cmd(node, exec_cmd):
''' """
:param node: The node the command should be issued too :param node: The node the command should be issued too
:param exec_cmd: A string with the command to be run :param exec_cmd: A string with the command to be run
:return: Returns the result of the command :return: Returns the result of the command
''' """
global exec_num global exec_num
# Set up the command api message # Set up the command api message
tlvdata = coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_NODE, node.objid) tlvdata = CoreExecuteTlv.pack(ExecuteTlvs.NODE.value, node.objid)
tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_NUM, exec_num) tlvdata += CoreExecuteTlv.pack(ExecuteTlvs.NUMBER.value, exec_num)
tlvdata += coreapi.CoreExecTlv.pack(coreapi.CORE_TLV_EXEC_CMD, exec_cmd) tlvdata += CoreExecuteTlv.pack(ExecuteTlvs.COMMAND.value, exec_cmd)
msg = coreapi.CoreExecMessage.pack(coreapi.CORE_API_STR_FLAG | coreapi.CORE_API_TXT_FLAG, tlvdata) msg = coreapi.CoreExecMessage.pack(MessageFlags.STRING.value | MessageFlags.TEXT.value, tlvdata)
node.session.broker.handlerawmsg(msg) node.session.broker.handlerawmsg(msg)
exec_num += 1 exec_num += 1
# Now wait for the response # Now wait for the response
(h, p, sock) = node.session.broker.servers['localhost'] server = node.session.broker.servers["localhost"]
sock.settimeout(50.0) server.sock.settimeout(50.0)
msghdr = sock.recv(coreapi.CoreMessage.hdrsiz)
msgtype, msgflags, msglen = coreapi.CoreMessage.unpackhdr(msghdr) # receive messages until we get our execute response
msgdata = sock.recv(msglen) result = None
while True:
msghdr = server.sock.recv(coreapi.CoreMessage.header_len)
msgtype, msgflags, msglen = coreapi.CoreMessage.unpack_header(msghdr)
msgdata = server.sock.recv(msglen)
# If we get the right response return the results # If we get the right response return the results
if msgtype == coreapi.CORE_API_EXEC_MSG: print "received response message: %s" % MessageTypes(msgtype)
if msgtype == MessageTypes.EXECUTE.value:
msg = coreapi.CoreExecMessage(msgflags, msghdr, msgdata) msg = coreapi.CoreExecMessage(msgflags, msghdr, msgdata)
return msg.gettlv(coreapi.CORE_TLV_EXEC_RESULT) result = msg.get_tlv(ExecuteTlvs.RESULT.value)
else: break
return None
return result
def main(): def main():
usagestr = "usage: %prog [-n] number of nodes [-d] daemon address" usagestr = "usage: %prog [-n] number of nodes [-d] daemon address"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes = 5, daemon = '127.0.0.1:'+str(coreapi.CORE_API_PORT)) parser.set_defaults(numnodes=5, daemon="127.0.0.1:" + str(CORE_API_PORT))
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, parser.add_option("-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes") help="number of nodes")
parser.add_option("-d", "--daemon-server", dest = "daemon", type = str, parser.add_option("-d", "--daemon-server", dest="daemon", type=str,
help = "daemon server IP address") help="daemon server IP address")
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -85,94 +100,95 @@ def main():
usage("daemon server IP address (-d) is a required argument") usage("daemon server IP address (-d) is a required argument")
for a in args: for a in args:
sys.stderr.write("ignoring command line argument: '%s'\n" % a) sys.stderr.write("ignoring command line argument: %s\n" % a)
start = datetime.datetime.now() start = datetime.datetime.now()
prefix = ipaddr.IPv4Prefix("10.83.0.0/16") prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
session = pycore.Session(persistent=True) session = Session(1, persistent=True)
if 'server' in globals(): if "server" in globals():
server.addsession(session) server.addsession(session)
# distributed setup - connect to daemon server # distributed setup - connect to daemon server
daemonport = options.daemon.split(':') daemonport = options.daemon.split(":")
daemonip = daemonport[0] daemonip = daemonport[0]
# Localhost is already set in the session but we change it to be the remote daemon # Localhost is already set in the session but we change it to be the remote daemon
# This stops the remote daemon trying to build a tunnel back which would fail # This stops the remote daemon trying to build a tunnel back which would fail
daemon = 'localhost' daemon = "localhost"
if len(daemonport) > 1: if len(daemonport) > 1:
port = int(daemonport[1]) port = int(daemonport[1])
else: else:
port = coreapi.CORE_API_PORT port = CORE_API_PORT
print "connecting to daemon at %s:%d" % (daemon, port) print "connecting to daemon at %s:%d" % (daemon, port)
session.broker.addserver(daemon, daemonip, port) session.broker.addserver(daemon, daemonip, port)
# Set the local session id to match the port. # Set the local session id to match the port.
# Not necessary but seems neater. # Not necessary but seems neater.
session.sessionid = session.broker.getserver('localhost')[2].getsockname()[1] # session.sessionid = session.broker.getserver("localhost")[2].getsockname()[1]
session.broker.setupserver(daemon) session.broker.setupserver(daemon)
# We do not want the recvloop running as we will deal ourselves # We do not want the recvloop running as we will deal ourselves
session.broker.dorecvloop = False session.broker.dorecvloop = False
# Change to configuration state on both machines # Change to configuration state on both machines
session.setstate(coreapi.CORE_EVENT_CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE.value)
tlvdata = coreapi.CoreEventTlv.pack(coreapi.CORE_TLV_EVENT_TYPE, tlvdata = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value, EventTypes.CONFIGURATION_STATE.value)
coreapi.CORE_EVENT_CONFIGURATION_STATE)
session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata)) session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata))
flags = coreapi.CORE_API_ADD_FLAG flags = MessageFlags.ADD.value
switch = pycore.nodes.SwitchNode(session = session, name='switch', start=False) switch = nodes.SwitchNode(session=session, name="switch", start=False)
switch.setposition(x=80,y=50) switch.setposition(x=80, y=50)
switch.server = daemon switch.server = daemon
session.broker.handlerawmsg(switch.tonodemsg(flags=flags)) switch_data = switch.data(flags)
switch_message = dataconversion.convert_node(switch_data)
session.broker.handlerawmsg(switch_message)
numberOfNodes = options.numnodes number_of_nodes = options.numnodes
print "creating %d remote nodes with addresses from %s" % \ print "creating %d remote nodes with addresses from %s" % (options.numnodes, prefix)
(options.numnodes, prefix)
# create remote nodes via API # create remote nodes via API
for i in xrange(1, numberOfNodes + 1): for i in xrange(1, number_of_nodes + 1):
tmp = pycore.nodes.CoreNode(session = session, objid = i, node = nodes.CoreNode(session=session, objid=i, name="n%d" % i, start=False)
name = "n%d" % i, start=False) node.setposition(x=150 * i, y=150)
tmp.setposition(x=150*i,y=150) node.server = daemon
tmp.server = daemon node_data = node.data(flags)
session.broker.handlerawmsg(tmp.tonodemsg(flags=flags)) node_message = dataconversion.convert_node(node_data)
n.append(tmp) session.broker.handlerawmsg(node_message)
n.append(node)
# create remote links via API # create remote links via API
for i in xrange(1, numberOfNodes + 1): for i in xrange(1, number_of_nodes + 1):
tlvdata = coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER, tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.objid)
switch.objid) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER, i) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_TYPE, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, 0)
coreapi.CORE_LINK_WIRED) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4.value, prefix.addr(i))
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, 0) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4_MASK.value, prefix.prefixlen)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2IP4,
prefix.addr(i))
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2IP4MASK,
prefix.prefixlen)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata) msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
session.broker.handlerawmsg(msg) session.broker.handlerawmsg(msg)
# We change the daemon to Instantiation state # We change the daemon to Instantiation state
# We do not change the local session as it would try and build a tunnel and fail # We do not change the local session as it would try and build a tunnel and fail
tlvdata = coreapi.CoreEventTlv.pack(coreapi.CORE_TLV_EVENT_TYPE, tlvdata = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value, EventTypes.INSTANTIATION_STATE.value)
coreapi.CORE_EVENT_INSTANTIATION_STATE)
msg = coreapi.CoreEventMessage.pack(0, tlvdata) msg = coreapi.CoreEventMessage.pack(0, tlvdata)
session.broker.handlerawmsg(msg) session.broker.handlerawmsg(msg)
# Get the ip or last node and ping it from the first # Get the ip or last node and ping it from the first
print 'Pinging from the first to the last node' print "Pinging from the first to the last node"
pingip = cmd(n[-1], 'ip -4 -o addr show dev eth0').split()[3].split('/')[0] pingip = cmd(n[-1], "ip -4 -o addr show dev eth0").split()[3].split("/")[0]
print cmd(n[1], 'ping -c 5 ' + pingip) print cmd(n[1], "ping -c 5 " + pingip)
print "elapsed time: %s" % (datetime.datetime.now() - start) print "elapsed time: %s" % (datetime.datetime.now() - start)
print "To stop this session, use the 'core-cleanup' script on the remote daemon server." print "To stop this session, use the core-cleanup script on the remote daemon server."
raw_input("press enter to exit")
if __name__ == "__main__" or __name__ == "__builtin__": if __name__ == "__main__" or __name__ == "__builtin__":
main() # configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main()

View file

@ -9,33 +9,32 @@
# running the daemon with listenaddr=0.0.0.0 in the core.conf file. # running the daemon with listenaddr=0.0.0.0 in the core.conf file.
# #
import sys, datetime, optparse, time import datetime
import optparse
import sys
from core import pycore from core import constants
from core.misc import ipaddr from core.api import coreapi, dataconversion
from core.constants import * from core.enumerations import CORE_API_PORT, EventTypes, EventTlvs, LinkTlvs, LinkTypes, MessageFlags
from core.api import coreapi from core.misc import ipaddress, nodeutils, nodemaps
from core.netns import nodes
# declare classes for use with Broker from core.session import Session
coreapi.add_node_class("CORE_NODE_DEF",
coreapi.CORE_NODE_DEF, pycore.nodes.CoreNode)
coreapi.add_node_class("CORE_NODE_SWITCH",
coreapi.CORE_NODE_SWITCH, pycore.nodes.SwitchNode)
# node list (count from 1) # node list (count from 1)
n = [None] n = [None]
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes = 5, slave = None) parser.set_defaults(numnodes=5, slave=None)
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, parser.add_option("-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes") help="number of nodes")
parser.add_option("-s", "--slave-server", dest = "slave", type = str, parser.add_option("-s", "--slave-server", dest="slave", type=str,
help = "slave server IP address") help="slave server IP address")
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -55,8 +54,8 @@ def main():
start = datetime.datetime.now() start = datetime.datetime.now()
prefix = ipaddr.IPv4Prefix("10.83.0.0/16") prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
session = pycore.Session(persistent=True) session = Session(1, persistent=True)
if 'server' in globals(): if 'server' in globals():
server.addsession(session) server.addsession(session)
@ -66,59 +65,53 @@ def main():
if len(slaveport) > 1: if len(slaveport) > 1:
port = int(slaveport[1]) port = int(slaveport[1])
else: else:
port = coreapi.CORE_API_PORT port = CORE_API_PORT
print "connecting to slave at %s:%d" % (slave, port) print "connecting to slave at %s:%d" % (slave, port)
session.broker.addserver(slave, slave, port) session.broker.addserver(slave, slave, port)
session.broker.setupserver(slave) session.broker.setupserver(slave)
session.setstate(coreapi.CORE_EVENT_CONFIGURATION_STATE) session.set_state(EventTypes.CONFIGURATION_STATE.value)
tlvdata = coreapi.CoreEventTlv.pack(coreapi.CORE_TLV_EVENT_TYPE, tlvdata = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value, EventTypes.CONFIGURATION_STATE.value)
coreapi.CORE_EVENT_CONFIGURATION_STATE)
session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata)) session.broker.handlerawmsg(coreapi.CoreEventMessage.pack(0, tlvdata))
switch = session.addobj(cls = pycore.nodes.SwitchNode, name = "switch") switch = session.add_object(cls=nodes.SwitchNode, name="switch")
switch.setposition(x=80,y=50) switch.setposition(x=80, y=50)
num_local = options.numnodes / 2 num_local = options.numnodes / 2
num_remote = options.numnodes / 2 + options.numnodes % 2 num_remote = options.numnodes / 2 + options.numnodes % 2
print "creating %d (%d local / %d remote) nodes with addresses from %s" % \ print "creating %d (%d local / %d remote) nodes with addresses from %s" % \
(options.numnodes, num_local, num_remote, prefix) (options.numnodes, num_local, num_remote, prefix)
for i in xrange(1, num_local + 1): for i in xrange(1, num_local + 1):
tmp = session.addobj(cls = pycore.nodes.CoreNode, name = "n%d" % i, node = session.add_object(cls=nodes.CoreNode, name="n%d" % i, objid=i)
objid=i) node.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
tmp.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) node.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"])
tmp.cmd([SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) node.setposition(x=150 * i, y=150)
tmp.setposition(x=150*i,y=150) n.append(node)
n.append(tmp)
flags = coreapi.CORE_API_ADD_FLAG flags = MessageFlags.ADD.value
session.broker.handlerawmsg(switch.tonodemsg(flags=flags)) session.broker.handlerawmsg(switch.tonodemsg(flags=flags))
# create remote nodes via API # create remote nodes via API
for i in xrange(num_local + 1, options.numnodes + 1): for i in xrange(num_local + 1, options.numnodes + 1):
tmp = pycore.nodes.CoreNode(session = session, objid = i, node = nodes.CoreNode(session=session, objid=i, name="n%d" % i, start=False)
name = "n%d" % i, start=False) node.setposition(x=150 * i, y=150)
tmp.setposition(x=150*i,y=150) node.server = slave
tmp.server = slave n.append(node)
n.append(tmp) node_data = node.data(flags)
session.broker.handlerawmsg(tmp.tonodemsg(flags=flags)) node_message = dataconversion.convert_node(node_data)
session.broker.handlerawmsg(node_message)
# create remote links via API # create remote links via API
for i in xrange(num_local + 1, options.numnodes + 1): for i in xrange(num_local + 1, options.numnodes + 1):
tlvdata = coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER, tlvdata = coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, switch.objid)
switch.objid) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, i)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER, i) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, LinkTypes.WIRED.value)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_TYPE, tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, 0)
coreapi.CORE_LINK_WIRED) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4.value, prefix.addr(i))
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, 0) tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_IP4_MASK.value, prefix.prefixlen)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2IP4,
prefix.addr(i))
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2IP4MASK,
prefix.prefixlen)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata) msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
session.broker.handlerawmsg(msg) session.broker.handlerawmsg(msg)
session.instantiate() session.instantiate()
tlvdata = coreapi.CoreEventTlv.pack(coreapi.CORE_TLV_EVENT_TYPE, tlvdata = coreapi.CoreEventTlv.pack(EventTlvs.TYPE.value, EventTypes.INSTANTIATION_STATE.value)
coreapi.CORE_EVENT_INSTANTIATION_STATE)
msg = coreapi.CoreEventMessage.pack(0, tlvdata) msg = coreapi.CoreEventMessage.pack(0, tlvdata)
session.broker.handlerawmsg(msg) session.broker.handlerawmsg(msg)
@ -132,6 +125,10 @@ def main():
print "To stop this session, use the 'core-cleanup' script on this server" print "To stop this session, use the 'core-cleanup' script on this server"
print "and on the remote slave server." print "and on the remote slave server."
if __name__ == "__main__" or __name__ == "__builtin__":
main()
if __name__ == "__main__" or __name__ == "__builtin__":
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main()

View file

@ -6,25 +6,31 @@
# Example CORE Python script that attaches N nodes to an EMANE 802.11abg # Example CORE Python script that attaches N nodes to an EMANE 802.11abg
# network. One of the parameters is changed, the pathloss mode. # network. One of the parameters is changed, the pathloss mode.
import sys, datetime, optparse import datetime
import optparse
import sys
from core import pycore from core import constants
from core.misc import ipaddr
from core.constants import *
from core.emane.ieee80211abg import EmaneIeee80211abgModel from core.emane.ieee80211abg import EmaneIeee80211abgModel
from core.emane.nodes import EmaneNode
from core.misc import ipaddress, nodeutils, nodemaps
from core.netns import nodes
# node list (count from 1) # node list (count from 1)
from core.session import Session
n = [None] n = [None]
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes = 5) parser.set_defaults(numnodes=5)
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, parser.add_option("-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes") help="number of nodes")
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -43,44 +49,44 @@ def main():
start = datetime.datetime.now() start = datetime.datetime.now()
# IP subnet # IP subnet
prefix = ipaddr.IPv4Prefix("10.83.0.0/16") prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
# session with some EMANE initialization # session with some EMANE initialization
cfg = {'verbose': 'false'} cfg = {'verbose': 'false'}
session = pycore.Session(cfg = cfg, persistent = True) session = Session(1, config=cfg, persistent=True)
session.master = True session.master = True
session.location.setrefgeo(47.57917,-122.13232,2.00000) session.location.setrefgeo(47.57917, -122.13232, 2.00000)
session.location.refscale = 150.0 session.location.refscale = 150.0
session.cfg['emane_models'] = "RfPipe, Ieee80211abg, Bypass" session.config['emane_models'] = "RfPipe, Ieee80211abg, Bypass"
session.emane.loadmodels() session.emane.loadmodels()
if 'server' in globals(): if 'server' in globals():
server.addsession(session) server.addsession(session)
# EMANE WLAN # EMANE WLAN
print "creating EMANE WLAN wlan1" print "creating EMANE WLAN wlan1"
wlan = session.addobj(cls = pycore.nodes.EmaneNode, name = "wlan1") wlan = session.add_object(cls=EmaneNode, name="wlan1")
wlan.setposition(x=80,y=50) wlan.setposition(x=80, y=50)
names = EmaneIeee80211abgModel.getnames() names = EmaneIeee80211abgModel.getnames()
values = list(EmaneIeee80211abgModel.getdefaultvalues()) values = list(EmaneIeee80211abgModel.getdefaultvalues())
# TODO: change any of the EMANE 802.11 parameter values here # TODO: change any of the EMANE 802.11 parameter values here
for i in range(0, len(names)): for i in range(0, len(names)):
print "EMANE 80211 \"%s\" = \"%s\"" % (names[i], values[i]) print "EMANE 80211 \"%s\" = \"%s\"" % (names[i], values[i])
try: try:
values[ names.index('pathlossmode') ] = '2ray' values[names.index('pathlossmode')] = '2ray'
except ValueError: except ValueError:
values[ names.index('propagationmodel') ] = '2ray' values[names.index('propagationmodel')] = '2ray'
session.emane.setconfig(wlan.objid, EmaneIeee80211abgModel._name, values) session.emane.setconfig(wlan.objid, EmaneIeee80211abgModel.name, values)
services_str = "zebra|OSPFv3MDR|IPForward" services_str = "zebra|OSPFv3MDR|IPForward"
print "creating %d nodes with addresses from %s" % \ print "creating %d nodes with addresses from %s" % \
(options.numnodes, prefix) (options.numnodes, prefix)
for i in xrange(1, options.numnodes + 1): for i in xrange(1, options.numnodes + 1):
tmp = session.addobj(cls = pycore.nodes.CoreNode, name = "n%d" % i, tmp = session.add_object(cls=nodes.CoreNode, name="n%d" % i,
objid=i) objid=i)
tmp.newnetif(wlan, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) tmp.newnetif(wlan, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
tmp.cmd([SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) tmp.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"])
tmp.setposition(x=150*i,y=150) tmp.setposition(x=150 * i, y=150)
session.services.addservicestonode(tmp, "", services_str, verbose=False) session.services.addservicestonode(tmp, "", services_str)
n.append(tmp) n.append(tmp)
# this starts EMANE, etc. # this starts EMANE, etc.
@ -92,6 +98,10 @@ def main():
print "elapsed time: %s" % (datetime.datetime.now() - start) print "elapsed time: %s" % (datetime.datetime.now() - start)
if __name__ == "__main__" or __name__ == "__builtin__":
main()
if __name__ == "__main__" or __name__ == "__builtin__":
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main()

View file

@ -5,49 +5,48 @@
# #
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com> # author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
# #
'''
"""
howmanynodes.py - This is a CORE script that creates network namespace nodes howmanynodes.py - This is a CORE script that creates network namespace nodes
having one virtual Ethernet interface connected to a bridge. It continues to having one virtual Ethernet interface connected to a bridge. It continues to
add nodes until an exception occurs. The number of nodes per bridge can be add nodes until an exception occurs. The number of nodes per bridge can be
specified. specified.
''' """
import optparse, sys, os, datetime, time, shutil import datetime
try: import optparse
from core import pycore import shutil
except ImportError: import sys
# hack for Fedora autoconf that uses the following pythondir: import time
if "/usr/lib/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.6/site-packages") from core import constants
if "/usr/lib64/python2.6/site-packages" in sys.path: from core.misc import ipaddress, nodeutils, nodemaps
sys.path.append("/usr/local/lib64/python2.6/site-packages") from core.netns import nodes
if "/usr/lib/python2.7/site-packages" in sys.path: from core.session import Session
sys.path.append("/usr/local/lib/python2.7/site-packages")
if "/usr/lib64/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.7/site-packages")
from core import pycore
from core.misc import ipaddr
from core.constants import *
GBD = 1024.0 * 1024.0 GBD = 1024.0 * 1024.0
def linuxversion(): def linuxversion():
''' Return a string having the Linux kernel version. """ Return a string having the Linux kernel version.
''' """
f = open('/proc/version', 'r') f = open("/proc/version", "r")
v = f.readline().split() v = f.readline().split()
version_str = ' '.join(v[:3]) version_str = " ".join(v[:3])
f.close() f.close()
return version_str return version_str
MEMKEYS = ('total', 'free', 'buff', 'cached', 'stotal', 'sfree')
MEMKEYS = ("total", "free", "buff", "cached", "stotal", "sfree")
def memfree(): def memfree():
''' Returns kilobytes memory [total, free, buff, cached, stotal, sfree]. """ Returns kilobytes memory [total, free, buff, cached, stotal, sfree].
useful stats are: useful stats are:
free memory = free + buff + cached free memory = free + buff + cached
swap used = stotal - sfree swap used = stotal - sfree
''' """
f = open('/proc/meminfo', 'r') f = open("/proc/meminfo", "r")
lines = f.readlines() lines = f.readlines()
f.close() f.close()
kbs = {} kbs = {}
@ -55,20 +54,21 @@ def memfree():
kbs[k] = 0 kbs[k] = 0
for l in lines: for l in lines:
if l[:9] == "MemTotal:": if l[:9] == "MemTotal:":
kbs['total'] = int(l.split()[1]) kbs["total"] = int(l.split()[1])
elif l[:8] == "MemFree:": elif l[:8] == "MemFree:":
kbs['free'] = int(l.split()[1]) kbs["free"] = int(l.split()[1])
elif l[:8] == "Buffers:": elif l[:8] == "Buffers:":
kbs['buff'] = int(l.split()[1]) kbs["buff"] = int(l.split()[1])
elif l[:8] == "Cached:": elif l[:8] == "Cached:":
kbs['cache'] = int(l.split()[1]) kbs["cache"] = int(l.split()[1])
elif l[:10] == "SwapTotal:": elif l[:10] == "SwapTotal:":
kbs['stotal'] = int(l.split()[1]) kbs["stotal"] = int(l.split()[1])
elif l[:9] == "SwapFree:": elif l[:9] == "SwapFree:":
kbs['sfree'] = int(l.split()[1]) kbs["sfree"] = int(l.split()[1])
break break
return kbs return kbs
# node list (count from 1) # node list (count from 1)
nodelist = [None] nodelist = [None]
switchlist = [] switchlist = []
@ -76,30 +76,30 @@ switchlist = []
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(waittime = 0.2, numnodes = 0, bridges = 0, retries = 0, parser.set_defaults(waittime=0.2, numnodes=0, bridges=0, retries=0,
logfile = None, services = None) logfile=None, services=None)
parser.add_option("-w", "--waittime", dest = "waittime", type = float, parser.add_option("-w", "--waittime", dest="waittime", type=float,
help = "number of seconds to wait between node creation" \ help="number of seconds to wait between node creation" \
" (default = %s)" % parser.defaults["waittime"]) " (default = %s)" % parser.defaults["waittime"])
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, parser.add_option("-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes (default = unlimited)") help="number of nodes (default = unlimited)")
parser.add_option("-b", "--bridges", dest = "bridges", type = int, parser.add_option("-b", "--bridges", dest="bridges", type=int,
help = "number of nodes per bridge; 0 = one bridge " \ help="number of nodes per bridge; 0 = one bridge " \
"(def. = %s)" % parser.defaults["bridges"]) "(def. = %s)" % parser.defaults["bridges"])
parser.add_option("-r", "--retry", dest = "retries", type = int, parser.add_option("-r", "--retry", dest="retries", type=int,
help = "number of retries on error (default = %s)" % \ help="number of retries on error (default = %s)" % \
parser.defaults["retries"]) parser.defaults["retries"])
parser.add_option("-l", "--log", dest = "logfile", type = str, parser.add_option("-l", "--log", dest="logfile", type=str,
help = "log memory usage to this file (default = %s)" % \ help="log memory usage to this file (default = %s)" % \
parser.defaults["logfile"]) parser.defaults["logfile"])
parser.add_option("-s", "--services", dest = "services", type = str, parser.add_option("-s", "--services", dest="services", type=str,
help = "pipe-delimited list of services added to each " \ help="pipe-delimited list of services added to each "
"node (default = %s)\n(Example: 'zebra|OSPFv2|OSPFv3|" \ "node (default = %s)\n(Example: zebra|OSPFv2|OSPFv3|"
"IPForward')" % parser.defaults["services"]) "IPForward)" % parser.defaults["services"])
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -109,16 +109,16 @@ def main():
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
for a in args: for a in args:
sys.stderr.write("ignoring command line argument: '%s'\n" % a) sys.stderr.write("ignoring command line argument: %s\n" % a)
start = datetime.datetime.now() start = datetime.datetime.now()
prefix = ipaddr.IPv4Prefix("10.83.0.0/16") prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
print "Testing how many network namespace nodes this machine can create." print "Testing how many network namespace nodes this machine can create."
print " - %s" % linuxversion() print " - %s" % linuxversion()
mem = memfree() mem = memfree()
print " - %.02f GB total memory (%.02f GB swap)" % \ print " - %.02f GB total memory (%.02f GB swap)" % \
(mem['total']/GBD, mem['stotal']/GBD) (mem["total"] / GBD, mem["stotal"] / GBD)
print " - using IPv4 network prefix %s" % prefix print " - using IPv4 network prefix %s" % prefix
print " - using wait time of %s" % options.waittime print " - using wait time of %s" % options.waittime
print " - using %d nodes per bridge" % options.bridges print " - using %d nodes per bridge" % options.bridges
@ -132,11 +132,11 @@ def main():
lfp = open(options.logfile, "a") lfp = open(options.logfile, "a")
lfp.write("# log from howmanynodes.py %s\n" % time.ctime()) lfp.write("# log from howmanynodes.py %s\n" % time.ctime())
lfp.write("# options = %s\n#\n" % options) lfp.write("# options = %s\n#\n" % options)
lfp.write("# numnodes,%s\n" % ','.join(MEMKEYS)) lfp.write("# numnodes,%s\n" % ",".join(MEMKEYS))
lfp.flush() lfp.flush()
session = pycore.Session(persistent=True) session = Session(1, persistent=True)
switch = session.addobj(cls = pycore.nodes.SwitchNode) switch = session.add_object(cls=nodes.SwitchNode)
switchlist.append(switch) switchlist.append(switch)
print "Added bridge %s (%d)." % (switch.brname, len(switchlist)) print "Added bridge %s (%d)." % (switch.brname, len(switchlist))
@ -147,33 +147,32 @@ def main():
# optionally add a bridge (options.bridges nodes per bridge) # optionally add a bridge (options.bridges nodes per bridge)
try: try:
if options.bridges > 0 and switch.numnetif() >= options.bridges: if options.bridges > 0 and switch.numnetif() >= options.bridges:
switch = session.addobj(cls = pycore.nodes.SwitchNode) switch = session.add_object(cls=nodes.SwitchNode)
switchlist.append(switch) switchlist.append(switch)
print "\nAdded bridge %s (%d) for node %d." % \ print "\nAdded bridge %s (%d) for node %d." % \
(switch.brname, len(switchlist), i) (switch.brname, len(switchlist), i)
except Exception, e: except Exception, e:
print "At %d bridges (%d nodes) caught exception:\n%s\n" % \ print "At %d bridges (%d nodes) caught exception:\n%s\n" % \
(len(switchlist), i-1, e) (len(switchlist), i - 1, e)
break break
# create a node # create a node
try: try:
n = session.addobj(cls = pycore.nodes.LxcNode, name = "n%d" % i) n = session.add_object(cls=nodes.LxcNode, name="n%d" % i)
n.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) n.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
n.cmd([SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) n.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"])
if options.services is not None: if options.services is not None:
session.services.addservicestonode(n, "", options.services, session.services.addservicestonode(n, "", options.services)
verbose=False)
n.boot() n.boot()
nodelist.append(n) nodelist.append(n)
if i % 25 == 0: if i % 25 == 0:
print "\n%s nodes created " % i, print "\n%s nodes created " % i,
mem = memfree() mem = memfree()
free = mem['free'] + mem['buff'] + mem['cached'] free = mem["free"] + mem["buff"] + mem["cached"]
swap = mem['stotal'] - mem['sfree'] swap = mem["stotal"] - mem["sfree"]
print "(%.02f/%.02f GB free/swap)" % (free/GBD , swap/GBD), print "(%.02f/%.02f GB free/swap)" % (free / GBD, swap / GBD),
if lfp: if lfp:
lfp.write("%d," % i) lfp.write("%d," % i)
lfp.write("%s\n" % ','.join(str(mem[x]) for x in MEMKEYS)) lfp.write("%s\n" % ",".join(str(mem[x]) for x in MEMKEYS))
lfp.flush() lfp.flush()
else: else:
sys.stdout.write(".") sys.stdout.write(".")
@ -183,7 +182,7 @@ def main():
print "At %d nodes caught exception:\n" % i, e print "At %d nodes caught exception:\n" % i, e
if retry_count > 0: if retry_count > 0:
print "\nWill retry creating node %d." % i print "\nWill retry creating node %d." % i
shutil.rmtree(n.nodedir, ignore_errors = True) shutil.rmtree(n.nodedir, ignore_errors=True)
retry_count -= 1 retry_count -= 1
i -= 1 i -= 1
time.sleep(options.waittime) time.sleep(options.waittime)
@ -205,5 +204,10 @@ def main():
print "elapsed time: %s" % (datetime.datetime.now() - start) print "elapsed time: %s" % (datetime.datetime.now() - start)
print "Use the core-cleanup script to remove nodes and bridges." print "Use the core-cleanup script to remove nodes and bridges."
if __name__ == "__main__": if __name__ == "__main__":
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main() main()

View file

@ -14,24 +14,29 @@
# Use core-cleanup to clean up after this script as the session is left running. # Use core-cleanup to clean up after this script as the session is left running.
# #
import sys, datetime, optparse import datetime
import optparse
import sys
from core import pycore from core import constants
from core.misc import ipaddr from core.misc import ipaddress, nodeutils, nodemaps
from core.constants import * from core.netns import nodes
# node list (count from 1) # node list (count from 1)
from core.session import Session
n = [None] n = [None]
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes = 5) parser.set_defaults(numnodes=5)
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, parser.add_option("-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes") help="number of nodes")
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -52,48 +57,51 @@ def main():
start = datetime.datetime.now() start = datetime.datetime.now()
session = pycore.Session(persistent=True) session = Session(1, persistent=True)
if 'server' in globals(): if 'server' in globals():
server.addsession(session) server.addsession(session)
print "creating %d nodes" % options.numnodes print "creating %d nodes" % options.numnodes
left = None left = None
prefix = None prefix = None
for i in xrange(1, options.numnodes + 1): for i in xrange(1, options.numnodes + 1):
tmp = session.addobj(cls = pycore.nodes.CoreNode, name = "n%d" % i, tmp = session.add_object(cls=nodes.CoreNode, name="n%d" % i, objid=i)
objid=i)
if left: if left:
tmp.newnetif(left, ["%s/%s" % (prefix.addr(2), prefix.prefixlen)]) tmp.newnetif(left, ["%s/%s" % (prefix.addr(2), prefix.prefixlen)])
prefix = ipaddr.IPv4Prefix("10.83.%d.0/24" % i) # limit: i < 255 # limit: i < 255
right = session.addobj(cls = pycore.nodes.PtpNet) prefix = ipaddress.Ipv4Prefix("10.83.%d.0/24" % i)
right = session.add_object(cls=nodes.PtpNet)
tmp.newnetif(right, ["%s/%s" % (prefix.addr(1), prefix.prefixlen)]) tmp.newnetif(right, ["%s/%s" % (prefix.addr(1), prefix.prefixlen)])
tmp.cmd([SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) tmp.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"])
tmp.cmd([SYSCTL_BIN, "net.ipv4.conf.all.forwarding=1"]) tmp.cmd([constants.SYSCTL_BIN, "net.ipv4.conf.all.forwarding=1"])
tmp.cmd([SYSCTL_BIN, "net.ipv4.conf.default.rp_filter=0"]) tmp.cmd([constants.SYSCTL_BIN, "net.ipv4.conf.default.rp_filter=0"])
tmp.setposition(x=100*i,y=150) tmp.setposition(x=100 * i, y=150)
n.append(tmp) n.append(tmp)
left = right left = right
prefixes = map(lambda(x): ipaddr.IPv4Prefix("10.83.%d.0/24" % x), prefixes = map(lambda (x): ipaddress.Ipv4Prefix("10.83.%d.0/24" % x),
xrange(1, options.numnodes + 1)) xrange(1, options.numnodes + 1))
# set up static routing in the chain # set up static routing in the chain
for i in xrange(1, options.numnodes + 1): for i in xrange(1, options.numnodes + 1):
for j in xrange(1, options.numnodes + 1): for j in xrange(1, options.numnodes + 1):
if j < i - 1: if j < i - 1:
gw = prefixes[i-2].addr(1) gw = prefixes[i - 2].addr(1)
elif j > i: elif j > i:
if i > len(prefixes) - 1: if i > len(prefixes) - 1:
continue continue
gw = prefixes[i-1].addr(2) gw = prefixes[i - 1].addr(2)
else: else:
continue continue
net = prefixes[j-1] net = prefixes[j - 1]
n[i].cmd([IP_BIN, "route", "add", str(net), "via", str(gw)]) n[i].cmd([constants.IP_BIN, "route", "add", str(net), "via", str(gw)])
print "elapsed time: %s" % (datetime.datetime.now() - start) print "elapsed time: %s" % (datetime.datetime.now() - start)
if __name__ == "__main__" or __name__ == "__builtin__":
main()
if __name__ == "__main__" or __name__ == "__builtin__":
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main()

View file

@ -7,26 +7,22 @@
# that all neighbor states are either full or two-way, and check the routes # that all neighbor states are either full or two-way, and check the routes
# in zebra vs those installed in the kernel. # in zebra vs those installed in the kernel.
import os, sys, random, time, optparse, datetime import datetime
import optparse
import os
import random
import sys
import time
from string import Template from string import Template
try:
from core import pycore
except ImportError:
# hack for Fedora autoconf that uses the following pythondir:
if "/usr/lib/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.6/site-packages")
if "/usr/lib64/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.6/site-packages")
if "/usr/lib/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.7/site-packages")
if "/usr/lib64/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.7/site-packages")
from core import pycore
from core.misc import ipaddr
from core.misc.utils import mutecall
from core.constants import QUAGGA_STATE_DIR from core.constants import QUAGGA_STATE_DIR
from core.misc import ipaddress, nodeutils, nodemaps
from core.misc.utils import mutecall
from core.netns import nodes
# this is the /etc/core/core.conf default # this is the /etc/core/core.conf default
from core.session import Session
quagga_sbin_search = ("/usr/local/sbin", "/usr/sbin", "/usr/lib/quagga") quagga_sbin_search = ("/usr/local/sbin", "/usr/sbin", "/usr/lib/quagga")
quagga_path = "zebra" quagga_path = "zebra"
@ -42,7 +38,8 @@ except OSError:
sys.stderr.write("ERROR: running zebra failed\n") sys.stderr.write("ERROR: running zebra failed\n")
sys.exit(1) sys.exit(1)
class ManetNode(pycore.nodes.LxcNode):
class ManetNode(nodes.LxcNode):
""" An Lxc namespace node configured for Quagga OSPFv3 MANET MDR """ An Lxc namespace node configured for Quagga OSPFv3 MANET MDR
""" """
conftemp = Template("""\ conftemp = Template("""\
@ -66,19 +63,19 @@ ip forwarding
confdir = "/usr/local/etc/quagga" confdir = "/usr/local/etc/quagga"
def __init__(self, core, ipaddr, routerid = None, def __init__(self, core, ipaddr, routerid=None,
objid = None, name = None, nodedir = None): objid=None, name=None, nodedir=None):
if routerid is None: if routerid is None:
routerid = ipaddr.split("/")[0] routerid = ipaddr.split("/")[0]
self.ipaddr = ipaddr self.ipaddr = ipaddr
self.routerid = routerid self.routerid = routerid
pycore.nodes.LxcNode.__init__(self, core, objid, name, nodedir) nodes.LxcNode.__init__(self, core, objid, name, nodedir)
self.privatedir(self.confdir) self.privatedir(self.confdir)
self.privatedir(QUAGGA_STATE_DIR) self.privatedir(QUAGGA_STATE_DIR)
def qconf(self): def qconf(self):
return self.conftemp.substitute(ipaddr = self.ipaddr, return self.conftemp.substitute(ipaddr=self.ipaddr,
routerid = self.routerid) routerid=self.routerid)
def config(self): def config(self):
filename = os.path.join(self.confdir, "Quagga.conf") filename = os.path.join(self.confdir, "Quagga.conf")
@ -87,7 +84,7 @@ ip forwarding
f.close() f.close()
tmp = self.bootscript() tmp = self.bootscript()
if tmp: if tmp:
self.nodefile(self.bootsh, tmp, mode = 0755) self.nodefile(self.bootsh, tmp, mode=0755)
def boot(self): def boot(self):
self.config() self.config()
@ -125,11 +122,13 @@ waitfile $STATEDIR/ospf6d.vty
vtysh -b vtysh -b
""" % (QUAGGA_STATE_DIR, quagga_path, quagga_path) """ % (QUAGGA_STATE_DIR, quagga_path, quagga_path)
class Route(object): class Route(object):
""" Helper class for organzing routing table entries. """ """ Helper class for organzing routing table entries. """
def __init__(self, prefix = None, gw = None, metric = None):
def __init__(self, prefix=None, gw=None, metric=None):
try: try:
self.prefix = ipaddr.IPv4Prefix(prefix) self.prefix = ipaddress.Ipv4Prefix(prefix)
except Exception, e: except Exception, e:
raise ValueError, "Invalid prefix given to Route object: %s\n%s" % \ raise ValueError, "Invalid prefix given to Route object: %s\n%s" % \
(prefix, e) (prefix, e)
@ -156,6 +155,7 @@ class Route(object):
class ManetExperiment(object): class ManetExperiment(object):
""" A class for building an MDR network and checking and logging its state. """ A class for building an MDR network and checking and logging its state.
""" """
def __init__(self, options, start): def __init__(self, options, start):
""" Initialize with options and start time. """ """ Initialize with options and start time. """
self.session = None self.session = None
@ -234,19 +234,19 @@ class ManetExperiment(object):
msg += "%s" % rt msg += "%s" % rt
self.log(msg) self.log(msg)
def topology(self, numnodes, linkprob, verbose = False): def topology(self, numnodes, linkprob, verbose=False):
""" Build a topology consisting of the given number of ManetNodes """ Build a topology consisting of the given number of ManetNodes
connected to a WLAN and probabilty of links and set connected to a WLAN and probabilty of links and set
the session, WLAN, and node list objects. the session, WLAN, and node list objects.
""" """
# IP subnet # IP subnet
prefix = ipaddr.IPv4Prefix("10.14.0.0/16") prefix = ipaddress.Ipv4Prefix("10.14.0.0/16")
self.session = pycore.Session() self.session = Session(1)
# emulated network # emulated network
self.net = self.session.addobj(cls = pycore.nodes.WlanNode) self.net = self.session.add_object(cls=nodes.WlanNode)
for i in xrange(1, numnodes + 1): for i in xrange(1, numnodes + 1):
addr = "%s/%s" % (prefix.addr(i), 32) addr = "%s/%s" % (prefix.addr(i), 32)
tmp = self.session.addobj(cls = ManetNode, ipaddr = addr, objid= "%d" % i, name = "n%d" % i) tmp = self.session.add_object(cls=ManetNode, ipaddr=addr, objid="%d" % i, name="n%d" % i)
tmp.newnetif(self.net, [addr]) tmp.newnetif(self.net, [addr])
self.nodes.append(tmp) self.nodes.append(tmp)
# connect nodes with probability linkprob # connect nodes with probability linkprob
@ -353,9 +353,11 @@ class ManetExperiment(object):
self.comparelsdbs(lsdbs) self.comparelsdbs(lsdbs)
self.logdata(nbrs, mdrs, lsdbs, krs, zrs) self.logdata(nbrs, mdrs, lsdbs, krs, zrs)
class Cmd: class Cmd:
""" Helper class for running a command on a node and parsing the result. """ """ Helper class for running a command on a node and parsing the result. """
args = "" args = ""
def __init__(self, node, verbose=False): def __init__(self, node, verbose=False):
""" Initialize with a CoreNode (LxcNode) """ """ Initialize with a CoreNode (LxcNode) """
self.id = None self.id = None
@ -384,7 +386,7 @@ class Cmd:
def open(self): def open(self):
""" Exceute call to node.popen(). """ """ Exceute call to node.popen(). """
self.id, self.stdin, self.out, self.err = \ self.id, self.stdin, self.out, self.err = \
self.node.popen((self.args)) self.node.popen(self.args)
def parse(self): def parse(self):
""" This method is overloaded by child classes and should return some """ This method is overloaded by child classes and should return some
@ -401,18 +403,22 @@ class Cmd:
if tmp: if tmp:
self.warn("nonzero exit status:", tmp) self.warn("nonzero exit status:", tmp)
class VtyshCmd(Cmd): class VtyshCmd(Cmd):
""" Runs a vtysh command. """ """ Runs a vtysh command. """
def open(self): def open(self):
args = ("vtysh", "-c", self.args) args = ("vtysh", "-c", self.args)
self.id, self.stdin, self.out, self.err = self.node.popen((args)) self.id, self.stdin, self.out, self.err = self.node.popen(args)
class Ospf6NeighState(VtyshCmd): class Ospf6NeighState(VtyshCmd):
""" Check a node for OSPFv3 neighbors in the full/two-way states. """ """ Check a node for OSPFv3 neighbors in the full/two-way states. """
args = "show ipv6 ospf6 neighbor" args = "show ipv6 ospf6 neighbor"
def parse(self): def parse(self):
self.out.readline() # skip first line # skip first line
self.out.readline()
nbrlist = [] nbrlist = []
for line in self.out: for line in self.out:
field = line.split() field = line.split()
@ -428,6 +434,7 @@ class Ospf6NeighState(VtyshCmd):
self.info(" %s has %d neighbors" % (self.node.routerid, len(nbrlist))) self.info(" %s has %d neighbors" % (self.node.routerid, len(nbrlist)))
return nbrlist return nbrlist
class Ospf6MdrLevel(VtyshCmd): class Ospf6MdrLevel(VtyshCmd):
""" Retrieve the OSPFv3 MDR level for a node. """ """ Retrieve the OSPFv3 MDR level for a node. """
args = "show ipv6 ospf6 mdrlevel" args = "show ipv6 ospf6 mdrlevel"
@ -443,6 +450,7 @@ class Ospf6MdrLevel(VtyshCmd):
self.info(" %s is %s" % (self.node.routerid, mdrlevel)) self.info(" %s is %s" % (self.node.routerid, mdrlevel))
return mdrlevel return mdrlevel
class Ospf6Database(VtyshCmd): class Ospf6Database(VtyshCmd):
""" Retrieve the OSPFv3 LSDB summary for a node. """ """ Retrieve the OSPFv3 LSDB summary for a node. """
args = "show ipv6 ospf6 database" args = "show ipv6 ospf6 database"
@ -458,6 +466,7 @@ class Ospf6Database(VtyshCmd):
db += " ".join(filtered) + "\n" db += " ".join(filtered) + "\n"
return db return db
class ZebraRoutes(VtyshCmd): class ZebraRoutes(VtyshCmd):
""" Return a list of Route objects for a node based on its zebra """ Return a list of Route objects for a node based on its zebra
routing table. routing table.
@ -465,8 +474,9 @@ class ZebraRoutes(VtyshCmd):
args = "show ip route" args = "show ip route"
def parse(self): def parse(self):
for i in xrange(0,3): for i in xrange(0, 3):
self.out.readline() # skip first three lines # skip first three lines
self.out.readline()
r = [] r = []
prefix = None prefix = None
for line in self.out: for line in self.out:
@ -497,6 +507,7 @@ class ZebraRoutes(VtyshCmd):
self.info(" %s has %d zebra routes" % (self.node.routerid, len(r))) self.info(" %s has %d zebra routes" % (self.node.routerid, len(r)))
return r return r
class KernelRoutes(Cmd): class KernelRoutes(Cmd):
""" Return a list of Route objects for a node based on its kernel """ Return a list of Route objects for a node based on its kernel
routing table. routing table.
@ -521,7 +532,8 @@ class KernelRoutes(Cmd):
if field[1] == "proto": if field[1] == "proto":
# nexthop entry is on the next line # nexthop entry is on the next line
continue continue
gw = field[2] # nexthop IP or interface # nexthop IP or interface
gw = field[2]
r.append(Route(prefix, gw, metric)) r.append(Route(prefix, gw, metric))
prefix = None prefix = None
@ -531,25 +543,26 @@ class KernelRoutes(Cmd):
self.info(" %s has %d kernel routes" % (self.node.routerid, len(r))) self.info(" %s has %d kernel routes" % (self.node.routerid, len(r)))
return r return r
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes = 10, linkprob = 0.35, delay = 20, seed = None) parser.set_defaults(numnodes=10, linkprob=0.35, delay=20, seed=None)
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, parser.add_option("-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes") help="number of nodes")
parser.add_option("-p", "--linkprob", dest = "linkprob", type = float, parser.add_option("-p", "--linkprob", dest="linkprob", type=float,
help = "link probabilty") help="link probabilty")
parser.add_option("-d", "--delay", dest = "delay", type = float, parser.add_option("-d", "--delay", dest="delay", type=float,
help = "wait time before checking") help="wait time before checking")
parser.add_option("-s", "--seed", dest = "seed", type = int, parser.add_option("-s", "--seed", dest="seed", type=int,
help = "specify integer to use for random seed") help="specify integer to use for random seed")
parser.add_option("-v", "--verbose", dest = "verbose", parser.add_option("-v", "--verbose", dest="verbose",
action = "store_true", help = "be more verbose") action="store_true", help="be more verbose")
parser.add_option("-l", "--logfile", dest = "logfile", type = str, parser.add_option("-l", "--logfile", dest="logfile", type=str,
help = "log detailed output to the specified file") help="log detailed output to the specified file")
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -572,7 +585,7 @@ def main():
if options.seed: if options.seed:
random.seed(options.seed) random.seed(options.seed)
me = ManetExperiment(options = options, start=datetime.datetime.now()) me = ManetExperiment(options=options, start=datetime.datetime.now())
me.info("creating topology: numnodes = %s; linkprob = %s" % \ me.info("creating topology: numnodes = %s; linkprob = %s" % \
(options.numnodes, options.linkprob)) (options.numnodes, options.linkprob))
me.topology(options.numnodes, options.linkprob) me.topology(options.numnodes, options.linkprob)
@ -587,5 +600,10 @@ def main():
return me return me
if __name__ == "__main__": if __name__ == "__main__":
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
me = main() me = main()

View file

@ -5,24 +5,29 @@
# connect n nodes to a virtual switch/hub # connect n nodes to a virtual switch/hub
import sys, datetime, optparse import datetime
import optparse
import sys
from core import pycore from core import constants
from core.misc import ipaddr from core.misc import ipaddress, nodeutils, nodemaps
from core.constants import * from core.netns import nodes
# node list (count from 1) # node list (count from 1)
from core.session import Session
n = [None] n = [None]
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes = 5) parser.set_defaults(numnodes=5)
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, parser.add_option("-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes") help="number of nodes")
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -41,30 +46,35 @@ def main():
start = datetime.datetime.now() start = datetime.datetime.now()
# IP subnet # IP subnet
prefix = ipaddr.IPv4Prefix("10.83.0.0/16") prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
session = pycore.Session(persistent=True) session = Session(1, persistent=True)
if 'server' in globals(): if 'server' in globals():
server.addsession(session) server.addsession(session)
# emulated Ethernet switch # emulated Ethernet switch
switch = session.addobj(cls = pycore.nodes.SwitchNode, name = "switch") switch = session.add_object(cls=nodes.SwitchNode, name="switch")
switch.setposition(x=80,y=50) switch.setposition(x=80, y=50)
print "creating %d nodes with addresses from %s" % \ print "creating %d nodes with addresses from %s" % (options.numnodes, prefix)
(options.numnodes, prefix)
for i in xrange(1, options.numnodes + 1): for i in xrange(1, options.numnodes + 1):
tmp = session.addobj(cls = pycore.nodes.CoreNode, name = "n%d" % i, tmp = session.add_object(cls=nodes.CoreNode, name="n%d" % i, objid=i)
objid=i)
tmp.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) tmp.newnetif(switch, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
tmp.cmd([SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"]) tmp.cmd([constants.SYSCTL_BIN, "net.ipv4.icmp_echo_ignore_broadcasts=0"])
tmp.setposition(x=150*i,y=150) tmp.setposition(x=150 * i, y=150)
n.append(tmp) n.append(tmp)
session.node_count = str(options.numnodes + 1) session.node_count = str(options.numnodes + 1)
session.instantiate() session.instantiate()
print "elapsed time: %s" % (datetime.datetime.now() - start)
# start a shell on node 1 # start a shell on node 1
n[1].term("bash") n[1].term("bash")
print "elapsed time: %s" % (datetime.datetime.now() - start) raw_input("press enter to exit")
session.shutdown()
if __name__ == "__main__" or __name__ == "__builtin__": if __name__ == "__main__" or __name__ == "__builtin__":
main() # configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main()

View file

@ -8,11 +8,14 @@
# and repeat for minnodes <= n <= maxnodes with a step size of # and repeat for minnodes <= n <= maxnodes with a step size of
# nodestep # nodestep
import optparse, sys, os, datetime import datetime
import optparse
import sys
from core import pycore from core.misc import ipaddress, nodeutils, nodemaps
from core.misc import ipaddr
from core.misc.utils import mutecall from core.misc.utils import mutecall
from core.netns import nodes
from core.session import Session
try: try:
mutecall(["iperf", "-v"]) mutecall(["iperf", "-v"])
@ -20,48 +23,52 @@ except OSError:
sys.stderr.write("ERROR: running iperf failed\n") sys.stderr.write("ERROR: running iperf failed\n")
sys.exit(1) sys.exit(1)
def test(numnodes, testsec): def test(numnodes, testsec):
# node list # node list
n = [] n = []
# IP subnet # IP subnet
prefix = ipaddr.IPv4Prefix("10.83.0.0/16") prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
session = pycore.Session() session = Session(1)
# emulated network # emulated network
net = session.addobj(cls = pycore.nodes.SwitchNode) net = session.add_object(cls=nodes.SwitchNode)
for i in xrange(1, numnodes + 1): for i in xrange(1, numnodes + 1):
tmp = session.addobj(cls = pycore.nodes.LxcNode, name = "n%d" % i) tmp = session.add_object(cls=nodes.LxcNode, name="n%d" % i)
tmp.newnetif(net, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) tmp.newnetif(net, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
n.append(tmp) n.append(tmp)
n[0].cmd(["iperf", "-s", "-D"]) n[0].cmd(["iperf", "-s", "-D"])
n[-1].icmd(["iperf", "-t", str(int(testsec)), "-c", str(prefix.addr(1))]) n[-1].icmd(["iperf", "-t", str(int(testsec)), "-c", str(prefix.addr(1))])
n[0].cmd(["killall", "-9", "iperf"]) n[0].cmd(["killall", "-9", "iperf"])
raw_input("press enter to exit")
session.shutdown() session.shutdown()
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(minnodes = 2) parser.set_defaults(minnodes=2)
parser.add_option("-m", "--minnodes", dest = "minnodes", type = int, parser.add_option("-m", "--minnodes", dest="minnodes", type=int,
help = "min number of nodes to test; default = %s" % help="min number of nodes to test; default = %s" %
parser.defaults["minnodes"]) parser.defaults["minnodes"])
parser.set_defaults(maxnodes = 2) parser.set_defaults(maxnodes=2)
parser.add_option("-n", "--maxnodes", dest = "maxnodes", type = int, parser.add_option("-n", "--maxnodes", dest="maxnodes", type=int,
help = "max number of nodes to test; default = %s" % help="max number of nodes to test; default = %s" %
parser.defaults["maxnodes"]) parser.defaults["maxnodes"])
parser.set_defaults(testsec = 10) parser.set_defaults(testsec=10)
parser.add_option("-t", "--testsec", dest = "testsec", type = int, parser.add_option("-t", "--testsec", dest="testsec", type=int,
help = "test time in seconds; default = %s" % help="test time in seconds; default = %s" %
parser.defaults["testsec"]) parser.defaults["testsec"])
parser.set_defaults(nodestep = 1) parser.set_defaults(nodestep=1)
parser.add_option("-s", "--nodestep", dest = "nodestep", type = int, parser.add_option("-s", "--nodestep", dest="nodestep", type=int,
help = "number of nodes step size; default = %s" % help="number of nodes step size; default = %s" %
parser.defaults["nodestep"]) parser.defaults["nodestep"])
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -90,8 +97,12 @@ def main():
test(i, options.testsec) test(i, options.testsec)
print >> sys.stderr, "" print >> sys.stderr, ""
print >> sys.stderr, \ print >> sys.stderr, "elapsed time: %s" % (datetime.datetime.now() - start)
"elapsed time: %s" % (datetime.datetime.now() - start)
if __name__ == "__main__": if __name__ == "__main__":
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main() main()

View file

@ -5,7 +5,7 @@
# #
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com> # author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
# #
''' """
wlanemanetests.py - This script tests the performance of the WLAN device in wlanemanetests.py - This script tests the performance of the WLAN device in
CORE by measuring various metrics: CORE by measuring various metrics:
- delay experienced when pinging end-to-end - delay experienced when pinging end-to-end
@ -28,29 +28,22 @@ Various underlying network types are tested:
Results are printed/logged in CSV format. Results are printed/logged in CSV format.
''' """
import os, sys, time, optparse, datetime, math import datetime
from string import Template import math
try: import optparse
from core import pycore import os
except ImportError: import sys
# hack for Fedora autoconf that uses the following pythondir: import time
if "/usr/lib/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.6/site-packages") from core import emane
if "/usr/lib64/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.6/site-packages")
if "/usr/lib/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.7/site-packages")
if "/usr/lib64/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.7/site-packages")
from core import pycore
from core.misc import ipaddr
from core.misc.utils import mutecall
from core.constants import QUAGGA_STATE_DIR
from core.emane.emane import Emane
from core.emane.bypass import EmaneBypassModel from core.emane.bypass import EmaneBypassModel
from core.emane.nodes import EmaneNode
from core.emane.rfpipe import EmaneRfPipeModel from core.emane.rfpipe import EmaneRfPipeModel
from core.misc import ipaddress, nodemaps, nodeutils
from core.netns import nodes
from core.session import Session
try: try:
import emaneeventservice import emaneeventservice
@ -60,12 +53,12 @@ except Exception, e:
from emanesh.events import EventService from emanesh.events import EventService
from emanesh.events import PathlossEvent from emanesh.events import PathlossEvent
except Exception, e2: except Exception, e2:
raise ImportError, "failed to import EMANE Python bindings:\n%s\n%s" % \ raise ImportError("failed to import EMANE Python bindings:\n%s\n%s" % (e, e2))
(e, e2)
# global Experiment object (for interaction with 'python -i') # global Experiment object (for interaction with "python -i")
exp = None exp = None
# move these to core.misc.utils # move these to core.misc.utils
def readstat(): def readstat():
f = open("/proc/stat", "r") f = open("/proc/stat", "r")
@ -73,6 +66,7 @@ def readstat():
f.close() f.close()
return lines return lines
def numcpus(): def numcpus():
lines = readstat() lines = readstat()
n = 0 n = 0
@ -82,14 +76,16 @@ def numcpus():
n += 1 n += 1
return n return n
def getcputimes(line): def getcputimes(line):
# return (user, nice, sys, idle) from a /proc/stat cpu line # return (user, nice, sys, idle) from a /proc/stat cpu line
# assume columns are: # assume columns are:
# cpu# user nice sys idle iowait irq softirq steal guest (man 5 proc) # cpu# user nice sys idle iowait irq softirq steal guest (man 5 proc)
items = line.split() items = line.split()
(user, nice, sys, idle) = map(lambda(x): int(x), items[1:5]) (user, nice, sys, idle) = map(lambda (x): int(x), items[1:5])
return [user, nice, sys, idle] return [user, nice, sys, idle]
def calculatecpu(timesa, timesb): def calculatecpu(timesa, timesb):
for i in range(len(timesa)): for i in range(len(timesa)):
timesb[i] -= timesa[i] timesb[i] -= timesa[i]
@ -100,13 +96,15 @@ def calculatecpu(timesa, timesb):
# subtract % time spent in idle time # subtract % time spent in idle time
return 100 - ((100.0 * timesb[-1]) / total) return 100 - ((100.0 * timesb[-1]) / total)
# end move these to core.misc.utils # end move these to core.misc.utils
class Cmd(object): class Cmd(object):
''' Helper class for running a command on a node and parsing the result. ''' """ Helper class for running a command on a node and parsing the result. """
args = "" args = ""
def __init__(self, node, verbose=False): def __init__(self, node, verbose=False):
''' Initialize with a CoreNode (LxcNode) ''' """ Initialize with a CoreNode (LxcNode) """
self.id = None self.id = None
self.stdin = None self.stdin = None
self.out = None self.out = None
@ -114,17 +112,17 @@ class Cmd(object):
self.verbose = verbose self.verbose = verbose
def info(self, msg): def info(self, msg):
''' Utility method for writing output to stdout.''' """ Utility method for writing output to stdout."""
print msg print msg
sys.stdout.flush() sys.stdout.flush()
def warn(self, msg): def warn(self, msg):
''' Utility method for writing output to stderr. ''' """ Utility method for writing output to stderr. """
print >> sys.stderr, "XXX %s:" % self.node.name, msg print >> sys.stderr, "XXX %s:" % self.node.name, msg
sys.stderr.flush() sys.stderr.flush()
def run(self): def run(self):
''' This is the primary method used for running this command. ''' """ This is the primary method used for running this command. """
self.open() self.open()
status = self.id.wait() status = self.id.wait()
r = self.parse() r = self.parse()
@ -132,18 +130,17 @@ class Cmd(object):
return r return r
def open(self): def open(self):
''' Exceute call to node.popen(). ''' """ Exceute call to node.popen(). """
self.id, self.stdin, self.out, self.err = \ self.id, self.stdin, self.out, self.err = self.node.popen(self.args)
self.node.popen((self.args))
def parse(self): def parse(self):
''' This method is overloaded by child classes and should return some """ This method is overloaded by child classes and should return some
result. result.
''' """
return None return None
def cleanup(self): def cleanup(self):
''' Close the Popen channels.''' """ Close the Popen channels."""
self.stdin.close() self.stdin.close()
self.out.close() self.out.close()
self.err.close() self.err.close()
@ -153,69 +150,70 @@ class Cmd(object):
class ClientServerCmd(Cmd): class ClientServerCmd(Cmd):
''' Helper class for running a command on a node and parsing the result. ''' """ Helper class for running a command on a node and parsing the result. """
args = "" args = ""
client_args = "" client_args = ""
def __init__(self, node, client_node, verbose=False): def __init__(self, node, client_node, verbose=False):
''' Initialize with two CoreNodes, node is the server ''' """ Initialize with two CoreNodes, node is the server """
Cmd.__init__(self, node, verbose) Cmd.__init__(self, node, verbose)
self.client_node = client_node self.client_node = client_node
def run(self): def run(self):
''' Run the server command, then the client command, then """ Run the server command, then the client command, then
kill the server ''' kill the server """
self.open() # server self.open() # server
self.client_open() # client self.client_open() # client
status = self.client_id.wait() status = self.client_id.wait()
self.node.cmdresult(['killall', self.args[0]]) # stop the server # stop the server
self.node.cmdresult(["killall", self.args[0]])
r = self.parse() r = self.parse()
self.cleanup() self.cleanup()
return r return r
def client_open(self): def client_open(self):
''' Exceute call to client_node.popen(). ''' """ Exceute call to client_node.popen(). """
self.client_id, self.client_stdin, self.client_out, self.client_err = \ self.client_id, self.client_stdin, self.client_out, self.client_err = \
self.client_node.popen((self.client_args)) self.client_node.popen(self.client_args)
def parse(self): def parse(self):
''' This method is overloaded by child classes and should return some """ This method is overloaded by child classes and should return some
result. result.
''' """
return None return None
def cleanup(self): def cleanup(self):
''' Close the Popen channels.''' """ Close the Popen channels."""
self.stdin.close() self.stdin.close()
self.out.close() self.out.close()
self.err.close() self.err.close()
tmp = self.id.wait() tmp = self.id.wait()
if tmp: if tmp:
self.warn("nonzero exit status: %s" % tmp) self.warn("nonzero exit status: %s" % tmp)
self.warn("command was: %s" % ((self.args, ))) self.warn("command was: %s" % (self.args,))
class PingCmd(Cmd): class PingCmd(Cmd):
''' Test latency using ping. """ Test latency using ping.
''' """
def __init__(self, node, verbose=False, addr=None, count=50, interval=0.1, ): def __init__(self, node, verbose=False, addr=None, count=50, interval=0.1, ):
Cmd.__init__(self, node, verbose) Cmd.__init__(self, node, verbose)
self.addr = addr self.addr = addr
self.count = count self.count = count
self.interval = interval self.interval = interval
self.args = ['ping', '-q', '-c', '%s' % count, '-i', '%s' % interval, self.args = ["ping", "-q", "-c", "%s" % count, "-i", "%s" % interval, addr]
addr]
def run(self): def run(self):
if self.verbose: if self.verbose:
self.info("%s initial test ping (max 1 second)..." % self.node.name) self.info("%s initial test ping (max 1 second)..." % self.node.name)
(status, result) = self.node.cmdresult(["ping", "-q", "-c", "1", "-w", (status, result) = self.node.cmdresult(["ping", "-q", "-c", "1", "-w", "1", self.addr])
"1", self.addr])
if status != 0: if status != 0:
self.warn("initial ping from %s to %s failed! result:\n%s" % \ self.warn("initial ping from %s to %s failed! result:\n%s" %
(self.node.name, self.addr, result)) (self.node.name, self.addr, result))
return (0.0, 0.0) return 0.0, 0.0
if self.verbose: if self.verbose:
self.info("%s pinging %s (%d seconds)..." % \ self.info("%s pinging %s (%d seconds)..." %
(self.node.name, self.addr, self.count * self.interval)) (self.node.name, self.addr, self.count * self.interval))
return Cmd.run(self) return Cmd.run(self)
@ -224,17 +222,19 @@ class PingCmd(Cmd):
avg_latency = 0 avg_latency = 0
mdev = 0 mdev = 0
try: try:
stats_str = lines[-1].split('=')[1] stats_str = lines[-1].split("=")[1]
stats = stats_str.split('/') stats = stats_str.split("/")
avg_latency = float(stats[1]) avg_latency = float(stats[1])
mdev = float(stats[3].split(' ')[0]) mdev = float(stats[3].split(" ")[0])
except Exception, e: except Exception, e:
self.warn("ping parsing exception: %s" % e) self.warn("ping parsing exception: %s" % e)
return (avg_latency, mdev) return avg_latency, mdev
class IperfCmd(ClientServerCmd): class IperfCmd(ClientServerCmd):
''' Test throughput using iperf. """ Test throughput using iperf.
''' """
def __init__(self, node, client_node, verbose=False, addr=None, time=10): def __init__(self, node, client_node, verbose=False, addr=None, time=10):
# node is the server # node is the server
ClientServerCmd.__init__(self, node, client_node, verbose) ClientServerCmd.__init__(self, node, client_node, verbose)
@ -254,35 +254,37 @@ class IperfCmd(ClientServerCmd):
def parse(self): def parse(self):
lines = self.out.readlines() lines = self.out.readlines()
try: try:
bps = int(lines[-1].split(',')[-1].strip('\n')) bps = int(lines[-1].split(",")[-1].strip("\n"))
except Exception, e: except Exception, e:
self.warn("iperf parsing exception: %s" % e) self.warn("iperf parsing exception: %s" % e)
bps = 0 bps = 0
return bps return bps
class MgenCmd(ClientServerCmd): class MgenCmd(ClientServerCmd):
''' Run a test traffic flow using an MGEN sender and receiver. """ Run a test traffic flow using an MGEN sender and receiver.
''' """
def __init__(self, node, client_node, verbose=False, addr=None, time=10, def __init__(self, node, client_node, verbose=False, addr=None, time=10,
rate=512): rate=512):
ClientServerCmd.__init__(self, node, client_node, verbose) ClientServerCmd.__init__(self, node, client_node, verbose)
self.addr = addr self.addr = addr
self.time = time self.time = time
self.args = ['mgen', 'event', 'listen udp 5000', 'output', self.args = ["mgen", "event", "listen udp 5000", "output",
'/var/log/mgen.log'] "/var/log/mgen.log"]
self.rate = rate self.rate = rate
sendevent = "ON 1 UDP DST %s/5000 PERIODIC [%s]" % \ sendevent = "ON 1 UDP DST %s/5000 PERIODIC [%s]" % \
(addr, self.mgenrate(self.rate)) (addr, self.mgenrate(self.rate))
stopevent = "%s OFF 1" % time stopevent = "%s OFF 1" % time
self.client_args = ['mgen', 'event', sendevent, 'event', stopevent, self.client_args = ["mgen", "event", sendevent, "event", stopevent,
'output', '/var/log/mgen.log'] "output", "/var/log/mgen.log"]
@staticmethod @staticmethod
def mgenrate(kbps): def mgenrate(kbps):
''' Return a MGEN periodic rate string for the given kilobits-per-sec. """ Return a MGEN periodic rate string for the given kilobits-per-sec.
Assume 1500 byte MTU, 20-byte IP + 8-byte UDP headers, leaving Assume 1500 byte MTU, 20-byte IP + 8-byte UDP headers, leaving
1472 bytes for data. 1472 bytes for data.
''' """
bps = (kbps / 8) * 1000.0 bps = (kbps / 8) * 1000.0
maxdata = 1472 maxdata = 1472
pps = math.ceil(bps / maxdata) pps = math.ceil(bps / maxdata)
@ -296,26 +298,27 @@ class MgenCmd(ClientServerCmd):
return ClientServerCmd.run(self) return ClientServerCmd.run(self)
def cleanup(self): def cleanup(self):
''' Close the Popen channels.''' """ Close the Popen channels."""
self.stdin.close() self.stdin.close()
self.out.close() self.out.close()
self.err.close() self.err.close()
tmp = self.id.wait() # non-zero mgen exit status OK # non-zero mgen exit status OK
tmp = self.id.wait()
def parse(self): def parse(self):
''' Check MGEN receiver's log file for packet sequence numbers, and """ Check MGEN receiver"s log file for packet sequence numbers, and
return the percentage of lost packets. return the percentage of lost packets.
''' """
logfile = os.path.join(self.node.nodedir, 'var.log/mgen.log') logfile = os.path.join(self.node.nodedir, "var.log/mgen.log")
f = open(logfile, 'r') f = open(logfile, "r")
numlost = 0 numlost = 0
lastseq = 0 lastseq = 0
for line in f.readlines(): for line in f.readlines():
fields = line.split() fields = line.split()
if fields[1] != 'RECV': if fields[1] != "RECV":
continue continue
try: try:
seq = int(fields[4].split('>')[1]) seq = int(fields[4].split(">")[1])
except: except:
self.info("Unexpected MGEN line:\n%s" % fields) self.info("Unexpected MGEN line:\n%s" % fields)
if seq > (lastseq + 1): if seq > (lastseq + 1):
@ -333,10 +336,11 @@ class MgenCmd(ClientServerCmd):
class Experiment(object): class Experiment(object):
''' Experiment object to organize tests. """ Experiment object to organize tests.
''' """
def __init__(self, opt, start): def __init__(self, opt, start):
''' Initialize with opt and start time. ''' """ Initialize with opt and start time. """
self.session = None self.session = None
# node list # node list
self.nodes = [] self.nodes = []
@ -352,19 +356,19 @@ class Experiment(object):
self.logbegin() self.logbegin()
def info(self, msg): def info(self, msg):
''' Utility method for writing output to stdout. ''' """ Utility method for writing output to stdout. """
print msg print msg
sys.stdout.flush() sys.stdout.flush()
self.log(msg) self.log(msg)
def warn(self, msg): def warn(self, msg):
''' Utility method for writing output to stderr. ''' """ Utility method for writing output to stderr. """
print >> sys.stderr, msg print >> sys.stderr, msg
sys.stderr.flush() sys.stderr.flush()
self.log(msg) self.log(msg)
def logbegin(self): def logbegin(self):
''' Start logging. ''' """ Start logging. """
self.logfp = None self.logfp = None
if not self.opt.logfile: if not self.opt.logfile:
return return
@ -375,7 +379,7 @@ class Experiment(object):
self.log("%s %s %s %s on %s" % (sysname, rel, ver, machine, nodename)) self.log("%s %s %s %s on %s" % (sysname, rel, ver, machine, nodename))
def logend(self): def logend(self):
''' End logging. ''' """ End logging. """
if not self.logfp: if not self.logfp:
return return
end = datetime.datetime.now() end = datetime.datetime.now()
@ -386,14 +390,14 @@ class Experiment(object):
self.logfp = None self.logfp = None
def log(self, msg): def log(self, msg):
''' Write to the log file, if any. ''' """ Write to the log file, if any. """
if not self.logfp: if not self.logfp:
return return
print >> self.logfp, msg print >> self.logfp, msg
def reset(self): def reset(self):
''' Prepare for another experiment run. """ Prepare for another experiment run.
''' """
if self.session: if self.session:
self.session.shutdown() self.session.shutdown()
del self.session del self.session
@ -401,25 +405,22 @@ class Experiment(object):
self.nodes = [] self.nodes = []
self.net = None self.net = None
def createbridgedsession(self, numnodes, verbose = False): def createbridgedsession(self, numnodes, verbose=False):
''' Build a topology consisting of the given number of LxcNodes """ Build a topology consisting of the given number of LxcNodes
connected to a WLAN. connected to a WLAN.
''' """
# IP subnet # IP subnet
prefix = ipaddr.IPv4Prefix("10.0.0.0/16") prefix = ipaddress.Ipv4Prefix("10.0.0.0/16")
self.session = pycore.Session() self.session = Session(1)
# emulated network # emulated network
self.net = self.session.addobj(cls = pycore.nodes.WlanNode, self.net = self.session.add_object(cls=nodes.WlanNode, name="wlan1")
name = "wlan1")
prev = None prev = None
for i in xrange(1, numnodes + 1): for i in xrange(1, numnodes + 1):
addr = "%s/%s" % (prefix.addr(i), 32) addr = "%s/%s" % (prefix.addr(i), 32)
tmp = self.session.addobj(cls = pycore.nodes.CoreNode, objid = i, tmp = self.session.add_object(cls=nodes.CoreNode, objid=i, name="n%d" % i)
name = "n%d" % i)
tmp.newnetif(self.net, [addr]) tmp.newnetif(self.net, [addr])
self.nodes.append(tmp) self.nodes.append(tmp)
self.session.services.addservicestonode(tmp, "router", self.session.services.addservicestonode(tmp, "router", "IPForward")
"IPForward", self.verbose)
self.session.services.bootnodeservices(tmp) self.session.services.bootnodeservices(tmp)
self.staticroutes(i, prefix, numnodes) self.staticroutes(i, prefix, numnodes)
@ -428,62 +429,58 @@ class Experiment(object):
self.net.link(prev.netif(0), tmp.netif(0)) self.net.link(prev.netif(0), tmp.netif(0))
prev = tmp prev = tmp
def createemanesession(self, numnodes, verbose = False, cls = None, def createemanesession(self, numnodes, verbose=False, cls=None,
values = None): values=None):
''' Build a topology consisting of the given number of LxcNodes """ Build a topology consisting of the given number of LxcNodes
connected to an EMANE WLAN. connected to an EMANE WLAN.
''' """
prefix = ipaddr.IPv4Prefix("10.0.0.0/16") prefix = ipaddress.Ipv4Prefix("10.0.0.0/16")
self.session = pycore.Session() self.session = Session(2)
self.session.node_count = str(numnodes + 1) self.session.node_count = str(numnodes + 1)
self.session.master = True self.session.master = True
self.session.location.setrefgeo(47.57917,-122.13232,2.00000) self.session.location.setrefgeo(47.57917, -122.13232, 2.00000)
self.session.location.refscale = 150.0 self.session.location.refscale = 150.0
self.session.cfg['emane_models'] = "RfPipe, Ieee80211abg, Bypass" self.session.config["emane_models"] = "RfPipe, Ieee80211abg, Bypass"
self.session.emane.loadmodels() self.session.emane.loadmodels()
self.net = self.session.addobj(cls = pycore.nodes.EmaneNode, self.net = self.session.add_object(cls=EmaneNode, objid=numnodes + 1, name="wlan1")
objid = numnodes + 1, name = "wlan1")
self.net.verbose = verbose self.net.verbose = verbose
#self.session.emane.addobj(self.net) # self.session.emane.addobj(self.net)
for i in xrange(1, numnodes + 1): for i in xrange(1, numnodes + 1):
addr = "%s/%s" % (prefix.addr(i), 32) addr = "%s/%s" % (prefix.addr(i), 32)
tmp = self.session.addobj(cls = pycore.nodes.CoreNode, objid = i, tmp = self.session.add_object(cls=nodes.CoreNode, objid=i,
name = "n%d" % i) name="n%d" % i)
#tmp.setposition(i * 20, 50, None) # tmp.setposition(i * 20, 50, None)
tmp.setposition(50, 50, None) tmp.setposition(50, 50, None)
tmp.newnetif(self.net, [addr]) tmp.newnetif(self.net, [addr])
self.nodes.append(tmp) self.nodes.append(tmp)
self.session.services.addservicestonode(tmp, "router", self.session.services.addservicestonode(tmp, "router", "IPForward")
"IPForward", self.verbose)
if values is None: if values is None:
values = cls.getdefaultvalues() values = cls.getdefaultvalues()
self.session.emane.setconfig(self.net.objid, cls._name, values) self.session.emane.setconfig(self.net.objid, cls.name, values)
self.session.instantiate() self.session.instantiate()
self.info("waiting %s sec (TAP bring-up)" % 2) self.info("waiting %s sec (TAP bring-up)" % 2)
time.sleep(2) time.sleep(2)
for i in xrange(1, numnodes + 1): for i in xrange(1, numnodes + 1):
tmp = self.nodes[i-1] tmp = self.nodes[i - 1]
self.session.services.bootnodeservices(tmp) self.session.services.bootnodeservices(tmp)
self.staticroutes(i, prefix, numnodes) self.staticroutes(i, prefix, numnodes)
def setnodes(self): def setnodes(self):
''' Set the sender and receiver nodes for use in this experiment, """ Set the sender and receiver nodes for use in this experiment,
along with the address of the receiver to be used. along with the address of the receiver to be used.
''' """
self.firstnode = self.nodes[0] self.firstnode = self.nodes[0]
self.lastnode = self.nodes[-1] self.lastnode = self.nodes[-1]
self.lastaddr = self.lastnode.netif(0).addrlist[0].split('/')[0] self.lastaddr = self.lastnode.netif(0).addrlist[0].split("/")[0]
def staticroutes(self, i, prefix, numnodes): def staticroutes(self, i, prefix, numnodes):
''' Add static routes on node number i to the other nodes in the chain. """ Add static routes on node number i to the other nodes in the chain.
''' """
routecmd = ["/sbin/ip", "route", "add"] routecmd = ["/sbin/ip", "route", "add"]
node = self.nodes[i-1] node = self.nodes[i - 1]
neigh_left = "" neigh_left = ""
neigh_right = "" neigh_right = ""
# add direct interface routes first # add direct interface routes first
@ -515,17 +512,17 @@ class Experiment(object):
self.warn("failed to add route: %s" % cmd) self.warn("failed to add route: %s" % cmd)
def setpathloss(self, numnodes): def setpathloss(self, numnodes):
''' Send EMANE pathloss events to connect all NEMs in a chain. """ Send EMANE pathloss events to connect all NEMs in a chain.
''' """
if self.session.emane.version < self.session.emane.EMANE091: if self.session.emane.version < self.session.emane.EMANE091:
service = emaneeventservice.EventService() service = emaneeventservice.EventService()
e = emaneeventpathloss.EventPathloss(1) e = emaneeventpathloss.EventPathloss(1)
old = True old = True
else: else:
if self.session.emane.version == self.session.emane.EMANE091: if self.session.emane.version == self.session.emane.EMANE091:
dev = 'lo' dev = "lo"
else: else:
dev = self.session.obj('ctrlnet').brname dev = self.session.obj("ctrlnet").brname
service = EventService(eventchannel=("224.1.2.8", 45703, dev), service = EventService(eventchannel=("224.1.2.8", 45703, dev),
otachannel=None) otachannel=None)
old = False old = False
@ -558,26 +555,26 @@ class Experiment(object):
e.append(txnem, forward=10.0, reverse=10.0) e.append(txnem, forward=10.0, reverse=10.0)
service.publish(rxnem, e) service.publish(rxnem, e)
def setneteffects(self, bw = None, delay = None): def setneteffects(self, bw=None, delay=None):
''' Set link effects for all interfaces attached to the network node. """ Set link effects for all interfaces attached to the network node.
''' """
if not self.net: if not self.net:
self.warn("failed to set effects: no network node") self.warn("failed to set effects: no network node")
return return
for netif in self.net.netifs(): for netif in self.net.netifs():
self.net.linkconfig(netif, bw = bw, delay = delay) self.net.linkconfig(netif, bw=bw, delay=delay)
def runalltests(self, title=""): def runalltests(self, title=""):
''' Convenience helper to run all defined experiment tests. """ Convenience helper to run all defined experiment tests.
If tests are run multiple times, this returns the average of If tests are run multiple times, this returns the average of
those runs. those runs.
''' """
duration = self.opt.duration duration = self.opt.duration
rate = self.opt.rate rate = self.opt.rate
if len(title) > 0: if len(title) > 0:
self.info("----- running %s tests (duration=%s, rate=%s) -----" % \ self.info("----- running %s tests (duration=%s, rate=%s) -----" % \
(title, duration, rate)) (title, duration, rate))
(latency, mdev, throughput, cpu, loss) = (0,0,0,0,0) (latency, mdev, throughput, cpu, loss) = (0, 0, 0, 0, 0)
self.info("number of runs: ping=%d, iperf=%d, mgen=%d" % \ self.info("number of runs: ping=%d, iperf=%d, mgen=%d" % \
(self.numping, self.numiperf, self.nummgen)) (self.numping, self.numiperf, self.nummgen))
@ -591,7 +588,8 @@ class Experiment(object):
throughput = self.iperftest(time=duration) throughput = self.iperftest(time=duration)
if self.numiperf > 1: if self.numiperf > 1:
throughputs += throughput throughputs += throughput
time.sleep(1) # iperf is very CPU intensive # iperf is very CPU intensive
time.sleep(1)
if self.numiperf > 1: if self.numiperf > 1:
throughput = sum(throughputs) / len(throughputs) throughput = sum(throughputs) / len(throughputs)
self.info("throughputs=%s" % ["%.2f" % v for v in throughputs]) self.info("throughputs=%s" % ["%.2f" % v for v in throughputs])
@ -610,30 +608,30 @@ class Experiment(object):
self.info("cpus=%s" % ["%.2f" % v for v in cpus]) self.info("cpus=%s" % ["%.2f" % v for v in cpus])
self.info("losses=%s" % ["%.2f" % v for v in losses]) self.info("losses=%s" % ["%.2f" % v for v in losses])
return (latency, mdev, throughput, cpu, loss) return latency, mdev, throughput, cpu, loss
def pingtest(self, count=50): def pingtest(self, count=50):
''' Ping through a chain of nodes and report the average latency. """ Ping through a chain of nodes and report the average latency.
''' """
p = PingCmd(node=self.firstnode, verbose=self.verbose, p = PingCmd(node=self.firstnode, verbose=self.verbose,
addr = self.lastaddr, count=count, interval=0.1).run() addr=self.lastaddr, count=count, interval=0.1).run()
(latency, mdev) = p (latency, mdev) = p
self.info("latency (ms): %.03f, %.03f" % (latency, mdev)) self.info("latency (ms): %.03f, %.03f" % (latency, mdev))
return p return p
def iperftest(self, time=10): def iperftest(self, time=10):
''' Run iperf through a chain of nodes and report the maximum """ Run iperf through a chain of nodes and report the maximum
throughput. throughput.
''' """
bps = IperfCmd(node=self.lastnode, client_node=self.firstnode, bps = IperfCmd(node=self.lastnode, client_node=self.firstnode,
verbose=False, addr=self.lastaddr, time=time).run() verbose=False, addr=self.lastaddr, time=time).run()
self.info("throughput (bps): %s" % bps) self.info("throughput (bps): %s" % bps)
return bps return bps
def cputest(self, time=10, rate=512): def cputest(self, time=10, rate=512):
''' Run MGEN through a chain of nodes and report the CPU usage and """ Run MGEN through a chain of nodes and report the CPU usage and
percent of lost packets. Rate is in kbps. percent of lost packets. Rate is in kbps.
''' """
if self.verbose: if self.verbose:
self.info("%s initial test ping (max 1 second)..." % \ self.info("%s initial test ping (max 1 second)..." % \
self.firstnode.name) self.firstnode.name)
@ -642,7 +640,7 @@ class Experiment(object):
if status != 0: if status != 0:
self.warn("initial ping from %s to %s failed! result:\n%s" % \ self.warn("initial ping from %s to %s failed! result:\n%s" % \
(self.firstnode.name, self.lastaddr, result)) (self.firstnode.name, self.lastaddr, result))
return (0.0, 0.0) return 0.0, 0.0
lines = readstat() lines = readstat()
cpustart = getcputimes(lines[0]) cpustart = getcputimes(lines[0])
loss = MgenCmd(node=self.lastnode, client_node=self.firstnode, loss = MgenCmd(node=self.lastnode, client_node=self.firstnode,
@ -654,35 +652,36 @@ class Experiment(object):
self.info("CPU usage (%%): %.02f, %.02f loss" % (percent, loss)) self.info("CPU usage (%%): %.02f, %.02f loss" % (percent, loss))
return percent, loss return percent, loss
def main(): def main():
''' Main routine when running from command-line. """ Main routine when running from command-line.
''' """
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(numnodes = 10, delay = 3, duration = 10, rate = 512, parser.set_defaults(numnodes=10, delay=3, duration=10, rate=512,
verbose = False, verbose=False,
numping = 50, numiperf = 1, nummgen = 1) numping=50, numiperf=1, nummgen=1)
parser.add_option("-d", "--delay", dest = "delay", type = float, parser.add_option("-d", "--delay", dest="delay", type=float,
help = "wait time before testing") help="wait time before testing")
parser.add_option("-l", "--logfile", dest = "logfile", type = str, parser.add_option("-l", "--logfile", dest="logfile", type=str,
help = "log detailed output to the specified file") help="log detailed output to the specified file")
parser.add_option("-n", "--numnodes", dest = "numnodes", type = int, parser.add_option("-n", "--numnodes", dest="numnodes", type=int,
help = "number of nodes") help="number of nodes")
parser.add_option("-r", "--rate", dest = "rate", type = float, parser.add_option("-r", "--rate", dest="rate", type=float,
help = "kbps rate to use for MGEN CPU tests") help="kbps rate to use for MGEN CPU tests")
parser.add_option("--numping", dest = "numping", type = int, parser.add_option("--numping", dest="numping", type=int,
help = "number of ping latency test runs") help="number of ping latency test runs")
parser.add_option("--numiperf", dest = "numiperf", type = int, parser.add_option("--numiperf", dest="numiperf", type=int,
help = "number of iperf throughput test runs") help="number of iperf throughput test runs")
parser.add_option("--nummgen", dest = "nummgen", type = int, parser.add_option("--nummgen", dest="nummgen", type=int,
help = "number of MGEN CPU tests runs") help="number of MGEN CPU tests runs")
parser.add_option("-t", "--time", dest = "duration", type = int, parser.add_option("-t", "--time", dest="duration", type=int,
help = "duration in seconds of throughput and CPU tests") help="duration in seconds of throughput and CPU tests")
parser.add_option("-v", "--verbose", dest = "verbose", parser.add_option("-v", "--verbose", dest="verbose",
action = "store_true", help = "be more verbose") action="store_true", help="be more verbose")
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -700,59 +699,57 @@ def main():
usage("invalid rate: %s" % opt.rate) usage("invalid rate: %s" % opt.rate)
for a in args: for a in args:
sys.stderr.write("ignoring command line argument: '%s'\n" % a) sys.stderr.write("ignoring command line argument: %s\n" % a)
results = {} results = {}
starttime = datetime.datetime.now() starttime = datetime.datetime.now()
exp = Experiment(opt = opt, start=starttime) exp = Experiment(opt=opt, start=starttime)
exp.info("Starting wlanemanetests.py tests %s" % starttime.ctime()) exp.info("Starting wlanemanetests.py tests %s" % starttime.ctime())
# system sanity checks here # system sanity checks here
emanever, emaneverstr = Emane.detectversionfromcmd() emanever, emaneverstr = emane.VERSION, emane.VERSIONSTR
if opt.verbose: if opt.verbose:
exp.info("Detected EMANE version %s" % (emaneverstr,)) exp.info("Detected EMANE version %s" % (emaneverstr,))
# bridged # bridged
exp.info("setting up bridged tests 1/2 no link effects") exp.info("setting up bridged tests 1/2 no link effects")
exp.info("creating topology: numnodes = %s" % \ exp.info("creating topology: numnodes = %s" % (opt.numnodes,))
(opt.numnodes, ))
exp.createbridgedsession(numnodes=opt.numnodes, verbose=opt.verbose) exp.createbridgedsession(numnodes=opt.numnodes, verbose=opt.verbose)
exp.setnodes() exp.setnodes()
exp.info("waiting %s sec (node/route bring-up)" % opt.delay) exp.info("waiting %s sec (node/route bring-up)" % opt.delay)
time.sleep(opt.delay) time.sleep(opt.delay)
results['0 bridged'] = exp.runalltests("bridged") results["0 bridged"] = exp.runalltests("bridged")
exp.info("done; elapsed time: %s" % (datetime.datetime.now() - exp.start)) exp.info("done; elapsed time: %s" % (datetime.datetime.now() - exp.start))
# bridged with netem # bridged with netem
exp.info("setting up bridged tests 2/2 with netem") exp.info("setting up bridged tests 2/2 with netem")
exp.setneteffects(bw=54000000, delay=0) exp.setneteffects(bw=54000000, delay=0)
exp.info("waiting %s sec (queue bring-up)" % opt.delay) exp.info("waiting %s sec (queue bring-up)" % opt.delay)
results['1.0 netem'] = exp.runalltests("netem") results["1.0 netem"] = exp.runalltests("netem")
exp.info("shutting down bridged session") exp.info("shutting down bridged session")
# bridged with netem (1 Mbps,200ms) # bridged with netem (1 Mbps,200ms)
exp.info("setting up bridged tests 3/2 with netem") exp.info("setting up bridged tests 3/2 with netem")
exp.setneteffects(bw=1000000, delay=20000) exp.setneteffects(bw=1000000, delay=20000)
exp.info("waiting %s sec (queue bring-up)" % opt.delay) exp.info("waiting %s sec (queue bring-up)" % opt.delay)
results['1.2 netem_1M'] = exp.runalltests("netem_1M") results["1.2 netem_1M"] = exp.runalltests("netem_1M")
exp.info("shutting down bridged session") exp.info("shutting down bridged session")
# bridged with netem (54 kbps,500ms) # bridged with netem (54 kbps,500ms)
exp.info("setting up bridged tests 3/2 with netem") exp.info("setting up bridged tests 3/2 with netem")
exp.setneteffects(bw=54000, delay=100000) exp.setneteffects(bw=54000, delay=100000)
exp.info("waiting %s sec (queue bring-up)" % opt.delay) exp.info("waiting %s sec (queue bring-up)" % opt.delay)
results['1.4 netem_54K'] = exp.runalltests("netem_54K") results["1.4 netem_54K"] = exp.runalltests("netem_54K")
exp.info("shutting down bridged session") exp.info("shutting down bridged session")
exp.reset() exp.reset()
# EMANE bypass model # EMANE bypass model
exp.info("setting up EMANE tests 1/2 with bypass model") exp.info("setting up EMANE tests 1/2 with bypass model")
exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose, exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose, cls=EmaneBypassModel, values=None)
cls=EmaneBypassModel, values=None)
exp.setnodes() exp.setnodes()
exp.info("waiting %s sec (node/route bring-up)" % opt.delay) exp.info("waiting %s sec (node/route bring-up)" % opt.delay)
time.sleep(opt.delay) time.sleep(opt.delay)
results['2.0 bypass'] = exp.runalltests("bypass") results["2.0 bypass"] = exp.runalltests("bypass")
exp.info("shutting down bypass session") exp.info("shutting down bypass session")
exp.reset() exp.reset()
@ -763,18 +760,18 @@ def main():
exp.info("setting up EMANE tests 2/4 with RF-PIPE model") exp.info("setting up EMANE tests 2/4 with RF-PIPE model")
rfpipevals = list(EmaneRfPipeModel.getdefaultvalues()) rfpipevals = list(EmaneRfPipeModel.getdefaultvalues())
rfpnames = EmaneRfPipeModel.getnames() rfpnames = EmaneRfPipeModel.getnames()
rfpipevals[ rfpnames.index('datarate') ] = '4294967295' # max value # max value
if emanever < Emane.EMANE091: rfpipevals[rfpnames.index("datarate")] = "4294967295"
rfpipevals[ rfpnames.index('pathlossmode') ] = '2ray' if emanever < emane.EMANE091:
rfpipevals[ rfpnames.index('defaultconnectivitymode') ] = '1' rfpipevals[rfpnames.index("pathlossmode")] = "2ray"
rfpipevals[rfpnames.index("defaultconnectivitymode")] = "1"
else: else:
rfpipevals[ rfpnames.index('propagationmodel') ] = '2ray' rfpipevals[rfpnames.index("propagationmodel")] = "2ray"
exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose, exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose, cls=EmaneRfPipeModel, values=rfpipevals)
cls=EmaneRfPipeModel, values=rfpipevals)
exp.setnodes() exp.setnodes()
exp.info("waiting %s sec (node/route bring-up)" % opt.delay) exp.info("waiting %s sec (node/route bring-up)" % opt.delay)
time.sleep(opt.delay) time.sleep(opt.delay)
results['3.0 rfpipe'] = exp.runalltests("rfpipe") results["3.0 rfpipe"] = exp.runalltests("rfpipe")
exp.info("shutting down RF-PIPE session") exp.info("shutting down RF-PIPE session")
exp.reset() exp.reset()
@ -782,20 +779,20 @@ def main():
exp.info("setting up EMANE tests 3/4 with RF-PIPE model 54M") exp.info("setting up EMANE tests 3/4 with RF-PIPE model 54M")
rfpipevals = list(EmaneRfPipeModel.getdefaultvalues()) rfpipevals = list(EmaneRfPipeModel.getdefaultvalues())
rfpnames = EmaneRfPipeModel.getnames() rfpnames = EmaneRfPipeModel.getnames()
rfpipevals[ rfpnames.index('datarate') ] = '54000000' rfpipevals[rfpnames.index("datarate")] = "54000000"
# TX delay != propagation delay # TX delay != propagation delay
#rfpipevals[ rfpnames.index('delay') ] = '5000' # rfpipevals[ rfpnames.index("delay") ] = "5000"
if emanever < Emane.EMANE091: if emanever < emane.EMANE091:
rfpipevals[ rfpnames.index('pathlossmode') ] = '2ray' rfpipevals[rfpnames.index("pathlossmode")] = "2ray"
rfpipevals[ rfpnames.index('defaultconnectivitymode') ] = '1' rfpipevals[rfpnames.index("defaultconnectivitymode")] = "1"
else: else:
rfpipevals[ rfpnames.index('propagationmodel') ] = '2ray' rfpipevals[rfpnames.index("propagationmodel")] = "2ray"
exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose, exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose,
cls=EmaneRfPipeModel, values=rfpipevals) cls=EmaneRfPipeModel, values=rfpipevals)
exp.setnodes() exp.setnodes()
exp.info("waiting %s sec (node/route bring-up)" % opt.delay) exp.info("waiting %s sec (node/route bring-up)" % opt.delay)
time.sleep(opt.delay) time.sleep(opt.delay)
results['4.0 rfpipe54m'] = exp.runalltests("rfpipe54m") results["4.0 rfpipe54m"] = exp.runalltests("rfpipe54m")
exp.info("shutting down RF-PIPE session") exp.info("shutting down RF-PIPE session")
exp.reset() exp.reset()
@ -803,12 +800,12 @@ def main():
exp.info("setting up EMANE tests 4/4 with RF-PIPE model pathloss") exp.info("setting up EMANE tests 4/4 with RF-PIPE model pathloss")
rfpipevals = list(EmaneRfPipeModel.getdefaultvalues()) rfpipevals = list(EmaneRfPipeModel.getdefaultvalues())
rfpnames = EmaneRfPipeModel.getnames() rfpnames = EmaneRfPipeModel.getnames()
rfpipevals[ rfpnames.index('datarate') ] = '54000' rfpipevals[rfpnames.index("datarate")] = "54000"
if emanever < Emane.EMANE091: if emanever < emane.EMANE091:
rfpipevals[ rfpnames.index('pathlossmode') ] = 'pathloss' rfpipevals[rfpnames.index("pathlossmode")] = "pathloss"
rfpipevals[ rfpnames.index('defaultconnectivitymode') ] = '0' rfpipevals[rfpnames.index("defaultconnectivitymode")] = "0"
else: else:
rfpipevals[ rfpnames.index('propagationmodel') ] = 'precomputed' rfpipevals[rfpnames.index("propagationmodel")] = "precomputed"
exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose, exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose,
cls=EmaneRfPipeModel, values=rfpipevals) cls=EmaneRfPipeModel, values=rfpipevals)
exp.setnodes() exp.setnodes()
@ -816,7 +813,7 @@ def main():
time.sleep(opt.delay) time.sleep(opt.delay)
exp.info("sending pathloss events to govern connectivity") exp.info("sending pathloss events to govern connectivity")
exp.setpathloss(opt.numnodes) exp.setpathloss(opt.numnodes)
results['5.0 pathloss'] = exp.runalltests("pathloss") results["5.0 pathloss"] = exp.runalltests("pathloss")
exp.info("shutting down RF-PIPE session") exp.info("shutting down RF-PIPE session")
exp.reset() exp.reset()
@ -824,10 +821,10 @@ def main():
exp.info("setting up EMANE tests 4/4 with RF-PIPE model pathloss") exp.info("setting up EMANE tests 4/4 with RF-PIPE model pathloss")
rfpipevals = list(EmaneRfPipeModel.getdefaultvalues()) rfpipevals = list(EmaneRfPipeModel.getdefaultvalues())
rfpnames = EmaneRfPipeModel.getnames() rfpnames = EmaneRfPipeModel.getnames()
rfpipevals[ rfpnames.index('datarate') ] = '512000' rfpipevals[rfpnames.index("datarate")] = "512000"
rfpipevals[ rfpnames.index('delay') ] = '200' rfpipevals[rfpnames.index("delay")] = "200"
rfpipevals[ rfpnames.index('pathlossmode') ] = 'pathloss' rfpipevals[rfpnames.index("pathlossmode")] = "pathloss"
rfpipevals[ rfpnames.index('defaultconnectivitymode') ] = '0' rfpipevals[rfpnames.index("defaultconnectivitymode")] = "0"
exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose, exp.createemanesession(numnodes=opt.numnodes, verbose=opt.verbose,
cls=EmaneRfPipeModel, values=rfpipevals) cls=EmaneRfPipeModel, values=rfpipevals)
exp.setnodes() exp.setnodes()
@ -835,7 +832,7 @@ def main():
time.sleep(opt.delay) time.sleep(opt.delay)
exp.info("sending pathloss events to govern connectivity") exp.info("sending pathloss events to govern connectivity")
exp.setpathloss(opt.numnodes) exp.setpathloss(opt.numnodes)
results['5.1 pathloss'] = exp.runalltests("pathloss") results["5.1 pathloss"] = exp.runalltests("pathloss")
exp.info("shutting down RF-PIPE session") exp.info("shutting down RF-PIPE session")
exp.reset() exp.reset()
@ -847,10 +844,15 @@ def main():
for test in sorted(results.keys()): for test in sorted(results.keys()):
(latency, mdev, throughput, cpu, loss) = results[test] (latency, mdev, throughput, cpu, loss) = results[test]
exp.info("%s:%.03f,%.03f,%d,%.02f,%.02f" % \ exp.info("%s:%.03f,%.03f,%d,%.02f,%.02f" % \
(test, latency, mdev, throughput, cpu,loss)) (test, latency, mdev, throughput, cpu, loss))
exp.logend() exp.logend()
return exp return exp
if __name__ == "__main__": if __name__ == "__main__":
exp = main() # configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main()

View file

@ -8,11 +8,14 @@
# and repeat for minnodes <= n <= maxnodes with a step size of # and repeat for minnodes <= n <= maxnodes with a step size of
# nodestep # nodestep
import optparse, sys, os, datetime import datetime
import optparse
import sys
from core import pycore from core.misc import ipaddress, nodeutils, nodemaps
from core.misc import ipaddr
from core.misc.utils import mutecall from core.misc.utils import mutecall
from core.netns import nodes
from core.session import Session
try: try:
mutecall(["iperf", "-v"]) mutecall(["iperf", "-v"])
@ -20,16 +23,17 @@ except OSError:
sys.stderr.write("ERROR: running iperf failed\n") sys.stderr.write("ERROR: running iperf failed\n")
sys.exit(1) sys.exit(1)
def test(numnodes, testsec): def test(numnodes, testsec):
# node list # node list
n = [] n = []
# IP subnet # IP subnet
prefix = ipaddr.IPv4Prefix("10.83.0.0/16") prefix = ipaddress.Ipv4Prefix("10.83.0.0/16")
session = pycore.Session() session = Session(1)
# emulated network # emulated network
net = session.addobj(cls = pycore.nodes.WlanNode) net = session.add_object(cls=nodes.WlanNode)
for i in xrange(1, numnodes + 1): for i in xrange(1, numnodes + 1):
tmp = session.addobj(cls = pycore.nodes.LxcNode, objid= "%d" % i, name = "n%d" % i) tmp = session.add_object(cls=nodes.LxcNode, objid="%d" % i, name="n%d" % i)
tmp.newnetif(net, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)]) tmp.newnetif(net, ["%s/%s" % (prefix.addr(i), prefix.prefixlen)])
n.append(tmp) n.append(tmp)
net.link(n[0].netif(0), n[-1].netif(0)) net.link(n[0].netif(0), n[-1].netif(0))
@ -38,31 +42,31 @@ def test(numnodes, testsec):
n[0].cmd(["killall", "-9", "iperf"]) n[0].cmd(["killall", "-9", "iperf"])
session.shutdown() session.shutdown()
def main(): def main():
usagestr = "usage: %prog [-h] [options] [args]" usagestr = "usage: %prog [-h] [options] [args]"
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(minnodes = 2) parser.set_defaults(minnodes=2)
parser.add_option("-m", "--minnodes", dest = "minnodes", type = int, parser.add_option("-m", "--minnodes", dest="minnodes", type=int,
help = "min number of nodes to test; default = %s" % help="min number of nodes to test; default = %s" % parser.defaults["minnodes"])
parser.defaults["minnodes"])
parser.set_defaults(maxnodes = 2) parser.set_defaults(maxnodes=2)
parser.add_option("-n", "--maxnodes", dest = "maxnodes", type = int, parser.add_option("-n", "--maxnodes", dest="maxnodes", type=int,
help = "max number of nodes to test; default = %s" % help="max number of nodes to test; default = %s" %
parser.defaults["maxnodes"]) parser.defaults["maxnodes"])
parser.set_defaults(testsec = 10) parser.set_defaults(testsec=10)
parser.add_option("-t", "--testsec", dest = "testsec", type = int, parser.add_option("-t", "--testsec", dest="testsec", type=int,
help = "test time in seconds; default = %s" % help="test time in seconds; default = %s" %
parser.defaults["testsec"]) parser.defaults["testsec"])
parser.set_defaults(nodestep = 1) parser.set_defaults(nodestep=1)
parser.add_option("-s", "--nodestep", dest = "nodestep", type = int, parser.add_option("-s", "--nodestep", dest="nodestep", type=int,
help = "number of nodes step size; default = %s" % help="number of nodes step size; default = %s" %
parser.defaults["nodestep"]) parser.defaults["nodestep"])
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -91,8 +95,12 @@ def main():
test(i, options.testsec) test(i, options.testsec)
print >> sys.stderr, "" print >> sys.stderr, ""
print >> sys.stderr, \ print >> sys.stderr, "elapsed time: %s" % (datetime.datetime.now() - start)
"elapsed time: %s" % (datetime.datetime.now() - start)
if __name__ == "__main__": if __name__ == "__main__":
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
nodeutils.set_node_map(node_map)
main() main()

View file

@ -5,41 +5,45 @@
# List and stop CORE sessions from the command line. # List and stop CORE sessions from the command line.
# #
import socket, optparse import optparse
from core.constants import * import socket
from core.api import coreapi from core.api import coreapi
from core.enumerations import MessageFlags, SessionTlvs, CORE_API_PORT
def main(): def main():
parser = optparse.OptionParser(usage = "usage: %prog [-l] <sessionid>") parser = optparse.OptionParser(usage="usage: %prog [-l] <sessionid>")
parser.add_option("-l", "--list", dest = "list", action = "store_true", parser.add_option("-l", "--list", dest="list", action="store_true",
help = "list running sessions") help="list running sessions")
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
if options.list is True: if options.list is True:
num = '0' num = '0'
flags = coreapi.CORE_API_STR_FLAG flags = MessageFlags.STRING.value
else: else:
num = args[0] num = args[0]
flags = coreapi.CORE_API_DEL_FLAG flags = MessageFlags.DELETE.value
tlvdata = coreapi.CoreSessionTlv.pack(coreapi.CORE_TLV_SESS_NUMBER, num) tlvdata = coreapi.CoreSessionTlv.pack(SessionTlvs.NUMBER.value, num)
msg = coreapi.CoreSessionMessage.pack(flags, tlvdata) message = coreapi.CoreSessionMessage.pack(flags, tlvdata)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', coreapi.CORE_API_PORT)) sock.connect(('localhost', CORE_API_PORT))
sock.send(msg) sock.send(message)
# receive and print a session list # receive and print a session list
if options.list is True: if options.list is True:
hdr = sock.recv(coreapi.CoreMessage.hdrsiz) hdr = sock.recv(coreapi.CoreMessage.header_len)
msgtype, msgflags, msglen = coreapi.CoreMessage.unpackhdr(hdr) msgtype, msgflags, msglen = coreapi.CoreMessage.unpack_header(hdr)
data = "" data = ""
if msglen: if msglen:
data = sock.recv(msglen) data = sock.recv(msglen)
msg = coreapi.CoreMessage(msgflags, hdr, data) message = coreapi.CoreMessage(msgflags, hdr, data)
sessions = msg.gettlv(coreapi.CORE_TLV_SESS_NUMBER) sessions = message.get_tlv(coreapi.SessionTlvs.NUMBER.value)
print "sessions:", sessions print "sessions:", sessions
sock.close() sock.close()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -7,160 +7,177 @@
# authors: Tom Goff <thomas.goff@boeing.com> # authors: Tom Goff <thomas.goff@boeing.com>
# Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com> # Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
# #
'''
"""
core-daemon: the CORE daemon is a server process that receives CORE API core-daemon: the CORE daemon is a server process that receives CORE API
messages and instantiates emulated nodes and networks within the kernel. Various messages and instantiates emulated nodes and networks within the kernel. Various
message handlers are defined and some support for sending messages. message handlers are defined and some support for sending messages.
''' """
import os, optparse, ConfigParser, gc, shlex, socket, shutil import ConfigParser
import atexit import atexit
import signal
import importlib import importlib
import logging
import optparse
import os
import signal
import socket
import sys
import threading
import time
try: from core import constants
from core import pycore from core import corehandlers
except ImportError: from core import coreserver
# hack for Fedora autoconf that uses the following pythondir: from core import enumerations
if "/usr/lib/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.6/site-packages")
if "/usr/lib64/python2.6/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.6/site-packages")
if "/usr/lib/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib/python2.7/site-packages")
if "/usr/lib64/python2.7/site-packages" in sys.path:
sys.path.append("/usr/local/lib64/python2.7/site-packages")
from core import pycore
from core.coreserver import *
from core.constants import *
from core.api import coreapi from core.api import coreapi
from core.misc.utils import daemonize, closeonexec from core.corehandlers import CoreDatagramRequestHandler
from core.enumerations import MessageFlags
from core.enumerations import RegisterTlvs
from core.misc import log
from core.misc import nodemaps
from core.misc import nodeutils
from core.misc.utils import closeonexec
from core.misc.utils import daemonize
from core.services import bird
from core.services import dockersvc
from core.services import nrl
from core.services import quagga
from core.services import security
from core.services import startup
from core.services import ucarp
from core.services import utility
from core.services import xorp
logger = log.get_logger(__name__)
DEFAULT_MAXFD = 1024 DEFAULT_MAXFD = 1024
# garbage collection debugging
# gc.set_debug(gc.DEBUG_STATS | gc.DEBUG_LEAK) def startudp(core_server, server_address):
"""
Start a thread running a UDP server on the same host,port for connectionless requests.
:param core.coreserver.CoreServer core_server: core server instance
:param tuple[str, int] server_address: server address
:return: created core udp server
:rtype: core.coreserver.CoreUdpServer
"""
core_server.udpserver = coreserver.CoreUdpServer(server_address, CoreDatagramRequestHandler, core_server)
core_server.udpthread = threading.Thread(target=core_server.udpserver.start)
core_server.udpthread.daemon = True
core_server.udpthread.start()
return core_server.udpserver
coreapi.add_node_class("CORE_NODE_DEF", def startaux(core_server, aux_address, aux_handler):
coreapi.CORE_NODE_DEF, pycore.nodes.CoreNode) """
coreapi.add_node_class("CORE_NODE_PHYS", Start a thread running an auxiliary TCP server on the given address.
coreapi.CORE_NODE_PHYS, pycore.pnodes.PhysicalNode)
try:
coreapi.add_node_class("CORE_NODE_XEN",
coreapi.CORE_NODE_XEN, pycore.xen.XenNode)
except Exception:
#print "XenNode class unavailable."
pass
coreapi.add_node_class("CORE_NODE_TBD",
coreapi.CORE_NODE_TBD, None)
coreapi.add_node_class("CORE_NODE_SWITCH",
coreapi.CORE_NODE_SWITCH, pycore.nodes.SwitchNode)
coreapi.add_node_class("CORE_NODE_HUB",
coreapi.CORE_NODE_HUB, pycore.nodes.HubNode)
coreapi.add_node_class("CORE_NODE_WLAN",
coreapi.CORE_NODE_WLAN, pycore.nodes.WlanNode)
coreapi.add_node_class("CORE_NODE_RJ45",
coreapi.CORE_NODE_RJ45, pycore.nodes.RJ45Node)
coreapi.add_node_class("CORE_NODE_TUNNEL",
coreapi.CORE_NODE_TUNNEL, pycore.nodes.TunnelNode)
coreapi.add_node_class("CORE_NODE_EMANE",
coreapi.CORE_NODE_EMANE, pycore.nodes.EmaneNode)
#
# UDP server startup
#
def startudp(mainserver, server_address):
''' Start a thread running a UDP server on the same host,port for
connectionless requests.
'''
mainserver.udpserver = CoreUdpServer(server_address,
CoreDatagramRequestHandler, mainserver)
mainserver.udpthread = threading.Thread(target = mainserver.udpserver.start)
mainserver.udpthread.daemon = True
mainserver.udpthread.start()
return mainserver.udpserver
#
# Auxiliary server startup
#
def startaux(mainserver, aux_address, aux_handler):
''' Start a thread running an auxiliary TCP server on the given address.
This server will communicate with client requests using a handler This server will communicate with client requests using a handler
using the aux_handler class. The aux_handler can provide an alternative using the aux_handler class. The aux_handler can provide an alternative
API to CORE. API to CORE.
'''
handlermodname,dot,handlerclassname = aux_handler.rpartition('.') :param core.coreserver.CoreServer core_server: core server instance
:param tuple[str, int] aux_address: auxiliary server address
:param str aux_handler: auxiliary handler string to import
:return: auxiliary server
"""
handlermodname, dot, handlerclassname = aux_handler.rpartition(".")
handlermod = importlib.import_module(handlermodname) handlermod = importlib.import_module(handlermodname)
handlerclass = getattr(handlermod, handlerclassname) handlerclass = getattr(handlermod, handlerclassname)
mainserver.auxserver = CoreAuxServer(aux_address, core_server.auxserver = coreserver.CoreAuxServer(aux_address, handlerclass, core_server)
handlerclass, core_server.auxthread = threading.Thread(target=core_server.auxserver.start)
mainserver) core_server.auxthread.daemon = True
mainserver.auxthread = threading.Thread(target = mainserver.auxserver.start) core_server.auxthread.start()
mainserver.auxthread.daemon = True return core_server.auxserver
mainserver.auxthread.start()
return mainserver.auxserver
def banner(): def banner():
''' Output the program banner printed to the terminal or log file. """
''' Output the program banner printed to the terminal or log file.
sys.stdout.write("CORE daemon v.%s started %s\n" % \
(COREDPY_VERSION, time.ctime())) :return: nothing
sys.stdout.flush() """
logger.info("CORE daemon v.%s started %s\n" % (constants.COREDPY_VERSION, time.ctime()))
def cored(cfg = None): def cored(cfg=None):
''' Start the CoreServer object and enter the server loop. """
''' Start the CoreServer object and enter the server loop.
host = cfg['listenaddr']
port = int(cfg['port']) :param dict cfg: core configuration
if host == '' or host is None: :return: nothing
"""
host = cfg["listenaddr"]
port = int(cfg["port"])
if host == "" or host is None:
host = "localhost" host = "localhost"
try: try:
server = CoreServer((host, port), CoreRequestHandler, cfg) server = coreserver.CoreServer((host, port), corehandlers.CoreRequestHandler, cfg)
except Exception, e: except:
sys.stderr.write("error starting main server on: %s:%s\n\t%s\n" % \ logger.exception("error starting main server on: %s:%s", host, port)
(host, port, e))
sys.stderr.flush()
sys.exit(1) sys.exit(1)
closeonexec(server.fileno())
sys.stdout.write("main server started, listening on: %s:%s\n" % (host, port))
sys.stdout.flush()
udpserver = startudp(server, (host,port)) closeonexec(server.fileno())
logger.info("main server started, listening on: %s:%s\n" % (host, port))
udpserver = startudp(server, (host, port))
closeonexec(udpserver.fileno()) closeonexec(udpserver.fileno())
auxreqhandler = cfg['aux_request_handler'] auxreqhandler = cfg["aux_request_handler"]
if auxreqhandler: if auxreqhandler:
try: handler, auxport = auxreqhandler.rsplit(":")
handler, auxport = auxreqhandler.rsplit(':') auxserver = startaux(server, (host, int(auxport)), handler)
auxserver = startaux(server, (host,int(auxport)), handler)
closeonexec(auxserver.fileno()) closeonexec(auxserver.fileno())
except Exception as e:
raise ValueError, "invalid auxreqhandler:(%s)\nError: %s" % (auxreqhandler, e)
server.serve_forever() server.serve_forever()
# TODO: should sessions and the main core daemon both catch at exist to shutdown independently?
def cleanup(): def cleanup():
while CoreServer.servers: """
server = CoreServer.servers.pop() Runs server shutdown and cleanup when catching an exit signal.
:return: nothing
"""
while coreserver.CoreServer.servers:
server = coreserver.CoreServer.servers.pop()
server.shutdown() server.shutdown()
atexit.register(cleanup) atexit.register(cleanup)
def sighandler(signum, stackframe): def sighandler(signum, stackframe):
print >> sys.stderr, "terminated by signal:", signum """
Signal handler when different signals are sent.
:param int signum: singal number sent
:param stackframe: stack frame sent
:return: nothing
"""
logger.error("terminated by signal: %s", signum)
sys.exit(signum) sys.exit(signum)
signal.signal(signal.SIGHUP, sighandler) signal.signal(signal.SIGHUP, sighandler)
signal.signal(signal.SIGINT, sighandler) signal.signal(signal.SIGINT, sighandler)
signal.signal(signal.SIGTERM, sighandler) signal.signal(signal.SIGTERM, sighandler)
signal.signal(signal.SIGUSR1, sighandler) signal.signal(signal.SIGUSR1, sighandler)
signal.signal(signal.SIGUSR2, sighandler) signal.signal(signal.SIGUSR2, sighandler)
def logrotate(stdout, stderr, stdoutmode = 0644, stderrmode = 0644):
def logrotate(stdout, stderr, stdoutmode=0644, stderrmode=0644):
"""
Log rotation method.
:param stdout: stdout
:param stderr: stderr
:param int stdoutmode: stdout mode
:param int stderrmode: stderr mode
:return:
"""
def reopen(fileno, filename, mode): def reopen(fileno, filename, mode):
err = 0 err = 0
fd = -1 fd = -1
@ -174,6 +191,7 @@ def logrotate(stdout, stderr, stdoutmode = 0644, stderrmode = 0644):
if fd >= 0: if fd >= 0:
os.close(fd) os.close(fd)
return err return err
if stdout: if stdout:
err = reopen(1, stdout, stdoutmode) err = reopen(1, stdout, stdoutmode)
if stderr: if stderr:
@ -185,59 +203,64 @@ def logrotate(stdout, stderr, stdoutmode = 0644, stderrmode = 0644):
else: else:
reopen(2, stderr, stderrmode) reopen(2, stderr, stderrmode)
def getMergedConfig(filename):
''' Return a configuration after merging config file and command-line def get_merged_config(filename):
arguments. """
''' Return a configuration after merging config file and command-line arguments.
:param str filename: file name to merge configuration settings with
:return: merged configuration
:rtype: dict
"""
# these are the defaults used in the config file # these are the defaults used in the config file
defaults = { 'port' : '%d' % coreapi.CORE_API_PORT, defaults = {"port": "%d" % enumerations.CORE_API_PORT,
'listenaddr' : 'localhost', "listenaddr": "localhost",
'pidfile' : '%s/run/core-daemon.pid' % CORE_STATE_DIR, "pidfile": "%s/run/core-daemon.pid" % constants.CORE_STATE_DIR,
'logfile' : '%s/log/core-daemon.log' % CORE_STATE_DIR, "logfile": "%s/log/core-daemon.log" % constants.CORE_STATE_DIR,
'xmlfilever' : '1.0', "xmlfilever": "1.0",
'numthreads' : '1', "numthreads": "1",
'verbose' : 'False', "verbose": "False",
'daemonize' : 'False', "daemonize": "False",
'debug' : 'False', "debug": "False",
'execfile' : None, "execfile": None,
'aux_request_handler' : None, "aux_request_handler": None,
} }
usagestr = "usage: %prog [-h] [options] [args]\n\n" + \ usagestr = "usage: %prog [-h] [options] [args]\n\n" + \
"CORE daemon v.%s instantiates Linux network namespace " \ "CORE daemon v.%s instantiates Linux network namespace " \
"nodes." % COREDPY_VERSION "nodes." % constants.COREDPY_VERSION
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.add_option("-f", "--configfile", dest = "configfile", parser.add_option("-f", "--configfile", dest="configfile",
type = "string", type="string",
help = "read config from specified file; default = %s" % help="read config from specified file; default = %s" %
filename) filename)
parser.add_option("-d", "--daemonize", dest = "daemonize", parser.add_option("-d", "--daemonize", dest="daemonize",
action="store_true", action="store_true",
help = "run in background as daemon; default=%s" % \ help="run in background as daemon; default=%s" % \
defaults["daemonize"]) defaults["daemonize"])
parser.add_option("-e", "--execute", dest = "execfile", type = "string", parser.add_option("-e", "--execute", dest="execfile", type="string",
help = "execute a Python/XML-based session") help="execute a Python/XML-based session")
parser.add_option("-l", "--logfile", dest = "logfile", type = "string", parser.add_option("-l", "--logfile", dest="logfile", type="string",
help = "log output to specified file; default = %s" % help="log output to specified file; default = %s" %
defaults["logfile"]) defaults["logfile"])
parser.add_option("-p", "--port", dest = "port", type = int, parser.add_option("-p", "--port", dest="port", type=int,
help = "port number to listen on; default = %s" % \ help="port number to listen on; default = %s" % \
defaults["port"]) defaults["port"])
parser.add_option("-i", "--pidfile", dest = "pidfile", parser.add_option("-i", "--pidfile", dest="pidfile",
help = "filename to write pid to; default = %s" % \ help="filename to write pid to; default = %s" % \
defaults["pidfile"]) defaults["pidfile"])
parser.add_option("-t", "--numthreads", dest = "numthreads", type = int, parser.add_option("-t", "--numthreads", dest="numthreads", type=int,
help = "number of server threads; default = %s" % \ help="number of server threads; default = %s" % \
defaults["numthreads"]) defaults["numthreads"])
parser.add_option("-v", "--verbose", dest = "verbose", action="store_true", parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help = "enable verbose logging; default = %s" % \ help="enable verbose logging; default = %s" % \
defaults["verbose"]) defaults["verbose"])
parser.add_option("-g", "--debug", dest = "debug", action="store_true", parser.add_option("-g", "--debug", dest="debug", action="store_true",
help = "enable debug logging; default = %s" % \ help="enable debug logging; default = %s" % \
defaults["debug"]) defaults["debug"])
# parse command line options # parse command line options
(options, args) = parser.parse_args() options, args = parser.parse_args()
# read the config file # read the config file
if options.configfile is not None: if options.configfile is not None:
@ -252,14 +275,14 @@ def getMergedConfig(filename):
# gracefully support legacy configs (cored.py/cored now core-daemon) # gracefully support legacy configs (cored.py/cored now core-daemon)
if cfg.has_section("cored.py"): if cfg.has_section("cored.py"):
for name, val in cfg.items("cored.py"): for name, val in cfg.items("cored.py"):
if name == 'pidfile' or name == 'logfile': if name == "pidfile" or name == "logfile":
bn = os.path.basename(val).replace('coredpy', 'core-daemon') bn = os.path.basename(val).replace("coredpy", "core-daemon")
val = os.path.join(os.path.dirname(val), bn) val = os.path.join(os.path.dirname(val), bn)
cfg.set(section, name, val) cfg.set(section, name, val)
if cfg.has_section("cored"): if cfg.has_section("cored"):
for name, val in cfg.items("cored"): for name, val in cfg.items("cored"):
if name == 'pidfile' or name == 'logfile': if name == "pidfile" or name == "logfile":
bn = os.path.basename(val).replace('cored', 'core-daemon') bn = os.path.basename(val).replace("cored", "core-daemon")
val = os.path.join(os.path.dirname(val), bn) val = os.path.join(os.path.dirname(val), bn)
cfg.set(section, name, val) cfg.set(section, name, val)
@ -271,49 +294,74 @@ def getMergedConfig(filename):
return dict(cfg.items(section)), args return dict(cfg.items(section)), args
def exec_file(cfg): def exec_file(cfg):
''' Send a Register Message to execute a new session based on XML or Python """
script file. Send a Register Message to execute a new session based on XML or Python script file.
'''
filename = cfg['execfile'] :param dict cfg: configuration settings
sys.stdout.write("Telling daemon to execute file: '%s'...\n" % filename) :return: 0
sys.stdout.flush() """
tlvdata = coreapi.CoreRegTlv.pack(coreapi.CORE_TLV_REG_EXECSRV, filename) filename = cfg["execfile"]
msg = coreapi.CoreRegMessage.pack(coreapi.CORE_API_ADD_FLAG, tlvdata) logger.info("Telling daemon to execute file: %s...", filename)
tlvdata = coreapi.CoreRegisterTlv.pack(RegisterTlvs.EXECUTE_SERVER.value, filename)
msg = coreapi.CoreRegMessage.pack(MessageFlags.ADD.value, tlvdata)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("localhost", int(cfg['port']))) # TODO: connect address option sock.connect(("localhost", int(cfg["port"]))) # TODO: connect address option
sock.sendall(msg) sock.sendall(msg)
return 0 return 0
def main():
''' Main program startup.
'''
# get a configuration merged from config file and command-line arguments
cfg, args = getMergedConfig("%s/core.conf" % CORE_CONF_DIR)
for a in args:
sys.stderr.write("ignoring command line argument: '%s'\n" % a)
if cfg['daemonize'] == 'True': def main():
daemonize(rootdir = None, umask = 0, close_fds = False, """
stdin = os.devnull, Main program startup.
stdout = cfg['logfile'], stderr = cfg['logfile'],
pidfilename = cfg['pidfile'], :return: nothing
defaultmaxfd = DEFAULT_MAXFD) """
# get a configuration merged from config file and command-line arguments
cfg, args = get_merged_config("%s/core.conf" % constants.CORE_CONF_DIR)
for a in args:
logger.error("ignoring command line argument: %s", a)
if cfg["daemonize"] == "True":
daemonize(rootdir=None, umask=0, close_fds=False,
stdin=os.devnull,
stdout=cfg["logfile"], stderr=cfg["logfile"],
pidfilename=cfg["pidfile"],
defaultmaxfd=DEFAULT_MAXFD)
signal.signal(signal.SIGUSR1, lambda signum, stackframe: signal.signal(signal.SIGUSR1, lambda signum, stackframe:
logrotate(stdout = cfg['logfile'], logrotate(stdout=cfg["logfile"], stderr=cfg["logfile"]))
stderr = cfg['logfile']))
banner() banner()
if cfg['execfile']: if cfg["execfile"]:
cfg['execfile'] = os.path.abspath(cfg['execfile']) cfg["execfile"] = os.path.abspath(cfg["execfile"])
sys.exit(exec_file(cfg)) sys.exit(exec_file(cfg))
try: try:
cored(cfg) cored(cfg)
except KeyboardInterrupt: except KeyboardInterrupt:
pass logger.info("keyboard interrupt, stopping core daemon")
sys.exit(0) sys.exit(0)
if __name__ == "__main__": if __name__ == "__main__":
log.setup(level=logging.INFO)
# configure nodes to use
node_map = nodemaps.CLASSIC_NODES
if len(sys.argv) == 2 and sys.argv[1] == "ovs":
node_map = nodemaps.OVS_NODES
nodeutils.set_node_map(node_map)
# load default services
quagga.load_services()
nrl.load_services()
xorp.load_services()
bird.load_services()
utility.load_services()
security.load_services()
ucarp.load_services()
dockersvc.load_services()
startup.load_services()
main() main()

View file

@ -6,29 +6,31 @@
# #
# author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com> # author: Jeff Ahrenholz <jeffrey.m.ahrenholz@boeing.com>
# #
''' """
core-manage: Helper tool to add, remove, or check for services, models, and core-manage: Helper tool to add, remove, or check for services, models, and
node types in a CORE installation. node types in a CORE installation.
''' """
import os
import sys
import ast import ast
import optparse import optparse
import os
import re import re
import sys
from core import pycore from core import services
from core.constants import CORE_CONF_DIR from core.constants import CORE_CONF_DIR
class FileUpdater(object): class FileUpdater(object):
''' Helper class for changing configuration files. """
''' Helper class for changing configuration files.
"""
actions = ("add", "remove", "check") actions = ("add", "remove", "check")
targets = ("service", "model", "nodetype") targets = ("service", "model", "nodetype")
def __init__(self, action, target, data, options): def __init__(self, action, target, data, options):
''' """
''' """
self.action = action self.action = action
self.target = target self.target = target
self.data = data self.data = data
@ -37,13 +39,13 @@ class FileUpdater(object):
self.search, self.filename = self.get_filename(target) self.search, self.filename = self.get_filename(target)
def process(self): def process(self):
''' Invoke update_file() using a helper method depending on target. """ Invoke update_file() using a helper method depending on target.
''' """
if self.verbose: if self.verbose:
txt = "Updating" txt = "Updating"
if self.action == "check": if self.action == "check":
txt = "Checking" txt = "Checking"
sys.stdout.write("%s file: '%s'\n" % (txt, self.filename)) sys.stdout.write("%s file: %s\n" % (txt, self.filename))
if self.target == "service": if self.target == "service":
r = self.update_file(fn=self.update_services) r = self.update_file(fn=self.update_services)
@ -64,41 +66,40 @@ class FileUpdater(object):
return r return r
def update_services(self, line): def update_services(self, line):
''' Modify the __init__.py file having this format: """ Modify the __init__.py file having this format:
__all__ = ["quagga", "nrl", "xorp", "bird", ] __all__ = ["quagga", "nrl", "xorp", "bird", ]
Returns True or False when "check" is the action, a modified line Returns True or False when "check" is the action, a modified line
otherwise. otherwise.
''' """
line = line.strip('\n') line = line.strip("\n")
key, valstr = line.split('= ') key, valstr = line.split("= ")
vals = ast.literal_eval(valstr) vals = ast.literal_eval(valstr)
r = self.update_keyvals(key, vals) r = self.update_keyvals(key, vals)
if self.action == "check": if self.action == "check":
return r return r
valstr = '%s' % r valstr = "%s" % r
return '= '.join([key, valstr]) + '\n' return "= ".join([key, valstr]) + "\n"
def update_emane_models(self, line): def update_emane_models(self, line):
''' Modify the core.conf file having this format: """ Modify the core.conf file having this format:
emane_models = RfPipe, Ieee80211abg, CommEffect, Bypass emane_models = RfPipe, Ieee80211abg, CommEffect, Bypass
Returns True or False when "check" is the action, a modified line Returns True or False when "check" is the action, a modified line
otherwise. otherwise.
''' """
line = line.strip('\n') line = line.strip("\n")
key, valstr = line.split('= ') key, valstr = line.split("= ")
vals = valstr.split(', ') vals = valstr.split(", ")
r = self.update_keyvals(key, vals) r = self.update_keyvals(key, vals)
if self.action == "check": if self.action == "check":
return r return r
valstr = ', '.join(r) valstr = ", ".join(r)
return '= '.join([key, valstr]) + '\n' return "= ".join([key, valstr]) + "\n"
def update_keyvals(self, key, vals): def update_keyvals(self, key, vals):
''' Perform self.action on (key, vals). """ Perform self.action on (key, vals).
Returns True or False when "check" is the action, a modified line Returns True or False when "check" is the action, a modified line
otherwise. otherwise.
''' """
if self.action == "check": if self.action == "check":
if self.data in vals: if self.data in vals:
return True return True
@ -115,11 +116,10 @@ class FileUpdater(object):
return vals return vals
def get_filename(self, target): def get_filename(self, target):
''' Return search string and filename based on target. """ Return search string and filename based on target.
''' """
if target == "service": if target == "service":
pypath = os.path.dirname(pycore.__file__) filename = os.path.abspath(services.__file__)
filename = os.path.join(pypath, "services", "__init__.py")
search = "__all__ =" search = "__all__ ="
elif target == "model": elif target == "model":
filename = os.path.join(CORE_CONF_DIR, "core.conf") filename = os.path.join(CORE_CONF_DIR, "core.conf")
@ -132,15 +132,15 @@ class FileUpdater(object):
else: else:
raise ValueError, "unknown target" raise ValueError, "unknown target"
if not os.path.exists(filename): if not os.path.exists(filename):
raise ValueError, "file '%s' does not exist" % filename raise ValueError, "file %s does not exist" % filename
return search, filename return search, filename
def update_file(self, fn=None): def update_file(self, fn=None):
''' Open a file and search for self.search, invoking the supplied """ Open a file and search for self.search, invoking the supplied
function on the matching line. Write file changes if necessary. function on the matching line. Write file changes if necessary.
Returns True if the file has changed (or action is "check" and the Returns True if the file has changed (or action is "check" and the
search string is found), False otherwise. search string is found), False otherwise.
''' """
changed = False changed = False
output = "" # this accumulates output, assumes input is small output = "" # this accumulates output, assumes input is small
with open(self.filename, "r") as f: with open(self.filename, "r") as f:
@ -161,11 +161,11 @@ class FileUpdater(object):
return changed return changed
def update_nodes_conf(self): def update_nodes_conf(self):
''' Add/remove/check entries from nodes.conf. This file """ Add/remove/check entries from nodes.conf. This file
contains a Tcl-formatted array of node types. The array index must be contains a Tcl-formatted array of node types. The array index must be
properly set for new entries. Uses self.{action, filename, search, properly set for new entries. Uses self.{action, filename, search,
data} variables as input and returns the same value as update_file(). data} variables as input and returns the same value as update_file().
''' """
changed = False changed = False
output = "" # this accumulates output, assumes input is small output = "" # this accumulates output, assumes input is small
with open(self.filename, "r") as f: with open(self.filename, "r") as f:
@ -181,9 +181,10 @@ class FileUpdater(object):
continue continue
else: else:
output += line output += line
if self.action == "add": if self.action == "add":
index = int(re.match('^\d+', line).group(0)) index = int(re.match("^\d+", line).group(0))
output += str(index + 1) + ' ' + self.data + '\n' output += str(index + 1) + " " + self.data + "\n"
changed = True changed = True
if changed: if changed:
with open(self.filename, "w") as f: with open(self.filename, "w") as f:
@ -200,21 +201,21 @@ def main():
usagestr += "\n %prog -v check model RfPipe" usagestr += "\n %prog -v check model RfPipe"
usagestr += "\n %prog --userpath=\"$HOME/.core\" add nodetype \"{ftp ftp.gif ftp.gif {DefaultRoute FTP} netns {FTP server} }\" \n" usagestr += "\n %prog --userpath=\"$HOME/.core\" add nodetype \"{ftp ftp.gif ftp.gif {DefaultRoute FTP} netns {FTP server} }\" \n"
usagestr += "\nArguments:\n <action> should be one of: %s" % \ usagestr += "\nArguments:\n <action> should be one of: %s" % \
', '.join(FileUpdater.actions) ", ".join(FileUpdater.actions)
usagestr += "\n <target> should be one of: %s" % \ usagestr += "\n <target> should be one of: %s" % \
', '.join(FileUpdater.targets) ", ".join(FileUpdater.targets)
usagestr += "\n <string> is the text to %s" % \ usagestr += "\n <string> is the text to %s" % \
', '.join(FileUpdater.actions) ", ".join(FileUpdater.actions)
parser = optparse.OptionParser(usage = usagestr) parser = optparse.OptionParser(usage=usagestr)
parser.set_defaults(userpath = None, verbose = False,) parser.set_defaults(userpath=None, verbose=False, )
parser.add_option("--userpath", dest = "userpath", type = "string", parser.add_option("--userpath", dest="userpath", type="string",
help = "use the specified user path (e.g. \"$HOME/.core" \ help="use the specified user path (e.g. \"$HOME/.core" \
"\") to access nodes.conf") "\") to access nodes.conf")
parser.add_option("-v", "--verbose", dest = "verbose", action="store_true", parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help = "be verbose when performing action") help="be verbose when performing action")
def usage(msg = None, err = 0): def usage(msg=None, err=0):
sys.stdout.write("\n") sys.stdout.write("\n")
if msg: if msg:
sys.stdout.write(msg + "\n\n") sys.stdout.write(msg + "\n\n")
@ -228,11 +229,11 @@ def main():
action = args[0] action = args[0]
if action not in FileUpdater.actions: if action not in FileUpdater.actions:
usage("invalid action '%s'" % action, 1) usage("invalid action %s" % action, 1)
target = args[1] target = args[1]
if target not in FileUpdater.targets: if target not in FileUpdater.targets:
usage("invalid target '%s'" % target, 1) usage("invalid target %s" % target, 1)
if target == "nodetype" and not options.userpath: if target == "nodetype" and not options.userpath:
usage("user path option required for this target (%s)" % target) usage("user path option required for this target (%s)" % target)
@ -249,5 +250,6 @@ def main():
sys.exit(1) sys.exit(1)
sys.exit(0) sys.exit(0)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View file

@ -1,28 +1,37 @@
# Copyright (c)2010-2012 the Boeing Company. # Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution. # See the LICENSE file included in this distribution.
import os, glob """
from distutils.core import setup Defines how CORE will be built for installation.
"""
from setuptools import setup
from core.constants import COREDPY_VERSION from core.constants import COREDPY_VERSION
setup(name = "core-python", setup(name="core-python",
version = COREDPY_VERSION, version=COREDPY_VERSION,
packages = [ packages=[
"core", "core",
"core.addons", "core.addons",
"core.api", "core.api",
"core.bsd",
"core.emane", "core.emane",
"core.misc", "core.misc",
"core.bsd",
"core.netns", "core.netns",
"core.phys", "core.phys",
"core.xen",
"core.services", "core.services",
"core.xen",
"core.xml",
], ],
description = "Python components of CORE", install_requires=[
url = "http://www.nrl.navy.mil/itd/ncs/products/core", "enum34"
author = "Boeing Research & Technology", ],
author_email = "core-dev@pf.itd.nrl.navy.mil", setup_requires=["pytest-runner"],
license = "BSD", tests_require=["pytest"],
long_description="Python scripts and modules for building virtual " \ description="Python components of CORE",
"emulated networks.") url="http://www.nrl.navy.mil/itd/ncs/products/core",
author="Boeing Research & Technology",
author_email="core-dev@pf.itd.nrl.navy.mil",
license="BSD",
long_description="Python scripts and modules for building virtual emulated networks.")

View file

@ -1,29 +1,43 @@
# Copyright (c)2010-2012 the Boeing Company. # Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution. # See the LICENSE file included in this distribution.
import os, glob from setuptools import setup, Extension
from distutils.core import setup, Extension
netns = Extension("netns", sources = ["netnsmodule.c", "netns.c"]) netns = Extension(
vcmd = Extension("vcmd", "netns",
sources = ["vcmdmodule.c", sources=[
"netnsmodule.c",
"netns.c"
]
)
vcmd = Extension(
"vcmd",
sources=[
"vcmdmodule.c",
"vnode_client.c", "vnode_client.c",
"vnode_chnl.c", "vnode_chnl.c",
"vnode_io.c", "vnode_io.c",
"vnode_msg.c", "vnode_msg.c",
"vnode_cmd.c", "vnode_cmd.c",
], ],
library_dirs = ["build/lib"], library_dirs=["build/lib"],
libraries = ["ev"]) libraries=["ev"]
)
setup(name = "core-python-netns", setup(
version = "1.0", name="core-python-netns",
description = "Extension modules to support virtual nodes using " \ version="1.0",
description="Extension modules to support virtual nodes using "
"Linux network namespaces", "Linux network namespaces",
ext_modules = [netns, vcmd], ext_modules=[
url = "http://www.nrl.navy.mil/itd/ncs/products/core", netns,
author = "Boeing Research & Technology", vcmd
author_email = "core-dev@pf.itd.nrl.navy.mil", ],
license = "BSD", url="http://www.nrl.navy.mil/itd/ncs/products/core",
long_description="Extension modules and utilities to support virtual " \ author="Boeing Research & Technology",
"nodes using Linux network namespaces") author_email="core-dev@pf.itd.nrl.navy.mil",
license="BSD",
long_description="Extension modules and utilities to support virtual "
"nodes using Linux network namespaces"
)

View file

@ -1,26 +1,26 @@
#!/usr/bin/env python #!/usr/bin/env python
import sys
import os.path import os.path
import site import site
import sys
def main(): def main():
'''\ """
Check if the given prefix is included in sys.path for the given Check if the given prefix is included in sys.path for the given
python version; if not find an alternate valid prefix. Print the python version; if not find an alternate valid prefix. Print the
result to standard out. result to standard out.
''' """
if len(sys.argv) != 3: if len(sys.argv) != 3:
msg = 'usage: %s <prefix> <python version>\n' % \ msg = "usage: %s <prefix> <python version>\n" % os.path.basename(sys.argv[0])
os.path.basename(sys.argv[0])
sys.stderr.write(msg) sys.stderr.write(msg)
return 1 return 1
python_prefix = sys.argv[1] python_prefix = sys.argv[1]
python_version = sys.argv[2] python_version = sys.argv[2]
path = '%s/lib/python%s' % (python_prefix, python_version) path = "%s/lib/python%s" % (python_prefix, python_version)
path = os.path.normpath(path) path = os.path.normpath(path)
if path[-1] != '/': if path[-1] != "/":
path = path + '/' path += "/"
prefix = None prefix = None
for p in sys.path: for p in sys.path:
if p.startswith(path): if p.startswith(path):
@ -28,8 +28,9 @@ def main():
break break
if not prefix: if not prefix:
prefix = site.PREFIXES[-1] prefix = site.PREFIXES[-1]
sys.stdout.write('%s\n' % prefix) sys.stdout.write("%s\n" % prefix)
return 0 return 0
if __name__ == '__main__':
if __name__ == "__main__":
sys.exit(main()) sys.exit(main())