quick base to try and help flesh out documentation under core.*

This commit is contained in:
Blake J. Harnden 2017-05-03 09:30:49 -07:00
parent 8f45e5c4da
commit 4ae7958a63
15 changed files with 1956 additions and 292 deletions

View file

@ -6,19 +6,12 @@ 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.
"""

View file

@ -44,20 +44,46 @@ class CoreTlvData(object):
@classmethod
def pack(cls, value):
"""
Convenience method for packing data using the struct module.
:param value: value to pack
:return: length of data and the packed data itself
:rtype: tuple
"""
data = struct.pack(cls.data_format, value)
length = len(data) - cls.pad_len
return length, data
@classmethod
def unpack(cls, data):
"""
Convenience method for unpacking data using the struct module.
:param data: data to unpack
:return: the value of the unpacked data
"""
return struct.unpack(cls.data_format, data)[0]
@classmethod
def pack_string(cls, value):
"""
Convenience method for packing data from a string representation.
:param str value: value to pack
:return: length of data and the packed data itself
:rtype: tuple
"""
return cls.pack(cls.from_string(value))
@classmethod
def from_string(cls, value):
"""
Retrieve the value type from a string representation.
:param str value: value to get a data type from
:return: value parse from string representation
"""
return cls.data_type(value)
@ -68,20 +94,45 @@ class CoreTlvDataObj(CoreTlvData):
@classmethod
def pack(cls, obj):
data = struct.pack(cls.data_format, cls.get_value(obj))
length = len(data) - cls.pad_len
return length, data
"""
Convenience method for packing custom object data.
:param obj: custom object to pack
:return: length of data and the packed data itself
:rtype: tuple
"""
value = cls.get_value(obj)
return super(CoreTlvDataObj, cls).pack(value)
@classmethod
def unpack(cls, data):
return cls.new_obj(struct.unpack(cls.data_format, data)[0])
"""
Convenience method for unpacking custom object data.
:param data: data to unpack custom object from
:return: unpacked custom object
"""
data = cls.new_obj(data)
return super(CoreTlvDataObj, cls).unpack(data)
@staticmethod
def get_value(obj):
"""
Method that will be used to retrieve the data to pack from a custom object.
:param obj: custom object to get data to pack
:return: data value to pack
"""
raise NotImplementedError
@staticmethod
def new_obj(obj):
"""
Method for retrieving data to unpack from an object.
:param obj: object to get unpack data from
:return: value of unpacked data
"""
raise NotImplementedError
@ -120,6 +171,13 @@ class CoreTlvDataString(CoreTlvData):
@classmethod
def pack(cls, value):
"""
Convenience method for packing string data.
:param str value: string to pack
:return: length of data packed and the packed data
:rtype: tuple
"""
if not isinstance(value, str):
raise ValueError("value not a string: %s" % value)
@ -129,11 +187,17 @@ class CoreTlvDataString(CoreTlvData):
header_len = CoreTlv.long_header_len
pad_len = -(header_len + len(value)) % 4
return len(value), value + '\0' * pad_len
return len(value), value + "\0" * pad_len
@classmethod
def unpack(cls, data):
return data.rstrip('\0')
"""
Convenience method for unpacking string data.
:param str data: unpack string data
:return: unpacked string data
"""
return data.rstrip("\0")
class CoreTlvDataUint16List(CoreTlvData):
@ -145,6 +209,13 @@ class CoreTlvDataUint16List(CoreTlvData):
@classmethod
def pack(cls, values):
"""
Convenience method for packing a uint 16 list.
:param list values: unint 16 list to pack
:return: length of data packed and the packed data
:rtype: tuple
"""
if not isinstance(values, tuple):
raise ValueError("value not a tuple: %s" % values)
@ -157,59 +228,123 @@ class CoreTlvDataUint16List(CoreTlvData):
@classmethod
def unpack(cls, data):
"""
Convenience method for unpacking a uint 16 list.
:param data: data to unpack
:return: unpacked data
"""
data_format = "!%dH" % (len(data) / 2)
return struct.unpack(data_format, data)
@classmethod
def from_string(cls, value):
"""
Retrieves a unint 16 list from a string
:param str value: string representation of a uint 16 list
:return: unint 16 list
:rtype: list
"""
return tuple(map(lambda (x): int(x), value.split()))
class CoreTlvDataIpv4Addr(CoreTlvDataObj):
"""
Utility class for packing/unpacking Ipv4 addresses.
"""
data_type = IpAddress.from_string
data_format = "!2x4s"
pad_len = 2
@staticmethod
def get_value(obj):
"""
Retrieve Ipv4 address value from object.
:param core.misc.ipaddress.IpAddress obj: ip address to get value from
:return:
"""
return obj.addr
@staticmethod
def new_obj(value):
"""
Retrieve Ipv4 address from a string representation.
:param str value: value to get Ipv4 address from
:return: Ipv4 address
:rtype: core.misc.ipaddress.IpAddress
"""
return IpAddress(af=socket.AF_INET, address=value)
class CoreTlvDataIPv6Addr(CoreTlvDataObj):
"""
Utility class for packing/unpacking Ipv6 addresses.
"""
data_format = "!16s2x"
data_type = IpAddress.from_string
pad_len = 2
@staticmethod
def get_value(obj):
"""
Retrieve Ipv6 address value from object.
:param core.misc.ipaddress.IpAddress obj: ip address to get value from
:return:
"""
return obj.addr
@staticmethod
def new_obj(value):
"""
Retrieve Ipv6 address from a string representation.
:param str value: value to get Ipv4 address from
:return: Ipv4 address
:rtype: core.misc.ipaddress.IpAddress
"""
return IpAddress(af=socket.AF_INET6, address=value)
class CoreTlvDataMacAddr(CoreTlvDataObj):
"""
Utility class for packing/unpacking mac addresses.
"""
data_format = "!2x8s"
data_type = MacAddress.from_string
pad_len = 2
@staticmethod
def get_value(obj):
"""
Retrieve Ipv6 address value from object.
:param core.misc.ipaddress.MacAddress obj: mac address to get value from
:return:
"""
# extend to 64 bits
return '\0\0' + obj.addr
return "\0\0" + obj.addr
@staticmethod
def new_obj(value):
"""
Retrieve mac address from a string representation.
:param str value: value to get Ipv4 address from
:return: Ipv4 address
:rtype: core.misc.ipaddress.MacAddress
"""
# only use 48 bits
return MacAddress(address=value[2:])
class CoreTlv(object):
"""
Base class for representing CORE TLVs.
"""
header_format = "!BB"
header_len = struct.calcsize(header_format)
@ -220,6 +355,13 @@ class CoreTlv(object):
tlv_data_class_map = {}
def __init__(self, tlv_type, tlv_data):
"""
Create a CoreTlv instance.
:param int tlv_type: tlv type
:param tlv_data: data to unpack
:return: unpacked data
"""
self.tlv_type = tlv_type
if tlv_data:
try:
@ -232,7 +374,10 @@ class CoreTlv(object):
@classmethod
def unpack(cls, data):
"""
parse data and return unpacked class.
Parse data and return unpacked class.
:param data: data to unpack
:return: unpacked data class
"""
tlv_type, tlv_len = struct.unpack(cls.header_format, data[:cls.header_len])
header_len = cls.header_len
@ -240,11 +385,19 @@ class CoreTlv(object):
tlv_type, zero, tlv_len = struct.unpack(cls.long_header_format, data[:cls.long_header_len])
header_len = cls.long_header_len
tlv_size = header_len + tlv_len
tlv_size += -tlv_size % 4 # for 32-bit alignment
# for 32-bit alignment
tlv_size += -tlv_size % 4
return cls(tlv_type, data[header_len:tlv_size]), data[tlv_size:]
@classmethod
def pack(cls, tlv_type, value):
"""
Pack a TLV value, based on type.
:param int tlv_type: type of data to pack
:param value: data to pack
:return: header and packed data
"""
tlv_len, tlv_data = cls.tlv_data_class_map[tlv_type].pack(value)
if tlv_len < 256:
@ -256,19 +409,42 @@ class CoreTlv(object):
@classmethod
def pack_string(cls, tlv_type, value):
"""
Pack data type from a string representation
:param int tlv_type: type of data to pack
:param str value: string representation of data
:return: header and packed data
"""
return cls.pack(tlv_type, cls.tlv_data_class_map[tlv_type].from_string(value))
def type_str(self):
"""
Retrieve type string for this data type.
:return: data type name
:rtype: str
"""
try:
return self.tlv_type_map(self.tlv_type).name
except ValueError:
return "unknown tlv type: %s" % str(self.tlv_type)
def __str__(self):
"""
String representation of this data type.
:return: string representation
:rtype: str
"""
return "%s <tlvtype = %s, value = %s>" % (self.__class__.__name__, self.type_str(), self.value)
class CoreNodeTlv(CoreTlv):
"""
Class for representing CORE Node TLVs.
"""
tlv_type_map = NodeTlvs
tlv_data_class_map = {
NodeTlvs.NUMBER.value: CoreTlvDataUint32,
@ -295,6 +471,10 @@ class CoreNodeTlv(CoreTlv):
class CoreLinkTlv(CoreTlv):
"""
Class for representing CORE link TLVs.
"""
tlv_type_map = LinkTlvs
tlv_data_class_map = {
LinkTlvs.N1_NUMBER.value: CoreTlvDataUint32,
@ -333,6 +513,10 @@ class CoreLinkTlv(CoreTlv):
class CoreExecuteTlv(CoreTlv):
"""
Class for representing CORE execute TLVs.
"""
tlv_type_map = ExecuteTlvs
tlv_data_class_map = {
ExecuteTlvs.NODE.value: CoreTlvDataUint32,
@ -346,6 +530,10 @@ class CoreExecuteTlv(CoreTlv):
class CoreRegisterTlv(CoreTlv):
"""
Class for representing CORE register TLVs.
"""
tlv_type_map = RegisterTlvs
tlv_data_class_map = {
RegisterTlvs.WIRELESS.value: CoreTlvDataString,
@ -359,6 +547,10 @@ class CoreRegisterTlv(CoreTlv):
class CoreConfigTlv(CoreTlv):
"""
Class for representing CORE configuration TLVs.
"""
tlv_type_map = ConfigTlvs
tlv_data_class_map = {
ConfigTlvs.NODE.value: CoreTlvDataUint32,
@ -378,6 +570,10 @@ class CoreConfigTlv(CoreTlv):
class CoreFileTlv(CoreTlv):
"""
Class for representing CORE file TLVs.
"""
tlv_type_map = FileTlvs
tlv_data_class_map = {
FileTlvs.NODE.value: CoreTlvDataUint32,
@ -393,6 +589,10 @@ class CoreFileTlv(CoreTlv):
class CoreInterfaceTlv(CoreTlv):
"""
Class for representing CORE interface TLVs.
"""
tlv_type_map = InterfaceTlvs
tlv_data_class_map = {
InterfaceTlvs.NODE.value: CoreTlvDataUint32,
@ -412,6 +612,10 @@ class CoreInterfaceTlv(CoreTlv):
class CoreEventTlv(CoreTlv):
"""
Class for representing CORE event TLVs.
"""
tlv_type_map = EventTlvs
tlv_data_class_map = {
EventTlvs.NODE.value: CoreTlvDataUint32,
@ -424,6 +628,10 @@ class CoreEventTlv(CoreTlv):
class CoreSessionTlv(CoreTlv):
"""
Class for representing CORE session TLVs.
"""
tlv_type_map = SessionTlvs
tlv_data_class_map = {
SessionTlvs.NUMBER.value: CoreTlvDataString,
@ -438,6 +646,10 @@ class CoreSessionTlv(CoreTlv):
class CoreExceptionTlv(CoreTlv):
"""
Class for representing CORE exception TLVs.
"""
tlv_type_map = ExceptionTlvs
tlv_data_class_map = {
ExceptionTlvs.NODE.value: CoreTlvDataUint32,
@ -451,6 +663,10 @@ class CoreExceptionTlv(CoreTlv):
class CoreMessage(object):
"""
Base class for representing CORE messages.
"""
header_format = "!BBH"
header_len = struct.calcsize(header_format)
message_type = None
@ -477,19 +693,45 @@ class CoreMessage(object):
@classmethod
def pack(cls, message_flags, tlv_data):
"""
Pack CORE message data.
:param message_flags: message flags to pack with data
:param tlv_data: data to get length from for packing
:return: combined header and tlv data
"""
header = struct.pack(cls.header_format, cls.message_type, message_flags, len(tlv_data))
return header + tlv_data
def add_tlv_data(self, key, value):
"""
Add TLV data into the data map.
:param int key: key to store TLV data
:param value: data to associate with key
:return: nothing
"""
if key in self.tlv_data:
raise KeyError("key already exists: %s (val=%s)" % (key, value))
self.tlv_data[key] = value
def get_tlv(self, tlv_type):
"""
Retrieve TLV data from data map.
:param int tlv_type: type of data to retrieve
:return: TLV type data
"""
return self.tlv_data.get(tlv_type)
def parse_data(self, data):
"""
Parse data while possible and adding TLV data to the data map.
:param data: data to parse for TLV data
:return: nothing
"""
while data:
tlv, data = self.tlv_class.unpack(data)
self.add_tlv_data(tlv.tlv_type, tlv.value)
@ -522,12 +764,24 @@ class CoreMessage(object):
self.raw_message = self.pack(self.flags, tlv_data)
def type_str(self):
"""
Retrieve data of the message type.
:return: name of message type
:rtype: str
"""
try:
return MessageTypes(self.message_type).name
except ValueError:
return "unknown message type: %s" % str(self.message_type)
def flag_str(self):
"""
Retrieve message flag string.
:return: message flag string
:rtype: str
"""
message_flags = []
flag = 1L
@ -544,6 +798,12 @@ class CoreMessage(object):
return "0x%x <%s>" % (self.flags, " | ".join(message_flags))
def __str__(self):
"""
Retrieve string representation of the message.
:return: string representation
:rtype: str
"""
result = "%s <msgtype = %s, flags = %s>" % (self.__class__.__name__, self.type_str(), self.flag_str())
for key, value in self.tlv_data.iteritems():
@ -612,55 +872,86 @@ class CoreMessage(object):
class CoreNodeMessage(CoreMessage):
"""
CORE node message class.
"""
message_type = MessageTypes.NODE.value
tlv_class = CoreNodeTlv
class CoreLinkMessage(CoreMessage):
"""
CORE link message class.
"""
message_type = MessageTypes.LINK.value
tlv_class = CoreLinkTlv
class CoreExecMessage(CoreMessage):
"""
CORE execute message class.
"""
message_type = MessageTypes.EXECUTE.value
tlv_class = CoreExecuteTlv
class CoreRegMessage(CoreMessage):
"""
CORE register message class.
"""
message_type = MessageTypes.REGISTER.value
tlv_class = CoreRegisterTlv
class CoreConfMessage(CoreMessage):
"""
CORE configuration message class.
"""
message_type = MessageTypes.CONFIG.value
tlv_class = CoreConfigTlv
class CoreFileMessage(CoreMessage):
"""
CORE file message class.
"""
message_type = MessageTypes.FILE.value
tlv_class = CoreFileTlv
class CoreIfaceMessage(CoreMessage):
"""
CORE interface message class.
"""
message_type = MessageTypes.INTERFACE.value
tlv_class = CoreInterfaceTlv
class CoreEventMessage(CoreMessage):
"""
CORE event message class.
"""
message_type = MessageTypes.EVENT.value
tlv_class = CoreEventTlv
class CoreSessionMessage(CoreMessage):
"""
CORE session message class.
"""
message_type = MessageTypes.SESSION.value
tlv_class = CoreSessionTlv
class CoreExceptionMessage(CoreMessage):
"""
CORE exception message class.
"""
message_type = MessageTypes.EXCEPTION.value
tlv_class = CoreExceptionTlv
# map used to translate enumerated message type values to message class objects
CLASS_MAP = {
MessageTypes.NODE.value: CoreNodeMessage,
MessageTypes.LINK.value: CoreLinkMessage,

View file

@ -1,6 +1,7 @@
"""
Converts CORE data objects into legacy API messages.
"""
from core.api import coreapi
from core.enumerations import NodeTlvs
from core.misc import log

View file

@ -1,9 +1,7 @@
"""
broker.py: definition of CoreBroker class that is part of the
pycore session object. Handles distributing parts of the emulation out to
other emulation servers. The broker is consulted during the
CoreRequestHandler.handlemsg() loop to determine if messages should be handled
locally or forwarded on to another emulation server.
Broker class that is part of the session object. Handles distributing parts of the emulation out to
other emulation servers. The broker is consulted when handling messages to determine if messages
should be handled locally or forwarded on to another emulation server.
"""
import os
@ -40,7 +38,17 @@ logger = log.get_logger(__name__)
# TODO: name conflict with main core server, probably should rename
class CoreServer(object):
"""
Reptesents CORE daemon servers for communication.
"""
def __init__(self, name, host, port):
"""
Creates a CoreServer instance.
:param str name: name of the CORE server
:param str host: server address
:param int port: server port
"""
self.name = name
self.host = host
self.port = port
@ -48,17 +56,28 @@ class CoreServer(object):
self.instantiation_complete = False
def connect(self):
"""
Connect to CORE server and save connection.
:return: nothing
"""
assert self.sock is None
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
sock.connect((self.host, self.port))
except:
except IOError as e:
sock.close()
raise
raise e
self.sock = sock
def close(self):
"""
Close connection with CORE server.
:return: nothing
"""
if self.sock is not None:
self.sock.close()
self.sock = None
@ -66,9 +85,13 @@ class CoreServer(object):
class CoreBroker(ConfigurableManager):
"""
Member of pycore session class for handling global emulation server data.
Helps with brokering messages between CORE daemon servers.
"""
# configurable manager name
name = "broker"
# configurable manager type
config_type = RegisterTlvs.UTILITY.value
def __init__(self, session):
@ -104,6 +127,7 @@ class CoreBroker(ConfigurableManager):
self.tunnels = {}
self.dorecvloop = False
self.recvthread = None
self.bootcount = 0
def startup(self):
"""
@ -151,7 +175,7 @@ class CoreBroker(ConfigurableManager):
def startrecvloop(self):
"""
Spawn the recvloop() thread if it hasn"t been already started.
Spawn the receive loop for receiving messages.
"""
if self.recvthread is not None:
if self.recvthread.isAlive():
@ -166,7 +190,7 @@ class CoreBroker(ConfigurableManager):
def recvloop(self):
"""
Thread target that receives messages from server sockets.
Receive loop for receiving messages from server sockets.
"""
self.dorecvloop = True
# note: this loop continues after emulation is stopped,
@ -250,9 +274,13 @@ class CoreBroker(ConfigurableManager):
def addserver(self, name, host, port):
"""
Add a new server, and try to connect to it. If we"re already
connected to this (host, port), then leave it alone. When host,port
is None, do not try to connect.
Add a new server, and try to connect to it. If we"re already connected to this
(host, port), then leave it alone. When host,port is None, do not try to connect.
:param str name: name of server
:param str host: server address
:param int port: server port
:return: nothing
"""
with self.servers_lock:
server = self.servers.get(name)
@ -282,14 +310,15 @@ class CoreBroker(ConfigurableManager):
Remove a server and hang up any connection.
:param CoreServer server: server to delete
:return:
:return: nothing
"""
with self.servers_lock:
try:
s = self.servers.pop(server.name)
assert s == server
except KeyError:
pass
logger.exception("error deleting server")
if server.sock is not None:
logger.info("closing connection with %s @ %s:%s" % (server.name, server.host, server.port))
server.close()
@ -322,6 +351,9 @@ class CoreBroker(ConfigurableManager):
def getservers(self):
"""
Return a list of servers sorted by name.
:return: sorted server list
:rtype: list
"""
with self.servers_lock:
return sorted(self.servers.values(), key=lambda x: x.name)
@ -329,6 +361,9 @@ class CoreBroker(ConfigurableManager):
def getservernames(self):
"""
Return a sorted list of server names (keys from self.servers).
:return: sorted server names
:rtype: list
"""
with self.servers_lock:
return sorted(self.servers.keys())
@ -338,6 +373,11 @@ class CoreBroker(ConfigurableManager):
Compute a 32-bit key used to uniquely identify a GRE tunnel.
The hash(n1num), hash(n2num) values are used, so node numbers may be
None or string values (used for e.g. "ctrlnet").
:param int n1num: node one id
:param int n2num: node two id
:return: tunnel key for the node pair
:rtype: int
"""
sid = self.session_id_master
if sid is None:
@ -349,7 +389,13 @@ class CoreBroker(ConfigurableManager):
def addtunnel(self, remoteip, n1num, n2num, localnum):
"""
Add a new GreTapBridge between nodes on two different machines.
Adds a new GreTapBridge between nodes on two different machines.
:param str remoteip: remote address for tunnel
:param int n1num: node one id
:param int n2num: node two id
:param int localnum: local id
:return: nothing
"""
key = self.tunnelkey(n1num, n2num)
if localnum == n2num:
@ -380,27 +426,37 @@ class CoreBroker(ConfigurableManager):
for n in self.network_nodes:
self.addnettunnel(n)
def addnettunnel(self, n):
def addnettunnel(self, node):
"""
Add network tunnel between node and broker.
:param node: node to add network tunnel to
:return: list of grep taps
:rtype: list
"""
try:
net = self.session.get_object(n)
net = self.session.get_object(node)
except KeyError:
raise KeyError("network node %s not found" % n)
raise KeyError("network node %s not found" % node)
# add other nets here that do not require tunnels
if nodeutils.is_node(net, NodeTypes.EMANE_NET):
return None
if nodeutils.is_node(net, NodeTypes.CONTROL_NET):
if hasattr(net, "serverintf"):
if net.serverintf is not None:
return None
servers = self.getserversbynode(n)
server_interface = getattr(net, "serverintf", None)
if nodeutils.is_node(net, NodeTypes.CONTROL_NET) and server_interface is not None:
return None
servers = self.getserversbynode(node)
if len(servers) < 2:
return None
hosts = []
for server in servers:
if server.host is None:
continue
hosts.append(server.host)
if len(hosts) == 0 and self.session_handler.client_address != "":
# get IP address from API message sender (master)
hosts.append(self.session_handler.client_address[0])
@ -413,21 +469,26 @@ class CoreBroker(ConfigurableManager):
else:
# we are the session master
myip = host
key = self.tunnelkey(n, IpAddress.to_int(myip))
key = self.tunnelkey(node, IpAddress.to_int(myip))
if key in self.tunnels.keys():
continue
logger.info("Adding tunnel for net %s to %s with key %s" % (n, host, key))
logger.info("Adding tunnel for net %s to %s with key %s" % (node, host, key))
gt = GreTap(node=None, name=None, session=self.session, remoteip=host, key=key)
self.tunnels[key] = gt
r.append(gt)
# attaching to net will later allow gt to be destroyed
# during net.shutdown()
net.attach(gt)
return r
def deltunnel(self, n1num, n2num):
"""
Cleanup of the GreTapBridge.
Delete tunnel between nodes.
:param int n1num: node one id
:param int n2num: node two id
:return: nothing
"""
key = self.tunnelkey(n1num, n2num)
try:
@ -441,6 +502,10 @@ class CoreBroker(ConfigurableManager):
def gettunnel(self, n1num, n2num):
"""
Return the GreTap between two nodes if it exists.
:param int n1num: node one id
:param int n2num: node two id
:return: gre tap between nodes or none
"""
key = self.tunnelkey(n1num, n2num)
if key in self.tunnels.keys():
@ -451,6 +516,10 @@ class CoreBroker(ConfigurableManager):
def addnodemap(self, server, nodenum):
"""
Record a node number to emulation server mapping.
:param CoreServer server: core server to associate node with
:param int nodenum: node id
:return: nothing
"""
with self.nodemap_lock:
if nodenum in self.nodemap:
@ -459,6 +528,7 @@ class CoreBroker(ConfigurableManager):
self.nodemap[nodenum].add(server)
else:
self.nodemap[nodenum] = {server}
if server in self.nodecounts:
self.nodecounts[server] += 1
else:
@ -468,21 +538,32 @@ class CoreBroker(ConfigurableManager):
"""
Remove a node number to emulation server mapping.
Return the number of nodes left on this server.
:param CoreServer server: server to remove from node map
:param int nodenum: node id
:return: number of nodes left on server
:rtype: int
"""
count = None
with self.nodemap_lock:
if nodenum not in self.nodemap:
return count
self.nodemap[nodenum].remove(server)
if server in self.nodecounts:
count = self.nodecounts[server]
count -= 1
self.nodecounts[server] = count
return count
def getserversbynode(self, nodenum):
"""
Retrieve a set of emulation servers given a node number.
:param int nodenum: node id
:return: core server associated with node
:rtype: set
"""
with self.nodemap_lock:
if nodenum not in self.nodemap:
@ -492,12 +573,18 @@ class CoreBroker(ConfigurableManager):
def addnet(self, nodenum):
"""
Add a node number to the list of link-layer nodes.
:param int nodenum: node id to add
:return: nothing
"""
self.network_nodes.add(nodenum)
def addphys(self, nodenum):
"""
Add a node number to the list of physical nodes.
:param int nodenum: node id to add
:return: nothing
"""
self.physical_nodes.add(nodenum)
@ -507,7 +594,7 @@ class CoreBroker(ConfigurableManager):
arrive and require the use of nodecounts.
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:return: None
:return: nothing
"""
return None
@ -517,7 +604,7 @@ class CoreBroker(ConfigurableManager):
combinations that we"ll need to connect with.
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:return: None
:return: nothing
"""
values = config_data.data_values
session_id = config_data.session
@ -614,8 +701,10 @@ class CoreBroker(ConfigurableManager):
def setupserver(self, servername):
"""
Send the appropriate API messages for configuring the specified
emulation server.
Send the appropriate API messages for configuring the specified emulation server.
:param str servername: name of server to configure
:return: nothing
"""
server = self.getserverbyname(servername)
if server is None:
@ -647,6 +736,11 @@ class CoreBroker(ConfigurableManager):
"""
When an interactive TTY request comes from the GUI, snoop the reply
and add an SSH command to the appropriate remote server.
:param msghdr: message header
:param msgdata: message data
:param str host: host address
:return: packed core execute tlv data
"""
msgtype, msgflags, msglen = coreapi.CoreMessage.unpack_header(msghdr)
msgcls = coreapi.CLASS_MAP[msgtype]
@ -674,13 +768,16 @@ class CoreBroker(ConfigurableManager):
nodes to servers.
:param core.api.coreapi.CoreMessage message: message to handle
:return:
:return: boolean for handling locally and set of servers
:rtype: tuple
"""
servers = set()
handle_locally = False
serverfiletxt = None
# snoop Node Message for emulation server TLV and record mapping
n = message.tlv_data[NodeTlvs.NUMBER.value]
# replicate link-layer nodes on all servers
nodetype = message.get_tlv(NodeTlvs.TYPE.value)
if nodetype is not None:
@ -720,9 +817,11 @@ class CoreBroker(ConfigurableManager):
servers.add(server)
if serverfiletxt and self.session.master:
self.writenodeserver(serverfiletxt, server)
# hook to update coordinates of physical nodes
if n in self.physical_nodes:
self.session.mobility.physnodeupdateposition(message)
return handle_locally, servers
def handlelinkmsg(self, message):
@ -732,7 +831,8 @@ class CoreBroker(ConfigurableManager):
opaque data to the link message before forwarding.
:param core.api.coreapi.CoreMessage message: message to handle
:return:
:return: boolean to handle locally, a set of server, and message
:rtype: tuple
"""
servers = set()
handle_locally = False
@ -799,7 +899,8 @@ class CoreBroker(ConfigurableManager):
:param core.api.coreapi.CoreMessage message: message to link end points
:param servers1:
:param servers2:
:return:
:return: core link message
:rtype: coreapi.CoreLinkMessage
"""
ip1 = ""
for server in servers1:
@ -823,6 +924,11 @@ class CoreBroker(ConfigurableManager):
and we need to determine the tunnel endpoint. First look for
opaque data in the link message, otherwise use the IP of the message
sender (the master server).
:param coreapi.CoreLinkMessage msg:
:param bool first_is_local: is first local
:return: host address
:rtype: str
"""
host = None
opaque = msg.get_tlv(LinkTlvs.OPAQUE.value)
@ -840,7 +946,11 @@ class CoreBroker(ConfigurableManager):
def handlerawmsg(self, msg):
"""
Helper to invoke handlemsg() using raw (packed) message bytes.
Helper to invoke message handler, using raw (packed) message bytes.
:param msg: raw message butes
:return: should handle locally or not
:rtype: bool
"""
hdr = msg[:coreapi.CoreMessage.header_len]
msgtype, flags, msglen = coreapi.CoreMessage.unpack_header(hdr)
@ -856,7 +966,8 @@ class CoreBroker(ConfigurableManager):
:param core.api.coreapi.CoreMessage message: message to forward
:param list servers: server to forward message to
:return:
:return: handle locally value
:rtype: bool
"""
handle_locally = len(servers) == 0
for server in servers:
@ -874,6 +985,8 @@ class CoreBroker(ConfigurableManager):
"""
Write the server list to a text file in the session directory upon
startup: /tmp/pycore.nnnnn/servers
:return: nothing
"""
servers = self.getservers()
filename = os.path.join(self.session.session_dir, "servers")
@ -900,6 +1013,10 @@ class CoreBroker(ConfigurableManager):
and server info. This may be used by scripts for accessing nodes on
other machines, much like local nodes may be accessed via the
VnodeClient class.
:param str nodestr: node string
:param CoreServer server: core server
:return: nothing
"""
serverstr = "%s %s %s" % (server.name, server.host, server.port)
name = nodestr.split()[1]
@ -920,6 +1037,8 @@ class CoreBroker(ConfigurableManager):
def local_instantiation_complete(self):
"""
Set the local server"s instantiation-complete status to True.
:return: nothing
"""
# TODO: do we really want to allow a localhost to not exist?
with self.servers_lock:
@ -937,6 +1056,9 @@ class CoreBroker(ConfigurableManager):
"""
Return True if all servers have completed instantiation, False
otherwise.
:return: have all server completed instantiation
:rtype: bool
"""
with self.servers_lock:
for server in self.servers.itervalues():

