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

View file

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

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

@ -3,63 +3,71 @@
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# 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
ngctl commands.
'''
"""
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):
''' Create a new Netgraph node of type and optionally assign name. The
hook string hookstr should contain two names. This is a string so
other commands may be inserted after the two names.
Return the name and netgraph ID of the new node.
'''
"""
Create a new Netgraph node of type and optionally assign name. The
hook string hookstr should contain two names. This is a string so
other commands may be inserted after the two names.
Return the name and netgraph ID of the new node.
"""
hook1 = hookstr.split()[0]
ngcmd = "mkpeer %s %s \n show .%s" % (type, hookstr, hook1)
cmd = [NGCTL_BIN, "-f", "-"]
cmdid = subprocess.Popen(cmd, stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT)
result, err = cmdid.communicate(input = ngcmd) # err will always be None
cmd = [constants.NGCTL_BIN, "-f", "-"]
cmdid = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
# err will always be None
result, err = cmdid.communicate(input=ngcmd)
status = cmdid.wait()
if status > 0:
raise Exception, "error creating Netgraph node %s (%s): %s" % \
(type, ngcmd, result)
raise Exception("error creating Netgraph node %s (%s): %s" % (type, ngcmd, result))
results = result.split()
ngname = results[1]
ngid = results[5]
if name:
check_call([NGCTL_BIN, "name", "[0x%s]:" % ngid, name])
return (ngname, ngid)
utils.check_call([constants.NGCTL_BIN, "name", "[0x%s]:" % ngid, name])
return ngname, ngid
def destroyngnode(name):
''' Shutdown a Netgraph node having the given name.
'''
check_call([NGCTL_BIN, "shutdown", "%s:" % name])
""" Shutdown a Netgraph node having the given name.
"""
utils.check_call([constants.NGCTL_BIN, "shutdown", "%s:" % name])
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
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):
''' Send a Netgraph message to the node named name.
'''
cmd = [NGCTL_BIN, "msg", "%s:" % name] + msg
check_call(cmd)
""" Send a Netgraph message to the node named name.
"""
cmd = [constants.NGCTL_BIN, "msg", "%s:" % name] + msg
utils.check_call(cmd)
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.
'''
mutecall(["kldload", name])
"""
utils.mutecall(["kldload", name])

View file

@ -6,172 +6,173 @@
# author: core-dev@pf.itd.nrl.navy.mil
#
'''
"""
nodes.py: definition of CoreNode classes and other node classes that inherit
from the CoreNode, implementing specific node types.
'''
"""
from vnode import *
from vnet import *
from core.constants import *
from core.misc.ipaddr import *
import socket
from core import constants
from core.api import coreapi
from core.bsd.netgraph import connectngnodes
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):
apitype = coreapi.CORE_NODE_DEF
apitype = NodeTypes.DEFAULT.value
class PtpNet(NetgraphPipeNet):
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.
'''
"""
pass
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.
'''
"""
tlvdata = ""
if len(self._netif) != 2:
return tlvdata
(if1, if2) = self._netif.items()
if1 = if1[1]
if2 = if2[1]
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
if1.node.objid)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
if2.node.objid)
delay = if1.getparam('delay')
bw = if1.getparam('bw')
loss = if1.getparam('loss')
duplicate = if1.getparam('duplicate')
jitter = if1.getparam('jitter')
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N1_NUMBER.value, if1.node.objid)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.N2_NUMBER.value, if2.node.objid)
delay = if1.getparam("delay")
bw = if1.getparam("bw")
loss = if1.getparam("loss")
duplicate = if1.getparam("duplicate")
jitter = if1.getparam("jitter")
if delay is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_DELAY,
delay)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DELAY.value, delay)
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:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_PER,
str(loss))
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.PER.value, str(loss))
if duplicate is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_DUP,
str(duplicate))
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DUP.value, str(duplicate))
if jitter is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_JITTER,
jitter)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_TYPE,
self.linktype)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.JITTER.value, jitter)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.TYPE.value, self.linktype)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \
if1.node.getifindex(if1))
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE1_NUMBER.value, if1.node.getifindex(if1))
if if1.hwaddr:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1MAC,
if1.hwaddr)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE1_MAC.value, if1.hwaddr)
for addr in if1.addrlist:
(ip, sep, mask) = addr.partition('/')
(ip, sep, mask) = addr.partition("/")
mask = int(mask)
if isIPv4Address(ip):
family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP4
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP4MASK
if ipaddress.is_ipv4_address(ip):
family = socket.AF_INET
tlvtypeip = LinkTlvs.INTERFACE1_IP4.value
tlvtypemask = LinkTlvs.INTERFACE1_IP4_MASK
else:
family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP6MASK
family = socket.AF_INET6
tlvtypeip = LinkTlvs.INTERFACE1_IP6.value
tlvtypemask = LinkTlvs.INTERFACE1_IP6_MASK.value
ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip,
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, ipaddress.IpAddress(af=family, address=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \
if2.node.getifindex(if2))
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_NUMBER.value, if2.node.getifindex(if2))
if if2.hwaddr:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2MAC,
if2.hwaddr)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.INTERFACE2_MAC.value, if2.hwaddr)
for addr in if2.addrlist:
(ip, sep, mask) = addr.partition('/')
(ip, sep, mask) = addr.partition("/")
mask = int(mask)
if isIPv4Address(ip):
family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP4
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP4MASK
if ipaddress.is_ipv4_address(ip):
family = socket.AF_INET
tlvtypeip = LinkTlvs.INTERFACE2_IP4.value
tlvtypemask = LinkTlvs.INTERFACE2_IP4_MASK
else:
family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP6MASK
family = socket.AF_INET6
tlvtypeip = LinkTlvs.INTERFACE2_IP6.value
tlvtypemask = LinkTlvs.INTERFACE2_IP6_MASK.value
ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip,
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, ipaddress.IpAddress(af=family, address=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
return [msg,]
return [msg, ]
class SwitchNode(NetgraphNet):
ngtype = "bridge"
nghooks = "link0 link0\nmsg .link0 setpersistent"
apitype = coreapi.CORE_NODE_SWITCH
apitype = NodeTypes.SWITCH.value
policy = "ACCEPT"
class HubNode(NetgraphNet):
ngtype = "hub"
nghooks = "link0 link0\nmsg .link0 setpersistent"
apitype = coreapi.CORE_NODE_HUB
apitype = NodeTypes.HUB.value
policy = "ACCEPT"
class WlanNode(NetgraphNet):
ngtype = "wlan"
nghooks = "anchor anchor"
apitype = coreapi.CORE_NODE_WLAN
linktype = coreapi.CORE_LINK_WIRELESS
apitype = NodeTypes.WIRELESS_LAN.value
linktype = LinkTypes.WIRELESS.value
policy = "DROP"
def __init__(self, session, objid = None, name = None, verbose = False,
start = True, policy = None):
def __init__(self, session, objid=None, name=None, verbose=False,
start=True, policy=None):
NetgraphNet.__init__(self, session, objid, name, verbose, start, policy)
# wireless model such as basic range
self.model = None
# mobility model such as scripted
self.mobility = None
def attach(self, netif):
NetgraphNet.attach(self, netif)
if self.model:
netif.poshook = self.model._positioncallback
if netif.node is None:
return
(x,y,z) = netif.node.position.get()
x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z)
def setmodel(self, model, config):
''' Mobility and wireless model.
'''
if (self.verbose):
""" Mobility and wireless model.
"""
if self.verbose:
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,
verbose=self.verbose, values=config)
if self.model._positioncallback:
for netif in self.netifs():
netif.poshook = self.model._positioncallback
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)
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,
verbose=self.verbose, values=config)
class RJ45Node(NetgraphPipeNet):
apitype = coreapi.CORE_NODE_RJ45
apitype = NodeTypes.RJ45.value
policy = "ACCEPT"
def __init__(self, session, objid, name, verbose, start = True):
def __init__(self, session, objid, name, verbose, start=True):
if start:
ngloadkernelmodule("ng_ether")
NetgraphPipeNet.__init__(self, session, objid, name, verbose, start)
@ -186,18 +187,18 @@ class RJ45Node(NetgraphPipeNet):
p = "promisc"
if not promisc:
p = "-" + p
check_call([IFCONFIG_BIN, self.name, "up", p])
utils.check_call([constants.IFCONFIG_BIN, self.name, "up", p])
def attach(self, netif):
if len(self._netif) > 0:
raise ValueError, \
"RJ45 networks support at most 1 network interface"
"RJ45 networks support at most 1 network interface"
NetgraphPipeNet.attach(self, netif)
connectngnodes(self.ngname, self.name, self.gethook(), "lower")
class TunnelNode(NetgraphNet):
ngtype = "pipe"
nghooks = "upper lower"
apitype = coreapi.CORE_NODE_TUNNEL
apitype = NodeTypes.TUNNEL.value
policy = "ACCEPT"

View file

@ -5,25 +5,25 @@
#
# authors: core-dev@pf.itd.nrl.navy.mil
#
'''
"""
vnet.py: NetgraphNet and NetgraphPipeNet classes that implement virtual networks
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):
ngtype = None
nghooks = ()
def __init__(self, session, objid = None, name = None, verbose = False,
start = True, policy = None):
def __init__(self, session, objid=None, name=None, verbose=False,
start=True, policy=None):
PyCoreNet.__init__(self, session, objid, name)
if name is None:
name = str(self.objid)
@ -40,8 +40,7 @@ class NetgraphNet(PyCoreNet):
self.startup()
def startup(self):
tmp, self.ngid = createngnode(type=self.ngtype, hookstr=self.nghooks,
name=self.ngname)
tmp, self.ngid = createngnode(type=self.ngtype, hookstr=self.nghooks, name=self.ngname)
self.up = True
def shutdown(self):
@ -62,13 +61,12 @@ class NetgraphNet(PyCoreNet):
destroyngnode(self.ngname)
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.
(Note that the PtpNet subclass overrides this method.)
'''
"""
if self.up:
pipe = self.session.addobj(cls = NetgraphPipeNet,
verbose = self.verbose, start = True)
pipe = self.session.addobj(cls=NetgraphPipeNet, verbose=self.verbose, start=True)
pipe.attach(netif)
hook = "link%d" % len(self._netif)
pipe.attachnet(self, hook)
@ -82,14 +80,16 @@ class NetgraphNet(PyCoreNet):
def linked(self, netif1, netif2):
# check if the network interfaces are attached to this network
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:
raise ValueError, "inconsistency for netif %s" % netif2.name
raise ValueError("inconsistency for netif %s" % netif2.name)
try:
linked = self._linked[netif1][netif2]
except KeyError:
linked = False
self._linked[netif1][netif2] = linked
return linked
def unlink(self, netif1, netif2):
@ -109,108 +109,106 @@ class NetgraphNet(PyCoreNet):
self._linked[netif1][netif2] = True
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.
'''
"""
raise NotImplementedError
def linkconfig(self, netif, bw = None, delay = None,
loss = None, duplicate = None, jitter = None, netif2=None):
''' Set link effects by modifying the pipe connected to an interface.
'''
def linkconfig(self, netif, bw=None, delay=None,
loss=None, duplicate=None, jitter=None, netif2=None):
""" Set link effects by modifying the pipe connected to an interface.
"""
if not netif.pipe:
self.warn("linkconfig for %s but interface %s has no pipe" % \
(self.name, netif.name))
self.warn("linkconfig for %s but interface %s has no pipe" % (self.name, netif.name))
return
return netif.pipe.linkconfig(netif, bw, delay, loss, duplicate, jitter,
netif2)
return netif.pipe.linkconfig(netif, bw, delay, loss, duplicate, jitter, netif2)
class NetgraphPipeNet(NetgraphNet):
ngtype = "pipe"
nghooks = "upper lower"
def __init__(self, session, objid = None, name = None, verbose = False,
start = True, policy = None):
def __init__(self, session, objid=None, name=None, verbose=False,
start=True, policy=None):
NetgraphNet.__init__(self, session, objid, name, verbose, start, policy)
if start:
# account for Ethernet header
ngmessage(self.ngname, ["setcfg", "{", "header_offset=14", "}"])
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
connected to the "lower" hook.
'''
"""
if len(self._netif) > 1:
raise ValueError, \
"Netgraph pipes support at most 2 network interfaces"
"Netgraph pipes support at most 2 network interfaces"
if self.up:
hook = self.gethook()
connectngnodes(self.ngname, netif.localname, hook, netif.hook)
if netif.pipe:
raise ValueError, \
"Interface %s already attached to pipe %s" % \
(netif.name, netif.pipe.name)
"Interface %s already attached to pipe %s" % \
(netif.name, netif.pipe.name)
netif.pipe = self
self._netif[netif] = netif
self._linked[netif] = {}
def attachnet(self, net, hook):
''' Attach another NetgraphNet to this pipe node.
'''
""" Attach another NetgraphNet to this pipe node.
"""
localhook = self.gethook()
connectngnodes(self.ngname, net.ngname, localhook, hook)
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.
'''
"""
hooks = self.nghooks.split()
if len(self._netif) == 0:
return hooks[0]
else:
return hooks[1]
def linkconfig(self, netif, bw = None, delay = None,
loss = None, duplicate = None, jitter = None, netif2 = None):
''' Set link effects by sending a Netgraph setcfg message to the pipe.
'''
netif.setparam('bw', bw)
netif.setparam('delay', delay)
netif.setparam('loss', loss)
netif.setparam('duplicate', duplicate)
netif.setparam('jitter', jitter)
def linkconfig(self, netif, bw=None, delay=None,
loss=None, duplicate=None, jitter=None, netif2=None):
""" Set link effects by sending a Netgraph setcfg message to the pipe.
"""
netif.setparam("bw", bw)
netif.setparam("delay", delay)
netif.setparam("loss", loss)
netif.setparam("duplicate", duplicate)
netif.setparam("jitter", jitter)
if not self.up:
return
params = []
upstream = []
downstream = []
if bw is not None:
if str(bw)=="0":
bw="-1"
params += ["bandwidth=%s" % bw,]
if str(bw) == "0":
bw = "-1"
params += ["bandwidth=%s" % bw, ]
if delay is not None:
if str(delay)=="0":
delay="-1"
params += ["delay=%s" % delay,]
if str(delay) == "0":
delay = "-1"
params += ["delay=%s" % delay, ]
if loss is not None:
if str(loss)=="0":
loss="-1"
upstream += ["BER=%s" % loss,]
downstream += ["BER=%s" % loss,]
if str(loss) == "0":
loss = "-1"
upstream += ["BER=%s" % loss, ]
downstream += ["BER=%s" % loss, ]
if duplicate is not None:
if str(duplicate)=="0":
duplicate="-1"
upstream += ["duplicate=%s" % duplicate,]
downstream += ["duplicate=%s" % duplicate,]
if str(duplicate) == "0":
duplicate = "-1"
upstream += ["duplicate=%s" % duplicate, ]
downstream += ["duplicate=%s" % duplicate, ]
if jitter:
self.warn("jitter parameter ignored for link %s" % self.name)
if len(params) > 0 or len(upstream) > 0 or len(downstream) > 0:
setcfg = ["setcfg", "{",] + params
setcfg = ["setcfg", "{", ] + params
if len(upstream) > 0:
setcfg += ["upstream={",] + upstream + ["}",]
setcfg += ["upstream={", ] + upstream + ["}", ]
if len(downstream) > 0:
setcfg += ["downstream={",] + downstream + ["}",]
setcfg += ["}",]
setcfg += ["downstream={", ] + downstream + ["}", ]
setcfg += ["}", ]
ngmessage(self.ngname, setcfg)

View file

@ -3,27 +3,32 @@
# Copyright (c)2010-2012 the Boeing Company.
# See the LICENSE file included in this distribution.
#
# 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
jail-based virtual node.
'''
"""
import os, signal, sys, subprocess, threading, string
import random, time
from core.misc.utils import *
from core.constants import *
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position
from core.emane.nodes import EmaneNode
from core.bsd.netgraph import *
import os
import subprocess
import threading
from core import constants
from core.bsd.netgraph import createngnode
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):
def __init__(self, node, name, localname, mtu = 1500, net = None,
start = True):
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu)
def __init__(self, node, name, localname, mtu=1500, net=None,
start=True):
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
# name is the device name (e.g. ngeth0, ngeth1, etc.) before it is
# installed in a node; the Netgraph name is renamed to localname
# e.g. before install: name = ngeth0 localname = n0_0_123
@ -45,7 +50,7 @@ class VEth(PyCoreNetIf):
name=self.localname)
self.name = ngname
self.ngid = ngid
check_call([IFCONFIG_BIN, ngname, "up"])
utils.check_call([constants.IFCONFIG_BIN, ngname, "up"])
self.up = True
def shutdown(self):
@ -74,15 +79,18 @@ class VEth(PyCoreNetIf):
def sethwaddr(self, addr):
self.hwaddr = addr
class TunTap(PyCoreNetIf):
'''TUN/TAP virtual device in TAP mode'''
def __init__(self, node, name, localname, mtu = None, net = None,
start = True):
"""TUN/TAP virtual device in TAP mode"""
def __init__(self, node, name, localname, mtu=None, net=None,
start=True):
raise NotImplementedError
class SimpleJailNode(PyCoreNode):
def __init__(self, session, objid = None, name = None, nodedir = None,
verbose = False):
def __init__(self, session, objid=None, name=None, nodedir=None,
verbose=False):
PyCoreNode.__init__(self, session, objid, name)
self.nodedir = nodedir
self.verbose = verbose
@ -94,17 +102,17 @@ class SimpleJailNode(PyCoreNode):
def startup(self):
if self.up:
raise Exception, "already up"
vimg = [VIMAGE_BIN, "-c", self.name]
vimg = [constants.VIMAGE_BIN, "-c", self.name]
try:
os.spawnlp(os.P_WAIT, VIMAGE_BIN, *vimg)
os.spawnlp(os.P_WAIT, constants.VIMAGE_BIN, *vimg)
except OSError:
raise Exception, ("vimage command not found while running: %s" % \
vimg)
vimg)
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.cmd(["hostname", self.name])
self.cmd([SYSCTL_BIN, "vfs.morphing_symlinks=1"])
self.cmd([constants.SYSCTL_BIN, "vfs.morphing_symlinks=1"])
self.up = True
def shutdown(self):
@ -114,27 +122,26 @@ class SimpleJailNode(PyCoreNode):
netif.shutdown()
self._netif.clear()
del self.session
vimg = [VIMAGE_BIN, "-d", self.name]
vimg = [constants.VIMAGE_BIN, "-d", self.name]
try:
os.spawnlp(os.P_WAIT, VIMAGE_BIN, *vimg)
os.spawnlp(os.P_WAIT, constants.VIMAGE_BIN, *vimg)
except OSError:
raise Exception, ("vimage command not found while running: %s" % \
vimg)
raise Exception("vimage command not found while running: %s" % vimg)
self.up = False
def cmd(self, args, wait = True):
def cmd(self, args, wait=True):
if wait:
mode = os.P_WAIT
else:
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:
tmp = None
if tmp:
self.warn("cmd exited with status %s: %s" % (tmp, str(args)))
return tmp
def cmdresult(self, args, wait = True):
def cmdresult(self, args, wait=True):
cmdid, cmdin, cmdout, cmderr = self.popen(args)
result = cmdout.read()
result += cmderr.read()
@ -145,30 +152,30 @@ class SimpleJailNode(PyCoreNode):
status = cmdid.wait()
else:
status = 0
return (status, result)
return status, result
def popen(self, args):
cmd = [VIMAGE_BIN, self.name]
cmd = [constants.VIMAGE_BIN, self.name]
cmd.extend(args)
tmp = subprocess.Popen(cmd, stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd=self.nodedir)
tmp = subprocess.Popen(cmd, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE, cwd=self.nodedir)
return tmp, tmp.stdin, tmp.stdout, tmp.stderr
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"):
return os.spawnlp(os.P_WAIT, "xterm", "xterm", "-ut",
"-title", self.name, "-e", VIMAGE_BIN, self.name, sh)
def term(self, sh="/bin/sh"):
return os.spawnlp(os.P_WAIT, "xterm", "xterm", "-ut",
"-title", self.name, "-e", constants.VIMAGE_BIN, self.name, sh)
def termcmdstring(self, sh = "/bin/sh"):
''' We add 'sudo' to the command string because the GUI runs as a
def termcmdstring(self, sh="/bin/sh"):
""" We add "sudo" to the command string because the GUI runs as a
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])
def boot(self):
@ -180,9 +187,9 @@ class SimpleJailNode(PyCoreNode):
self.addsymlink(path=target, file=None)
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()
try:
if ifindex is None:
@ -191,15 +198,17 @@ class SimpleJailNode(PyCoreNode):
ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid()
name = "n%s_%s_%s" % (self.objid, ifindex, sessionid)
localname = name
localname = name
ifclass = VEth
veth = ifclass(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:
# 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"
self.cmd([IFCONFIG_BIN, veth.name, "name", ifname])
self.cmd([constants.IFCONFIG_BIN, veth.name, "name", ifname])
veth.name = ifname
try:
self.addnetif(veth, ifindex)
@ -214,17 +223,17 @@ class SimpleJailNode(PyCoreNode):
def sethwaddr(self, ifindex, addr):
self._netif[ifindex].sethwaddr(addr)
if self.up:
self.cmd([IFCONFIG_BIN, self.ifname(ifindex), "link",
str(addr)])
self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), "link",
str(addr)])
def addaddr(self, ifindex, addr):
if self.up:
if ':' in addr:
if ":" in addr:
family = "inet6"
else:
family = "inet"
self.cmd([IFCONFIG_BIN, self.ifname(ifindex), family, "alias",
str(addr)])
self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), family, "alias",
str(addr)])
self._netif[ifindex].addaddr(addr)
def deladdr(self, ifindex, addr):
@ -233,40 +242,40 @@ class SimpleJailNode(PyCoreNode):
except ValueError:
self.warn("trying to delete unknown address: %s" % addr)
if self.up:
if ':' in addr:
if ":" in addr:
family = "inet6"
else:
family = "inet"
self.cmd([IFCONFIG_BIN, self.ifname(ifindex), family, "-alias",
str(addr)])
self.cmd([constants.IFCONFIG_BIN, self.ifname(ifindex), family, "-alias",
str(addr)])
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:
if t not in self.valid_deladdrtype:
raise ValueError, "addr type must be in: " + \
" ".join(self.valid_deladdrtype)
" ".join(self.valid_deladdrtype)
for a in addr[t]:
self.deladdr(ifindex, a)
# update cached information
self.getaddr(self.ifname(ifindex), rescan = True)
self.getaddr(self.ifname(ifindex), rescan=True)
def ifup(self, ifindex):
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,
ifindex = None, ifname = None):
def newnetif(self, net=None, addrlist=[], hwaddr=None,
ifindex=None, ifname=None):
self.lock.acquire()
try:
ifindex = self.newveth(ifindex = ifindex, ifname = ifname,
net = net)
ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net)
if net is not None:
self.attachnet(ifindex, net)
if hwaddr:
self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist):
for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr)
self.ifup(ifindex)
return ifindex
@ -280,18 +289,17 @@ class SimpleJailNode(PyCoreNode):
self._netif[ifindex].detachnet()
def addfile(self, srcname, filename):
shcmd = "mkdir -p $(dirname '%s') && mv '%s' '%s' && sync" % \
(filename, srcname, filename)
shcmd = 'mkdir -p $(dirname "%s") && mv "%s" "%s" && sync' % (filename, srcname, filename)
self.shcmd(shcmd)
def getaddr(self, ifname, rescan = False):
def getaddr(self, ifname, rescan=False):
return None
#return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan)
# return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan)
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
'''
"""
dirname = path
if dirname and dirname[0] == "/":
dirname = dirname[1:]
@ -316,14 +324,14 @@ class SimpleJailNode(PyCoreNode):
self.info("creating symlink %s -> %s" % (pathname, sym))
os.symlink(sym, pathname)
class JailNode(SimpleJailNode):
def __init__(self, session, objid = None, name = None,
nodedir = None, bootsh = "boot.sh", verbose = False,
start = True):
super(JailNode, self).__init__(session = session, objid = objid,
name = name, nodedir = nodedir,
verbose = verbose)
class JailNode(SimpleJailNode):
def __init__(self, session, objid=None, name=None,
nodedir=None, bootsh="boot.sh", verbose=False,
start=True):
super(JailNode, self).__init__(session=session, objid=objid,
name=name, nodedir=nodedir,
verbose=verbose)
self.bootsh = bootsh
if not start:
return
@ -341,8 +349,8 @@ class JailNode(SimpleJailNode):
self.lock.acquire()
try:
super(JailNode, self).startup()
#self.privatedir("/var/run")
#self.privatedir("/var/log")
# self.privatedir("/var/run")
# self.privatedir("/var/log")
finally:
self.lock.release()
@ -351,7 +359,7 @@ class JailNode(SimpleJailNode):
return
self.lock.acquire()
# services are instead stopped when session enters datacollect state
#self.session.services.stopnodeservices(self)
# self.session.services.stopnodeservices(self)
try:
super(JailNode, self).shutdown()
finally:
@ -362,7 +370,7 @@ class JailNode(SimpleJailNode):
if path[0] != "/":
raise ValueError, "path not fully qualified: " + path
hostpath = os.path.join(self.nodedir,
os.path.normpath(path).strip('/').replace('/', '.'))
os.path.normpath(path).strip("/").replace("/", "."))
try:
os.mkdir(hostpath)
except OSError:
@ -371,9 +379,9 @@ class JailNode(SimpleJailNode):
raise Exception, e
self.mount(hostpath, path)
def opennodefile(self, filename, mode = "w"):
def opennodefile(self, filename, mode="w"):
dirname, basename = os.path.split(filename)
#self.addsymlink(path=dirname, file=basename)
# self.addsymlink(path=dirname, file=basename)
if not basename:
raise ValueError, "no basename for filename: " + filename
if dirname and dirname[0] == "/":
@ -381,14 +389,13 @@ class JailNode(SimpleJailNode):
dirname = dirname.replace("/", ".")
dirname = os.path.join(self.nodedir, dirname)
if not os.path.isdir(dirname):
os.makedirs(dirname, mode = 0755)
os.makedirs(dirname, mode=0755)
hostfilename = os.path.join(dirname, basename)
return open(hostfilename, mode)
def nodefile(self, filename, contents, mode = 0644):
def nodefile(self, filename, contents, mode=0644):
f = self.opennodefile(filename, "w")
f.write(contents)
os.chmod(f.name, mode)
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,81 +1,112 @@
#
# CORE
# 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
'''
"""
Common support for configurable CORE objects.
"""
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):
''' A generic class for managing Configurables. This class can register
with a session to receive Config Messages for setting some parameters
for itself or for the Configurables that it manages.
'''
"""
A generic class for managing Configurables. This class can register
with a session to receive Config Messages for setting some parameters
for itself or for the Configurables that it manages.
"""
# name corresponds to configuration object field
_name = ""
# type corresponds with register message types
_type = None
def __init__(self, session=None):
self.session = session
self.session.addconfobj(self._name, self._type, self.configure)
# Configurable key=values, indexed by node number
name = ""
# type corresponds with register message types
config_type = None
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 = {}
def configure(self, session, msg):
''' Handle configure messages. The configuration message sent to a
ConfigurableManager usually is used to:
1. Request a list of Configurables (request flag)
2. Reset manager and clear configs (reset flag)
3. Send values that configure the manager or one of its
Configurables
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)
# TODO: fix the need for this and isolate to the mobility class that wants it
self._modelclsmap = {}
def configure(self, session, config_data):
"""
Handle configure messages. The configuration message sent to a
ConfigurableManager usually is used to:
1. Request a list of Configurables (request flag)
2. Reset manager and clear configs (reset flag)
3. Send values that configure the manager or one of its
Configurables
Returns any reply messages.
:param core.session.Session session: CORE session object
: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(msg,
msg.gettlv(coreapi.CORE_TLV_CONF_VALUES))
return self.configure_values(config_data)
def configure_request(self, msg):
''' Request configuration 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
def configure_reset(self, msg):
''' By default, resets this manager to clear configs.
'''
def configure_reset(self, config_data):
"""
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()
def configure_values(self, msg, values):
''' Values have been sent to this manager.
'''
def configure_values(self, config_data):
"""
Values have been sent to this manager.
:param ConfigData config_data: configuration data for carrying out a configuration
:return: nothing
"""
return None
def configure_values_keyvalues(self, msg, values, target, keys):
''' Helper that can be used for configure_values for parsing in
'key=value' strings from a values field. The key name must be
in the keys list, and target.key=value is set.
'''
def configure_values_keyvalues(self, config_data, target, keys):
"""
Helper that can be used for configure_values for parsing in
'key=value' strings from a values field. The key name must be
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:
return None
kvs = values.split('|')
for kv in kvs:
try:
# key=value
(key, value) = kv.split('=', 1)
key, value = kv.split('=', 1)
if value is not None and not value.strip():
value = None
except ValueError:
@ -83,25 +114,38 @@ class ConfigurableManager(object):
key = keys[kvs.index(kv)]
value = kv
if key not in keys:
raise ValueError, "invalid key: %s" % key
raise ValueError("invalid key: %s" % key)
if value is not None:
setattr(target, key, value)
return None
def reset(self):
"""
Reset functionality for the configurable class.
:return: nothing
"""
return None
def setconfig(self, nodenum, conftype, values):
''' add configuration values for a node to a dictionary; values are
usually received from a Configuration Message, and may refer to a
node for which no object exists yet
'''
"""
Add configuration values for a node to a dictionary; values are
usually received from a Configuration Message, and may refer to a
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 = []
if nodenum in self.configs:
oldlist = self.configs[nodenum]
found = False
for (t, v) in oldlist:
if (t == conftype):
for t, v in oldlist:
if t == conftype:
# replace existing config
found = True
conflist.append((conftype, values))
@ -114,34 +158,52 @@ class ConfigurableManager(object):
self.configs[nodenum] = conflist
def getconfig(self, nodenum, conftype, defaultvalues):
''' get configuration values for a node; if the values don't exist in
our dictionary then return the default values supplied
'''
"""
Get configuration values for a node; if the values don't exist in
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:
# return configured values
conflist = self.configs[nodenum]
for (t, v) in conflist:
if (conftype is None) or (t == conftype):
return (t, v)
for t, v in conflist:
if conftype is None or t == conftype:
return t, v
# return default values provided (may be None)
return (conftype, defaultvalues)
return conftype, defaultvalues
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.
'''
:param bool use_clsmap: should a class map be used, default to True
:return: list of all configurations
:rtype: list
"""
r = []
for nodenum in self.configs:
for (t, v) in self.configs[nodenum]:
for t, v in self.configs[nodenum]:
if use_clsmap:
t = self._modelclsmap[t]
r.append( (nodenum, t, v) )
r.append((nodenum, t, v))
return r
def clearconfig(self, nodenum):
''' remove configuration values for the specified node;
when nodenum is None, remove all configuration values
'''
"""
remove configuration values for the specified node;
when nodenum is None, remove all configuration values
:param int nodenum: node id
:return: nothing
"""
if nodenum is None:
self.configs = {}
return
@ -149,10 +211,16 @@ class ConfigurableManager(object):
self.configs.pop(nodenum)
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:
self.warn("Unknown model type '%s'" % (conftype))
logger.warn("Unknown model type '%s'" % conftype)
return
model = self._modelclsmap[conftype]
keys = model.getnames()
@ -160,19 +228,24 @@ class ConfigurableManager(object):
values = list(model.getdefaultvalues())
for key, value in keyvalues:
if key not in keys:
self.warn("Skipping unknown configuration key for %s: '%s'" % \
(conftype, key))
logger.warn("Skipping unknown configuration key for %s: '%s'" % \
(conftype, key))
continue
i = keys.index(key)
values[i] = value
self.setconfig(nodenum, conftype, values)
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.
This assumes self.configs contains an iterable of (model-names, values)
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 = []
if n.objid in self.configs:
v = self.configs[n.objid]
@ -183,92 +256,119 @@ class ConfigurableManager(object):
return r
def info(self, msg):
self.session.info(msg)
def warn(self, msg):
self.session.warn(msg)
class Configurable(object):
''' A generic class for managing configuration parameters.
Parameters are sent via Configuration Messages, which allow the GUI
to build dynamic dialogs depending on what is being configured.
'''
_name = ""
"""
A generic class for managing configuration parameters.
Parameters are sent via Configuration Messages, which allow the GUI
to build dynamic dialogs depending on what is being configured.
"""
name = ""
# Configuration items:
# ('name', 'type', 'default', 'possible-value-list', 'caption')
_confmatrix = []
_confgroups = None
_bitmap = None
def __init__(self, session=None, objid=None):
config_matrix = []
config_groups = None
bitmap = 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.objid = objid
self.object_id = object_id
def reset(self):
"""
Reset method.
:return: nothing
"""
pass
def register(self):
"""
Register method.
:return: nothing
"""
pass
@classmethod
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
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
def configure(cls, mgr, msg):
''' Handle configuration messages for this object.
'''
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)
ifacenum = msg.gettlv(coreapi.CORE_TLV_CONF_IFNUM)
if ifacenum is not None:
nodenum = nodenum*1000 + ifacenum
def configure(cls, manager, config_data):
"""
Handle configuration messages for this object.
if mgr.verbose:
mgr.info("received configure message for %s nodenum:%s" % (cls._name, str(nodenum)))
if conftype == coreapi.CONF_TYPE_FLAGS_REQUEST:
if mgr.verbose:
mgr.info("replying to configure request for %s model" %
cls._name)
:param ConfigurableManager manager: configuration manager
:param config_data: configuration data
:return: configuration data object
:rtype: ConfigData
"""
reply = None
node_id = config_data.node
object_name = config_data.object
config_type = config_data.type
interface_id = config_data.interface_number
values_str = config_data.data_values
if interface_id is not None:
node_id = node_id * 1000 + interface_id
logger.info("received configure message for %s nodenum:%s", cls.name, str(node_id))
if config_type == ConfigFlags.REQUEST.value:
logger.info("replying to configure request for %s model", cls.name)
# 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
# reply with the defaults for this model
if objname == "all":
if object_name == "all":
defaults = None
typeflags = coreapi.CONF_TYPE_FLAGS_UPDATE
typeflags = ConfigFlags.UPDATE.value
else:
defaults = cls.getdefaultvalues()
typeflags = coreapi.CONF_TYPE_FLAGS_NONE
values = mgr.getconfig(nodenum, cls._name, defaults)[1]
typeflags = ConfigFlags.coreapi.CONF_TYPE_FLAGS_NONE
values = manager.getconfig(node_id, cls.name, defaults)[1]
if values is None:
# node has no active config for this model (don't send defaults)
return None
# reply with config options
reply = cls.toconfmsg(0, nodenum, typeflags, values)
elif conftype == coreapi.CONF_TYPE_FLAGS_RESET:
if objname == "all":
mgr.clearconfig(nodenum)
#elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE:
reply = cls.config_data(0, node_id, typeflags, values)
elif config_type == ConfigFlags.RESET.value:
if object_name == "all":
manager.clearconfig(node_id)
# elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE:
else:
# store the configuration values for later use, when the node
# object has been created
if objname is None:
mgr.info("no configuration object for node %s" % nodenum)
if object_name is None:
logger.info("no configuration object for node %s", node_id)
return None
values_str = msg.gettlv(coreapi.CORE_TLV_CONF_VALUES)
defaults = cls.getdefaultvalues()
if values_str is None:
# use default or preconfigured values
values = mgr.getconfig(nodenum, cls._name, defaults)[1]
values = manager.getconfig(node_id, cls.name, defaults)[1]
else:
# use new values supplied from the conf message
values = values_str.split('|')
@ -282,61 +382,69 @@ class Configurable(object):
try:
new_values[keys.index(key)] = value
except ValueError:
mgr.info("warning: ignoring invalid key '%s'" % key)
logger.info("warning: ignoring invalid key '%s'" % key)
values = new_values
mgr.setconfig(nodenum, objname, values)
manager.setconfig(node_id, object_name, values)
return reply
@classmethod
def toconfmsg(cls, flags, nodenum, typeflags, values):
''' Convert this class to a Config API message. Some TLVs are defined
by the class, but node number, conf type flags, and values must
be passed in.
'''
def config_data(cls, flags, node_id, type_flags, values):
"""
Convert this class to a Config API message. Some TLVs are defined
by the class, but node number, conf type flags, and values must
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()
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, '|')
tlvdata = ""
if nodenum is not None:
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_NODE,
nodenum)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OBJ,
cls._name)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_TYPE,
typeflags)
datatypes = tuple( map(lambda x: x[1], cls._confmatrix) )
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_DATA_TYPES,
datatypes)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_VALUES,
values_str)
captions = reduce( lambda a,b: a + '|' + b, \
map(lambda x: x[4], cls._confmatrix))
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
datatypes = tuple(map(lambda x: x[1], cls.config_matrix))
captions = reduce(lambda a, b: a + '|' + b, map(lambda x: x[4], cls.config_matrix))
possible_valuess = reduce(lambda a, b: a + '|' + b, map(lambda x: x[3], cls.config_matrix))
return ConfigData(
message_type=flags,
node=node_id,
object=cls.name,
type=type_flags,
data_types=datatypes,
data_values=values_str,
captions=captions,
possible_values=possible_valuess,
bitmap=cls.bitmap,
groups=cls.config_groups
)
@staticmethod
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":
return "on"
else:
return "off"
@staticmethod
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 value.lower() == "on":
return 1
@ -345,36 +453,51 @@ class Configurable(object):
return value
@classmethod
def valueof(cls, name, values):
''' Helper to return a value by the name defined in confmatrix.
Checks if it is boolean'''
def valueof(cls, name, values):
"""
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)
if cls._confmatrix[i][1] == coreapi.CONF_DATA_TYPE_BOOL and \
values[i] != "":
if cls.config_matrix[i][1] == ConfigDataTypes.BOOL.value and values[i] != "":
return cls.booltooffon(values[i])
else:
return values[i]
@staticmethod
def haskeyvalues(values):
''' Helper to check for list of key=value pairs versus a plain old
list of values. Returns True if all elements are "key=value".
'''
"""
Helper to check for list of key=value pairs versus a plain old
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:
return False
for v in values:
if "=" not in v:
return False
return True
def getkeyvaluelist(self):
''' Helper to return a list of (key, value) tuples. Keys come from
self._confmatrix and values are instance attributes.
'''
r = []
for k in self.getnames():
if hasattr(self, k):
r.append((k, getattr(self, k)))
return r
"""
Helper to return a list of (key, value) tuples. Keys come from
configuration matrix and values are instance attributes.
: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
# Copyright (c)2010-2013 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>
#
'''
coreobj.py: defines the basic objects for emulation: the PyCoreObj base class,
along with PyCoreNode, PyCoreNet, and PyCoreNetIf
'''
import sys, threading, os, shutil
"""
Defines the basic objects for CORE emulation: the PyCoreObj base class, along with PyCoreNode,
PyCoreNet, and PyCoreNetIf.
"""
import os
import shutil
import socket
import threading
from socket import AF_INET
from socket import AF_INET6
from core.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):
''' 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)
"""
Helper class for Cartesian coordinate position
"""
def set(self, x = None, y = None, z = None):
''' Returns True if the position has actually changed.
'''
def __init__(self, x=None, y=None, z=None):
"""
Creates a Position instance.
:param x: x position
:param y: y position
:param z: z position
:return:
"""
self.x = x
self.y = y
self.z = z
def set(self, x=None, y=None, z=None):
"""
Returns True if the position has actually changed.
:param 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:
return False
self.x = x
@ -35,20 +53,36 @@ class Position(object):
return True
def get(self):
''' Fetch the (x,y,z) position tuple.
'''
return (self.x, self.y, self.z)
"""
Retrieve x,y,z position.
:return: x,y,z position tuple
:rtype: tuple
"""
return self.x, self.y, self.z
class PyCoreObj(object):
''' Base class for pycore objects (nodes and nets)
'''
"""
Base class for CORE objects (nodes and networks)
"""
apitype = None
def __init__(self, session, objid = None, name = None, verbose = False,
start = True):
# TODO: appears start has no usage, verify and remove
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
if objid is None:
objid = session.getobjid()
objid = session.get_object_id()
self.objid = objid
if name is None:
name = "o%s" % self.objid
@ -59,286 +93,414 @@ class PyCoreObj(object):
self.canvas = None
self.icon = None
self.opaque = None
self.verbose = verbose
self.position = Position()
def startup(self):
''' Each object implements its own startup method.
'''
"""
Each object implements its own startup method.
:return: nothing
"""
raise NotImplementedError
def shutdown(self):
''' Each object implements its own shutdown method.
'''
"""
Each object implements its own shutdown method.
:return: nothing
"""
raise NotImplementedError
def setposition(self, x = None, y = None, z = None):
''' Set the (x,y,z) position of the object.
'''
return self.position.set(x = x, y = y, z = z)
def setposition(self, x=None, y=None, z=None):
"""
Set the (x,y,z) position of the object.
: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):
''' 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()
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):
''' 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:
return map(lambda k: self._netif[k], sorted(self._netif.keys()))
else:
return self._netif.itervalues()
def numnetif(self):
''' Return the attached interface count.
'''
"""
Return the attached interface count.
:return: number of network interfaces
:rtype: int
"""
return len(self._netif)
def getifindex(self, netif):
"""
Retrieve index for an interface.
:param PyCoreNetIf netif: interface to get index for
:return: interface index if found, -1 otherwise
:rtype: int
"""
for ifindex in self._netif:
if self._netif[ifindex] is netif:
return ifindex
return -1
def newifindex(self):
"""
Create a new interface index.
:return: interface index
:rtype: int
"""
while self.ifindex in self._netif:
self.ifindex += 1
ifindex = self.ifindex
self.ifindex += 1
return ifindex
def tonodemsg(self, flags):
''' Build a CORE API Node Message for this object. Both nodes and
networks can be represented by a Node Message.
'''
def data(self, message_type):
"""
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:
return None
tlvdata = ""
(x, y, z) = self.getposition()
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_NUMBER,
self.objid)
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_TYPE,
self.apitype)
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_NAME,
self.name)
if hasattr(self, "type") and self.type is not None:
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_MODEL,
self.type)
if hasattr(self, "server") and self.server is not None:
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_EMUSRV,
self.server)
x, y, z = self.getposition()
model = None
if hasattr(self, "type"):
model = self.type
emulation_server = None
if hasattr(self, "server"):
emulation_server = self.server
services = None
if hasattr(self, "services") and len(self.services) != 0:
nodeservices = []
for s in self.services:
nodeservices.append(s._name)
tlvdata += coreapi.CoreNodeTlv.pack(coreapi.CORE_TLV_NODE_SERVICES,
"|".join(nodeservices))
nodeservices.append(s._name)
services = "|".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:
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
return node_data
def tolinkmsgs(self, flags):
''' Build CORE API Link Messages for this object. There is no default
method for PyCoreObjs as PyCoreNodes do not implement this but
PyCoreNets do.
'''
def all_link_data(self, flags):
"""
Build CORE Link data for this object. There is no default
method for PyCoreObjs as PyCoreNodes do not implement this but
PyCoreNets do.
:param flags: message flags
:return: list of link data
:rtype: link
"""
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):
''' Base class for nodes
'''
def __init__(self, session, objid = None, name = None, verbose = False,
start = True):
''' Initialization for node objects.
'''
PyCoreObj.__init__(self, session, objid, name, verbose=verbose,
start=start)
"""
Base class for CORE nodes.
"""
# TODO: start seems like it should go away
def __init__(self, session, objid=None, name=None, start=True):
"""
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 = []
if not hasattr(self, "type"):
self.type = None
self.nodedir = None
self.tmpnodedir = False
# TODO: getter method that should not be needed
def nodeid(self):
"""
Retrieve node id.
:return: node id
:rtype: int
"""
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:
self.services.append(service)
def makenodedir(self):
"""
Create the node directory.
:return: nothing
"""
if self.nodedir is None:
self.nodedir = \
os.path.join(self.session.sessiondir, self.name + ".conf")
self.nodedir = os.path.join(self.session.session_dir, self.name + ".conf")
os.makedirs(self.nodedir)
self.tmpnodedir = True
else:
self.tmpnodedir = False
def rmnodedir(self):
if hasattr(self.session.options, 'preservedir'):
if self.session.options.preservedir == '1':
return
"""
Remove the node directory, unless preserve directory has been set.
:return: nothing
"""
preserve = getattr(self.session.options, "preservedir", None)
if preserve == "1":
return
if self.tmpnodedir:
shutil.rmtree(self.nodedir, ignore_errors = True)
shutil.rmtree(self.nodedir, ignore_errors=True)
def addnetif(self, netif, ifindex):
"""
Add network interface to node and set the network interface index if successful.
:param PyCoreNetIf netif: network interface to add
:param int ifindex: interface index
:return: nothing
"""
if ifindex in self._netif:
raise ValueError, "ifindex %s already exists" % ifindex
raise ValueError("ifindex %s already exists" % ifindex)
self._netif[ifindex] = netif
# TODO: this hould have probably been set ahead, seems bad to me, check for failure and fix
netif.netindex = ifindex
def delnetif(self, ifindex):
"""
Delete a network interface
:param int ifindex: interface index to delete
:return: nothing
"""
if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex
raise ValueError("ifindex %s does not exist" % ifindex)
netif = self._netif.pop(ifindex)
netif.shutdown()
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:
return self._netif[ifindex]
else:
return None
def attachnet(self, ifindex, net):
"""
Attach a network.
:param int ifindex: interface of index to attach
:param PyCoreNetIf net: network to attach
:return:
"""
if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex
raise ValueError("ifindex %s does not exist" % ifindex)
self._netif[ifindex].attachnet(net)
def detachnet(self, ifindex):
"""
Detach network interface.
:param int ifindex: interface index to detach
:return: nothing
"""
if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex
raise ValueError("ifindex %s does not exist" % ifindex)
self._netif[ifindex].detachnet()
def setposition(self, x = None, y = None, z = None):
changed = PyCoreObj.setposition(self, x = x, y = y, z = z)
if not changed:
# save extra interface range calculations
return
for netif in self.netifs(sort=True):
netif.setposition(x, y, z)
def setposition(self, x=None, y=None, z=None):
"""
Set position.
:param x: x position
:param y: y position
:param z: z position
:return: nothing
"""
changed = super(PyCoreNode, self).setposition(x, y, z)
if changed:
for netif in self.netifs(sort=True):
netif.setposition(x, y, z)
def commonnets(self, obj, want_ctrl=False):
''' Given another node or net object, return common networks between
this node and that object. A list of tuples is returned, with each tuple
consisting of (network, interface1, interface2).
'''
r = []
"""
Given another node or net object, return common networks between
this node and that object. A list of tuples is returned, with each tuple
consisting of (network, interface1, interface2).
:param obj: object to get common network with
:param want_ctrl: flag set to determine if control network are wanted
:return: tuples of common networks
:rtype: list
"""
common = []
for netif1 in self.netifs():
if not want_ctrl and hasattr(netif1, 'control'):
if not want_ctrl and hasattr(netif1, "control"):
continue
for netif2 in obj.netifs():
if netif1.net == netif2.net:
r += (netif1.net, netif1, netif2),
return r
common.append((netif1.net, netif1, netif2))
return common
class PyCoreNet(PyCoreObj):
''' Base class for networks
'''
linktype = coreapi.CORE_LINK_WIRED
"""
Base class for networks
"""
linktype = LinkTypes.WIRED.value
def __init__(self, session, objid, name, verbose = False, start = True):
''' Initialization for network objects.
'''
PyCoreObj.__init__(self, session, objid, name, verbose=verbose,
start=start)
# TODO: remove start if appropriate
def __init__(self, session, objid, name, start=True):
"""
Create a PyCoreNet instance.
: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_lock = threading.Lock()
def attach(self, netif):
"""
Attach network interface.
:param PyCoreNetIf netif: network interface to attach
:return: nothing
"""
i = self.newifindex()
self._netif[i] = netif
netif.netifi = i
with self._linked_lock:
self._linked[netif] = {}
def detach(self, netif):
"""
Detach network interface.
:param PyCoreNetIf netif: network interface to detach
:return: nothing
"""
del self._netif[netif.netifi]
netif.netifi = None
with self._linked_lock:
del self._linked[netif]
# TODO: needs to be abstracted out, seems like it may be ok to remove
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 = ""
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:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_DELAY,
delay)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DELAY.value, delay)
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:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_PER,
str(loss))
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.PER.value, str(loss))
if duplicate is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_DUP,
str(duplicate))
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DUP.value, str(duplicate))
if jitter is not None:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_JITTER,
jitter)
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.JITTER.value, jitter)
return tlvdata
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 = []
def all_link_data(self, flags):
"""
Build link data objects for this network. Each link object describes a link
between this network and a node.
"""
all_links = []
# build a link message from this network node to each node having a
# connected interface
for netif in self.netifs(sort=True):
@ -358,62 +520,86 @@ class PyCoreNet(PyCoreObj):
netif.swapparams('_params_up')
if netif.getparams() != upstream_params:
uni = True
tlvdata = ""
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)
unidirectional = 0
if uni:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI,
1)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM,
otherobj.getifindex(netif))
if netif.hwaddr:
tlvdata += \
coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2MAC,
netif.hwaddr)
for addr in netif.addrlist:
(ip, sep, mask) = addr.partition('/')
unidirectional = 1
interface2_ip4 = None
interface2_ip4_mask = None
interface2_ip6 = None
interface2_ip6_mask = None
for address in netif.addrlist:
ip, sep, mask = address.partition('/')
mask = int(mask)
if isIPv4Address(ip):
if ipaddress.is_ipv4_address(ip):
family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP4
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP4MASK
ipl = socket.inet_pton(family, ip)
interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip4_mask = mask
else:
family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP6MASK
ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip, \
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
ipl = socket.inet_pton(family, ip)
interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip6_mask = mask
# TODO: not currently used
# loss = netif.getparam('loss')
link_data = LinkData(
message_type=flags,
node1_id=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:
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')
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')
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1)
msg = coreapi.CoreLinkMessage.pack(0, tlvdata)
msgs.append(msg)
return msgs
all_links.append(link_data)
return all_links
class PyCoreNetIf(object):
''' Base class for interfaces.
'''
"""
Base class for network interfaces.
"""
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.name = name
if not isinstance(mtu, (int, long)):
@ -428,67 +614,119 @@ class PyCoreNetIf(object):
self.transport_type = None
# interface index on the network
self.netindex = None
# index used to find flow data
self.flow_id = None
def startup(self):
"""
Startup method for the interface.
:return: nothing
"""
pass
def shutdown(self):
"""
Shutdown method for the interface.
:return: nothing
"""
pass
def attachnet(self, net):
"""
Attach network.
:param PyCoreNet net: network to attach to
:return:nothing
"""
if self.net:
self.detachnet()
self.net = None
net.attach(self)
self.net = net
def detachnet(self):
"""
Detach from a network.
:return: nothing
"""
if self.net is not None:
self.net.detach(self)
def addaddr(self, addr):
"""
Add address.
:param str addr: address to add
:return: nothing
"""
self.addrlist.append(addr)
def deladdr(self, addr):
"""
Delete address.
:param str addr: address to delete
:return: nothing
"""
self.addrlist.remove(addr)
def sethwaddr(self, addr):
"""
Set hardware address.
:param core.misc.ipaddress.MacAddress addr: hardware address to set to.
:return: nothing
"""
self.hwaddr = addr
def getparam(self, key):
''' Retrieve a parameter from the _params dict,
or None if the parameter does not exist.
'''
if key not in self._params:
return None
return self._params[key]
"""
Retrieve a parameter from the, or None if the parameter does not exist.
:param key: parameter to get value for
:return: parameter value
"""
return self._params.get(key)
def getparams(self):
''' Return (key, value) pairs from the _params dict.
'''
r = []
"""
Return (key, value) pairs for parameters.
"""
parameters = []
for k in sorted(self._params.keys()):
r.append((k, self._params[k]))
return r
parameters.append((k, self._params[k]))
return parameters
def setparam(self, key, value):
''' Set a parameter in the _params dict.
Returns True if the parameter has changed.
'''
if key in self._params:
if self._params[key] == value:
return False
elif self._params[key] <= 0 and value <= 0:
# treat None and 0 as unchanged values
return False
"""
Set a parameter value, returns True if the parameter has changed.
:param key: parameter name to set
:param value: parameter value
:return: True if parameter changed, False otherwise
"""
# treat None and 0 as unchanged values
current_value = self._params.get(key)
if current_value == value or current_value <= 0 and value <= 0:
return False
self._params[key] = value
return True
def swapparams(self, name):
''' Swap out 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
parameters when two layer-2 nodes are linked together.
'''
:param str name: name of parameter to swap
:return: nothing
"""
tmp = self._params
if not hasattr(self, name):
setattr(self, name, {})
@ -496,8 +734,13 @@ class PyCoreNetIf(object):
setattr(self, name, tmp)
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:
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
'''
"""
import sys
import string
from core.api import coreapi
from core.emane.emanemodel import EmaneModel
from core.enumerations import ConfigDataTypes
from core.constants import *
from emane import EmaneModel
class EmaneBypassModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False):
EmaneModel.__init__(self, session, objid, verbose)
def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, object_id)
_name = "emane_bypass"
_confmatrix = [
("none",coreapi.CONF_DATA_TYPE_BOOL, '0',
'True,False','There are no parameters for the bypass model.'),
name = "emane_bypass"
config_matrix = [
("none", ConfigDataTypes.BOOL.value, '0',
'True,False', 'There are no parameters for the bypass model.'),
]
# value groupings
_confgroups = "Bypass Parameters:1-1"
config_groups = "Bypass Parameters:1-1"
def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_bypassnem.xml,
nXXemane_bypassmac.xml, nXXemane_bypassphy.xml are used.
'''
values = e.getifcconfig(self.objid, self._name,
self.getdefaultvalues(), ifc)
"""
Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_bypassnem.xml,
nXXemane_bypassmac.xml, nXXemane_bypassphy.xml are used.
"""
values = e.getifcconfig(self.object_id, self.name, self.getdefaultvalues(), ifc)
if values is None:
return
nemdoc = e.xmldoc("nem")
nem = nemdoc.getElementsByTagName("nem").pop()
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.setAttribute("definition", self.macxmlname(ifc))
nem.appendChild(mactag)
@ -62,5 +52,3 @@ class EmaneBypassModel(EmaneModel):
phy.setAttribute("name", "BYPASS PHY")
phy.setAttribute("library", "bypassphylayer")
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
'''
"""
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:
from emanesh.events import EventService
except:
pass
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
except ImportError:
logger.error("error importing emanesh")
try:
import emaneeventservice
import emaneeventcommeffect
except Exception, e:
pass
except ImportError:
logger.error("error importing emaneeventservice and emaneeventcommeffect")
class EmaneCommEffectModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False):
EmaneModel.__init__(self, session, objid, verbose)
def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, object_id)
# model name
_name = "emane_commeffect"
name = "emane_commeffect"
# CommEffect parameters
_confmatrix_shim_base = [
("filterfile", coreapi.CONF_DATA_TYPE_STRING, '',
("filterfile", ConfigDataTypes.STRING.value, '',
'', 'filter file'),
("groupid", coreapi.CONF_DATA_TYPE_UINT32, '0',
("groupid", ConfigDataTypes.UINT32.value, '0',
'', 'NEM Group ID'),
("enablepromiscuousmode", coreapi.CONF_DATA_TYPE_BOOL, '0',
("enablepromiscuousmode", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable promiscuous mode'),
("receivebufferperiod", coreapi.CONF_DATA_TYPE_FLOAT, '1.0',
("receivebufferperiod", ConfigDataTypes.FLOAT.value, '1.0',
'', 'receivebufferperiod'),
]
_confmatrix_shim_081 = [
("defaultconnectivity", coreapi.CONF_DATA_TYPE_BOOL, '0',
("defaultconnectivity", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'defaultconnectivity'),
("enabletighttimingmode", coreapi.CONF_DATA_TYPE_BOOL, '0',
("enabletighttimingmode", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable tight timing mode'),
]
_confmatrix_shim_091 = [
("defaultconnectivitymode", coreapi.CONF_DATA_TYPE_BOOL, '0',
("defaultconnectivitymode", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'defaultconnectivity'),
]
if Emane.version >= Emane.EMANE091:
if emane.VERSION >= emane.EMANE091:
_confmatrix_shim = _confmatrix_shim_base + _confmatrix_shim_091
else:
_confmatrix_shim = _confmatrix_shim_base + _confmatrix_shim_081
_confmatrix = _confmatrix_shim
config_matrix = _confmatrix_shim
# value groupings
_confgroups = "CommEffect SHIM Parameters:1-%d" \
% len(_confmatrix_shim)
config_groups = "CommEffect SHIM Parameters:1-%d" % len(_confmatrix_shim)
def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem and commeffect XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide
nXXemane_commeffectnem.xml, nXXemane_commeffectshim.xml are used.
'''
values = e.getifcconfig(self.objid, self._name,
self.getdefaultvalues(), ifc)
"""
Build the necessary nem and commeffect XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide
nXXemane_commeffectnem.xml, nXXemane_commeffectshim.xml are used.
"""
values = e.getifcconfig(self.object_id, self.name, self.getdefaultvalues(), ifc)
if values is None:
return
shimdoc = e.xmldoc("shim")
@ -83,8 +77,7 @@ class EmaneCommEffectModel(EmaneModel):
shimnames.remove("filterfile")
# append all shim options (except filterfile) to shimdoc
map( lambda n: shim.appendChild(e.xmlparam(shimdoc, n, \
self.valueof(n, values))), shimnames)
map(lambda n: shim.appendChild(e.xmlparam(shimdoc, n, self.valueof(n, values))), shimnames)
# empty filterfile is not allowed
ff = self.valueof("filterfile", values)
if ff.strip() != '':
@ -95,20 +88,24 @@ class EmaneCommEffectModel(EmaneModel):
nem = nemdoc.getElementsByTagName("nem").pop()
nem.setAttribute("name", "commeffect NEM")
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)))
e.xmlwrite(nemdoc, self.nemxmlname(ifc))
def linkconfig(self, netif, bw = None, delay = None,
loss = None, duplicate = None, jitter = None, netif2 = None):
''' Generate CommEffect events when a Link Message is received having
def linkconfig(self, netif, bw=None, delay=None,
loss=None, duplicate=None, jitter=None, netif2=None):
"""
Generate CommEffect events when a Link Message is received having
link parameters.
'''
if self.session.emane.version >= self.session.emane.EMANE091:
"""
if emane.VERSION >= emane.EMANE091:
raise NotImplementedError, \
"CommEffect linkconfig() not implemented for EMANE 0.9.1+"
"CommEffect linkconfig() not implemented for EMANE 0.9.1+"
def z(x):
''' Helper to use 0 for None values. '''
"""
Helper to use 0 for None values.
"""
if type(x) is str:
x = float(x)
if x is None:
@ -118,17 +115,16 @@ class EmaneCommEffectModel(EmaneModel):
service = self.session.emane.service
if service is None:
self.session.warn("%s: EMANE event service unavailable" % \
self._name)
logger.warn("%s: EMANE event service unavailable" % self.name)
return
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
# TODO: batch these into multiple events per transmission
# TODO: may want to split out seconds portion of delay and jitter
event = emaneeventcommeffect.EventCommEffect(1)
index = 0
e = self.session.obj(self.objid)
e = self.session.get_object(self.object_id)
nemid = e.getnemid(netif)
nemid2 = e.getnemid(netif2)
mbw = bw
@ -139,6 +135,3 @@ class EmaneCommEffectModel(EmaneModel):
emaneeventservice.PLATFORMID_ANY,
nemid2, emaneeventservice.COMPONENTID_ANY,
event.export())

File diff suppressed because it is too large Load diff

View file

@ -1,117 +1,113 @@
#
# 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
'''
"""
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:
from emanesh.events import EventService
except:
pass
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
from universal import EmaneUniversalModel
logger.error("error importing emanesh")
class EmaneIeee80211abgModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False):
EmaneModel.__init__(self, session, objid, verbose)
def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, object_id)
# 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,' + \
'6 9 Mbps,7 12 Mbps,8 18 Mbps,9 24 Mbps,10 36 Mbps,11 48 Mbps,' + \
'12 54 Mbps'
if Emane.version >= Emane.EMANE091:
'6 9 Mbps,7 12 Mbps,8 18 Mbps,9 24 Mbps,10 36 Mbps,11 48 Mbps,' + \
'12 54 Mbps'
if emane.VERSION >= emane.EMANE091:
xml_path = '/usr/share/emane/xml/models/mac/ieee80211abg'
else:
xml_path = "/usr/share/emane/models/ieee80211abg/xml"
# MAC parameters
_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),' + \
'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'),
("distance", coreapi.CONF_DATA_TYPE_UINT32, '1000',
("distance", ConfigDataTypes.UINT32.value, '1000',
'', 'max distance (m)'),
("unicastrate", coreapi.CONF_DATA_TYPE_UINT8, '4', _80211rates,
("unicastrate", ConfigDataTypes.UINT8.value, '4', _80211rates,
'unicast rate (Mbps)'),
("multicastrate", coreapi.CONF_DATA_TYPE_UINT8, '1', _80211rates,
("multicastrate", ConfigDataTypes.UINT8.value, '1', _80211rates,
'multicast rate (Mbps)'),
("rtsthreshold", coreapi.CONF_DATA_TYPE_UINT16, '0',
("rtsthreshold", ConfigDataTypes.UINT16.value, '0',
'', 'RTS threshold (bytes)'),
("pcrcurveuri", coreapi.CONF_DATA_TYPE_STRING,
("pcrcurveuri", ConfigDataTypes.STRING.value,
'%s/ieee80211pcr.xml' % xml_path,
'', 'SINR/PCR curve file'),
("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0',
("flowcontrolenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable traffic flow control'),
("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10',
("flowcontroltokens", ConfigDataTypes.UINT16.value, '10',
'', 'number of flow control tokens'),
]
# mac parameters introduced in EMANE 0.8.1
# mac parameters introduced in EMANE 0.8.1
# Note: The entry format for category queue parameters (queuesize, aifs, etc) were changed in
# 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 = [
("wmmenable", coreapi.CONF_DATA_TYPE_BOOL, '0',
("wmmenable", ConfigDataTypes.BOOL.value, '0',
'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)'),
("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)'),
("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)'),
("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)'),
("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)'),
("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)'),
]
_confmatrix_mac_091 = [
('radiometricenable', coreapi.CONF_DATA_TYPE_BOOL, '0',
('radiometricenable', ConfigDataTypes.BOOL.value, '0',
'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)'),
('neighbormetricdeletetime', coreapi.CONF_DATA_TYPE_FLOAT, '60.0',
('neighbormetricdeletetime', ConfigDataTypes.FLOAT.value, '60.0',
'', 'R2RI neighbor table inactivity time (sec)'),
]
_confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_extended
if Emane.version >= Emane.EMANE091:
if emane.VERSION >= emane.EMANE091:
_confmatrix_mac += _confmatrix_mac_091
# 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
_confgroups = "802.11 MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" \
% (len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(_confmatrix))
config_groups = "802.11 MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % (
len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(config_matrix))
def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide
nXXemane_ieee80211abgnem.xml, nXXemane_ieee80211abgemac.xml,
nXXemane_ieee80211abgphy.xml are used.
'''
values = e.getifcconfig(self.objid, self._name,
self.getdefaultvalues(), ifc)
"""
Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide
nXXemane_ieee80211abgnem.xml, nXXemane_ieee80211abgemac.xml,
nXXemane_ieee80211abgphy.xml are used.
"""
values = e.getifcconfig(self.object_id, self.name, self.getdefaultvalues(), ifc)
if values is None:
return
nemdoc = e.xmldoc("nem")
nem = nemdoc.getElementsByTagName("nem").pop()
nem.setAttribute("name", "ieee80211abg NEM")
e.appendtransporttonem(nemdoc, nem, self.objid, ifc)
e.appendtransporttonem(nemdoc, nem, self.object_id, ifc)
mactag = nemdoc.createElement("mac")
mactag.setAttribute("definition", self.macxmlname(ifc))
nem.appendChild(mactag)
@ -130,15 +126,14 @@ class EmaneIeee80211abgModel(EmaneModel):
phynames = names[len(self._confmatrix_mac):]
# append all MAC options to macdoc
if Emane.version >= Emane.EMANE091:
if emane.VERSION >= emane.EMANE091:
for macname in macnames:
mac9xnvpairlist = self.get9xmacparamequivalent(macname, values)
for nvpair in mac9xnvpairlist:
mac.appendChild(e.xmlparam(macdoc, nvpair[0], nvpair[1]))
else:
map( lambda n: mac.appendChild(e.xmlparam(macdoc, n, \
self.valueof(n, values))), macnames)
map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, self.valueof(n, values))), macnames)
e.xmlwrite(macdoc, self.macxmlname(ifc))
phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames)
@ -149,25 +144,25 @@ class EmaneIeee80211abgModel(EmaneModel):
# This allows CORE to preserve the entry layout for the mac 'category' parameters
# and work with EMANE 9.x onwards.
#
def get9xmacparamequivalent(self, macname, values):
''' 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
def get9xmacparamequivalent(self, macname, values):
"""
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
equivalent 9.x parameter and value pairs. Otherwise, the list returned will only
contain a single name and value pair.
'''
"""
nvpairlist = []
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():
idx_and_val = catval.split(":")
idx = int(idx_and_val[0])
val = idx_and_val[1]
# aifs and tx are in microseconds. Convert to seconds.
if macname in ["aifs","txop"]:
val = "%f" % (float(val)*(1e-6))
if macname in ["aifs", "txop"]:
val = "%f" % (float(val) * 1e-6)
name9x = "%s%d" % (macname, idx)
nvpairlist.append([name9x, val])
else:
nvpairlist.append([macname, macparmval])
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
control of an EMANE emulation. An EmaneNode has several attached NEMs that
share the same MAC+PHY model.
'''
"""
import sys
import os.path
from os import path
from core.api import coreapi
from core import emane
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:
from emanesh.events import EventService
from emanesh.events import LocationEvent
except Exception, e:
pass
except ImportError:
logger.error("error loading emanesh")
try:
import emaneeventservice
import emaneeventlocation
except Exception, e:
''' Don't require all CORE users to have EMANE libeventservice and its
Python bindings installed.
'''
pass
except ImportError:
"""
Don't require all CORE users to have EMANE libeventservice and its
Python bindings installed.
"""
logger.error("error loading emaneeventservice and emaneeventlocation")
class EmaneNet(PyCoreNet):
''' EMANE network base class.
'''
apitype = coreapi.CORE_NODE_EMANE
linktype = coreapi.CORE_LINK_WIRELESS
type = "wlan" # icon used
"""
EMANE network base class.
"""
apitype = NodeTypes.EMANE.value
linktype = LinkTypes.WIRELESS.value
# icon used
type = "wlan"
class EmaneNode(EmaneNet):
''' EMANE node contains NEM configuration and causes connected nodes
to have TAP interfaces (instead of VEth). These are managed by the
Emane controller object that exists in a session.
'''
def __init__(self, session, objid = None, name = None, verbose = False,
start = True):
PyCoreNet.__init__(self, session, objid, name, verbose, start)
self.verbose = verbose
"""
EMANE node contains NEM configuration and causes connected nodes
to have TAP interfaces (instead of VEth). These are managed by the
Emane controller object that exists in a session.
"""
def __init__(self, session, objid=None, name=None, start=True):
PyCoreNet.__init__(self, session, objid, name, start)
self.conf = ""
self.up = False
self.nemidmap = {}
self.model = None
self.mobility = None
def linkconfig(self, netif, bw = None, delay = None,
loss = None, duplicate = None, jitter = None, netif2 = None):
''' The CommEffect model supports link configuration.
'''
def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None, jitter=None, netif2=None):
"""
The CommEffect model supports link configuration.
"""
if not self.model:
return
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):
#print "emane", self.name, "got config:", conf
self.conf = conf
def shutdown(self):
@ -76,55 +79,57 @@ class EmaneNode(EmaneNet):
pass
def setmodel(self, model, config):
''' set the EmaneModel associated with this node
'''
if (self.verbose):
self.info("adding model %s" % model._name)
if model._type == coreapi.CORE_TLV_REG_WIRELESS:
"""
set the EmaneModel associated with this node
"""
logger.info("adding model: %s", model.name)
if model.config_type == RegisterTlvs.WIRELESS.value:
# EmaneModel really uses values from ConfigurableManager
# when buildnemxml() is called, not during init()
self.model = model(session=self.session, objid=self.objid,
verbose=self.verbose)
elif model._type == coreapi.CORE_TLV_REG_MOBILITY:
self.mobility = model(session=self.session, objid=self.objid,
verbose=self.verbose, values=config)
self.model = model(session=self.session, object_id=self.objid)
elif model.config_type == RegisterTlvs.MOBILITY.value:
self.mobility = model(session=self.session, object_id=self.objid, values=config)
def setnemid(self, netif, nemid):
''' Record an interface to numerical ID mapping. The Emane controller
object manages and assigns these IDs for all NEMs.
'''
"""
Record an interface to numerical ID mapping. The Emane controller
object manages and assigns these IDs for all NEMs.
"""
self.nemidmap[netif] = nemid
def getnemid(self, netif):
''' Given an interface, return its numerical ID.
'''
"""
Given an interface, return its numerical ID.
"""
if netif not in self.nemidmap:
return None
else:
return self.nemidmap[netif]
def getnemnetif(self, nemid):
''' Given a numerical NEM ID, return its interface. This returns the
first interface that matches the given NEM ID.
'''
"""
Given a numerical NEM ID, return its interface. This returns the
first interface that matches the given NEM ID.
"""
for netif in self.nemidmap:
if self.nemidmap[netif] == nemid:
return netif
return None
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)
def buildplatformxmlentry(self, doc):
''' Return a dictionary of XML elements describing the NEMs
connected to this EmaneNode for inclusion in the platform.xml file.
'''
"""
Return a dictionary of XML elements describing the NEMs
connected to this EmaneNode for inclusion in the platform.xml file.
"""
ret = {}
if self.model is None:
self.info("warning: EmaneNode %s has no associated model" % \
self.name)
logger.info("warning: EmaneNode %s has no associated model" % self.name)
return ret
for netif in self.netifs():
# <nem name="NODE-001" definition="rfpipenem.xml">
@ -139,9 +144,9 @@ class EmaneNode(EmaneNet):
return ret
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:
return
# build XML for overall network (EmaneNode) configs
@ -166,8 +171,9 @@ class EmaneNode(EmaneNet):
self.buildtransportxml(emane, rtype)
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")
trans = transdoc.getElementsByTagName("transport").pop()
trans.setAttribute("name", "%s Transport" % type.capitalize())
@ -176,7 +182,7 @@ class EmaneNode(EmaneNet):
flowcontrol = False
names = self.model.getnames()
values = emane.getconfig(self.objid, self.model._name,
values = emane.getconfig(self.objid, self.model.name,
self.model.getdefaultvalues())[1]
if "flowcontrolenable" in names and values:
i = names.index("flowcontrolenable")
@ -184,35 +190,30 @@ class EmaneNode(EmaneNet):
flowcontrol = True
if "virtual" in type.lower():
if os.path.exists("/dev/net/tun_flowctl"):
trans.appendChild(emane.xmlparam(transdoc, "devicepath",
"/dev/net/tun_flowctl"))
if path.exists("/dev/net/tun_flowctl"):
trans.appendChild(emane.xmlparam(transdoc, "devicepath", "/dev/net/tun_flowctl"))
else:
trans.appendChild(emane.xmlparam(transdoc, "devicepath",
"/dev/net/tun"))
trans.appendChild(emane.xmlparam(transdoc, "devicepath", "/dev/net/tun"))
if flowcontrol:
trans.appendChild(emane.xmlparam(transdoc, "flowcontrolenable",
"on"))
trans.appendChild(emane.xmlparam(transdoc, "flowcontrolenable", "on"))
emane.xmlwrite(transdoc, self.transportxmlname(type.lower()))
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)
def installnetifs(self, do_netns=True):
''' Install TAP devices into their namespaces. This is done after
EMANE daemons have been started, because that is their only chance
to bind to the TAPs.
'''
if self.session.emane.genlocationevents() and \
self.session.emane.service is None:
"""
Install TAP devices into their namespaces. This is done after
EMANE daemons have been started, because that is their only chance
to bind to the TAPs.
"""
if self.session.emane.genlocationevents() and self.session.emane.service is None:
warntxt = "unable to publish EMANE events because the eventservice "
warntxt += "Python bindings failed to load"
self.session.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.name,
self.objid, warntxt)
logger.error(warntxt)
for netif in self.netifs():
if do_netns and "virtual" in netif.transport_type.lower():
@ -224,98 +225,99 @@ class EmaneNode(EmaneNet):
# at this point we register location handlers for generating
# EMANE location events
netif.poshook = self.setnemposition
(x,y,z) = netif.node.position.get()
(x, y, z) = netif.node.position.get()
self.setnemposition(netif, x, y, z)
def deinstallnetifs(self):
''' Uninstall TAP devices. This invokes their shutdown method for
any required cleanup; the device may be actually removed when
emanetransportd terminates.
'''
"""
Uninstall TAP devices. This invokes their shutdown method for
any required cleanup; the device may be actually removed when
emanetransportd terminates.
"""
for netif in self.netifs():
if "virtual" in netif.transport_type.lower():
netif.shutdown()
netif.poshook = None
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.verbose:
self.info("position service not available")
logger.info("position service not available")
return
nemid = self.getnemid(netif)
nemid = self.getnemid(netif)
ifname = netif.localname
if nemid is None:
self.info("nemid for %s is unknown" % ifname)
logger.info("nemid for %s is unknown" % ifname)
return
(lat, long, alt) = self.session.location.getgeo(x, y, z)
if self.verbose:
self.info("setnemposition %s (%s) x,y,z=(%d,%d,%s)"
"(%.6f,%.6f,%.6f)" % \
(ifname, nemid, x, y, z, lat, long, alt))
if self.session.emane.version >= self.session.emane.EMANE091:
logger.info("setnemposition %s (%s) x,y,z=(%d,%d,%s)"
"(%.6f,%.6f,%.6f)" % \
(ifname, nemid, x, y, z, lat, long, alt))
if emane.VERSION >= emane.EMANE091:
event = LocationEvent()
else:
event = emaneeventlocation.EventLocation(1)
# altitude must be an integer or warning is printed
# unused: yaw, pitch, roll, azimuth, elevation, velocity
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)
self.session.emane.service.publish(0, event)
else:
event.set(0, nemid, lat, long, alt)
self.session.emane.service.publish(emaneeventlocation.EVENT_ID,
emaneeventservice.PLATFORMID_ANY,
emaneeventservice.NEMID_ANY,
emaneeventservice.COMPONENTID_ANY,
event.export())
self.session.emane.service.publish(
emaneeventlocation.EVENT_ID,
emaneeventservice.PLATFORMID_ANY,
emaneeventservice.NEMID_ANY,
emaneeventservice.COMPONENTID_ANY,
event.export()
)
def setnempositions(self, moved_netifs):
''' Several NEMs have moved, from e.g. a WaypointMobilityModel
calculation. Generate an EMANE Location Event having several
entries for each netif that has moved.
'''
"""
Several NEMs have moved, from e.g. a WaypointMobilityModel
calculation. Generate an EMANE Location Event having several
entries for each netif that has moved.
"""
if len(moved_netifs) == 0:
return
if self.session.emane.service is None:
if self.verbose:
self.info("position service not available")
logger.info("position service not available")
return
if self.session.emane.version >= self.session.emane.EMANE091:
if emane.VERSION >= emane.EMANE091:
event = LocationEvent()
else:
event = emaneeventlocation.EventLocation(len(moved_netifs))
i = 0
for netif in moved_netifs:
nemid = self.getnemid(netif)
nemid = self.getnemid(netif)
ifname = netif.localname
if nemid is None:
self.info("nemid for %s is unknown" % ifname)
logger.info("nemid for %s is unknown" % ifname)
continue
(x, y, z) = netif.node.getposition()
(lat, long, alt) = self.session.location.getgeo(x, y, z)
if self.verbose:
self.info("setnempositions %d %s (%s) x,y,z=(%d,%d,%s)"
"(%.6f,%.6f,%.6f)" % \
(i, ifname, nemid, x, y, z, lat, long, alt))
logger.info("setnempositions %d %s (%s) x,y,z=(%d,%d,%s)"
"(%.6f,%.6f,%.6f)" %
(i, ifname, nemid, x, y, z, lat, long, alt))
# altitude must be an integer or warning is printed
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)
else:
event.set(i, nemid, lat, long, alt)
i += 1
if self.session.emane.version >= self.session.emane.EMANE091:
if emane.VERSION >= emane.EMANE091:
self.session.emane.service.publish(0, event)
else:
self.session.emane.service.publish(emaneeventlocation.EVENT_ID,
emaneeventservice.PLATFORMID_ANY,
emaneeventservice.NEMID_ANY,
emaneeventservice.COMPONENTID_ANY,
event.export())
self.session.emane.service.publish(
emaneeventlocation.EVENT_ID,
emaneeventservice.PLATFORMID_ANY,
emaneeventservice.NEMID_ANY,
emaneeventservice.COMPONENTID_ANY,
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
'''
"""
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:
from emanesh.events import EventService
except:
pass
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
from universal import EmaneUniversalModel
except ImportError:
logger.error("error importing emanesh")
class EmaneRfPipeModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False):
EmaneModel.__init__(self, session, objid, verbose)
def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, object_id)
# model name
_name = "emane_rfpipe"
if Emane.version >= Emane.EMANE091:
name = "emane_rfpipe"
if emane.VERSION >= emane.EMANE091:
xml_path = '/usr/share/emane/xml/models/mac/rfpipe'
else:
xml_path = "/usr/share/emane/models/rfpipe/xml"
@ -36,68 +31,69 @@ class EmaneRfPipeModel(EmaneModel):
# ( 'name', 'type', 'default', 'possible-value-list', 'caption')
# MAC parameters
_confmatrix_mac_base = [
("enablepromiscuousmode", coreapi.CONF_DATA_TYPE_BOOL, '0',
("enablepromiscuousmode", ConfigDataTypes.BOOL.value, '0',
'True,False', 'enable promiscuous mode'),
("datarate", coreapi.CONF_DATA_TYPE_UINT32, '1M',
("datarate", ConfigDataTypes.UINT32.value, '1M',
'', 'data rate (bps)'),
("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0',
("flowcontrolenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable traffic flow control'),
("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10',
("flowcontroltokens", ConfigDataTypes.UINT16.value, '10',
'', 'number of flow control tokens'),
("pcrcurveuri", coreapi.CONF_DATA_TYPE_STRING,
("pcrcurveuri", ConfigDataTypes.STRING.value,
'%s/rfpipepcr.xml' % xml_path,
'', 'SINR/PCR curve file'),
]
_confmatrix_mac_081 = [
("jitter", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
("jitter", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmission jitter (usec)'),
("delay", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
("delay", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmission delay (usec)'),
("transmissioncontrolmap", coreapi.CONF_DATA_TYPE_STRING, '',
("transmissioncontrolmap", ConfigDataTypes.STRING.value, '',
'', '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'),
]
_confmatrix_mac_091 = [
("jitter", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
("jitter", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmission jitter (sec)'),
("delay", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
("delay", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmission delay (sec)'),
('radiometricenable', coreapi.CONF_DATA_TYPE_BOOL, '0',
('radiometricenable', ConfigDataTypes.BOOL.value, '0',
'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)'),
('neighbormetricdeletetime', coreapi.CONF_DATA_TYPE_FLOAT, '60.0',
('neighbormetricdeletetime', ConfigDataTypes.FLOAT.value, '60.0',
'', 'R2RI neighbor table inactivity time (sec)'),
]
if Emane.version >= Emane.EMANE091:
if emane.VERSION >= emane.EMANE091:
_confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_091
else:
_confmatrix_mac = _confmatrix_mac_base + _confmatrix_mac_081
# 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
_confgroups = "RF-PIPE MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" \
% ( len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(_confmatrix))
config_groups = "RF-PIPE MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % (
len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(config_matrix))
def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_rfpipenem.xml,
nXXemane_rfpipemac.xml, nXXemane_rfpipephy.xml are used.
'''
values = e.getifcconfig(self.objid, self._name,
"""
Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_rfpipenem.xml,
nXXemane_rfpipemac.xml, nXXemane_rfpipephy.xml are used.
"""
values = e.getifcconfig(self.object_id, self.name,
self.getdefaultvalues(), ifc)
if values is None:
return
nemdoc = e.xmldoc("nem")
nem = nemdoc.getElementsByTagName("nem").pop()
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.setAttribute("definition", self.macxmlname(ifc))
nem.appendChild(mactag)
@ -115,7 +111,7 @@ class EmaneRfPipeModel(EmaneModel):
mac.setAttribute("name", "RF-PIPE MAC")
mac.setAttribute("library", "rfpipemaclayer")
if e.version < e.EMANE091 and \
self.valueof("transmissioncontrolmap", values) is "":
self.valueof("transmissioncontrolmap", values) is "":
macnames.remove("transmissioncontrolmap")
# EMANE 0.7.4 support
if e.version == e.EMANE074:
@ -124,10 +120,8 @@ class EmaneRfPipeModel(EmaneModel):
values = list(values)
values[i] = self.emane074_fixup(values[i], 1000)
# append MAC options to macdoc
map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, \
self.valueof(n, values))), macnames)
map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, self.valueof(n, values))), macnames)
e.xmlwrite(macdoc, self.macxmlname(ifc))
phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames)
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
'''
"""
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:
from emanesh.events import EventService
except:
pass
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
from universal import EmaneUniversalModel
logger.error("error importing emanesh")
class EmaneTdmaModel(EmaneModel):
def __init__(self, session, objid = None, verbose = False):
EmaneModel.__init__(self, session, objid, verbose)
def __init__(self, session, object_id=None):
EmaneModel.__init__(self, session, object_id)
# model name
_name = "emane_tdma"
if Emane.version >= Emane.EMANE101:
name = "emane_tdma"
if emane.VERSION >= emane.EMANE101:
xml_path = '/usr/share/emane/xml/models/mac/tdmaeventscheduler'
else:
raise Exception("EMANE TDMA requires EMANE 1.0.1 or greater")
# MAC parameters
_confmatrix_mac = [
("enablepromiscuousmode", coreapi.CONF_DATA_TYPE_BOOL, '0',
("enablepromiscuousmode", ConfigDataTypes.BOOL.value, '0',
'True,False', 'enable promiscuous mode'),
("flowcontrolenable", coreapi.CONF_DATA_TYPE_BOOL, '0',
("flowcontrolenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable traffic flow control'),
("flowcontroltokens", coreapi.CONF_DATA_TYPE_UINT16, '10',
("flowcontroltokens", ConfigDataTypes.UINT16.value, '10',
'', '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'),
("fragmenttimeoutthreshold", coreapi.CONF_DATA_TYPE_UINT16, '5',
("fragmenttimeoutthreshold", ConfigDataTypes.UINT16.value, '5',
'', '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)'),
('neighbormetricupdateinterval', coreapi.CONF_DATA_TYPE_FLOAT, '1.0',
('neighbormetricupdateinterval', ConfigDataTypes.FLOAT.value, '1.0',
'', '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'),
("queue.aggregationenable", coreapi.CONF_DATA_TYPE_BOOL, '1',
("queue.aggregationenable", ConfigDataTypes.BOOL.value, '1',
'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'),
("queue.depth", coreapi.CONF_DATA_TYPE_UINT16, '256',
("queue.depth", ConfigDataTypes.UINT16.value, '256',
'', '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)'),
("queue.strictdequeueenable", coreapi.CONF_DATA_TYPE_BOOL, '0',
("queue.strictdequeueenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable strict dequeueing to specified queues only'),
]
# 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
_confgroups = "TDMA MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % \
(len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(_confmatrix))
config_groups = "TDMA MAC Parameters:1-%d|Universal PHY Parameters:%d-%d" % (
len(_confmatrix_mac), len(_confmatrix_mac) + 1, len(config_matrix))
def buildnemxmlfiles(self, e, ifc):
''' Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_tdmanem.xml,
nXXemane_tdmamac.xml, nXXemane_tdmaphy.xml are used.
'''
values = e.getifcconfig(self.objid, self._name,
self.getdefaultvalues(), ifc)
"""
Build the necessary nem, mac, and phy XMLs in the given path.
If an individual NEM has a nonstandard config, we need to build
that file also. Otherwise the WLAN-wide nXXemane_tdmanem.xml,
nXXemane_tdmamac.xml, nXXemane_tdmaphy.xml are used.
"""
values = e.getifcconfig(self.object_id, self.name, self.getdefaultvalues(), ifc)
if values is None:
return
nemdoc = e.xmldoc("nem")
nem = nemdoc.getElementsByTagName("nem").pop()
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.setAttribute("definition", self.macxmlname(ifc))
nem.appendChild(mactag)
@ -105,10 +99,8 @@ class EmaneTdmaModel(EmaneModel):
mac.setAttribute("name", "TDMA MAC")
mac.setAttribute("library", "tdmaeventschedulerradiomodel")
# append MAC options to macdoc
map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, \
self.valueof(n, values))), macnames)
map(lambda n: mac.appendChild(e.xmlparam(macdoc, n, self.valueof(n, values))), macnames)
e.xmlwrite(macdoc, self.macxmlname(ifc))
phydoc = EmaneUniversalModel.getphydoc(e, self, values, phynames)
e.xmlwrite(phydoc, self.phyxmlname(ifc))

View file

@ -1,99 +1,97 @@
#
# 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
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:
from emanesh.events import EventService
except:
pass
from core.api import coreapi
from core.constants import *
from emane import Emane, EmaneModel
except ImportError:
logger.error("error importing emanesh")
class EmaneUniversalModel(EmaneModel):
''' This Univeral PHY model is meant to be imported by other models,
not instantiated.
'''
def __init__(self, session, objid = None, verbose = False):
raise SyntaxError
"""
This Univeral PHY model is meant to be imported by other models,
not instantiated.
"""
_name = "emane_universal"
def __init__(self, session, object_id=None):
raise NotImplemented("Cannot use this class directly")
name = "emane_universal"
_xmlname = "universalphy"
_xmllibrary = "universalphylayer"
# universal PHY parameters
_confmatrix_base = [
("bandwidth", coreapi.CONF_DATA_TYPE_UINT64, '1M',
'', 'rf bandwidth (hz)'),
("frequency", coreapi.CONF_DATA_TYPE_UINT64, '2.347G',
'','frequency (Hz)'),
("frequencyofinterest", coreapi.CONF_DATA_TYPE_UINT64, '2.347G',
'','frequency of interest (Hz)'),
("subid", coreapi.CONF_DATA_TYPE_UINT16, '1',
'','subid'),
("systemnoisefigure", coreapi.CONF_DATA_TYPE_FLOAT, '4.0',
'','system noise figure (dB)'),
("txpower", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
'','transmit power (dBm)'),
("bandwidth", ConfigDataTypes.UINT64.value, '1M',
'', 'rf bandwidth (hz)'),
("frequency", ConfigDataTypes.UINT64.value, '2.347G',
'', 'frequency (Hz)'),
("frequencyofinterest", ConfigDataTypes.UINT64.value, '2.347G',
'', 'frequency of interest (Hz)'),
("subid", ConfigDataTypes.UINT16.value, '1',
'', 'subid'),
("systemnoisefigure", ConfigDataTypes.FLOAT.value, '4.0',
'', 'system noise figure (dB)'),
("txpower", ConfigDataTypes.FLOAT.value, '0.0',
'', 'transmit power (dBm)'),
]
_confmatrix_081 = [
("antennagain", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
'','antenna gain (dBi)'),
("antennaazimuth", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
'','antenna azimuth (deg)'),
("antennaelevation", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
'','antenna elevation (deg)'),
("antennaprofileid", coreapi.CONF_DATA_TYPE_STRING, '1',
'','antenna profile ID'),
("antennaprofilemanifesturi", coreapi.CONF_DATA_TYPE_STRING, '',
'','antenna profile manifest URI'),
("antennaprofileenable", coreapi.CONF_DATA_TYPE_BOOL, '0',
'On,Off','antenna profile mode'),
("defaultconnectivitymode", coreapi.CONF_DATA_TYPE_BOOL, '1',
'On,Off','default connectivity'),
("frequencyofinterestfilterenable", coreapi.CONF_DATA_TYPE_BOOL, '1',
'On,Off','frequency of interest filter enable'),
("noiseprocessingmode", coreapi.CONF_DATA_TYPE_BOOL, '0',
'On,Off','enable noise processing'),
("pathlossmode", coreapi.CONF_DATA_TYPE_STRING, '2ray',
'pathloss,2ray,freespace','path loss mode'),
("antennagain", ConfigDataTypes.FLOAT.value, '0.0',
'', 'antenna gain (dBi)'),
("antennaazimuth", ConfigDataTypes.FLOAT.value, '0.0',
'', 'antenna azimuth (deg)'),
("antennaelevation", ConfigDataTypes.FLOAT.value, '0.0',
'', 'antenna elevation (deg)'),
("antennaprofileid", ConfigDataTypes.STRING.value, '1',
'', 'antenna profile ID'),
("antennaprofilemanifesturi", ConfigDataTypes.STRING.value, '',
'', 'antenna profile manifest URI'),
("antennaprofileenable", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'antenna profile mode'),
("defaultconnectivitymode", ConfigDataTypes.BOOL.value, '1',
'On,Off', 'default connectivity'),
("frequencyofinterestfilterenable", ConfigDataTypes.BOOL.value, '1',
'On,Off', 'frequency of interest filter enable'),
("noiseprocessingmode", ConfigDataTypes.BOOL.value, '0',
'On,Off', 'enable noise processing'),
("pathlossmode", ConfigDataTypes.STRING.value, '2ray',
'pathloss,2ray,freespace', 'path loss mode'),
]
_confmatrix_091 = [
("fixedantennagain", coreapi.CONF_DATA_TYPE_FLOAT, '0.0',
'','antenna gain (dBi)'),
("fixedantennagainenable", coreapi.CONF_DATA_TYPE_BOOL, '1',
'On,Off','enable fixed antenna gain'),
("noisemode", coreapi.CONF_DATA_TYPE_STRING, 'none',
'none,all,outofband','noise processing mode'),
("noisebinsize", coreapi.CONF_DATA_TYPE_UINT64, '20',
'','noise bin size in microseconds'),
("propagationmodel", coreapi.CONF_DATA_TYPE_STRING, '2ray',
'precomputed,2ray,freespace','path loss mode'),
("fixedantennagain", ConfigDataTypes.FLOAT.value, '0.0',
'', 'antenna gain (dBi)'),
("fixedantennagainenable", ConfigDataTypes.BOOL.value, '1',
'On,Off', 'enable fixed antenna gain'),
("noisemode", ConfigDataTypes.STRING.value, 'none',
'none,all,outofband', 'noise processing mode'),
("noisebinsize", ConfigDataTypes.UINT64.value, '20',
'', 'noise bin size in microseconds'),
("propagationmodel", ConfigDataTypes.STRING.value, '2ray',
'precomputed,2ray,freespace', 'path loss mode'),
]
if Emane.version >= Emane.EMANE091:
_confmatrix = _confmatrix_base + _confmatrix_091
if emane.VERSION >= emane.EMANE091:
config_matrix = _confmatrix_base + _confmatrix_091
else:
_confmatrix = _confmatrix_base + _confmatrix_081
config_matrix = _confmatrix_base + _confmatrix_081
# old parameters
_confmatrix_ver074 = [
("antennaazimuthbeamwidth", coreapi.CONF_DATA_TYPE_FLOAT, '360.0',
'','azimith beam width (deg)'),
("antennaelevationbeamwidth", coreapi.CONF_DATA_TYPE_FLOAT, '180.0',
'','elevation beam width (deg)'),
("antennatype", coreapi.CONF_DATA_TYPE_STRING, 'omnidirectional',
'omnidirectional,unidirectional','antenna type'),
]
("antennaazimuthbeamwidth", ConfigDataTypes.FLOAT.value, '360.0',
'', 'azimith beam width (deg)'),
("antennaelevationbeamwidth", ConfigDataTypes.FLOAT.value, '180.0',
'', 'elevation beam width (deg)'),
("antennatype", ConfigDataTypes.STRING.value, 'omnidirectional',
'omnidirectional,unidirectional', 'antenna type'),
]
# parameters that require unit conversion for 0.7.4
_update_ver074 = ("bandwidth", "frequency", "frequencyofinterest")
@ -102,16 +100,15 @@ class EmaneUniversalModel(EmaneModel):
"antennaprofilemanifesturi",
"frequencyofinterestfilterenable")
@classmethod
def getphydoc(cls, e, mac, values, phynames):
phydoc = e.xmldoc("phy")
phy = phydoc.getElementsByTagName("phy").pop()
phy.setAttribute("name", cls._xmlname)
if e.version < e.EMANE091:
if emane.VERSION < emane.EMANE091:
phy.setAttribute("library", cls._xmllibrary)
# 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()
values = list(values)
phynames = list(phynames)
@ -128,7 +125,7 @@ class EmaneUniversalModel(EmaneModel):
phy.appendChild(e.xmlparam(phydoc, old[0], old[2]))
frequencies = None
if e.version >= e.EMANE091:
if emane.VERSION >= emane.EMANE091:
name = "frequencyofinterest"
value = mac.valueof(name, values)
frequencies = cls.valuestrtoparamlist(phydoc, name, value)
@ -137,10 +134,7 @@ class EmaneUniversalModel(EmaneModel):
phynames.remove("frequencyofinterest")
# append all PHY options to phydoc
map( lambda n: phy.appendChild(e.xmlparam(phydoc, n, \
mac.valueof(n, values))), phynames)
map(lambda n: phy.appendChild(e.xmlparam(phydoc, n, mac.valueof(n, values))), phynames)
if frequencies:
phy.appendChild(frequencies)
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
Session object. Provides conversions between Cartesian and geographic coordinate
systems. Depends on utm contributed module, from
https://pypi.python.org/pypi/utm (version 0.3.0).
'''
"""
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
logger = log.get_logger(__name__)
class CoreLocation(ConfigurableManager):
''' Member of session class for handling global location data. This keeps
track of a latitude/longitude/altitude reference point and scale in
order to convert between X,Y and geo coordinates.
TODO: this could be updated to use more generic
Configurable/ConfigurableManager code like other Session objects
'''
_name = "location"
_type = coreapi.CORE_TLV_REG_UTILITY
def __init__(self, session):
ConfigurableManager.__init__(self, session)
"""
Member of session class for handling global location data. This keeps
track of a latitude/longitude/altitude reference point and scale in
order to convert between X,Y and geo coordinates.
TODO: this could be updated to use more generic
Configurable/ConfigurableManager code like other Session objects
"""
name = "location"
config_type = RegisterTlvs.UTILITY.value
def __init__(self):
"""
Creates a MobilityManager instance.
:return: nothing
"""
ConfigurableManager.__init__(self)
self.reset()
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:
self.zonemap[l] = n
def reset(self):
''' Reset to initial state.
'''
"""
Reset to initial state.
"""
# (x, y, z) coordinates of the point given by self.refgeo
self.refxyz = (0.0, 0.0, 0.0)
# 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
self.zoneshifts = {}
def configure_values(self, msg, values):
''' Receive configuration message for setting the reference point
and scale.
'''
def configure_values(self, config_data):
"""
Receive configuration message for setting the reference point
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:
self.session.info("location data missing")
logger.info("location data missing")
return None
values = values.split('|')
# 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
self.refxyz = (refx, refy, refz)
# Geographic reference point
lat,long,alt = map(lambda x: float(x), values[2:5])
self.setrefgeo(lat, long, alt)
lat, lon, alt = map(lambda x: float(x), values[2:5])
self.setrefgeo(lat, lon, alt)
self.refscale = float(values[5])
self.session.info("location configured: (%.2f,%.2f,%.2f) = "
"(%.5f,%.5f,%.5f) scale=%.2f" %
(self.refxyz[0], self.refxyz[1], self.refxyz[2], self.refgeo[0],
self.refgeo[1], self.refgeo[2], self.refscale))
self.session.info("location configured: UTM(%.5f,%.5f,%.5f)" %
(self.refutm[1], self.refutm[2], self.refutm[3]))
logger.info("location configured: (%.2f,%.2f,%.2f) = (%.5f,%.5f,%.5f) scale=%.2f" %
(self.refxyz[0], self.refxyz[1], self.refxyz[2], self.refgeo[0],
self.refgeo[1], self.refgeo[2], self.refscale))
logger.info("location configured: UTM(%.5f,%.5f,%.5f)" %
(self.refutm[1], self.refutm[2], self.refutm[3]))
def px2m(self, val):
''' Convert the specified value in pixels to meters using the
configured scale. The scale is given as s, where
100 pixels = s meters.
'''
"""
Convert the specified value in pixels to meters using the
configured scale. The scale is given as s, where
100 pixels = s meters.
:param val: value to use in converting to meters
:return: value converted to meters
"""
return (val / 100.0) * self.refscale
def m2px(self, val):
''' Convert the specified value in meters to pixels using the
configured scale. The scale is given as s, where
100 pixels = s meters.
'''
"""
Convert the specified value in meters to pixels using the
configured scale. The scale is given as s, where
100 pixels = s meters.
:param val: value to convert to pixels
:return: value converted to pixels
"""
if self.refscale == 0.0:
return 0.0
return 100.0 * (val / self.refscale)
def setrefgeo(self, lat, lon, alt):
''' Record the geographical reference point decimal (lat, lon, alt)
and convert and store its UTM equivalent for later use.
'''
"""
Record the geographical reference point decimal (lat, lon, alt)
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)
# easting, northing, zone
(e, n, zonen, zonel) = utm.from_latlon(lat, lon)
self.refutm = ( (zonen, zonel), e, n, alt)
e, n, zonen, zonel = utm.from_latlon(lat, lon)
self.refutm = ((zonen, zonel), e, n, alt)
def getgeo(self, x, y, z):
''' Given (x, y, z) Cartesian coordinates, convert them to latitude,
longitude, and altitude based on the configured reference point
and scale.
'''
"""
Given (x, y, z) Cartesian coordinates, convert them to latitude,
longitude, and altitude based on the configured reference point
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)
x = x - self.refxyz[0]
x -= self.refxyz[0]
y = -(y - self.refxyz[1])
if z is None:
z = self.refxyz[2]
else:
z = z - self.refxyz[2]
z -= self.refxyz[2]
# use UTM coordinates since unit is meters
zone = self.refutm[0]
if zone == "":
raise ValueError, "reference point not configured"
raise ValueError("reference point not configured")
e = self.refutm[1] + self.px2m(x)
n = self.refutm[2] + self.px2m(y)
alt = self.refutm[3] + self.px2m(z)
@ -117,23 +152,29 @@ class CoreLocation(ConfigurableManager):
try:
lat, lon = utm.to_latlon(e, n, zone[0], zone[1])
except utm.OutOfRangeError:
self.info("UTM out of range error for e=%s n=%s zone=%s" \
"xyz=(%s,%s,%s)" % (e, n, zone, x, y, z))
(lat, lon) = self.refgeo[:2]
#self.info("getgeo(%s,%s,%s) e=%s n=%s zone=%s lat,lon,alt=" \
logger.exception("UTM out of range error for n=%s zone=%s xyz=(%s,%s,%s)", n, zone, x, y, z)
lat, lon = self.refgeo[:2]
# 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))
return (lat, lon, alt)
return lat, lon, alt
def getxyz(self, lat, lon, alt):
''' Given latitude, longitude, and altitude location data, convert them
to (x, y, z) Cartesian coordinates based on the configured
reference point and scale. Lat/lon is converted to UTM meter
coordinates, UTM zones are accounted for, and the scale turns
meters to pixels.
'''
"""
Given latitude, longitude, and altitude location data, convert them
to (x, y, z) Cartesian coordinates based on the configured
reference point and scale. Lat/lon is converted to UTM meter
coordinates, UTM zones are accounted for, and the scale turns
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
(e, n, zonen, zonel) = utm.from_latlon(lat, lon)
(rlat, rlon, ralt) = self.refgeo
e, n, zonen, zonel = utm.from_latlon(lat, lon)
rlat, rlon, ralt = self.refgeo
xshift = self.geteastingshift(zonen, zonel)
if xshift is None:
xm = e - self.refutm[1]
@ -145,31 +186,40 @@ class CoreLocation(ConfigurableManager):
else:
ym = n + yshift
zm = alt - ralt
# shift (x,y,z) over to reference point (x,y,z)
x = self.m2px(xm) + self.refxyz[0]
y = -(self.m2px(ym) + self.refxyz[1])
z = self.m2px(zm) + self.refxyz[2]
return (x, y, z)
return x, y, z
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
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
meters for the x coordinate.
'''
:param zonen: zonen
:param zonel: zone1
:return: the x shift value
"""
rzonen = int(self.refutm[0][0])
# same zone number, no x shift required
if zonen == rzonen:
return None # same zone number, no x shift required
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] # x shift already calculated, cached
(rlat, rlon, ralt) = self.refgeo
lon2 = rlon + 6*(zonen - rzonen) # ea. zone is 6deg band
(e2, n2, zonen2, zonel2) = utm.from_latlon(rlat, lon2) # ignore northing
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!
xshift = utm.haversine(rlon, rlat, lon2, rlat) - e2
# cache the return value
@ -178,27 +228,35 @@ class CoreLocation(ConfigurableManager):
yshift = self.zoneshifts[z][1]
self.zoneshifts[z] = (xshift, yshift)
return xshift
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
may need to be shifted.
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
y coordinate.
'''
: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 # same zone letter, no y shift required
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] # y shift already calculated, cached
(rlat, rlon, ralt) = self.refgeo
return self.zoneshifts[z][1]
rlat, rlon, ralt = self.refgeo
# zonemap is used to calculate degrees difference between zone letters
latshift = self.zonemap[zonel] - self.zonemap[rzonel]
lat2 = rlat + latshift # ea. latitude band is 8deg high
(e2, n2, zonen2, zonel2) = utm.from_latlon(lat2, rlon)
# ea. latitude band is 8deg high
lat2 = rlat + latshift
e2, n2, zonen2, zonel2 = utm.from_latlon(lat2, rlon)
# NOTE: great circle distance used here, not reference ellipsoid
yshift = -(utm.haversine(rlon, rlat, rlon, lat2) + n2)
# cache the return value
@ -209,26 +267,32 @@ class CoreLocation(ConfigurableManager):
return yshift
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
different zone and the new zone if they do. Zone lettering is only
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]
(rlat, rlon, ralt) = self.refgeo
rlat, rlon, ralt = self.refgeo
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)
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)
# after >3 zones away from refpt, the above estimate won't work
# (the above estimate could be improved)
if not 100000 <= (e - xshift) < 1000000:
# 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)
(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)
e = e - xshift
zone = (zonen2, zonel2)
@ -240,7 +304,4 @@ class CoreLocation(ConfigurableManager):
# refpt in southern hemisphere and we crossed north of equator
n -= 10000000
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
#LatLong- UTM conversion..h
#definitions for lat/long to UTM and UTM to lat/lng conversions
#include <string.h>
# LatLong- UTM conversion..h
# definitions for lat/long to UTM and UTM to lat/lng conversions
# include <string.h>
_deg2rad = pi / 180.0
_rad2deg = 180.0 / pi
@ -16,48 +16,49 @@ _EquatorialRadius = 2
_eccentricitySquared = 3
_ellipsoid = [
# id, Ellipsoid name, Equatorial Radius, square of eccentricity
# first once is a placeholder only, To allow array indices to match id numbers
[ -1, "Placeholder", 0, 0],
[ 1, "Airy", 6377563, 0.00667054],
[ 2, "Australian National", 6378160, 0.006694542],
[ 3, "Bessel 1841", 6377397, 0.006674372],
[ 4, "Bessel 1841 (Nambia] ", 6377484, 0.006674372],
[ 5, "Clarke 1866", 6378206, 0.006768658],
[ 6, "Clarke 1880", 6378249, 0.006803511],
[ 7, "Everest", 6377276, 0.006637847],
[ 8, "Fischer 1960 (Mercury] ", 6378166, 0.006693422],
[ 9, "Fischer 1968", 6378150, 0.006693422],
[ 10, "GRS 1967", 6378160, 0.006694605],
[ 11, "GRS 1980", 6378137, 0.00669438],
[ 12, "Helmert 1906", 6378200, 0.006693422],
[ 13, "Hough", 6378270, 0.00672267],
[ 14, "International", 6378388, 0.00672267],
[ 15, "Krassovsky", 6378245, 0.006693422],
[ 16, "Modified Airy", 6377340, 0.00667054],
[ 17, "Modified Everest", 6377304, 0.006637847],
[ 18, "Modified Fischer 1960", 6378155, 0.006693422],
[ 19, "South American 1969", 6378160, 0.006694542],
[ 20, "WGS 60", 6378165, 0.006693422],
[ 21, "WGS 66", 6378145, 0.006694542],
[ 22, "WGS-72", 6378135, 0.006694318],
[ 23, "WGS-84", 6378137, 0.00669438]
# id, Ellipsoid name, Equatorial Radius, square of eccentricity
# first once is a placeholder only, To allow array indices to match id numbers
[-1, "Placeholder", 0, 0],
[1, "Airy", 6377563, 0.00667054],
[2, "Australian National", 6378160, 0.006694542],
[3, "Bessel 1841", 6377397, 0.006674372],
[4, "Bessel 1841 (Nambia] ", 6377484, 0.006674372],
[5, "Clarke 1866", 6378206, 0.006768658],
[6, "Clarke 1880", 6378249, 0.006803511],
[7, "Everest", 6377276, 0.006637847],
[8, "Fischer 1960 (Mercury] ", 6378166, 0.006693422],
[9, "Fischer 1968", 6378150, 0.006693422],
[10, "GRS 1967", 6378160, 0.006694605],
[11, "GRS 1980", 6378137, 0.00669438],
[12, "Helmert 1906", 6378200, 0.006693422],
[13, "Hough", 6378270, 0.00672267],
[14, "International", 6378388, 0.00672267],
[15, "Krassovsky", 6378245, 0.006693422],
[16, "Modified Airy", 6377340, 0.00667054],
[17, "Modified Everest", 6377304, 0.006637847],
[18, "Modified Fischer 1960", 6378155, 0.006693422],
[19, "South American 1969", 6378160, 0.006694542],
[20, "WGS 60", 6378165, 0.006693422],
[21, "WGS 66", 6378145, 0.006694542],
[22, "WGS-72", 6378135, 0.006694318],
[23, "WGS-84", 6378137, 0.00669438]
]
#Reference ellipsoids derived from Peter H. Dana's website-
#http://www.utexas.edu/depts/grg/gcraft/notes/datum/elist.html
#Department of Geography, University of Texas at Austin
#Internet: pdana@mail.utexas.edu
#3/22/95
#Source
#Defense Mapping Agency. 1987b. DMA Technical Report: Supplement to Department of Defense World Geodetic System
#1984 Technical Report. Part I and II. Washington, DC: Defense Mapping Agency
# 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
#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)
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
East Longitudes are positive, West longitudes 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]
k0 = 0.9996
#Make sure the longitude is between -180.00 .. 179.9
LongTemp = (Long+180)-int((Long+180)/360)*360-180 # -180.00 .. 179.9
# Make sure the longitude is between -180.00 .. 179.9
LongTemp = (Long + 180) - int((Long + 180) / 360) * 360 - 180 # -180.00 .. 179.9
LatRad = Lat*_deg2rad
LongRad = LongTemp*_deg2rad
LatRad = Lat * _deg2rad
LongRad = LongTemp * _deg2rad
if zone is None:
ZoneNumber = int((LongTemp + 180)/6) + 1
ZoneNumber = int((LongTemp + 180) / 6) + 1
else:
ZoneNumber = zone
@ -84,46 +85,50 @@ def LLtoUTM(ReferenceEllipsoid, Lat, Long, zone = None):
# Special zones for Svalbard
if Lat >= 72.0 and Lat < 84.0:
if LongTemp >= 0.0 and LongTemp < 9.0:ZoneNumber = 31
elif LongTemp >= 9.0 and LongTemp < 21.0: ZoneNumber = 33
elif LongTemp >= 21.0 and LongTemp < 33.0: ZoneNumber = 35
elif LongTemp >= 33.0 and LongTemp < 42.0: ZoneNumber = 37
if LongTemp >= 0.0 and LongTemp < 9.0:
ZoneNumber = 31
elif LongTemp >= 9.0 and LongTemp < 21.0:
ZoneNumber = 33
elif LongTemp >= 21.0 and LongTemp < 33.0:
ZoneNumber = 35
elif LongTemp >= 33.0 and LongTemp < 42.0:
ZoneNumber = 37
LongOrigin = (ZoneNumber - 1)*6 - 180 + 3 #+3 puts origin in middle of zone
LongOrigin = (ZoneNumber - 1) * 6 - 180 + 3 # +3 puts origin in middle of zone
LongOriginRad = LongOrigin * _deg2rad
#compute the UTM Zone from the latitude and longitude
# compute the UTM Zone from the latitude and longitude
UTMZone = "%d%c" % (ZoneNumber, _UTMLetterDesignator(Lat))
eccPrimeSquared = (eccSquared)/(1-eccSquared)
N = a/sqrt(1-eccSquared*sin(LatRad)*sin(LatRad))
T = tan(LatRad)*tan(LatRad)
C = eccPrimeSquared*cos(LatRad)*cos(LatRad)
A = cos(LatRad)*(LongRad-LongOriginRad)
eccPrimeSquared = (eccSquared) / (1 - eccSquared)
N = a / sqrt(1 - eccSquared * sin(LatRad) * sin(LatRad))
T = tan(LatRad) * tan(LatRad)
C = eccPrimeSquared * cos(LatRad) * cos(LatRad)
A = cos(LatRad) * (LongRad - LongOriginRad)
M = a*((1
- eccSquared/4
- 3*eccSquared*eccSquared/64
- 5*eccSquared*eccSquared*eccSquared/256)*LatRad
- (3*eccSquared/8
+ 3*eccSquared*eccSquared/32
+ 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*LatRad)
+ (15*eccSquared*eccSquared/256 + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*LatRad)
- (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*LatRad))
M = a * ((1
- eccSquared / 4
- 3 * eccSquared * eccSquared / 64
- 5 * eccSquared * eccSquared * eccSquared / 256) * LatRad
- (3 * eccSquared / 8
+ 3 * eccSquared * eccSquared / 32
+ 45 * eccSquared * eccSquared * eccSquared / 1024) * sin(2 * LatRad)
+ (15 * eccSquared * eccSquared / 256 + 45 * eccSquared * eccSquared * eccSquared / 1024) * sin(4 * LatRad)
- (35 * eccSquared * eccSquared * eccSquared / 3072) * sin(6 * LatRad))
UTMEasting = (k0*N*(A+(1-T+C)*A*A*A/6
+ (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120)
UTMEasting = (k0 * N * (A + (1 - T + C) * A * A * A / 6
+ (5 - 18 * T + T * T + 72 * C - 58 * eccPrimeSquared) * A * A * A * A * A / 120)
+ 500000.0)
UTMNorthing = (k0*(M+N*tan(LatRad)*(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24
+ (61
-58*T
+T*T
+600*C
-330*eccPrimeSquared)*A*A*A*A*A*A/720)))
UTMNorthing = (k0 * (M + N * tan(LatRad) * (A * A / 2 + (5 - T + 9 * C + 4 * C * C) * A * A * A * A / 24
+ (61
- 58 * T
+ T * T
+ 600 * C
- 330 * eccPrimeSquared) * A * A * A * A * A * A / 720)))
if Lat < 0:
UTMNorthing = UTMNorthing + 10000000.0; #10000000 meter offset for southern hemisphere
UTMNorthing = UTMNorthing + 10000000.0; # 10000000 meter offset for southern hemisphere
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
Written by Chuck Gantz- chuck.gantz@globalstar.com"""
if 84 >= Lat >= 72: return 'X'
elif 72 > Lat >= 64: return 'W'
elif 64 > Lat >= 56: return 'V'
elif 56 > Lat >= 48: return 'U'
elif 48 > Lat >= 40: return 'T'
elif 40 > Lat >= 32: return 'S'
elif 32 > Lat >= 24: return 'R'
elif 24 > Lat >= 16: return 'Q'
elif 16 > Lat >= 8: return 'P'
elif 8 > Lat >= 0: return 'N'
elif 0 > Lat >= -8: return 'M'
elif -8> Lat >= -16: return 'L'
elif -16 > Lat >= -24: return 'K'
elif -24 > Lat >= -32: return 'J'
elif -32 > Lat >= -40: return 'H'
elif -40 > Lat >= -48: return 'G'
elif -48 > Lat >= -56: return 'F'
elif -56 > Lat >= -64: return 'E'
elif -64 > Lat >= -72: return 'D'
elif -72 > Lat >= -80: return 'C'
else: return 'Z' # if the Latitude is outside the UTM limits
if 84 >= Lat >= 72:
return 'X'
elif 72 > Lat >= 64:
return 'W'
elif 64 > Lat >= 56:
return 'V'
elif 56 > Lat >= 48:
return 'U'
elif 48 > Lat >= 40:
return 'T'
elif 40 > Lat >= 32:
return 'S'
elif 32 > Lat >= 24:
return 'R'
elif 24 > Lat >= 16:
return 'Q'
elif 16 > Lat >= 8:
return 'P'
elif 8 > Lat >= 0:
return 'N'
elif 0 > Lat >= -8:
return 'M'
elif -8 > Lat >= -16:
return 'L'
elif -16 > Lat >= -24:
return 'K'
elif -24 > Lat >= -32:
return 'J'
elif -32 > Lat >= -40:
return 'H'
elif -40 > Lat >= -48:
return 'G'
elif -48 > Lat >= -56:
return 'F'
elif -56 > Lat >= -64:
return 'E'
elif -64 > Lat >= -72:
return 'D'
elif -72 > Lat >= -80:
return 'C'
else:
return 'Z' # if the Latitude is outside the UTM limits
#void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone,
# void UTMtoLL(int ReferenceEllipsoid, const double UTMNorthing, const double UTMEasting, const char* UTMZone,
# double& Lat, double& Long )
def UTMtoLL(ReferenceEllipsoid, northing, easting, zone):
@ -168,10 +195,10 @@ Converted to Python by Russ Nelson <nelson@crynwr.com>"""
k0 = 0.9996
a = _ellipsoid[ReferenceEllipsoid][_EquatorialRadius]
eccSquared = _ellipsoid[ReferenceEllipsoid][_eccentricitySquared]
e1 = (1-sqrt(1-eccSquared))/(1+sqrt(1-eccSquared))
#NorthernHemisphere; //1 for northern hemispher, 0 for southern
e1 = (1 - sqrt(1 - eccSquared)) / (1 + sqrt(1 - eccSquared))
# NorthernHemisphere; //1 for northern hemispher, 0 for southern
x = easting - 500000.0 #remove 500,000 meter offset for longitude
x = easting - 500000.0 # remove 500,000 meter offset for longitude
y = northing
ZoneLetter = zone[-1]
@ -180,37 +207,40 @@ Converted to Python by Russ Nelson <nelson@crynwr.com>"""
NorthernHemisphere = 1 # point is in northern hemisphere
else:
NorthernHemisphere = 0 # point is in southern hemisphere
y -= 10000000.0 # remove 10,000,000 meter offset used for southern hemisphere
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
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)
+ (21*e1*e1/16-55*e1*e1*e1*e1/32)*sin(4*mu)
+(151*e1*e1*e1/96)*sin(6*mu))
phi1 = phi1Rad*_rad2deg;
phi1Rad = (mu + (3 * e1 / 2 - 27 * e1 * e1 * e1 / 32) * sin(2 * mu)
+ (21 * e1 * e1 / 16 - 55 * e1 * e1 * e1 * e1 / 32) * sin(4 * mu)
+ (151 * e1 * e1 * e1 / 96) * sin(6 * mu))
phi1 = phi1Rad * _rad2deg;
N1 = a/sqrt(1-eccSquared*sin(phi1Rad)*sin(phi1Rad))
T1 = tan(phi1Rad)*tan(phi1Rad)
C1 = eccPrimeSquared*cos(phi1Rad)*cos(phi1Rad)
R1 = a*(1-eccSquared)/pow(1-eccSquared*sin(phi1Rad)*sin(phi1Rad), 1.5)
D = x/(N1*k0)
N1 = a / sqrt(1 - eccSquared * sin(phi1Rad) * sin(phi1Rad))
T1 = tan(phi1Rad) * tan(phi1Rad)
C1 = eccPrimeSquared * cos(phi1Rad) * cos(phi1Rad)
R1 = a * (1 - eccSquared) / pow(1 - eccSquared * sin(phi1Rad) * sin(phi1Rad), 1.5)
D = x / (N1 * k0)
Lat = phi1Rad - (N1*tan(phi1Rad)/R1)*(D*D/2-(5+3*T1+10*C1-4*C1*C1-9*eccPrimeSquared)*D*D*D*D/24
+(61+90*T1+298*C1+45*T1*T1-252*eccPrimeSquared-3*C1*C1)*D*D*D*D*D*D/720)
Lat = phi1Rad - (N1 * tan(phi1Rad) / R1) * (
D * D / 2 - (5 + 3 * T1 + 10 * C1 - 4 * C1 * C1 - 9 * eccPrimeSquared) * D * D * D * D / 24
+ (61 + 90 * T1 + 298 * C1 + 45 * T1 * T1 - 252 * eccPrimeSquared - 3 * C1 * C1) * D * D * D * D * D * D / 720)
Lat = Lat * _rad2deg
Long = (D-(1+2*T1+C1)*D*D*D/6+(5-2*C1+28*T1-3*C1*C1+8*eccPrimeSquared+24*T1*T1)
*D*D*D*D*D/120)/cos(phi1Rad)
Long = (D - (1 + 2 * T1 + C1) * D * D * D / 6 + (
5 - 2 * C1 + 28 * T1 - 3 * C1 * C1 + 8 * eccPrimeSquared + 24 * T1 * T1)
* D * D * D * D * D / 120) / cos(phi1Rad)
Long = LongOrigin + Long * _rad2deg
return (Lat, Long)
if __name__ == '__main__':
(z, e, n) = LLtoUTM(23, 45.00, -75.00)
print z, e, n
print UTMtoLL(23, n, e, z)

View file

@ -1,76 +1,141 @@
#
# 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.
'''
import time
import threading
"""
import heapq
import threading
import time
from core.misc import log
logger = log.get_logger(__name__)
class Timer(threading.Thread):
"""
Based on threading.Timer but cancel() returns if the timer was
already running.
"""
def __init__(self, interval, function, args=None, kwargs=None):
"""
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.function = function
self.finished = threading.Event()
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):
"""
Stop the timer if it hasn't finished yet. Return False if
the timer was already running.
:return: True if canceled, False otherwise
:rtype: bool
"""
locked = self._running.acquire(False)
if locked:
self.finished.set()
self._running.release()
return locked
def run(self):
"""
Run the timer.
:return: nothing
"""
self.finished.wait(self.interval)
with self._running:
if not self.finished.is_set():
self.function(*self.args, **self.kwargs)
self.finished.set()
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.time = event_time
self.func = func
self.args = args
self.kwds = kwds
self.canceled = False
def __cmp__(self, other):
"""
Comparison function.
:param Event other: event to compare with
:return: comparison result
:rtype: int
"""
tmp = cmp(self.time, other.time)
if tmp == 0:
tmp = cmp(self.eventnum, other.eventnum)
return tmp
def run(self):
"""
Run an event.
:return: nothing
"""
if self.canceled:
return
self.func(*self.args, **self.kwds)
def cancel(self):
"""
Cancel event.
:return: nothing
"""
# XXX not thread-safe
self.canceled = True
class EventLoop(object):
class Timer(threading.Thread):
'''\
Based on threading.Timer but cancel() returns if the timer was
already running.
'''
def __init__(self, interval, function, args=[], kwargs={}):
super(EventLoop.Timer, self).__init__()
self.interval = interval
self.function = function
self.args = args
self.kwargs = kwargs
self.finished = threading.Event()
self._running = threading.Lock()
def cancel(self):
'''\
Stop the timer if it hasn't finished yet. Return False if
the timer was already running.
'''
locked = self._running.acquire(False)
if locked:
self.finished.set()
self._running.release()
return locked
def run(self):
self.finished.wait(self.interval)
with self._running:
if not self.finished.is_set():
self.function(*self.args, **self.kwargs)
self.finished.set()
class Event(object):
def __init__(self, eventnum, time, func, *args, **kwds):
self.eventnum = eventnum
self.time = time
self.func = func
self.args = args
self.kwds = kwds
self.canceled = False
def __cmp__(self, other):
tmp = cmp(self.time, other.time)
if tmp == 0:
tmp = cmp(self.eventnum, other.eventnum)
return tmp
def run(self):
if self.canceled:
return
self.func(*self.args, **self.kwds)
def cancel(self):
self.canceled = True # XXX not thread-safe
"""
Provides an event loop for running events.
"""
def __init__(self):
"""
Creates a EventLoop instance.
"""
self.lock = threading.RLock()
self.queue = []
self.eventnum = 0
@ -79,6 +144,11 @@ class EventLoop(object):
self.start = None
def __run_events(self):
"""
Run events.
:return: nothing
"""
schedule = False
while True:
with self.lock:
@ -91,23 +161,34 @@ class EventLoop(object):
event = heapq.heappop(self.queue)
assert event.time <= now
event.run()
with self.lock:
self.timer = None
if schedule:
self.__schedule_event()
def __schedule_event(self):
"""
Schedule event.
:return: nothing
"""
with self.lock:
assert self.running
if not self.queue:
return
delay = self.queue[0].time - time.time()
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.start()
def run(self):
"""
Start event loop.
:return: nothing
"""
with self.lock:
if self.running:
return
@ -118,6 +199,11 @@ class EventLoop(object):
self.__schedule_event()
def stop(self):
"""
Stop event loop.
:return: nothing
"""
with self.lock:
if not self.running:
return
@ -130,13 +216,23 @@ class EventLoop(object):
self.start = None
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:
eventnum = self.eventnum
self.eventnum += 1
evtime = float(delaysec)
if self.running:
evtime += time.time()
event = self.Event(eventnum, evtime, func, *args, **kwds)
event = Event(eventnum, evtime, func, *args, **kwds)
if self.queue:
prevhead = self.queue[0]
@ -152,12 +248,14 @@ class EventLoop(object):
self.__schedule_event()
return event
# TODO: move example to documentation
def example():
loop = EventLoop()
def msg(arg):
delta = time.time() - loop.start
print delta, 'arg:', arg
logger.debug("%s arg: %s", delta, arg)
def repeat(interval, count):
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>
#
'''
quagga.py: helper class for generating Quagga configuration.
'''
import os.path
"""
quagga.py: helper class for generating Quagga configuration.
"""
from string import Template
def maketuple(obj):
if hasattr(obj, "__iter__"):
return tuple(obj)
from core.misc import utils
def addrstr(x):
if x.find(".") >= 0:
return "ip address %s" % x
elif x.find(":") >= 0:
return "ipv6 address %s" % x
else:
return (obj,)
raise ValueError("invalid address: %s" % x)
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.addrlist = addrlist
if addrlist:
self.addrlist = addrlist
else:
self.addrlist = []
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):
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]
return tmp
class QuaggaOSPF6Interface(Conf):
"""
Provides quagga ospf6 interface functionality.
"""
AF_IPV6_ID = 0
AF_IPV4_ID = 65
@ -50,32 +91,40 @@ interface $interface
ipv6 ospf6 lsafullness mincostlsa
""")
# ip address $ipaddr/32
# ipv6 ospf6 simhelloLLtoULRecv :$simhelloport
# !$ipaddr:$simhelloport
# ip address $ipaddr/32
# ipv6 ospf6 simhelloLLtoULRecv :$simhelloport
# !$ipaddr:$simhelloport
def __init__(self, netif, instanceid = AF_IPV4_ID,
network = "manet-designated-router", **kwds):
def __init__(self, netif, instanceid=AF_IPV4_ID, network="manet-designated-router", **kwargs):
"""
Create a QuaggaOSPF6Interface instance.
:param netif: network interface
:param int instanceid: instance id
:param network: network
:param dict kwargs: keyword arguments
"""
self.netif = netif
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))
self.instanceid = instanceid
self.network = network
Conf.__init__(self, interface = netif.name, addr = addr,
instanceid = instanceid, network = network, **kwds)
Conf.__init__(self, interface=netif.name, addr=addr,
instanceid=instanceid, network=network, **kwargs)
def name(self):
"""
Retrieve network interface name.
:return: network interface name
:rtype: str
"""
return self.netif.name
class QuaggaOSPF6(Conf):
class QuaggaOSPF6(Conf):
"""
Provides quagga ospf6 functionality.
"""
template = Template("""\
$interfaces
!
@ -85,17 +134,25 @@ router ospf6
$redistribute
""")
def __init__(self, ospf6ifs, area, routerid,
redistribute = "! no redistribute"):
ospf6ifs = maketuple(ospf6ifs)
def __init__(self, ospf6ifs, area, routerid, redistribute="! no redistribute"):
"""
Create a QuaggaOSPF6 instance.
:param list ospf6ifs: ospf6 interfaces
:param area: area
:param routerid: router id
:param str redistribute: redistribute value
"""
ospf6ifs = utils.maketuple(ospf6ifs)
interfaces = "\n!\n".join(map(str, ospf6ifs))
ospfifs = "\n ".join(map(lambda x: "interface %s area %s" % \
(x.name(), area), ospf6ifs))
Conf.__init__(self, interfaces = interfaces, routerid = routerid,
ospfifs = ospfifs, redistribute = redistribute)
ospfifs = "\n ".join(map(lambda x: "interface %s area %s" % (x.name(), area), ospf6ifs))
Conf.__init__(self, interfaces=interfaces, routerid=routerid, ospfifs=ospfifs, redistribute=redistribute)
class QuaggaConf(Conf):
"""
Provides quagga configuration functionality.
"""
template = Template("""\
log file $logfile
$debugs
@ -105,12 +162,18 @@ $routers
$forwarding
""")
def __init__(self, routers, logfile, debugs = ()):
routers = "\n!\n".join(map(str, maketuple(routers)))
def __init__(self, routers, logfile, debugs=()):
"""
Create a QuaggaConf instance.
:param list routers: routers
:param str logfile: log file name
:param debugs: debug options
"""
routers = "\n!\n".join(map(str, utils.maketuple(routers)))
if debugs:
debugs = "\n".join(maketuple(debugs))
debugs = "\n".join(utils.maketuple(debugs))
else:
debugs = "! no debugs"
forwarding = "ip forwarding\nipv6 forwarding"
Conf.__init__(self, logfile = logfile, debugs = debugs,
routers = routers, forwarding = forwarding)
Conf.__init__(self, logfile=logfile, debugs=debugs, routers=routers, forwarding=forwarding)

View file

@ -1,123 +1,235 @@
#
# CORE
# 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>
#
'''
utils.py: miscellaneous utility functions, wrappers around some subprocess
procedures.
'''
"""
Miscellaneous utility functions, wrappers around some subprocess procedures.
"""
import ast
import os
import subprocess
import subprocess, os, ast
import fcntl
import resource
from core.misc import log
logger = log.get_logger(__name__)
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)
fcntl.fcntl(fd, fcntl.F_SETFD, fdflags | fcntl.FD_CLOEXEC)
def checkexec(execlist):
for bin in execlist:
if which(bin) is None:
raise EnvironmentError, "executable not found: %s" % bin
def check_executables(executables):
"""
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):
''' 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)
"""
From: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
:param str program: program to check for
:return: path if it exists, none otherwise
"""
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
path = path.strip('"')
path = path.strip("\"")
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
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(":")
for p in set(pathlist):
if p not in searchpath:
os.environ["PATH"] += ":" + p
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__"):
return tuple(obj)
else:
return (obj,)
return obj,
# TODO: remove unused parameter 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 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):
kwds["stdout"] = open(os.devnull, "w")
kwds["stderr"] = subprocess.STDOUT
return call(*args, **kwds)
def mutecall(*args, **kwargs):
"""
Run a muted call command.
def check_call(*args, **kwds):
return subprocess.check_call(*args, **kwds)
: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.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):
return subprocess.Popen(*args, **kwds).pid
def mutecheck_call(*args, **kwargs):
"""
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():
"""
Fork a child process and exit.
:return: nothing
"""
if os.fork():
os._exit(0) # parent exits
# parent exits
os._exit(0)
os.setsid()
def detach(*args, **kwds):
kwds["preexec_fn"] = detachinit
return subprocess.Popen(*args, **kwds).pid
def mutedetach(*args, **kwds):
kwds["preexec_fn"] = detachinit
kwds["stdout"] = open(os.devnull, "w")
kwds["stderr"] = subprocess.STDOUT
return subprocess.Popen(*args, **kwds).pid
def detach(*args, **kwargs):
"""
Run a 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
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):
''' 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.
'''
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)
"""
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.
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 = ""
count = 0
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)
return dump[:-1]
def filemunge(pathname, header, text):
''' Insert text at the end of a file, surrounded by header comments.
'''
filedemunge(pathname, header) # prevent duplicates
f = open(pathname, 'a')
"""
Insert text at the end of a file, surrounded by header comments.
: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(text)
f.write("# END %s\n" % header)
f.close()
def filedemunge(pathname, header):
''' Remove text that was inserted in a file surrounded by header comments.
'''
f = open(pathname, 'r')
"""
Remove text that was inserted in a file surrounded by header comments.
: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()
f.close()
start = None
@ -157,66 +283,101 @@ def filedemunge(pathname, header):
end = i + 1
if start is None or end is None:
return
f = open(pathname, 'w')
f = open(pathname, "w")
lines = lines[:start] + lines[end:]
f.write("".join(lines))
f.close()
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:
pathname = pathname.replace('~', "/home/%s" % session.user)
pathname = pathname.replace('%SESSION%', str(session.sessionid))
pathname = pathname.replace('%SESSION_DIR%', session.sessiondir)
pathname = pathname.replace('%SESSION_USER%', session.user)
pathname = pathname.replace("~", "/home/%s" % session.user)
pathname = pathname.replace("%SESSION%", str(session.session_id))
pathname = pathname.replace("%SESSION_DIR%", session.session_dir)
pathname = pathname.replace("%SESSION_USER%", session.user)
if node is not None:
pathname = pathname.replace('%NODE%', str(node.objid))
pathname = pathname.replace('%NODENAME%', node.name)
pathname = pathname.replace("%NODE%", str(node.objid))
pathname = pathname.replace("%NODENAME%", node.name)
return pathname
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:
return None
return devname.replace(".", "/")
def daemonize(rootdir = "/", umask = 0, close_fds = False, dontclose = (),
stdin = os.devnull, stdout = os.devnull, stderr = os.devnull,
stdoutmode = 0644, stderrmode = 0644, pidfilename = None,
defaultmaxfd = 1024):
''' Run the background process as a daemon.
'''
def daemonize(rootdir="/", umask=0, close_fds=False, dontclose=(),
stdin=os.devnull, stdout=os.devnull, stderr=os.devnull,
stdoutmode=0644, stderrmode=0644, pidfilename=None,
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 isinstance(dontclose, int):
raise TypeError, "dontclose must be an integer"
raise TypeError("dontclose must be an integer")
dontclose = (int(dontclose),)
else:
for fd in dontclose:
if not isinstance(fd, int):
raise TypeError, "dontclose must contain only integers"
raise TypeError("dontclose must contain only integers")
# redirect stdin
if stdin:
fd = os.open(stdin, os.O_RDONLY)
os.dup2(fd, 0)
os.close(fd)
# redirect stdout
if stdout:
fd = os.open(stdout, os.O_WRONLY | os.O_CREAT | os.O_APPEND,
stdoutmode)
os.dup2(fd, 1)
if (stdout == stderr):
if stdout == stderr:
os.dup2(1, 2)
os.close(fd)
# redirect stderr
if stderr and (stderr != stdout):
fd = os.open(stderr, os.O_WRONLY | os.O_CREAT | os.O_APPEND,
stderrmode)
os.dup2(fd, 2)
os.close(fd)
if os.fork():
os._exit(0) # parent exits
# parent exits
os._exit(0)
os.setsid()
pid = os.fork()
if pid:
@ -225,11 +386,14 @@ def daemonize(rootdir = "/", umask = 0, close_fds = False, dontclose = (),
f = open(pidfilename, "w")
f.write("%s\n" % pid)
f.close()
except:
pass
os._exit(0) # parent exits
except IOError:
logger.exception("error writing to file: %s", pidfilename)
# parent exits
os._exit(0)
if rootdir:
os.chdir(rootdir)
os.umask(umask)
if close_fds:
try:
@ -238,38 +402,49 @@ def daemonize(rootdir = "/", umask = 0, close_fds = False, dontclose = (),
raise ValueError
except:
maxfd = defaultmaxfd
for fd in xrange(3, maxfd):
if fd in dontclose:
continue
try:
os.close(fd)
except:
pass
except IOError:
logger.exception("error closing file descriptor")
def readfileintodict(filename, d):
''' Read key=value pairs from a file, into a dict.
Skip comments; strip newline characters and spacing.
'''
with open(filename, 'r') as f:
"""
Read key=value pairs from a file, into a dict. Skip comments; strip newline characters and spacing.
: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()
for l in lines:
if l[:1] == '#':
if l[:1] == "#":
continue
try:
key, value = l.split('=', 1)
key, value = l.split("=", 1)
d[key] = value.strip()
except ValueError:
pass
logger.exception("error reading file to dict: %s", filename)
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,
memory size (bytes), number of loaded instances, dependencies, state,
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:
if line.startswith(name + ' '):
if line.startswith(name + " "):
return line.rstrip()
return None

View file

@ -66,6 +66,7 @@ import math
__all__ = ['to_latlon', 'from_latlon']
class OutOfRangeError(ValueError):
pass
@ -139,7 +140,7 @@ def to_latlon(easting, northing, zone_number, zone_letter):
n = R / ep_sin_sqrt
r = (1 - E) / ep_sin
c = _E * p_cos**2
c = _E * p_cos ** 2
c2 = c * c
d = x / (n * K0)
@ -152,7 +153,7 @@ def to_latlon(easting, northing, zone_number, zone_letter):
latitude = (p_rad - (p_tan / r) *
(d2 / 2 -
d4 / 24 * (5 + 3 * p_tan2 + 10 * c - 4 * c2 - 9 * E_P2)) +
d6 / 720 * (61 + 90 * p_tan2 + 298 * c + 45 * p_tan4 - 252 * E_P2 - 3 * c2))
d6 / 720 * (61 + 90 * p_tan2 + 298 * c + 45 * p_tan4 - 252 * E_P2 - 3 * c2))
longitude = (d -
d3 / 6 * (1 + 2 * p_tan2 + c) +
@ -184,8 +185,8 @@ def from_latlon(latitude, longitude):
zone_letter = latitude_to_zone_letter(latitude)
n = R / math.sqrt(1 - E * lat_sin**2)
c = E_P2 * lat_cos**2
n = R / math.sqrt(1 - E * lat_sin ** 2)
c = E_P2 * lat_cos ** 2
a = lat_cos * (lon_rad - central_lon_rad)
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
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)))
if latitude < 0:
@ -244,16 +245,15 @@ def zone_number_to_central_longitude(zone_number):
def haversine(lon1, lat1, lon2, lat2):
"""
Calculate the great circle distance between two points
Calculate the great circle distance between two points
on the earth (specified in decimal degrees)
"""
# convert decimal degrees to radians
# convert decimal degrees to radians
lon1, lat1, lon2, lat2 = map(math.radians, [lon1, lat1, lon2, lat2])
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
c = 2 * math.asin(math.sqrt(a))
# haversine formula
dlon = lon2 - lon1
dlat = lat2 - lat1
a = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2
c = 2 * math.asin(math.sqrt(a))
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,80 +1,114 @@
#
# CORE
# Copyright (c)2010-2013 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>
#
'''
nodes.py: definition of an LxcNode and CoreNode classes, and other node classes
that inherit from the CoreNode, implementing specific node types.
'''
"""
Definition of LxcNode, CoreNode, and other node classes that inherit from the CoreNode,
implementing specific node types.
"""
from vnode import *
from vnet import *
from core.misc.ipaddr import *
from core.api import coreapi
import socket
import subprocess
import threading
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 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):
"""
Control network functionality.
"""
policy = "ACCEPT"
CTRLIF_IDX_BASE = 99 # base control interface index
DEFAULT_PREFIX_LIST = ["172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24"]
def __init__(self, session, objid = "ctrlnet", name = None,
verbose = False, prefix = None,
hostid = None, start = True, assign_address = True,
updown_script = None, serverintf = None):
self.prefix = IPv4Prefix(prefix)
# base control interface index
CTRLIF_IDX_BASE = 99
DEFAULT_PREFIX_LIST = [
"172.16.0.0/24 172.16.1.0/24 172.16.2.0/24 172.16.3.0/24 172.16.4.0/24",
"172.17.0.0/24 172.17.1.0/24 172.17.2.0/24 172.17.3.0/24 172.17.4.0/24",
"172.18.0.0/24 172.18.1.0/24 172.18.2.0/24 172.18.3.0/24 172.18.4.0/24",
"172.19.0.0/24 172.19.1.0/24 172.19.2.0/24 172.19.3.0/24 172.19.4.0/24"
]
def __init__(self, session, objid="ctrlnet", name=None, prefix=None,
hostid=None, start=True, assign_address=True,
updown_script=None, serverintf=None):
"""
Creates a CtrlNet instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node namee
:param prefix: control network ipv4 prefix
:param hostid: host id
:param bool start: start flag
:param str assign_address: assigned address
:param str updown_script: updown script
:param serverintf: server interface
:return:
"""
self.prefix = ipaddress.Ipv4Prefix(prefix)
self.hostid = hostid
self.assign_address = assign_address
self.updown_script = updown_script
self.serverintf = serverintf
LxBrNet.__init__(self, session, objid = objid, name = name,
verbose = verbose, start = start)
LxBrNet.__init__(self, session, objid=objid, name=name, start=start)
def startup(self):
"""
Startup functionality for the control network.
:return: nothing
"""
if self.detectoldbridge():
return
LxBrNet.startup(self)
if self.hostid:
addr = self.prefix.addr(self.hostid)
else:
addr = self.prefix.maxaddr()
addr = self.prefix.max_addr()
msg = "Added control network bridge: %s %s" % \
(self.brname, self.prefix)
(self.brname, self.prefix)
addrlist = ["%s/%s" % (addr, self.prefix.prefixlen)]
if self.assign_address:
self.addrconfig(addrlist = addrlist)
self.addrconfig(addrlist=addrlist)
msg += " address %s" % addr
self.session.info(msg)
logger.info(msg)
if self.updown_script is not None:
self.info("interface %s updown script '%s startup' called" % \
(self.brname, self.updown_script))
check_call([self.updown_script, self.brname, "startup"])
logger.info("interface %s updown script '%s startup' called" % \
(self.brname, self.updown_script))
subprocess.check_call([self.updown_script, self.brname, "startup"])
if self.serverintf is not None:
try:
check_call([BRCTL_BIN, "addif", self.brname, self.serverintf])
check_call([IP_BIN, "link", "set", self.serverintf, "up"])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, self.brname,
"Error joining server interface %s to controlnet bridge %s: %s" % \
(self.serverintf, self.brname, e))
subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, self.serverintf])
subprocess.check_call([constants.IP_BIN, "link", "set", self.serverintf, "up"])
except subprocess.CalledProcessError:
logger.exception("Error joining server interface %s to controlnet bridge %s",
self.serverintf, self.brname)
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
'''
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:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, None,
"Unable to retrieve list of installed bridges")
logger.error("Unable to retrieve list of installed bridges")
lines = retstr.split('\n')
for line in lines[1:]:
cols = line.split('\t')
@ -82,248 +116,346 @@ class CtrlNet(LxBrNet):
flds = cols[0].split('.')
if len(flds) == 3:
if flds[0] == 'b' and flds[1] == self.objid:
self.session.exception(coreapi.CORE_EXCP_LEVEL_FATAL, "CtrlNet.startup()", None,
"Error: An active control net bridge (%s) found. "\
"An older session might still be running. " \
"Stop all sessions and, if needed, delete %s to continue." % \
(oldbr, oldbr))
logger.error(
"Error: An active control net bridge (%s) found. " \
"An older session might still be running. " \
"Stop all sessions and, if needed, delete %s to continue." % \
(oldbr, oldbr)
)
return True
'''
"""
# 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:
check_call([BRCTL_BIN, 'delbr', oldbr])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, oldbr,
"Error deleting old bridge %s" % oldbr)
self.info("Deleted %s" % oldbr)
'''
except subprocess.CalledProcessError as e:
logger.exception("Error deleting old bridge %s", oldbr, e)
logger.info("Deleted %s", oldbr)
"""
return False
def shutdown(self):
"""
Control network shutdown.
:return: nothing
"""
if self.serverintf is not None:
try:
check_call([BRCTL_BIN, "delif", self.brname, self.serverintf])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
"Error deleting server interface %s to controlnet bridge %s: %s" % \
(self.serverintf, self.brname, e))
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, self.serverintf])
except subprocess.CalledProcessError:
logger.exception("Error deleting server interface %s to controlnet bridge %s",
self.serverintf, self.brname)
if self.updown_script is not None:
self.info("interface %s updown script '%s shutdown' called" % \
(self.brname, self.updown_script))
check_call([self.updown_script, self.brname, "shutdown"])
logger.info("interface %s updown script '%s shutdown' called" % (self.brname, self.updown_script))
subprocess.check_call([self.updown_script, self.brname, "shutdown"])
LxBrNet.shutdown(self)
def tolinkmsgs(self, flags):
''' Do not include CtrlNet in link messages describing this session.
'''
def all_link_data(self, flags):
"""
Do not include CtrlNet in link messages describing this session.
:return: nothing
"""
return []
class CoreNode(LxcNode):
apitype = coreapi.CORE_NODE_DEF
"""
Basic core node class for nodes to extend.
"""
apitype = NodeTypes.DEFAULT.value
class PtpNet(LxBrNet):
"""
Peer to peer network node.
"""
policy = "ACCEPT"
def attach(self, netif):
if len(self._netif) > 1:
raise ValueError, \
"Point-to-point links support at most 2 network interfaces"
"""
Attach a network interface, but limit attachment to two 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)
def tonodemsg(self, flags):
''' Do not generate a Node Message for point-to-point links. They are
built using a link message instead.
'''
def data(self, message_type):
"""
Do not generate a Node Message for point-to-point links. They are
built using a link message instead.
:return: nothing
"""
pass
def tolinkmsgs(self, flags):
''' Build CORE API TLVs for a point-to-point link. One Link message
describes this network.
'''
tlvdata = ""
def all_link_data(self, flags):
"""
Build CORE API TLVs for a point-to-point link. One Link message
describes this network.
:return: all link data
:rtype: list[LinkData]
"""
all_links = []
if len(self._netif) != 2:
return tlvdata
(if1, if2) = self._netif.items()
return all_links
if1, if2 = self._netif.items()
if1 = if1[1]
if2 = if2[1]
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
if1.node.objid)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
if2.node.objid)
uni = False
unidirectional = 0
if if1.getparams() != if2.getparams():
uni = True
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)
unidirectional = 1
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \
if1.node.getifindex(if1))
if if1.hwaddr:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1MAC,
if1.hwaddr)
for addr in if1.addrlist:
(ip, sep, mask) = addr.partition('/')
interface1_ip4 = None
interface1_ip4_mask = None
interface1_ip6 = None
interface1_ip6_mask = None
for address in if1.addrlist:
ip, sep, mask = address.partition('/')
mask = int(mask)
if isIPv4Address(ip):
if ipaddress.is_ipv4_address(ip):
family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP4
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP4MASK
ipl = socket.inet_pton(family, ip)
interface1_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface1_ip4_mask = mask
else:
family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF1IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF1IP6MASK
ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip,
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
ipl = socket.inet_pton(family, ip)
interface1_ip6 = ipaddress.IpAddress(af=family, address=ipl)
interface1_ip6_mask = mask
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \
if2.node.getifindex(if2))
if if2.hwaddr:
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2MAC,
if2.hwaddr)
for addr in if2.addrlist:
(ip, sep, mask) = addr.partition('/')
interface2_ip4 = None
interface2_ip4_mask = None
interface2_ip6 = None
interface2_ip6_mask = None
for address in if2.addrlist:
ip, sep, mask = address.partition('/')
mask = int(mask)
if isIPv4Address(ip):
if ipaddress.is_ipv4_address(ip):
family = AF_INET
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP4
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP4MASK
ipl = socket.inet_pton(family, ip)
interface2_ip4 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip4_mask = mask
else:
family = AF_INET6
tlvtypeip = coreapi.CORE_TLV_LINK_IF2IP6
tlvtypemask = coreapi.CORE_TLV_LINK_IF2IP6MASK
ipl = socket.inet_pton(family, ip)
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypeip,
IPAddr(af=family, addr=ipl))
tlvdata += coreapi.CoreLinkTlv.pack(tlvtypemask, mask)
msg = coreapi.CoreLinkMessage.pack(flags, tlvdata)
if not uni:
return [msg,]
ipl = socket.inet_pton(family, ip)
interface2_ip6 = ipaddress.IpAddress(af=family, address=ipl)
interface2_ip6_mask = mask
# TODO: not currently used
# loss=netif.getparam('loss')
link_data = LinkData(
message_type=flags,
node1_id=if1.node.objid,
node2_id=if2.node.objid,
link_type=self.linktype,
unidirectional=unidirectional,
delay=if1.getparam("delay"),
bandwidth=if1.getparam("bw"),
dup=if1.getparam("duplicate"),
jitter=if1.getparam("jitter"),
interface1_id=if1.node.getifindex(if1),
interface1_mac=if1.hwaddr,
interface1_ip4=interface1_ip4,
interface1_ip4_mask=interface1_ip4_mask,
interface1_ip6=interface1_ip6,
interface1_ip6_mask=interface1_ip6_mask,
interface2_id=if2.node.getifindex(if2),
interface2_mac=if2.hwaddr,
interface2_ip4=interface2_ip4,
interface2_ip4_mask=interface2_ip4_mask,
interface2_ip6=interface2_ip6,
interface2_ip6_mask=interface2_ip6_mask,
)
all_links.append(link_data)
# build a 2nd link message for the upstream link parameters
# (swap if1 and if2)
tlvdata = ""
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N1NUMBER,
if2.node.objid)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_N2NUMBER,
if1.node.objid)
tlvdata += self.netifparamstolink(if2)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_UNI, 1)
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF1NUM, \
if2.node.getifindex(if2))
tlvdata += coreapi.CoreLinkTlv.pack(coreapi.CORE_TLV_LINK_IF2NUM, \
if1.node.getifindex(if1))
msg2 = coreapi.CoreLinkMessage.pack(0, tlvdata)
return [msg, msg2]
if unidirectional:
link_data = LinkData(
message_type=0,
node1_id=if2.node.objid,
node2_id=if1.node.objid,
delay=if1.getparam("delay"),
bandwidth=if1.getparam("bw"),
dup=if1.getparam("duplicate"),
jitter=if1.getparam("jitter"),
unidirectional=1,
interface1_id=if2.node.getifindex(if2),
interface2_id=if1.node.getifindex(if1)
)
all_links.append(link_data)
return all_links
class SwitchNode(LxBrNet):
apitype = coreapi.CORE_NODE_SWITCH
"""
Provides switch functionality within a core node.
"""
apitype = NodeTypes.SWITCH.value
policy = "ACCEPT"
type = "lanswitch"
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"
type = "hub"
def __init__(self, session, objid = None, name = None, verbose = False,
start = True):
''' the Hub node forwards packets to all bridge ports by turning off
the MAC address learning
'''
LxBrNet.__init__(self, session, objid, name, verbose, start)
def __init__(self, session, objid=None, name=None, start=True):
"""
Creates a HubNode instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param str name: node namee
:param bool start: start flag
"""
LxBrNet.__init__(self, session, objid, name, start)
if start:
check_call([BRCTL_BIN, "setageing", self.brname, "0"])
subprocess.check_call([constants.BRCTL_BIN, "setageing", self.brname, "0"])
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"
type = "wlan"
def __init__(self, session, objid = None, name = None, verbose = False,
start = True, policy = None):
LxBrNet.__init__(self, session, objid, name, verbose, start, policy)
def __init__(self, session, objid=None, name=None, start=True, policy=None):
"""
Create a WlanNode instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param 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
self.model = None
# mobility model such as scripted
self.mobility = None
def attach(self, netif):
"""
Attach a network interface.
:param core.coreobj.PyCoreNetIf netif: network interface
:return: nothing
"""
LxBrNet.attach(self, netif)
if self.model:
netif.poshook = self.model._positioncallback
netif.poshook = self.model.position_callback
if netif.node is None:
return
(x,y,z) = netif.node.position.get()
x, y, z = netif.node.position.get()
# invokes any netif.poshook
netif.setposition(x, y, z)
#self.model.setlinkparams()
# self.model.setlinkparams()
def setmodel(self, model, config):
''' Mobility and wireless model.
'''
if (self.verbose):
self.info("adding model %s" % model._name)
if model._type == coreapi.CORE_TLV_REG_WIRELESS:
self.model = model(session=self.session, objid=self.objid,
verbose=self.verbose, values=config)
if self.model._positioncallback:
"""
Sets the mobility and wireless model.
:param core.mobility.WirelessModel.cls model: wireless model to set to
:param config: model configuration
:return: nothing
"""
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():
netif.poshook = self.model._positioncallback
netif.poshook = self.model.position_callback
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)
self.model.setlinkparams()
elif model._type == coreapi.CORE_TLV_REG_MOBILITY:
self.mobility = model(session=self.session, objid=self.objid,
verbose=self.verbose, values=config)
elif model.config_type == RegisterTlvs.MOBILITY.value:
self.mobility = model(session=self.session, object_id=self.objid, values=config)
def updatemodel(self, model_name, values):
''' Allow for model updates during runtime (similar to setmodel().)
'''
if (self.verbose):
self.info("updating model %s" % model_name)
if self.model is None or self.model._name != model_name:
"""
Allow for model updates during runtime (similar to setmodel().)
:param model_name: model name to update
: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
model = self.model
if model._type == coreapi.CORE_TLV_REG_WIRELESS:
if model.config_type == RegisterTlvs.WIRELESS.value:
if not model.updateconfig(values):
return
if self.model._positioncallback:
if self.model.position_callback:
for netif in self.netifs():
netif.poshook = self.model._positioncallback
netif.poshook = self.model.position_callback
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)
self.model.setlinkparams()
def tolinkmsgs(self, flags):
msgs = LxBrNet.tolinkmsgs(self, flags)
def all_link_data(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:
msgs += self.model.tolinkmsgs(flags)
return msgs
all_links.extend(self.model.all_link_data(flags))
return all_links
class RJ45Node(PyCoreNode, PyCoreNetIf):
''' RJ45Node is a physical interface on the host linked to the emulated
network.
'''
apitype = coreapi.CORE_NODE_RJ45
"""
RJ45Node is a physical interface on the host linked to the emulated
network.
"""
apitype = NodeTypes.RJ45.value
type = "rj45"
def __init__(self, session, objid = None, name = None, mtu = 1500,
verbose = False, start = True):
PyCoreNode.__init__(self, session, objid, name, verbose=verbose,
start=start)
def __init__(self, session, objid=None, name=None, mtu=1500, start=True):
"""
Create an RJ45Node instance.
:param core.session.Session session: core session instance
:param int objid: node id
:param 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
PyCoreNetIf.__init__(self, node=self, name=name, mtu = mtu)
PyCoreNetIf.__init__(self, node=self, name=name, mtu=mtu)
self.up = False
self.lock = threading.RLock()
self.ifindex = None
@ -334,65 +466,101 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
self.startup()
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()
self.savestate()
try:
check_call([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
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
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):
''' Bring the interface down. Remove any addresses and queuing
disciplines.
'''
"""
Bring the interface down. Remove any addresses and queuing
disciplines.
:return: nothing
"""
if not self.up:
return
check_call([IP_BIN, "link", "set", self.localname, "down"])
check_call([IP_BIN, "addr", "flush", "dev", self.localname])
mutecall([TC_BIN, "qdisc", "del", "dev", self.localname, "root"])
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "down"])
subprocess.check_call([constants.IP_BIN, "addr", "flush", "dev", self.localname])
utils.mutecall([constants.TC_BIN, "qdisc", "del", "dev", self.localname, "root"])
self.up = False
self.restorestate()
# TODO: issue in that both classes inherited from provide the same method with different signatures
def attachnet(self, net):
"""
Attach a network.
:param core.coreobj.PyCoreNet net: network to attach
:return: nothing
"""
PyCoreNetIf.attachnet(self, net)
def detachnet(self):
"""
Detach a network.
:return: nothing
"""
PyCoreNetIf.detachnet(self)
def newnetif(self, net = None, addrlist = [], hwaddr = None,
ifindex = None, ifname = None):
''' This is called when linking with another node. Since this node
represents an interface, we do not create another object here,
but attach ourselves to the given network.
'''
self.lock.acquire()
try:
# TODO: parameters are not used
def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None):
"""
This is called when linking with another node. Since this node
represents an interface, we do not create another object here,
but attach ourselves to the given network.
:param core.coreobj.PyCoreNet net: new network instance
:param list[str] addrlist: address list
:param str hwaddr: hardware address
:param int ifindex: interface index
:param str ifname: interface name
:return:
"""
with self.lock:
if ifindex is None:
ifindex = 0
if self.net is not None:
raise ValueError, \
"RJ45 nodes support at most 1 network interface"
raise ValueError("RJ45 nodes support at most 1 network interface")
self._netif[ifindex] = self
self.node = self # PyCoreNetIf.node is self
# PyCoreNetIf.node is self
self.node = self
self.ifindex = ifindex
if net is not None:
self.attachnet(net)
for addr in maketuple(addrlist):
self.addaddr(addr)
if addrlist:
for addr in utils.maketuple(addrlist):
self.addaddr(addr)
return ifindex
finally:
self.lock.release()
def delnetif(self, ifindex):
"""
Delete a network interface.
:param int ifindex: interface index to delete
:return: nothing
"""
if ifindex is None:
ifindex = 0
if ifindex not in self._netif:
raise ValueError, "ifindex %s does not exist" % ifindex
self._netif.pop(ifindex)
if ifindex == self.ifindex:
self.shutdown()
@ -400,46 +568,78 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
raise ValueError, "ifindex %s does not exist" % ifindex
def netif(self, ifindex, net=None):
''' This object is considered the network interface, so we only
return self here. This keeps the RJ45Node compatible with
real nodes.
'''
"""
This object is considered the network interface, so we only
return self here. This keeps the RJ45Node compatible with
real nodes.
:param int ifindex: interface index to retrieve
:param net: network to retrieve
:return: a network interface
:rtype: core.coreobj.PyCoreNetIf
"""
if net is not None and net == self.net:
return self
if ifindex is None:
ifindex = 0
if ifindex == self.ifindex:
return self
return None
def getifindex(self, netif):
"""
Retrieve network interface index.
:param core.coreobj.PyCoreNetIf netif: network interface to retrieve index for
:return: interface index, None otherwise
:rtype: int
"""
if netif != self:
return None
return self.ifindex
def addaddr(self, addr):
"""
Add address to to network interface.
:param str addr: address to add
:return: nothing
"""
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)
def deladdr(self, addr):
"""
Delete address from network interface.
:param str addr: address to delete
:return: nothing
"""
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)
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
'''
:return: nothing
"""
self.old_up = False
self.old_addrs = []
cmd = [IP_BIN, "addr", "show", "dev", self.localname]
cmd = [constants.IP_BIN, "addr", "show", "dev", self.localname]
try:
tmp = subprocess.Popen(cmd, stdout = subprocess.PIPE)
tmp = subprocess.Popen(cmd, stdout=subprocess.PIPE)
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():
self.warn("Command failed: %s" % cmd)
logger.warn("Command failed: %s", cmd)
return
lines = tmp.stdout.read()
tmp.stdout.close()
@ -459,31 +659,34 @@ class RJ45Node(PyCoreNode, PyCoreNetIf):
self.old_addrs.append((items[1], None))
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:
if addr[1] is None:
check_call([IP_BIN, "addr", "add", addr[0], "dev",
self.localname])
subprocess.check_call([constants.IP_BIN, "addr", "add", addr[0], "dev", self.localname])
else:
check_call([IP_BIN, "addr", "add", addr[0], "brd", addr[1],
"dev", self.localname])
subprocess.check_call([constants.IP_BIN, "addr", "add", addr[0], "brd", addr[1], "dev", self.localname])
if self.old_up:
check_call([IP_BIN, "link", "set", self.localname, "up"])
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
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)
# invoke any poshook
PyCoreNetIf.setposition(self, x, y, z)
class TunnelNode(GreTapBridge):
apitype = coreapi.CORE_NODE_TUNNEL
"""
Provides tunnel functionality in a core node.
"""
apitype = NodeTypes.TUNNEL.value
policy = "ACCEPT"
type = "tunnel"

View file

@ -1,58 +1,91 @@
#
# CORE
# 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.
'''
"""
virtual ethernet classes that implement the interfaces available under Linux.
"""
import os, signal, shutil, sys, subprocess, vnodeclient, threading, string
import random, time
from core.api import coreapi
from core.misc.utils import *
from core.constants import *
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position
from core.emane.nodes import EmaneNode
import subprocess
import time
from core import constants
from core.coreobj import PyCoreNetIf
from core.enumerations import NodeTypes
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):
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
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu)
PyCoreNetIf.__init__(self, node=node, name=name, mtu=mtu)
self.localname = localname
self.up = False
if start:
self.startup()
def startup(self):
check_call([IP_BIN, "link", "add", "name", self.localname,
"type", "veth", "peer", "name", self.name])
check_call([IP_BIN, "link", "set", self.localname, "up"])
"""
Interface startup logic.
:return: nothing
"""
subprocess.check_call([constants.IP_BIN, "link", "add", "name", self.localname,
"type", "veth", "peer", "name", self.name])
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
self.up = True
def shutdown(self):
"""
Interface shutdown logic.
:return: nothing
"""
if not self.up:
return
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:
mutedetach([IP_BIN, "link", "delete", self.localname])
utils.mutedetach([constants.IP_BIN, "link", "delete", self.localname])
self.up = False
class TunTap(PyCoreNetIf):
''' 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)
"""
TUN/TAP virtual device in TAP mode
"""
# 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.up = False
self.transport_type = "virtual"
@ -60,26 +93,41 @@ class TunTap(PyCoreNetIf):
self.startup()
def startup(self):
"""
Startup logic for a tunnel tap.
:return: nothing
"""
# TODO: more sophisticated TAP creation here
# 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)
#check_call(["tunctl", "-t", self.name])
# check_call(["tunctl", "-t", self.name])
# self.install()
self.up = True
def shutdown(self):
"""
Shutdown functionality for a tunnel tap.
:return: nothing
"""
if not self.up:
return
self.node.cmd([IP_BIN, "-6", "addr", "flush", "dev", self.name])
#if self.name:
self.node.cmd([constants.IP_BIN, "-6", "addr", "flush", "dev", self.name])
# if self.name:
# mutedetach(["tunctl", "-d", self.localname])
self.up = False
def waitfor(self, func, attempts = 10, maxretrydelay = 0.25):
'''\
Wait for func() to return zero with exponential backoff
'''
def waitfor(self, func, attempts=10, maxretrydelay=0.25):
"""
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
for i in xrange(1, attempts + 1):
r = func()
@ -88,98 +136,130 @@ class TunTap(PyCoreNetIf):
msg = 'attempt %s failed with nonzero exit status %s' % (i, r)
if i < attempts + 1:
msg += ', retrying...'
self.node.info(msg)
logger.info(msg)
time.sleep(delay)
delay = delay + delay
if delay > maxretrydelay:
delay = maxretrydelay
else:
msg += ', giving up'
self.node.info(msg)
raise RuntimeError, 'command failed after %s attempts' % attempts
logger.info(msg)
raise RuntimeError('command failed after %s attempts' % attempts)
def waitfordevicelocal(self):
'''\
"""
Check for presence of a local device - tap device may not
appear right away waits
'''
:return: wait for device local response
:rtype: int
"""
def localdevexists():
cmd = (IP_BIN, 'link', 'show', self.localname)
return mutecall(cmd)
cmd = (constants.IP_BIN, 'link', 'show', self.localname)
return utils.mutecall(cmd)
self.waitfor(localdevexists)
def waitfordevicenode(self):
'''\
Check for presence of a node device - tap device may not
appear right away waits
'''
"""
Check for presence of a node device - tap device may not appear right away waits.
:return: nothing
"""
def nodedevexists():
cmd = (IP_BIN, 'link', 'show', self.name)
cmd = (constants.IP_BIN, 'link', 'show', self.name)
return self.node.cmd(cmd)
count = 0
while True:
try:
self.waitfor(nodedevexists)
break
except RuntimeError:
except RuntimeError as e:
# check if this is an EMANE interface; if so, continue
# waiting if EMANE is still running
if count < 5 and isinstance(self.net, EmaneNode) and \
self.node.session.emane.emanerunning(self.node):
# TODO: remove emane code
if count < 5 and nodeutils.is_node(self.net, NodeTypes.EMANE) and \
self.node.session.emane.emanerunning(self.node):
count += 1
else:
raise
raise e
def install(self):
''' Install this TAP into its namespace. This is not done from the
startup() method but called at a later time when a userspace
program (running on the host) has had a chance to open the socket
end of the TAP.
'''
"""
Install this TAP into its namespace. This is not done from the
startup() method but called at a later time when a userspace
program (running on the host) has had a chance to open the socket
end of the TAP.
:return: nothing
"""
self.waitfordevicelocal()
netns = str(self.node.pid)
try:
check_call([IP_BIN, "link", "set", self.localname, "netns", netns])
except Exception, e:
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "netns", netns])
except subprocess.CalledProcessError:
msg = "error installing TAP interface %s, command:" % self.localname
msg += "ip link set %s netns %s" % (self.localname, netns)
self.node.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.localname, msg)
self.node.warn(msg)
logger.exception(msg)
return
self.node.cmd([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.localname, "name", self.name])
self.node.cmd([constants.IP_BIN, "link", "set", self.name, "up"])
def setaddrs(self):
''' Set interface addresses based on self.addrlist.
'''
"""
Set interface addresses based on self.addrlist.
:return: nothing
"""
self.waitfordevicenode()
for addr in self.addrlist:
self.node.cmd([IP_BIN, "addr", "add", str(addr),
"dev", self.name])
self.node.cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.name])
class GreTap(PyCoreNetIf):
''' GRE TAP device for tunneling between emulation servers.
Uses the "gretap" tunnel device type from Linux which is a GRE device
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,
key = None, start = True):
PyCoreNetIf.__init__(self, node = node, name = name, mtu = mtu)
"""
GRE TAP device for tunneling between emulation servers.
Uses the "gretap" tunnel device type from Linux which is a GRE device
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,
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
if objid is None:
# from PyCoreObj
objid = (((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff)
objid = ((id(self) >> 16) ^ (id(self) & 0xffff)) & 0xffff
self.objid = objid
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
# interface name on the local host machine
self.localname = "gt.%s.%s" % (self.objid, sessionid)
self.transport_type = "raw"
if not start:
self.up = False
return
if remoteip is None:
raise ValueError, "missing remote IP required for GRE TAP device"
cmd = ("ip", "link", "add", self.localname, "type", "gretap",
@ -190,21 +270,39 @@ class GreTap(PyCoreNetIf):
cmd += ("ttl", str(ttl))
if key:
cmd += ("key", str(key))
check_call(cmd)
subprocess.check_call(cmd)
cmd = ("ip", "link", "set", self.localname, "up")
check_call(cmd)
subprocess.check_call(cmd)
self.up = True
def shutdown(self):
"""
Shutdown logic for a GreTap.
:return: nothing
"""
if self.localname:
cmd = ("ip", "link", "set", self.localname, "down")
check_call(cmd)
subprocess.check_call(cmd)
cmd = ("ip", "link", "del", self.localname)
check_call(cmd)
subprocess.check_call(cmd)
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
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 []

View file

@ -1,42 +1,48 @@
#
# CORE
# 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
"""
PyCoreNet and LxBrNet classes that implement virtual networks using
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.misc.utils import *
from core.constants import *
from core.coreobj import PyCoreNet, PyCoreObj
from core.netns.vif import VEth, GreTap
from core import constants
from core.coreobj import PyCoreNet
from core.misc import log
from core.misc import utils
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()
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
many WLAN link updates.
'''
"""
# update rate is every 300ms
rate = 0.3
# ebtables
atomic_file = "/tmp/pycore.ebtables.atomic"
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.
'''
"""
self.doupdateloop = False
self.updatethread = None
# this lock protects cmds and updates lists
@ -48,28 +54,35 @@ class EbtablesQueue(object):
# timestamps of last WLAN update; this keeps track of WLANs that are
# using this queue
self.last_update_time = {}
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.last_update_time[wlan] = time.time()
self.updatelock.release()
if self.doupdateloop:
return
self.doupdateloop = True
self.updatethread = threading.Thread(target = self.updateloop)
self.updatethread = threading.Thread(target=self.updateloop)
self.updatethread.daemon = True
self.updatethread.start()
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()
try:
del self.last_update_time[wlan]
except KeyError:
pass
logger.exception("error deleting last update time for wlan: %s", wlan)
self.updatelock.release()
if len(self.last_update_time) > 0:
return
@ -77,152 +90,186 @@ class EbtablesQueue(object):
if self.updatethread:
self.updatethread.join()
self.updatethread = None
def ebatomiccmd(self, cmd):
''' Helper for building ebtables atomic file command list.
'''
r = [EBTABLES_BIN, "--atomic-file", self.atomic_file]
"""
Helper for building ebtables atomic file command list.
:param list[str] cmd: ebtable command
:return: ebtable atomic command
:rtype: list[str]
"""
r = [constants.EBTABLES_BIN, "--atomic-file", self.atomic_file]
if cmd:
r.extend(cmd)
return r
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:
elapsed = time.time() - self.last_update_time[wlan]
except KeyError:
self.last_update_time[wlan] = time.time()
elapsed = 0.0
return elapsed
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.updates.remove(wlan)
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
should use ebtables at any given time, or results can be unpredictable.
'''
:return: nothing
"""
while self.doupdateloop:
self.updatelock.acquire()
for wlan in self.updates:
'''
Check if wlan is from a previously closed session. Because of the
rate limiting scheme employed here, this may happen if a new session
"""
Check if wlan is from a previously closed session. Because of the
rate limiting scheme employed here, this may happen if a new session
is started soon after closing a previous session.
'''
"""
try:
wlan.session
except:
# Just mark as updated to remove from self.updates.
# Just mark as updated to remove from self.updates.
self.updated(wlan)
continue
if self.lastupdate(wlan) > self.rate:
self.buildcmds(wlan)
#print "ebtables commit %d rules" % len(self.cmds)
# print "ebtables commit %d rules" % len(self.cmds)
self.ebcommit(wlan)
self.updated(wlan)
self.updatelock.release()
time.sleep(self.rate)
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
cmd = self.ebatomiccmd(["--atomic-save",])
cmd = self.ebatomiccmd(["--atomic-save", ])
try:
check_call(cmd)
except Exception, e:
self.eberror(wlan, "atomic-save (%s)" % cmd, e)
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
logger.exception("atomic-save (%s)", cmd)
# no atomic file, exit
return
# modify the table file using queued ebtables commands
for c in self.cmds:
cmd = self.ebatomiccmd(c)
try:
check_call(cmd)
except Exception, e:
self.eberror(wlan, "cmd=%s" % cmd, e)
pass
subprocess.check_call(cmd)
except subprocess.CalledProcessError:
logger.exception("cmd=%s", cmd)
self.cmds = []
# commit the table file to the kernel
cmd = self.ebatomiccmd(["--atomic-commit",])
cmd = self.ebatomiccmd(["--atomic-commit", ])
try:
check_call(cmd)
subprocess.check_call(cmd)
os.unlink(self.atomic_file)
except Exception, e:
self.eberror(wlan, "atomic-commit (%s)" % cmd, e)
except OSError:
logger.exception("atomic-commit (%s)", cmd)
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.
'''
:return: nothing
"""
self.updatelock.acquire()
if wlan not in self.updates:
self.updates.append(wlan)
self.updatelock.release()
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()
# flush the chain
self.cmds.extend([["-F", wlan.brname],])
self.cmds.extend([["-F", wlan.brname], ])
# rebuild the chain
for (netif1, v) in wlan._linked.items():
for (netif2, linked) in v.items():
for netif1, v in wlan._linked.items():
for netif2, linked in v.items():
if wlan.policy == "DROP" and linked:
self.cmds.extend([["-A", wlan.brname, "-i", netif1.localname,
"-o", netif2.localname, "-j", "ACCEPT"],
["-A", wlan.brname, "-o", netif1.localname,
"-i", netif2.localname, "-j", "ACCEPT"]])
"-o", netif2.localname, "-j", "ACCEPT"],
["-A", wlan.brname, "-o", netif1.localname,
"-i", netif2.localname, "-j", "ACCEPT"]])
elif wlan.policy == "ACCEPT" and not linked:
self.cmds.extend([["-A", wlan.brname, "-i", netif1.localname,
"-o", netif2.localname, "-j", "DROP"],
["-A", wlan.brname, "-o", netif1.localname,
"-i", netif2.localname, "-j", "DROP"]])
"-o", netif2.localname, "-j", "DROP"],
["-A", wlan.brname, "-o", netif1.localname,
"-i", netif2.localname, "-j", "DROP"]])
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
# cannot have multiple threads invoking the ebtables commnd
ebq = EbtablesQueue()
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:
call(cmd)
finally:
ebtables_lock.release()
class LxBrNet(PyCoreNet):
"""
Provides linux bridge network functionlity for core nodes.
"""
policy = "DROP"
def __init__(self, session, objid = None, name = None, verbose = False,
start = True, policy = None):
PyCoreNet.__init__(self, session, objid, name, verbose, start)
def __init__(self, session, objid=None, name=None, start=True, policy=None):
"""
Creates a LxBrNet instance.
:param core.session.Session session: core session instance
:param int objid: object id
:param str name: object name
:param bool start: start flag
:param policy: network policy
"""
PyCoreNet.__init__(self, session, objid, name, start)
if name is None:
name = str(self.objid)
if policy is not None:
self.policy = policy
self.name = name
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
self.brname = "b.%s.%s" % (str(self.objid), sessionid)
self.up = False
if start:
@ -230,42 +277,50 @@ class LxBrNet(PyCoreNet):
ebq.startupdateloop(self)
def startup(self):
"""
Linux bridge starup logic.
:return: nothing
"""
try:
check_call([BRCTL_BIN, "addbr", self.brname])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL, self.brname,
"Error adding bridge: %s" % e)
subprocess.check_call([constants.BRCTL_BIN, "addbr", self.brname])
except subprocess.CalledProcessError:
logger.exception("Error adding bridge")
try:
# turn off spanning tree protocol and forwarding delay
check_call([BRCTL_BIN, "stp", self.brname, "off"])
check_call([BRCTL_BIN, "setfd", self.brname, "0"])
check_call([IP_BIN, "link", "set", self.brname, "up"])
subprocess.check_call([constants.BRCTL_BIN, "stp", self.brname, "off"])
subprocess.check_call([constants.BRCTL_BIN, "setfd", self.brname, "0"])
subprocess.check_call([constants.IP_BIN, "link", "set", self.brname, "up"])
# create a new ebtables chain for this bridge
ebtablescmds(check_call, [
[EBTABLES_BIN, "-N", self.brname, "-P", self.policy],
[EBTABLES_BIN, "-A", "FORWARD",
"--logical-in", self.brname, "-j", self.brname]])
ebtablescmds(subprocess.check_call, [
[constants.EBTABLES_BIN, "-N", self.brname, "-P", self.policy],
[constants.EBTABLES_BIN, "-A", "FORWARD", "--logical-in", self.brname, "-j", self.brname]
])
# turn off multicast snooping so mcast forwarding occurs w/o IGMP joins
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % \
self.brname
snoop = "/sys/devices/virtual/net/%s/bridge/multicast_snooping" % self.brname
if os.path.exists(snoop):
open(snoop, "w").write('0')
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_WARNING, self.brname,
"Error setting bridge parameters: %s" % e)
except subprocess.CalledProcessError:
logger.exception("Error setting bridge parameters")
self.up = True
def shutdown(self):
"""
Linux bridge shutdown logic.
:return: nothing
"""
if not self.up:
return
ebq.stopupdateloop(self)
mutecall([IP_BIN, "link", "set", self.brname, "down"])
mutecall([BRCTL_BIN, "delbr", self.brname])
ebtablescmds(mutecall, [
[EBTABLES_BIN, "-D", "FORWARD",
utils.mutecall([constants.IP_BIN, "link", "set", self.brname, "down"])
utils.mutecall([constants.BRCTL_BIN, "delbr", self.brname])
ebtablescmds(utils.mutecall, [
[constants.EBTABLES_BIN, "-D", "FORWARD",
"--logical-in", self.brname, "-j", self.brname],
[EBTABLES_BIN, "-X", self.brname]])
[constants.EBTABLES_BIN, "-X", self.brname]])
for netif in self.netifs():
# removes veth pairs used for bridge-to-bridge connections
netif.shutdown()
@ -275,34 +330,52 @@ class LxBrNet(PyCoreNet):
self.up = False
def attach(self, netif):
"""
Attach a network interface.
:param core.netns.vif.VEth netif: network interface to attach
:return: nothing
"""
if self.up:
try:
check_call([BRCTL_BIN, "addif", self.brname, netif.localname])
check_call([IP_BIN, "link", "set", netif.localname, "up"])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
"Error joining interface %s to bridge %s: %s" % \
(netif.localname, self.brname, e))
subprocess.check_call([constants.BRCTL_BIN, "addif", self.brname, netif.localname])
subprocess.check_call([constants.IP_BIN, "link", "set", netif.localname, "up"])
except subprocess.CalledProcessError:
logger.exception("Error joining interface %s to bridge %s", netif.localname, self.brname)
return
PyCoreNet.attach(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:
try:
check_call([BRCTL_BIN, "delif", self.brname, netif.localname])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
"Error removing interface %s from bridge %s: %s" % \
(netif.localname, self.brname, e))
subprocess.check_call([constants.BRCTL_BIN, "delif", self.brname, netif.localname])
except subprocess.CalledProcessError:
logger.exception("Error removing interface %s from bridge %s", netif.localname, self.brname)
return
PyCoreNet.detach(self, netif)
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
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:
raise ValueError, "inconsistency for netif %s" % netif2.name
raise ValueError("inconsistency for netif %s" % netif2.name)
try:
linked = self._linked[netif1][netif2]
except KeyError:
@ -311,14 +384,20 @@ class LxBrNet(PyCoreNet):
elif self.policy == "DROP":
linked = False
else:
raise Exception, "unknown policy: %s" % self.policy
raise Exception("unknown policy: %s" % self.policy)
self._linked[netif1][netif2] = linked
return linked
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.
'''
:param core.netns.vif.Veth netif1: interface one
:param core.netns.vif.Veth netif2: interface two
:return: nothing
"""
self._linked_lock.acquire()
if not self.linked(netif1, netif2):
self._linked_lock.release()
@ -328,9 +407,14 @@ class LxBrNet(PyCoreNet):
ebq.ebchange(self)
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.
'''
:param core.netns.vif.Veth netif1: interface one
:param core.netns.vif.Veth netif2: interface two
:return: nothing
"""
self._linked_lock.acquire()
if self.linked(netif1, netif2):
self._linked_lock.release()
@ -339,37 +423,45 @@ class LxBrNet(PyCoreNet):
self._linked_lock.release()
ebq.ebchange(self)
def linkconfig(self, netif, bw = None, delay = None,
loss = None, duplicate = None, jitter = None, netif2 = None,
devname = None):
''' Configure link parameters by applying tc queuing disciplines on the
interface.
'''
def linkconfig(self, netif, bw=None, delay=None, loss=None, duplicate=None,
jitter=None, netif2=None, devname=None):
"""
Configure link parameters by applying tc queuing disciplines on the interface.
: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:
devname = netif.localname
tc = [TC_BIN, "qdisc", "replace", "dev", devname]
tc = [constants.TC_BIN, "qdisc", "replace", "dev", devname]
parent = ["root"]
changed = False
if netif.setparam('bw', bw):
# from tc-tbf(8): minimum value for burst is rate / kernel_hz
if bw is not None:
burst = max(2 * netif.mtu, bw / 1000)
limit = 0xffff # max IP payload
# max IP payload
limit = 0xffff
tbf = ["tbf", "rate", str(bw),
"burst", str(burst), "limit", str(limit)]
if bw > 0:
if self.up:
if (self.verbose):
self.info("linkconfig: %s" % \
([tc + parent + ["handle", "1:"] + tbf],))
check_call(tc + parent + ["handle", "1:"] + tbf)
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "1:"] + tbf],))
subprocess.check_call(tc + parent + ["handle", "1:"] + tbf)
netif.setparam('has_tbf', True)
changed = True
elif netif.getparam('has_tbf') and bw <= 0:
tcd = [] + tc
tcd[2] = "delete"
if self.up:
check_call(tcd + parent)
subprocess.check_call(tcd + parent)
netif.setparam('has_tbf', False)
# removing the parent removes the child
netif.setparam('has_netem', False)
@ -395,7 +487,7 @@ class LxBrNet(PyCoreNet):
netem += ["delay", "0us", "%sus" % jitter, "25%"]
else:
netem += ["%sus" % jitter, "25%"]
if loss is not None:
netem += ["loss", "%s%%" % min(loss, 100)]
if duplicate is not None:
@ -406,47 +498,47 @@ class LxBrNet(PyCoreNet):
return
tc[2] = "delete"
if self.up:
if self.verbose:
self.info("linkconfig: %s" % \
([tc + parent + ["handle", "10:"]],))
check_call(tc + parent + ["handle", "10:"])
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"]],))
subprocess.check_call(tc + parent + ["handle", "10:"])
netif.setparam('has_netem', False)
elif len(netem) > 1:
if self.up:
if self.verbose:
self.info("linkconfig: %s" % \
([tc + parent + ["handle", "10:"] + netem],))
check_call(tc + parent + ["handle", "10:"] + netem)
logger.info("linkconfig: %s" % ([tc + parent + ["handle", "10:"] + netem],))
subprocess.check_call(tc + parent + ["handle", "10:"] + netem)
netif.setparam('has_netem', True)
def linknet(self, net):
''' Link this bridge with another by creating a veth pair and installing
each device into each bridge.
'''
sessionid = self.session.shortsessionid()
"""
Link this bridge with another by creating a veth pair and installing
each device into each bridge.
:param core.netns.vnet.LxBrNet net: network to link with
:return: created interface
:rtype: Veth
"""
sessionid = self.session.short_session_id()
try:
self_objid = '%x' % self.objid
self_objid = "%x" % self.objid
except TypeError:
self_objid = '%s' % self.objid
self_objid = "%s" % self.objid
try:
net_objid = '%x' % net.objid
net_objid = "%x" % net.objid
except TypeError:
net_objid = '%s' % net.objid
localname = 'veth%s.%s.%s' % (self_objid, net_objid, sessionid)
net_objid = "%s" % net.objid
localname = "veth%s.%s.%s" % (self_objid, net_objid, sessionid)
if len(localname) >= 16:
raise ValueError, "interface local name '%s' too long" % \
localname
name = 'veth%s.%s.%s' % (net_objid, self_objid, sessionid)
raise ValueError("interface local name %s too long" % localname)
name = "veth%s.%s.%s" % (net_objid, self_objid, sessionid)
if len(name) >= 16:
raise ValueError, "interface name '%s' too long" % name
netif = VEth(node = None, name = name, localname = localname,
mtu = 1500, net = self, start = self.up)
raise ValueError("interface name %s too long" % name)
netif = VEth(node=None, name=name, localname=localname,
mtu=1500, net=self, start=self.up)
self.attach(netif)
if net.up:
# this is similar to net.attach() but uses netif.name instead
# this is similar to net.attach() but uses netif.name instead
# of localname
check_call([BRCTL_BIN, "addif", net.brname, netif.name])
check_call([IP_BIN, "link", "set", netif.name, "up"])
subprocess.check_call([constants.BRCTL_BIN, "addif", net.brname, netif.name])
subprocess.check_call([constants.IP_BIN, "link", "set", netif.name, "up"])
i = net.newifindex()
net._netif[i] = netif
with net._linked_lock:
@ -454,38 +546,60 @@ class LxBrNet(PyCoreNet):
netif.net = self
netif.othernet = net
return netif
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()).
'''
: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():
if hasattr(netif, 'othernet') and netif.othernet == net:
if hasattr(netif, "othernet") and netif.othernet == net:
return netif
return None
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:
return
for addr in addrlist:
try:
check_call([IP_BIN, "addr", "add", str(addr), "dev", self.brname])
except Exception, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR, self.brname,
"Error adding IP address: %s" % e)
subprocess.check_call([constants.IP_BIN, "addr", "add", str(addr), "dev", self.brname])
except subprocess.CalledProcessError:
logger.exception("Error adding IP address")
class GreTapBridge(LxBrNet):
''' A network consisting of a bridge with a gretap device for tunneling to
another system.
'''
def __init__(self, session, remoteip = None, objid = None, name = None,
policy = "ACCEPT", localip = None, ttl = 255, key = None,
verbose = False, start = True):
LxBrNet.__init__(self, session = session, objid = objid,
name = name, verbose = verbose, policy = policy,
start = False)
"""
A network consisting of a bridge with a gretap device for tunneling to
another system.
"""
def __init__(self, session, remoteip=None, objid=None, name=None,
policy="ACCEPT", localip=None, ttl=255, key=None, start=True):
"""
Create a GreTapBridge instance.
: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
if self.grekey is None:
self.grekey = self.session.sessionid ^ self.objid
@ -497,47 +611,59 @@ class GreTapBridge(LxBrNet):
if remoteip is None:
self.gretap = None
else:
self.gretap = GreTap(node = self, name = None, session = session,
remoteip = remoteip, objid = None, localip = localip, ttl = ttl,
key = self.grekey)
self.gretap = GreTap(node=self, name=None, session=session, remoteip=remoteip,
objid=None, localip=localip, ttl=ttl, key=self.grekey)
if start:
self.startup()
def startup(self):
''' Creates a bridge and adds the gretap device to it.
'''
"""
Creates a bridge and adds the gretap device to it.
:return: nothing
"""
LxBrNet.startup(self)
if self.gretap:
self.attach(self.gretap)
def shutdown(self):
''' Detach the gretap device and remove the bridge.
'''
"""
Detach the gretap device and remove the bridge.
:return: nothing
"""
if self.gretap:
self.detach(self.gretap)
self.gretap.shutdown()
self.gretap = None
LxBrNet.shutdown(self)
def addrconfig(self, addrlist):
''' Set the remote tunnel endpoint. This is a one-time method for
creating the GreTap device, which requires the remoteip at startup.
The 1st address in the provided list is remoteip, 2nd optionally
specifies localip.
'''
"""
Set the remote tunnel endpoint. This is a one-time method for
creating the GreTap device, which requires the remoteip at startup.
The 1st address in the provided list is remoteip, 2nd optionally
specifies localip.
:param list addrlist: address list
:return: nothing
"""
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]
localip = None
if len(addrlist) > 1:
localip = addrlist[1].split('/')[0]
self.gretap = GreTap(session = self.session, remoteip = remoteip,
objid = None, name = None,
localip = localip, ttl = self.ttl, key = self.grekey)
self.gretap = GreTap(session=self.session, remoteip=remoteip, objid=None, name=None,
localip=localip, ttl=self.ttl, key=self.grekey)
self.attach(self.gretap)
def setkey(self, key):
''' Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig).
'''
"""
Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig).
:param key: gre key
:return: nothing
"""
self.grekey = key

View file

@ -1,35 +1,50 @@
#
# CORE
# 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.
'''
"""
PyCoreNode and LxcNode classes that implement the network namespac virtual node.
"""
import os, signal, sys, subprocess, vnodeclient, threading, string, shutil
import random, time
from core.api import coreapi
from core.misc.utils import *
from core.constants import *
from core.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf, Position
from core.netns.vif import VEth, TunTap
from core.emane.nodes import EmaneNode
import os
import random
import shutil
import signal
import string
import subprocess
import threading
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):
def __init__(self, session, objid = None, name = None, nodedir = None,
verbose = False, start = True):
PyCoreNode.__init__(self, session, objid, name, verbose=verbose,
start=start)
"""
Provides simple lxc functionality for core nodes.
"""
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.ctrlchnlname = \
os.path.abspath(os.path.join(self.session.sessiondir, self.name))
self.ctrlchnlname = os.path.abspath(os.path.join(self.session.session_dir, self.name))
self.vnodeclient = None
self.pid = None
self.up = False
@ -37,234 +52,411 @@ class SimpleLxcNode(PyCoreNode):
self._mounts = []
def alive(self):
"""
Check if the node is alive.
:return: True if node is alive, False otherwise
:rtype: bool
"""
try:
os.kill(self.pid, 0)
except OSError:
return False
return True
def startup(self):
''' Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set
the hostname.
'''
"""
Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set
the hostname.
:return: nothing
"""
if self.up:
raise Exception, "already up"
vnoded = ["%s/vnoded" % CORE_SBIN_DIR, "-v", "-c", self.ctrlchnlname,
raise Exception("already up")
vnoded = ["%s/vnoded" % constants.CORE_SBIN_DIR, "-v", "-c", self.ctrlchnlname,
"-l", self.ctrlchnlname + ".log",
"-p", self.ctrlchnlname + ".pid"]
if 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_NAME'] = str(self.name)
try:
tmp = subprocess.Popen(vnoded, stdout = subprocess.PIPE, env = env)
except OSError, e:
msg = "error running vnoded command: %s (%s)" % (vnoded, e)
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL,
"SimpleLxcNode.startup()", msg)
raise Exception, msg
tmp = subprocess.Popen(vnoded, stdout=subprocess.PIPE, env=env)
except OSError:
msg = "error running vnoded command: %s" % vnoded
logger.exception("SimpleLxcNode.startup(): %s", msg)
raise Exception(msg)
try:
self.pid = int(tmp.stdout.read())
tmp.stdout.close()
except Exception:
except ValueError:
msg = "vnoded failed to create a namespace; "
msg += "check kernel support and user priveleges"
self.exception(coreapi.CORE_EXCP_LEVEL_FATAL,
"SimpleLxcNode.startup()", msg)
logger.exception("SimpleLxcNode.startup(): %s", msg)
if tmp.wait():
raise Exception, ("command failed: %s" % vnoded)
self.vnodeclient = vnodeclient.VnodeClient(self.name,
self.ctrlchnlname)
self.info("bringing up loopback interface")
self.cmd([IP_BIN, "link", "set", "lo", "up"])
self.info("setting hostname: %s" % self.name)
raise Exception("command failed: %s" % vnoded)
self.vnodeclient = vnodeclient.VnodeClient(self.name, self.ctrlchnlname)
logger.info("bringing up loopback interface")
self.cmd([constants.IP_BIN, "link", "set", "lo", "up"])
logger.info("setting hostname: %s" % self.name)
self.cmd(["hostname", self.name])
self.up = True
def shutdown(self):
"""
Shutdown logic for simple lxc nodes.
:return: nothing
"""
# nothing to do if node is not up
if not self.up:
return
# unmount all targets
while self._mounts:
source, target = self._mounts.pop(-1)
self.umount(target)
# shutdown all interfaces
for netif in self.netifs():
netif.shutdown()
# attempt to kill node process and wait for termination of children
try:
os.kill(self.pid, signal.SIGTERM)
os.waitpid(self.pid, 0)
except OSError:
pass
logger.exception("error killing process")
# remove node directory if present
try:
os.unlink(self.ctrlchnlname)
if os.path.exists(self.ctrlchnlname):
os.unlink(self.ctrlchnlname)
except OSError:
pass
logger.exception("error removing file")
# clear interface data, close client, and mark self and not up
self._netif.clear()
self.vnodeclient.close()
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)
def cmdresult(self, args):
"""
Wrapper around vnodeclient cmdresult.
:param args: arguments for ocmmand
:return:
"""
return self.vnodeclient.cmdresult(args)
def popen(self, args):
"""
Wrapper around vnodeclient popen.
:param args: arguments for ocmmand
:return:
"""
return self.vnodeclient.popen(args)
def icmd(self, args):
"""
Wrapper around vnodeclient icmd.
:param args: arguments for ocmmand
:return:
"""
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)
def term(self, sh = "/bin/sh"):
return self.vnodeclient.term(sh = sh)
def term(self, sh="/bin/sh"):
"""
Wrapper around vnodeclient term.
def termcmdstring(self, sh = "/bin/sh"):
return self.vnodeclient.termcmdstring(sh = sh)
:param sh: shell to create terminal for
:return:
"""
return self.vnodeclient.term(sh=sh)
def shcmd(self, cmdstr, sh = "/bin/sh"):
return self.vnodeclient.shcmd(cmdstr, sh = sh)
def termcmdstring(self, sh="/bin/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):
"""
Boot logic.
:return: nothing
"""
pass
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)
self.info("mounting %s at %s" % (source, target))
logger.info("mounting %s at %s" % (source, target))
try:
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % \
(target, MOUNT_BIN, source, target)
shcmd = "mkdir -p '%s' && %s -n --bind '%s' '%s'" % (
target, constants.MOUNT_BIN, source, target)
self.shcmd(shcmd)
self._mounts.append((source, target))
except:
self.warn("mounting failed for %s at %s" % (source, target))
except IOError:
logger.exception("mounting failed for %s at %s", source, 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:
self.cmd([UMOUNT_BIN, "-n", "-l", target])
except:
self.warn("unmounting failed for %s" % target)
self.cmd([constants.UMOUNT_BIN, "-n", "-l", target])
except IOError:
logger.exception("unmounting failed for %s" % target)
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()
try:
if ifindex is None:
ifindex = self.newifindex()
if ifname is None:
ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
try:
suffix = '%x.%s.%s' % (self.objid, ifindex, sessionid)
except TypeError:
suffix = '%s.%s.%s' % (self.objid, ifindex, sessionid)
localname = 'veth' + suffix
if len(localname) >= 16:
raise ValueError, "interface local name '%s' too long" % \
localname
raise ValueError("interface local name '%s' too long" % localname)
name = localname + 'p'
if len(name) >= 16:
raise ValueError, "interface name '%s' too long" % name
ifclass = VEth
veth = ifclass(node = self, name = name, localname = localname,
mtu = 1500, net = net, start = self.up)
veth = VEth(node=self, name=name, localname=localname, mtu=1500, net=net, start=self.up)
if self.up:
check_call([IP_BIN, "link", "set", veth.name,
"netns", str(self.pid)])
self.cmd([IP_BIN, "link", "set", veth.name, "name", ifname])
subprocess.check_call([constants.IP_BIN, "link", "set", veth.name, "netns", str(self.pid)])
self.cmd([constants.IP_BIN, "link", "set", veth.name, "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:
self.addnetif(veth, ifindex)
except:
veth.shutdown()
del veth
raise
return ifindex
finally:
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()
try:
if ifindex is None:
ifindex = self.newifindex()
if ifname is None:
ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
localname = "tap%s.%s.%s" % (self.objid, ifindex, sessionid)
name = ifname
ifclass = TunTap
tuntap = ifclass(node = self, name = name, localname = localname,
mtu = 1500, net = net, start = self.up)
tuntap = ifclass(node=self, name=name, localname=localname,
mtu=1500, net=net, start=self.up)
try:
self.addnetif(tuntap, ifindex)
except:
except Exception as e:
tuntap.shutdown()
del tuntap
raise
raise e
return ifindex
finally:
self.lock.release()
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)
if self.up:
(status, result) = self.cmdresult([IP_BIN, "link", "set", "dev",
self.ifname(ifindex), "address", str(addr)])
(status, result) = self.cmdresult([constants.IP_BIN, "link", "set", "dev",
self.ifname(ifindex), "address", str(addr)])
if status:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
"SimpleLxcNode.sethwaddr()",
"error setting MAC address %s" % str(addr))
logger.error("error setting MAC address %s", str(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 ":" 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)])
else:
self.cmd([IP_BIN, "addr", "add", str(addr), "broadcast", "+",
self.cmd([constants.IP_BIN, "addr", "add", str(addr), "broadcast", "+",
"dev", self.ifname(ifindex)])
self._netif[ifindex].addaddr(addr)
def deladdr(self, ifindex, addr):
"""
Delete address from an interface.
:param int ifindex: index of interface to delete address from
:param str addr: address to delete from interface
:return: nothing
"""
try:
self._netif[ifindex].deladdr(addr)
except ValueError:
self.warn("trying to delete unknown address: %s" % addr)
if self.up:
self.cmd([IP_BIN, "addr", "del", str(addr),
"dev", self.ifname(ifindex)])
logger.exception("trying to delete unknown address: %s" % addr)
valid_deladdrtype = ("inet", "inet6", "inet6link")
def delalladdr(self, ifindex, addrtypes = valid_deladdrtype):
addr = self.getaddr(self.ifname(ifindex), rescan = True)
if self.up:
self.cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)])
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:
if t not in self.valid_deladdrtype:
raise ValueError, "addr type must be in: " + \
" ".join(self.valid_deladdrtype)
raise ValueError("addr type must be in: " + " ".join(self.valid_deladdrtype))
for a in addr[t]:
self.deladdr(ifindex, a)
# update cached information
self.getaddr(self.ifname(ifindex), rescan = True)
self.getaddr(self.ifname(ifindex), rescan=True)
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,
ifindex = None, ifname = None):
:param int ifindex: index of interface to bring up
: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()
try:
if isinstance(net, EmaneNode):
ifindex = self.newtuntap(ifindex = ifindex, ifname = ifname,
net = net)
# TODO: see if you can move this to emane specific code
if nodeutils.is_node(net, NodeTypes.EMANE):
ifindex = self.newtuntap(ifindex=ifindex, ifname=ifname, net=net)
# TUN/TAP is not ready for addressing yet; the device may
# take some time to appear, and installing it into a
# namespace after it has been bound removes addressing;
@ -272,143 +464,235 @@ class SimpleLxcNode(PyCoreNode):
self.attachnet(ifindex, net)
netif = self.netif(ifindex)
netif.sethwaddr(hwaddr)
for addr in maketuple(addrlist):
for addr in utils.maketuple(addrlist):
netif.addaddr(addr)
return ifindex
else:
ifindex = self.newveth(ifindex = ifindex, ifname = ifname,
net = net)
ifindex = self.newveth(ifindex=ifindex, ifname=ifname, net=net)
if net is not None:
self.attachnet(ifindex, net)
if hwaddr:
self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist):
self.addaddr(ifindex, addr)
if addrlist:
for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr)
self.ifup(ifindex)
return ifindex
finally:
self.lock.release()
def connectnode(self, ifname, othernode, otherifname):
"""
Connect a node.
:param str ifname: name of interface to connect
:param core.netns.nodes.LxcNode othernode: node to connect to
:param str otherifname: interface name to connect to
:return: nothing
"""
tmplen = 8
tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase)
for x in xrange(tmplen)])
tmp2 = "tmp." + "".join([random.choice(string.ascii_lowercase)
for x in xrange(tmplen)])
check_call([IP_BIN, "link", "add", "name", tmp1,
"type", "veth", "peer", "name", tmp2])
subprocess.check_call([constants.IP_BIN, "link", "add", "name", tmp1,
"type", "veth", "peer", "name", tmp2])
check_call([IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
self.cmd([IP_BIN, "link", "set", tmp1, "name", ifname])
subprocess.call([constants.IP_BIN, "link", "set", tmp1, "netns", str(self.pid)])
self.cmd([constants.IP_BIN, "link", "set", tmp1, "name", ifname])
self.addnetif(PyCoreNetIf(self, ifname), self.newifindex())
check_call([IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)])
othernode.cmd([IP_BIN, "link", "set", tmp2, "name", otherifname])
subprocess.check_call([constants.IP_BIN, "link", "set", tmp2, "netns", str(othernode.pid)])
othernode.cmd([constants.IP_BIN, "link", "set", tmp2, "name", otherifname])
othernode.addnetif(PyCoreNetIf(othernode, otherifname),
othernode.newifindex())
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)
def getaddr(self, ifname, rescan = False):
return self.vnodeclient.getaddr(ifname = ifname, rescan = rescan)
def getaddr(self, ifname, rescan=False):
"""
Wrapper around vnodeclient getaddr.
def netifstats(self, ifname = None):
return self.vnodeclient.netifstats(ifname = ifname)
:param str ifname: interface name to get address for
: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):
def __init__(self, session, objid = None, name = None,
nodedir = None, bootsh = "boot.sh", verbose = False,
start = True):
super(LxcNode, self).__init__(session = session, objid = objid,
name = name, nodedir = nodedir,
verbose = verbose, start = start)
"""
Provides lcx node functionality for core nodes.
"""
def __init__(self, session, objid=None, name=None,
nodedir=None, bootsh="boot.sh", start=True):
"""
Create a LxcNode instance.
:param core.session.Session session: core session instance
:param int objid: object id
:param 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
if start:
self.startup()
def boot(self):
"""
Boot the node.
:return: nothing
"""
self.session.services.bootnodeservices(self)
def validate(self):
"""
Validate the node.
:return: nothing
"""
self.session.services.validatenodeservices(self)
def startup(self):
"""
Startup logic for the node.
:return: nothing
"""
self.lock.acquire()
try:
self.makenodedir()
super(LxcNode, self).startup()
self.privatedir("/var/run")
self.privatedir("/var/log")
except OSError, e:
self.warn("Error with LxcNode.startup(): %s" % e)
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
"LxcNode.startup()", "%s" % e)
except OSError:
logger.exception("error during LxcNode.startup()")
finally:
self.lock.release()
def shutdown(self):
"""
Shutdown logic for the node.
:return: nothing
"""
if not self.up:
return
self.lock.acquire()
# services are instead stopped when session enters datacollect state
#self.session.services.stopnodeservices(self)
# self.session.services.stopnodeservices(self)
try:
super(LxcNode, self).shutdown()
except:
logger.exception("error during shutdown")
finally:
self.rmnodedir()
self.lock.release()
def privatedir(self, path):
"""
Create a private directory.
:param str path: path to create
:return: nothing
"""
if path[0] != "/":
raise ValueError, "path not fully qualified: " + path
hostpath = os.path.join(self.nodedir,
os.path.normpath(path).strip('/').replace('/', '.'))
raise ValueError("path not fully qualified: %s" % path)
hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.'))
try:
os.mkdir(hostpath)
except OSError:
pass
except Exception, e:
raise Exception, e
logger.exception("error creating directory: %s", hostpath)
self.mount(hostpath, path)
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)
if not basename:
raise ValueError, "no basename for filename: " + filename
raise ValueError("no basename for filename: " + filename)
if dirname and dirname[0] == "/":
dirname = dirname[1:]
dirname = dirname.replace("/", ".")
dirname = os.path.join(self.nodedir, dirname)
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)
dirname, basename = os.path.split(hostfilename)
if not os.path.isdir(dirname):
os.makedirs(dirname, mode = 0755)
os.makedirs(dirname, mode=0755)
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.write(contents)
os.chmod(f.name, mode)
f.close()
self.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode))
def nodefilecopy(self, filename, srcfilename, mode = None):
''' Copy a file to a node, following symlinks and preserving metadata.
logger.info("created nodefile: '%s'; mode: 0%o" % (f.name, mode))
def nodefilecopy(self, filename, srcfilename, mode=None):
"""
Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified.
'''
:param str filename: file name to copy file to
:param str srcfilename: file to copy
:param int mode: mode to copy to
:return: nothing
"""
hostfilename = self.hostfilename(filename)
shutil.copy2(srcfilename, hostfilename)
if mode is not None:
os.chmod(hostfilename, mode)
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
over a control channel to the vnoded process running in a network namespace.
The control channel can be accessed via calls to the vcmd Python module or
by invoking the vcmd shell command.
'''
"""
import os, stat, sys
from core.constants import *
import os
import stat
from core import constants
from core.misc import log
logger = log.get_logger(__name__)
USE_VCMD_MODULE = True
@ -22,10 +20,21 @@ if USE_VCMD_MODULE:
else:
import subprocess
VCMD = os.path.join(CORE_SBIN_DIR, "vcmd")
VCMD = os.path.join(constants.CORE_SBIN_DIR, "vcmd")
class VnodeClient(object):
"""
Provides client functionality for interacting with a virtual node.
"""
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.ctrlchnlname = ctrlchnlname
if USE_VCMD_MODULE:
@ -34,25 +43,39 @@ class VnodeClient(object):
self.cmdchnl = None
self._addr = {}
def warn(self, msg):
print >> sys.stderr, "%s: %s" % (self.name, msg)
def connected(self):
"""
Check if node is connected or not.
:return: True if connected, False otherwise
:rtype: bool
"""
if USE_VCMD_MODULE:
return self.cmdchnl.connected()
else:
return True
def close(self):
"""
Close the client connection.
:return: nothing
"""
if USE_VCMD_MODULE:
self.cmdchnl.close()
def cmd(self, args, wait = True):
''' Execute a command on a node and return the status (return code).
'''
def cmd(self, args, wait=True):
"""
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 not self.cmdchnl.connected():
raise ValueError, "self.cmdchnl not connected"
raise ValueError("self.cmdchnl not connected")
tmp = self.cmdchnl.qcmd(args)
if not wait:
return tmp
@ -62,19 +85,25 @@ class VnodeClient(object):
mode = os.P_WAIT
else:
mode = os.P_NOWAIT
tmp = os.spawnlp(mode, VCMD, VCMD, "-c",
self.ctrlchnlname, "-q", "--", *args)
tmp = os.spawnlp(mode, VCMD, VCMD, "-c", self.ctrlchnlname, "-q", "--", *args)
if not wait:
return 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
def cmdresult(self, args):
''' Execute a command on a node and return a tuple containing the
exit status and result string. stderr output
is folded into the stdout result string.
'''
"""
Execute a command on a node and return a tuple containing the
exit status and result string. stderr output
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)
result = cmdout.read()
result += cmderr.read()
@ -82,43 +111,69 @@ class VnodeClient(object):
cmdout.close()
cmderr.close()
status = cmdid.wait()
return (status, result)
return status, result
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 not self.cmdchnl.connected():
raise ValueError, "self.cmdchnl not connected"
raise ValueError("self.cmdchnl not connected")
return self.cmdchnl.popen(args)
else:
cmd = [VCMD, "-c", self.ctrlchnlname, "--"]
cmd.extend(args)
tmp = subprocess.Popen(cmd, stdin = subprocess.PIPE,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE)
tmp = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return tmp, tmp.stdin, tmp.stdout, tmp.stderr
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
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:
raise NotImplementedError
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)
if not wait:
return tmp
tmp = tmp.wait()
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
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",
VCMD, "-c", self.ctrlchnlname, "--", sh)
if "SUDO_USER" in os.environ:
@ -127,19 +182,42 @@ class VnodeClient(object):
os.environ["SUDO_USER"])
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)
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])
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:
return self._addr[ifname]
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)
cmdin.close()
for line in cmdout:
line = line.strip().split()
if line[0] == "link/ether":
@ -152,21 +230,27 @@ class VnodeClient(object):
elif line[3] == "link":
tmp["inet6link"].append(line[1])
else:
self.warn("unknown scope: %s" % line[3])
else:
pass
logger.warn("unknown scope: %s" % line[3])
err = cmderr.read()
cmdout.close()
cmderr.close()
status = cmdid.wait()
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:
self.warn("error output: %s" % err)
logger.warn("error output: %s" % err)
self._addr[ifname] = 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 = {}
cmd = ["cat", "/proc/net/dev"]
cmdid, cmdin, cmdout, cmderr = self.popen(cmd)
@ -195,34 +279,47 @@ class VnodeClient(object):
cmderr.close()
status = cmdid.wait()
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:
self.warn("error output: %s" % err)
logger.warn("error output: %s" % err)
if ifname is not None:
return stats[ifname]
else:
return stats
def createclients(sessiondir, clientcls = VnodeClient,
cmdchnlfilterfunc = None):
direntries = map(lambda x: os.path.join(sessiondir, x),
os.listdir(sessiondir))
def createclients(sessiondir, clientcls=VnodeClient, cmdchnlfilterfunc=None):
"""
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)
if cmdchnlfilterfunc:
cmdchnls = filter(cmdchnlfilterfunc, cmdchnls)
cmdchnls.sort()
return map(lambda x: clientcls(os.path.basename(x), x), cmdchnls)
def createremoteclients(sessiondir, clientcls = VnodeClient,
filterfunc = None):
''' Creates remote VnodeClients, for nodes emulated on other machines. The
def createremoteclients(sessiondir, clientcls=VnodeClient, filterfunc=None):
"""
Creates remote VnodeClients, for nodes emulated on other machines. The
session.Broker writes a n1.conf/server file having the server's info.
'''
direntries = map(lambda x: os.path.join(sessiondir, x),
os.listdir(sessiondir))
:param str sessiondir: session directory to create clients
: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: os.path.exists(os.path.join(x, "server")),
nodedirs)
nodedirs = filter(lambda x: os.path.exists(os.path.join(x, "server")), nodedirs)
if filterfunc:
nodedirs = filter(filterfunc, nodedirs)
nodedirs.sort()

View file

@ -1,42 +1,34 @@
#
# CORE
# 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
"""
PhysicalNode class for including real systems in the emulated network.
"""
from core.misc.ipaddr import *
from core.misc.utils import *
from core.constants import *
from core.api import coreapi
from core.coreobj import PyCoreNode, PyCoreNetIf
from core.emane.nodes import EmaneNode
if os.uname()[0] == "Linux":
from core.netns.vnet import LxBrNet
from core.netns.vif import GreTap
elif os.uname()[0] == "FreeBSD":
from core.bsd.vnet import NetgraphNet
import os
import subprocess
import threading
from core import constants
from core.coreobj import PyCoreNode
from core.misc import log
from core.misc import utils
from core.netns.vnet import GreTap
from core.netns.vnet import LxBrNet
logger = log.get_logger(__name__)
class PhysicalNode(PyCoreNode):
def __init__(self, session, objid = None, name = None,
nodedir = None, verbose = False, start = True):
PyCoreNode.__init__(self, session, objid, name, verbose=verbose,
start=start)
def __init__(self, session, objid=None, name=None, nodedir=None, start=True):
PyCoreNode.__init__(self, session, objid, name, start=start)
self.nodedir = nodedir
self.up = start
self.lock = threading.RLock()
self._mounts = []
if start:
self.startup()
def boot(self):
self.session.services.bootnodeservices(self)
def validate(self):
self.session.services.validatenodeservices(self)
@ -44,11 +36,10 @@ class PhysicalNode(PyCoreNode):
self.lock.acquire()
try:
self.makenodedir()
#self.privatedir("/var/run")
#self.privatedir("/var/log")
except OSError, e:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
"PhysicalNode.startup()", e)
# self.privatedir("/var/run")
# self.privatedir("/var/log")
except OSError:
logger.exception("startup error")
finally:
self.lock.release()
@ -64,16 +55,17 @@ class PhysicalNode(PyCoreNode):
self.rmnodedir()
self.lock.release()
def termcmdstring(self, sh = "/bin/sh"):
''' The broker will add the appropriate SSH command to open a terminal
def termcmdstring(self, sh="/bin/sh"):
"""
The broker will add the appropriate SSH command to open a terminal
on this physical node.
'''
"""
return sh
def cmd(self, args, wait = True):
''' run a command on the physical node
'''
def cmd(self, args, wait=True):
"""
run a command on the physical node
"""
os.chdir(self.nodedir)
try:
if wait:
@ -82,109 +74,105 @@ class PhysicalNode(PyCoreNode):
else:
# os.spawnlp(os.P_NOWAIT, args)
subprocess.Popen(args)
except CalledProcessError, e:
self.warn("cmd exited with status %s: %s" % (e, str(args)))
except subprocess.CalledProcessError:
logger.exception("cmd exited with status: %s", str(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)
# in Python 2.7 we can use subprocess.check_output() here
tmp = subprocess.Popen(args, stdin = open(os.devnull, 'r'),
stdout = subprocess.PIPE,
stderr = subprocess.STDOUT)
result, err = tmp.communicate() # err will always be None
tmp = subprocess.Popen(args, stdin=open(os.devnull, 'r'),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
# err will always be None
result, err = tmp.communicate()
status = tmp.wait()
return (status, result)
def shcmd(self, cmdstr, sh = "/bin/sh"):
return status, result
def shcmd(self, cmdstr, sh="/bin/sh"):
return self.cmd([sh, "-c", cmdstr])
def sethwaddr(self, ifindex, addr):
''' same as SimpleLxcNode.sethwaddr()
'''
"""
same as SimpleLxcNode.sethwaddr()
"""
self._netif[ifindex].sethwaddr(addr)
ifname = self.ifname(ifindex)
if self.up:
(status, result) = self.cmdresult([IP_BIN, "link", "set", "dev",
ifname, "address", str(addr)])
(status, result) = self.cmdresult(
[constants.IP_BIN, "link", "set", "dev", ifname, "address", str(addr)])
if status:
self.exception(coreapi.CORE_EXCP_LEVEL_ERROR,
"PhysicalNode.sethwaddr()",
"error setting MAC address %s" % str(addr))
logger.error("error setting MAC address %s", str(addr))
def addaddr(self, ifindex, addr):
''' same as SimpleLxcNode.addaddr()
'''
"""
same as SimpleLxcNode.addaddr()
"""
if self.up:
self.cmd([IP_BIN, "addr", "add", str(addr),
"dev", self.ifname(ifindex)])
self.cmd([constants.IP_BIN, "addr", "add", str(addr), "dev", self.ifname(ifindex)])
self._netif[ifindex].addaddr(addr)
def deladdr(self, ifindex, addr):
''' same as SimpleLxcNode.deladdr()
'''
"""
same as SimpleLxcNode.deladdr()
"""
try:
self._netif[ifindex].deladdr(addr)
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)])
self.cmd([constants.IP_BIN, "addr", "del", str(addr), "dev", self.ifname(ifindex)])
def adoptnetif(self, netif, ifindex, hwaddr, addrlist):
''' The broker builds a GreTap tunnel device to this physical node.
"""
The broker builds a GreTap tunnel device to this physical node.
When a link message is received linking this node to another part of
the emulation, no new interface is created; instead, adopt the
GreTap netif as the node interface.
'''
"""
netif.name = "gt%d" % ifindex
netif.node = self
self.addnetif(netif, ifindex)
# use a more reasonable name, e.g. "gt0" instead of "gt.56286.150"
if self.up:
self.cmd([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", "dev", netif.localname, "down"])
self.cmd([constants.IP_BIN, "link", "set", netif.localname, "name", netif.name])
netif.localname = netif.name
if hwaddr:
self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist):
for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr)
if self.up:
self.cmd([IP_BIN, "link", "set", "dev", netif.localname, "up"])
def linkconfig(self, netif, bw = None, delay = None,
loss = None, duplicate = None, jitter = None, netif2 = None):
''' Apply tc queing disciplines using LxBrNet.linkconfig()
'''
if os.uname()[0] == "Linux":
netcls = LxBrNet
elif os.uname()[0] == "FreeBSD":
netcls = NetgraphNet
else:
raise NotImplementedError, "unsupported platform"
self.cmd([constants.IP_BIN, "link", "set", "dev", netif.localname, "up"])
def linkconfig(self, netif, bw=None, delay=None,
loss=None, duplicate=None, jitter=None, netif2=None):
"""
Apply tc queing disciplines using LxBrNet.linkconfig()
"""
# borrow the tc qdisc commands from LxBrNet.linkconfig()
tmp = netcls(session=self.session, start=False)
tmp.up = True
tmp.linkconfig(netif, bw=bw, delay=delay, loss=loss,
duplicate=duplicate, jitter=jitter, netif2=netif2)
del tmp
linux_bridge = LxBrNet(session=self.session, start=False)
linux_bridge.up = True
linux_bridge.linkconfig(netif, bw=bw, delay=delay, loss=loss, duplicate=duplicate,
jitter=jitter, netif2=netif2)
del linux_bridge
def newifindex(self):
self.lock.acquire()
try:
with self.lock:
while self.ifindex in self._netif:
self.ifindex += 1
ifindex = self.ifindex
self.ifindex += 1
return ifindex
finally:
self.lock.release()
def newnetif(self, net = None, addrlist = [], hwaddr = None,
ifindex = None, ifname = None):
def newnetif(self, net=None, addrlist=[], hwaddr=None, ifindex=None, ifname=None):
if self.up and net is None:
raise NotImplementedError
if ifindex is None:
ifindex = self.newifindex()
@ -193,56 +181,52 @@ class PhysicalNode(PyCoreNode):
# tunnel to net not built yet, so build it now and adopt it
gt = self.session.broker.addnettunnel(net.objid)
if gt is None or len(gt) != 1:
self.session.warn("Error building tunnel from PhysicalNode."
"newnetif()")
raise ValueError("error building tunnel from adding a new network interface: %s" % gt)
gt = gt[0]
net.detach(gt)
self.adoptnetif(gt, ifindex, hwaddr, addrlist)
return ifindex
# this is reached when configuring services (self.up=False)
if ifname is None:
ifname = "gt%d" % ifindex
netif = GreTap(node = self, name = ifname, session = self.session,
start = False)
netif = GreTap(node=self, name=ifname, session=self.session, start=False)
self.adoptnetif(netif, ifindex, hwaddr, addrlist)
return ifindex
def privatedir(self, path):
if path[0] != "/":
raise ValueError, "path not fully qualified: " + path
hostpath = os.path.join(self.nodedir,
os.path.normpath(path).strip('/').replace('/', '.'))
hostpath = os.path.join(self.nodedir, os.path.normpath(path).strip('/').replace('/', '.'))
try:
os.mkdir(hostpath)
except OSError:
pass
except Exception, e:
raise Exception, e
logger.exception("error creating directory: %s", hostpath)
self.mount(hostpath, path)
def mount(self, source, target):
source = os.path.abspath(source)
self.info("mounting %s at %s" % (source, target))
logger.info("mounting %s at %s" % (source, target))
try:
os.makedirs(target)
except OSError:
pass
try:
self.cmd([MOUNT_BIN, "--bind", source, target])
self.cmd([constants.MOUNT_BIN, "--bind", source, target])
self._mounts.append((source, target))
except OSError:
logger.exception("error making directories")
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):
self.info("unmounting '%s'" % target)
logger.info("unmounting '%s'" % target)
try:
self.cmd([UMOUNT_BIN, "-l", target])
self.cmd([constants.UMOUNT_BIN, "-l", target])
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)
if not basename:
raise ValueError, "no basename for filename: " + filename
@ -251,15 +235,13 @@ class PhysicalNode(PyCoreNode):
dirname = dirname.replace("/", ".")
dirname = os.path.join(self.nodedir, dirname)
if not os.path.isdir(dirname):
os.makedirs(dirname, mode = 0755)
os.makedirs(dirname, mode=0755)
hostfilename = os.path.join(dirname, basename)
return open(hostfilename, mode)
def nodefile(self, filename, contents, mode = 0644):
def nodefile(self, filename, contents, mode=0644):
f = self.opennodefile(filename, "w")
f.write(contents)
os.chmod(f.name, mode)
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
'''
"""
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
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):
''' 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
when a node position or link has changed.
'''
"""
DEFAULT_SDT_URL = "tcp://127.0.0.1:50000/"
# default altitude (in meters) for flyto view
DEFAULT_ALT = 2500
# TODO: read in user's nodes.conf here; below are default node types
# from the GUI
DEFAULT_SPRITES = [('router', 'router.gif'), ('host', 'host.gif'),
('PC', 'pc.gif'), ('mdr', 'mdr.gif'),
('prouter', 'router_green.gif'), ('xen', 'xen.gif'),
('hub', 'hub.gif'), ('lanswitch','lanswitch.gif'),
('wlan', 'wlan.gif'), ('rj45','rj45.gif'),
('tunnel','tunnel.gif'),
]
# TODO: read in user"s nodes.conf here; below are default node types from the GUI
DEFAULT_SPRITES = [
("router", "router.gif"), ("host", "host.gif"),
("PC", "pc.gif"), ("mdr", "mdr.gif"),
("prouter", "router_green.gif"), ("xen", "xen.gif"),
("hub", "hub.gif"), ("lanswitch", "lanswitch.gif"),
("wlan", "wlan.gif"), ("rj45", "rj45.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):
"""
Creates a Sdt instance.
:param core.session.Session session: session this manager is tied to
"""
self.session = session
self.sock = None
self.connected = False
self.showerror = True
self.url = self.DEFAULT_SDT_URL
self.verbose = self.session.getcfgitembool('verbose', False)
# node information for remote nodes not in session._objs
# local nodes also appear here since their obj may not exist yet
self.remotes = {}
session.broker.handlers.add(self.handledistributed)
def is_enabled(self):
''' Check for 'enablesdt' session option. Return False by default if
the option is missing.
'''
if not hasattr(self.session.options, 'enablesdt'):
"""
Check for "enablesdt" session option. Return False by default if
the option is missing.
:return: True if enabled, False otherwise
:rtype: bool
"""
if not hasattr(self.session.options, "enablesdt"):
return False
enabled = self.session.options.enablesdt
if enabled in ('1', 'true', 1, True):
if enabled in ("1", "true", 1, True):
return True
return False
def seturl(self):
''' Read 'sdturl' from session options, or use the default value.
Set self.url, self.address, self.protocol
'''
"""
Read "sdturl" from session options, or use the default value.
Set self.url, self.address, self.protocol
:return: nothing
"""
url = None
if hasattr(self.session.options,'sdturl'):
if hasattr(self.session.options, "sdturl"):
if self.session.options.sdturl != "":
url = self.session.options.sdturl
if url is None or url == "":
@ -78,147 +106,193 @@ class Sdt(object):
self.protocol = self.url.scheme
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():
return False
if self.connected:
return True
if self.session.getstate() == coreapi.CORE_EVENT_SHUTDOWN_STATE:
if self.session.state == EventTypes.SHUTDOWN_STATE.value:
return False
self.seturl()
if self.showerror:
self.session.info("connecting to SDT at %s://%s" \
% (self.protocol, self.address))
logger.info("connecting to SDT at %s://%s" % (self.protocol, self.address))
if self.sock is None:
try:
if (self.protocol.lower() == 'udp'):
if self.protocol.lower() == "udp":
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.connect(self.address)
else:
# Default to tcp
self.sock = socket.create_connection(self.address, 5)
except Exception, e:
if self.showerror:
self.session.warn("SDT socket connect error: %s" % e)
self.showerror = False
except IOError:
logger.exception("SDT socket connect error")
return False
if not self.initialize():
return False
self.connected = True
# refresh all objects in SDT3D when connecting after session start
if not flags & coreapi.CORE_API_ADD_FLAG:
if not self.sendobjs():
return False
if not flags & MessageFlags.ADD.value and not self.sendobjs():
return False
return True
def initialize(self):
''' Load icon sprites, and fly to the reference point location on
the virtual globe.
'''
if not self.cmd('path "%s/icons/normal"' % CORE_DATA_DIR):
"""
Load icon sprites, and fly to the reference point location on
the virtual globe.
:return: initialize command status
:rtype: bool
"""
if not self.cmd("path \"%s/icons/normal\"" % constants.CORE_DATA_DIR):
return False
# send node type to icon mappings
for (type, icon) in self.DEFAULT_SPRITES:
if not self.cmd('sprite %s image %s' % (type, icon)):
for type, icon in self.DEFAULT_SPRITES:
if not self.cmd("sprite %s image %s" % (type, icon)):
return False
(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):
try:
self.sock.close()
except:
pass
self.sock = None
"""
Disconnect from SDT.
:return: nothing
"""
if self.sock:
try:
self.sock.close()
except IOError:
logger.error("error closing socket")
finally:
self.sock = None
self.connected = False
def shutdown(self):
''' Invoked from Session.shutdown() and Session.checkshutdown().
'''
self.cmd('clear all')
"""
Invoked from Session.shutdown() and Session.checkshutdown().
:return: nothing
"""
self.cmd("clear all")
self.disconnect()
self.showerror = True
def cmd(self, cmdstr):
''' Send an SDT command over a UDP socket. socket.sendall() is used
as opposed to socket.sendto() because an exception is raised when
there is no socket listener.
'''
"""
Send an SDT command over a UDP socket. socket.sendall() is used
as opposed to socket.sendto() because an exception is raised when
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:
return False
try:
if self.verbose:
self.session.info("sdt: %s" % cmdstr)
logger.info("sdt: %s" % cmdstr)
self.sock.sendall("%s\n" % cmdstr)
return True
except Exception, e:
if self.showerror:
self.session.warn("SDT connection error: %s" % e)
self.showerror = False
except IOError:
logger.exception("SDT connection error")
self.sock = None
self.connected = False
return False
def updatenode(self, nodenum, flags, x, y, z,
name=None, type=None, icon=None):
''' Node is updated from a Node Message or mobility script.
'''
def updatenode(self, nodenum, flags, x, y, z, name=None, type=None, icon=None):
"""
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():
return
if flags & coreapi.CORE_API_DEL_FLAG:
self.cmd('delete node,%d' % nodenum)
if flags & MessageFlags.DELETE.value:
self.cmd("delete node,%d" % nodenum)
return
if x is None or y is None:
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)
if flags & coreapi.CORE_API_ADD_FLAG:
if flags & MessageFlags.ADD.value:
if icon is not None:
type = name
icon = icon.replace("$CORE_DATA_DIR", CORE_DATA_DIR)
icon = icon.replace("$CORE_CONF_DIR", CORE_CONF_DIR)
self.cmd('sprite %s image %s' % (type, icon))
self.cmd('node %d type %s label on,"%s" %s' % \
(nodenum, type, name, pos))
icon = icon.replace("$CORE_DATA_DIR", constants.CORE_DATA_DIR)
icon = icon.replace("$CORE_CONF_DIR", constants.CORE_CONF_DIR)
self.cmd("sprite %s image %s" % (type, icon))
self.cmd("node %d type %s label on,\"%s\" %s" % (nodenum, type, name, pos))
else:
self.cmd('node %d %s' % (nodenum, pos))
self.cmd("node %d %s" % (nodenum, pos))
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():
return
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):
''' 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:
return
if not self.connect():
return
if flags & coreapi.CORE_API_DEL_FLAG:
self.cmd('delete link,%s,%s' % (node1num, node2num))
elif flags & coreapi.CORE_API_ADD_FLAG:
if flags & MessageFlags.DELETE.value:
self.cmd("delete link,%s,%s" % (node1num, node2num))
elif flags & MessageFlags.ADD.value:
attr = ""
if wireless:
attr = " line green,2"
else:
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):
''' Session has already started, and the SDT3D GUI later connects.
Send all node and link objects for display. Otherwise, nodes and
links will only be drawn when they have been updated (e.g. moved).
'''
"""
Session has already started, and the SDT3D GUI later connects.
Send all node and link objects for display. Otherwise, nodes and
links will only be drawn when they have been updated (e.g. moved).
:return: nothing
"""
nets = []
with self.session._objslock:
for obj in self.session.objs():
with self.session._objects_lock:
for obj in self.session.objects.itervalues():
if isinstance(obj, PyCoreNet):
nets.append(obj)
if not isinstance(obj, PyCoreObj):
@ -226,88 +300,96 @@ class Sdt(object):
(x, y, z) = obj.getposition()
if x is None or y is None:
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)
for nodenum in sorted(self.remotes.keys()):
r = self.remotes[nodenum]
(x, y, z) = r.pos
self.updatenode(nodenum, coreapi.CORE_API_ADD_FLAG, x, y, z,
x, y, z = r.pos
self.updatenode(nodenum, MessageFlags.ADD.value, x, y, z,
r.name, r.type, r.icon)
for net in nets:
# use tolinkmsgs() to handle various types of links
msgs = net.tolinkmsgs(flags = coreapi.CORE_API_ADD_FLAG)
for msg in msgs:
msghdr = msg[:coreapi.CoreMessage.hdrsiz]
flags = coreapi.CoreMessage.unpackhdr(msghdr)[1]
m = coreapi.CoreLinkMessage(flags, msghdr,
msg[coreapi.CoreMessage.hdrsiz:])
n1num = m.gettlv(coreapi.CORE_TLV_LINK_N1NUMBER)
n2num = m.gettlv(coreapi.CORE_TLV_LINK_N2NUMBER)
link_msg_type = m.gettlv(coreapi.CORE_TLV_LINK_TYPE)
if isinstance(net, nodes.WlanNode) or \
isinstance(net, nodes.EmaneNode):
if (n1num == net.objid):
messages = net.all_link_data(flags=MessageFlags.ADD.value)
for message in messages:
msghdr = message[:coreapi.CoreMessage.header_len]
flags = coreapi.CoreMessage.unpack_header(msghdr)[1]
m = coreapi.CoreLinkMessage(flags, msghdr, message[coreapi.CoreMessage.header_len:])
n1num = m.get_tlv(LinkTlvs.N1_NUMBER.value)
n2num = m.get_tlv(LinkTlvs.N2_NUMBER.value)
link_msg_type = m.get_tlv(LinkTlvs.TYPE.value)
if nodeutils.is_node(net, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
if n1num == net.objid:
continue
wl = (link_msg_type == coreapi.CORE_LINK_WIRELESS)
self.updatelink(n1num, n2num, coreapi.CORE_API_ADD_FLAG, wl)
wl = link_msg_type == LinkTypes.WIRELESS.value
self.updatelink(n1num, n2num, MessageFlags.ADD.value, wl)
for n1num in sorted(self.remotes.keys()):
r = self.remotes[n1num]
for (n2num, wl) in r.links:
self.updatelink(n1num, n2num, coreapi.CORE_API_ADD_FLAG, wl)
def handledistributed(self, msg):
''' Broker handler for processing CORE API messages as they are
received. This is used to snoop the Node messages and update
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)
for n2num, wl in r.links:
self.updatelink(n1num, n2num, MessageFlags.ADD.value, wl)
# TODO: remove the need for this
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
node positions.
: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):
''' Process a Node Message to add/delete or move a node on
the SDT display. Node properties are found in session._objs or
self.remotes for remote nodes (or those not yet instantiated).
'''
"""
Process a Node Message to add/delete or move a node on
the SDT display. Node properties are found in session._objs or
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
# enabled prior to starting the session
if not self.is_enabled():
return False
# 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:
return
x = msg.gettlv(coreapi.CORE_TLV_NODE_XPOS)
y = msg.gettlv(coreapi.CORE_TLV_NODE_YPOS)
x = msg.get_tlv(NodeTlvs.X_POSITION.value)
y = msg.get_tlv(NodeTlvs.Y_POSITION.value)
z = None
name = msg.gettlv(coreapi.CORE_TLV_NODE_NAME)
nodetype = msg.gettlv(coreapi.CORE_TLV_NODE_TYPE)
model = msg.gettlv(coreapi.CORE_TLV_NODE_MODEL)
icon = msg.gettlv(coreapi.CORE_TLV_NODE_ICON)
name = msg.get_tlv(NodeTlvs.NAME.value)
nodetype = msg.get_tlv(NodeTlvs.TYPE.value)
model = msg.get_tlv(NodeTlvs.MODEL.value)
icon = msg.get_tlv(NodeTlvs.ICON.value)
net = False
if nodetype == coreapi.CORE_NODE_DEF or \
nodetype == coreapi.CORE_NODE_PHYS or \
nodetype == coreapi.CORE_NODE_XEN:
if nodetype == NodeTypes.DEFAULT.value or \
nodetype == NodeTypes.PHYSICAL.value or \
nodetype == NodeTypes.XEN.value:
if model is None:
model = "router"
type = model
elif nodetype != None:
type = coreapi.node_class(nodetype).type
elif nodetype is not None:
type = nodeutils.get_node_class(NodeTypes(nodetype)).type
net = True
else:
type = None
try:
node = self.session.obj(nodenum)
node = self.session.get_object(nodenum)
except KeyError:
node = None
if node:
self.updatenode(node.objid, msg.flags, x, y, z,
node.name, node.type, node.icon)
self.updatenode(node.objid, msg.flags, x, y, z, node.name, node.type, node.icon)
else:
if nodenum in self.remotes:
remote = self.remotes[nodenum]
@ -318,29 +400,33 @@ class Sdt(object):
if icon is None:
icon = remote.icon
else:
remote = self.Bunch(objid=nodenum, type=type, icon=icon,
name=name, net=net, links=set())
remote = Bunch(objid=nodenum, type=type, icon=icon, name=name, net=net, links=set())
self.remotes[nodenum] = remote
remote.pos = (x, y, z)
self.updatenode(nodenum, msg.flags, x, y, z, name, type, icon)
# TODO: remove the need for this
def handlelinkmsg(self, msg):
''' Process a Link Message to add/remove links on the SDT display.
Links are recorded in the remotes[nodenum1].links set for updating
the SDT display at a later time.
'''
"""
Process a Link Message to add/remove links on the SDT display.
Links are recorded in the remotes[nodenum1].links set for updating
the SDT display at a later time.
:param msg: link message to handle
:return: nothing
"""
if not self.is_enabled():
return False
nodenum1 = msg.gettlv(coreapi.CORE_TLV_LINK_N1NUMBER)
nodenum2 = msg.gettlv(coreapi.CORE_TLV_LINK_N2NUMBER)
link_msg_type = msg.gettlv(coreapi.CORE_TLV_LINK_TYPE)
nodenum1 = msg.get_tlv(LinkTlvs.N1_NUMBER.value)
nodenum2 = msg.get_tlv(LinkTlvs.N2_NUMBER.value)
link_msg_type = msg.get_tlv(LinkTlvs.TYPE.value)
# this filters out links to WLAN and EMANE nodes which are not drawn
if self.wlancheck(nodenum1):
return
wl = (link_msg_type == coreapi.CORE_LINK_WIRELESS)
wl = link_msg_type == LinkTypes.WIRELESS.value
if nodenum1 in self.remotes:
r = self.remotes[nodenum1]
if msg.flags & coreapi.CORE_API_DEL_FLAG:
if msg.flags & MessageFlags.DELETE.value:
if (nodenum2, wl) in r.links:
r.links.remove((nodenum2, wl))
else:
@ -348,18 +434,22 @@ class Sdt(object):
self.updatelink(nodenum1, nodenum2, msg.flags, wireless=wl)
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:
type = self.remotes[nodenum].type
if type in ("wlan", "emane"):
return True
else:
try:
n = self.session.obj(nodenum)
n = self.session.get_object(nodenum)
except KeyError:
return False
if isinstance(n, (nodes.WlanNode, nodes.EmaneNode)):
if nodeutils.is_node(n, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
return True
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
__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.
'''
"""
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):
''' Bird router support
'''
"""
Bird router support
"""
_name = "bird"
_group = "BIRD"
_depends = ()
@ -26,13 +17,14 @@ class Bird(CoreService):
_configs = ("/etc/bird/bird.conf",)
_startindex = 35
_startup = ("bird -c %s" % (_configs[0]),)
_shutdown = ("killall bird", )
_validate = ("pidof bird", )
_shutdown = ("killall bird",)
_validate = ("pidof bird",)
@classmethod
def generateconfig(cls, node, filename, services):
''' Return the bird.conf file contents.
'''
"""
Return the bird.conf file contents.
"""
if filename == cls._configs[0]:
return cls.generateBirdConf(node, services)
else:
@ -40,28 +32,30 @@ class Bird(CoreService):
@staticmethod
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():
if hasattr(ifc, 'control') and ifc.control == True:
continue
for a in ifc.addrlist:
if a.find(".") >= 0:
return a .split('/') [0]
#raise ValueError, "no IPv4 address found for router ID"
return a.split('/')[0]
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0"
@classmethod
def generateBirdConf(cls, node, services):
''' Returns configuration file text. Other services that depend on bird
will have generatebirdifcconfig() and generatebirdconfig()
hooks that are invoked here.
'''
cfg = """\
"""
Returns configuration file text. Other services that depend on bird
will have generatebirdifcconfig() and generatebirdconfig()
hooks that are invoked here.
"""
cfg = """\
/* Main configuration file for BIRD. This is ony a template,
* you will *need* to customize it according to your needs
* Beware that only double quotes \'"\' are valid. No singles. */
log "/var/log/%s.log" all;
#debug protocols all;
@ -90,14 +84,16 @@ protocol device {
return cfg
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.
'''
"""
_name = "BirdDaemon"
_group = "BIRD"
_depends = ("bird", )
_depends = ("bird",)
_dirs = ()
_configs = ()
_startindex = 40
@ -106,7 +102,7 @@ class BirdService(CoreService):
_meta = "The config file for this service can be found in the bird service."
@classmethod
def generatebirdconfig(cls, node):
def generatebirdconfig(cls, node):
return ""
@classmethod
@ -118,20 +114,23 @@ class BirdService(CoreService):
cfg = ""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True: continue
cfg += ' interface "%s";\n'% ifc.name
if hasattr(ifc, 'control') and ifc.control == True:
continue
cfg += ' interface "%s";\n' % ifc.name
return cfg
class BirdBgp(BirdService):
'''BGP BIRD Service (configuration generation)'''
"""
BGP BIRD Service (configuration generation)
"""
_name = "BIRD_BGP"
_custom_needed = True
@classmethod
def generatebirdconfig(cls, node):
def generatebirdconfig(cls, node):
return """
/* This is a sample config that should be customized with appropriate AS numbers
* and peers; add one section like this for each neighbor */
@ -152,13 +151,16 @@ protocol bgp {
"""
class BirdOspf(BirdService):
'''OSPF BIRD Service (configuration generation)'''
"""
OSPF BIRD Service (configuration generation)
"""
_name = "BIRD_OSPFv2"
@classmethod
def generatebirdconfig(cls, node):
def generatebirdconfig(cls, node):
cfg = 'protocol ospf {\n'
cfg += ' export filter {\n'
cfg += ' if source = RTS_BGP then {\n'
@ -168,7 +170,7 @@ class BirdOspf(BirdService):
cfg += ' accept;\n'
cfg += ' };\n'
cfg += ' area 0.0.0.0 {\n'
cfg += cls.generatebirdifcconfig(node)
cfg += cls.generatebirdifcconfig(node)
cfg += ' };\n'
cfg += '}\n\n'
@ -176,17 +178,19 @@ class BirdOspf(BirdService):
class BirdRadv(BirdService):
'''RADV BIRD Service (configuration generation)'''
"""
RADV BIRD Service (configuration generation)
"""
_name = "BIRD_RADV"
@classmethod
def generatebirdconfig(cls, node):
cfg = '/* This is a sample config that must be customized */\n'
def generatebirdconfig(cls, node):
cfg = '/* This is a sample config that must be customized */\n'
cfg += 'protocol radv {\n'
cfg += ' # auto configuration on all interfaces\n'
cfg += cls.generatebirdifcconfig(node)
cfg += cls.generatebirdifcconfig(node)
cfg += ' # Advertise DNS\n'
cfg += ' rdnss {\n'
cfg += '# lifetime mult 10;\n'
@ -202,16 +206,18 @@ class BirdRadv(BirdService):
class BirdRip(BirdService):
'''RIP BIRD Service (configuration generation)'''
"""
RIP BIRD Service (configuration generation)
"""
_name = "BIRD_RIP"
@classmethod
def generatebirdconfig(cls, node):
def generatebirdconfig(cls, node):
cfg = 'protocol rip {\n'
cfg += ' period 10;\n'
cfg += ' garbage time 60;\n'
cfg += cls.generatebirdifcconfig(node)
cfg += cls.generatebirdifcconfig(node)
cfg += ' honor neighbor;\n'
cfg += ' authentication none;\n'
cfg += ' import all;\n'
@ -222,13 +228,15 @@ class BirdRip(BirdService):
class BirdStatic(BirdService):
'''Static Bird Service (configuration generation)'''
"""
Static Bird Service (configuration generation)
"""
_name = "BIRD_static"
_custom_needed = True
@classmethod
def generatebirdconfig(cls, node):
def generatebirdconfig(cls, node):
cfg = '/* This is a sample config that must be customized */\n'
cfg += 'protocol static {\n'
@ -240,10 +248,11 @@ class BirdStatic(BirdService):
return cfg
# Register all protocols
addservice(Bird)
addservice(BirdOspf)
addservice(BirdBgp)
#addservice(BirdRadv) # untested
addservice(BirdRip)
addservice(BirdStatic)
def load_services():
# Register all protocols
ServiceManager.add(Bird)
ServiceManager.add(BirdOspf)
ServiceManager.add(BirdBgp)
# ServiceManager.add(BirdRadv) # untested
ServiceManager.add(BirdRip)
ServiceManager.add(BirdStatic)

View file

@ -1,12 +1,5 @@
#
# CORE
# 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.
"""
Docker service allows running docker containers within CORE nodes.
The running of Docker within a CORE node allows for additional extensibility to
the CORE services. This allows network applications and protocols to be easily
@ -20,7 +13,7 @@ service to the Docker group. The image will then be auto run if that service is
selected.
This requires a recent version of Docker. This was tested using a PPA on Ubuntu
with version 1.2.0. The version in the standard Ubuntu repo is to old for
with version 1.2.0. The version in the standard Ubuntu repo is to old for
this purpose (we need --net host).
It also requires docker-py (https://pypi.python.org/pypi/docker-py) which can be
@ -47,13 +40,13 @@ The id will be different on your machine so use it in the following command:
sudo docker tag 4833487e66d2 stuartmarsden/multicastping:core
This image will be listed in the services after we restart the core-daemon:
This image will be listed in the services after we restart the core-daemon:
sudo service core-daemon restart
You can set up a simple network with a number of PCs connected to a switch. Set
the stuartmarsden/multicastping service for all the PCs. When started they will
all begin sending Multicast pings.
all begin sending Multicast pings.
In order to see what is happening you can go in to the terminal of a node and
look at the docker log. Easy shorthand is:
@ -89,11 +82,11 @@ Datagram 'Client: Ping' received from ('10.0.5.20', 8005)
Limitations:
1. Docker images must be downloaded on the host as usually a CORE node does not
1. Docker images must be downloaded on the host as usually a CORE node does not
have access to the internet.
2. Each node isolates running containers (keeps things simple)
3. Recent version of docker needed so that --net host can be used. This does
not further abstract the network within a node and allows multicast which
3. Recent version of docker needed so that --net host can be used. This does
not further abstract the network within a node and allows multicast which
is not enabled within Docker containers at the moment.
4. The core-daemon must be restarted for new images to show up.
5. A Docker-daemon is run within each node but the images are shared. This
@ -101,43 +94,46 @@ Limitations:
host. At startup all the nodes will try to access this and it will be locked
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
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:
from docker import Client
except Exception:
pass
except ImportError:
logger.error("failure to import docker")
from core.service import CoreService, addservice
from core.misc.ipaddr import IPv4Prefix, IPv6Prefix
class DockerService(CoreService):
''' This is a service which will allow running docker containers in a CORE
node.
'''
"""
This is a service which will allow running docker containers in a CORE
node.
"""
_name = "Docker"
_group = "Docker"
_depends = ()
_dirs = ('/var/lib/docker/containers/', '/run/shm', '/run/resolvconf',)
_configs = ('docker.sh', )
_configs = ('docker.sh',)
_startindex = 50
_startup = ('sh docker.sh',)
_shutdown = ('service docker stop', )
_shutdown = ('service docker stop',)
# Container image to start
_image = ""
@classmethod
def generateconfig(cls, node, filename, services):
''' Returns a string having contents of a docker.sh script that
can be modified to start a specific docker image.
'''
"""
Returns a string having contents of a docker.sh script that
can be modified to start a specific docker image.
"""
cfg = "#!/bin/sh\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.
# Unless your network was attached to the Internet this is
# non-functional but hides error messages.
cfg += 'echo "nameserver 8.8.8.8" > /run/resolvconf/resolv.conf\n'
@ -156,27 +152,30 @@ until [ $result -eq 0 ]; do
# this is to alleviate contention to docker's SQLite database
sleep 0.3
done
""" % (cls._image, )
""" % (cls._image,)
return cfg
addservice(DockerService)
# This auto-loads Docker images having a :core tag, adding them to the list
# of services under the "Docker" group.
if 'Client' in globals():
client = Client(version='1.10')
images = client.images()
del client
else:
images = []
for image in images:
if u'<none>' in image['RepoTags'][0]:
continue
for repo in image['RepoTags']:
if u':core' not in repo:
def load_services():
ServiceManager.add(DockerService)
# 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')
images = client.images()
del client
else:
images = []
for image in images:
if u'<none>' in image['RepoTags'][0]:
continue
dockerid = repo.encode('ascii','ignore').split(':')[0]
SubClass = type('SubClass', (DockerService,),
{'_name': dockerid, '_image': dockerid})
addservice(SubClass)
del images
for repo in image['RepoTags']:
if u':core' not in repo:
continue
dockerid = repo.encode('ascii', 'ignore').split(':')[0]
sub_class = type('SubClass', (DockerService,), {'_name': dockerid, '_image': dockerid})
ServiceManager.add(sub_class)
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:
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):
''' Parent class for NRL services. Defines properties and methods
common to NRL's routing daemons.
'''
"""
Parent class for NRL services. Defines properties and methods
common to NRL's routing daemons.
"""""
_name = "Protean"
_group = "ProtoSvc"
_depends = ()
@ -29,108 +24,111 @@ class NrlService(CoreService):
_shutdown = ()
@classmethod
def generateconfig(cls, node, filename, services):
def generateconfig(cls, node, filename, services):
return ""
@staticmethod
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
interface's prefix length, so e.g. '/32' can turn into '/24'.
'''
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
continue
for a in ifc.addrlist:
if a.find(".") >= 0:
addr = a.split('/')[0]
pre = IPv4Prefix("%s/%s" % (addr, prefixlen))
pre = Ipv4Prefix("%s/%s" % (addr, prefixlen))
return str(pre)
#raise ValueError, "no IPv4 address found"
# raise ValueError, "no IPv4 address found"
return "0.0.0.0/%s" % prefixlen
class MgenSinkService(NrlService):
_name = "MGEN_Sink"
_configs = ("sink.mgen", )
_configs = ("sink.mgen",)
_startindex = 5
_startup = ("mgen input sink.mgen", )
_validate = ("pidof mgen", )
_shutdown = ("killall mgen", )
_startup = ("mgen input sink.mgen",)
_validate = ("pidof mgen",)
_shutdown = ("killall mgen",)
@classmethod
def generateconfig(cls, node, filename, services):
cfg = "0.0 LISTEN UDP 5000\n"
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
return cfg
@classmethod
def getstartup(cls, node, services):
cmd =cls._startup[0]
def getstartup(cls, node, services):
cmd = cls._startup[0]
cmd += " output /tmp/mgen_%s.log" % node.name
return (cmd, )
return cmd,
addservice(MgenSinkService)
class NrlNhdp(NrlService):
''' NeighborHood Discovery Protocol for MANET networks.
'''
"""
NeighborHood Discovery Protocol for MANET networks.
"""
_name = "NHDP"
_startup = ("nrlnhdp", )
_shutdown = ("killall nrlnhdp", )
_validate = ("pidof nrlnhdp", )
_startup = ("nrlnhdp",)
_shutdown = ("killall nrlnhdp",)
_validate = ("pidof nrlnhdp",)
@classmethod
def getstartup(cls, node, services):
''' Generate the appropriate command-line based on node interfaces.
'''
def getstartup(cls, node, services):
"""
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0]
cmd += " -l /var/log/nrlnhdp.log"
cmd += " -rpipe %s_nhdp" % node.name
servicenames = map(lambda x: x._name, services)
servicenames = map(lambda x: x._name, services)
if "SMF" in servicenames:
cmd += " -flooding ecds-etx sticky"
cmd += " -smfClient %s_smf" % node.name
netifs = filter(lambda x: not getattr(x, 'control', False), \
node.netifs())
if len(netifs) > 0:
interfacenames = map(lambda x: x.name, netifs)
cmd += " -i "
cmd += " -i ".join(interfacenames)
return (cmd, )
addservice(NrlNhdp)
return cmd,
class NrlSmf(NrlService):
''' Simplified Multicast Forwarding for MANET networks.
'''
"""
Simplified Multicast Forwarding for MANET networks.
"""
_name = "SMF"
_startup = ("sh startsmf.sh", )
_shutdown = ("killall nrlsmf", )
_validate = ("pidof nrlsmf", )
_configs = ("startsmf.sh", )
_startup = ("sh startsmf.sh",)
_shutdown = ("killall nrlsmf",)
_validate = ("pidof nrlsmf",)
_configs = ("startsmf.sh",)
@classmethod
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
directly using vcmd.
'''
"""
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by nrl.py:NrlSmf.generateconfig()\n"
comments = ""
cmd = "nrlsmf instance %s_smf" % (node.name)
cmd = "nrlsmf instance %s_smf" % node.name
servicenames = map(lambda x: x._name, services)
netifs = filter(lambda x: not getattr(x, 'control', False), \
node.netifs())
servicenames = map(lambda x: x._name, services)
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
if len(netifs) == 0:
return ()
if "arouted" in servicenames:
comments += "# arouted service is enabled\n"
cmd += " tap %s_tap" % (node.name,)
@ -145,29 +143,30 @@ class NrlSmf(NrlService):
cmd += " smpr "
else:
cmd += " cf "
interfacenames = map(lambda x: x.name, netifs)
interfacenames = map(lambda x: x.name, netifs)
cmd += ",".join(interfacenames)
cmd += " hash MD5"
cmd += " log /var/log/nrlsmf.log"
cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n"
return cfg
addservice(NrlSmf)
class NrlOlsr(NrlService):
''' Optimized Link State Routing protocol for MANET networks.
'''
"""
Optimized Link State Routing protocol for MANET networks.
"""
_name = "OLSR"
_startup = ("nrlolsrd", )
_shutdown = ("killall nrlolsrd", )
_validate = ("pidof nrlolsrd", )
_startup = ("nrlolsrd",)
_shutdown = ("killall nrlolsrd",)
_validate = ("pidof nrlolsrd",)
@classmethod
def getstartup(cls, node, services):
''' Generate the appropriate command-line based on node interfaces.
'''
def getstartup(cls, node, services):
"""
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0]
# are multiple interfaces supported? No.
netifs = list(node.netifs())
@ -177,78 +176,80 @@ class NrlOlsr(NrlService):
cmd += " -l /var/log/nrlolsrd.log"
cmd += " -rpipe %s_olsr" % node.name
servicenames = map(lambda x: x._name, services)
servicenames = map(lambda x: x._name, services)
if "SMF" in servicenames and not "NHDP" in servicenames:
cmd += " -flooding s-mpr"
cmd += " -smfClient %s_smf" % node.name
if "zebra" in servicenames:
cmd += " -z"
return (cmd, )
addservice(NrlOlsr)
return cmd,
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"
_startup = ("nrlolsrv2", )
_shutdown = ("killall nrlolsrv2", )
_validate = ("pidof nrlolsrv2", )
_startup = ("nrlolsrv2",)
_shutdown = ("killall nrlolsrv2",)
_validate = ("pidof nrlolsrv2",)
@classmethod
def getstartup(cls, node, services):
''' Generate the appropriate command-line based on node interfaces.
'''
def getstartup(cls, node, services):
"""
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0]
cmd += " -l /var/log/nrlolsrv2.log"
cmd += " -rpipe %s_olsrv2" % node.name
servicenames = map(lambda x: x._name, services)
servicenames = map(lambda x: x._name, services)
if "SMF" in servicenames:
cmd += " -flooding ecds"
cmd += " -smfClient %s_smf" % node.name
cmd += " -p olsr"
netifs = filter(lambda x: not getattr(x, 'control', False), \
node.netifs())
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
if len(netifs) > 0:
interfacenames = map(lambda x: x.name, netifs)
cmd += " -i "
cmd += " -i ".join(interfacenames)
return (cmd, )
addservice(NrlOlsrv2)
return cmd,
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"
_configs = ("/etc/olsrd/olsrd.conf",)
_dirs = ("/etc/olsrd",)
_startup = ("olsrd", )
_shutdown = ("killall olsrd", )
_validate = ("pidof olsrd", )
_startup = ("olsrd",)
_shutdown = ("killall olsrd",)
_validate = ("pidof olsrd",)
@classmethod
def getstartup(cls, node, services):
''' Generate the appropriate command-line based on node interfaces.
'''
def getstartup(cls, node, services):
"""
Generate the appropriate command-line based on node interfaces.
"""
cmd = cls._startup[0]
netifs = filter(lambda x: not getattr(x, 'control', False), \
node.netifs())
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
if len(netifs) > 0:
interfacenames = map(lambda x: x.name, netifs)
cmd += " -i "
cmd += " -i ".join(interfacenames)
return (cmd, )
return cmd,
@classmethod
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 = """\
#
# OLSR.org routing daemon config file
@ -314,7 +315,7 @@ class OlsrOrg(NrlService):
# 1 gets remapped by olsrd to 0 UNSPECIFIED (1 is reserved for ICMP redirects)
# 2 KERNEL routes (not very wise to use)
# 3 BOOT (should in fact not be used by routing daemons)
# 4 STATIC
# 4 STATIC
# 8 .. 15 various routing daemons (gated, zebra, bird, & co)
# (defaults to 0 which gets replaced by an OS-specific default value
# under linux 3 (BOOT) (for backward compatibility)
@ -510,7 +511,7 @@ LinkQualityFishEye 0
# Olsrd plugins to load
# This must be the absolute path to the file
# or the loader will use the following scheme:
# - Try the paths in the LD_LIBRARY_PATH
# - Try the paths in the LD_LIBRARY_PATH
# environment variable.
# - The list of libraries cached in /etc/ld.so.cache
# - /lib, followed by /usr/lib
@ -566,11 +567,11 @@ InterfaceDefaults {
"""
return cfg
addservice(OlsrOrg)
class MgenActor(NrlService):
''' ZpcMgenActor.
'''
"""
ZpcMgenActor.
"""
# a unique name is required, without spaces
_name = "MgenActor"
@ -582,53 +583,53 @@ class MgenActor(NrlService):
_dirs = ()
# generated files (without a full path this file goes in the node's dir,
# 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
_startindex = 50
# 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
_validate = ("pidof mgen", )
_validate = ("pidof mgen",)
# list of shutdown commands
_shutdown = ("killall mgen", )
_shutdown = ("killall mgen",)
@classmethod
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
directly using vcmd.
'''
"""
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by nrl.py:MgenActor.generateconfig()\n"
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)
netifs = filter(lambda x: not getattr(x, 'control', False), \
node.netifs())
servicenames = map(lambda x: x._name, services)
netifs = filter(lambda x: not getattr(x, 'control', False), node.netifs())
if len(netifs) == 0:
return ()
cfg += comments + cmd + " < /dev/null > /dev/null 2>&1 &\n\n"
return cfg
# this line is required to add the above class to the list of available services
addservice(MgenActor)
class Arouted(NrlService):
''' Adaptive Routing
'''
"""
Adaptive Routing
"""
_name = "arouted"
_configs = ("startarouted.sh", )
_configs = ("startarouted.sh",)
_startindex = NrlService._startindex + 10
_startup = ("sh startarouted.sh", )
_shutdown = ("pkill arouted", )
_validate = ("pidof arouted", )
_startup = ("sh startarouted.sh",)
_shutdown = ("pkill arouted",)
_validate = ("pidof arouted",)
@classmethod
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 = """
#!/bin/sh
for f in "/tmp/%s_smf"; do
@ -643,12 +644,23 @@ for f in "/tmp/%s_smf"; do
done
done
""" % (node.name)
""" % node.name
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 += " 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"
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
if os.uname()[0] == "Linux":
from core.netns import nodes
elif os.uname()[0] == "FreeBSD":
from core.bsd import nodes
from core.service import CoreService, addservice
from core.misc.ipaddr import IPv4Prefix, isIPv4Address, isIPv6Address
from core.api import coreapi
from core.constants import *
from core import constants
from core.enumerations import LinkTypes, NodeTypes
from core.misc import ipaddress
from core.misc import nodeutils
from core.service import CoreService
from core.service import ServiceManager
class Zebra(CoreService):
'''
'''
_name = "zebra"
_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",
"quaggaboot.sh","/usr/local/etc/quagga/vtysh.conf")
"quaggaboot.sh", "/usr/local/etc/quagga/vtysh.conf")
_startindex = 35
_startup = ("sh quaggaboot.sh zebra",)
_shutdown = ("killall zebra", )
_validate = ("pidof zebra", )
_shutdown = ("killall zebra",)
_validate = ("pidof zebra",)
@classmethod
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]:
return cls.generateQuaggaConf(node, services)
elif filename == cls._configs[1]:
@ -45,19 +36,21 @@ class Zebra(CoreService):
return cls.generateVtyshConf(node, services)
else:
raise ValueError
@classmethod
def generateVtyshConf(cls, node, services):
''' Returns configuration file text.
'''
"""
Returns configuration file text.
"""
return "service integrated-vtysh-config\n"
@classmethod
def generateQuaggaConf(cls, node, services):
''' Returns configuration file text. Other services that depend on zebra
will have generatequaggaifcconfig() and generatequaggaconfig()
hooks that are invoked here.
'''
"""
Returns configuration file text. Other services that depend on zebra
will have generatequaggaifcconfig() and generatequaggaconfig()
hooks that are invoked here.
"""
# we could verify here that filename == Quagga.conf
cfg = ""
for ifc in node.netifs():
@ -75,7 +68,7 @@ class Zebra(CoreService):
for s in services:
if cls._name not in s._depends:
continue
ifccfg = s.generatequaggaifcconfig(node, ifc)
ifccfg = s.generatequaggaifcconfig(node, ifc)
if s._ipv4_routing:
want_ipv4 = True
if s._ipv6_routing:
@ -83,47 +76,47 @@ class Zebra(CoreService):
cfgv6 += ifccfg
else:
cfgv4 += ifccfg
if want_ipv4:
ipv4list = filter(lambda x: isIPv4Address(x.split('/')[0]),
ifc.addrlist)
ipv4list = filter(lambda x: ipaddress.is_ipv4_address(x.split('/')[0]), ifc.addrlist)
cfg += " "
cfg += "\n ".join(map(cls.addrstr, ipv4list))
cfg += "\n"
cfg += cfgv4
if want_ipv6:
ipv6list = filter(lambda x: isIPv6Address(x.split('/')[0]),
ifc.addrlist)
ipv6list = filter(lambda x: ipaddress.is_ipv6_address(x.split('/')[0]), ifc.addrlist)
cfg += " "
cfg += "\n ".join(map(cls.addrstr, ipv6list))
cfg += "\n"
cfg += cfgv6
cfg += "!\n"
for s in services:
if cls._name not in s._depends:
continue
cfg += s.generatequaggaconfig(node)
return cfg
@staticmethod
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:
return "ip address %s" % x
elif x.find(":") >= 0:
return "ipv6 address %s" % x
else:
raise Value, "invalid address: %s", x
raise ValueError("invalid address: %s", x)
@classmethod
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:
quagga_bin_search = node.session.cfg['quagga_bin_search']
quagga_sbin_search = node.session.cfg['quagga_sbin_search']
quagga_bin_search = node.session.config['quagga_bin_search']
quagga_sbin_search = node.session.config['quagga_sbin_search']
except KeyError:
quagga_bin_search = '"/usr/local/bin /usr/bin /usr/lib/quagga"'
quagga_sbin_search = '"/usr/local/sbin /usr/sbin /usr/lib/quagga"'
@ -220,73 +213,74 @@ if [ "$1" != "zebra" ]; then
fi
confcheck
bootquagga
""" % (cls._configs[0], quagga_sbin_search, quagga_bin_search, \
QUAGGA_STATE_DIR)
""" % (cls._configs[0], quagga_sbin_search, quagga_bin_search, constants.QUAGGA_STATE_DIR)
addservice(Zebra)
class QuaggaService(CoreService):
''' Parent class for Quagga services. Defines properties and methods
common to Quagga's routing daemons.
'''
"""
Parent class for Quagga services. Defines properties and methods
common to Quagga's routing daemons.
"""
_name = "QuaggaDaemon"
_group = "Quagga"
_depends = ("zebra", )
_depends = ("zebra",)
_dirs = ()
_configs = ()
_startindex = 40
_startup = ()
_shutdown = ()
_meta = "The config file for this service can be found in the Zebra service."
_ipv4_routing = False
_ipv6_routing = False
@staticmethod
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():
if hasattr(ifc, 'control') and ifc.control == True:
continue
for a in ifc.addrlist:
if a.find(".") >= 0:
return a .split('/') [0]
#raise ValueError, "no IPv4 address found for router ID"
return a.split('/')[0]
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0"
@staticmethod
def rj45check(ifc):
''' Helper to detect whether interface is connected an external RJ45
"""
Helper to detect whether interface is connected an external RJ45
link.
'''
"""
if ifc.net:
for peerifc in ifc.net.netifs():
if peerifc == ifc:
continue
if isinstance(peerifc, nodes.RJ45Node):
if nodeutils.is_node(peerifc, NodeTypes.RJ45):
return True
return False
@classmethod
def generateconfig(cls, node, filename, services):
def generateconfig(cls, node, filename, services):
return ""
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
def generatequaggaifcconfig(cls, node, ifc):
return ""
@classmethod
def generatequaggaconfig(cls, node):
def generatequaggaconfig(cls, node):
return ""
class Ospfv2(QuaggaService):
''' The OSPFv2 service provides IPv4 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the
unified Quagga.conf file.
'''
"""
The OSPFv2 service provides IPv4 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the
unified Quagga.conf file.
"""
_name = "OSPFv2"
_startup = ()
_shutdown = ("killall ospfd", )
@ -295,10 +289,11 @@ class Ospfv2(QuaggaService):
@staticmethod
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
GreTap device.
'''
"""
if ifc.mtu != 1500:
# a workaround for PhysicalNode GreTap, which has no knowledge of
# the other nodes/nets
@ -312,52 +307,55 @@ class Ospfv2(QuaggaService):
@staticmethod
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.
'''
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 ""
@classmethod
def generatequaggaconfig(cls, node):
def generatequaggaconfig(cls, node):
cfg = "router ospf\n"
rtrid = cls.routerid(node)
cfg += " router-id %s\n" % rtrid
# network 10.0.0.0/24 area 0
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
for a in ifc.addrlist:
if a.find(".") < 0:
continue
net = IPv4Prefix(a)
net = ipaddress.Ipv4Prefix(a)
cfg += " network %s area 0\n" % net
cfg += "!\n"
cfg += "!\n"
return cfg
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc)
#cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers
#if cls.rj45check(ifc):
# return cfg
#cfg += cls.ptpcheck(ifc)
#return cfg + """\
# ip ospf hello-interval 2
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc)
# cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers
# if cls.rj45check(ifc):
# return cfg
# cfg += cls.ptpcheck(ifc)
# return cfg + """\
# ip ospf hello-interval 2
# ip ospf dead-interval 6
# ip ospf retransmit-interval 5
#"""
addservice(Ospfv2)
# """
class Ospfv3(QuaggaService):
''' The OSPFv3 service provides IPv6 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the
unified Quagga.conf file.
'''
"""
The OSPFv3 service provides IPv6 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the
unified Quagga.conf file.
"""
_name = "OSPFv3"
_startup = ()
_shutdown = ("killall ospf6d", )
@ -367,9 +365,10 @@ class Ospfv3(QuaggaService):
@staticmethod
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.
'''
"""
mtu = ifc.mtu
if not ifc.net:
return mtu
@ -377,13 +376,14 @@ class Ospfv3(QuaggaService):
if i.mtu < mtu:
mtu = i.mtu
return mtu
@classmethod
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
GreTap device.
'''
"""
minmtu = cls.minmtu(ifc)
if minmtu < ifc.mtu:
return " ipv6 ospf6 ifmtu %d\n" % minmtu
@ -392,57 +392,59 @@ class Ospfv3(QuaggaService):
@staticmethod
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.
'''
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 ""
@classmethod
def generatequaggaconfig(cls, node):
def generatequaggaconfig(cls, node):
cfg = "router ospf6\n"
rtrid = cls.routerid(node)
cfg += " router-id %s\n" % rtrid
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += " interface %s area 0.0.0.0\n" % ifc.name
cfg += "!\n"
return cfg
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc)
#cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers
#if cls.rj45check(ifc):
# return cfg
#cfg += cls.ptpcheck(ifc)
#return cfg + """\
# ipv6 ospf6 hello-interval 2
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
return cls.mtucheck(ifc)
# cfg = cls.mtucheck(ifc)
# external RJ45 connections will use default OSPF timers
# if cls.rj45check(ifc):
# return cfg
# cfg += cls.ptpcheck(ifc)
# return cfg + """\
# ipv6 ospf6 hello-interval 2
# ipv6 ospf6 dead-interval 6
# ipv6 ospf6 retransmit-interval 5
#"""
# """
addservice(Ospfv3)
class Ospfv3mdr(Ospfv3):
''' The OSPFv3 MANET Designated Router (MDR) service provides IPv6
routing for wireless networks. It does not build its own
configuration file but has hooks for adding to the
unified Quagga.conf file.
'''
"""
The OSPFv3 MANET Designated Router (MDR) service provides IPv6
routing for wireless networks. It does not build its own
configuration file but has hooks for adding to the
unified Quagga.conf file.
"""
_name = "OSPFv3MDR"
_ipv4_routing = True
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
def generatequaggaifcconfig(cls, node, ifc):
cfg = cls.mtucheck(ifc)
cfg += " ipv6 ospf6 instance-id 65\n"
if ifc.net is not None and \
isinstance(ifc.net, (nodes.WlanNode, nodes.EmaneNode)):
if ifc.net is not None and nodeutils.is_node(ifc.net, (NodeTypes.WIRELESS_LAN, NodeTypes.EMANE)):
return cfg + """\
ipv6 ospf6 hello-interval 2
ipv6 ospf6 dead-interval 6
@ -455,13 +457,13 @@ class Ospfv3mdr(Ospfv3):
else:
return cfg
addservice(Ospfv3mdr)
class Bgp(QuaggaService):
'''' The BGP service provides interdomain routing.
Peers must be manually configured, with a full mesh for those
having the same AS number.
'''
"""
The BGP service provides interdomain routing.
Peers must be manually configured, with a full mesh for those
having the same AS number.
"""
_name = "BGP"
_startup = ()
_shutdown = ("killall bgpd", )
@ -471,7 +473,7 @@ class Bgp(QuaggaService):
_ipv6_routing = True
@classmethod
def generatequaggaconfig(cls, node):
def generatequaggaconfig(cls, node):
cfg = "!\n! BGP configuration\n!\n"
cfg += "! You should configure the AS number below,\n"
cfg += "! along with this router's peers.\n!\n"
@ -482,11 +484,11 @@ class Bgp(QuaggaService):
cfg += "! neighbor 1.2.3.4 remote-as 555\n!\n"
return cfg
addservice(Bgp)
class Rip(QuaggaService):
''' The RIP service provides IPv4 routing for wired networks.
'''
"""
The RIP service provides IPv4 routing for wired networks.
"""
_name = "RIP"
_startup = ()
_shutdown = ("killall ripd", )
@ -494,7 +496,7 @@ class Rip(QuaggaService):
_ipv4_routing = True
@classmethod
def generatequaggaconfig(cls, node):
def generatequaggaconfig(cls, node):
cfg = """\
router rip
redistribute static
@ -505,11 +507,11 @@ router rip
"""
return cfg
addservice(Rip)
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"
_startup = ()
_shutdown = ("killall ripngd", )
@ -517,7 +519,7 @@ class Ripng(QuaggaService):
_ipv6_routing = True
@classmethod
def generatequaggaconfig(cls, node):
def generatequaggaconfig(cls, node):
cfg = """\
router ripng
redistribute static
@ -528,12 +530,12 @@ router ripng
"""
return cfg
addservice(Ripng)
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.
'''
"""
_name = "Babel"
_startup = ()
_shutdown = ("killall babeld", )
@ -541,29 +543,28 @@ class Babel(QuaggaService):
_ipv6_routing = True
@classmethod
def generatequaggaconfig(cls, node):
def generatequaggaconfig(cls, node):
cfg = "router babel\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += " network %s\n" % ifc.name
cfg += " redistribute static\n redistribute connected\n"
return cfg
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
def generatequaggaifcconfig(cls, node, ifc):
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"
else:
return " babel wired\n babel split-horizon\n"
addservice(Babel)
class Xpimd(QuaggaService):
'''\
"""
PIM multicast routing based on XORP.
'''
"""
_name = 'Xpimd'
_startup = ()
_shutdown = ('killall xpimd', )
@ -571,7 +572,7 @@ class Xpimd(QuaggaService):
_ipv4_routing = True
@classmethod
def generatequaggaconfig(cls, node):
def generatequaggaconfig(cls, node):
ifname = 'eth0'
for ifc in node.netifs():
if ifc.name != 'lo':
@ -587,7 +588,17 @@ class Xpimd(QuaggaService):
return cfg
@classmethod
def generatequaggaifcconfig(cls, node, ifc):
def generatequaggaifcconfig(cls, node, ifc):
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)
'''
"""
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):
'''
'''
_name = "VPNClient"
_group = "Security"
_configs = ('vpnclient.sh', )
_configs = ('vpnclient.sh',)
_startindex = 60
_startup = ('sh vpnclient.sh',)
_shutdown = ("killall openvpn",)
_validate = ("pidof openvpn", )
_validate = ("pidof openvpn",)
_custom_needed = True
@classmethod
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 += "# 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:
cfg += open(fname, "rb").read()
except e:
print "Error opening VPN client configuration template (%s): %s" % \
(fname, e)
except IOError:
logger.exception("Error opening VPN client configuration template (%s)", fname)
return cfg
# this line is required to add the above class to the list of available services
addservice(VPNClient)
class VPNServer(CoreService):
'''
'''
_name = "VPNServer"
_group = "Security"
_configs = ('vpnserver.sh', )
_configs = ('vpnserver.sh',)
_startindex = 50
_startup = ('sh vpnserver.sh',)
_shutdown = ("killall openvpn",)
_validate = ("pidof openvpn", )
_validate = ("pidof openvpn",)
_custom_needed = True
@classmethod
def generateconfig(cls, node, filename, services):
''' Return the sample server.conf and vpnserver.sh file contents to
GUI for user customization.
'''
"""
Return the sample server.conf and vpnserver.sh file contents to
GUI for user customization.
"""
cfg = "#!/bin/sh\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:
cfg += open(fname, "rb").read()
except e:
print "Error opening VPN server configuration template (%s): %s" % \
(fname, e)
except IOError:
logger.exception("Error opening VPN server configuration template (%s)", fname)
return cfg
addservice(VPNServer)
class IPsec(CoreService):
'''
'''
_name = "IPsec"
_group = "Security"
_configs = ('ipsec.sh', )
_configs = ('ipsec.sh',)
_startindex = 60
_startup = ('sh ipsec.sh',)
_shutdown = ("killall racoon",)
@ -85,45 +77,51 @@ class IPsec(CoreService):
@classmethod
def generateconfig(cls, node, filename, services):
''' Return the ipsec.conf and racoon.conf file contents to
GUI for user customization.
'''
"""
Return the ipsec.conf and racoon.conf file contents to
GUI for user customization.
"""
cfg = "#!/bin/sh\n"
cfg += "# set up static tunnel mode security assocation for service "
cfg += "(security.py)\n"
fname = "%s/examples/services/sampleIPsec" % CORE_DATA_DIR
fname = "%s/examples/services/sampleIPsec" % constants.CORE_DATA_DIR
try:
cfg += open(fname, "rb").read()
except e:
print "Error opening IPsec configuration template (%s): %s" % \
(fname, e)
except IOError:
logger.exception("Error opening IPsec configuration template (%s)", fname)
return cfg
addservice(IPsec)
class Firewall(CoreService):
'''
'''
_name = "Firewall"
_group = "Security"
_configs = ('firewall.sh', )
_configs = ('firewall.sh',)
_startindex = 20
_startup = ('sh firewall.sh',)
_custom_needed = True
@classmethod
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 += "# 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:
cfg += open(fname, "rb").read()
except e:
print "Error opening Firewall configuration template (%s): %s" % \
(fname, e)
except IOError:
logger.exception("Error opening Firewall configuration template (%s)", fname)
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 sys import maxint
from core.service import CoreService
from core.service import ServiceManager
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'
_group = 'Utility'
_depends = ()
_dirs = ()
_configs = ('startup.sh', )
_configs = ('startup.sh',)
_startindex = maxint
_startup = ('sh startup.sh', )
_startup = ('sh startup.sh',)
_shutdown = ()
_validate = ()
@staticmethod
def isStartupService(s):
return isinstance(s, Startup) or \
(isclass(s) and issubclass(s, Startup))
def is_startup_service(s):
return isinstance(s, Startup) or (isclass(s) and issubclass(s, Startup))
@classmethod
def generateconfig(cls, node, filename, services):
@ -26,12 +30,14 @@ class Startup(CoreService):
script = '#!/bin/sh\n' \
'# auto-generated by Startup (startup.py)\n\n' \
'exec > startup.log 2>&1\n\n'
for s in sorted(services, key = lambda x: x._startindex):
if cls.isStartupService(s) or len(str(s._starttime)) > 0:
for s in sorted(services, key=lambda x: x._startindex):
if cls.is_startup_service(s) or len(str(s._starttime)) > 0:
continue
start = '\n'.join(s.getstartup(node, services))
if start:
script += start + '\n'
return script
addservice(Startup)
def load_services():
ServiceManager.add(Startup)

View file

@ -1,189 +1,185 @@
#
# 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
'''
import os
from core.service import CoreService, addservice
from core.misc.ipaddr import IPv4Prefix
from core.constants import *
UCARP_ETC="/usr/local/etc/ucarp"
class Ucarp(CoreService):
'''
'''
_name = "ucarp"
_group = "Utility"
_depends = ( )
_dirs = (UCARP_ETC, )
_configs = (UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh",)
_startindex = 65
_startup = ("sh ucarpboot.sh",)
_shutdown = ("killall ucarp", )
_validate = ("pidof ucarp", )
@classmethod
def generateconfig(cls, node, filename, services):
''' Return the default file contents
'''
if filename == cls._configs[0]:
return cls.generateUcarpConf(node, services)
elif filename == cls._configs[1]:
return cls.generateVipUp(node, services)
elif filename == cls._configs[2]:
return cls.generateVipDown(node, services)
elif filename == cls._configs[3]:
return cls.generateUcarpBoot(node, services)
else:
raise ValueError
@classmethod
def generateUcarpConf(cls, node, services):
''' Returns configuration file text.
'''
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/sh
# Location of UCARP executable
UCARP_EXEC=%s
# Location of the UCARP config directory
UCARP_CFGDIR=%s
# Logging Facility
FACILITY=daemon
# Instance ID
# Any number from 1 to 255
INSTANCE_ID=1
# Password
# Master and Backup(s) need to be the same
PASSWORD="changeme"
# The failover application address
VIRTUAL_ADDRESS=127.0.0.254
VIRTUAL_NET=8
# Interface for IP Address
INTERFACE=lo
# Maintanence address of the local machine
SOURCE_ADDRESS=127.0.0.1
# The ratio number to be considered before marking the node as dead
DEAD_RATIO=3
# UCARP base, lower number will be preferred master
# set to same to have master stay as long as possible
UCARP_BASE=1
SKEW=0
# UCARP options
# -z run shutdown script on exit
# -P force preferred master
# -n don't run down script at start up when we are backup
# -M use broadcast instead of multicast
# -S ignore interface state
OPTIONS="-z -n -M"
# Send extra parameter to down and up scripts
#XPARAM="-x <enter param here>"
XPARAM="-x ${VIRTUAL_NET}"
# The start and stop scripts
START_SCRIPT=${UCARP_CFGDIR}/default-up.sh
STOP_SCRIPT=${UCARP_CFGDIR}/default-down.sh
# These line should not need to be touched
UCARP_OPTS="$OPTIONS -b $UCARP_BASE -k $SKEW -i $INTERFACE -v $INSTANCE_ID -p $PASSWORD -u $START_SCRIPT -d $STOP_SCRIPT -a $VIRTUAL_ADDRESS -s $SOURCE_ADDRESS -f $FACILITY $XPARAM"
${UCARP_EXEC} -B ${UCARP_OPTS}
""" % (ucarp_bin, UCARP_ETC)
@classmethod
def generateUcarpBoot(cls, node, services):
''' Generate a shell script used to boot the Ucarp daemons.
'''
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/sh
# Location of the UCARP config directory
UCARP_CFGDIR=%s
chmod a+x ${UCARP_CFGDIR}/*.sh
# Start the default ucarp daemon configuration
${UCARP_CFGDIR}/default.sh
""" % (UCARP_ETC)
@classmethod
def generateVipUp(cls, node, services):
''' Generate a shell script used to start the virtual ip
'''
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/bash
# Should be invoked as "default-up.sh <dev> <ip>"
exec 2> /dev/null
IP="${2}"
NET="${3}"
if [ -z "$NET" ]; then
NET="24"
fi
/sbin/ip addr add ${IP}/${NET} dev "$1"
"""
@classmethod
def generateVipDown(cls, node, services):
''' Generate a shell script used to stop the virtual ip
'''
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/bash
# Should be invoked as "default-down.sh <dev> <ip>"
exec 2> /dev/null
IP="${2}"
NET="${3}"
if [ -z "$NET" ]; then
NET="24"
fi
/sbin/ip addr del ${IP}/${NET} dev "$1"
"""
addservice(Ucarp)
"""
ucarp.py: defines high-availability IP address controlled by ucarp
"""
from core.service import CoreService
from core.service import ServiceManager
UCARP_ETC = "/usr/local/etc/ucarp"
class Ucarp(CoreService):
_name = "ucarp"
_group = "Utility"
_depends = ( )
_dirs = (UCARP_ETC,)
_configs = (
UCARP_ETC + "/default.sh", UCARP_ETC + "/default-up.sh", UCARP_ETC + "/default-down.sh", "ucarpboot.sh",)
_startindex = 65
_startup = ("sh ucarpboot.sh",)
_shutdown = ("killall ucarp",)
_validate = ("pidof ucarp",)
@classmethod
def generateconfig(cls, node, filename, services):
"""
Return the default file contents
"""
if filename == cls._configs[0]:
return cls.generateUcarpConf(node, services)
elif filename == cls._configs[1]:
return cls.generateVipUp(node, services)
elif filename == cls._configs[2]:
return cls.generateVipDown(node, services)
elif filename == cls._configs[3]:
return cls.generateUcarpBoot(node, services)
else:
raise ValueError
@classmethod
def generateUcarpConf(cls, node, services):
"""
Returns configuration file text.
"""
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/sh
# Location of UCARP executable
UCARP_EXEC=%s
# Location of the UCARP config directory
UCARP_CFGDIR=%s
# Logging Facility
FACILITY=daemon
# Instance ID
# Any number from 1 to 255
INSTANCE_ID=1
# Password
# Master and Backup(s) need to be the same
PASSWORD="changeme"
# The failover application address
VIRTUAL_ADDRESS=127.0.0.254
VIRTUAL_NET=8
# Interface for IP Address
INTERFACE=lo
# Maintanence address of the local machine
SOURCE_ADDRESS=127.0.0.1
# The ratio number to be considered before marking the node as dead
DEAD_RATIO=3
# UCARP base, lower number will be preferred master
# set to same to have master stay as long as possible
UCARP_BASE=1
SKEW=0
# UCARP options
# -z run shutdown script on exit
# -P force preferred master
# -n don't run down script at start up when we are backup
# -M use broadcast instead of multicast
# -S ignore interface state
OPTIONS="-z -n -M"
# Send extra parameter to down and up scripts
#XPARAM="-x <enter param here>"
XPARAM="-x ${VIRTUAL_NET}"
# The start and stop scripts
START_SCRIPT=${UCARP_CFGDIR}/default-up.sh
STOP_SCRIPT=${UCARP_CFGDIR}/default-down.sh
# These line should not need to be touched
UCARP_OPTS="$OPTIONS -b $UCARP_BASE -k $SKEW -i $INTERFACE -v $INSTANCE_ID -p $PASSWORD -u $START_SCRIPT -d $STOP_SCRIPT -a $VIRTUAL_ADDRESS -s $SOURCE_ADDRESS -f $FACILITY $XPARAM"
${UCARP_EXEC} -B ${UCARP_OPTS}
""" % (ucarp_bin, UCARP_ETC)
@classmethod
def generateUcarpBoot(cls, node, services):
"""
Generate a shell script used to boot the Ucarp daemons.
"""
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/sh
# Location of the UCARP config directory
UCARP_CFGDIR=%s
chmod a+x ${UCARP_CFGDIR}/*.sh
# Start the default ucarp daemon configuration
${UCARP_CFGDIR}/default.sh
""" % UCARP_ETC
@classmethod
def generateVipUp(cls, node, services):
"""
Generate a shell script used to start the virtual ip
"""
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/bash
# Should be invoked as "default-up.sh <dev> <ip>"
exec 2> /dev/null
IP="${2}"
NET="${3}"
if [ -z "$NET" ]; then
NET="24"
fi
/sbin/ip addr add ${IP}/${NET} dev "$1"
"""
@classmethod
def generateVipDown(cls, node, services):
"""
Generate a shell script used to stop the virtual ip
"""
try:
ucarp_bin = node.session.cfg['ucarp_bin']
except KeyError:
ucarp_bin = "/usr/sbin/ucarp"
return """\
#!/bin/bash
# Should be invoked as "default-down.sh <dev> <ip>"
exec 2> /dev/null
IP="${2}"
NET="${3}"
if [ -z "$NET" ]; then
NET="24"
fi
/sbin/ip addr del ${IP}/${NET} dev "$1"
"""
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 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):
''' Parent class for utility services.
'''
"""
Parent class for utility services.
"""
_name = "UtilityProcess"
_group = "Utility"
_depends = ()
@ -29,15 +27,16 @@ class UtilService(CoreService):
_shutdown = ()
@classmethod
def generateconfig(cls, node, filename, services):
def generateconfig(cls, node, filename, services):
return ""
class IPForwardService(UtilService):
_name = "IPForward"
_configs = ("ipforward.sh", )
_configs = ("ipforward.sh",)
_startindex = 5
_startup = ("sh ipforward.sh", )
_startup = ("sh ipforward.sh",)
@classmethod
def generateconfig(cls, node, filename, services):
if os.uname()[0] == "Linux":
@ -60,13 +59,13 @@ class IPForwardService(UtilService):
%(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.default.rp_filter=0
""" % {'sysctl': SYSCTL_BIN}
""" % {'sysctl': constants.SYSCTL_BIN}
for ifc in node.netifs():
name = sysctldevname(ifc.name)
cfg += "%s -w net.ipv4.conf.%s.forwarding=1\n" % (SYSCTL_BIN, name)
name = utils.sysctldevname(ifc.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" % \
(SYSCTL_BIN, name)
cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (SYSCTL_BIN, name)
(constants.SYSCTL_BIN, name)
cfg += "%s -w net.ipv4.conf.%s.rp_filter=0\n" % (constants.SYSCTL_BIN, name)
return cfg
@classmethod
@ -78,9 +77,8 @@ class IPForwardService(UtilService):
%s -w net.inet6.ip6.forwarding=1
%s -w net.inet.icmp.bmcastecho=1
%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):
_name = "DefaultRoute"
@ -92,21 +90,21 @@ class DefaultRouteService(UtilService):
cfg = "#!/bin/sh\n"
cfg += "# auto-generated by DefaultRoute service (utility.py)\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\n".join(map(cls.addrstr, ifc.addrlist))
cfg += "\n"
return cfg
@staticmethod
def addrstr(x):
if x.find(":") >= 0:
net = IPv6Prefix(x)
net = Ipv6Prefix(x)
fam = "inet6 ::"
else:
net = IPv4Prefix(x)
net = Ipv4Prefix(x)
fam = "inet 0.0.0.0"
if net.maxaddr() == net.minaddr():
if net.max_addr() == net.min_addr():
return ""
else:
if os.uname()[0] == "Linux":
@ -115,9 +113,8 @@ class DefaultRouteService(UtilService):
rtcmd = "route add -%s" % fam
else:
raise Exception, "unknown platform"
return "%s %s" % (rtcmd, net.minaddr())
addservice(DefaultRouteService)
return "%s %s" % (rtcmd, net.min_addr())
class DefaultMulticastRouteService(UtilService):
_name = "DefaultMulticastRoute"
@ -144,8 +141,7 @@ class DefaultMulticastRouteService(UtilService):
cfg += "\n"
break
return cfg
addservice(DefaultMulticastRouteService)
class StaticRouteService(UtilService):
_name = "StaticRoute"
@ -160,23 +156,23 @@ class StaticRouteService(UtilService):
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"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\n".join(map(cls.routestr, ifc.addrlist))
cfg += "\n"
return cfg
@staticmethod
def routestr(x):
if x.find(":") >= 0:
net = IPv6Prefix(x)
net = Ipv6Prefix(x)
fam = "inet6"
dst = "3ffe:4::/64"
else:
net = IPv4Prefix(x)
net = Ipv4Prefix(x)
fam = "inet"
dst = "10.9.8.0/24"
if net.maxaddr() == net.minaddr():
if net.max_addr() == net.min_addr():
return ""
else:
if os.uname()[0] == "Linux":
@ -185,9 +181,8 @@ class StaticRouteService(UtilService):
rtcmd = "#/sbin/route add -%s %s" % (fam, dst)
else:
raise Exception, "unknown platform"
return "%s %s" % (rtcmd, net.minaddr())
return "%s %s" % (rtcmd, net.min_addr())
addservice(StaticRouteService)
class SshService(UtilService):
_name = "SSH"
@ -200,12 +195,13 @@ class SshService(UtilService):
_startup = ("sh startsshd.sh",)
_shutdown = ("killall sshd",)
_validate = ()
@classmethod
def generateconfig(cls, node, filename, services):
''' Use a startup script for launching sshd in order to wait for host
key generation.
'''
"""
Use a startup script for launching sshd in order to wait for host
key generation.
"""
if os.uname()[0] == "FreeBSD":
sshcfgdir = node.nodedir
sshstatedir = node.nodedir
@ -264,7 +260,6 @@ UsePAM yes
UseDNS no
""" % (sshcfgdir, sshstatedir, sshlibdir)
addservice(SshService)
class DhcpService(UtilService):
_name = "DHCP"
@ -273,12 +268,13 @@ class DhcpService(UtilService):
_startup = ("dhcpd",)
_shutdown = ("killall dhcpd",)
_validate = ("pidof dhcpd",)
@classmethod
def generateconfig(cls, node, filename, services):
''' Generate a dhcpd config file using the network address of
each interface.
'''
"""
Generate a dhcpd config file using the network address of
each interface.
"""
cfg = """\
# auto-generated by DHCP service (utility.py)
# NOTE: move these option lines into the desired pool { } block(s) below
@ -294,25 +290,26 @@ max-lease-time 7200;
ddns-update-style none;
"""
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\n".join(map(cls.subnetentry, ifc.addrlist))
cfg += "\n"
return cfg
@staticmethod
def subnetentry(x):
''' Generate a subnet declaration block given an IPv4 prefix string
for inclusion in the dhcpd3 config file.
'''
"""
Generate a subnet declaration block given an IPv4 prefix string
for inclusion in the dhcpd3 config file.
"""
if x.find(":") >= 0:
return ""
else:
addr = x.split("/")[0]
net = IPv4Prefix(x)
net = Ipv4Prefix(x)
# divide the address space in half
rangelow = net.addr(net.numaddr() / 2)
rangehigh = net.maxaddr()
rangelow = net.addr(net.num_addr() / 2)
rangehigh = net.max_addr()
return """
subnet %s netmask %s {
pool {
@ -321,23 +318,24 @@ subnet %s netmask %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):
''' Use a DHCP client for all interfaces for addressing.
'''
"""
Use a DHCP client for all interfaces for addressing.
"""
_name = "DHCPClient"
_configs = ("startdhcpclient.sh",)
_startup = ("sh startdhcpclient.sh",)
_shutdown = ("killall dhclient",)
_validate = ("pidof dhclient",)
@classmethod
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 += "# auto-generated by DHCPClient service (utility.py)\n"
cfg += "# uncomment this mkdir line and symlink line to enable client-"
@ -350,25 +348,26 @@ class DhcpClientService(UtilService):
cfg += "#ln -s /var/run/resolvconf/interface/%s.dhclient" % ifc.name
cfg += " /var/run/resolvconf/resolv.conf\n"
cfg += "/sbin/dhclient -nw -pf /var/run/dhclient-%s.pid" % ifc.name
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
addservice(DhcpClientService)
class FtpService(UtilService):
''' Start a vsftpd server.
'''
"""
Start a vsftpd server.
"""
_name = "FTP"
_configs = ("vsftpd.conf",)
_dirs = ("/var/run/vsftpd/empty", "/var/ftp",)
_startup = ("vsftpd ./vsftpd.conf",)
_shutdown = ("killall vsftpd",)
_validate = ("pidof vsftpd",)
@classmethod
def generateconfig(cls, node, filename, services):
''' Generate a vsftpd.conf configuration file.
'''
"""
Generate a vsftpd.conf configuration file.
"""
return """\
# vsftpd.conf auto-generated by FTP service (utility.py)
listen=YES
@ -384,26 +383,27 @@ secure_chroot_dir=/var/run/vsftpd/empty
anon_root=/var/ftp
"""
addservice(FtpService)
class HttpService(UtilService):
''' Start an apache server.
'''
"""
Start an apache server.
"""
_name = "HTTP"
_configs = ("/etc/apache2/apache2.conf", "/etc/apache2/envvars",
"/var/www/index.html",)
_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",)
_shutdown = ("apache2ctl stop",)
_validate = ("pidof apache2",)
APACHEVER22, APACHEVER24 = (22, 24)
@classmethod
def generateconfig(cls, node, filename, services):
''' Generate an apache2.conf configuration file.
'''
"""
Generate an apache2.conf configuration file.
"""
if filename == cls._configs[0]:
return cls.generateapache2conf(node, filename, services)
elif filename == cls._configs[1]:
@ -415,43 +415,45 @@ class HttpService(UtilService):
@classmethod
def detectversionfromcmd(cls):
''' Detect the apache2 version using the 'a2query' command.
'''
"""
Detect the apache2 version using the 'a2query' command.
"""
try:
status, result = cmdresult(['a2query', '-v'])
except Exception:
status, result = utils.cmdresult(['a2query', '-v'])
except subprocess.CalledProcessError:
status = -1
if status == 0 and result[:3] == '2.4':
return cls.APACHEVER24
return cls.APACHEVER22
return cls.APACHEVER22
@classmethod
def generateapache2conf(cls, node, filename, services):
lockstr = { cls.APACHEVER22:
'LockFile ${APACHE_LOCK_DIR}/accept.lock\n',
cls.APACHEVER24:
'Mutex file:${APACHE_LOCK_DIR} default\n', }
mpmstr = { cls.APACHEVER22: '', cls.APACHEVER24:
'LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n', }
lockstr = {cls.APACHEVER22:
'LockFile ${APACHE_LOCK_DIR}/accept.lock\n',
cls.APACHEVER24:
'Mutex file:${APACHE_LOCK_DIR} default\n', }
mpmstr = {cls.APACHEVER22: '', cls.APACHEVER24:
'LoadModule mpm_worker_module /usr/lib/apache2/modules/mod_mpm_worker.so\n', }
permstr = { cls.APACHEVER22:
' Order allow,deny\n Deny from all\n Satisfy all\n',
cls.APACHEVER24:
' Require all denied\n', }
permstr = {cls.APACHEVER22:
' Order allow,deny\n Deny from all\n Satisfy all\n',
cls.APACHEVER24:
' Require all denied\n', }
authstr = { cls.APACHEVER22:
'LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n',
cls.APACHEVER24:
'LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n', }
authstr = {cls.APACHEVER22:
'LoadModule authz_default_module /usr/lib/apache2/modules/mod_authz_default.so\n',
cls.APACHEVER24:
'LoadModule authz_core_module /usr/lib/apache2/modules/mod_authz_core.so\n', }
permstr2 = { cls.APACHEVER22:
'\t\tOrder allow,deny\n\t\tallow from all\n',
permstr2 = {cls.APACHEVER22:
'\t\tOrder allow,deny\n\t\tallow from all\n',
cls.APACHEVER24:
'\t\tRequire all granted\n', }
'\t\tRequire all granted\n', }
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 += """\
PidFile ${APACHE_PID_FILE}
@ -474,7 +476,7 @@ KeepAliveTimeout 5
<IfModule mpm_worker_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxClients 150
@ -484,7 +486,7 @@ KeepAliveTimeout 5
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxClients 150
@ -590,16 +592,16 @@ export LANG
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
continue
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
addservice(HttpService)
class PcapService(UtilService):
''' Pcap service for logging packets.
'''
"""
Pcap service for logging packets.
"""
_name = "pcap"
_configs = ("pcap.sh", )
_configs = ("pcap.sh",)
_dirs = ()
_startindex = 1
_startup = ("sh pcap.sh start",)
@ -609,8 +611,9 @@ class PcapService(UtilService):
@classmethod
def generateconfig(cls, node, filename, services):
''' Generate a startpcap.sh traffic logging script.
'''
"""
Generate a startpcap.sh traffic logging script.
"""
cfg = """
#!/bin/sh
# set tcpdump options here (see 'man tcpdump' for help)
@ -625,7 +628,7 @@ if [ "x$1" = "xstart" ]; then
cfg += '# '
redir = "< /dev/null"
cfg += "tcpdump ${DUMPOPTS} -w %s.%s.pcap -i %s %s &\n" % \
(node.name, ifc.name, ifc.name, redir)
(node.name, ifc.name, ifc.name, redir)
cfg += """
elif [ "x$1" = "xstop" ]; then
@ -635,7 +638,6 @@ fi;
"""
return cfg
addservice(PcapService)
class RadvdService(UtilService):
_name = "radvd"
@ -644,12 +646,13 @@ class RadvdService(UtilService):
_startup = ("radvd -C /etc/radvd/radvd.conf -m logfile -l /var/log/radvd.log",)
_shutdown = ("pkill radvd",)
_validate = ("pidof radvd",)
@classmethod
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.
'''
"""
cfg = "# auto-generated by RADVD service (utility.py)\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
@ -679,29 +682,30 @@ interface %s
""" % prefix
cfg += "};\n"
return cfg
@staticmethod
def subnetentry(x):
''' Generate a subnet declaration block given an IPv6 prefix string
for inclusion in the RADVD config file.
'''
"""
Generate a subnet declaration block given an IPv6 prefix string
for inclusion in the RADVD config file.
"""
if x.find(":") >= 0:
net = IPv6Prefix(x)
net = Ipv6Prefix(x)
return str(net)
else:
return ""
addservice(RadvdService)
class AtdService(UtilService):
''' Atd service for scheduling at jobs
'''
"""
Atd service for scheduling at jobs
"""
_name = "atd"
_configs = ("startatd.sh",)
_dirs = ("/var/spool/cron/atjobs", "/var/spool/cron/atspool")
_startup = ("sh startatd.sh", )
_shutdown = ("pkill atd", )
_startup = ("sh startatd.sh",)
_shutdown = ("pkill atd",)
@classmethod
def generateconfig(cls, node, filename, services):
return """
@ -711,14 +715,28 @@ chown -R daemon /var/spool/cron/*
chmod -R 700 /var/spool/cron/*
atd
"""
addservice(AtdService)
class UserDefinedService(UtilService):
''' Dummy service allowing customization of anything.
'''
"""
Dummy service allowing customization of anything.
"""
_name = "UserDefined"
_startindex = 50
_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.
'''
"""
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):
''' 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.
'''
"""
_name = "xorp_rtrmgr"
_group = "XORP"
_depends = ()
@ -26,15 +21,16 @@ class XorpRtrmgr(CoreService):
_configs = ("/etc/xorp/config.boot",)
_startindex = 35
_startup = ("xorp_rtrmgr -d -b %s -l /var/log/%s.log -P /var/run/%s.pid" % (_configs[0], _name, _name),)
_shutdown = ("killall xorp_rtrmgr", )
_validate = ("pidof xorp_rtrmgr", )
_shutdown = ("killall xorp_rtrmgr",)
_validate = ("pidof xorp_rtrmgr",)
@classmethod
def generateconfig(cls, node, filename, services):
''' Returns config.boot configuration file text. Other services that
depend on this will have generatexorpconfig() hooks that are
"""
Returns config.boot configuration file text. Other services that
depend on this will have generatexorpconfig() hooks that are
invoked here. Filename currently ignored.
'''
"""
cfg = "interfaces {\n"
for ifc in node.netifs():
cfg += " interface %s {\n" % ifc.name
@ -50,40 +46,40 @@ class XorpRtrmgr(CoreService):
s._depends.index(cls._name)
cfg += s.generatexorpconfig(node)
except ValueError:
pass
logger.exception("error getting value from service: %s", cls._name)
return cfg
@staticmethod
def addrstr(x):
''' helper for mapping IP addresses to XORP config statements
'''
try:
(addr, plen) = x.split("/")
except Exception:
raise ValueError, "invalid address"
"""
helper for mapping IP addresses to XORP config statements
"""
addr, plen = x.split("/")
cfg = "\t address %s {\n" % addr
cfg += "\t\tprefix-length: %s\n" % plen
cfg +="\t }\n"
cfg += "\t }\n"
return cfg
@staticmethod
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\tprefix-length: 64\n"
cfg += "\t }\n"
return cfg
addservice(XorpRtrmgr)
class XorpService(CoreService):
''' Parent class for XORP services. Defines properties and methods
common to XORP's routing daemons.
'''
"""
Parent class for XORP services. Defines properties and methods
common to XORP's routing daemons.
"""
_name = "XorpDaemon"
_group = "XORP"
_depends = ("xorp_rtrmgr", )
_depends = ("xorp_rtrmgr",)
_dirs = ()
_configs = ()
_startindex = 40
@ -93,22 +89,24 @@ class XorpService(CoreService):
@staticmethod
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 += " %s {\n" % forwarding
cfg += "\tdisable:false\n"
cfg += " }\n"
cfg += "}\n"
return cfg
@staticmethod
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 = []
for ifc in ifcs:
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
names.append(ifc.name)
names.append("register_vif")
@ -125,11 +123,11 @@ class XorpService(CoreService):
cfg += "}\n"
return cfg
@staticmethod
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-statement export-connected {\n"
cfg += "\tterm 100 {\n"
@ -143,34 +141,37 @@ class XorpService(CoreService):
@staticmethod
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():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
for a in ifc.addrlist:
if a.find(".") >= 0:
return a.split('/')[0]
#raise ValueError, "no IPv4 address found for router ID"
return a.split('/')[0]
# raise ValueError, "no IPv4 address found for router ID"
return "0.0.0.0"
@classmethod
def generateconfig(cls, node, filename, services):
def generateconfig(cls, node, filename, services):
return ""
@classmethod
def generatexorpconfig(cls, node):
def generatexorpconfig(cls, node):
return ""
class XorpOspfv2(XorpService):
''' The OSPFv2 service provides IPv4 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the
unified XORP configuration file.
'''
"""
The OSPFv2 service provides IPv4 routing for wired networks. It does
not build its own configuration file but has hooks for adding to the
unified XORP configuration file.
"""
_name = "XORP_OSPFv2"
@classmethod
def generatexorpconfig(cls, node):
def generatexorpconfig(cls, node):
cfg = cls.fea("unicast-forwarding4")
rtrid = cls.routerid(node)
cfg += "\nprotocols {\n"
@ -178,7 +179,7 @@ class XorpOspfv2(XorpService):
cfg += "\trouter-id: %s\n" % rtrid
cfg += "\tarea 0.0.0.0 {\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\t interface %s {\n" % ifc.name
cfg += "\t\tvif %s {\n" % ifc.name
@ -194,18 +195,18 @@ class XorpOspfv2(XorpService):
cfg += " }\n"
cfg += "}\n"
return cfg
addservice(XorpOspfv2)
class XorpOspfv3(XorpService):
''' The OSPFv3 service provides IPv6 routing. It does
not build its own configuration file but has hooks for adding to the
unified XORP configuration file.
'''
"""
The OSPFv3 service provides IPv6 routing. It does
not build its own configuration file but has hooks for adding to the
unified XORP configuration file.
"""
_name = "XORP_OSPFv3"
@classmethod
def generatexorpconfig(cls, node):
def generatexorpconfig(cls, node):
cfg = cls.fea("unicast-forwarding6")
rtrid = cls.routerid(node)
cfg += "\nprotocols {\n"
@ -213,7 +214,7 @@ class XorpOspfv3(XorpService):
cfg += "\trouter-id: %s\n" % rtrid
cfg += "\tarea 0.0.0.0 {\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\t interface %s {\n" % ifc.name
cfg += "\t\tvif %s {\n" % ifc.name
@ -223,15 +224,15 @@ class XorpOspfv3(XorpService):
cfg += " }\n"
cfg += "}\n"
return cfg
addservice(XorpOspfv3)
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"
_custom_needed = True
@classmethod
def generatexorpconfig(cls, node):
cfg = "/* This is a sample config that should be customized with\n"
@ -253,22 +254,23 @@ class XorpBgp(XorpService):
cfg += "}\n"
return cfg
addservice(XorpBgp)
class XorpRip(XorpService):
''' RIP IPv4 unicast routing.
'''
"""
RIP IPv4 unicast routing.
"""
_name = "XORP_RIP"
@classmethod
def generatexorpconfig(cls, node):
def generatexorpconfig(cls, node):
cfg = cls.fea("unicast-forwarding4")
cfg += cls.policyexportconnected()
cfg += "\nprotocols {\n"
cfg += " rip {\n"
cfg += "\texport: \"export-connected\"\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name
@ -284,68 +286,68 @@ class XorpRip(XorpService):
cfg += " }\n"
cfg += "}\n"
return cfg
addservice(XorpRip)
class XorpRipng(XorpService):
''' RIP NG IPv6 unicast routing.
'''
"""
RIP NG IPv6 unicast routing.
"""
_name = "XORP_RIPNG"
@classmethod
def generatexorpconfig(cls, node):
def generatexorpconfig(cls, node):
cfg = cls.fea("unicast-forwarding6")
cfg += cls.policyexportconnected()
cfg += "\nprotocols {\n"
cfg += " ripng {\n"
cfg += "\texport: \"export-connected\"\n"
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name
# for a in ifc.addrlist:
# if a.find(":") < 0:
# continue
# addr = a.split("/")[0]
# cfg += "\t\taddress %s {\n" % addr
# cfg += "\t\t disable: false\n"
# cfg += "\t\t}\n"
# for a in ifc.addrlist:
# if a.find(":") < 0:
# continue
# addr = a.split("/")[0]
# cfg += "\t\taddress %s {\n" % addr
# cfg += "\t\t disable: false\n"
# cfg += "\t\t}\n"
cfg += "\t\taddress %s {\n" % ifc.hwaddr.tolinklocal()
cfg += "\t\t disable: false\n"
cfg += "\t\t}\n"
cfg += "\t }\n"
cfg += "\t}\n"
cfg += "\t}\n"
cfg += " }\n"
cfg += "}\n"
return cfg
addservice(XorpRipng)
class XorpPimSm4(XorpService):
''' PIM Sparse Mode IPv4 multicast routing.
'''
"""
PIM Sparse Mode IPv4 multicast routing.
"""
_name = "XORP_PIMSM4"
@classmethod
def generatexorpconfig(cls, node):
def generatexorpconfig(cls, node):
cfg = cls.mfea("mfea4", node.netifs())
cfg += "\nprotocols {\n"
cfg += " igmp {\n"
names = []
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
names.append(ifc.name)
cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name
cfg += "\t\tdisable: false\n"
cfg += "\t }\n"
cfg += "\t}\n"
cfg += "\t}\n"
cfg += " }\n"
cfg += "}\n"
cfg += "\nprotocols {\n"
cfg += " pimsm4 {\n"
@ -368,46 +370,46 @@ class XorpPimSm4(XorpService):
cfg += "\t\t}\n"
cfg += "\t }\n"
cfg += "\t}\n"
cfg += " }\n"
cfg += "}\n"
cfg += "\nprotocols {\n"
cfg += " fib2mrib {\n"
cfg += "\tdisable: false\n"
cfg += " }\n"
cfg += "}\n"
return cfg
addservice(XorpPimSm4)
class XorpPimSm6(XorpService):
''' PIM Sparse Mode IPv6 multicast routing.
'''
"""
PIM Sparse Mode IPv6 multicast routing.
"""
_name = "XORP_PIMSM6"
@classmethod
def generatexorpconfig(cls, node):
def generatexorpconfig(cls, node):
cfg = cls.mfea("mfea6", node.netifs())
cfg += "\nprotocols {\n"
cfg += " mld {\n"
names = []
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
names.append(ifc.name)
cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name
cfg += "\t\tdisable: false\n"
cfg += "\t }\n"
cfg += "\t}\n"
cfg += "\t}\n"
cfg += " }\n"
cfg += "}\n"
cfg += "\nprotocols {\n"
cfg += " pimsm6 {\n"
names.append("register_vif")
for name in names:
cfg += "\tinterface %s {\n" % name
@ -427,33 +429,33 @@ class XorpPimSm6(XorpService):
cfg += "\t\t}\n"
cfg += "\t }\n"
cfg += "\t}\n"
cfg += " }\n"
cfg += "}\n"
cfg += "\nprotocols {\n"
cfg += " fib2mrib {\n"
cfg += "\tdisable: false\n"
cfg += " }\n"
cfg += "}\n"
return cfg
addservice(XorpPimSm6)
class XorpOlsr(XorpService):
''' OLSR IPv4 unicast MANET routing.
'''
"""
OLSR IPv4 unicast MANET routing.
"""
_name = "XORP_OLSR"
@classmethod
def generatexorpconfig(cls, node):
def generatexorpconfig(cls, node):
cfg = cls.fea("unicast-forwarding4")
rtrid = cls.routerid(node)
cfg += "\nprotocols {\n"
cfg += " olsr4 {\n"
cfg += "\tmain-address: %s\n" % rtrid
for ifc in node.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
if hasattr(ifc, 'control') and ifc.control is True:
continue
cfg += "\tinterface %s {\n" % ifc.name
cfg += "\t vif %s {\n" % ifc.name
@ -468,5 +470,15 @@ class XorpOlsr(XorpService):
cfg += " }\n"
cfg += "}\n"
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
'''
"""
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.coreobj import PyCoreObj, PyCoreNode, PyCoreNetIf
from core.misc.ipaddr import *
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
logger = log.get_logger(__name__)
try:
import parted
except ImportError, e:
#print "Failed to load parted Python module required by Xen support."
#print "Error was:", e
raise ImportError
except ImportError:
logger.error("failed to import parted for xen nodes")
import base64
import crypt
import subprocess
try:
import fsimage
except ImportError, e:
except ImportError:
# fix for fsimage under Ubuntu
sys.path.append("/usr/lib/xen-default/lib/python")
try:
import fsimage
except ImportError, e:
#print "Failed to load fsimage Python module required by Xen support."
#print "Error was:", e
raise ImportError
import os
import time
import shutil
import string
except ImportError:
logger.error("failed to import fsimage for xen nodes")
# XXX move these out to config file
AWK_PATH = "/bin/awk"
@ -60,11 +52,12 @@ SED_PATH = "/bin/sed"
XM_PATH = "/usr/sbin/xm"
UDEVADM_PATH = "/sbin/udevadm"
class XenVEth(PyCoreNetIf):
def __init__(self, node, name, localname, mtu = 1500, net = None,
start = True, hwaddr = None):
def __init__(self, node, name, localname, mtu=1500, net=None,
start=True, hwaddr=None):
# 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.up = False
self.hwaddr = hwaddr
@ -76,8 +69,8 @@ class XenVEth(PyCoreNetIf):
'vifname=%s' % self.localname, 'script=vif-core']
if self.hwaddr is not None:
cmd.append('mac=%s' % self.hwaddr)
check_call(cmd)
check_call([IP_BIN, "link", "set", self.localname, "up"])
subprocess.check_call(cmd)
subprocess.check_call([constants.IP_BIN, "link", "set", self.localname, "up"])
self.up = True
def shutdown(self):
@ -87,28 +80,28 @@ class XenVEth(PyCoreNetIf):
if self.hwaddr is not None:
pass
# 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.up = False
class XenNode(PyCoreNode):
apitype = coreapi.CORE_NODE_XEN
apitype = NodeTypes.XEN.value
FilesToIgnore = frozenset([
#'ipforward.sh',
# 'ipforward.sh',
'quaggaboot.sh',
])
FilesRedirection = {
'ipforward.sh' : '/core-tmp/ipforward.sh',
'ipforward.sh': '/core-tmp/ipforward.sh',
}
CmdsToIgnore = frozenset([
#'sh ipforward.sh',
#'sh quaggaboot.sh zebra',
#'sh quaggaboot.sh ospfd',
#'sh quaggaboot.sh ospf6d',
# 'sh ipforward.sh',
# 'sh quaggaboot.sh zebra',
# 'sh quaggaboot.sh ospfd',
# 'sh quaggaboot.sh ospf6d',
'killall zebra',
'killall ospfd',
'killall ospf6d',
@ -116,43 +109,39 @@ class XenNode(PyCoreNode):
])
def RedirCmd_ipforward(self):
sysctlFile = open(os.path.join(self.mountdir, self.etcdir,
'sysctl.conf'), 'a')
p1 = subprocess.Popen([AWK_PATH,
'/^\/sbin\/sysctl -w/ {print $NF}',
os.path.join(self.nodedir,
'core-tmp/ipforward.sh') ],
stdout=sysctlFile)
sysctlFile = open(os.path.join(self.mountdir, self.etcdir, 'sysctl.conf'), 'a')
p1 = subprocess.Popen([AWK_PATH, '/^\/sbin\/sysctl -w/ {print $NF}',
os.path.join(self.nodedir, 'core-tmp/ipforward.sh')], stdout=sysctlFile)
p1.wait()
sysctlFile.close()
def RedirCmd_zebra(self):
check_call([SED_PATH, '-i', '-e', 's/^zebra=no/zebra=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
subprocess.check_call([SED_PATH, '-i', '-e', 's/^zebra=no/zebra=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
def RedirCmd_ospfd(self):
check_call([SED_PATH, '-i', '-e', 's/^ospfd=no/ospfd=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
subprocess.check_call([SED_PATH, '-i', '-e', 's/^ospfd=no/ospfd=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
def RedirCmd_ospf6d(self):
check_call([SED_PATH, '-i', '-e',
's/^ospf6d=no/ospf6d=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
subprocess.check_call([SED_PATH, '-i', '-e',
's/^ospf6d=no/ospf6d=yes/',
os.path.join(self.mountdir, self.etcdir, 'quagga/daemons')])
CmdsRedirection = {
'sh ipforward.sh' : RedirCmd_ipforward,
'sh quaggaboot.sh zebra' : RedirCmd_zebra,
'sh quaggaboot.sh ospfd' : RedirCmd_ospfd,
'sh quaggaboot.sh ospf6d' : RedirCmd_ospf6d,
'sh ipforward.sh': RedirCmd_ipforward,
'sh quaggaboot.sh zebra': RedirCmd_zebra,
'sh quaggaboot.sh ospfd': RedirCmd_ospfd,
'sh quaggaboot.sh ospf6d': RedirCmd_ospf6d,
}
# CoreNode: no __init__, take from LxcNode & SimpleLxcNode
def __init__(self, session, objid = None, name = None,
nodedir = None, bootsh = "boot.sh", verbose = False,
start = True, model = None,
vgname = None, ramsize = None, disksize = None,
isofile = None):
def __init__(self, session, objid=None, name=None,
nodedir=None, bootsh="boot.sh", start=True, model=None,
vgname=None, ramsize=None, disksize=None,
isofile=None):
# SimpleLxcNode initialization
PyCoreNode.__init__(self, session = session, objid = objid, name = name,
verbose = verbose)
PyCoreNode.__init__(self, session=session, objid=objid, name=name)
self.nodedir = nodedir
self.model = model
# indicates startup() has been invoked and disk has been initialized
@ -180,36 +169,35 @@ class XenNode(PyCoreNode):
# TODO: remove this temporary hack
self.FilesRedirection['/usr/local/etc/quagga/Quagga.conf'] = \
os.path.join(self.getconfigitem('mount_path'), self.etcdir,
'quagga/Quagga.conf')
'quagga/Quagga.conf')
# LxcNode initialization
# self.makenodedir()
if self.nodedir is None:
self.nodedir = \
os.path.join(session.sessiondir, self.name + ".conf")
self.nodedir = os.path.join(session.sessiondir, self.name + ".conf")
self.mountdir = self.nodedir + self.getconfigitem('mount_path')
if not os.path.isdir(self.mountdir):
os.makedirs(self.mountdir)
self.tmpnodedir = True
else:
raise Exception, "Xen PVM node requires a temporary nodedir"
raise Exception("Xen PVM node requires a temporary nodedir")
self.tmpnodedir = False
self.bootsh = bootsh
if start:
self.startup()
def getconfigitem(self, name, default=None):
''' Configuration items come from the xen.conf file and/or input from
the GUI, and are stored in the session using the XenConfigManager
object. self.model is used to identify particular profiles
associated with a node type in the GUI.
'''
return self.session.xen.getconfigitem(name=name, model=self.model,
node=self, value=default)
"""
Configuration items come from the xen.conf file and/or input from
the GUI, and are stored in the session using the XenConfigManager
object. self.model is used to identify particular profiles
associated with a node type in the GUI.
"""
return self.session.xen.getconfigitem(name=name, model=self.model, node=self, value=default)
# from class LxcNode (also SimpleLxcNode)
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()
try:
if self.up:
@ -217,10 +205,10 @@ class XenNode(PyCoreNode):
self.createlogicalvolume()
self.createpartitions()
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'),
iso=True)
self.setrootpassword(pw = self.getconfigitem('root_password'))
self.setrootpassword(pw=self.getconfigitem('root_password'))
self.sethostname(old='UBASE', new=self.name)
self.setupssh(keypath=self.getconfigitem('ssh_key_path'))
self.createvm()
@ -230,11 +218,11 @@ class XenNode(PyCoreNode):
# from class LxcNode (also SimpleLxcNode)
def boot(self):
self.warn("XEN PVM boot() called")
logger.warn("XEN PVM boot() called")
self.lock.acquire()
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:
self.lock.release()
@ -246,18 +234,17 @@ class XenNode(PyCoreNode):
self.untarpersistent(tarname=tarname, iso=False)
try:
check_call([UMOUNT_BIN, self.mountdir])
subprocess.check_call([constants.UMOUNT_BIN, self.mountdir])
self.unmount_all(self.mountdir)
check_call([UDEVADM_PATH, 'settle'])
check_call([KPARTX_PATH, '-d', self.lvpath])
subprocess.check_call([UDEVADM_PATH, 'settle'])
subprocess.check_call([KPARTX_PATH, '-d', self.lvpath])
#time.sleep(5)
#time.sleep(1)
# time.sleep(5)
# time.sleep(1)
# unpause VM
if self.verbose:
self.warn("XEN PVM boot() unpause domU %s" % self.vmname)
mutecheck_call([XM_PATH, 'unpause', self.vmname])
logger.warn("XEN PVM boot() unpause domU %s" % self.vmname)
utils.mutecheck_call([XM_PATH, 'unpause', self.vmname])
self.booted = True
finally:
@ -265,10 +252,10 @@ class XenNode(PyCoreNode):
def validate(self):
self.session.services.validatenodeservices(self)
# from class LxcNode (also SimpleLxcNode)
def shutdown(self):
self.warn("XEN PVM shutdown() called")
logger.warn("XEN PVM shutdown() called")
if not self.up:
return
self.lock.acquire()
@ -281,27 +268,25 @@ class XenNode(PyCoreNode):
try:
# RJE XXX what to do here
if self.booted:
mutecheck_call([XM_PATH, 'destroy', self.vmname])
utils.mutecheck_call([XM_PATH, 'destroy', self.vmname])
self.booted = False
except OSError:
pass
except subprocess.CalledProcessError:
except (OSError, subprocess.CalledProcessError):
# ignore this error too, the VM may have exited already
pass
logger.exception("error during shutdown")
# discard LVM volume
lvmRemoveCount = 0
while os.path.exists(self.lvpath):
try:
check_call([UDEVADM_PATH, 'settle'])
mutecall([LVCHANGE_PATH, '-an', self.lvpath])
subprocess.check_call([UDEVADM_PATH, 'settle'])
utils.mutecall([LVCHANGE_PATH, '-an', self.lvpath])
lvmRemoveCount += 1
mutecall([LVREMOVE_PATH, '-f', self.lvpath])
utils.mutecall([LVREMOVE_PATH, '-f', self.lvpath])
except OSError:
pass
if (lvmRemoveCount > 1):
self.warn("XEN PVM shutdown() required %d lvremove " \
"executions." % lvmRemoveCount)
logger.exception("error during shutdown")
if lvmRemoveCount > 1:
logger.warn("XEN PVM shutdown() required %d lvremove executions." % lvmRemoveCount)
self._netif.clear()
del self.session
@ -313,117 +298,124 @@ class XenNode(PyCoreNode):
self.lock.release()
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):
raise Exception, "LVM volume already exists"
mutecheck_call([LVCREATE_PATH, '--size', self.disksize,
'--name', self.lvname, self.vgname])
utils.mutecheck_call([LVCREATE_PATH, '--size', self.disksize,
'--name', self.lvname, self.vgname])
def createpartitions(self):
''' Partition the LVM volume into persistent and swap partitions
using the parted module.
'''
"""
Partition the LVM volume into persistent and swap partitions
using the parted module.
"""
dev = parted.Device(path=self.lvpath)
dev.removeFromCache()
disk = parted.freshDisk(dev, 'msdos')
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,
end=(persist_size - 1) , type="ext4")
end=persist_size - 1, type="ext4")
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()
def createpartition(self, device, disk, start, end, type):
''' Create a single partition of the specified type and size and add
it to the disk object, using the parted module.
'''
"""
Create a single partition of the specified type and size and add
it to the disk object, using the parted module.
"""
geo = parted.Geometry(device=device, start=start, end=end)
fs = parted.FileSystem(type=type, geometry=geo)
part = parted.Partition(disk=disk, fs=fs, type=parted.PARTITION_NORMAL,
geometry=geo)
part = parted.Partition(disk=disk, fs=fs, type=parted.PARTITION_NORMAL, geometry=geo)
constraint = parted.Constraint(exactGeom=geo)
disk.addPartition(partition=part, constraint=constraint)
def createfilesystems(self):
''' Make an ext4 filesystem and swap space. Return the device name for
the persistent partition so we can mount it.
'''
"""
Make an ext4 filesystem and swap space. Return the device name for
the persistent partition so we can mount it.
"""
output = subprocess.Popen([KPARTX_PATH, '-l', self.lvpath],
stdout=subprocess.PIPE).communicate()[0]
lines = output.splitlines()
persistdev = '/dev/mapper/' + lines[0].strip().split(' ')[0].strip()
swapdev = '/dev/mapper/' + lines[1].strip().split(' ')[0].strip()
check_call([KPARTX_PATH, '-a', self.lvpath])
mutecheck_call([MKFSEXT4_PATH, '-L', 'persist', persistdev])
mutecheck_call([MKSWAP_PATH, '-f', '-L', 'swap', swapdev])
subprocess.check_call([KPARTX_PATH, '-a', self.lvpath])
utils.mutecheck_call([MKFSEXT4_PATH, '-L', 'persist', persistdev])
utils.mutecheck_call([MKSWAP_PATH, '-f', '-L', 'swap', swapdev])
return persistdev
def untarpersistent(self, tarname, iso):
''' Unpack a persistent template tar file to the mounted mount dir.
Uses fsimage library to read from an ISO file.
'''
tarname = tarname.replace('%h', self.name) # filename may use hostname
"""
Unpack a persistent template tar file to the mounted mount dir.
Uses fsimage library to read from an ISO file.
"""
tarname = tarname.replace('%h', self.name) # filename may use hostname
if iso:
try:
fs = fsimage.open(self.isofile, 0)
except IOError, e:
self.warn("Failed to open ISO file: %s (%s)" % (self.isofile,e))
except IOError:
logger.exception("Failed to open ISO file: %s", self.isofile)
return
try:
tardata = fs.open_file(tarname).read();
except IOError, e:
self.warn("Failed to open tar file: %s (%s)" % (tarname, e))
tardata = fs.open_file(tarname).read()
except IOError:
logger.exception("Failed to open tar file: %s", tarname)
return
finally:
del fs;
del fs
else:
try:
f = open(tarname)
tardata = f.read()
f.close()
except IOError, e:
self.warn("Failed to open tar file: %s (%s)" % (tarname, e))
except IOError:
logger.exception("Failed to open tar file: %s", tarname)
return
p = subprocess.Popen([TAR_PATH, '-C', self.mountdir, '--numeric-owner',
'-xf', '-'], stdin=subprocess.PIPE)
'-xf', '-'], stdin=subprocess.PIPE)
p.communicate(input=tardata)
p.wait()
def setrootpassword(self, pw):
''' Set the root password by updating the shadow password file that
is on the filesystem mounted in the temporary area.
'''
saltedpw = crypt.crypt(pw, '$6$'+base64.b64encode(os.urandom(12)))
check_call([SED_PATH, '-i', '-e',
'/^root:/s_^root:\([^:]*\):_root:' + saltedpw + ':_',
os.path.join(self.mountdir, self.etcdir, 'shadow')])
"""
Set the root password by updating the shadow password file that
is on the filesystem mounted in the temporary area.
"""
saltedpw = crypt.crypt(pw, '$6$' + base64.b64encode(os.urandom(12)))
subprocess.check_call([SED_PATH, '-i', '-e',
'/^root:/s_^root:\([^:]*\):_root:' + saltedpw + ':_',
os.path.join(self.mountdir, self.etcdir, 'shadow')])
def sethostname(self, old, new):
''' Set the hostname by updating the hostname and hosts files that
reside on the filesystem mounted in the temporary area.
'''
check_call([SED_PATH, '-i', '-e', 's/%s/%s/' % (old, new),
os.path.join(self.mountdir, self.etcdir, 'hostname')])
check_call([SED_PATH, '-i', '-e', 's/%s/%s/' % (old, new),
os.path.join(self.mountdir, self.etcdir, 'hosts')])
"""
Set the hostname by updating the hostname and hosts files that
reside on the filesystem mounted in the temporary area.
"""
subprocess.check_call([SED_PATH, '-i', '-e', 's/%s/%s/' % (old, new),
os.path.join(self.mountdir, self.etcdir, 'hostname')])
subprocess.check_call([SED_PATH, '-i', '-e', 's/%s/%s/' % (old, new),
os.path.join(self.mountdir, self.etcdir, 'hosts')])
def setupssh(self, keypath):
''' Configure SSH access by installing host keys and a system-wide
authorized_keys file.
'''
"""
Configure SSH access by installing host keys and a system-wide
authorized_keys file.
"""
sshdcfg = os.path.join(self.mountdir, self.etcdir, 'ssh/sshd_config')
check_call([SED_PATH, '-i', '-e',
's/PermitRootLogin no/PermitRootLogin yes/', sshdcfg])
subprocess.check_call([SED_PATH, '-i', '-e',
's/PermitRootLogin no/PermitRootLogin yes/', sshdcfg])
sshdir = os.path.join(self.getconfigitem('mount_path'), self.etcdir,
'ssh')
sshdir = sshdir.replace('/','\\/') # backslash slashes for use in sed
check_call([SED_PATH, '-i', '-e',
's/#AuthorizedKeysFile %h\/.ssh\/authorized_keys/' + \
'AuthorizedKeysFile ' + sshdir + '\/authorized_keys/',
sshdcfg])
for f in ('ssh_host_rsa_key','ssh_host_rsa_key.pub','authorized_keys'):
sshdir = sshdir.replace('/', '\\/') # backslash slashes for use in sed
subprocess.check_call([SED_PATH, '-i', '-e',
's/#AuthorizedKeysFile %h\/.ssh\/authorized_keys/' + \
'AuthorizedKeysFile ' + sshdir + '\/authorized_keys/',
sshdcfg])
for f in 'ssh_host_rsa_key', 'ssh_host_rsa_key.pub', 'authorized_keys':
src = os.path.join(keypath, f)
dst = os.path.join(self.mountdir, self.etcdir, 'ssh', f)
shutil.copy(src, dst)
@ -431,10 +423,11 @@ class XenNode(PyCoreNode):
os.chmod(dst, 0600)
def createvm(self):
''' Instantiate a *paused* domU VM
Instantiate it now, so we can add network interfaces,
pause it so we can have the filesystem open for configuration.
'''
"""
Instantiate a *paused* domU VM
Instantiate it now, so we can add network interfaces,
pause it so we can have the filesystem open for configuration.
"""
args = [XM_PATH, 'create', os.devnull, '--paused']
args.extend(['name=' + self.vmname, 'memory=' + str(self.ramsize)])
args.append('disk=tap:aio:' + self.isofile + ',hda,r')
@ -445,110 +438,109 @@ class XenNode(PyCoreNode):
for action in ('poweroff', 'reboot', 'suspend', 'crash', 'halt'):
args.append('on_%s=destroy' % action)
args.append('extra=' + self.getconfigitem('xm_create_extra'))
mutecheck_call(args)
utils.mutecheck_call(args)
# from class LxcNode
def privatedir(self, path):
#self.warn("XEN PVM privatedir() called")
# self.warn("XEN PVM privatedir() called")
# Do nothing, Xen PVM nodes are fully private
pass
# from class LxcNode
def opennodefile(self, filename, mode = "w"):
self.warn("XEN PVM opennodefile() called")
raise Exception, "Can't open VM file with opennodefile()"
def opennodefile(self, filename, mode="w"):
logger.warn("XEN PVM opennodefile() called")
raise Exception("Can't open VM file with opennodefile()")
# from class LxcNode
# 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)
if not basename:
raise ValueError, "no basename for filename: " + filename
if dirname and dirname[0] == "/":
dirname = dirname[1:]
#dirname = dirname.replace("/", ".")
# dirname = dirname.replace("/", ".")
dirname = os.path.join(self.nodedir, dirname)
if not os.path.isdir(dirname):
os.makedirs(dirname, mode = 0755)
os.makedirs(dirname, mode=0755)
hostfilename = os.path.join(dirname, basename)
return open(hostfilename, mode)
# from class LxcNode
def nodefile(self, filename, contents, mode = 0644):
def nodefile(self, filename, contents, mode=0644):
if filename in self.FilesToIgnore:
#self.warn("XEN PVM nodefile(filename=%s) ignored" % [filename])
# self.warn("XEN PVM nodefile(filename=%s) ignored" % [filename])
return
if filename in self.FilesRedirection:
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
self.warn("XEN PVM nodefile(filename=%s) called" % [filename])
logger.warn("XEN PVM nodefile(filename=%s) called" % [filename])
self.lock.acquire()
if not self.up:
self.lock.release()
raise Exception, "Can't access VM file as VM disk isn't ready"
return
raise Exception("Can't access VM file as VM disk isn't ready")
if self.booted:
self.lock.release()
raise Exception, "Can't access VM file as VM is already running"
return
raise Exception("Can't access VM file as VM is already running")
try:
f = self.openpausednodefile(filename, "w")
f.write(contents)
os.chmod(f.name, mode)
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:
self.lock.release()
# from class SimpleLxcNode
def alive(self):
# 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, ' ')
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
if cmdAsString in self.CmdsRedirection:
self.CmdsRedirection[cmdAsString](self)
return 0
self.warn("XEN PVM cmd(args=[%s]) called, but not yet implemented" % cmdAsString)
return 0
logger("XEN PVM cmd(args=[%s]) called, but not yet implemented" % cmdAsString)
return 0
def cmdresult(self, args):
cmdAsString = string.join(args, ' ')
if cmdAsString in self.CmdsToIgnore:
#self.warn("XEN PVM cmd(args=[%s]) called and ignored" % cmdAsString)
return (0, "")
self.warn("XEN PVM cmdresult(args=[%s]) called, but not yet implemented" % cmdAsString)
return (0, "")
# self.warn("XEN PVM cmd(args=[%s]) called and ignored" % cmdAsString)
return 0, ""
logger.warn("XEN PVM cmdresult(args=[%s]) called, but not yet implemented" % cmdAsString)
return 0, ""
def popen(self, 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
def icmd(self, 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
def term(self, sh = "/bin/sh"):
self.warn("XEN PVM term() called, but not yet implemented")
def term(self, sh="/bin/sh"):
logger.warn("XEN PVM term() called, but not yet implemented")
return
def termcmdstring(self, sh = "/bin/sh"):
''' We may add 'sudo' to the command string because the GUI runs as a
normal user. Use SSH if control interface is available, otherwise
use Xen console with a keymapping for easy login.
'''
def termcmdstring(self, sh="/bin/sh"):
"""
We may add 'sudo' to the command string because the GUI runs as a
normal user. Use SSH if control interface is available, otherwise
use Xen console with a keymapping for easy login.
"""
controlifc = None
for ifc in self.netifs():
if hasattr(ifc, 'control') and ifc.control == True:
@ -560,33 +552,22 @@ class XenNode(PyCoreNode):
controlip = controlifc.addrlist[0].split('/')[0]
cmd += "-e ssh root@%s" % controlip
return cmd
# otherwise use 'xm console'
#pw = self.getconfigitem('root_password')
#cmd += "-xrm 'XTerm*VT100.translations: #override <Key>F1: "
#cmd += "string(\"root\\n\") \\n <Key>F2: string(\"%s\\n\")' " % pw
# otherwise use 'xm console'
# pw = self.getconfigitem('root_password')
# cmd += "-xrm 'XTerm*VT100.translations: #override <Key>F1: "
# cmd += "string(\"root\\n\") \\n <Key>F2: string(\"%s\\n\")' " % pw
cmd += "-e sudo %s console %s" % (XM_PATH, self.vmname)
return cmd
def shcmd(self, cmdstr, sh = "/bin/sh"):
self.warn("XEN PVM shcmd(args=[%s]) called, but not yet implemented" % cmdstr)
def shcmd(self, cmdstr, sh="/bin/sh"):
logger("XEN PVM shcmd(args=[%s]) called, but not yet implemented" % cmdstr)
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):
self.warn("XEN PVM Nodes can't bind-mount filesystems")
logger.warn("XEN PVM Nodes can't bind-mount filesystems")
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):
self.lock.acquire()
@ -606,16 +587,16 @@ class XenNode(PyCoreNode):
return -1
def addnetif(self, netif, ifindex):
self.warn("XEN PVM addnetif() called")
logger.warn("XEN PVM addnetif() called")
PyCoreNode.addnetif(self, netif, ifindex)
def delnetif(self, ifindex):
self.warn("XEN PVM delnetif() called")
logger.warn("XEN PVM delnetif() called")
PyCoreNode.delnetif(self, ifindex)
def newveth(self, ifindex = None, ifname = None, net = None, hwaddr = None):
self.warn("XEN PVM newveth(ifindex=%s, ifname=%s) called" %
(ifindex, ifname))
def newveth(self, ifindex=None, ifname=None, net=None, hwaddr=None):
logger.warn("XEN PVM newveth(ifindex=%s, ifname=%s) called" %
(ifindex, ifname))
self.lock.acquire()
try:
@ -623,12 +604,12 @@ class XenNode(PyCoreNode):
ifindex = self.newifindex()
if ifname is None:
ifname = "eth%d" % ifindex
sessionid = self.session.shortsessionid()
sessionid = self.session.short_session_id()
name = "n%s.%s.%s" % (self.objid, ifindex, sessionid)
localname = "n%s.%s.%s" % (self.objid, ifname, sessionid)
ifclass = XenVEth
veth = ifclass(node = self, name = name, localname = localname,
mtu = 1500, net = net, hwaddr = hwaddr)
veth = ifclass(node=self, name=name, localname=localname,
mtu=1500, net=net, hwaddr=hwaddr)
veth.name = ifname
try:
@ -641,14 +622,14 @@ class XenNode(PyCoreNode):
finally:
self.lock.release()
def newtuntap(self, ifindex = None, ifname = None, net = None):
self.warn("XEN PVM newtuntap() called but not implemented")
def newtuntap(self, ifindex=None, ifname=None, net=None):
logger.warn("XEN PVM newtuntap() called but not implemented")
def sethwaddr(self, ifindex, addr):
self._netif[ifindex].sethwaddr(addr)
if self.up:
pass
#self.cmd([IP_BIN, "link", "set", "dev", self.ifname(ifindex),
# self.cmd([IP_BIN, "link", "set", "dev", self.ifname(ifindex),
# "address", str(addr)])
def addaddr(self, ifindex, addr):
@ -662,49 +643,49 @@ class XenNode(PyCoreNode):
try:
self._netif[ifindex].deladdr(addr)
except ValueError:
self.warn("trying to delete unknown address: %s" % addr)
logger.exception("trying to delete unknown address: %s", addr)
if self.up:
pass
# self.cmd([IP_BIN, "addr", "del", str(addr),
# "dev", self.ifname(ifindex)])
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:
if t not in self.valid_deladdrtype:
raise ValueError, "addr type must be in: " + \
" ".join(self.valid_deladdrtype)
" ".join(self.valid_deladdrtype)
for a in addr[t]:
self.deladdr(ifindex, a)
# 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
#def ifup(self, ifindex):
# def ifup(self, ifindex):
# if self.up:
# self.cmd([IP_BIN, "link", "set", self.ifname(ifindex), "up"])
def newnetif(self, net = None, addrlist = [], hwaddr = None,
ifindex = None, ifname = None):
self.warn("XEN PVM newnetif(ifindex=%s, ifname=%s) called" %
(ifindex, ifname))
def newnetif(self, net=None, addrlist=[], hwaddr=None,
ifindex=None, ifname=None):
logger.warn("XEN PVM newnetif(ifindex=%s, ifname=%s) called" %
(ifindex, ifname))
self.lock.acquire()
if not self.up:
self.lock.release()
raise Exception, "Can't access add veth as VM disk isn't ready"
return
raise Exception("Can't access add veth as VM disk isn't ready")
if self.booted:
self.lock.release()
raise Exception, "Can't access add veth as VM is already running"
return
raise Exception("Can't access add veth as VM is already running")
try:
if isinstance(net, EmaneNode):
raise Exception, "Xen PVM doesn't yet support Emane nets"
if nodeutils.is_node(net, NodeTypes.EMANE):
raise Exception("Xen PVM doesn't yet support Emane nets")
# ifindex = self.newtuntap(ifindex = ifindex, ifname = ifname,
# net = net)
@ -719,8 +700,8 @@ class XenNode(PyCoreNode):
# netif.addaddr(addr)
# return ifindex
else:
ifindex = self.newveth(ifindex = ifindex, ifname = ifname,
net = net, hwaddr = hwaddr)
ifindex = self.newveth(ifindex=ifindex, ifname=ifname,
net=net, hwaddr=hwaddr)
if net is not None:
self.attachnet(ifindex, net)
@ -728,24 +709,27 @@ class XenNode(PyCoreNode):
self.etcdir,
'udev/rules.d/70-persistent-net.rules')
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"
# 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('SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", DEVPATH=="/devices/vif-%s/?*", KERNEL=="eth*", NAME="%s"\n' % (ifindex, self.ifname(ifindex)))
f.write(
'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()
if hwaddr:
self.sethwaddr(ifindex, hwaddr)
for addr in maketuple(addrlist):
for addr in utils.maketuple(addrlist):
self.addaddr(ifindex, addr)
#self.ifup(ifindex)
# self.ifup(ifindex)
return ifindex
finally:
self.lock.release()
def connectnode(self, ifname, othernode, otherifname):
self.warn("XEN PVM connectnode() called")
logger.warn("XEN PVM connectnode() called")
# tmplen = 8
# tmp1 = "tmp." + "".join([random.choice(string.ascii_lowercase)
@ -768,21 +752,19 @@ class XenNode(PyCoreNode):
self.lock.acquire()
if not self.up:
self.lock.release()
raise Exception, "Can't access VM file as VM disk isn't ready"
return
raise Exception("Can't access VM file as VM disk isn't ready")
if self.booted:
self.lock.release()
raise Exception, "Can't access VM file as VM is already running"
return
raise Exception("Can't access VM file as VM is already running")
if filename in self.FilesToIgnore:
#self.warn("XEN PVM addfile(filename=%s) ignored" % [filename])
# self.warn("XEN PVM addfile(filename=%s) ignored" % [filename])
return
if filename in self.FilesRedirection:
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
try:
@ -794,24 +776,24 @@ class XenNode(PyCoreNode):
fout.write(contents)
os.chmod(fout.name, mode)
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:
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)
#self.shcmd(shcmd)
# self.shcmd(shcmd)
def unmount_all(self, path):
''' Namespaces inherit the host mounts, so we need to ensure that all
namespaces have unmounted our temporary mount area so that the
kpartx command will succeed.
'''
"""
Namespaces inherit the host mounts, so we need to ensure that all
namespaces have unmounted our temporary mount area so that the
kpartx command will succeed.
"""
# 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):
continue
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
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.
Node-specific config: XenConfigManager.configs[nodenumber] = (type, values)
The node having this specific number has this config.
'''
"""
import sys, os, threading, subprocess, time, string
import ConfigParser
from xml.dom.minidom import parseString, Document
from core.constants import *
import os
import string
from core import constants
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):
''' Xen controller object. Lives in a Session instance and is used for
building Xen profiles.
'''
_name = "xen"
_type = coreapi.CORE_TLV_REG_EMULSRV
"""
Xen controller object. Lives in a Session instance and is used for
building Xen profiles.
"""
name = "xen"
config_type = RegisterTlvs.EMULATION_SERVER.value
def __init__(self, session):
ConfigurableManager.__init__(self, session)
self.verbose = self.session.getcfgitembool('verbose', False)
self.default_config = XenDefaultConfig(session, objid=None)
"""
Creates a XenConfigManager instance.
: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()
def setconfig(self, nodenum, conftype, values):
''' add configuration values for a node to a dictionary; values are
usually received from a Configuration Message, and may refer to a
node for which no object exists yet
'''
if nodenum is None:
nodenum = 0 # used for storing the global default config
"""
add configuration values for a node to a dictionary; values are
usually received from a Configuration Message, and may refer to a
node for which no object exists yet
"""
if nodenum is None:
nodenum = 0 # used for storing the global default config
return ConfigurableManager.setconfig(self, nodenum, conftype, values)
def getconfig(self, nodenum, conftype, defaultvalues):
''' get configuration values for a node; if the values don't exist in
our dictionary then return the default values supplied; if conftype
is None then we return a match on any conftype.
'''
if nodenum is None:
nodenum = 0 # used for storing the global default config
return ConfigurableManager.getconfig(self, nodenum, conftype,
defaultvalues)
"""
get configuration values for a node; if the values don't exist in
our dictionary then return the default values supplied; if conftype
is None then we return a match on any conftype.
"""
if nodenum is None:
nodenum = 0 # used for storing the global default config
return ConfigurableManager.getconfig(self, nodenum, conftype, defaultvalues)
def clearconfig(self, nodenum):
''' remove configuration values for a node
'''
"""
remove configuration values for a node
"""
ConfigurableManager.clearconfig(self, nodenum)
if 0 in self.configs:
self.configs.pop(0)
def configure(self, session, msg):
''' Handle configuration messages for global Xen config.
'''
return self.default_config.configure(self, msg)
def configure(self, session, config_data):
"""
Handle configuration messages for global Xen config.
: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):
''' 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:
filename = os.path.join(CORE_CONF_DIR, 'xen.conf')
filename = os.path.join(constants.CORE_CONF_DIR, 'xen.conf')
cfg = ConfigParser.SafeConfigParser()
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
section = "xen"
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
self.configfile = dict(cfg.items(section))
# populate default config items from config file entries
@ -92,13 +106,14 @@ class XenConfigManager(ConfigurableManager):
if names[i] in self.configfile:
vals[i] = self.configfile[names[i]]
# 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):
''' Get a config item of the given name, first looking for node-specific
configuration, then model specific, and finally global defaults.
If a value is supplied, it will override any stored config.
'''
"""
Get a config item of the given name, first looking for node-specific
configuration, then model specific, and finally global defaults.
If a value is supplied, it will override any stored config.
"""
if value is not None:
return value
n = None
@ -111,8 +126,8 @@ class XenConfigManager(ConfigurableManager):
defaultvalues=None)
if v is None:
# get item from default config for the machine type
(t, v) = self.getconfig(nodenum=None,
conftype=self.default_config._name,
(t, v) = self.getconfig(nodenum=None,
conftype=self.default_config.name,
defaultvalues=None)
confignames = self.default_config.getnames()
@ -124,142 +139,136 @@ class XenConfigManager(ConfigurableManager):
if name in self.configfile:
return self.configfile[name]
else:
#self.warn("missing config item '%s'" % name)
# logger.warn("missing config item '%s'" % name)
return None
class XenConfig(Configurable):
''' Manage Xen configuration profiles.
'''
@classmethod
def configure(cls, xen, msg):
''' Handle configuration messages for setting up a model.
Similar to Configurable.configure(), but considers opaque data
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)
"""
Manage Xen configuration profiles.
"""
nodetype = objname
@classmethod
def configure(cls, xen, config_data):
"""
Handle configuration messages for setting up a model.
Similar to Configurable.configure(), but considers opaque data
for indicating node types.
: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:
opaque_items = opaque.split(':')
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
nodetype = opaque_items[1]
if xen.verbose:
xen.info("received configure message for %s" % nodetype)
if conftype == coreapi.CONF_TYPE_FLAGS_REQUEST:
if xen.verbose:
xen.info("replying to configure request for %s " % nodetype)
logger.info("received configure message for %s", nodetype)
if config_type == ConfigFlags.REQUEST.value:
logger.info("replying to configure request for %s " % nodetype)
# 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
# reply with the defaults for this model
if objname == "all":
typeflags = coreapi.CONF_TYPE_FLAGS_UPDATE
if object_name == "all":
typeflags = ConfigFlags.UPDATE.value
else:
typeflags = coreapi.CONF_TYPE_FLAGS_NONE
values = xen.getconfig(nodenum, nodetype, defaultvalues=None)[1]
typeflags = ConfigFlags.NONE.value
values = xen.getconfig(node_id, nodetype, defaultvalues=None)[1]
if values is None:
# 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()
values = xen.getconfig(nodenum, cls._name, defaults)[1]
values = xen.getconfig(node_id, cls.name, defaults)[1]
if values is None:
return None
# reply with config options
if nodenum is None:
nodenum = 0
reply = cls.toconfmsg(0, nodenum, typeflags, nodetype, values)
elif conftype == coreapi.CONF_TYPE_FLAGS_RESET:
if objname == "all":
xen.clearconfig(nodenum)
#elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE:
if node_id is None:
node_id = 0
reply = cls.config_data(0, node_id, typeflags, nodetype, values)
elif config_type == ConfigFlags.RESET.value:
if object_name == "all":
xen.clearconfig(node_id)
# elif conftype == coreapi.CONF_TYPE_FLAGS_UPDATE:
else:
# store the configuration values for later use, when the XenNode
# object has been created
if objname is None:
xen.info("no configuration object for node %s" % nodenum)
if object_name is None:
logger.info("no configuration object for node %s" % node_id)
return None
values_str = msg.gettlv(coreapi.CORE_TLV_CONF_VALUES)
if values_str is None:
# use default or preconfigured values
defaults = cls.getdefaultvalues()
values = xen.getconfig(nodenum, cls._name, defaults)[1]
values = xen.getconfig(node_id, cls.name, defaults)[1]
else:
# use new values supplied from the conf message
values = values_str.split('|')
xen.setconfig(nodenum, nodetype, values)
xen.setconfig(node_id, nodetype, values)
return reply
@classmethod
def toconfmsg(cls, flags, nodenum, typeflags, nodetype, values):
''' Convert this class to a Config API message. Some TLVs are defined
by the class, but node number, conf type flags, and values must
be passed in.
'''
def config_data(cls, flags, node_id, type_flags, nodetype, values):
"""
Convert this class to a Config API message. Some TLVs are defined
by the class, but node number, conf type flags, and values must
be passed in.
"""
values_str = string.join(values, '|')
tlvdata = ""
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_NODE, nodenum)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OBJ,
cls._name)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_TYPE,
typeflags)
datatypes = tuple( map(lambda x: x[1], cls._confmatrix) )
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_DATA_TYPES,
datatypes)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_VALUES,
values_str)
captions = reduce( lambda a,b: a + '|' + b, \
map(lambda x: x[4], cls._confmatrix))
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)
opaque = "%s:%s" % (cls._name, nodetype)
tlvdata += coreapi.CoreConfTlv.pack(coreapi.CORE_TLV_CONF_OPAQUE,
opaque)
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.NODE.value, node_id)
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.OBJECT.value, cls.name)
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.TYPE.value, type_flags)
datatypes = tuple(map(lambda x: x[1], cls.config_matrix))
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.DATA_TYPES.value, datatypes)
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.VALUES.value, values_str)
captions = reduce(lambda a, b: a + '|' + b, map(lambda x: x[4], cls.config_matrix))
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.CAPTIONS, captions)
possiblevals = reduce(lambda a, b: a + '|' + b, map(lambda x: x[3], cls.config_matrix))
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.POSSIBLE_VALUES.value, possiblevals)
if cls.bitmap is not None:
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.BITMAP.value, cls.bitmap)
if cls.config_groups is not None:
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.GROUPS.value, cls.config_groups)
opaque = "%s:%s" % (cls.name, nodetype)
tlvdata += coreapi.CoreConfigTlv.pack(ConfigTlvs.OPAQUE.value, opaque)
msg = coreapi.CoreConfMessage.pack(flags, tlvdata)
return msg
class XenDefaultConfig(XenConfig):
''' Global default Xen configuration options.
'''
_name = "xen"
"""
Global default Xen configuration options.
"""
name = "xen"
# Configuration items:
# ('name', 'type', 'default', 'possible-value-list', 'caption')
_confmatrix = [
('ram_size', coreapi.CONF_DATA_TYPE_STRING, '256', '',
config_matrix = [
('ram_size', ConfigDataTypes.STRING.value, '256', '',
'ram size (MB)'),
('disk_size', coreapi.CONF_DATA_TYPE_STRING, '256M', '',
('disk_size', ConfigDataTypes.STRING.value, '256M', '',
'disk size (use K/M/G suffix)'),
('iso_file', coreapi.CONF_DATA_TYPE_STRING, '', '',
('iso_file', ConfigDataTypes.STRING.value, '', '',
'iso file'),
('mount_path', coreapi.CONF_DATA_TYPE_STRING, '', '',
('mount_path', ConfigDataTypes.STRING.value, '', '',
'mount path'),
('etc_path', coreapi.CONF_DATA_TYPE_STRING, '', '',
('etc_path', ConfigDataTypes.STRING.value, '', '',
'etc path'),
('persist_tar_iso', coreapi.CONF_DATA_TYPE_STRING, '', '',
('persist_tar_iso', ConfigDataTypes.STRING.value, '', '',
'iso persist tar file'),
('persist_tar', coreapi.CONF_DATA_TYPE_STRING, '', '',
('persist_tar', ConfigDataTypes.STRING.value, '', '',
'persist tar file'),
('root_password', coreapi.CONF_DATA_TYPE_STRING, 'password', '',
('root_password', ConfigDataTypes.STRING.value, 'password', '',
'root password'),
]
_confgroups = "domU properties:1-%d" % len(_confmatrix)
]
config_groups = "domU properties:1-%d" % len(config_matrix)