995 lines
34 KiB
Python
995 lines
34 KiB
Python
import atexit
|
|
import os
|
|
import signal
|
|
import sys
|
|
|
|
import core.services
|
|
from core import logger
|
|
from core.coreobj import PyCoreNode, PyCoreNet
|
|
from core.data import NodeData
|
|
from core.emane.nodes import EmaneNode
|
|
from core.enumerations import NodeTypes, EventTypes, LinkTypes
|
|
from core.misc import nodeutils
|
|
from core.misc.ipaddress import Ipv4Prefix
|
|
from core.netns.nodes import CoreNode
|
|
from core.session import Session
|
|
from core.xml.xmlparser import core_document_parser
|
|
from core.xml.xmlwriter import core_document_writer
|
|
|
|
|
|
def signal_handler(signal_number, _):
|
|
"""
|
|
Handle signals and force an exit with cleanup.
|
|
|
|
:param int signal_number: signal number
|
|
:param _: ignored
|
|
:return: nothing
|
|
"""
|
|
logger.info("caught signal: %s", signal_number)
|
|
sys.exit(signal_number)
|
|
|
|
|
|
signal.signal(signal.SIGHUP, signal_handler)
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
signal.signal(signal.SIGUSR1, signal_handler)
|
|
signal.signal(signal.SIGUSR2, signal_handler)
|
|
|
|
|
|
class InterfaceData(object):
|
|
"""
|
|
Convenience class for storing interface data.
|
|
"""
|
|
|
|
def __init__(self, _id, name, mac, ip4, ip4_mask, ip6, ip6_mask):
|
|
"""
|
|
Creates an InterfaceData object.
|
|
|
|
:param int _id:
|
|
:param str name:
|
|
:param str mac:
|
|
:param str ip4:
|
|
:param int ip4_mask:
|
|
:param str ip6:
|
|
:param int ip6_mask:
|
|
"""
|
|
self.id = _id
|
|
self.name = name
|
|
self.mac = mac
|
|
self.ip4 = ip4
|
|
self.ip4_mask = ip4_mask
|
|
self.ip6 = ip6
|
|
self.ip6_mask = ip6_mask
|
|
|
|
def has_ip4(self):
|
|
return all([self.ip4, self.ip4_mask])
|
|
|
|
def has_ip6(self):
|
|
return all([self.ip6, self.ip6_mask])
|
|
|
|
def ip4_address(self):
|
|
if self.has_ip4():
|
|
return "%s/%s" % (self.ip4, self.ip4_mask)
|
|
else:
|
|
return None
|
|
|
|
def ip6_address(self):
|
|
if self.has_ip6():
|
|
return "%s/%s" % (self.ip6, self.ip6_mask)
|
|
else:
|
|
return None
|
|
|
|
def get_addresses(self):
|
|
ip4 = self.ip4_address()
|
|
ip6 = self.ip6_address()
|
|
return [i for i in [ip4, ip6] if i]
|
|
|
|
|
|
def get_interfaces(link_data):
|
|
"""
|
|
Creates interface data objects for the interfaces defined within link data.
|
|
|
|
:param core.data.LinkData link_data: data to create interface data from
|
|
:return: interface one and two data
|
|
:rtype: tuple[InterfaceData]
|
|
"""
|
|
interface_one = InterfaceData(
|
|
_id=link_data.interface1_id,
|
|
name=link_data.interface1_name,
|
|
mac=link_data.interface1_mac,
|
|
ip4=link_data.interface1_ip4,
|
|
ip4_mask=link_data.interface1_ip4_mask,
|
|
ip6=link_data.interface1_ip6,
|
|
ip6_mask=link_data.interface1_ip6_mask,
|
|
)
|
|
interface_two = InterfaceData(
|
|
_id=link_data.interface2_id,
|
|
name=link_data.interface2_name,
|
|
mac=link_data.interface2_mac,
|
|
ip4=link_data.interface2_ip4,
|
|
ip4_mask=link_data.interface2_ip4_mask,
|
|
ip6=link_data.interface2_ip6,
|
|
ip6_mask=link_data.interface2_ip6_mask,
|
|
)
|
|
return interface_one, interface_two
|
|
|
|
|
|
def create_interface(node, network, interface_data):
|
|
"""
|
|
Create an interface for a node on a network using provided interface data.
|
|
|
|
:param node: node to create interface for
|
|
:param network: network to associate interface with
|
|
:param InterfaceData interface_data: interface data
|
|
:return: created interface
|
|
"""
|
|
node.newnetif(
|
|
network,
|
|
addrlist=interface_data.get_addresses(),
|
|
hwaddr=interface_data.mac,
|
|
ifindex=interface_data.id,
|
|
ifname=interface_data.name
|
|
)
|
|
return node.netif(interface_data.id, network)
|
|
|
|
|
|
def link_config(network, interface, link_data, devname=None, interface_two=None):
|
|
"""
|
|
Convenience method for configuring a link,
|
|
|
|
:param network: network to configure link for
|
|
:param interface: interface to configure
|
|
:param core.data.LinkData link_data: data to configure link with
|
|
:param str devname: device name, default is None
|
|
:param interface_two: other interface associated, default is None
|
|
:return: nothing
|
|
"""
|
|
config = {
|
|
"netif": interface,
|
|
"bw": link_data.bandwidth,
|
|
"delay": link_data.delay,
|
|
"loss": link_data.per,
|
|
"duplicate": link_data.dup,
|
|
"jitter": link_data.jitter,
|
|
"netif2": interface_two
|
|
}
|
|
|
|
# hacky check here, because physical and emane nodes do not conform to the same linkconfig interface
|
|
if not nodeutils.is_node(network, [NodeTypes.EMANE, NodeTypes.PHYSICAL]):
|
|
config["devname"] = devname
|
|
|
|
network.linkconfig(**config)
|
|
|
|
|
|
def is_net_node(node):
|
|
"""
|
|
Convenience method for testing if a legacy core node is considered a network node.
|
|
|
|
:param object node: object to test against
|
|
:return: True if object is an instance of a network node, False otherwise
|
|
:rtype: bool
|
|
"""
|
|
return isinstance(node, PyCoreNet)
|
|
|
|
|
|
def is_core_node(node):
|
|
"""
|
|
Convenience method for testing if a legacy core node is considered a core node.
|
|
|
|
:param object node: object to test against
|
|
:return: True if object is an instance of a core node, False otherwise
|
|
:rtype: bool
|
|
"""
|
|
return isinstance(node, PyCoreNode)
|
|
|
|
|
|
class IdGen(object):
|
|
def __init__(self, _id=0):
|
|
self.id = _id
|
|
|
|
def next(self):
|
|
self.id += 1
|
|
return self.id
|
|
|
|
|
|
class FutureIpv4Prefix(Ipv4Prefix):
|
|
def get_address(self, node_id):
|
|
address = self.addr(node_id)
|
|
return "%s/%s" % (address, self.prefixlen)
|
|
|
|
|
|
class FutureSession(Session):
|
|
def __init__(self, session_id, config=None, persistent=True, mkdir=True):
|
|
super(FutureSession, self).__init__(session_id, config, persistent, mkdir)
|
|
|
|
# object management
|
|
self.node_id_gen = IdGen()
|
|
|
|
# set default services
|
|
self.services.defaultservices = {
|
|
"mdr": ("zebra", "OSPFv3MDR", "IPForward"),
|
|
"PC": ("DefaultRoute",),
|
|
"prouter": ("zebra", "OSPFv2", "OSPFv3", "IPForward"),
|
|
"router": ("zebra", "OSPFv2", "OSPFv3", "IPForward"),
|
|
"host": ("DefaultRoute", "SSH"),
|
|
}
|
|
|
|
def link_nodes(self, link_data):
|
|
"""
|
|
Convenience method for retrieving nodes within link data.
|
|
|
|
:param core.data.LinkData link_data: data to retrieve nodes from
|
|
:return: nodes, network nodes if presetn, and tunnel
|
|
:rtype: tuple
|
|
"""
|
|
logger.info("link message between node1(%s:%s) and node2(%s:%s)",
|
|
link_data.node1_id, link_data.interface1_id, link_data.node2_id, link_data.interface2_id)
|
|
|
|
# values to fill
|
|
net_one = None
|
|
net_two = None
|
|
|
|
# retrieve node one
|
|
n1_id = link_data.node1_id
|
|
n2_id = link_data.node2_id
|
|
node_one = self.get_object(n1_id)
|
|
node_two = self.get_object(n2_id)
|
|
|
|
# both node ids are provided
|
|
tunnel = self.broker.gettunnel(n1_id, n2_id)
|
|
logger.info("tunnel between nodes: %s", tunnel)
|
|
if nodeutils.is_node(tunnel, NodeTypes.TAP_BRIDGE):
|
|
net_one = tunnel
|
|
if tunnel.remotenum == n1_id:
|
|
node_one = None
|
|
else:
|
|
node_two = None
|
|
# physical node connected via gre tap tunnel
|
|
elif tunnel:
|
|
if tunnel.remotenum == n1_id:
|
|
node_one = None
|
|
else:
|
|
node_two = None
|
|
|
|
if is_net_node(node_one):
|
|
if not net_one:
|
|
net_one = node_one
|
|
else:
|
|
net_two = node_one
|
|
node_one = None
|
|
|
|
if is_net_node(node_two):
|
|
if not net_one:
|
|
net_one = node_two
|
|
else:
|
|
net_two = node_two
|
|
node_two = None
|
|
|
|
logger.info("link node types n1(%s) n2(%s) net1(%s) net2(%s) tunnel(%s)",
|
|
node_one, node_two, net_one, net_two, tunnel)
|
|
return node_one, node_two, net_one, net_two, tunnel
|
|
|
|
# TODO: this doesn't appear to ever be used, EMANE or basic wireless range
|
|
def _link_wireless(self, objects, connect):
|
|
"""
|
|
Objects to deal with when connecting/disconnecting wireless links.
|
|
|
|
:param list objects: possible objects to deal with
|
|
:param bool connect: link interfaces if True, unlink otherwise
|
|
:return: nothing
|
|
"""
|
|
objects = [x for x in objects if x]
|
|
if len(objects) < 2:
|
|
raise ValueError("wireless link failure: %s", objects)
|
|
logger.info("handling wireless linking objects(%) connect(%s)", objects, connect)
|
|
common_networks = objects[0].commonnets(objects[1])
|
|
for common_network, interface_one, interface_two in common_networks:
|
|
if not nodeutils.is_node(common_network, [NodeTypes.WIRELESS_LAN, NodeTypes.EMANE]):
|
|
logger.info("skipping common network that is not wireless/emane: %s", common_network)
|
|
continue
|
|
|
|
logger.info("wireless linking connect(%s): %s - %s", connect, interface_one, interface_two)
|
|
if connect:
|
|
common_network.link(interface_one, interface_two)
|
|
else:
|
|
common_network.unlink(interface_one, interface_two)
|
|
else:
|
|
raise ValueError("no common network found for wireless link/unlink")
|
|
|
|
def link_add(self, link_data):
|
|
"""
|
|
Add a link between nodes.
|
|
|
|
:param core.data.LinkData link_data: data to create a link with
|
|
:return: nothing
|
|
"""
|
|
# interface data
|
|
interface_one_data, interface_two_data = get_interfaces(link_data)
|
|
|
|
# get node objects identified by link data
|
|
node_one, node_two, net_one, net_two, tunnel = self.link_nodes(link_data)
|
|
|
|
if node_one:
|
|
node_one.lock.acquire()
|
|
if node_two:
|
|
node_two.lock.acquire()
|
|
|
|
try:
|
|
# wireless link
|
|
if link_data.link_type == LinkTypes.WIRELESS.value:
|
|
objects = [node_one, node_two, net_one, net_two]
|
|
self._link_wireless(objects, connect=True)
|
|
# wired link
|
|
else:
|
|
# 2 nodes being linked, ptp network
|
|
if all([node_one, node_two]) and not net_one:
|
|
ptp_class = nodeutils.get_node_class(NodeTypes.PEER_TO_PEER)
|
|
start = self.state > EventTypes.DEFINITION_STATE.value
|
|
net_one = self.add_object(cls=ptp_class, start=start)
|
|
|
|
# node to network
|
|
if node_one and net_one:
|
|
interface = create_interface(node_one, net_one, interface_one_data)
|
|
link_config(net_one, interface, link_data)
|
|
|
|
# network to node
|
|
if node_two and net_one:
|
|
interface = create_interface(node_two, net_one, interface_two_data)
|
|
if not link_data.unidirectional:
|
|
link_config(net_one, interface, link_data)
|
|
|
|
# network to network
|
|
if net_one and net_two:
|
|
if nodeutils.is_node(net_two, NodeTypes.RJ45):
|
|
interface = net_two.linknet(net_one)
|
|
else:
|
|
interface = net_one.linknet(net_two)
|
|
|
|
link_config(net_one, interface, link_data)
|
|
|
|
if not link_data.unidirectional:
|
|
interface.swapparams("_params_up")
|
|
link_config(net_two, interface, link_data, devname=interface.name)
|
|
interface.swapparams("_params_up")
|
|
|
|
# a tunnel node was found for the nodes
|
|
addresses = []
|
|
if not node_one and net_one:
|
|
addresses.extend(interface_one_data.get_addresses())
|
|
|
|
if not node_two and net_two:
|
|
addresses.extend(interface_two_data.get_addresses())
|
|
|
|
# tunnel node logic
|
|
key = link_data.key
|
|
if key and nodeutils.is_node(net_one, NodeTypes.TUNNEL):
|
|
net_one.setkey(key)
|
|
if addresses:
|
|
net_one.addrconfig(addresses)
|
|
if key and nodeutils.is_node(net_two, NodeTypes.TUNNEL):
|
|
net_two.setkey(key)
|
|
if addresses:
|
|
net_two.addrconfig(addresses)
|
|
|
|
# physical node connected with tunnel
|
|
if not net_one and not net_two and (node_one or node_two):
|
|
if node_one and nodeutils.is_node(node_one, NodeTypes.PHYSICAL):
|
|
addresses = interface_one_data.get_addresses()
|
|
node_one.adoptnetif(tunnel, link_data.interface1_id, link_data.interface1_mac, addresses)
|
|
link_config(node_one, tunnel, link_data)
|
|
elif node_two and nodeutils.is_node(node_two, NodeTypes.PHYSICAL):
|
|
addresses = interface_two_data.get_addresses()
|
|
node_two.adoptnetif(tunnel, link_data.interface2_id, link_data.interface2_mac, addresses)
|
|
link_config(node_two, tunnel, link_data)
|
|
finally:
|
|
if node_one:
|
|
node_one.lock.release()
|
|
if node_two:
|
|
node_two.lock.release()
|
|
|
|
def link_delete(self, link_data):
|
|
"""
|
|
Delete a link between nodes.
|
|
|
|
:param core.data.LinkData link_data: data to delete link with
|
|
:return: nothing
|
|
"""
|
|
# interface data
|
|
interface_one_data, interface_two_data = get_interfaces(link_data)
|
|
|
|
# get node objects identified by link data
|
|
node_one, node_two, net_one, net_two, tunnel = self.link_nodes(link_data)
|
|
|
|
if node_one:
|
|
node_one.lock.acquire()
|
|
if node_two:
|
|
node_two.lock.acquire()
|
|
|
|
try:
|
|
# wireless link
|
|
if link_data.link_type == LinkTypes.WIRELESS.value:
|
|
objects = [node_one, node_two, net_one, net_two]
|
|
self._link_wireless(objects, connect=False)
|
|
# wired link
|
|
else:
|
|
if all([node_one, node_two]):
|
|
# TODO: fix this for the case where ifindex[1,2] are not specified
|
|
# a wired unlink event, delete the connecting bridge
|
|
interface_one = node_one.netif(interface_one_data.id)
|
|
interface_two = node_two.netif(interface_two_data.id)
|
|
|
|
# get interfaces from common network, if no network node
|
|
# otherwise get interfaces between a node and network
|
|
if not interface_one and not interface_two:
|
|
common_networks = node_one.commonnets(node_two)
|
|
for network, common_interface_one, common_interface_two in common_networks:
|
|
if (net_one and network == net_one) or not net_one:
|
|
interface_one = common_interface_one
|
|
interface_two = common_interface_two
|
|
break
|
|
|
|
logger.info("deleting link for interfaces interface_one(%s) interface_two(%s)",
|
|
interface_one, interface_two)
|
|
if all([interface_one, interface_two]) and any([interface_one.net, interface_two.net]):
|
|
if interface_one.net != interface_two.net and all([interface_one.up, interface_two.up]):
|
|
raise ValueError("no common network found")
|
|
net_one = interface_one.net
|
|
interface_one.detachnet()
|
|
interface_two.detachnet()
|
|
if net_one.numnetif() == 0:
|
|
self.delete_object(net_one.objid)
|
|
node_one.delnetif(interface_one.netindex)
|
|
node_two.delnetif(interface_two.netindex)
|
|
finally:
|
|
if node_one:
|
|
node_one.lock.release()
|
|
if node_two:
|
|
node_two.lock.release()
|
|
|
|
def link_update(self, link_data):
|
|
"""
|
|
Update link information between nodes.
|
|
|
|
:param core.data.LinkData link_data: data to update link with
|
|
:return: nothing
|
|
"""
|
|
# interface data
|
|
interface_one_data, interface_two_data = get_interfaces(link_data)
|
|
|
|
# get node objects identified by link data
|
|
node_one, node_two, net_one, net_two, tunnel = self.link_nodes(link_data)
|
|
|
|
if node_one:
|
|
node_one.lock.acquire()
|
|
if node_two:
|
|
node_two.lock.acquire()
|
|
|
|
try:
|
|
# wireless link
|
|
if link_data.link_type == LinkTypes.WIRELESS.value:
|
|
raise ValueError("cannot update wireless link")
|
|
else:
|
|
if not node_one and not node_two:
|
|
if net_one and net_two:
|
|
# modify link between nets
|
|
interface = net_one.getlinknetif(net_two)
|
|
upstream = False
|
|
|
|
if not interface:
|
|
upstream = True
|
|
interface = net_two.getlinknetif(net_one)
|
|
|
|
if not interface:
|
|
raise ValueError("modify unknown link between nets")
|
|
|
|
if upstream:
|
|
interface.swapparams("_params_up")
|
|
link_config(net_one, interface, link_data, devname=interface.name)
|
|
interface.swapparams("_params_up")
|
|
else:
|
|
link_config(net_one, interface, link_data)
|
|
|
|
if not link_data.unidirectional:
|
|
if upstream:
|
|
link_config(net_two, interface, link_data)
|
|
else:
|
|
interface.swapparams("_params_up")
|
|
link_config(net_two, interface, link_data, devname=interface.name)
|
|
interface.swapparams("_params_up")
|
|
else:
|
|
raise ValueError("modify link for unknown nodes")
|
|
elif not node_one:
|
|
# node1 = layer 2node, node2 = layer3 node
|
|
interface = node_two.netif(interface_two_data.id, net_one)
|
|
link_config(net_one, interface, link_data)
|
|
elif not node_two:
|
|
# node2 = layer 2node, node1 = layer3 node
|
|
interface = node_one.netif(interface_one_data.id, net_one)
|
|
link_config(net_one, interface, link_data)
|
|
else:
|
|
common_networks = node_one.commonnets(node_two)
|
|
for net_one, interface_one, interface_two in common_networks:
|
|
if interface_one_data.id and interface_one_data.id != node_one.getifindex(interface_one):
|
|
continue
|
|
|
|
link_config(net_one, interface_one, link_data, interface_two=interface_two)
|
|
if not link_data.unidirectional:
|
|
link_config(net_one, interface_two, link_data, interface_two=interface_one)
|
|
else:
|
|
raise ValueError("no common network found")
|
|
finally:
|
|
if node_one:
|
|
node_one.lock.release()
|
|
if node_two:
|
|
node_two.lock.release()
|
|
|
|
def node_add(self, node_data):
|
|
"""
|
|
Add a node to the session, based on the provided node data.
|
|
|
|
:param core.data.NodeData node_data: data to create node with
|
|
:return: nothing
|
|
"""
|
|
|
|
# retrieve node class for given node type
|
|
try:
|
|
node_type = NodeTypes(node_data.node_type)
|
|
node_class = nodeutils.get_node_class(node_type)
|
|
except KeyError:
|
|
logger.error("invalid node type to create: %s", node_data.node_type)
|
|
return None
|
|
|
|
# set node start based on current session state, override and check when rj45
|
|
start = self.state > EventTypes.DEFINITION_STATE.value
|
|
enable_rj45 = getattr(self.options, "enablerj45", "0") == "1"
|
|
if node_type == NodeTypes.RJ45 and not enable_rj45:
|
|
start = False
|
|
|
|
# determine node id
|
|
node_id = node_data.id
|
|
if not node_id:
|
|
while True:
|
|
node_id = self.node_id_gen.next()
|
|
if node_id not in self.objects:
|
|
break
|
|
|
|
# generate name if not provided
|
|
name = node_data.name
|
|
if not name:
|
|
name = "%s%s" % (node_class.__name__, node_id)
|
|
|
|
# create node
|
|
logger.info("creating node(%s) id(%s) name(%s) start(%s)", node_class, node_id, name, start)
|
|
node = self.add_object(cls=node_class, objid=node_id, name=name, start=start)
|
|
|
|
# set node attributes
|
|
node.type = node_data.model or "router"
|
|
node.icon = node_data.icon
|
|
node.canvas = node_data.canvas
|
|
node.opaque = node_data.opaque
|
|
|
|
# set node position and broadcast it
|
|
self.node_set_position(node, node_data)
|
|
|
|
# add services to default and physical nodes only
|
|
services = node_data.services
|
|
if node_type in [NodeTypes.DEFAULT, NodeTypes.PHYSICAL]:
|
|
logger.info("setting model (%s) with services (%s)", node.type, services)
|
|
self.services.addservicestonode(node, node.type, services)
|
|
|
|
# boot nodes if created after runtime, LcxNodes, Physical, and RJ45 are all PyCoreNodes
|
|
is_boot_node = isinstance(node, PyCoreNode) and not nodeutils.is_node(node, NodeTypes.RJ45)
|
|
if self.state == EventTypes.RUNTIME_STATE.value and is_boot_node:
|
|
self.write_objects()
|
|
self.add_remove_control_interface(node=node, remove=False)
|
|
|
|
# TODO: common method to both Physical and LxcNodes, but not the common PyCoreNode
|
|
node.boot()
|
|
|
|
# return node id, in case it was generated
|
|
return node_id
|
|
|
|
def node_update(self, node_data):
|
|
"""
|
|
Update node information.
|
|
|
|
:param core.data.NodeData node_data: data to update node with
|
|
:return: nothing
|
|
"""
|
|
try:
|
|
# get node to update
|
|
node = self.get_object(node_data.id)
|
|
|
|
# set node position and broadcast it
|
|
self.node_set_position(node, node_data)
|
|
|
|
# update attributes
|
|
node.canvas = node_data.canvas
|
|
node.icon = node_data.icon
|
|
except KeyError:
|
|
logger.error("failure to update node that does not exist: %s", node_data.id)
|
|
|
|
def node_delete(self, node_id):
|
|
"""
|
|
Delete a node from the session and check if session should shutdown, if no nodes are left.
|
|
|
|
:param int node_id:
|
|
:return: True if node deleted, False otherwise
|
|
"""
|
|
# delete node and check for session shutdown if a node was removed
|
|
result = self.custom_delete_object(node_id)
|
|
if result:
|
|
self.check_shutdown()
|
|
return result
|
|
|
|
def node_set_position(self, node, node_data):
|
|
"""
|
|
Set position for a node, use lat/lon/alt if needed.
|
|
|
|
:param node: node to set position for
|
|
:param NodeData node_data: data for node
|
|
:return: nothing
|
|
"""
|
|
# extract location values
|
|
x = node_data.x_position
|
|
y = node_data.y_position
|
|
lat = node_data.latitude
|
|
lon = node_data.longitude
|
|
alt = node_data.altitude
|
|
|
|
# check if we need to generate position from lat/lon/alt
|
|
has_empty_position = all(i is None for i in [x, y])
|
|
has_lat_lon_alt = all(i is not None for i in [lat, lon, alt])
|
|
using_lat_lon_alt = has_empty_position and has_lat_lon_alt
|
|
if using_lat_lon_alt:
|
|
x, y, _ = self.location.getxyz(lat, lon, alt)
|
|
|
|
# set position and broadcast
|
|
node.setposition(x, y, None)
|
|
|
|
# broadcast updated location when using lat/lon/alt
|
|
if using_lat_lon_alt:
|
|
self.broadcast_node_location(node)
|
|
|
|
def broadcast_node_location(self, node):
|
|
"""
|
|
Broadcast node location to all listeners.
|
|
|
|
:param core.netns.nodes.PyCoreObj node: node to broadcast location for
|
|
:return: nothing
|
|
"""
|
|
node_data = NodeData(
|
|
message_type=0,
|
|
id=node.objid,
|
|
x_position=node.position.x,
|
|
y_position=node.position.y
|
|
)
|
|
self.broadcast_node(node_data)
|
|
|
|
def start_mobility(self, node_ids=None):
|
|
"""
|
|
Start mobility for the provided node ids.
|
|
|
|
:param list[int] node_ids: nodes to start mobility for
|
|
:return: nothing
|
|
"""
|
|
self.mobility.startup(node_ids)
|
|
|
|
def shutdown(self):
|
|
"""
|
|
Shutdown session.
|
|
|
|
:return: nothing
|
|
"""
|
|
self.set_state(state=EventTypes.DATACOLLECT_STATE.value, send_event=True)
|
|
self.set_state(state=EventTypes.SHUTDOWN_STATE.value, send_event=True)
|
|
super(FutureSession, self).shutdown()
|
|
|
|
def custom_delete_object(self, object_id):
|
|
"""
|
|
Remove an emulation object.
|
|
|
|
:param int object_id: object id to remove
|
|
:return: True if object deleted, False otherwise
|
|
"""
|
|
result = False
|
|
with self._objects_lock:
|
|
if object_id in self.objects:
|
|
obj = self.objects.pop(object_id)
|
|
obj.shutdown()
|
|
result = True
|
|
return result
|
|
|
|
def is_active(self):
|
|
"""
|
|
Determine if this session is considered to be active. (Runtime or Data collect states)
|
|
|
|
:return: True if active, False otherwise
|
|
"""
|
|
result = self.state in {EventTypes.RUNTIME_STATE.value, EventTypes.DATACOLLECT_STATE.value}
|
|
logger.info("checking if session is active: %s", result)
|
|
return result
|
|
|
|
def open_xml(self, file_name, start=False):
|
|
"""
|
|
Import a session from the EmulationScript XML format.
|
|
|
|
:param str file_name: xml file to load session from
|
|
:param bool start: instantiate session if true, false otherwise
|
|
:return: nothing
|
|
"""
|
|
# clear out existing session
|
|
self.clear()
|
|
|
|
# set default node class when one is not provided
|
|
node_class = nodeutils.get_node_class(NodeTypes.DEFAULT)
|
|
options = {"start": start, "nodecls": node_class}
|
|
core_document_parser(self, file_name, options)
|
|
if start:
|
|
self.name = os.path.basename(file_name)
|
|
self.file_name = file_name
|
|
self.instantiate()
|
|
|
|
def save_xml(self, file_name, version):
|
|
"""
|
|
Export a session to the EmulationScript XML format.
|
|
|
|
:param str file_name: file name to write session xml to
|
|
:param str version: xml version type
|
|
:return: nothing
|
|
"""
|
|
doc = core_document_writer(self, version)
|
|
doc.writexml(file_name)
|
|
|
|
def hook_add(self, state, file_name, source_name, data):
|
|
"""
|
|
Store a hook from a received file message.
|
|
|
|
:param int state: when to run hook
|
|
:param str file_name: file name for hook
|
|
:param str source_name: source name
|
|
:param data: hook data
|
|
:return: nothing
|
|
"""
|
|
# hack to conform with old logic until updated
|
|
state = ":%s" % state
|
|
self.set_hook(state, file_name, source_name, data)
|
|
|
|
def node_service_file(self, node_id, service_name, file_name, source_name, data):
|
|
"""
|
|
Add a service file for a node.
|
|
|
|
:param int node_id: node to add service file to
|
|
:param str service_name: service file to add
|
|
:param str file_name: file name to use
|
|
:param str source_name: source file
|
|
:param str data: file data to save
|
|
:return: nothing
|
|
"""
|
|
# hack to conform with old logic until updated
|
|
service_name = ":%s" % service_name
|
|
self.services.setservicefile(node_id, service_name, file_name, source_name, data)
|
|
|
|
def node_file(self, node_id, source_name, file_name, data):
|
|
"""
|
|
Add a file to a node.
|
|
|
|
:param int node_id: node to add file to
|
|
:param str source_name: source file name
|
|
:param str file_name: file name to add
|
|
:param str data: file data
|
|
:return: nothing
|
|
"""
|
|
|
|
node = self.get_object(node_id)
|
|
|
|
if source_name is not None:
|
|
node.addfile(source_name, file_name)
|
|
elif data is not None:
|
|
node.nodefile(file_name, data)
|
|
|
|
def clear(self):
|
|
"""
|
|
Clear all CORE session data. (objects, hooks, broker)
|
|
|
|
:return: nothing
|
|
"""
|
|
self.delete_objects()
|
|
self.del_hooks()
|
|
self.broker.reset()
|
|
|
|
def start_events(self):
|
|
"""
|
|
Start event loop.
|
|
|
|
:return: nothing
|
|
"""
|
|
self.event_loop.run()
|
|
|
|
def services_event(self, event_data):
|
|
"""
|
|
Handle a service event.
|
|
|
|
:param core.data.EventData event_data: event data to handle
|
|
:return:
|
|
"""
|
|
self.services.handleevent(event_data)
|
|
|
|
def mobility_event(self, event_data):
|
|
"""
|
|
Handle a mobility event.
|
|
|
|
:param core.data.EventData event_data: event data to handle
|
|
:return: nothing
|
|
"""
|
|
self.mobility.handleevent(event_data)
|
|
|
|
def create_node(self, cls, name=None, model=None):
|
|
"""
|
|
Create a node
|
|
|
|
:param cls:
|
|
:param name:
|
|
:param model:
|
|
:return:
|
|
"""
|
|
object_id = self.node_id_gen.next()
|
|
|
|
if not name:
|
|
name = "%s%s" % (cls.__name__, object_id)
|
|
|
|
node = self.add_object(cls=cls, name=name, objid=object_id)
|
|
node.type = model
|
|
if node.type:
|
|
self.services.addservicestonode(node, node.type, services_str=None)
|
|
|
|
return node
|
|
|
|
def create_emane_node(self, name=None):
|
|
"""
|
|
Create an EMANE node for use within an EMANE network.
|
|
|
|
:param str name: name to five node
|
|
:return: CoreNode
|
|
"""
|
|
return self.create_node(cls=CoreNode, name=name, model="mdr")
|
|
|
|
def create_emane_network(self, model, geo_reference, geo_scale=None, name=None):
|
|
"""
|
|
Convenience method for creating an emane network.
|
|
|
|
:param model: emane model to use for emane network
|
|
:param geo_reference: geo reference point to use for emane node locations
|
|
:param geo_scale: geo scale to use for emane node locations, defaults to 1.0
|
|
:param name: name for emane network, defaults to node class name
|
|
:return: create emane network
|
|
"""
|
|
# required to be set for emane to function properly
|
|
self.location.setrefgeo(*geo_reference)
|
|
if geo_scale:
|
|
self.location.refscale = geo_scale
|
|
|
|
# create and return network
|
|
emane_network = self.create_node(cls=EmaneNode, name=name)
|
|
self.set_emane_model(emane_network, model)
|
|
return emane_network
|
|
|
|
def set_emane_model(self, emane_node, model):
|
|
"""
|
|
Set emane model for a given emane node.
|
|
|
|
:param emane_node: emane node to set model for
|
|
:param model: emane model to set
|
|
:return: nothing
|
|
"""
|
|
values = list(model.getdefaultvalues())
|
|
self.emane.setconfig(emane_node.objid, model.name, values)
|
|
|
|
|
|
class CoreEmu(object):
|
|
"""
|
|
Provides logic for creating and configuring CORE sessions and the nodes within them.
|
|
"""
|
|
|
|
def __init__(self, config=None):
|
|
"""
|
|
Create a CoreEmu object.
|
|
|
|
:param dict config: configuration options
|
|
"""
|
|
# configuration
|
|
self.config = config
|
|
|
|
# session management
|
|
self.session_id_gen = IdGen(_id=59999)
|
|
self.sessions = {}
|
|
|
|
# load default services
|
|
core.services.load()
|
|
|
|
# catch exit event
|
|
atexit.register(self.shutdown)
|
|
|
|
def shutdown(self):
|
|
"""
|
|
Shutdown all CORE session.
|
|
|
|
:return: nothing
|
|
"""
|
|
logger.info("shutting down all session")
|
|
for session in self.sessions.values():
|
|
session.shutdown()
|
|
self.sessions.clear()
|
|
|
|
def create_session(self, _id=None, master=False):
|
|
"""
|
|
Create a new CORE session, set to master if running standalone.
|
|
|
|
:param int _id: session id for new session
|
|
:param bool master: sets session to master
|
|
:return: created session
|
|
:rtype: FutureSession
|
|
"""
|
|
|
|
session_id = _id
|
|
if not session_id:
|
|
while True:
|
|
session_id = self.session_id_gen.next()
|
|
if session_id not in self.sessions:
|
|
break
|
|
|
|
session = FutureSession(session_id, config=self.config)
|
|
logger.info("created session: %s", session_id)
|
|
if master:
|
|
session.master = True
|
|
|
|
self.sessions[session_id] = session
|
|
return session
|
|
|
|
def delete_session(self, _id):
|
|
"""
|
|
Deletes a CORE session.
|
|
|
|
:param int _id: session id to delete
|
|
:return: nothing
|
|
"""
|
|
logger.info("deleting session: %s", _id)
|
|
session = self.sessions.pop(_id, None)
|
|
if not session:
|
|
logger.error("session to delete did not exist: %s", _id)
|
|
|
|
def set_wireless_model(self, node, model):
|
|
"""
|
|
Convenience method for setting a wireless model.
|
|
|
|
:param node: node to set wireless model for
|
|
:param core.mobility.WirelessModel model: wireless model to set node to
|
|
:return: nothing
|
|
"""
|
|
values = list(model.getdefaultvalues())
|
|
node.setmodel(model, values)
|
|
|
|
def wireless_link_all(self, network, nodes):
|
|
"""
|
|
Link all nodes to the provided wireless network.
|
|
|
|
:param network: wireless network to link nodes to
|
|
:param nodes: nodes to link to wireless network
|
|
:return: nothing
|
|
"""
|
|
for node in nodes:
|
|
for common_network, interface_one, interface_two in node.commonnets(network):
|
|
common_network.link(interface_one, interface_two)
|
|
|
|
def add_interface(self, network, node, prefix):
|
|
"""
|
|
Convenience method for adding an interface with a prefix based on node id.
|
|
|
|
:param network: network to add interface with
|
|
:param node: node to add interface to
|
|
:param prefix: prefix to get address from for interface
|
|
:return: created interface
|
|
"""
|
|
address = prefix.get_address(node.objid)
|
|
interface_index = node.newnetif(network, [address])
|
|
return node.netif(interface_index)
|