View file

@ -1,5 +1,5 @@
"""
conf.py: common support for configurable objects
Common support for configurable CORE objects.
"""
import string
@ -34,6 +34,9 @@ class ConfigurableManager(object):
# configurable key=values, indexed by node number
self.configs = {}
# 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
@ -45,6 +48,7 @@ class ConfigurableManager(object):
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
"""
@ -89,10 +93,10 @@ class ConfigurableManager(object):
'key=value' strings from a values field. The key name must be
in the keys list, and target.key=value is set.
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
: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: None
:return: nothing
"""
values = config_data.data_values
@ -117,13 +121,23 @@ class ConfigurableManager(object):
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
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 = []
@ -145,8 +159,14 @@ class ConfigurableManager(object):
def getconfig(self, nodenum, conftype, defaultvalues):
"""
get configuration values for a node; if the values don't exist in
Get configuration values for a node; if the values don't exist in
our dictionary then return the default values supplied
: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)
@ -163,6 +183,10 @@ class ConfigurableManager(object):
"""
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:
@ -176,6 +200,9 @@ class ConfigurableManager(object):
"""
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 = {}
@ -185,7 +212,12 @@ class ConfigurableManager(object):
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:
logger.warn("Unknown model type '%s'" % conftype)
@ -209,6 +241,10 @@ class ConfigurableManager(object):
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:
@ -239,29 +275,57 @@ class Configurable(object):
:param core.session.Session session: session for this configurable
:param object_id:
:return:
"""
self.session = session
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):
"""
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):
"""
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, manager, config_data):
"""
Handle configuration messages for this object.
:param ConfigurableManager manager: configuration manager
:param config_data: configuration data
:return: configuration data object
:rtype: ConfigData
"""
reply = None
node_id = config_data.node
@ -330,6 +394,13 @@ class Configurable(object):
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)
@ -355,6 +426,10 @@ class Configurable(object):
def booltooffon(value):
"""
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"
@ -363,6 +438,13 @@ class Configurable(object):
@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
@ -375,6 +457,10 @@ class Configurable(object):
"""
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.config_matrix[i][1] == ConfigDataTypes.BOOL.value and values[i] != "":
@ -387,6 +473,10 @@ class Configurable(object):
"""
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
@ -398,10 +488,16 @@ class Configurable(object):
def getkeyvaluelist(self):
"""
Helper to return a list of (key, value) tuples. Keys come from
self._confmatrix and values are instance attributes.
configuration matrix and values are instance attributes.
:return: tuples of key value pairs
:rtype: list
"""
r = []
for k in self.getnames():
if hasattr(self, k):
r.append((k, getattr(self, k)))
return r
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

@ -57,6 +57,14 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
"""
def __init__(self, request, client_address, server):
"""
Create a CoreRequestHandler instance.
:param request: request object
:param str client_address: client address
:param CoreServer server: core server instance
:return:
"""
self.done = False
self.message_handlers = {
MessageTypes.NODE.value: self.handle_node_message,
@ -93,6 +101,8 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def setup(self):
"""
Client has connected, set up a new connection.
:return: nothing
"""
logger.info("new TCP connection: %s", self.client_address)
# self.register()
@ -101,6 +111,8 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
"""
Client has disconnected, end this request handler and disconnect
from the session. Shutdown sessions that are not running.
:return: nothing
"""
logger.info("finishing request handler")
self.done = True
@ -322,6 +334,8 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def register(self):
"""
Return a Register Message
:return: register message data
"""
logger.info("GUI has connected to session %d at %s", self.session.session_id, time.ctime())
@ -341,14 +355,19 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
"""
Send raw data to the other end of this TCP connection
using socket"s sendall().
:param data: data to send over request socket
:return: data sent
"""
return self.request.sendall(data)
def receive_message(self):
"""
Receive data and return a CORE API message object.
"""
:return: received message
:rtype: coreapi.CoreMessage
"""
try:
header = self.request.recv(coreapi.CoreMessage.header_len)
if len(header) > 0:
@ -389,6 +408,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def queue_message(self, message):
"""
Queue an API message for later processing.
:param message: message to queue
:return: nothing
"""
logger.info("queueing msg (queuedtimes = %s): type %s",
message.queuedtimes, MessageTypes(message.message_type))
@ -399,6 +421,8 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
CORE API message handling loop that is spawned for each server
thread; get CORE API messages from the incoming message queue,
and call handlemsg() for processing.
:return: nothing
"""
while not self.done:
try:
@ -411,6 +435,8 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
"""
Handle an incoming message; dispatch based on message type,
optionally sending replies.
:return: nothing
"""
if self.session and self.session.broker.handle_message(message):
logger.info("%s forwarding message:\n%s", threading.currentThread().getName(), message)
@ -436,6 +462,10 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def dispatch_replies(self, replies, message):
"""
Dispatch replies by CORE to message msg previously received from the client.
:param replies: reply messages to dispatch
:param message: message for replies
:return: nothing
"""
for reply in replies:
message_type, message_flags, message_length = coreapi.CoreMessage.unpack_header(reply)
@ -462,6 +492,8 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
Handle a new connection request from a client. Dispatch to the
recvmsg() method for receiving data into CORE API messages, and
add them to an incoming message queue.
:return: nothing
"""
# use port as session id
port = self.request.getpeername()[1]
@ -505,6 +537,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_node_message(self, message):
"""
Node Message handler
:param coreapi.CoreNodeMessage message: node message
:return: replies to node message
"""
replies = []
if message.flags & MessageFlags.ADD.value and message.flags & MessageFlags.DELETE.value:
@ -634,6 +669,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_link_message(self, message):
"""
Link Message handler
:param coreapi.CoreLinkMessage message: link message to handle
:return: link message replies
"""
# get node classes
ptp_class = nodeutils.get_node_class(NodeTypes.PEER_TO_PEER)
@ -988,6 +1026,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_execute_message(self, message):
"""
Execute Message handler
:param coreapi.CoreExecMessage message: execute message to handle
:return: reply messages
"""
node_num = message.get_tlv(ExecuteTlvs.NODE.value)
execute_num = message.get_tlv(ExecuteTlvs.NUMBER.value)
@ -1060,6 +1101,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_register_message(self, message):
"""
Register Message Handler
:param coreapi.CoreRegMessage message: register message to handle
:return: reply messages
"""
replies = []
@ -1093,7 +1137,8 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
)
thread.daemon = True
thread.start()
time.sleep(0.25) # allow time for session creation
# allow time for session creation
time.sleep(0.25)
if message.flags & MessageFlags.STRING.value:
new_session_ids = set(server.get_session_ids())
new_sid = new_session_ids.difference(old_session_ids)
@ -1145,8 +1190,10 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_config_message(self, message):
"""
Configuration Message handler
"""
:param coreapi.CoreConfMessage message: configuration message to handle
:return: reply messages
"""
# convert config message to standard config data object
config_data = ConfigData(
node=message.get_tlv(ConfigTlvs.NODE.value),
@ -1174,6 +1221,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_file_message(self, message):
"""
File Message handler
:param coreapi.CoreFileMessage message: file message to handle
:return: reply messages
"""
if message.flags & MessageFlags.ADD.value:
node_num = message.get_tlv(NodeTlvs.NUMBER.value)
@ -1231,6 +1281,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_interface_message(self, message):
"""
Interface Message handler
:param message: interface message to handle
:return: reply messages
"""
logger.info("ignoring Interface message")
return ()
@ -1238,6 +1291,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_event_message(self, message):
"""
Event Message handler
:param coreapi.CoreEventMessage message: event message to handle
:return: reply messages
"""
event_data = EventData(
node=message.get_tlv(EventTlvs.NODE.value),
@ -1350,6 +1406,9 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def handle_session_message(self, message):
"""
Session Message handler
:param coreapi.CoreSessionMessage message: session message to handle
:return: reply messages
"""
session_id_str = message.get_tlv(SessionTlvs.NUMBER.value)
name_str = message.get_tlv(SessionTlvs.NAME.value)
@ -1436,10 +1495,11 @@ class CoreRequestHandler(SocketServer.BaseRequestHandler):
def send_node_emulation_id(self, node_id):
"""
Send back node messages to the GUI for node messages that had
the status request flag.
"""
Node emulation id to send.
:param int node_id: node id to send
:return: nothing
"""
if node_id in self.node_status_request:
tlv_data = ""
tlv_data += coreapi.CoreNodeTlv.pack(NodeTlvs.NUMBER.value, node_id)
@ -1462,6 +1522,13 @@ class CoreDatagramRequestHandler(CoreRequestHandler):
"""
def __init__(self, request, client_address, server):
"""
Create a CoreDatagramRequestHandler instance.
:param request: request object
:param str client_address: client address
:param CoreServer server: core server instance
"""
# TODO: decide which messages cannot be handled with connectionless UDP
self.message_handlers = {
MessageTypes.NODE.value: self.handle_node_message,
@ -1482,19 +1549,33 @@ class CoreDatagramRequestHandler(CoreRequestHandler):
def setup(self):
"""
Client has connected, set up a new connection.
:return: nothing
"""
logger.info("new UDP connection: %s:%s" % self.client_address)
def handle(self):
"""
Receive a message.
:return: nothing
"""
self.receive_message()
def finish(self):
"""
Handle the finish state of a client.
:return: nothing
"""
return SocketServer.BaseRequestHandler.finish(self)
def receive_message(self):
"""
Receive data, parse a CoreMessage and queue it onto an existing
session handler"s queue, if available.
:return: nothing
"""
data = self.request[0]
sock = self.request[1]
@ -1550,12 +1631,16 @@ class CoreDatagramRequestHandler(CoreRequestHandler):
def queue_message(self, message):
"""
UDP handlers are short-lived and do not have message queues.
:return: nothing
"""
raise Exception("Unable to queue %s message for later processing using UDP!" % message.type_str())
def sendall(self, data):
"""
Use sendto() on the connectionless UDP socket.
:return: nothing
"""
self.request[1].sendto(data, self.client_address)
@ -1568,6 +1653,13 @@ class BaseAuxRequestHandler(CoreRequestHandler):
"""
def __init__(self, request, client_address, server):
"""
Create a BaseAuxRequestHandler instance.
:param request: request client
:param str client_address: client address
:param CoreServer server: core server instance
"""
self.message_handlers = {
MessageTypes.NODE.value: self.handle_node_message,
MessageTypes.LINK.value: self.handle_link_message,
@ -1588,12 +1680,16 @@ class BaseAuxRequestHandler(CoreRequestHandler):
def setup(self):
"""
New client has connected to the auxiliary server.
:return: nothing
"""
logger.info("new auxiliary server client: %s:%s" % self.client_address)
def handle(self):
"""
The handler main loop
:return: nothing
"""
port = self.request.getpeername()[1]
self.session = self.server.mainserver.create_session(session_id=port)
@ -1616,6 +1712,8 @@ class BaseAuxRequestHandler(CoreRequestHandler):
def finish(self):
"""
Disconnect the client
:return: nothing
"""
if self.session:
self.session.event_handlers.remove(self.handle_broadcast_event)
@ -1639,6 +1737,7 @@ class BaseAuxRequestHandler(CoreRequestHandler):
EXAMPLE:
return self.handler.request.recv(siz)
:return: nothing
"""
raise NotImplemented
@ -1662,6 +1761,7 @@ class BaseAuxRequestHandler(CoreRequestHandler):
logger.info("-"*60)
raise e
:return: nothing
"""
raise NotImplemented
@ -1683,5 +1783,7 @@ class BaseAuxRequestHandler(CoreRequestHandler):
traceback.print_exc(file=sys.stdout)
logger.info("-"*60)
raise e
:return: nothing
"""
raise NotImplemented

View file

@ -1,6 +1,6 @@
"""
coreobj.py: defines the basic objects for emulation: the PyCoreObj base class,
along with PyCoreNode, PyCoreNet, and PyCoreNetIf
Defines the basic objects for CORE emulation: the PyCoreObj base class, along with PyCoreNode,
PyCoreNet, and PyCoreNetIf.
"""
import os
@ -23,14 +23,27 @@ class Position(object):
"""
def __init__(self, x=None, y=None, z=None):
self.x = None
self.y = None
self.z = None
self.set(x, y, z)
"""
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
@ -41,18 +54,32 @@ class Position(object):
def get(self):
"""
Fetch the (x,y,z) position tuple.
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
# 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.get_object_id()
@ -71,33 +98,57 @@ class PyCoreObj(object):
def startup(self):
"""
Each object implements its own startup method.
:return: nothing
"""
raise NotImplementedError
def shutdown(self):
"""
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.
: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: 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()))
@ -107,16 +158,34 @@ class PyCoreObj(object):
def numnetif(self):
"""
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
@ -171,102 +240,184 @@ class PyCoreObj(object):
def all_link_data(self, flags):
"""
Build CORE API Link Messages for this object. There is no default
Build CORE Link data for this object. There is no default
method for PyCoreObjs as PyCoreNodes do not implement this but
PyCoreNets do.
:param flags: message flags
:return: list of link data
:rtype: link
"""
return []
class PyCoreNode(PyCoreObj):
"""
Base class for nodes
Base class for CORE nodes.
"""
# TODO: start seems like it should go away
def __init__(self, session, objid=None, name=None, start=True):
"""
Initialization for node objects.
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):
"""
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.session_dir, 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)
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
# 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)
"""
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).
: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
"""
r = []
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):
@ -275,15 +426,27 @@ class PyCoreNet(PyCoreObj):
"""
linktype = LinkTypes.WIRED.value
# TODO: remove start if appropriate
def __init__(self, session, objid, name, start=True):
"""
Initialization for network objects.
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
@ -291,22 +454,31 @@ class PyCoreNet(PyCoreObj):
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')
delay = netif.getparam("delay")
bw = netif.getparam("bw")
loss = netif.getparam("loss")
duplicate = netif.getparam("duplicate")
jitter = netif.getparam("jitter")
tlvdata = ""
if delay is not None:
@ -319,12 +491,13 @@ class PyCoreNet(PyCoreObj):
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.DUP.value, str(duplicate))
if jitter is not None:
tlvdata += coreapi.CoreLinkTlv.pack(LinkTlvs.JITTER.value, jitter)
return tlvdata
def all_link_data(self, flags):
"""
Build CORE API Link Messages for this network. Each link message
describes a link between this network and a node.
Build link data objects for this network. Each link object describes a link
between this network and a node.
"""
all_links = []
@ -415,10 +588,18 @@ class PyCoreNet(PyCoreObj):
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)):
@ -437,68 +618,114 @@ class PyCoreNetIf(object):
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 str 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.
Retrieve a parameter from the, or None if the parameter does not exist.
:param key: parameter to get value for
:return: parameter value
"""
if key not in self._params:
return None
return self._params[key]
return self._params.get(key)
def getparams(self):
"""
Return (key, value) pairs from the _params dict.
Return (key, value) pairs for parameters.
"""
r = []
parameters = []
for k in sorted(self._params.keys()):
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.
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
"""
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
# 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):
@ -508,7 +735,12 @@ class PyCoreNetIf(object):
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)

View file

@ -1,5 +1,6 @@
"""
Defines server classes and request handlers for TCP and UDP. Also defined here is a TCP based auxiliary server class for supporting externally defined handlers.
Defines server classes and request handlers for TCP and UDP. Also defined here is a TCP based
auxiliary server class for supporting externally defined handlers.
"""
import SocketServer
@ -48,6 +49,7 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
@classmethod
def add_server(cls, server):
"""
Add a core server to the known servers set.
:param CoreServer server: server to add
:return: nothing
@ -57,9 +59,10 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
@classmethod
def remove_server(cls, server):
"""
Remove a core server from the known servers set.
:param CoreServer server: server to remove
:return:
:return: nothing
"""
try:
cls.servers.remove(server)
@ -67,6 +70,11 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
logger.exception("error removing server: %s", server)
def shutdown(self):
"""
Shutdown the server, all known sessions, and remove server from known servers set.
:return: nothing
"""
# shutdown all known sessions
for session in self.sessions.values():
session.shutdown()
@ -103,6 +111,7 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
:param core.session.Session session: session to remove
:return: removed session
:rtype: core.session.Session
"""
with self._sessions_lock:
if session.session_id not in self.sessions:
@ -115,6 +124,9 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def get_session_ids(self):
"""
Return a list of active session numbers.
:return: known session ids
:rtype: list
"""
with self._sessions_lock:
session_ids = self.sessions.keys()
@ -178,6 +190,9 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def to_session_message(self, flags=0):
"""
Build CORE API Sessions message based on current session info.
:param int flags: message flags
:return: session message
"""
id_list = []
name_list = []
@ -242,7 +257,7 @@ class CoreServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
def dump_sessions(self):
"""
Debug print all session info.
Log currently known session information.
"""
logger.info("sessions:")
with self._sessions_lock:
@ -277,6 +292,10 @@ class CoreUdpServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
"""
Server class initialization takes configuration data and calls
the SocketServer constructor
:param str server_address: server address
:param class handler_class: class for handling requests
:param main_server: main server to associate with
"""
self.mainserver = main_server
SocketServer.UDPServer.__init__(self, server_address, handler_class)
@ -284,6 +303,8 @@ class CoreUdpServer(SocketServer.ThreadingMixIn, SocketServer.UDPServer):
def start(self):
"""
Thread target to run concurrently with the TCP server.
:return: nothing
"""
self.serve_forever()
@ -296,18 +317,49 @@ class CoreAuxServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
allow_reuse_address = True
def __init__(self, server_address, handler_class, main_server):
"""
Create a CoreAuxServer instance.
:param str server_address: server address
:param class handler_class: class for handling requests
:param main_server: main server to associate with
"""
self.mainserver = main_server
logger.info("auxiliary server started, listening on: %s", server_address)
SocketServer.TCPServer.__init__(self, server_address, handler_class)
def start(self):
"""
Start the core auxiliary server.
:return: nothing
"""
self.serve_forever()
def set_session_master(self, handler):
"""
Set the session master handler.
:param func handler: session master handler
:return:
"""
return self.mainserver.set_session_master(handler)
def get_session(self, session_id=None):
"""
Retrieve a session.
:param int session_id: id of session to retrieve
:return: core.session.Session
"""
return self.mainserver.get_session(session_id)
def to_session_message(self, flags=0):
"""
Retrieve a session message.
:param flags: message flags
:return: session message
"""
return self.mainserver.to_session_message(flags)

View file

@ -29,12 +29,15 @@ class CoreLocation(ConfigurableManager):
"""
Creates a MobilityManager instance.
:param core.session.Session session: session this manager is tied to
: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
@ -85,6 +88,9 @@ class CoreLocation(ConfigurableManager):
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
@ -93,6 +99,9 @@ class CoreLocation(ConfigurableManager):
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
@ -102,10 +111,15 @@ class CoreLocation(ConfigurableManager):
"""
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)
e, n, zonen, zonel = utm.from_latlon(lat, lon)
self.refutm = ((zonen, zonel), e, n, alt)
def getgeo(self, x, y, z):
@ -113,9 +127,15 @@ class CoreLocation(ConfigurableManager):
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]
@ -124,7 +144,7 @@ class CoreLocation(ConfigurableManager):
# 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)
@ -133,7 +153,7 @@ class CoreLocation(ConfigurableManager):
lat, lon = utm.to_latlon(e, n, zone[0], zone[1])
except utm.OutOfRangeError:
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]
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
@ -145,6 +165,12 @@ class CoreLocation(ConfigurableManager):
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)
@ -175,17 +201,25 @@ class CoreLocation(ConfigurableManager):
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
return self.zoneshifts[z][0]
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
# 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
@ -203,18 +237,25 @@ class CoreLocation(ConfigurableManager):
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
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
# 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)
@ -231,6 +272,11 @@ class CoreLocation(ConfigurableManager):
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

View file

@ -39,7 +39,6 @@ class MobilityManager(ConfigurableManager):
Creates a MobilityManager instance.
:param core.session.Session session: session this manager is tied to
:return: nothing
"""
ConfigurableManager.__init__(self)
self.session = session
@ -59,6 +58,9 @@ class MobilityManager(ConfigurableManager):
"""
Session is transitioning from instantiation to runtime state.
Instantiate any mobility models that have been configured for a WLAN.
:param list node_ids: node ids to startup
:return: nothing
"""
if node_ids is None:
node_ids = self.configs.keys()
@ -96,12 +98,19 @@ class MobilityManager(ConfigurableManager):
def reset(self):
"""
Reset all configs.
:return: nothing
"""
self.clearconfig(nodenum=None)
def setconfig(self, node_id, config_type, values):
"""
Normal setconfig() with check for run-time updates for WLANs.
:param int node_id: node id
:param config_type: configuration type
:param values: configuration value
:return: nothing
"""
super(MobilityManager, self).setconfig(node_id, config_type, values)
if self.session is None:
@ -117,6 +126,9 @@ class MobilityManager(ConfigurableManager):
"""
Handle an Event Message used to start, stop, or pause
mobility scripts for a given WlanNode.
:param EventData event_data: event data to handle
:return: nothing
"""
event_type = event_data.event_type
node_id = event_data.node
@ -161,6 +173,9 @@ class MobilityManager(ConfigurableManager):
"""
Send an event message on behalf of a mobility model.
This communicates the current and end (max) times to the GUI.
:param WayPointMobility model: mobility model to send event for
:return: nothing
"""
event_type = EventTypes.NONE.value
if model.state == model.STATE_STOPPED:
@ -174,7 +189,7 @@ class MobilityManager(ConfigurableManager):
data += " end=%d" % int(model.endtime)
event_data = EventData(
node=model.objid,
node=model.object_id,
event_type=event_type,
name="mobility:%s" % model.name,
data=data,
@ -188,11 +203,16 @@ class MobilityManager(ConfigurableManager):
A mobility script has caused nodes in the 'moved' list to move.
Update every WlanNode. This saves range calculations if the model
were to recalculate for each individual node movement.
:param list moved: moved nodes
:param list moved_netifs: moved network interfaces
:return: nothing
"""
for nodenum in self.configs:
try:
n = self.session.get_object(nodenum)
except KeyError:
logger.exception("error getting session object")
continue
if n.model:
n.model.update(moved, moved_netifs)
@ -200,6 +220,10 @@ class MobilityManager(ConfigurableManager):
def addphys(self, netnum, node):
"""
Keep track of PhysicalNodes and which network they belong to.
:param int netnum: network number
:param core.coreobj.PyCoreNode node: node to add physical network to
:return: nothing
"""
nodenum = node.objid
self.phys[nodenum] = node
@ -208,12 +232,16 @@ class MobilityManager(ConfigurableManager):
else:
self.physnets[netnum].append(nodenum)
# TODO: remove need for handling old style message
def physnodehandlelink(self, message):
"""
Broker handler. Snoop Link add messages to get
node numbers of PhyiscalNodes and their nets.
Physical nodes exist only on other servers, but a shadow object is
created here for tracking node position.
:param message: link message to handle
:return: nothing
"""
if message.message_type == MessageTypes.LINK.value and message.flags & MessageFlags.ADD.value:
nn = message.node_numbers()
@ -226,10 +254,14 @@ class MobilityManager(ConfigurableManager):
name="n%d" % nn[1], start=False)
self.addphys(nn[0], dummy)
# TODO: remove need to handling old style messages
def physnodeupdateposition(self, message):
"""
Snoop node messages belonging to physical nodes. The dummy object
in self.phys[] records the node position.
:param message: message to handle
:return: nothing
"""
nodenum = message.node_numbers()[0]
try:
@ -245,6 +277,9 @@ class MobilityManager(ConfigurableManager):
After installing a mobility model on a net, include any physical
nodes that we have recorded. Use the GreTap tunnel to the physical node
as the node's interface.
:param net: network to install
:return: nothing
"""
try:
nodenums = self.physnets[net.objid]
@ -274,6 +309,13 @@ class WirelessModel(Configurable):
position_callback = None
def __init__(self, session, object_id, values=None):
"""
Create a WirelessModel instance.
:param core.session.Session session: core session we are tied to
:param int object_id: object id
:param values: values
"""
Configurable.__init__(self, session, object_id)
# 'values' can be retrieved from a ConfigurableManager, or used here
# during initialization, depending on the model.
@ -282,17 +324,31 @@ class WirelessModel(Configurable):
"""
May be used if the model can populate the GUI with wireless (green)
link lines.
:param flags: link data flags
:return: link data
:rtype: list
"""
return []
def update(self, moved, moved_netifs):
"""
Update this wireless model.
:param bool moved: flag is it was moved
:param list moved_netifs: moved network interfaces
:return: nothing
"""
raise NotImplementedError
def updateconfig(self, values):
"""
For run-time updates of model config.
Returns True when self._positioncallback() and self.setlinkparams()
should be invoked.
For run-time updates of model config. Returns True when position callback and set link
parameters should be invoked.
:param values: value to update
:return: False
:rtype: bool
"""
return False
@ -325,7 +381,11 @@ class BasicRangeModel(WirelessModel):
def __init__(self, session, object_id, values=None):
"""
Range model is only instantiated during runtime.
Create a BasicRangeModel instance.
:param core.session.Session session: related core session
:param int object_id: object id
:param values: values
"""
super(BasicRangeModel, self).__init__(session=session, object_id=object_id)
self.wlan = session.get_object(object_id)
@ -337,7 +397,19 @@ class BasicRangeModel(WirelessModel):
logger.info("Basic range model configured for WLAN %d using range %d", object_id, self.range)
self.valuestolinkparams(values)
# link parameters
self.bw = None
self.delay = None
self.loss = None
self.jitter = None
def valuestolinkparams(self, values):
"""
Values to convert to link parameters.
:param values: values to convert
:return: nothing
"""
self.bw = int(self.valueof("bandwidth", values))
if self.bw == 0.0:
self.bw = None
@ -357,8 +429,10 @@ class BasicRangeModel(WirelessModel):
Handle configuration messages for setting up a model.
Pass the MobilityManager object as the manager object.
:param session: current session calling function
:param core.session.Session session: current session calling function
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:return: configuration data
:rtype: core.data.ConfigData
"""
return cls.configure(session.mobility, config_data)
@ -374,6 +448,12 @@ class BasicRangeModel(WirelessModel):
jitter=self.jitter)
def get_position(self, netif):
"""
Retrieve network interface position.
:param netif: network interface position to retrieve
:return: network interface position
"""
with self._netifslock:
return self._netifs[netif]
@ -382,6 +462,12 @@ class BasicRangeModel(WirelessModel):
A node has moved; given an interface, a new (x,y,z) position has
been set; calculate the new distance between other nodes and link or
unlink node pairs based on the configured range.
:param netif: network interface to set position for
:param x: x position
:param y: y position
:param z: z position
:return: nothing
"""
# print "set_position(%s, x=%s, y=%s, z=%s)" % (netif.localname, x, y, z)
self._netifslock.acquire()
@ -401,6 +487,10 @@ class BasicRangeModel(WirelessModel):
node.position, then re-calculate links for those that have moved.
Assumes bidirectional links, with one calculation per node pair, where
one of the nodes has moved.
:param bool moved: flag is it was moved
:param list moved_netifs: moved network interfaces
:return: nothing
"""
with self._netifslock:
while len(moved_netifs):
@ -419,6 +509,10 @@ class BasicRangeModel(WirelessModel):
calculate distance between two interfaces and perform
linking/unlinking. Sends link/unlink messages and updates the
WlanNode's linked dict.
:param netif: interface one
:param netif2: interface two
:return: nothing
"""
if netif == netif2:
return
@ -455,6 +549,11 @@ class BasicRangeModel(WirelessModel):
def calcdistance(p1, p2):
"""
Calculate the distance between two three-dimensional points.
:param tuple p1: point one
:param tuple p2: point two
:return: distance petween the points
:rtype: float
"""
a = p1[0] - p2[0]
b = p1[1] - p2[1]
@ -468,6 +567,10 @@ class BasicRangeModel(WirelessModel):
Configuration has changed during runtime.
MobilityManager.setconfig() -> WlanNode.updatemodel() ->
WirelessModel.updateconfig()
:param values: values to update configuration
:return: was update successful
:rtype: bool
"""
self.valuestolinkparams(values)
self.range = float(self.valueof("range", values))
@ -476,6 +579,12 @@ class BasicRangeModel(WirelessModel):
def create_link_data(self, interface1, interface2, message_type):
"""
Create a wireless link/unlink data message.
:param core.coreobj.PyCoreNetIf interface1: interface one
:param core.coreobj.PyCoreNetIf interface2: interface two
:param message_type: link message type
:return: link data
:rtype: LinkData
"""
return LinkData(
@ -489,6 +598,11 @@ class BasicRangeModel(WirelessModel):
def sendlinkmsg(self, netif, netif2, unlink=False):
"""
Send a wireless link/unlink API message to the GUI.
:param core.coreobj.PyCoreNetIf netif: interface one
:param core.coreobj.PyCoreNetIf netif2: interface two
:param bool unlink: unlink or not
:return: nothing
"""
if unlink:
message_type = MessageFlags.DELETE.value
@ -504,6 +618,10 @@ class BasicRangeModel(WirelessModel):
def all_link_data(self, flags):
"""
Return a list of wireless link messages for when the GUI reconnects.
:param flags: link flags
:return: all link data
:rtype: list
"""
all_links = []
with self.wlan._linked_lock:
@ -514,6 +632,39 @@ class BasicRangeModel(WirelessModel):
return all_links
class WayPoint(object):
"""
Maintains information regarding waypoints.
"""
def __init__(self, time, nodenum, coords, speed):
"""
Creates a WayPoint instance.
:param time: waypoint time
:param int nodenum: node id
:param coords: waypoint coordinates
:param speed: waypoint speed
"""
self.time = time
self.nodenum = nodenum
self.coords = coords
self.speed = speed
def __cmp__(self, other):
"""
Custom comparison method for waypoints.
:param WayPoint other: waypoint to compare to
:return: the comparison result against the other waypoint
:rtype: int
"""
tmp = cmp(self.time, other.time)
if tmp == 0:
tmp = cmp(self.nodenum, other.nodenum)
return tmp
class WayPointMobility(WirelessModel):
"""
Abstract class for mobility models that set node waypoints.
@ -525,20 +676,15 @@ class WayPointMobility(WirelessModel):
STATE_RUNNING = 1
STATE_PAUSED = 2
class WayPoint(object):
def __init__(self, time, nodenum, coords, speed):
self.time = time
self.nodenum = nodenum
self.coords = coords
self.speed = speed
def __cmp__(self, other):
tmp = cmp(self.time, other.time)
if tmp == 0:
tmp = cmp(self.nodenum, other.nodenum)
return tmp
def __init__(self, session, object_id, values=None):
"""
Create a WayPointMobility instance.
:param core.session.Session session: CORE session instance
:param int object_id: object id
:param values: values for this model
:return:
"""
super(WayPointMobility, self).__init__(session=session, object_id=object_id, values=values)
self.state = self.STATE_STOPPED
self.queue = []
@ -558,6 +704,8 @@ class WayPointMobility(WirelessModel):
def runround(self):
"""
Advance script time and move nodes.
:return: nothing
"""
if self.state != self.STATE_RUNNING:
return
@ -608,6 +756,11 @@ class WayPointMobility(WirelessModel):
self.session.evq.add_event(0.001 * self.refresh_ms, self.runround)
def run(self):
"""
Run the waypoint mobility scenario.
:return: nothing
"""
self.timezero = time.time()
self.lasttime = self.timezero - (0.001 * self.refresh_ms)
self.movenodesinitial()
@ -618,6 +771,11 @@ class WayPointMobility(WirelessModel):
"""
Calculate next node location and update its coordinates.
Returns True if the node's position has changed.
:param core.netns.nodes.CoreNode node: node to move
:param dt: move factor
:return: True if node was moved, False otherwise
:rtype: bool
"""
if node.objid not in self.points:
return False
@ -666,6 +824,8 @@ class WayPointMobility(WirelessModel):
def movenodesinitial(self):
"""
Move nodes to their initial positions. Then calculate the ranges.
:return: nothing
"""
moved = []
moved_netifs = []
@ -683,21 +843,38 @@ class WayPointMobility(WirelessModel):
def addwaypoint(self, time, nodenum, x, y, z, speed):
"""
Waypoints are pushed to a heapq, sorted by time.
:param time: waypoint time
:param int nodenum: node id
:param x: x position
:param y: y position
:param z: z position
:param speed: speed
:return: nothing
"""
# print "addwaypoint: %s %s %s,%s,%s %s" % (time, nodenum, x, y, z, speed)
wp = self.WayPoint(time, nodenum, coords=(x, y, z), speed=speed)
wp = WayPoint(time, nodenum, coords=(x, y, z), speed=speed)
heapq.heappush(self.queue, wp)
def addinitial(self, nodenum, x, y, z):
"""
Record initial position in a dict.
:param int nodenum: node id
:param x: x position
:param y: y position
:param z: z position
:return: nothing
"""
wp = self.WayPoint(0, nodenum, coords=(x, y, z), speed=0)
wp = WayPoint(0, nodenum, coords=(x, y, z), speed=0)
self.initial[nodenum] = wp
def updatepoints(self, now):
"""
Move items from self.queue to self.points when their time has come.
:param int now: current timestamp
:return: nothing
"""
while len(self.queue):
if self.queue[0].time > now:
@ -708,12 +885,16 @@ class WayPointMobility(WirelessModel):
def copywaypoints(self):
"""
Store backup copy of waypoints for looping and stopping.
:return: nothing
"""
self.queue_copy = list(self.queue)
def loopwaypoints(self):
"""
Restore backup copy of waypoints when looping.
:return: nothing
"""
self.queue = list(self.queue_copy)
return self.loop
@ -723,6 +904,12 @@ class WayPointMobility(WirelessModel):
Helper to move a node, notify any GUI (connected session handlers),
without invoking the interface poshook callback that may perform
range calculation.
:param core.netns.nodes.CoreNode node: node to set position for
:param x: x position
:param y: y position
:param z: z position
:return: nothing
"""
# this would cause PyCoreNetIf.poshook() callback (range calculation)
# node.setposition(x, y, z)
@ -739,6 +926,8 @@ class WayPointMobility(WirelessModel):
waypoints. This is just an estimate. The endtime will later be
adjusted, after one round of the script has run, to be the time
that the last moving node has reached its final waypoint.
:return: nothing
"""
try:
self.endtime = self.queue[-1].time
@ -749,6 +938,8 @@ class WayPointMobility(WirelessModel):
"""
Run the script from the beginning or unpause from where it
was before.
:return: nothing
"""
laststate = self.state
self.state = self.STATE_RUNNING
@ -766,6 +957,9 @@ class WayPointMobility(WirelessModel):
def stop(self, move_initial=True):
"""
Stop the script and move nodes to initial positions.
:param bool move_initial: flag to check if we should move nodes to initial position
:return: nothing
"""
self.state = self.STATE_STOPPED
self.loopwaypoints()
@ -778,6 +972,8 @@ class WayPointMobility(WirelessModel):
def pause(self):
"""
Pause the script; pause time is stored to self.lasttime.
:return: nothing
"""
self.state = self.STATE_PAUSED
self.lasttime = time.time()
@ -811,6 +1007,13 @@ class Ns2ScriptedMobility(WayPointMobility):
config_groups = "ns-2 Mobility Script Parameters:1-%d" % len(config_matrix)
def __init__(self, session, object_id, values=None):
"""
Creates a Ns2ScriptedMobility instance.
:param core.session.Session session: CORE session instance
:param int object_id: object id
:param values: values
"""
super(Ns2ScriptedMobility, self).__init__(session=session, object_id=object_id, values=values)
self._netifs = {}
self._netifslock = threading.Lock()
@ -835,7 +1038,7 @@ class Ns2ScriptedMobility(WayPointMobility):
Handle configuration messages for setting up a model.
Pass the MobilityManager object as the manager object.
:param session: current session calling function
:param core.session.Session session: current session calling function
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
"""
return cls.configure(session.mobility, config_data)
@ -845,6 +1048,8 @@ class Ns2ScriptedMobility(WayPointMobility):
Read in mobility script from a file. This adds waypoints to a
priority queue, sorted by waypoint time. Initial waypoints are
stored in a separate dict.
:return: nothing
"""
filename = self.findfile(self.file)
try:
@ -901,51 +1106,66 @@ class Ns2ScriptedMobility(WayPointMobility):
if ix is not None and iy is not None:
self.addinitial(self.map(inodenum), ix, iy, iz)
def findfile(self, fn):
def findfile(self, file_name):
"""
Locate a script file. If the specified file doesn't exist, look in the
same directory as the scenario file (session.filename), or in the default
configs directory (~/.core/configs). This allows for sample files without
absolute pathnames.
:param str file_name: file name to find
:return: absolute path to the file
:rtype: str
"""
if os.path.exists(fn):
return fn
if os.path.exists(file_name):
return file_name
if self.session.filename is not None:
d = os.path.dirname(self.session.filename)
sessfn = os.path.join(d, fn)
sessfn = os.path.join(d, file_name)
if os.path.exists(sessfn):
return sessfn
if self.session.user is not None:
userfn = os.path.join('/home', self.session.user, '.core', 'configs', fn)
userfn = os.path.join('/home', self.session.user, '.core', 'configs', file_name)
if os.path.exists(userfn):
return userfn
return fn
return file_name
def parsemap(self, mapstr):
"""
Parse a node mapping string, given as a configuration parameter.
:param str mapstr: mapping string to parse
:return: nothing
"""
self.nodemap = {}
if mapstr.strip() == '':
if mapstr.strip() == "":
return
for pair in mapstr.split(','):
parts = pair.split(':')
for pair in mapstr.split(","):
parts = pair.split(":")
try:
if len(parts) != 2:
raise ValueError
self.nodemap[int(parts[0])] = int(parts[1])
except ValueError:
logger.warn("ns-2 mobility node map error")
return
logger.exception("ns-2 mobility node map error")
def map(self, nodenum):
"""
Map one node number (from a script file) to another.
:param str nodenum: node id to map
:return: mapped value or the node id itself
:rtype: int
"""
nodenum = int(nodenum)
try:
return self.nodemap[nodenum]
except KeyError:
logger.exception("error find value in node map")
return nodenum
def startup(self):
@ -954,6 +1174,8 @@ class Ns2ScriptedMobility(WayPointMobility):
Move node to initial positions when any autostart time is specified.
Ignore the script if autostart is an empty string (can still be
started via GUI controls).
:return: nothing
"""
if self.autostart == '':
logger.info("not auto-starting ns-2 script for %s" % self.wlan.name)
@ -971,6 +1193,8 @@ class Ns2ScriptedMobility(WayPointMobility):
def start(self):
"""
Handle the case when un-paused.
:return: nothing
"""
laststate = self.state
super(Ns2ScriptedMobility, self).start()
@ -980,19 +1204,38 @@ class Ns2ScriptedMobility(WayPointMobility):
def run(self):
"""
Start is pressed or autostart is triggered.
:return: nothing
"""
super(Ns2ScriptedMobility, self).run()
self.statescript("run")
def pause(self):
"""
Pause the mobility script.
:return: nothing
"""
super(Ns2ScriptedMobility, self).pause()
self.statescript("pause")
def stop(self, move_initial=True):
"""
Stop the mobility script.
:param bool move_initial: flag to check if we should move node to initial position
:return: nothing
"""
super(Ns2ScriptedMobility, self).stop(move_initial=move_initial)
self.statescript("stop")
def statescript(self, typestr):
"""
State of the mobility script.
:param str typestr: state type string
:return: nothing
"""
filename = None
if typestr == "run" or typestr == "unpause":
filename = self.script_start

View file

@ -22,32 +22,39 @@ from core.misc import nodeutils
logger = log.get_logger(__name__)
class Bunch:
# 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
# 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'),
("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"),
]
def __init__(self, session):
@ -55,7 +62,6 @@ class Sdt(object):
Creates a Sdt instance.
:param core.session.Session session: session this manager is tied to
:return: nothing
"""
self.session = session
self.sock = None
@ -69,23 +75,28 @@ class Sdt(object):
def is_enabled(self):
"""
Check for 'enablesdt' session option. Return False by default if
Check for "enablesdt" session option. Return False by default if
the option is missing.
:return: True if enabled, False otherwise
:rtype: bool
"""
if not hasattr(self.session.options, 'enablesdt'):
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.
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 == "":
@ -97,6 +108,9 @@ class Sdt(object):
def connect(self, flags=0):
"""
Connect to the SDT address/port if enabled.
:return: True if connected, False otherwise
:rtype: bool
"""
if not self.is_enabled():
return False
@ -109,7 +123,7 @@ class Sdt(object):
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:
@ -133,17 +147,25 @@ class Sdt(object):
"""
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):
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)):
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):
"""
Disconnect from SDT.
:return: nothing
"""
if self.sock:
try:
self.sock.close()
@ -157,8 +179,10 @@ class Sdt(object):
def shutdown(self):
"""
Invoked from Session.shutdown() and Session.checkshutdown().
:return: nothing
"""
self.cmd('clear all')
self.cmd("clear all")
self.disconnect()
self.showerror = True
@ -167,6 +191,10 @@ class Sdt(object):
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
@ -183,59 +211,84 @@ class Sdt(object):
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 & MessageFlags.DELETE.value:
self.cmd('delete node,%d' % nodenum)
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 & MessageFlags.ADD.value:
if icon is not None:
type = name
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))
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.
: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.
: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 & MessageFlags.DELETE.value:
self.cmd('delete link,%s,%s' % (node1num, node2num))
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).
:return: nothing
"""
nets = []
with self.session._objects_lock:
@ -251,7 +304,7 @@ class Sdt(object):
obj.name, obj.type, obj.icon)
for nodenum in sorted(self.remotes.keys()):
r = self.remotes[nodenum]
(x, y, z) = r.pos
x, y, z = r.pos
self.updatenode(nodenum, MessageFlags.ADD.value, x, y, z,
r.name, r.type, r.icon)
@ -276,22 +329,30 @@ class Sdt(object):
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).
: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
@ -344,11 +405,15 @@ class Sdt(object):
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.
:param msg: link message to handle
:return: nothing
"""
if not self.is_enabled():
return False
@ -370,8 +435,11 @@ class Sdt(object):
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

View file

@ -1,8 +1,8 @@
"""
service.py: definition of CoreService class that is subclassed to define
Definition of CoreService class that is subclassed to define
startup services and routing for nodes. A service is typically a daemon
program launched when a node starts that provides some sort of
service. The CoreServices class handles configuration messages for sending
program launched when a node starts that provides some sort of service.
The CoreServices class handles configuration messages for sending
a list of available services to the GUI and for configuring individual
services.
"""
@ -30,10 +30,19 @@ logger = log.get_logger(__name__)
class ServiceManager(object):
"""
Manages services available for CORE nodes to use.
"""
services = []
@classmethod
def add(cls, service):
"""
Add a service to manager.
:param CoreService service: service to add
:return: nothing
"""
insert = 0
for index, known_service in enumerate(cls.services):
if known_service._group == service._group:
@ -45,6 +54,13 @@ class ServiceManager(object):
@classmethod
def get(cls, name):
"""
Retrieve a service from the manager.
:param str name: name of the service to retrieve
:return: service if it exists, None otherwise
:rtype: CoreService
"""
for service in cls.services:
if service._name == name:
return service
@ -93,9 +109,13 @@ class CoreServices(ConfigurableManager):
def importcustom(self, path):
"""
Import services from a myservices directory.
:param str path: path to import custom services from
:return: nothing
"""
if not path or len(path) == 0:
return
if not os.path.isdir(path):
logger.warn("invalid custom service directory specified" ": %s" % path)
return
@ -124,6 +144,10 @@ class CoreServices(ConfigurableManager):
"""
Get the list of default services that should be enabled for a
node for the given node type.
:param service_type: service type to get default services for
:return: default services
:rtype: list
"""
logger.debug("getting default services for type: %s", service_type)
results = []
@ -138,22 +162,31 @@ class CoreServices(ConfigurableManager):
results.append(service)
return results
def getcustomservice(self, objid, service):
def getcustomservice(self, object_id, service):
"""
Get any custom service configured for the given node that
matches the specified service name. If no custom service
is found, return the specified service.
Get any custom service configured for the given node that matches the specified service name.
If no custom service is found, return the specified service.
:param int object_id: object id to get service from
:param CoreService service: custom service to retrieve
:return: custom service from the node
:rtype: CoreService
"""
if objid in self.customservices:
for s in self.customservices[objid]:
if object_id in self.customservices:
for s in self.customservices[object_id]:
if s._name == service._name:
return s
return service
def setcustomservice(self, objid, service, values):
def setcustomservice(self, object_id, service, values):
"""
Store service customizations in an instantiated service object
using a list of values that came from a config message.
:param int object_id: object id to set custom service for
:param class service: service to set
:param list values: values to
:return:
"""
if service._custom:
s = service
@ -174,19 +207,24 @@ class CoreServices(ConfigurableManager):
if service._custom:
return
# add the custom service to dict
if objid in self.customservices:
self.customservices[objid] += (s,)
if object_id in self.customservices:
self.customservices[object_id] += (s,)
else:
self.customservices[objid] = (s,)
self.customservices[object_id] = (s,)
def addservicestonode(self, node, nodetype, services_str):
"""
Populate the node.service list using (1) the list of services
requested from the services TLV, (2) using any custom service
configuration, or (3) using the default services for this node type.
:param core.coreobj.PyCoreNode node: node to add services to
:param str nodetype: node type to add services to
:param str services_str: string formatted service list
:return: nothing
"""
if services_str is not None:
services = services_str.split('|')
services = services_str.split("|")
for name in services:
s = ServiceManager.get(name)
if s is None:
@ -202,35 +240,48 @@ class CoreServices(ConfigurableManager):
s = self.getcustomservice(node.objid, s)
node.addservice(s)
def getallconfigs(self):
def getallconfigs(self, use_clsmap=True):
"""
Return (nodenum, service) tuples for all stored configs.
Used when reconnecting to a session or opening XML.
Return (nodenum, service) tuples for all stored configs. Used when reconnecting to a
session or opening XML.
:param bool use_clsmap: should a class map be used, default to True
:return: list of tuples of node ids and services
:rtype: list
"""
r = []
configs = []
for nodenum in self.customservices:
for s in self.customservices[nodenum]:
r.append((nodenum, s))
return r
for service in self.customservices[nodenum]:
configs.append((nodenum, service))
return configs
def getallfiles(self, service):
"""
Return all customized files stored with a service.
Used when reconnecting to a session or opening XML.
:param CoreService service: service to get files for
:return:
"""
r = []
files = []
if not service._custom:
return r
return files
for filename in service._configs:
data = self.getservicefiledata(service, filename)
if data is None:
continue
r.append((filename, data))
return r
files.append((filename, data))
return files
def bootnodeservices(self, node):
"""
Start all services on a node.
:param core.netns.nodes.CoreNode node: node to start services on
:return:
"""
services = sorted(node.services, key=lambda service: service._startindex)
use_startup_service = any(map(self.is_startup_service, services))
@ -246,49 +297,66 @@ class CoreServices(ConfigurableManager):
logger.exception("error converting start time to float")
self.bootnodeservice(node, s, services, use_startup_service)
def bootnodeservice(self, node, s, services, use_startup_service):
def bootnodeservice(self, node, service, services, use_startup_service):
"""
Start a service on a node. Create private dirs, generate config
files, and execute startup commands.
:param core.netns.nodes.CoreNode node: node to boot services on
:param CoreService service: service to start
:param list services: service list
:param bool use_startup_service: flag to use startup services or not
:return: nothing
"""
if s._custom:
self.bootnodecustomservice(node, s, services, use_startup_service)
if service._custom:
self.bootnodecustomservice(node, service, services, use_startup_service)
return
logger.info("starting service %s (%s)" % (s._name, s._startindex))
for d in s._dirs:
logger.info("starting service %s (%s)" % (service._name, service._startindex))
for directory in service._dirs:
try:
node.privatedir(d)
node.privatedir(directory)
except:
logger.exception("Error making node %s dir %s", node.name, d)
for filename in s.getconfigfilenames(node.objid, services):
cfg = s.generateconfig(node, filename, services)
logger.exception("Error making node %s dir %s", node.name, directory)
for filename in service.getconfigfilenames(node.objid, services):
cfg = service.generateconfig(node, filename, services)
node.nodefile(filename, cfg)
if use_startup_service and not self.is_startup_service(s):
if use_startup_service and not self.is_startup_service(service):
return
for cmd in s.getstartup(node, services):
for cmd in service.getstartup(node, services):
try:
# NOTE: this wait=False can be problematic!
node.cmd(shlex.split(cmd), wait=False)
except:
logger.exception("error starting command %s", cmd)
def bootnodecustomservice(self, node, s, services, use_startup_service):
def bootnodecustomservice(self, node, service, services, use_startup_service):
"""
Start a custom service on a node. Create private dirs, use supplied
config files, and execute supplied startup commands.
:param core.netns.nodes.CoreNode node: node to boot services on
:param CoreService service: service to start
:param list services: service list
:param bool use_startup_service: flag to use startup services or not
:return: nothing
"""
logger.info("starting service %s (%s)(custom)" % (s._name, s._startindex))
for d in s._dirs:
logger.info("starting service %s (%s)(custom)" % (service._name, service._startindex))
for directory in service._dirs:
try:
node.privatedir(d)
node.privatedir(directory)
except:
logger.exception("Error making node %s dir %s", node.name, d)
for i, filename in enumerate(s._configs):
logger.exception("Error making node %s dir %s", node.name, directory)
for i, filename in enumerate(service._configs):
if len(filename) == 0:
continue
cfg = self.getservicefiledata(s, filename)
cfg = self.getservicefiledata(service, filename)
if cfg is None:
cfg = s.generateconfig(node, filename, services)
cfg = service.generateconfig(node, filename, services)
# cfg may have a file:/// url for copying from a file
try:
if self.copyservicefile(node, filename, cfg):
@ -298,10 +366,10 @@ class CoreServices(ConfigurableManager):
continue
node.nodefile(filename, cfg)
if use_startup_service and not self.is_startup_service(s):
if use_startup_service and not self.is_startup_service(service):
return
for cmd in s._startup:
for cmd in service._startup:
try:
# NOTE: this wait=False can be problematic!
node.cmd(shlex.split(cmd), wait=False)
@ -313,6 +381,12 @@ class CoreServices(ConfigurableManager):
Given a configured service filename and config, determine if the
config references an existing file that should be copied.
Returns True for local files, False for generated.
:param core.netns.nodes.CoreNode node: node to copy service for
:param str filename: file name for a configured service
:param str cfg: configuration string
:return: True if successful, False otherwise
:rtype: bool
"""
if cfg[:7] == 'file://':
src = cfg[7:]
@ -326,6 +400,9 @@ class CoreServices(ConfigurableManager):
def validatenodeservices(self, node):
"""
Run validation commands for all services on a node.
:param core.netns.nodes.CoreNode node: node to validate services for
:return: nothing
"""
services = sorted(node.services, key=lambda service: service._startindex)
for s in services:
@ -334,6 +411,12 @@ class CoreServices(ConfigurableManager):
def validatenodeservice(self, node, service, services):
"""
Run the validation command(s) for a service.
:param core.netns.nodes.CoreNode node: node to validate service for
:param CoreService service: service to validate
:param list services: services for node
:return: service validation status
:rtype: int
"""
logger.info("validating service for node (%s - %s): %s (%s)",
node.objid, node.name, service._name, service._startindex)
@ -360,21 +443,29 @@ class CoreServices(ConfigurableManager):
def stopnodeservices(self, node):
"""
Stop all services on a node.
:param core.netns.nodes.CoreNode node: node to stop services on
:return: nothing
"""
services = sorted(node.services, key=lambda service: service._startindex)
for s in services:
self.stopnodeservice(node, s)
def stopnodeservice(self, node, s):
def stopnodeservice(self, node, service):
"""
Stop a service on a node.
:param core.netns.nodes.CoreNode node: node to stop a service on
:param CoreService service: service to stop
:return: status for stopping the services
:rtype: str
"""
status = ""
if len(s._shutdown) == 0:
if len(service._shutdown) == 0:
# doesn't have a shutdown command
status += "0"
else:
for cmd in s._shutdown:
for cmd in service._shutdown:
try:
tmp = node.cmd(shlex.split(cmd), wait=True)
status += "%s" % tmp
@ -392,6 +483,7 @@ class CoreServices(ConfigurableManager):
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:return: response messages
:rtype: ConfigData
"""
node_id = config_data.node
session_id = config_data.session
@ -525,9 +617,14 @@ class CoreServices(ConfigurableManager):
return None
def servicesfromopaque(self, opaque, objid):
def servicesfromopaque(self, opaque, object_id):
"""
Build a list of services from an opaque data string.
:param str opaque: opaque data string
:param int object_id: object id
:return: services and unknown services lists tuple
:rtype: tuple
"""
services = []
unknown = []
@ -537,7 +634,7 @@ class CoreServices(ConfigurableManager):
servicenames = servicesstring[1].split(',')
for name in servicenames:
s = ServiceManager.get(name)
s = self.getcustomservice(objid, s)
s = self.getcustomservice(object_id, s)
if s is None:
unknown.append(name)
else:
@ -551,6 +648,10 @@ class CoreServices(ConfigurableManager):
"title1:1-5|title2:6-9|10-12", where title is an optional group title
and i-j is a numeric range of value indices; groups are
separated by commas.
:param list servicelist: service list to build group string from
:return: groups string
:rtype: str
"""
i = 0
r = ""
@ -573,10 +674,16 @@ class CoreServices(ConfigurableManager):
r += "-%d" % i
return r
# TODO: need to remove depenency on old message structure below
def getservicefile(self, services, node, filename):
"""
Send a File Message when the GUI has requested a service file.
The file data is either auto-generated or comes from an existing config.
:param list services: service list
:param core.netns.nodes.CoreNode node: node to get service file from
:param str filename: file name to retrieve
:return: file message for node
"""
svc = services[0]
# get the filename and determine the config file index
@ -609,6 +716,10 @@ class CoreServices(ConfigurableManager):
"""
Get the customized file data associated with a service. Return None
for invalid filenames or missing file data.
:param CoreService service: service to get file data from
:param str filename: file name to get data from
:return: file data
"""
try:
i = service._configs.index(filename)
@ -623,6 +734,13 @@ class CoreServices(ConfigurableManager):
Receive a File Message from the GUI and store the customized file
in the service config. The filename must match one from the list of
config files in the service.
:param int nodenum: node id to set service file
:param str type: file type to set
:param str filename: file name to set
:param str srcname: source name of file to set
:param data: data for file to set
:return: nothing
"""
if len(type.split(':')) < 2:
logger.warn("Received file type did not contain service info.")
@ -653,6 +771,9 @@ class CoreServices(ConfigurableManager):
"""
Handle an Event Message used to start, stop, restart, or validate
a service on a given node.
:param EventData event_data: event data to handle
:return: nothing
"""
event_type = event_data.event_type
node_id = event_data.node
@ -670,7 +791,7 @@ class CoreServices(ConfigurableManager):
if event_type == EventTypes.STOP.value or event_type == EventTypes.RESTART.value:
status = self.stopnodeservice(node, s)
if status != "0":
fail += "Stop %s," % (s._name)
fail += "Stop %s," % s._name
if event_type == EventTypes.START.value or event_type == EventTypes.RESTART.value:
if s._custom:
cmds = s._startup
@ -779,6 +900,11 @@ class CoreService(object):
Return the tuple of configuration file filenames. This default method
returns the cls._configs tuple, but this method may be overriden to
provide node-specific filenames that may be based on other services.
:param int nodenum: node id to get config file names for
:param list services: node services
:return: class configuration files
:rtype: tuple
"""
return cls._configs
@ -790,6 +916,11 @@ class CoreService(object):
provided to allow interdependencies (e.g. zebra and OSPF).
Return the configuration string to be written to a file or sent
to the GUI for customization.
:param core.netns.nodes.CoreNode node: node to generate config for
:param str filename: file name to generate config for
:param list services: services for node
:return: nothing
"""
raise NotImplementedError
@ -798,8 +929,13 @@ class CoreService(object):
"""
Return the tuple of startup commands. This default method
returns the cls._startup tuple, but this method may be
overriden to provide node-specific commands that may be
overridden to provide node-specific commands that may be
based on other services.
:param core.netns.nodes.CoreNode node: node to get startup for
:param list services: services for node
:return: startup commands
:rtype: tuple
"""
return cls._startup
@ -810,6 +946,11 @@ class CoreService(object):
returns the cls._validate tuple, but this method may be
overriden to provide node-specific commands that may be
based on other services.
:param core.netns.nodes.CoreNode node: node to validate
:param list services: services for node
:return: validation commands
:rtype: tuple
"""
return cls._validate
@ -818,6 +959,11 @@ class CoreService(object):
"""
Convert service properties into a string list of key=value pairs,
separated by "|".
:param core.netns.nodes.CoreNode node: node to get value list for
:param list services: services for node
:return: value list string
:rtype: str
"""
valmap = [cls._dirs, cls._configs, cls._startindex, cls._startup,
cls._shutdown, cls._validate, cls._meta, cls._starttime]
@ -834,6 +980,9 @@ class CoreService(object):
"""
Convert list of values into properties for this instantiated
(customized) service.
:param list values: value list to set properties from
:return: nothing
"""
# TODO: support empty value? e.g. override default meta with ''
for key in self.keys:
@ -844,6 +993,13 @@ class CoreService(object):
logger.exception("error indexing into key")
def setvalue(self, key, value):
"""
Set values for this service.
:param str key: key to set value for
:param value: value of key to set
:return: nothing
"""
if key not in self.keys:
raise ValueError
# this handles data conversion to int, string, and tuples

