quick base to try and help flesh out documentation under core.*
This commit is contained in:
parent
8f45e5c4da
commit
4ae7958a63
15 changed files with 1956 additions and 292 deletions
|
@ -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 *
|
||||
|
|
|
@ -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__ = []
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
Contains code specific to the legacy TCP API for interacting with the TCL based GUI.
|
||||
"""
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue