From 2e3e085522f98bea7a010cd80703970f791e9fd7 Mon Sep 17 00:00:00 2001 From: Blake Harnden <32446120+bharnden@users.noreply.github.com> Date: Wed, 25 May 2022 10:51:42 -0700 Subject: [PATCH] daemon: adjustments to revamp how core nodes are created in session.add_node, nodes now provide a create_options function for node specific options that are type hinted --- daemon/core/api/grpc/grpcutils.py | 66 +++--- daemon/core/api/grpc/server.py | 19 +- daemon/core/emane/emanemanager.py | 2 +- daemon/core/emane/nodes.py | 22 +- daemon/core/emulator/session.py | 152 +++++--------- daemon/core/nodes/base.py | 210 ++++++++++++------- daemon/core/nodes/docker.py | 43 ++-- daemon/core/nodes/lxd.py | 35 +++- daemon/core/nodes/network.py | 78 ++++--- daemon/core/nodes/physical.py | 10 +- daemon/core/nodes/wireless.py | 5 +- daemon/core/xml/corexml.py | 72 ++++--- daemon/examples/configservices/testing.py | 4 +- daemon/examples/docker/docker2core.py | 5 +- daemon/examples/docker/docker2docker.py | 5 +- daemon/examples/docker/switch.py | 16 +- daemon/examples/lxd/lxd2core.py | 5 +- daemon/examples/lxd/lxd2lxd.py | 5 +- daemon/examples/lxd/switch.py | 5 +- daemon/examples/python/distributed_emane.py | 12 +- daemon/examples/python/distributed_lxd.py | 5 +- daemon/examples/python/distributed_ptp.py | 8 +- daemon/examples/python/distributed_switch.py | 4 +- daemon/examples/python/emane80211.py | 22 +- daemon/examples/python/peertopeer.py | 12 +- daemon/examples/python/switch.py | 16 +- daemon/examples/python/wireless.py | 20 +- daemon/examples/python/wlan.py | 20 +- daemon/tests/emane/test_emane.py | 99 ++++----- daemon/tests/test_core.py | 10 +- daemon/tests/test_distributed.py | 7 +- daemon/tests/test_grpc.py | 20 +- daemon/tests/test_nodes.py | 5 +- daemon/tests/test_xml.py | 9 +- docs/python.md | 96 +++++---- 35 files changed, 646 insertions(+), 478 deletions(-) diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 2c3996ba..7f63079c 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -17,15 +17,22 @@ from core.api.grpc.services_pb2 import ( ServiceDefaults, ) from core.config import ConfigurableOptions -from core.emane.nodes import EmaneNet -from core.emulator.data import InterfaceData, LinkData, LinkOptions, NodeOptions +from core.emane.nodes import EmaneNet, EmaneOptions +from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.links import CoreLink from core.emulator.session import Session from core.errors import CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility -from core.nodes.base import CoreNode, CoreNodeBase, NodeBase -from core.nodes.docker import DockerNode +from core.nodes.base import ( + CoreNode, + CoreNodeBase, + CoreNodeOptions, + NodeBase, + NodeOptions, + Position, +) +from core.nodes.docker import DockerNode, DockerOptions from core.nodes.interface import CoreInterface from core.nodes.lxd import LxcNode from core.nodes.network import CoreNetwork, CtrlNet, PtpNet, WlanNode @@ -55,34 +62,33 @@ class CpuUsage: return (total_diff - idle_diff) / total_diff -def add_node_data(node_proto: core_pb2.Node) -> Tuple[NodeTypes, int, NodeOptions]: +def add_node_data( + _class: Type[NodeBase], node_proto: core_pb2.Node +) -> Tuple[Position, NodeOptions]: """ Convert node protobuf message to data for creating a node. + :param _class: node class to create options from :param node_proto: node proto message :return: node type, id, and options """ - _id = node_proto.id - _type = NodeTypes(node_proto.type) - options = NodeOptions( - name=node_proto.name, - model=node_proto.model, - icon=node_proto.icon, - image=node_proto.image, - services=node_proto.services, - config_services=node_proto.config_services, - canvas=node_proto.canvas, - ) - if node_proto.emane: - options.emane = node_proto.emane - if node_proto.server: - options.server = node_proto.server - position = node_proto.position - options.set_position(position.x, position.y) + options = _class.create_options() + options.icon = node_proto.icon + options.canvas = node_proto.canvas + if isinstance(options, CoreNodeOptions): + options.model = node_proto.model + options.services = node_proto.services + options.config_services = node_proto.config_services + if isinstance(options, EmaneOptions): + options.emane_model = node_proto.emane + if isinstance(options, DockerOptions): + options.image = node_proto.image + position = Position() + position.set(node_proto.position.x, node_proto.position.y) if node_proto.HasField("geo"): geo = node_proto.geo - options.set_location(geo.lat, geo.lon, geo.alt) - return _type, _id, options + position.set_geo(geo.lon, geo.lat, geo.alt) + return position, options def link_iface(iface_proto: core_pb2.Interface) -> InterfaceData: @@ -150,9 +156,17 @@ def create_nodes( """ funcs = [] for node_proto in node_protos: - _type, _id, options = add_node_data(node_proto) + _type = NodeTypes(node_proto.type) _class = session.get_node_class(_type) - args = (_class, _id, options) + position, options = add_node_data(_class, node_proto) + args = ( + _class, + node_proto.id or None, + node_proto.name or None, + node_proto.server or None, + position, + options, + ) funcs.append((session.add_node, args, {})) start = time.monotonic() results, exceptions = utils.threadpool(funcs) diff --git a/daemon/core/api/grpc/server.py b/daemon/core/api/grpc/server.py index 3846701b..532dd91c 100644 --- a/daemon/core/api/grpc/server.py +++ b/daemon/core/api/grpc/server.py @@ -88,7 +88,12 @@ from core.configservice.base import ConfigServiceBootError from core.emane.modelmanager import EmaneModelManager from core.emulator.coreemu import CoreEmu from core.emulator.data import InterfaceData, LinkData, LinkOptions -from core.emulator.enumerations import EventTypes, ExceptionLevels, MessageFlags +from core.emulator.enumerations import ( + EventTypes, + ExceptionLevels, + MessageFlags, + NodeTypes, +) from core.emulator.session import NT, Session from core.errors import CoreCommandError, CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility @@ -548,9 +553,17 @@ class CoreGrpcServer(core_pb2_grpc.CoreApiServicer): """ logger.debug("add node: %s", request) session = self.get_session(request.session_id, context) - _type, _id, options = grpcutils.add_node_data(request.node) + _type = NodeTypes(request.node.type) _class = session.get_node_class(_type) - node = session.add_node(_class, _id, options) + position, options = grpcutils.add_node_data(_class, request.node) + node = session.add_node( + _class, + request.node.id or None, + request.node.name or None, + request.node.server or None, + position, + options, + ) grpcutils.configure_node(session, request.node, node, context) source = request.source if request.source else None session.broadcast_node(node, MessageFlags.ADD, source) diff --git a/daemon/core/emane/emanemanager.py b/daemon/core/emane/emanemanager.py index 92fdf93b..32949301 100644 --- a/daemon/core/emane/emanemanager.py +++ b/daemon/core/emane/emanemanager.py @@ -603,7 +603,7 @@ class EmaneManager: node = iface.node loglevel = str(DEFAULT_LOG_LEVEL) cfgloglevel = self.session.options.get_int("emane_log_level", 2) - realtime = self.session.options.get_bool("emane_realtime") + realtime = self.session.options.get_bool("emane_realtime", True) if cfgloglevel: logger.info("setting user-defined emane log level: %d", cfgloglevel) loglevel = str(cfgloglevel) diff --git a/daemon/core/emane/nodes.py b/daemon/core/emane/nodes.py index b8809dba..1a0b6e75 100644 --- a/daemon/core/emane/nodes.py +++ b/daemon/core/emane/nodes.py @@ -5,13 +5,14 @@ share the same MAC+PHY model. import logging import time +from dataclasses import dataclass from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type, Union from core.emulator.data import InterfaceData, LinkData, LinkOptions from core.emulator.distributed import DistributedServer from core.emulator.enumerations import EventTypes, MessageFlags, RegisterTlvs from core.errors import CoreCommandError, CoreError -from core.nodes.base import CoreNetworkBase, CoreNode +from core.nodes.base import CoreNetworkBase, CoreNode, NodeOptions from core.nodes.interface import CoreInterface logger = logging.getLogger(__name__) @@ -139,6 +140,12 @@ class TunTap(CoreInterface): self.node.node_net_client.create_address(self.name, str(ip)) +@dataclass +class EmaneOptions(NodeOptions): + emane_model: str = None + """name of emane model to associate an emane network to""" + + class EmaneNet(CoreNetworkBase): """ EMANE node contains NEM configuration and causes connected nodes @@ -152,11 +159,20 @@ class EmaneNet(CoreNetworkBase): _id: int = None, name: str = None, server: DistributedServer = None, + options: EmaneOptions = None, ) -> None: - super().__init__(session, _id, name, server) + options = options or EmaneOptions() + super().__init__(session, _id, name, server, options) self.conf: str = "" - self.wireless_model: Optional["EmaneModel"] = None self.mobility: Optional[WayPointMobility] = None + model_class = self.session.emane.get_model(options.emane_model) + self.wireless_model: Optional["EmaneModel"] = model_class(self.session, self.id) + if self.session.state == EventTypes.RUNTIME_STATE: + self.session.emane.add_node(self) + + @classmethod + def create_options(cls) -> EmaneOptions: + return EmaneOptions() def linkconfig( self, iface: CoreInterface, options: LinkOptions, iface2: CoreInterface = None diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 5064579f..4444e248 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -14,7 +14,7 @@ import tempfile import threading import time from pathlib import Path -from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union +from typing import Callable, Dict, List, Optional, Set, Tuple, Type, TypeVar, Union from core import constants, utils from core.configservice.manager import ConfigServiceManager @@ -29,7 +29,6 @@ from core.emulator.data import ( LinkData, LinkOptions, NodeData, - NodeOptions, ) from core.emulator.distributed import DistributedController from core.emulator.enumerations import ( @@ -44,7 +43,7 @@ from core.errors import CoreError from core.location.event import EventLoop from core.location.geo import GeoLocation from core.location.mobility import BasicRangeModel, MobilityManager -from core.nodes.base import CoreNetworkBase, CoreNode, CoreNodeBase, NodeBase +from core.nodes.base import CoreNode, CoreNodeBase, NodeBase, NodeOptions, Position from core.nodes.docker import DockerNode from core.nodes.interface import DEFAULT_MTU, CoreInterface from core.nodes.lxd import LxcNode @@ -476,14 +475,23 @@ class Session: return _id def add_node( - self, _class: Type[NT], _id: int = None, options: NodeOptions = None + self, + _class: Type[NT], + _id: int = None, + name: str = None, + server: str = None, + position: Position = None, + options: NodeOptions = None, ) -> NT: """ Add a node to the session, based on the provided node data. :param _class: node class to create :param _id: id for node, defaults to None for generated id - :param options: data to create node with + :param name: name to assign to node + :param server: distributed server for node, if desired + :param position: geo or x/y/z position to set + :param options: options to create node with :return: created node :raises core.CoreError: when an invalid node type is given """ @@ -492,89 +500,31 @@ class Session: enable_rj45 = self.options.get_int("enablerj45") == 1 if _class == Rj45Node and not enable_rj45: start = False - - # determine node id - if not _id: - _id = self.next_node_id() - - # generate name if not provided - if not options: - options = NodeOptions() - options.set_position(0, 0) - name = options.name - if not name: - name = f"{_class.__name__}{_id}" - + # generate options if not provided + options = options if options else _class.create_options() # verify distributed server - server = self.distributed.servers.get(options.server) - if options.server is not None and server is None: - raise CoreError(f"invalid distributed server: {options.server}") - + dist_server = None + if server is not None: + dist_server = self.distributed.servers.get(server) + if not dist_server: + raise CoreError(f"invalid distributed server: {server}") # create node - logger.info( - "creating node(%s) id(%s) name(%s) start(%s)", - _class.__name__, - _id, - name, - start, - ) - kwargs = dict(_id=_id, name=name, server=server) - if _class in CONTAINER_NODES: - kwargs["image"] = options.image - kwargs["binds"] = options.binds - kwargs["volumes"] = options.volumes - node = self.create_node(_class, start, **kwargs) - - # set node attributes - node.icon = options.icon - node.canvas = options.canvas - - # set node position and broadcast it - has_geo = all(i is not None for i in [options.lon, options.lat, options.alt]) - if has_geo: - self.set_node_geo(node, options.lon, options.lat, options.alt) + node = self.create_node(_class, start, _id, name, dist_server, options) + # set node position + position = position or Position() + if position.has_geo(): + self.set_node_geo(node, position.lon, position.lat, position.alt) else: - self.set_node_pos(node, options.x, options.y) - - # add services to needed nodes - if isinstance(node, (CoreNode, PhysicalNode)): - node.model = options.model - if options.legacy or options.services: - logger.debug("set node type: %s", node.model) - self.services.add_services(node, node.model, options.services) - - # add config services - config_services = options.config_services - if not options.legacy and not config_services and not node.services: - config_services = self.services.default_services.get(node.model, []) - logger.info("setting node config services: %s", config_services) - for name in config_services: - service_class = self.service_manager.get_service(name) - node.add_config_service(service_class) - - # set network mtu, if configured - mtu = self.options.get_int("mtu") - if isinstance(node, CoreNetworkBase) and mtu > 0: - node.mtu = mtu - - # ensure default emane configuration - if isinstance(node, EmaneNet) and options.emane: - model_class = self.emane.get_model(options.emane) - node.wireless_model = model_class(self, node.id) - if self.state == EventTypes.RUNTIME_STATE: - self.emane.add_node(node) - - # set default wlan config if needed + self.set_node_pos(node, position.x, position.y) + # setup default wlan if isinstance(node, WlanNode): - self.mobility.set_model_config(_id, BasicRangeModel.name) - - # boot nodes after runtime CoreNodes and PhysicalNodes - is_boot_node = isinstance(node, (CoreNode, PhysicalNode)) - if self.state == EventTypes.RUNTIME_STATE and is_boot_node: + self.mobility.set_model_config(self.id, BasicRangeModel.name) + # boot core nodes after runtime + is_runtime = self.state == EventTypes.RUNTIME_STATE + if is_runtime and isinstance(node, CoreNode): self.write_nodes() self.add_remove_control_iface(node, remove=False) self.boot_node(node) - self.sdt.add_node(node) return node @@ -980,24 +930,39 @@ class Session: logger.exception("failed to set permission on %s", self.directory) def create_node( - self, _class: Type[NT], start: bool, *args: Any, **kwargs: Any + self, + _class: Type[NT], + start: bool, + _id: int = None, + name: str = None, + server: str = None, + options: NodeOptions = None, ) -> NT: """ Create an emulation node. :param _class: node class to create :param start: True to start node, False otherwise - :param args: list of arguments for the class to create - :param kwargs: dictionary of arguments for the class to create + :param _id: id for node, defaults to None for generated id + :param name: name to assign to node + :param server: distributed server for node, if desired + :param options: options to create node with :return: the created node instance :raises core.CoreError: when id of the node to create already exists """ with self.nodes_lock: - node = _class(self, *args, **kwargs) + node = _class(self, _id=_id, name=name, server=server, options=options) if node.id in self.nodes: node.shutdown() raise CoreError(f"duplicate node id {node.id} for {node.name}") self.nodes[node.id] = node + logger.info( + "created node(%s) id(%s) name(%s) start(%s)", + _class.__name__, + node.id, + node.name, + start, + ) if start: node.startup() return node @@ -1219,7 +1184,7 @@ class Session: funcs = [] start = time.monotonic() for node in self.nodes.values(): - if isinstance(node, (CoreNode, PhysicalNode)): + if isinstance(node, CoreNode): self.add_remove_control_iface(node, remove=False) funcs.append((self.boot_node, (node,), {})) results, exceptions = utils.threadpool(funcs) @@ -1354,21 +1319,18 @@ class Session: updown_script, server_iface, ) - control_net = self.create_node( - CtrlNet, - start=False, - prefix=prefix, - _id=_id, - updown_script=updown_script, - serverintf=server_iface, - ) + options = CtrlNet.create_options() + options.prefix = prefix + options.updown_script = updown_script + options.serverintf = server_iface + control_net = self.create_node(CtrlNet, False, _id, options=options) control_net.brname = f"ctrl{net_index}.{self.short_session_id()}" control_net.startup() return control_net def add_remove_control_iface( self, - node: Union[CoreNode, PhysicalNode], + node: CoreNode, net_index: int = 0, remove: bool = False, conf_required: bool = True, diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index 1bac0885..404e9ab2 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -5,6 +5,7 @@ import abc import logging import shutil import threading +from dataclasses import dataclass, field from pathlib import Path from threading import RLock from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union @@ -33,6 +34,94 @@ if TYPE_CHECKING: PRIVATE_DIRS: List[Path] = [Path("/var/run"), Path("/var/log")] +@dataclass +class Position: + """ + Helper class for Cartesian coordinate position + """ + + x: float = 0.0 + y: float = 0.0 + z: float = 0.0 + lon: float = None + lat: float = None + alt: float = None + + def set(self, x: float = None, y: float = None, z: float = None) -> bool: + """ + 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 + """ + if self.x == x and self.y == y and self.z == z: + return False + self.x = x + self.y = y + self.z = z + return True + + def get(self) -> Tuple[float, float, float]: + """ + Retrieve x,y,z position. + + :return: x,y,z position tuple + """ + return self.x, self.y, self.z + + def has_geo(self) -> bool: + return all(x is not None for x in [self.lon, self.lat, self.alt]) + + def set_geo(self, lon: float, lat: float, alt: float) -> None: + """ + Set geo position lon, lat, alt. + + :param lon: longitude value + :param lat: latitude value + :param alt: altitude value + :return: nothing + """ + self.lon = lon + self.lat = lat + self.alt = alt + + def get_geo(self) -> Tuple[float, float, float]: + """ + Retrieve current geo position lon, lat, alt. + + :return: lon, lat, alt position tuple + """ + return self.lon, self.lat, self.alt + + +@dataclass +class NodeOptions: + """ + Base options for configuring a node. + """ + + canvas: int = None + """id of canvas for display within gui""" + icon: str = None + """custom icon for display, None for default""" + + +@dataclass +class CoreNodeOptions(NodeOptions): + model: str = "PC" + """model is used for providing a default set of services""" + services: List[str] = field(default_factory=list) + """services to start within node""" + config_services: List[str] = field(default_factory=list) + """config services to start within node""" + directory: Path = None + """directory to define node, defaults to path under the session directory""" + legacy: bool = False + """legacy nodes default to standard services""" + + class NodeBase(abc.ABC): """ Base class for CORE nodes (nodes and networks) @@ -44,6 +133,7 @@ class NodeBase(abc.ABC): _id: int = None, name: str = None, server: "DistributedServer" = None, + options: NodeOptions = None, ) -> None: """ Creates a NodeBase instance. @@ -53,26 +143,29 @@ class NodeBase(abc.ABC): :param name: object name :param server: remote server node will run on, default is None for localhost + :param options: options to create node with """ - self.session: "Session" = session - if _id is None: - _id = session.next_node_id() - self.id: int = _id - self.name: str = name or f"o{self.id}" + self.id: int = _id if _id is not None else self.session.next_node_id() + self.name: str = name or f"{self.__class__.__name__}{self.id}" self.server: "DistributedServer" = server self.model: Optional[str] = None self.services: CoreServices = [] self.ifaces: Dict[int, CoreInterface] = {} self.iface_id: int = 0 - self.canvas: Optional[int] = None - self.icon: Optional[str] = None self.position: Position = Position() self.up: bool = False self.lock: RLock = RLock() self.net_client: LinuxNetClient = get_net_client( self.session.use_ovs(), self.host_cmd ) + options = options if options else NodeOptions() + self.canvas: Optional[int] = options.canvas + self.icon: Optional[str] = options.icon + + @classmethod + def create_options(cls) -> NodeOptions: + return NodeOptions() @abc.abstractmethod def startup(self) -> None: @@ -288,6 +381,7 @@ class CoreNodeBase(NodeBase): _id: int = None, name: str = None, server: "DistributedServer" = None, + options: NodeOptions = None, ) -> None: """ Create a CoreNodeBase instance. @@ -298,7 +392,7 @@ class CoreNodeBase(NodeBase): :param server: remote server node will run on, default is None for localhost """ - super().__init__(session, _id, name, server) + super().__init__(session, _id, name, server, options) self.config_services: Dict[str, "ConfigService"] = {} self.directory: Optional[Path] = None self.tmpnodedir: bool = False @@ -460,8 +554,8 @@ class CoreNode(CoreNodeBase): session: "Session", _id: int = None, name: str = None, - directory: Path = None, server: "DistributedServer" = None, + options: CoreNodeOptions = None, ) -> None: """ Create a CoreNode instance. @@ -469,18 +563,37 @@ class CoreNode(CoreNodeBase): :param session: core session instance :param _id: object id :param name: object name - :param directory: node directory :param server: remote server node will run on, default is None for localhost + :param options: options to create node with """ - super().__init__(session, _id, name, server) - self.directory: Optional[Path] = directory + options = options or CoreNodeOptions() + super().__init__(session, _id, name, server, options) + self.directory: Optional[Path] = options.directory self.ctrlchnlname: Path = self.session.directory / self.name self.pid: Optional[int] = None self._mounts: List[Tuple[Path, Path]] = [] self.node_net_client: LinuxNetClient = self.create_node_net_client( self.session.use_ovs() ) + options = options or CoreNodeOptions() + self.model: Optional[str] = options.model + # setup services + if options.legacy or options.services: + logger.debug("set node type: %s", self.model) + self.session.services.add_services(self, self.model, options.services) + # add config services + config_services = options.config_services + if not options.legacy and not config_services and not options.services: + config_services = self.session.services.default_services.get(self.model, []) + logger.info("setting node config services: %s", config_services) + for name in config_services: + service_class = self.session.service_manager.get_service(name) + self.add_config_service(service_class) + + @classmethod + def create_options(cls) -> CoreNodeOptions: + return CoreNodeOptions() def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ @@ -797,6 +910,7 @@ class CoreNetworkBase(NodeBase): _id: int, name: str, server: "DistributedServer" = None, + options: NodeOptions = None, ) -> None: """ Create a CoreNetworkBase instance. @@ -806,9 +920,11 @@ class CoreNetworkBase(NodeBase): :param name: object name :param server: remote server node will run on, default is None for localhost + :param options: options to create node with """ - super().__init__(session, _id, name, server) - self.mtu: int = DEFAULT_MTU + super().__init__(session, _id, name, server, options) + mtu = self.session.options.get_int("mtu") + self.mtu: int = mtu if mtu > 0 else DEFAULT_MTU self.brname: Optional[str] = None self.linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {} self.linked_lock: threading.Lock = threading.Lock() @@ -839,69 +955,3 @@ class CoreNetworkBase(NodeBase): iface.net_id = None with self.linked_lock: del self.linked[iface] - - -class Position: - """ - Helper class for Cartesian coordinate position - """ - - def __init__(self, x: float = None, y: float = None, z: float = None) -> None: - """ - Creates a Position instance. - - :param x: x position - :param y: y position - :param z: z position - """ - self.x: float = x - self.y: float = y - self.z: float = z - self.lon: Optional[float] = None - self.lat: Optional[float] = None - self.alt: Optional[float] = None - - def set(self, x: float = None, y: float = None, z: float = None) -> bool: - """ - 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 - """ - if self.x == x and self.y == y and self.z == z: - return False - self.x = x - self.y = y - self.z = z - return True - - def get(self) -> Tuple[float, float, float]: - """ - Retrieve x,y,z position. - - :return: x,y,z position tuple - """ - return self.x, self.y, self.z - - def set_geo(self, lon: float, lat: float, alt: float) -> None: - """ - Set geo position lon, lat, alt. - - :param lon: longitude value - :param lat: latitude value - :param alt: altitude value - :return: nothing - """ - self.lon = lon - self.lat = lat - self.alt = alt - - def get_geo(self) -> Tuple[float, float, float]: - """ - Retrieve current geo position lon, lat, alt. - - :return: lon, lat, alt position tuple - """ - return self.lon, self.lat, self.alt diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index 122aa9b0..a5560b17 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -1,7 +1,7 @@ import json import logging import shlex -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path from tempfile import NamedTemporaryFile from typing import TYPE_CHECKING, Dict, List, Tuple @@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, Dict, List, Tuple from core.emulator.distributed import DistributedServer from core.errors import CoreCommandError, CoreError from core.executables import BASH -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, CoreNodeOptions logger = logging.getLogger(__name__) @@ -19,6 +19,21 @@ if TYPE_CHECKING: DOCKER: str = "docker" +@dataclass +class DockerOptions(CoreNodeOptions): + image: str = "ubuntu" + """image used when creating container""" + binds: List[Tuple[str, str]] = field(default_factory=list) + """bind mount source and destinations to setup within container""" + volumes: List[Tuple[str, str, bool, bool]] = field(default_factory=list) + """ + volume mount source, destination, unique, delete to setup within container + + unique is True for node unique volume naming + delete is True for deleting volume mount during shutdown + """ + + @dataclass class DockerVolume: src: str @@ -38,11 +53,8 @@ class DockerNode(CoreNode): session: "Session", _id: int = None, name: str = None, - directory: str = None, server: DistributedServer = None, - image: str = None, - binds: List[Tuple[str, str]] = None, - volumes: List[Tuple[str, str, bool, bool]] = None, + options: DockerOptions = None, ) -> None: """ Create a DockerNode instance. @@ -50,22 +62,23 @@ class DockerNode(CoreNode): :param session: core session instance :param _id: object id :param name: object name - :param directory: node directory :param server: remote server node will run on, default is None for localhost - :param image: image to start container with - :param binds: bind mounts to set for the created container - :param volumes: volume mount settings to set for the created container + :param options: options for creating node """ - super().__init__(session, _id, name, directory, server) - self.image: str = image if image is not None else "ubuntu" - self.binds: List[Tuple[str, str]] = binds or [] + options = options or DockerOptions() + super().__init__(session, _id, name, server, options) + self.image: str = options.image + self.binds: List[Tuple[str, str]] = options.binds self.volumes: Dict[str, DockerVolume] = {} - volumes = volumes or [] - for src, dst, unique, delete in volumes: + for src, dst, unique, delete in options.volumes: src_name = self._unique_name(src) if unique else src self.volumes[src] = DockerVolume(src_name, dst, unique, delete) + @classmethod + def create_options(cls) -> DockerOptions: + return DockerOptions() + def _create_cmd(self, args: str, shell: bool = False) -> str: """ Create command used to run commands within the context of a node. diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index 1c12328f..b99b4576 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -1,15 +1,16 @@ import json import logging import time +from dataclasses import dataclass, field from pathlib import Path from tempfile import NamedTemporaryFile -from typing import TYPE_CHECKING, Callable, Dict, Optional +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple from core import utils from core.emulator.data import InterfaceData, LinkOptions from core.emulator.distributed import DistributedServer from core.errors import CoreCommandError -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, CoreNodeOptions from core.nodes.interface import CoreInterface logger = logging.getLogger(__name__) @@ -66,15 +67,29 @@ class LxdClient: self.run(args) +@dataclass +class LxcOptions(CoreNodeOptions): + image: str = "ubuntu" + """image used when creating container""" + binds: List[Tuple[str, str]] = field(default_factory=list) + """bind mount source and destinations to setup within container""" + volumes: List[Tuple[str, str, bool, bool]] = field(default_factory=list) + """ + volume mount source, destination, unique, delete to setup within container + + unique is True for node unique volume naming + delete is True for deleting volume mount during shutdown + """ + + class LxcNode(CoreNode): def __init__( self, session: "Session", _id: int = None, name: str = None, - directory: str = None, server: DistributedServer = None, - image: str = None, + options: LxcOptions = None, ) -> None: """ Create a LxcNode instance. @@ -82,15 +97,19 @@ class LxcNode(CoreNode): :param session: core session instance :param _id: object id :param name: object name - :param directory: node directory :param server: remote server node will run on, default is None for localhost - :param image: image to start container with + :param options: option to create node with """ - super().__init__(session, _id, name, directory, server) - self.image: str = image if image is not None else "ubuntu" + options = options or LxcOptions() + super().__init__(session, _id, name, server, options) + self.image: str = options.image self.client: Optional[LxdClient] = None + @classmethod + def create_options(cls) -> LxcOptions: + return LxcOptions() + def alive(self) -> bool: """ Check if the node is alive. diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index 284a34ab..2b52cbdf 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -4,6 +4,7 @@ Defines network nodes used within core. import logging import threading +from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING, Dict, List, Optional, Type @@ -14,7 +15,7 @@ from core.emulator.data import InterfaceData, LinkData from core.emulator.enumerations import MessageFlags, NetworkPolicy, RegisterTlvs from core.errors import CoreCommandError, CoreError from core.executables import NFTABLES -from core.nodes.base import CoreNetworkBase +from core.nodes.base import CoreNetworkBase, NodeOptions from core.nodes.interface import CoreInterface, GreTap from core.nodes.netclient import get_net_client @@ -180,6 +181,12 @@ class NftablesQueue: nft_queue: NftablesQueue = NftablesQueue() +@dataclass +class NetworkOptions(NodeOptions): + policy: NetworkPolicy = None + """allows overriding the network policy, otherwise uses class defined default""" + + class CoreNetwork(CoreNetworkBase): """ Provides linux bridge network functionality for core nodes. @@ -193,7 +200,7 @@ class CoreNetwork(CoreNetworkBase): _id: int = None, name: str = None, server: "DistributedServer" = None, - policy: NetworkPolicy = None, + options: NetworkOptions = None, ) -> None: """ Creates a CoreNetwork instance. @@ -203,18 +210,19 @@ class CoreNetwork(CoreNetworkBase): :param name: object name :param server: remote server node will run on, default is None for localhost - :param policy: network policy + :param options: options to create node with """ - super().__init__(session, _id, name, server) - if name is None: - name = str(self.id) - if policy is not None: - self.policy: NetworkPolicy = policy - self.name: Optional[str] = name + options = options or NetworkOptions() + super().__init__(session, _id, name, server, options) + self.policy: NetworkPolicy = options.policy if options.policy else self.policy sessionid = self.session.short_session_id() self.brname: str = f"b.{self.id}.{sessionid}" self.has_nftables_chain: bool = False + @classmethod + def create_options(cls) -> NetworkOptions: + return NetworkOptions() + def host_cmd( self, args: str, @@ -482,6 +490,20 @@ class GreTapBridge(CoreNetwork): self.add_ips(ips) +@dataclass +class CtrlNetOptions(NetworkOptions): + prefix: str = None + """ip4 network prefix to use for generating an address""" + updown_script: str = None + """script to execute during startup and shutdown""" + serverintf: str = None + """used to associate an interface with the control network bridge""" + assign_address: bool = True + """used to determine if a specific address should be assign using hostid""" + hostid: int = None + """used with assign address to """ + + class CtrlNet(CoreNetwork): """ Control network functionality. @@ -500,36 +522,32 @@ class CtrlNet(CoreNetwork): def __init__( self, session: "Session", - prefix: str, _id: int = None, name: str = None, - hostid: int = None, server: "DistributedServer" = None, - assign_address: bool = True, - updown_script: str = None, - serverintf: str = None, + options: CtrlNetOptions = None, ) -> None: """ Creates a CtrlNet instance. :param session: core session instance :param _id: node id - :param name: node namee - :param prefix: control network ipv4 prefix - :param hostid: host id + :param name: node name :param server: remote server node will run on, default is None for localhost - :param assign_address: assigned address - :param updown_script: updown script - :param serverintf: server interface - :return: + :param options: node options for creation """ - self.prefix: netaddr.IPNetwork = netaddr.IPNetwork(prefix).cidr - self.hostid: Optional[int] = hostid - self.assign_address: bool = assign_address - self.updown_script: Optional[str] = updown_script - self.serverintf: Optional[str] = serverintf - super().__init__(session, _id, name, server) + options = options or CtrlNetOptions() + super().__init__(session, _id, name, server, options) + self.prefix: netaddr.IPNetwork = netaddr.IPNetwork(options.prefix).cidr + self.hostid: Optional[int] = options.hostid + self.assign_address: bool = options.assign_address + self.updown_script: Optional[str] = options.updown_script + self.serverintf: Optional[str] = options.serverintf + + @classmethod + def create_options(cls) -> CtrlNetOptions: + return CtrlNetOptions() def add_addresses(self, index: int) -> None: """ @@ -669,7 +687,7 @@ class WlanNode(CoreNetwork): _id: int = None, name: str = None, server: "DistributedServer" = None, - policy: NetworkPolicy = None, + options: NetworkOptions = None, ) -> None: """ Create a WlanNode instance. @@ -679,9 +697,9 @@ class WlanNode(CoreNetwork): :param name: node name :param server: remote server node will run on, default is None for localhost - :param policy: wlan policy + :param options: options to create node with """ - super().__init__(session, _id, name, server, policy) + super().__init__(session, _id, name, server, options) # wireless and mobility models (BasicRangeModel, Ns2WaypointMobility) self.wireless_model: Optional[WirelessModel] = None self.mobility: Optional[WayPointMobility] = None diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index ccbaf956..02c200db 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -13,7 +13,7 @@ from core.emulator.distributed import DistributedServer from core.emulator.enumerations import TransportType from core.errors import CoreCommandError, CoreError from core.executables import BASH, TEST, UMOUNT -from core.nodes.base import CoreNode, CoreNodeBase +from core.nodes.base import CoreNode, CoreNodeBase, CoreNodeOptions, NodeOptions from core.nodes.interface import CoreInterface logger = logging.getLogger(__name__) @@ -34,6 +34,7 @@ class Rj45Node(CoreNodeBase): _id: int = None, name: str = None, server: DistributedServer = None, + options: NodeOptions = None, ) -> None: """ Create an RJ45Node instance. @@ -43,8 +44,9 @@ class Rj45Node(CoreNodeBase): :param name: node name :param server: remote server node will run on, default is None for localhost + :param options: option to create node with """ - super().__init__(session, _id, name, server) + super().__init__(session, _id, name, server, options) self.iface: CoreInterface = CoreInterface( self.iface_id, name, name, session.use_ovs(), node=self, server=server ) @@ -224,12 +226,12 @@ class PhysicalNode(CoreNode): session: "Session", _id: int = None, name: str = None, - directory: Path = None, server: DistributedServer = None, + options: CoreNodeOptions = None, ) -> None: if not self.server: raise CoreError("physical nodes must be assigned to a remote server") - super().__init__(session, _id, name, directory, server) + super().__init__(session, _id, name, server, options) def startup(self) -> None: with self.lock: diff --git a/daemon/core/nodes/wireless.py b/daemon/core/nodes/wireless.py index 3416b65f..ef37db35 100644 --- a/daemon/core/nodes/wireless.py +++ b/daemon/core/nodes/wireless.py @@ -14,7 +14,7 @@ from core.emulator.data import LinkData, LinkOptions from core.emulator.enumerations import LinkTypes, MessageFlags from core.errors import CoreError from core.executables import NFTABLES -from core.nodes.base import CoreNetworkBase +from core.nodes.base import CoreNetworkBase, NodeOptions from core.nodes.interface import CoreInterface if TYPE_CHECKING: @@ -108,8 +108,9 @@ class WirelessNode(CoreNetworkBase): _id: int, name: str, server: "DistributedServer" = None, + options: NodeOptions = None, ): - super().__init__(session, _id, name, server) + super().__init__(session, _id, name, server, options) self.bridges: Dict[int, Tuple[CoreInterface, str]] = {} self.links: Dict[Tuple[int, int], WirelessLink] = {} self.position_enabled: bool = CONFIG_ENABLED diff --git a/daemon/core/xml/corexml.py b/daemon/core/xml/corexml.py index e947dece..a483a8ee 100644 --- a/daemon/core/xml/corexml.py +++ b/daemon/core/xml/corexml.py @@ -8,12 +8,12 @@ import core.nodes.base import core.nodes.physical from core import utils from core.config import Configuration -from core.emane.nodes import EmaneNet -from core.emulator.data import InterfaceData, LinkOptions, NodeOptions +from core.emane.nodes import EmaneNet, EmaneOptions +from core.emulator.data import InterfaceData, LinkOptions from core.emulator.enumerations import EventTypes, NodeTypes from core.errors import CoreXmlError -from core.nodes.base import CoreNodeBase, NodeBase -from core.nodes.docker import DockerNode +from core.nodes.base import CoreNodeBase, CoreNodeOptions, NodeBase, Position +from core.nodes.docker import DockerNode, DockerOptions from core.nodes.interface import CoreInterface from core.nodes.lxd import LxcNode from core.nodes.network import CtrlNet, GreTapBridge, PtpNet, WlanNode @@ -802,68 +802,76 @@ class CoreXmlReader: clazz = device_element.get("class") image = device_element.get("image") server = device_element.get("server") - options = NodeOptions( - name=name, model=model, image=image, icon=icon, server=server - ) + canvas = get_int(device_element, "canvas") node_type = NodeTypes.DEFAULT if clazz == "docker": node_type = NodeTypes.DOCKER elif clazz == "lxc": node_type = NodeTypes.LXC _class = self.session.get_node_class(node_type) - - service_elements = device_element.find("services") - if service_elements is not None: - options.services = [x.get("name") for x in service_elements.iterchildren()] - - config_service_elements = device_element.find("configservices") - if config_service_elements is not None: - options.config_services = [ - x.get("name") for x in config_service_elements.iterchildren() - ] - + options = _class.create_options() + options.icon = icon + options.canvas = canvas + # check for special options + if isinstance(options, CoreNodeOptions): + options.model = model + service_elements = device_element.find("services") + if service_elements is not None: + options.services.extend( + x.get("name") for x in service_elements.iterchildren() + ) + config_service_elements = device_element.find("configservices") + if config_service_elements is not None: + options.config_services.extend( + x.get("name") for x in config_service_elements.iterchildren() + ) + if isinstance(options, DockerOptions): + options.image = image + # get position information position_element = device_element.find("position") + position = None if position_element is not None: + position = Position() x = get_float(position_element, "x") y = get_float(position_element, "y") if all([x, y]): - options.set_position(x, y) - + position.set(x, y) lat = get_float(position_element, "lat") lon = get_float(position_element, "lon") alt = get_float(position_element, "alt") if all([lat, lon, alt]): - options.set_location(lat, lon, alt) - + position.set_geo(lon, lat, alt) logger.info("reading node id(%s) model(%s) name(%s)", node_id, model, name) - self.session.add_node(_class, node_id, options) + self.session.add_node(_class, node_id, name, server, position, options) def read_network(self, network_element: etree.Element) -> None: node_id = get_int(network_element, "id") name = network_element.get("name") + server = network_element.get("server") node_type = NodeTypes[network_element.get("type")] _class = self.session.get_node_class(node_type) - icon = network_element.get("icon") - server = network_element.get("server") - options = NodeOptions(name=name, icon=icon, server=server) - if node_type == NodeTypes.EMANE: - model = network_element.get("model") - options.emane = model + options = _class.create_options() + options.canvas = get_int(network_element, "canvas") + options.icon = network_element.get("icon") + if isinstance(options, EmaneOptions): + options.emane_model = network_element.get("model") position_element = network_element.find("position") + position = None if position_element is not None: + position = Position() x = get_float(position_element, "x") y = get_float(position_element, "y") if all([x, y]): - options.set_position(x, y) + position.set(x, y) lat = get_float(position_element, "lat") lon = get_float(position_element, "lon") alt = get_float(position_element, "alt") if all([lat, lon, alt]): - options.set_location(lat, lon, alt) + position.set_geo(lon, lat, alt) logger.info( "reading node id(%s) node_type(%s) name(%s)", node_id, node_type, name ) - node = self.session.add_node(_class, node_id, options) + node = self.session.add_node(_class, node_id, name, server, position, options) if isinstance(node, WirelessNode): wireless_element = network_element.find("wireless") if wireless_element: diff --git a/daemon/examples/configservices/testing.py b/daemon/examples/configservices/testing.py index 9706f2c9..937c3aa8 100644 --- a/daemon/examples/configservices/testing.py +++ b/daemon/examples/configservices/testing.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode @@ -11,13 +11,13 @@ if __name__ == "__main__": # setup basic network prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(model=None) coreemu = CoreEmu() session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) switch = session.add_node(SwitchNode) # node one + options = CoreNode.create_options() options.config_services = ["DefaultRoute", "IPForward"] node1 = session.add_node(CoreNode, options=options) interface = prefixes.create_iface(node1) diff --git a/daemon/examples/docker/docker2core.py b/daemon/examples/docker/docker2core.py index ae7dae79..cd6a5a20 100644 --- a/daemon/examples/docker/docker2core.py +++ b/daemon/examples/docker/docker2core.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.docker import DockerNode @@ -14,9 +14,10 @@ if __name__ == "__main__": try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(model=None, image="ubuntu") # create node one + options = DockerNode.create_options() + options.image = "ubuntu" node1 = session.add_node(DockerNode, options=options) interface1_data = prefixes.create_iface(node1) diff --git a/daemon/examples/docker/docker2docker.py b/daemon/examples/docker/docker2docker.py index 308fd00f..5fa65778 100644 --- a/daemon/examples/docker/docker2docker.py +++ b/daemon/examples/docker/docker2docker.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.docker import DockerNode @@ -15,9 +15,10 @@ if __name__ == "__main__": # create nodes and interfaces try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(model=None, image="ubuntu") # create node one + options = DockerNode.create_options() + options.image = "ubuntu" node1 = session.add_node(DockerNode, options=options) interface1_data = prefixes.create_iface(node1) diff --git a/daemon/examples/docker/switch.py b/daemon/examples/docker/switch.py index fa9e4e40..a3f8e31e 100644 --- a/daemon/examples/docker/switch.py +++ b/daemon/examples/docker/switch.py @@ -1,9 +1,8 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes -from core.nodes.base import CoreNode from core.nodes.docker import DockerNode from core.nodes.network import SwitchNode @@ -16,12 +15,15 @@ if __name__ == "__main__": try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(model=None, image="ubuntu") # create switch switch = session.add_node(SwitchNode) # node one + options = DockerNode.create_options() + options.image = "core" + options.binds.append(("/tmp/testbind", "/tmp/bind")) + options.volumes.append(("var.log", "/tmp/var.log", True, True)) node1 = session.add_node(DockerNode, options=options) interface1_data = prefixes.create_iface(node1) @@ -30,16 +32,18 @@ if __name__ == "__main__": interface2_data = prefixes.create_iface(node2) # node three - node_three = session.add_node(CoreNode) - interface_three = prefixes.create_iface(node_three) + # node_three = session.add_node(CoreNode) + # interface_three = prefixes.create_iface(node_three) # add links session.add_link(node1.id, switch.id, interface1_data) session.add_link(node2.id, switch.id, interface2_data) - session.add_link(node_three.id, switch.id, interface_three) + # session.add_link(node_three.id, switch.id, interface_three) # instantiate session.instantiate() + + print(f"{node2.name}: {node2.volumes.values()}") finally: input("continue to shutdown") coreemu.shutdown() diff --git a/daemon/examples/lxd/lxd2core.py b/daemon/examples/lxd/lxd2core.py index b41520d8..ec671b29 100644 --- a/daemon/examples/lxd/lxd2core.py +++ b/daemon/examples/lxd/lxd2core.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.lxd import LxcNode @@ -14,9 +14,10 @@ if __name__ == "__main__": try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(image="ubuntu") # create node one + options = LxcNode.create_options() + options.image = "ubuntu" node1 = session.add_node(LxcNode, options=options) interface1_data = prefixes.create_iface(node1) diff --git a/daemon/examples/lxd/lxd2lxd.py b/daemon/examples/lxd/lxd2lxd.py index 3a55e2e1..7e9e6a55 100644 --- a/daemon/examples/lxd/lxd2lxd.py +++ b/daemon/examples/lxd/lxd2lxd.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.lxd import LxcNode @@ -15,9 +15,10 @@ if __name__ == "__main__": # create nodes and interfaces try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(image="ubuntu:18.04") # create node one + options = LxcNode.create_options() + options.image = "ubuntu:18.04" node1 = session.add_node(LxcNode, options=options) interface1_data = prefixes.create_iface(node1) diff --git a/daemon/examples/lxd/switch.py b/daemon/examples/lxd/switch.py index 12767e71..c093fd77 100644 --- a/daemon/examples/lxd/switch.py +++ b/daemon/examples/lxd/switch.py @@ -1,7 +1,7 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.lxd import LxcNode @@ -16,12 +16,13 @@ if __name__ == "__main__": try: prefixes = IpPrefixes(ip4_prefix="10.83.0.0/16") - options = NodeOptions(image="ubuntu") # create switch switch = session.add_node(SwitchNode) # node one + options = LxcNode.create_options() + options.image = "ubuntu" node1 = session.add_node(LxcNode, options=options) interface1_data = prefixes.create_iface(node1) diff --git a/daemon/examples/python/distributed_emane.py b/daemon/examples/python/distributed_emane.py index cdc9cbc3..d19a2d87 100644 --- a/daemon/examples/python/distributed_emane.py +++ b/daemon/examples/python/distributed_emane.py @@ -9,7 +9,7 @@ import logging from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode @@ -50,11 +50,13 @@ def main(args): session.set_state(EventTypes.CONFIGURATION_STATE) # create local node, switch, and remote nodes - options = NodeOptions(model="mdr") - options.set_position(0, 0) + options = CoreNode.create_options() + options.model = "mdr" node1 = session.add_node(CoreNode, options=options) - emane_net = session.add_node(EmaneNet) - session.emane.set_model(emane_net, EmaneIeee80211abgModel) + options = EmaneNet.create_options() + options.emane_model = EmaneIeee80211abgModel.name + emane_net = session.add_node(EmaneNet, options=options) + options = CoreNode.create_options() options.server = server_name node2 = session.add_node(CoreNode, options=options) diff --git a/daemon/examples/python/distributed_lxd.py b/daemon/examples/python/distributed_lxd.py index 26f7caa6..70af8a29 100644 --- a/daemon/examples/python/distributed_lxd.py +++ b/daemon/examples/python/distributed_lxd.py @@ -7,7 +7,7 @@ import argparse import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.lxd import LxcNode @@ -42,7 +42,8 @@ def main(args): session.set_state(EventTypes.CONFIGURATION_STATE) # create local node, switch, and remote nodes - options = NodeOptions(image="ubuntu:18.04") + options = LxcNode.create_options() + options.image = "ubuntu:18.04" node1 = session.add_node(LxcNode, options=options) options.server = server_name node2 = session.add_node(LxcNode, options=options) diff --git a/daemon/examples/python/distributed_ptp.py b/daemon/examples/python/distributed_ptp.py index fe714e1d..30dbb6bb 100644 --- a/daemon/examples/python/distributed_ptp.py +++ b/daemon/examples/python/distributed_ptp.py @@ -7,7 +7,7 @@ import argparse import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode @@ -42,10 +42,8 @@ def main(args): session.set_state(EventTypes.CONFIGURATION_STATE) # create local node, switch, and remote nodes - options = NodeOptions() - node1 = session.add_node(CoreNode, options=options) - options.server = server_name - node2 = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode) + node2 = session.add_node(CoreNode, server=server_name) # create node interfaces and link interface1_data = prefixes.create_iface(node1) diff --git a/daemon/examples/python/distributed_switch.py b/daemon/examples/python/distributed_switch.py index 35de1cad..59a0447f 100644 --- a/daemon/examples/python/distributed_switch.py +++ b/daemon/examples/python/distributed_switch.py @@ -7,7 +7,7 @@ import argparse import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.nodes.base import CoreNode from core.nodes.network import SwitchNode @@ -47,7 +47,7 @@ def main(args): # create local node, switch, and remote nodes node1 = session.add_node(CoreNode) switch = session.add_node(SwitchNode) - options = NodeOptions() + options = CoreNode.create_options() options.server = server_name node2 = session.add_node(CoreNode, options=options) diff --git a/daemon/examples/python/emane80211.py b/daemon/examples/python/emane80211.py index 0bcc0157..005dc436 100644 --- a/daemon/examples/python/emane80211.py +++ b/daemon/examples/python/emane80211.py @@ -2,9 +2,9 @@ from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position # ip nerator for example ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24") @@ -21,14 +21,20 @@ session.location.refscale = 150.0 session.set_state(EventTypes.CONFIGURATION_STATE) # create emane -options = NodeOptions(x=200, y=200, emane=EmaneIeee80211abgModel.name) -emane = session.add_node(EmaneNet, options=options) +options = EmaneNet.create_options() +options.emane_model = EmaneIeee80211abgModel.name +position = Position(x=200, y=200) +emane = session.add_node(EmaneNet, position=position, options=options) # create nodes -options = NodeOptions(model="mdr", x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(model="mdr", x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +options = CoreNode.create_options() +options.model = "mdr" +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position, options=options) +options = CoreNode.create_options() +options.model = "mdr" +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position, options=options) # configure general emane settings config = session.emane.get_configs() diff --git a/daemon/examples/python/peertopeer.py b/daemon/examples/python/peertopeer.py index 56fbe258..7883cac2 100644 --- a/daemon/examples/python/peertopeer.py +++ b/daemon/examples/python/peertopeer.py @@ -1,8 +1,8 @@ # required imports from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position # ip nerator for example ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24") @@ -15,10 +15,10 @@ session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) # create nodes -options = NodeOptions(x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position) +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position) # link nodes together iface1 = ip_prefixes.create_iface(n1) diff --git a/daemon/examples/python/switch.py b/daemon/examples/python/switch.py index b7894bc3..a609aa03 100644 --- a/daemon/examples/python/switch.py +++ b/daemon/examples/python/switch.py @@ -1,8 +1,8 @@ # required imports from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position from core.nodes.network import SwitchNode # ip nerator for example @@ -16,14 +16,14 @@ session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) # create switch -options = NodeOptions(x=200, y=200) -switch = session.add_node(SwitchNode, options=options) +position = Position(x=200, y=200) +switch = session.add_node(SwitchNode, position=position) # create nodes -options = NodeOptions(x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position) +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position) # link nodes to switch iface1 = ip_prefixes.create_iface(n1) diff --git a/daemon/examples/python/wireless.py b/daemon/examples/python/wireless.py index 559cf2f8..574ca976 100644 --- a/daemon/examples/python/wireless.py +++ b/daemon/examples/python/wireless.py @@ -2,9 +2,9 @@ import logging from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position from core.nodes.network import WlanNode # enable info logging @@ -21,14 +21,18 @@ session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) # create wireless -options = NodeOptions(x=200, y=200) -wireless = session.add_node(WlanNode, options=options) +position = Position(x=200, y=200) +wireless = session.add_node(WlanNode, position=position) # create nodes -options = NodeOptions(model="mdr", x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(model="mdr", x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +options = CoreNode.create_options() +options.model = "mdr" +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position, options=options) +options = CoreNode.create_options() +options.model = "mdr" +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position, options=options) # link nodes to wireless iface1 = ip_prefixes.create_iface(n1) diff --git a/daemon/examples/python/wlan.py b/daemon/examples/python/wlan.py index f0dbc97a..512aea3e 100644 --- a/daemon/examples/python/wlan.py +++ b/daemon/examples/python/wlan.py @@ -1,9 +1,9 @@ # required imports from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.location.mobility import BasicRangeModel -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position from core.nodes.network import WlanNode # ip nerator for example @@ -17,14 +17,18 @@ session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) # create wlan -options = NodeOptions(x=200, y=200) -wlan = session.add_node(WlanNode, options=options) +position = Position(x=200, y=200) +wlan = session.add_node(WlanNode, position=position) # create nodes -options = NodeOptions(model="mdr", x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(model="mdr", x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +options = CoreNode.create_options() +options.model = "mdr" +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position, options=options) +options = CoreNode.create_options() +options.model = "mdr" +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position, options=options) # configuring wlan session.mobility.set_model_config( diff --git a/daemon/tests/emane/test_emane.py b/daemon/tests/emane/test_emane.py index 5cb14bdc..2ddb1a5d 100644 --- a/daemon/tests/emane/test_emane.py +++ b/daemon/tests/emane/test_emane.py @@ -16,10 +16,10 @@ from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.models.rfpipe import EmaneRfPipeModel from core.emane.models.tdma import EmaneTdmaModel from core.emane.nodes import EmaneNet -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.session import Session from core.errors import CoreCommandError, CoreError -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position _EMANE_MODELS = [ EmaneIeee80211abgModel, @@ -53,19 +53,22 @@ class TestEmane: """ # create emane node for networking the core nodes session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions() - options.set_position(80, 50) - options.emane = EmaneIeee80211abgModel.name - emane_net1 = session.add_node(EmaneNet, options=options) - options.emane = EmaneRfPipeModel.name - emane_net2 = session.add_node(EmaneNet, options=options) + options = EmaneNet.create_options() + options.emane_model = EmaneIeee80211abgModel.name + position = Position(x=80, y=50) + emane_net1 = session.add_node(EmaneNet, position=position, options=options) + options = EmaneNet.create_options() + options.emane_model = EmaneRfPipeModel.name + position = Position(x=80, y=50) + emane_net2 = session.add_node(EmaneNet, position=position, options=options) # create nodes - options = NodeOptions(model="mdr") - options.set_position(150, 150) - node1 = session.add_node(CoreNode, options=options) - options.set_position(300, 150) - node2 = session.add_node(CoreNode, options=options) + options = CoreNode.create_options() + options.model = "mdr" + position = Position(x=150, y=150) + node1 = session.add_node(CoreNode, position=position, options=options) + position = Position(x=300, y=150) + node2 = session.add_node(CoreNode, position=position, options=options) # create interfaces ip_prefix1 = IpPrefixes("10.0.0.0/24") @@ -100,9 +103,10 @@ class TestEmane: # create emane node for networking the core nodes session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions(emane=model.name) - options.set_position(80, 50) - emane_network = session.add_node(EmaneNet, options=options) + options = EmaneNet.create_options() + options.emane_model = model.name + position = Position(x=80, y=50) + emane_network = session.add_node(EmaneNet, position=position, options=options) # configure tdma if model == EmaneTdmaModel: @@ -111,11 +115,12 @@ class TestEmane: ) # create nodes - options = NodeOptions(model="mdr") - options.set_position(150, 150) - node1 = session.add_node(CoreNode, options=options) - options.set_position(300, 150) - node2 = session.add_node(CoreNode, options=options) + options = CoreNode.create_options() + options.model = "mdr" + position = Position(x=150, y=150) + node1 = session.add_node(CoreNode, position=position, options=options) + position = Position(x=300, y=150) + node2 = session.add_node(CoreNode, position=position, options=options) for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) @@ -141,9 +146,10 @@ class TestEmane: """ # create emane node for networking the core nodes session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions(emane=EmaneIeee80211abgModel.name) - options.set_position(80, 50) - emane_network = session.add_node(EmaneNet, options=options) + options = EmaneNet.create_options() + options.emane_model = EmaneIeee80211abgModel.name + position = Position(x=80, y=50) + emane_network = session.add_node(EmaneNet, position=position, options=options) config_key = "txpower" config_value = "10" session.emane.set_config( @@ -151,11 +157,12 @@ class TestEmane: ) # create nodes - options = NodeOptions(model="mdr") - options.set_position(150, 150) - node1 = session.add_node(CoreNode, options=options) - options.set_position(300, 150) - node2 = session.add_node(CoreNode, options=options) + options = CoreNode.create_options() + options.model = "mdr" + position = Position(x=150, y=150) + node1 = session.add_node(CoreNode, position=position, options=options) + position = Position(x=300, y=150) + node2 = session.add_node(CoreNode, position=position, options=options) for i, node in enumerate([node1, node2]): node.setposition(x=150 * (i + 1), y=150) @@ -205,14 +212,17 @@ class TestEmane: self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes ): # create nodes - options = NodeOptions(model="mdr", x=50, y=50) - node1 = session.add_node(CoreNode, options=options) + options = CoreNode.create_options() + options.model = "mdr" + position = Position(x=50, y=50) + node1 = session.add_node(CoreNode, position=position, options=options) iface1_data = ip_prefixes.create_iface(node1) - node2 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, position=position, options=options) iface2_data = ip_prefixes.create_iface(node2) # create emane node - options = NodeOptions(model=None, emane=EmaneRfPipeModel.name) + options = EmaneNet.create_options() + options.emane_model = EmaneRfPipeModel.name emane_node = session.add_node(EmaneNet, options=options) # create links @@ -255,11 +265,7 @@ class TestEmane: assert session.get_node(node1.id, CoreNode) assert session.get_node(node2.id, CoreNode) assert session.get_node(emane_node.id, EmaneNet) - links = [] - for node_id in session.nodes: - node = session.nodes[node_id] - links += node.links() - assert len(links) == 2 + assert len(session.link_manager.links()) == 2 config = session.emane.get_config(node1.id, EmaneRfPipeModel.name) assert config["datarate"] == datarate @@ -267,14 +273,17 @@ class TestEmane: self, session: Session, tmpdir: TemporaryFile, ip_prefixes: IpPrefixes ): # create nodes - options = NodeOptions(model="mdr", x=50, y=50) - node1 = session.add_node(CoreNode, options=options) + options = CoreNode.create_options() + options.model = "mdr" + position = Position(x=50, y=50) + node1 = session.add_node(CoreNode, position=position, options=options) iface1_data = ip_prefixes.create_iface(node1) - node2 = session.add_node(CoreNode, options=options) + node2 = session.add_node(CoreNode, position=position, options=options) iface2_data = ip_prefixes.create_iface(node2) # create emane node - options = NodeOptions(model=None, emane=EmaneRfPipeModel.name) + options = EmaneNet.create_options() + options.emane_model = EmaneRfPipeModel.name emane_node = session.add_node(EmaneNet, options=options) # create links @@ -318,10 +327,6 @@ class TestEmane: assert session.get_node(node1.id, CoreNode) assert session.get_node(node2.id, CoreNode) assert session.get_node(emane_node.id, EmaneNet) - links = [] - for node_id in session.nodes: - node = session.nodes[node_id] - links += node.links() - assert len(links) == 2 + assert len(session.link_manager.links()) == 2 config = session.emane.get_config(config_id, EmaneRfPipeModel.name) assert config["datarate"] == datarate diff --git a/daemon/tests/test_core.py b/daemon/tests/test_core.py index 71a3d972..919e4478 100644 --- a/daemon/tests/test_core.py +++ b/daemon/tests/test_core.py @@ -8,7 +8,7 @@ from typing import List, Type import pytest -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.session import Session from core.errors import CoreCommandError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility @@ -75,8 +75,8 @@ class TestCore: session.mobility.set_model(wlan_node, BasicRangeModel) # create nodes - options = NodeOptions(model="mdr") - options.set_position(0, 0) + options = CoreNode.create_options() + options.model = "mdr" node1 = session.add_node(CoreNode, options=options) node2 = session.add_node(CoreNode, options=options) @@ -105,8 +105,8 @@ class TestCore: session.mobility.set_model(wlan_node, BasicRangeModel) # create nodes - options = NodeOptions(model="mdr") - options.set_position(0, 0) + options = CoreNode.create_options() + options.model = "mdr" node1 = session.add_node(CoreNode, options=options) node2 = session.add_node(CoreNode, options=options) diff --git a/daemon/tests/test_distributed.py b/daemon/tests/test_distributed.py index 35b7af4e..3a9d43fb 100644 --- a/daemon/tests/test_distributed.py +++ b/daemon/tests/test_distributed.py @@ -1,4 +1,3 @@ -from core.emulator.data import NodeOptions from core.emulator.session import Session from core.nodes.base import CoreNode from core.nodes.network import HubNode @@ -12,8 +11,7 @@ class TestDistributed: # when session.distributed.add_server(server_name, host) - options = NodeOptions(server=server_name) - node = session.add_node(CoreNode, options=options) + node = session.add_node(CoreNode, server=server_name) session.instantiate() # then @@ -30,8 +28,7 @@ class TestDistributed: # when session.distributed.add_server(server_name, host) node1 = session.add_node(HubNode) - options = NodeOptions(server=server_name) - node2 = session.add_node(HubNode, options=options) + node2 = session.add_node(HubNode, server=server_name) session.add_link(node1.id, node2.id) session.instantiate() diff --git a/daemon/tests/test_grpc.py b/daemon/tests/test_grpc.py index a33fd5b8..9aed3395 100644 --- a/daemon/tests/test_grpc.py +++ b/daemon/tests/test_grpc.py @@ -34,7 +34,7 @@ from core.api.grpc.wrappers import ( ) from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet -from core.emulator.data import EventData, IpPrefixes, NodeData, NodeOptions +from core.emulator.data import EventData, IpPrefixes, NodeData from core.emulator.enumerations import EventTypes, ExceptionLevels, MessageFlags from core.errors import CoreError from core.location.mobility import BasicRangeModel, Ns2ScriptedMobility @@ -350,8 +350,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) - options = NodeOptions(model="Host") - node = session.add_node(CoreNode, options=options) + node = session.add_node(CoreNode) session.instantiate() expected_output = "hello world" expected_status = 0 @@ -369,8 +368,7 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) - options = NodeOptions(model="Host") - node = session.add_node(CoreNode, options=options) + node = session.add_node(CoreNode) session.instantiate() # then @@ -444,10 +442,12 @@ class TestGrpc: # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() + session.set_state(EventTypes.CONFIGURATION_STATE) switch = session.add_node(SwitchNode) node = session.add_node(CoreNode) iface_data = ip_prefixes.create_iface(node) iface, _ = session.add_link(node.id, switch.id, iface_data) + session.instantiate() options = LinkOptions(bandwidth=30000) assert iface.options.bandwidth != options.bandwidth link = Link(node.id, switch.id, iface1=Interface(id=iface.id), options=options) @@ -535,7 +535,8 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions(emane=EmaneIeee80211abgModel.name) + options = EmaneNet.create_options() + options.emane_model = EmaneIeee80211abgModel.name emane_network = session.add_node(EmaneNet, options=options) session.emane.node_models[emane_network.id] = EmaneIeee80211abgModel.name config_key = "bandwidth" @@ -565,7 +566,8 @@ class TestGrpc: client = CoreGrpcClient() session = grpc_server.coreemu.create_session() session.set_location(47.57917, -122.13232, 2.00000, 1.0) - options = NodeOptions(emane=EmaneIeee80211abgModel.name) + options = EmaneNet.create_options() + options.emane_model = EmaneIeee80211abgModel.name emane_network = session.add_node(EmaneNet, options=options) session.emane.node_models[emane_network.id] = EmaneIeee80211abgModel.name @@ -685,7 +687,8 @@ class TestGrpc: # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() - options = NodeOptions(legacy=True) + options = CoreNode.create_options() + options.legacy = True node = session.add_node(CoreNode, options=options) service_name = "DefaultRoute" @@ -932,6 +935,7 @@ class TestGrpc: # given client = CoreGrpcClient() session = grpc_server.coreemu.create_session() + session.set_state(EventTypes.CONFIGURATION_STATE) wlan = session.add_node(WlanNode) node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) diff --git a/daemon/tests/test_nodes.py b/daemon/tests/test_nodes.py index 112045fa..bb76bb4e 100644 --- a/daemon/tests/test_nodes.py +++ b/daemon/tests/test_nodes.py @@ -1,6 +1,6 @@ import pytest -from core.emulator.data import InterfaceData, NodeOptions +from core.emulator.data import InterfaceData from core.emulator.session import Session from core.errors import CoreError from core.nodes.base import CoreNode @@ -14,7 +14,8 @@ class TestNodes: @pytest.mark.parametrize("model", MODELS) def test_node_add(self, session: Session, model: str): # given - options = NodeOptions(model=model) + options = CoreNode.create_options() + options.model = model # when node = session.add_node(CoreNode, options=options) diff --git a/daemon/tests/test_xml.py b/daemon/tests/test_xml.py index b3e57bef..6841da8e 100644 --- a/daemon/tests/test_xml.py +++ b/daemon/tests/test_xml.py @@ -4,7 +4,7 @@ from xml.etree import ElementTree import pytest -from core.emulator.data import IpPrefixes, LinkOptions, NodeOptions +from core.emulator.data import IpPrefixes, LinkOptions from core.emulator.enumerations import EventTypes from core.emulator.session import Session from core.errors import CoreError @@ -116,8 +116,7 @@ class TestXml: :param ip_prefixes: generates ip addresses for nodes """ # create nodes - options = NodeOptions(model="host") - node1 = session.add_node(CoreNode, options=options) + node1 = session.add_node(CoreNode) node2 = session.add_node(CoreNode) # link nodes to ptp net @@ -180,8 +179,8 @@ class TestXml: session.mobility.set_model(wlan, BasicRangeModel, {"test": "1"}) # create nodes - options = NodeOptions(model="mdr") - options.set_position(0, 0) + options = CoreNode.create_options() + options.model = "mdr" node1 = session.add_node(CoreNode, options=options) node2 = session.add_node(CoreNode, options=options) diff --git a/docs/python.md b/docs/python.md index ba4b4f33..bf9a74e1 100644 --- a/docs/python.md +++ b/docs/python.md @@ -1,7 +1,7 @@ # Python API * Table of Contents -{:toc} + {:toc} ## Overview @@ -21,13 +21,13 @@ When creating nodes of type `core.nodes.base.CoreNode` these are the default mod and the services they map to. * mdr - * zebra, OSPFv3MDR, IPForward + * zebra, OSPFv3MDR, IPForward * PC - * DefaultRoute + * DefaultRoute * router - * zebra, OSPFv2, OSPFv3, IPForward + * zebra, OSPFv2, OSPFv3, IPForward * host - * DefaultRoute, SSH + * DefaultRoute, SSH ### Interface Helper @@ -36,8 +36,10 @@ when creating interface data for nodes. Alternatively one can manually create a `core.emulator.data.InterfaceData` class instead with appropriate information. Manually creating interface data: + ```python from core.emulator.data import InterfaceData + # id is optional and will set to the next available id # name is optional and will default to eth # mac is optional and will result in a randomly generated mac @@ -52,6 +54,7 @@ iface_data = InterfaceData( ``` Leveraging the interface prefixes helper class: + ```python from core.emulator.data import IpPrefixes @@ -69,6 +72,7 @@ iface_data = ip_prefixes.create_iface( Various events that can occur within a session can be listened to. Event types: + * session - events for changes in session state and mobility start/stop/pause * node - events for node movements and icon changes * link - events for link configuration changes and wireless link add/delete @@ -80,6 +84,7 @@ Event types: def event_listener(event): print(event) + # add an event listener to event type you want to listen to # each handler will receive an object unique to that type session.event_handlers.append(event_listener) @@ -95,6 +100,7 @@ session.config_handlers.append(event_listener) Links can be configured at the time of creation or during runtime. Currently supported configuration options: + * bandwidth (bps) * delay (us) * dup (%) @@ -119,12 +125,13 @@ session.update_link(n1_id, n2_id, iface1_id, iface2_id, options) ``` ### Peer to Peer Example + ```python # required imports from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position # ip nerator for example ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24") @@ -137,10 +144,10 @@ session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) # create nodes -options = NodeOptions(x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position) +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position) # link nodes together iface1 = ip_prefixes.create_iface(n1) @@ -158,12 +165,13 @@ session.shutdown() ``` ### Switch/Hub Example + ```python # required imports from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position from core.nodes.network import SwitchNode # ip nerator for example @@ -177,14 +185,14 @@ session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) # create switch -options = NodeOptions(x=200, y=200) -switch = session.add_node(SwitchNode, options=options) +position = Position(x=200, y=200) +switch = session.add_node(SwitchNode, position=position) # create nodes -options = NodeOptions(x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position) +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position) # link nodes to switch iface1 = ip_prefixes.create_iface(n1) @@ -203,13 +211,14 @@ session.shutdown() ``` ### WLAN Example + ```python # required imports from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes from core.location.mobility import BasicRangeModel -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position from core.nodes.network import WlanNode # ip nerator for example @@ -223,14 +232,16 @@ session = coreemu.create_session() session.set_state(EventTypes.CONFIGURATION_STATE) # create wlan -options = NodeOptions(x=200, y=200) -wlan = session.add_node(WlanNode, options=options) +position = Position(x=200, y=200) +wlan = session.add_node(WlanNode, position=position) # create nodes -options = NodeOptions(model="mdr", x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(model="mdr", x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +options = CoreNode.create_options() +options.model = "mdr" +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position, options=options) +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position, options=options) # configuring wlan session.mobility.set_model_config(wlan.id, BasicRangeModel.name, { @@ -263,6 +274,7 @@ For EMANE you can import and use one of the existing models and use its name for configuration. Current models: + * core.emane.ieee80211abg.EmaneIeee80211abgModel * core.emane.rfpipe.EmaneRfPipeModel * core.emane.tdma.EmaneTdmaModel @@ -281,9 +293,9 @@ will use the defaults. When no configuration is used, the defaults are used. from core.emane.models.ieee80211abg import EmaneIeee80211abgModel from core.emane.nodes import EmaneNet from core.emulator.coreemu import CoreEmu -from core.emulator.data import IpPrefixes, NodeOptions +from core.emulator.data import IpPrefixes from core.emulator.enumerations import EventTypes -from core.nodes.base import CoreNode +from core.nodes.base import CoreNode, Position # ip nerator for example ip_prefixes = IpPrefixes(ip4_prefix="10.0.0.0/24") @@ -300,25 +312,29 @@ session.location.refscale = 150.0 session.set_state(EventTypes.CONFIGURATION_STATE) # create emane -options = NodeOptions(x=200, y=200, emane=EmaneIeee80211abgModel.name) -emane = session.add_node(EmaneNet, options=options) +options = EmaneNet.create_options() +options.emane_model = EmaneIeee80211abgModel.name +position = Position(x=200, y=200) +emane = session.add_node(EmaneNet, position=position, options=options) # create nodes -options = NodeOptions(model="mdr", x=100, y=100) -n1 = session.add_node(CoreNode, options=options) -options = NodeOptions(model="mdr", x=300, y=100) -n2 = session.add_node(CoreNode, options=options) +options = CoreNode.create_options() +options.model = "mdr" +position = Position(x=100, y=100) +n1 = session.add_node(CoreNode, position=position, options=options) +position = Position(x=300, y=100) +n2 = session.add_node(CoreNode, position=position, options=options) # configure general emane settings config = session.emane.get_configs() config.update({ - "eventservicettl": "2" + "eventservicettl": "2" }) # configure emane model settings # using a dict mapping currently support values as strings session.emane.set_model_config(emane.id, EmaneIeee80211abgModel.name, { - "unicastrate": "3", + "unicastrate": "3", }) # link nodes to emane @@ -338,6 +354,7 @@ session.shutdown() ``` EMANE Model Configuration: + ```python from core import utils @@ -358,6 +375,7 @@ Configuring the files of a service results in a specific hard coded script being generated, instead of the default scripts, that may leverage dynamic generation. The following features can be configured for a service: + * configs - files that will be generated * dirs - directories that will be mounted unique to the node * startup - commands to run start a service @@ -365,6 +383,7 @@ The following features can be configured for a service: * shutdown - commands to run to stop a service Editing service properties: + ```python # configure a service, for a node, for a given session session.services.set_service(node_id, service_name) @@ -380,6 +399,7 @@ When editing a service file, it must be the name of `config` file that the service will generate. Editing a service file: + ```python # to edit the contents of a generated file you can specify # the service, the file name, and its contents @@ -397,10 +417,12 @@ File versions of the network examples can be found [here](https://github.com/coreemu/core/tree/master/daemon/examples/python). ## Executing Scripts from GUI + To execute a python script from a GUI you need have the following. The builtin name check here to know it is being executed from the GUI, this can be avoided if your script does not use a name check. + ```python if __name__ in ["__main__", "__builtin__"]: main()