View file

@ -53,11 +53,29 @@ logger = log.get_logger(__name__)
class HookManager(object):
"""
Manages hooks that can be ran as part of state changes.
"""
def __init__(self, hook_dir):
"""
Creates a HookManager instance.
:param str hook_dir: the hook directory
"""
self.hook_dir = hook_dir
self.hooks = {}
def add(self, hook_type, file_name, source_name, data):
"""
Adds a hook to the manager.
:param str hook_type: hook type
:param str file_name: file name for hook
:param str source_name: source name for hook
:param data: data for hook
:return: nothing
"""
logger.info("setting state hook: %s - %s from %s", hook_type, file_name, source_name)
hook_id, state = hook_type.split(":")[:2]
@ -73,9 +91,21 @@ class HookManager(object):
state_hooks.append(hook)
def clear(self):
"""
Clear all known hooks.
:return: nothing
"""
self.hooks.clear()
def state_change(self, state, environment):
"""
Mark a state change to notify related hooks to run with the provided environment.
:param int state: the new state after a change
:param dict environment: environment to run hook in
:return: nothing
"""
# retrieve all state hooks
hooks = self.hooks.get(state, [])
@ -87,6 +117,13 @@ class HookManager(object):
self.run(hook, environment)
def run(self, hook, environment):
"""
Run a hook, with a provided environment.
:param tuple hook: hook to run
:param dict environment: environment to run hook with
:return:
"""
file_name, data = hook
logger.info("running hook: %s", file_name)
hook_file_name = os.path.join(self.hook_dir, file_name)
@ -119,17 +156,32 @@ class HookManager(object):
class SessionManager(object):
"""
Manages currently known sessions.
"""
sessions = set()
session_lock = threading.Lock()
@classmethod
def add(cls, session):
"""
Add a session to the manager.
:param Session session: session to add
:return: nothing
"""
with cls.session_lock:
logger.info("adding session to manager: %s", session.session_id)
cls.sessions.add(session)
@classmethod
def remove(cls, session):
"""
Remove session from the manager.
:param Session session: session to remove
:return: nothing
"""
with cls.session_lock:
logger.info("removing session from manager: %s", session.session_id)
if session in cls.sessions:
@ -139,6 +191,11 @@ class SessionManager(object):
@classmethod
def on_exit(cls):
"""
Method used to shutdown all currently known sessions, in case of unexpected exit.
:return: nothing
"""
logger.info("caught program exit, shutting down all known sessions")
while cls.sessions:
with cls.session_lock:
@ -154,6 +211,15 @@ class Session(object):
"""
def __init__(self, session_id, config=None, server=None, persistent=False, mkdir=True):
"""
Create a Session instance.
:param int session_id: session id
:param dict config: session configuration
:param core.coreserver.CoreServer server: core server object
:param bool persistent: flag is session is considered persistent
:param bool mkdir: flag to determine if a directory should be made
"""
self.session_id = session_id
# dict of configuration items from /etc/core/core.conf config file
@ -346,7 +412,7 @@ class Session(object):
:param EventTypes state: state to set to
:param send_event: if true, generate core API event messages
:return:
:return: nothing
"""
state_name = coreapi.state_name(state)
@ -389,6 +455,9 @@ class Session(object):
def write_state(self, state):
"""
Write the current state to a state file in the session dir.
:param int state: state to write to file
:return: nothing
"""
try:
state_file = open(self._state_file, "w")
@ -401,7 +470,7 @@ class Session(object):
"""
Run hook scripts upon changing states. If hooks is not specified, run all hooks in the given state.
:param state: state to run hooks for
:param int state: state to run hooks for
:return: nothing
"""
@ -421,6 +490,12 @@ class Session(object):
def set_hook(self, hook_type, file_name, source_name, data):
"""
Store a hook from a received file message.
:param str hook_type: hook type
:param str file_name: file name for hook
:param str source_name: source name
:param data: hook data
:return: nothing
"""
logger.info("setting state hook: %s - %s from %s", hook_type, file_name, source_name)
@ -449,6 +524,12 @@ class Session(object):
self._hooks.clear()
def run_hook(self, hook):
"""
Run a hook.
:param tuple hook: hook to run
:return: nothing
"""
file_name, data = hook
logger.info("running hook %s", file_name)
@ -478,6 +559,12 @@ class Session(object):
logger.exception("error running hook '%s'", file_name)
def run_state_hooks(self, state):
"""
Run state hooks.
:param int state: state to run hooks for
:return: nothing
"""
for hook in self._state_hooks.get(state, []):
try:
hook(state)
@ -492,6 +579,13 @@ class Session(object):
)
def add_state_hook(self, state, hook):
"""
Add a state hook.
:param int state: state to add hook for
:param func hook: hook callback for the state
:return: nothing
"""
hooks = self._state_hooks.setdefault(state, [])
assert hook not in hooks
hooks.append(hook)
@ -500,10 +594,23 @@ class Session(object):
hook(state)
def del_state_hook(self, state, hook):
"""
Delete a state hook.
:param int state: state to delete hook for
:param func hook: hook to delete
:return:
"""
hooks = self._state_hooks.setdefault(state, [])
hooks.remove(hook)
def runtime_state_hook(self, state):
"""
Runtime state hook check.
:param int state: state to check
:return: nothing
"""
if state == EventTypes.RUNTIME_STATE.value:
self.emane.poststartup()
xml_file_version = self.get_config_item("xmlfilever")
@ -516,6 +623,9 @@ class Session(object):
Get an environment suitable for a subprocess.Popen call.
This is the current process environment with some session-specific
variables.
:param bool state: flag to determine if session state should be included
:return:
"""
env = os.environ.copy()
env["SESSION"] = "%s" % self.session_id
@ -550,6 +660,9 @@ class Session(object):
def set_thumbnail(self, thumb_file):
"""
Set the thumbnail filename. Move files from /tmp to session dir.
:param str thumb_file: tumbnail file to set for session
:return: nothing
"""
if not os.path.exists(thumb_file):
logger.error("thumbnail file to set does not exist: %s", thumb_file)
@ -565,7 +678,7 @@ class Session(object):
Set the username for this session. Update the permissions of the
session dir to allow the user write access.
:param user: user to give write permissions to for the session directory
:param str user: user to give write permissions to for the session directory
:return: nothing
"""
if user:
@ -595,6 +708,11 @@ class Session(object):
def add_object(self, cls, *clsargs, **clskwds):
"""
Add an emulation object.
:param class cls: object class to add
:param list clsargs: list of arguments for the class to create
:param dict clskwds: dictionary of arguments for the class to create
:return: the created class instance
"""
obj = cls(self, *clsargs, **clskwds)
@ -611,6 +729,9 @@ class Session(object):
def get_object(self, object_id):
"""
Get an emulation object.
:param int object_id: object id to retrieve
:return: object for the given id
"""
if object_id not in self.objects:
raise KeyError("unknown object id %s" % object_id)
@ -619,6 +740,9 @@ class Session(object):
def get_object_by_name(self, name):
"""
Get an emulation object using its name attribute.
:param str name: name of object to retrieve
:return: object for the name given
"""
with self._objects_lock:
for obj in self.objects.itervalues():
@ -630,7 +754,7 @@ class Session(object):
"""
Remove an emulation object.
:param object_id: object id to remove
:param int object_id: object id to remove
:return: nothing
"""
with self._objects_lock:
@ -642,7 +766,7 @@ class Session(object):
def delete_objects(self):
"""
Clear the _objs dictionary, and call each obj.shutdown() routine.
Clear the objects dictionary, and call shutdown for each object.
"""
with self._objects_lock:
while self.objects:
@ -652,8 +776,7 @@ class Session(object):
def write_objects(self):
"""
Write objects to a 'nodes' file in the session dir.
The 'nodes' file lists:
number, name, api-type, class-type
The 'nodes' file lists: number, name, api-type, class-type
"""
try:
nodes_file = open(os.path.join(self.session_dir, "nodes"), "w")
@ -670,6 +793,11 @@ class Session(object):
Objects can register configuration objects that are included in
the Register Message and may be configured via the Configure
Message. The callback is invoked when receiving a Configure Message.
:param str name: name of configuration object to add
:param int object_type: register tlv type
:param func callback: callback function for object
:return: nothing
"""
register_tlv = RegisterTlvs(object_type)
logger.info("adding config object callback: %s - %s", name, register_tlv)
@ -678,8 +806,12 @@ class Session(object):
def config_object(self, config_data):
"""
Invoke the callback for an object upon receipt of a Configure
Message for that object. A no-op if the object doesn't exist.
Invoke the callback for an object upon receipt of configuration data for that object.
A no-op if the object doesn't exist.
:param core.data.ConfigData config_data: configuration data to execute against
:return: responses to the configuration data
:rtype: list
"""
name = config_data.object
logger.info("session(%s): handling config message(%s): \n%s",
@ -715,7 +847,7 @@ class Session(object):
def dump_session(self):
"""
Debug print this session.
Log information about the session in its current state.
"""
logger.info("session id=%s name=%s state=%s", self.session_id, self.name, self.state)
logger.info("file=%s thumbnail=%s node_count=%s/%s",
@ -723,7 +855,13 @@ class Session(object):
def exception(self, level, source, object_id, text):
"""
Generate an Exception Message
Generate and broadcast an exception event.
:param str level: exception level
:param str source: source name
:param int object_id: object id
:param str text: exception message
:return: nothing
"""
exception_data = ExceptionData(
@ -751,6 +889,11 @@ class Session(object):
"""
Return a boolean entry from the configuration dictionary, may
return None if undefined.
:param str name: configuration item name
:param default: default value to return if not found
:return: boolean value of the configuration item
:rtype: bool
"""
item = self.get_config_item(name)
if item is None:
@ -761,6 +904,11 @@ class Session(object):
"""
Return an integer entry from the configuration dictionary, may
return None if undefined.
:param str name: configuration item name
:param default: default value to return if not found
:return: integer value of the configuration item
:rtype: int
"""
item = self.get_config_item(name)
if item is None:
@ -856,6 +1004,7 @@ class Session(object):
# stop node services
with self._objects_lock:
for obj in self.objects.itervalues():
# TODO: determine if checking for CoreNode alone is ok
if isinstance(obj, nodes.PyCoreNode):
self.services.stopnodeservices(obj)
@ -901,6 +1050,7 @@ class Session(object):
"""
with self._objects_lock:
for obj in self.objects.itervalues():
# TODO: determine instance type we need to check, due to method issue below
if isinstance(obj, nodes.PyCoreNode) and not nodeutils.is_node(obj, NodeTypes.RJ45):
# add a control interface if configured
self.add_remove_control_interface(node=obj, remove=False)
@ -909,9 +1059,14 @@ class Session(object):
self.update_control_interface_hosts()
def validate_nodes(self):
"""
Validate all nodes that are known by the session.
:return: nothing
"""
with self._objects_lock:
for obj in self.objects.itervalues():
# TODO: this can be extended to validate everything
# TODO: this can be extended to validate everything, bad node check here as well
# such as vnoded process, bridges, etc.
if not isinstance(obj, nodes.PyCoreNode):
continue
@ -922,6 +1077,12 @@ class Session(object):
obj.validate()
def get_control_lnet_prefixes(self):
"""
Retrieve control net prefixes.
:return: control net prefix list
:rtype: list
"""
p = getattr(self.options, "controlnet", self.config.get("controlnet"))
p0 = getattr(self.options, "controlnet0", self.config.get("controlnet0"))
p1 = getattr(self.options, "controlnet1", self.config.get("controlnet1"))
@ -934,6 +1095,12 @@ class Session(object):
return [p0, p1, p2, p3]
def get_control_net_server_interfaces(self):
"""
Retrieve control net server interfaces.
:return: list of control net server interfaces
:rtype: list
"""
d0 = self.config.get("controlnetif0")
if d0:
logger.error("controlnet0 cannot be assigned with a host interface")
@ -943,6 +1110,13 @@ class Session(object):
return [None, d1, d2, d3]
def get_control_net_index(self, dev):
"""
Retrieve control net index.
:param str dev: device to get control net index for
:return: control net index, -1 otherwise
:rtype: int
"""
if dev[0:4] == "ctrl" and int(dev[4]) in [0, 1, 2, 3]:
index = int(dev[4])
if index == 0:
@ -952,6 +1126,7 @@ class Session(object):
return -1
def get_control_net_object(self, net_index):
# TODO: all nodes use an integer id and now this wants to use a string =(
object_id = "ctrl%dnet" % net_index
return self.get_object(object_id)
@ -961,12 +1136,19 @@ class Session(object):
When the remove flag is True, remove the bridge that connects control
interfaces. The conf_reqd flag, when False, causes a control network
bridge to be added even if one has not been configured.
:param int net_index: network index
:param bool remove: flag to check if it should be removed
:param bool conf_required: flag to check if conf is required
:return: control net object
:rtype: core.netns.nodes.CtrlNet
"""
prefix_spec_list = self.get_control_lnet_prefixes()
prefix_spec = prefix_spec_list[net_index]
if not prefix_spec:
if conf_required:
return None # no controlnet needed
# no controlnet needed
return None
else:
control_net_class = nodeutils.get_node_class(NodeTypes.CONTROL_NET)
prefix_spec = control_net_class.DEFAULT_PREFIX_LIST[net_index]
@ -1041,7 +1223,8 @@ class Session(object):
prefix = prefixes[0].split(':', 1)[1]
except IndexError:
prefix = prefixes[0]
else: # len(prefixes) == 1
# len(prefixes) == 1
else:
# TODO: can we get the server name from the servers.conf or from the node assignments?
# with one prefix, only master gets a ctrlnet address
assign_address = self.master
@ -1053,6 +1236,7 @@ class Session(object):
updown_script=updown_script, serverintf=server_interface)
# tunnels between controlnets will be built with Broker.addnettunnels()
# TODO: potentialy remove documentation saying object ids are ints
self.broker.addnet(object_id)
for server in self.broker.getservers():
self.broker.addnodemap(server, object_id)
@ -1066,6 +1250,12 @@ class Session(object):
addremovectrlnet() to build or remove the control bridge.
If conf_reqd is False, the control network may be built even
when the user has not configured one (e.g. for EMANE.)
:param core.netns.nodes.CoreNode node: node to add or remove control interface
:param int net_index: network index
:param bool remove: flag to check if it should be removed
:param bool conf_required: flag to check if conf is required
:return: nothing
"""
control_net = self.add_remove_control_net(net_index, remove, conf_required)
if not control_net:
@ -1074,8 +1264,9 @@ class Session(object):
if not node:
return
# ctrl# already exists
if node.netif(control_net.CTRLIF_IDX_BASE + net_index):
return # ctrl# already exists
return
control_ip = node.objid
@ -1097,6 +1288,10 @@ class Session(object):
def update_control_interface_hosts(self, net_index=0, remove=False):
"""
Add the IP addresses of control interfaces to the /etc/hosts file.
:param int net_index: network index to update
:param bool remove: flag to check if it should be removed
:return: nothing
"""
if not self.get_config_item_bool("update_etc_hosts", False):
return
@ -1137,6 +1332,12 @@ class Session(object):
"""
Add an event to the event queue, with a start time relative to the
start of the runtime state.
:param event_time: event time
:param core.netns.nodes.CoreNode node: node to add event for
:param str name: name of event
:param data: data for event
:return: nothing
"""
event_time = float(event_time)
current_time = self.runtime()
@ -1156,6 +1357,11 @@ class Session(object):
def run_event(self, node_id=None, name=None, data=None):
"""
Run a scheduled event, executing commands in the data string.
:param int node_id: node id to run event
:param str name: event name
:param data: event data
:return: nothing
"""
now = self.runtime()
if not name:
@ -1266,6 +1472,9 @@ class Session(object):
class SessionConfig(ConfigurableManager, Configurable):
"""
Session configuration object.
"""
name = "session"
config_type = RegisterTlvs.UTILITY.value
config_matrix = [
@ -1291,6 +1500,11 @@ class SessionConfig(ConfigurableManager, Configurable):
self.reset()
def reset(self):
"""
Reset the session configuration.
:return: nothing
"""
defaults = self.getdefaultvalues()
for key in self.getnames():
# value may come from config file
@ -1302,6 +1516,8 @@ class SessionConfig(ConfigurableManager, Configurable):
def configure_values(self, config_data):
"""
Handle configuration values.
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:return: None
"""
@ -1309,6 +1525,7 @@ class SessionConfig(ConfigurableManager, Configurable):
def configure_request(self, config_data, type_flags=ConfigFlags.NONE.value):
"""
Handle a configuration request.
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:param type_flags:
@ -1325,11 +1542,15 @@ class SessionConfig(ConfigurableManager, Configurable):
return self.config_data(0, node_id, type_flags, values)
# TODO: update logic to not be tied to old style messages
def handle_distributed(self, message):
"""
Handle the session options config message as it has reached the
broker. Options requiring modification for distributed operation should
be handled here.
:param message: message to handle
:return: nothing
"""
if not self.session.master:
return
@ -1350,11 +1571,17 @@ class SessionConfig(ConfigurableManager, Configurable):
if key == "controlnet":
self.handle_distributed_control_net(message, value_strings, value_strings.index(value_string))
# TODO: update logic to not be tied to old style messages
def handle_distributed_control_net(self, message, values, index):
"""
Modify Config Message if multiple control network prefixes are
defined. Map server names to prefixes and repack the message before
it is forwarded to slave servers.
:param message: message to handle
:param list values: values to handle
:param int index: index ti get key value from
:return: nothing
"""
key_value = values[index]
key, value = key_value.split('=', 1)
@ -1391,8 +1618,10 @@ class SessionMetaData(ConfigurableManager):
def configure_values(self, config_data):
"""
Handle configuration values.
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:return:
:return: None
"""
values = config_data.data_values
if values is None:
@ -1411,17 +1640,28 @@ class SessionMetaData(ConfigurableManager):
def configure_request(self, config_data, type_flags=ConfigFlags.NONE.value):
"""
Handle a configuration request.
:param core.conf.ConfigData config_data: configuration data for carrying out a configuration
:param type_flags:
:return:
:param int type_flags: configuration request flag value
:return: configuration data
:rtype: ConfigData
"""
node_number = config_data.node
values_str = "|".join(map(lambda item: "%s=%s" % item, self.items()))
return self.config_data(0, node_number, type_flags, values_str)
def config_data(self, flags, node_id, type_flags, values_str):
"""
Retrieve configuration data object, leveraging provided data.
:param flags: configuration data flags
:param int node_id: node id
:param type_flags: type flags
:param values_str: values string
:return: configuration data
:rtype: ConfigData
"""
data_types = tuple(map(lambda (k, v): ConfigDataTypes.STRING.value, self.items()))
return ConfigData(
@ -1434,9 +1674,22 @@ class SessionMetaData(ConfigurableManager):
)
def add_item(self, key, value):
"""
Add configuration key/value pair.
:param key: configuration key
:param value: configuration value
:return: nothing
"""
self.configs[key] = value
def get_item(self, key):
"""
Retrieve configuration value.
:param key: key for configuration value to retrieve
:return: configuration value
"""
try:
return self.configs[key]
except KeyError:
@ -1445,7 +1698,13 @@ class SessionMetaData(ConfigurableManager):
return None
def items(self):
"""
Retrieve configuration items.
:return: configuration items iterator
"""
return self.configs.iteritems()
# configure the program exit function to run
atexit.register(SessionManager.on_exit)