diff --git a/daemon/core/api/grpc/client.py b/daemon/core/api/grpc/client.py index 9d132a3c..da65920d 100644 --- a/daemon/core/api/grpc/client.py +++ b/daemon/core/api/grpc/client.py @@ -5,7 +5,7 @@ gRpc client for interfacing with CORE, when gRPC mode is enabled. import logging import threading from contextlib import contextmanager -from typing import Any, Callable, Dict, List +from typing import Any, Callable, Dict, Generator, List import grpc import netaddr @@ -1144,7 +1144,7 @@ class CoreGrpcClient: self.channel = None @contextmanager - def context_connect(self) -> None: + def context_connect(self) -> Generator: """ Makes a context manager based connection to the server, will close after context ends. diff --git a/daemon/core/api/grpc/events.py b/daemon/core/api/grpc/events.py index 2e58c25f..2eebd8ae 100644 --- a/daemon/core/api/grpc/events.py +++ b/daemon/core/api/grpc/events.py @@ -1,6 +1,6 @@ import logging from queue import Empty, Queue -from typing import List +from typing import Iterable from core.api.grpc import core_pb2 from core.api.grpc.grpcutils import convert_value @@ -181,7 +181,9 @@ class EventStreamer: Processes session events to generate grpc events. """ - def __init__(self, session: Session, event_types: List[core_pb2.EventType]) -> None: + def __init__( + self, session: Session, event_types: Iterable[core_pb2.EventType] + ) -> None: """ Create a EventStreamer instance. diff --git a/daemon/core/nodes/base.py b/daemon/core/nodes/base.py index b8a8a992..ccfc2c1c 100644 --- a/daemon/core/nodes/base.py +++ b/daemon/core/nodes/base.py @@ -6,6 +6,7 @@ import logging import os import shutil import threading +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple import netaddr @@ -15,8 +16,12 @@ from core.emulator.data import LinkData, NodeData from core.emulator.enumerations import LinkTypes, NodeTypes from core.errors import CoreCommandError from core.nodes import client -from core.nodes.interface import TunTap, Veth -from core.nodes.netclient import get_net_client +from core.nodes.interface import CoreInterface, TunTap, Veth +from core.nodes.netclient import LinuxNetClient, get_net_client + +if TYPE_CHECKING: + from core.emulator.distributed import DistributedServer + from core.emulator.session import Session _DEFAULT_MTU = 1500 @@ -29,9 +34,16 @@ class NodeBase: apitype = None # TODO: appears start has no usage, verify and remove - def __init__(self, session, _id=None, name=None, start=True, server=None): + def __init__( + self, + session: "Session", + _id: int = None, + name: str = None, + start: bool = True, + server: "DistributedServer" = None, + ) -> None: """ - Creates a PyCoreObj instance. + Creates a NodeBase instance. :param core.emulator.session.Session session: CORE session object :param int _id: id @@ -63,7 +75,7 @@ class NodeBase: use_ovs = session.options.get_config("ovs") == "True" self.net_client = get_net_client(use_ovs, self.host_cmd) - def startup(self): + def startup(self) -> None: """ Each object implements its own startup method. @@ -71,7 +83,7 @@ class NodeBase: """ raise NotImplementedError - def shutdown(self): + def shutdown(self) -> None: """ Each object implements its own shutdown method. @@ -79,7 +91,14 @@ class NodeBase: """ raise NotImplementedError - def host_cmd(self, args, env=None, cwd=None, wait=True, shell=False): + def host_cmd( + self, + args: str, + env: Dict[str, str] = None, + cwd: str = None, + wait: bool = True, + shell: bool = False, + ) -> str: """ Runs a command on the host system or distributed server. @@ -97,7 +116,7 @@ class NodeBase: else: return self.server.remote_cmd(args, env, cwd, wait) - def setposition(self, x=None, y=None, z=None): + def setposition(self, x: float = None, y: float = None, z: float = None) -> bool: """ Set the (x,y,z) position of the object. @@ -109,7 +128,7 @@ class NodeBase: """ return self.position.set(x=x, y=y, z=z) - def getposition(self): + def getposition(self) -> Tuple[float, float, float]: """ Return an (x,y,z) tuple representing this object's position. @@ -118,7 +137,7 @@ class NodeBase: """ return self.position.get() - def ifname(self, ifindex): + def ifname(self, ifindex: int) -> str: """ Retrieve interface name for index. @@ -128,7 +147,7 @@ class NodeBase: """ return self._netif[ifindex].name - def netifs(self, sort=False): + def netifs(self, sort: bool = False) -> List[CoreInterface]: """ Retrieve network interfaces, sorted if desired. @@ -141,7 +160,7 @@ class NodeBase: else: return list(self._netif.values()) - def numnetif(self): + def numnetif(self) -> int: """ Return the attached interface count. @@ -150,7 +169,7 @@ class NodeBase: """ return len(self._netif) - def getifindex(self, netif): + def getifindex(self, netif: CoreInterface) -> int: """ Retrieve index for an interface. @@ -163,7 +182,7 @@ class NodeBase: return ifindex return -1 - def newifindex(self): + def newifindex(self) -> int: """ Create a new interface index. @@ -176,7 +195,14 @@ class NodeBase: self.ifindex += 1 return ifindex - def data(self, message_type, lat=None, lon=None, alt=None, source=None): + def data( + self, + message_type: int, + lat: float = None, + lon: float = None, + alt: float = None, + source: str = None, + ) -> NodeData: """ Build a data object for this node. @@ -223,7 +249,7 @@ class NodeBase: return node_data - def all_link_data(self, flags): + def all_link_data(self, flags: int) -> List: """ Build CORE Link data for this object. There is no default method for PyCoreObjs as PyCoreNodes do not implement this but @@ -241,7 +267,14 @@ class CoreNodeBase(NodeBase): Base class for CORE nodes. """ - def __init__(self, session, _id=None, name=None, start=True, server=None): + def __init__( + self, + session: "Session", + _id: int = None, + name: str = None, + start: bool = True, + server: "DistributedServer" = None, + ) -> None: """ Create a CoreNodeBase instance. @@ -257,7 +290,7 @@ class CoreNodeBase(NodeBase): self.nodedir = None self.tmpnodedir = False - def makenodedir(self): + def makenodedir(self) -> None: """ Create the node directory. @@ -270,7 +303,7 @@ class CoreNodeBase(NodeBase): else: self.tmpnodedir = False - def rmnodedir(self): + def rmnodedir(self) -> None: """ Remove the node directory, unless preserve directory has been set. @@ -283,7 +316,7 @@ class CoreNodeBase(NodeBase): if self.tmpnodedir: self.host_cmd(f"rm -rf {self.nodedir}") - def addnetif(self, netif, ifindex): + def addnetif(self, netif: CoreInterface, ifindex: int) -> None: """ Add network interface to node and set the network interface index if successful. @@ -296,7 +329,7 @@ class CoreNodeBase(NodeBase): self._netif[ifindex] = netif netif.netindex = ifindex - def delnetif(self, ifindex): + def delnetif(self, ifindex: int) -> None: """ Delete a network interface @@ -309,7 +342,7 @@ class CoreNodeBase(NodeBase): netif.shutdown() del netif - def netif(self, ifindex): + def netif(self, ifindex: int) -> Optional[CoreInterface]: """ Retrieve network interface. @@ -322,7 +355,7 @@ class CoreNodeBase(NodeBase): else: return None - def attachnet(self, ifindex, net): + def attachnet(self, ifindex: int, net: "CoreNetworkBase") -> None: """ Attach a network. @@ -334,7 +367,7 @@ class CoreNodeBase(NodeBase): raise ValueError(f"ifindex {ifindex} does not exist") self._netif[ifindex].attachnet(net) - def detachnet(self, ifindex): + def detachnet(self, ifindex: int) -> None: """ Detach network interface. @@ -345,7 +378,7 @@ class CoreNodeBase(NodeBase): raise ValueError(f"ifindex {ifindex} does not exist") self._netif[ifindex].detachnet() - def setposition(self, x=None, y=None, z=None): + def setposition(self, x: float = None, y: float = None, z: float = None) -> None: """ Set position. @@ -359,7 +392,9 @@ class CoreNodeBase(NodeBase): for netif in self.netifs(sort=True): netif.setposition(x, y, z) - def commonnets(self, obj, want_ctrl=False): + def commonnets( + self, obj: "CoreNodeBase", want_ctrl: bool = False + ) -> List[Tuple[NodeBase, CoreInterface, CoreInterface]]: """ Given another node or net object, return common networks between this node and that object. A list of tuples is returned, with each tuple @@ -377,10 +412,9 @@ class CoreNodeBase(NodeBase): for netif2 in obj.netifs(): if netif1.net == netif2.net: common.append((netif1.net, netif1, netif2)) - return common - def cmd(self, args, wait=True, shell=False): + def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: """ Runs a command within a node container. @@ -393,7 +427,7 @@ class CoreNodeBase(NodeBase): """ raise NotImplementedError - def termcmdstring(self, sh): + def termcmdstring(self, sh: str) -> str: """ Create a terminal command string. @@ -413,14 +447,14 @@ class CoreNode(CoreNodeBase): def __init__( self, - session, - _id=None, - name=None, - nodedir=None, - bootsh="boot.sh", - start=True, - server=None, - ): + session: "Session", + _id: int = None, + name: str = None, + nodedir: str = None, + bootsh: str = "boot.sh", + start: bool = True, + server: "DistributedServer" = None, + ) -> None: """ Create a CoreNode instance. @@ -451,17 +485,17 @@ class CoreNode(CoreNodeBase): if start: self.startup() - def create_node_net_client(self, use_ovs): + def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ Create node network client for running network commands within the nodes container. :param bool use_ovs: True for OVS bridges, False for Linux bridges - :return:node network client + :return: node network client """ return get_net_client(use_ovs, self.cmd) - def alive(self): + def alive(self) -> bool: """ Check if the node is alive. @@ -475,7 +509,7 @@ class CoreNode(CoreNodeBase): return True - def startup(self): + def startup(self) -> None: """ Start a new namespace node by invoking the vnoded process that allocates a new namespace. Bring up the loopback device and set @@ -521,7 +555,7 @@ class CoreNode(CoreNodeBase): self.privatedir("/var/run") self.privatedir("/var/log") - def shutdown(self): + def shutdown(self) -> None: """ Shutdown logic for simple lxc nodes. @@ -562,7 +596,7 @@ class CoreNode(CoreNodeBase): finally: self.rmnodedir() - def cmd(self, args, wait=True, shell=False): + def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: """ Runs a command that is used to configure and setup the network within a node. @@ -580,7 +614,7 @@ class CoreNode(CoreNodeBase): args = self.client.create_cmd(args) return self.server.remote_cmd(args, wait=wait) - def termcmdstring(self, sh="/bin/sh"): + def termcmdstring(self, sh: str = "/bin/sh") -> str: """ Create a terminal command string. @@ -593,7 +627,7 @@ class CoreNode(CoreNodeBase): else: return f"ssh -X -f {self.server.host} xterm -e {terminal}" - def privatedir(self, path): + def privatedir(self, path: str) -> None: """ Create a private directory. @@ -608,7 +642,7 @@ class CoreNode(CoreNodeBase): self.host_cmd(f"mkdir -p {hostpath}") self.mount(hostpath, path) - def mount(self, source, target): + def mount(self, source: str, target: str) -> None: """ Create and mount a directory. @@ -623,7 +657,7 @@ class CoreNode(CoreNodeBase): self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}") self._mounts.append((source, target)) - def newifindex(self): + def newifindex(self) -> int: """ Retrieve a new interface index. @@ -633,7 +667,7 @@ class CoreNode(CoreNodeBase): with self.lock: return super().newifindex() - def newveth(self, ifindex=None, ifname=None): + def newveth(self, ifindex: int = None, ifname: str = None) -> int: """ Create a new interface. @@ -690,7 +724,7 @@ class CoreNode(CoreNodeBase): return ifindex - def newtuntap(self, ifindex=None, ifname=None): + def newtuntap(self, ifindex: int = None, ifname: str = None) -> int: """ Create a new tunnel tap. @@ -720,7 +754,7 @@ class CoreNode(CoreNodeBase): return ifindex - def sethwaddr(self, ifindex, addr): + def sethwaddr(self, ifindex: int, addr: str) -> None: """ Set hardware addres for an interface. @@ -735,7 +769,7 @@ class CoreNode(CoreNodeBase): if self.up: self.node_net_client.device_mac(interface.name, addr) - def addaddr(self, ifindex, addr): + def addaddr(self, ifindex: int, addr: str) -> None: """ Add interface address. @@ -753,7 +787,7 @@ class CoreNode(CoreNodeBase): broadcast = "+" self.node_net_client.create_address(interface.name, addr, broadcast) - def deladdr(self, ifindex, addr): + def deladdr(self, ifindex: int, addr: str) -> None: """ Delete address from an interface. @@ -772,7 +806,7 @@ class CoreNode(CoreNodeBase): if self.up: self.node_net_client.delete_address(interface.name, addr) - def ifup(self, ifindex): + def ifup(self, ifindex: int) -> None: """ Bring an interface up. @@ -783,7 +817,14 @@ class CoreNode(CoreNodeBase): interface_name = self.ifname(ifindex) self.node_net_client.device_up(interface_name) - def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): + def newnetif( + self, + net: "CoreNetworkBase" = None, + addrlist: List[str] = None, + hwaddr: str = None, + ifindex: int = None, + ifname: str = None, + ) -> int: """ Create a new network interface. @@ -827,7 +868,7 @@ class CoreNode(CoreNodeBase): self.ifup(ifindex) return ifindex - def addfile(self, srcname, filename): + def addfile(self, srcname: str, filename: str) -> None: """ Add a file. @@ -846,7 +887,7 @@ class CoreNode(CoreNodeBase): self.host_cmd(f"mkdir -p {directory}") self.server.remote_put(srcname, filename) - def hostfilename(self, filename): + def hostfilename(self, filename: str) -> str: """ Return the name of a node"s file on the host filesystem. @@ -862,7 +903,7 @@ class CoreNode(CoreNodeBase): dirname = os.path.join(self.nodedir, dirname) return os.path.join(dirname, basename) - def nodefile(self, filename, contents, mode=0o644): + def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. @@ -887,7 +928,7 @@ class CoreNode(CoreNodeBase): "node(%s) added file: %s; mode: 0%o", self.name, hostfilename, mode ) - def nodefilecopy(self, filename, srcfilename, mode=None): + def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None: """ Copy a file to a node, following symlinks and preserving metadata. Change file mode if specified. @@ -917,7 +958,14 @@ class CoreNetworkBase(NodeBase): linktype = LinkTypes.WIRED.value is_emane = False - def __init__(self, session, _id, name, start=True, server=None): + def __init__( + self, + session: "Session", + _id: int, + name: str, + start: bool = True, + server: "DistributedServer" = None, + ) -> None: """ Create a CoreNetworkBase instance. @@ -932,7 +980,7 @@ class CoreNetworkBase(NodeBase): self._linked = {} self._linked_lock = threading.Lock() - def startup(self): + def startup(self) -> None: """ Each object implements its own startup method. @@ -940,7 +988,7 @@ class CoreNetworkBase(NodeBase): """ raise NotImplementedError - def shutdown(self): + def shutdown(self) -> None: """ Each object implements its own shutdown method. @@ -948,7 +996,7 @@ class CoreNetworkBase(NodeBase): """ raise NotImplementedError - def linknet(self, net): + def linknet(self, net: "CoreNetworkBase") -> CoreInterface: """ Link network to another. @@ -958,7 +1006,7 @@ class CoreNetworkBase(NodeBase): """ pass - def getlinknetif(self, net): + def getlinknetif(self, net: "CoreNetworkBase") -> CoreInterface: """ Return the interface of that links this net with another net. @@ -971,7 +1019,7 @@ class CoreNetworkBase(NodeBase): return netif return None - def attach(self, netif): + def attach(self, netif: CoreInterface) -> None: """ Attach network interface. @@ -984,7 +1032,7 @@ class CoreNetworkBase(NodeBase): with self._linked_lock: self._linked[netif] = {} - def detach(self, netif): + def detach(self, netif: CoreInterface) -> None: """ Detach network interface. @@ -996,7 +1044,7 @@ class CoreNetworkBase(NodeBase): with self._linked_lock: del self._linked[netif] - def all_link_data(self, flags): + def all_link_data(self, flags: int) -> List[LinkData]: """ Build link data objects for this network. Each link object describes a link between this network and a node. @@ -1004,7 +1052,6 @@ class CoreNetworkBase(NodeBase): :param int flags: message type :return: list of link data :rtype: list[core.data.LinkData] - """ all_links = [] @@ -1095,7 +1142,7 @@ class Position: Helper class for Cartesian coordinate position """ - def __init__(self, x=None, y=None, z=None): + def __init__(self, x: float = None, y: float = None, z: float = None) -> None: """ Creates a Position instance. @@ -1108,7 +1155,7 @@ class Position: self.y = y self.z = z - def set(self, x=None, y=None, z=None): + def set(self, x: float = None, y: float = None, z: float = None) -> bool: """ Returns True if the position has actually changed. @@ -1125,7 +1172,7 @@ class Position: self.z = z return True - def get(self): + def get(self) -> Tuple[float, float, float]: """ Retrieve x,y,z position. diff --git a/daemon/core/nodes/client.py b/daemon/core/nodes/client.py index 66b61c37..c2624d6b 100644 --- a/daemon/core/nodes/client.py +++ b/daemon/core/nodes/client.py @@ -13,7 +13,7 @@ class VnodeClient: Provides client functionality for interacting with a virtual node. """ - def __init__(self, name, ctrlchnlname): + def __init__(self, name: str, ctrlchnlname: str) -> None: """ Create a VnodeClient instance. @@ -23,7 +23,7 @@ class VnodeClient: self.name = name self.ctrlchnlname = ctrlchnlname - def _verify_connection(self): + def _verify_connection(self) -> None: """ Checks that the vcmd client is properly connected. @@ -33,7 +33,7 @@ class VnodeClient: if not self.connected(): raise IOError("vcmd not connected") - def connected(self): + def connected(self) -> bool: """ Check if node is connected or not. @@ -42,7 +42,7 @@ class VnodeClient: """ return True - def close(self): + def close(self) -> None: """ Close the client connection. @@ -50,10 +50,10 @@ class VnodeClient: """ pass - def create_cmd(self, args): + def create_cmd(self, args: str) -> str: return f"{VCMD_BIN} -c {self.ctrlchnlname} -- {args}" - def check_cmd(self, args, wait=True, shell=False): + def check_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: """ Run command and return exit status and combined stdout and stderr. diff --git a/daemon/core/nodes/docker.py b/daemon/core/nodes/docker.py index b56fcc5c..445fb3ec 100644 --- a/daemon/core/nodes/docker.py +++ b/daemon/core/nodes/docker.py @@ -2,22 +2,27 @@ import json import logging import os from tempfile import NamedTemporaryFile +from typing import TYPE_CHECKING, Callable, Dict from core import utils +from core.emulator.distributed import DistributedServer from core.emulator.enumerations import NodeTypes from core.errors import CoreCommandError from core.nodes.base import CoreNode -from core.nodes.netclient import get_net_client +from core.nodes.netclient import LinuxNetClient, get_net_client + +if TYPE_CHECKING: + from core.emulator.session import Session class DockerClient: - def __init__(self, name, image, run): + def __init__(self, name: str, image: str, run: Callable) -> None: self.name = name self.image = image self.run = run self.pid = None - def create_container(self): + def create_container(self) -> str: self.run( f"docker run -td --init --net=none --hostname {self.name} --name {self.name} " f"--sysctl net.ipv6.conf.all.disable_ipv6=0 {self.image} /bin/bash" @@ -25,7 +30,7 @@ class DockerClient: self.pid = self.get_pid() return self.pid - def get_info(self): + def get_info(self) -> Dict: args = f"docker inspect {self.name}" output = self.run(args) data = json.loads(output) @@ -33,35 +38,35 @@ class DockerClient: raise CoreCommandError(-1, args, f"docker({self.name}) not present") return data[0] - def is_alive(self): + def is_alive(self) -> bool: try: data = self.get_info() return data["State"]["Running"] except CoreCommandError: return False - def stop_container(self): + def stop_container(self) -> None: self.run(f"docker rm -f {self.name}") - def check_cmd(self, cmd, wait=True, shell=False): + def check_cmd(self, cmd: str, wait: bool = True, shell: bool = False) -> str: logging.info("docker cmd output: %s", cmd) return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell) - def create_ns_cmd(self, cmd): + def create_ns_cmd(self, cmd: str) -> str: return f"nsenter -t {self.pid} -u -i -p -n {cmd}" - def ns_cmd(self, cmd, wait): + def ns_cmd(self, cmd: str, wait: bool) -> str: args = f"nsenter -t {self.pid} -u -i -p -n {cmd}" return utils.cmd(args, wait=wait) - def get_pid(self): + def get_pid(self) -> str: args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}" output = self.run(args) self.pid = output logging.debug("node(%s) pid: %s", self.name, self.pid) return output - def copy_file(self, source, destination): + def copy_file(self, source: str, destination: str) -> str: args = f"docker cp {source} {self.name}:{destination}" return self.run(args) @@ -71,15 +76,15 @@ class DockerNode(CoreNode): def __init__( self, - session, - _id=None, - name=None, - nodedir=None, - bootsh="boot.sh", - start=True, - server=None, - image=None - ): + session: "Session", + _id: int = None, + name: str = None, + nodedir: str = None, + bootsh: str = "boot.sh", + start: bool = True, + server: DistributedServer = None, + image: str = None + ) -> None: """ Create a DockerNode instance. @@ -98,7 +103,7 @@ class DockerNode(CoreNode): self.image = image super().__init__(session, _id, name, nodedir, bootsh, start, server) - def create_node_net_client(self, use_ovs): + def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient: """ Create node network client for running network commands within the nodes container. @@ -108,7 +113,7 @@ class DockerNode(CoreNode): """ return get_net_client(use_ovs, self.nsenter_cmd) - def alive(self): + def alive(self) -> bool: """ Check if the node is alive. @@ -117,7 +122,7 @@ class DockerNode(CoreNode): """ return self.client.is_alive() - def startup(self): + def startup(self) -> None: """ Start a new namespace node by invoking the vnoded process that allocates a new namespace. Bring up the loopback device and set @@ -133,7 +138,7 @@ class DockerNode(CoreNode): self.pid = self.client.create_container() self.up = True - def shutdown(self): + def shutdown(self) -> None: """ Shutdown logic. @@ -148,7 +153,7 @@ class DockerNode(CoreNode): self.client.stop_container() self.up = False - def nsenter_cmd(self, args, wait=True, shell=False): + def nsenter_cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: if self.server is None: args = self.client.create_ns_cmd(args) return utils.cmd(args, wait=wait, shell=shell) @@ -156,7 +161,7 @@ class DockerNode(CoreNode): args = self.client.create_ns_cmd(args) return self.server.remote_cmd(args, wait=wait) - def termcmdstring(self, sh="/bin/sh"): + def termcmdstring(self, sh: str = "/bin/sh") -> str: """ Create a terminal command string. @@ -165,7 +170,7 @@ class DockerNode(CoreNode): """ return f"docker exec -it {self.name} bash" - def privatedir(self, path): + def privatedir(self, path: str) -> None: """ Create a private directory. @@ -176,7 +181,7 @@ class DockerNode(CoreNode): args = f"mkdir -p {path}" self.cmd(args) - def mount(self, source, target): + def mount(self, source: str, target: str) -> None: """ Create and mount a directory. @@ -188,7 +193,7 @@ class DockerNode(CoreNode): logging.debug("mounting source(%s) target(%s)", source, target) raise Exception("not supported") - def nodefile(self, filename, contents, mode=0o644): + def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. @@ -216,7 +221,7 @@ class DockerNode(CoreNode): "node(%s) added file: %s; mode: 0%o", self.name, filename, mode ) - def nodefilecopy(self, filename, srcfilename, mode=None): + def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None: """ Copy a file to a node, following symlinks and preserving metadata. Change file mode if specified. diff --git a/daemon/core/nodes/interface.py b/daemon/core/nodes/interface.py index 236bdd5c..aea3116d 100644 --- a/daemon/core/nodes/interface.py +++ b/daemon/core/nodes/interface.py @@ -4,18 +4,31 @@ virtual ethernet classes that implement the interfaces available under Linux. import logging import time +from typing import TYPE_CHECKING, Callable, Dict, List, Tuple from core import utils from core.errors import CoreCommandError from core.nodes.netclient import get_net_client +if TYPE_CHECKING: + from core.emulator.distributed import DistributedServer + from core.emulator.session import Session + from core.nodes.base import CoreNetworkBase, CoreNode + class CoreInterface: """ Base class for network interfaces. """ - def __init__(self, session, node, name, mtu, server=None): + def __init__( + self, + session: "Session", + node: "CoreNode", + name: str, + mtu: int, + server: "DistributedServer" = None, + ) -> None: """ Creates a CoreInterface instance. @@ -50,7 +63,14 @@ class CoreInterface: use_ovs = session.options.get_config("ovs") == "True" self.net_client = get_net_client(use_ovs, self.host_cmd) - def host_cmd(self, args, env=None, cwd=None, wait=True, shell=False): + def host_cmd( + self, + args: str, + env: Dict[str, str] = None, + cwd: str = None, + wait: bool = True, + shell: bool = False, + ) -> str: """ Runs a command on the host system or distributed server. @@ -68,7 +88,7 @@ class CoreInterface: else: return self.server.remote_cmd(args, env, cwd, wait) - def startup(self): + def startup(self) -> None: """ Startup method for the interface. @@ -76,7 +96,7 @@ class CoreInterface: """ pass - def shutdown(self): + def shutdown(self) -> None: """ Shutdown method for the interface. @@ -84,7 +104,7 @@ class CoreInterface: """ pass - def attachnet(self, net): + def attachnet(self, net: "CoreNetworkBase") -> None: """ Attach network. @@ -98,7 +118,7 @@ class CoreInterface: net.attach(self) self.net = net - def detachnet(self): + def detachnet(self) -> None: """ Detach from a network. @@ -107,7 +127,7 @@ class CoreInterface: if self.net is not None: self.net.detach(self) - def addaddr(self, addr): + def addaddr(self, addr: str) -> None: """ Add address. @@ -117,7 +137,7 @@ class CoreInterface: addr = utils.validate_ip(addr) self.addrlist.append(addr) - def deladdr(self, addr): + def deladdr(self, addr: str) -> None: """ Delete address. @@ -126,7 +146,7 @@ class CoreInterface: """ self.addrlist.remove(addr) - def sethwaddr(self, addr): + def sethwaddr(self, addr: str) -> None: """ Set hardware address. @@ -136,7 +156,7 @@ class CoreInterface: addr = utils.validate_mac(addr) self.hwaddr = addr - def getparam(self, key): + def getparam(self, key: str) -> float: """ Retrieve a parameter from the, or None if the parameter does not exist. @@ -145,7 +165,7 @@ class CoreInterface: """ return self._params.get(key) - def getparams(self): + def getparams(self) -> List[Tuple[str, float]]: """ Return (key, value) pairs for parameters. """ @@ -154,7 +174,7 @@ class CoreInterface: parameters.append((k, self._params[k])) return parameters - def setparam(self, key, value): + def setparam(self, key: str, value: float) -> bool: """ Set a parameter value, returns True if the parameter has changed. @@ -174,7 +194,7 @@ class CoreInterface: self._params[key] = value return True - def swapparams(self, name): + def swapparams(self, name: str) -> None: """ Swap out parameters dict for name. If name does not exist, intialize it. This is for supporting separate upstream/downstream @@ -189,7 +209,7 @@ class CoreInterface: self._params = getattr(self, name) setattr(self, name, tmp) - def setposition(self, x, y, z): + def setposition(self, x: float, y: float, z: float) -> None: """ Dispatch position hook handler. @@ -200,7 +220,7 @@ class CoreInterface: """ self.poshook(self, x, y, z) - def __lt__(self, other): + def __lt__(self, other: "CoreInterface") -> bool: """ Used for comparisons of this object. @@ -217,8 +237,15 @@ class Veth(CoreInterface): """ def __init__( - self, session, node, name, localname, mtu=1500, server=None, start=True - ): + self, + session: "Session", + node: "CoreNode", + name: str, + localname: str, + mtu: int = 1500, + server: "DistributedServer" = None, + start: bool = True, + ) -> None: """ Creates a VEth instance. @@ -239,7 +266,7 @@ class Veth(CoreInterface): if start: self.startup() - def startup(self): + def startup(self) -> None: """ Interface startup logic. @@ -250,7 +277,7 @@ class Veth(CoreInterface): self.net_client.device_up(self.localname) self.up = True - def shutdown(self): + def shutdown(self) -> None: """ Interface shutdown logic. @@ -280,8 +307,15 @@ class TunTap(CoreInterface): """ def __init__( - self, session, node, name, localname, mtu=1500, server=None, start=True - ): + self, + session: "Session", + node: "CoreNode", + name: str, + localname: str, + mtu: int = 1500, + server: "DistributedServer" = None, + start: bool = True, + ) -> None: """ Create a TunTap instance. @@ -301,7 +335,7 @@ class TunTap(CoreInterface): if start: self.startup() - def startup(self): + def startup(self) -> None: """ Startup logic for a tunnel tap. @@ -315,7 +349,7 @@ class TunTap(CoreInterface): # self.install() self.up = True - def shutdown(self): + def shutdown(self) -> None: """ Shutdown functionality for a tunnel tap. @@ -331,7 +365,9 @@ class TunTap(CoreInterface): self.up = False - def waitfor(self, func, attempts=10, maxretrydelay=0.25): + def waitfor( + self, func: Callable, attempts: int = 10, maxretrydelay: float = 0.25 + ) -> bool: """ Wait for func() to return zero with exponential backoff. @@ -362,7 +398,7 @@ class TunTap(CoreInterface): return result - def waitfordevicelocal(self): + def waitfordevicelocal(self) -> None: """ Check for presence of a local device - tap device may not appear right away waits @@ -381,7 +417,7 @@ class TunTap(CoreInterface): self.waitfor(localdevexists) - def waitfordevicenode(self): + def waitfordevicenode(self) -> None: """ Check for presence of a node device - tap device may not appear right away waits. @@ -412,7 +448,7 @@ class TunTap(CoreInterface): else: raise RuntimeError("node device failed to exist") - def install(self): + def install(self) -> None: """ Install this TAP into its namespace. This is not done from the startup() method but called at a later time when a userspace @@ -428,7 +464,7 @@ class TunTap(CoreInterface): self.node.node_net_client.device_name(self.localname, self.name) self.node.node_net_client.device_up(self.name) - def setaddrs(self): + def setaddrs(self) -> None: """ Set interface addresses based on self.addrlist. @@ -448,18 +484,18 @@ class GreTap(CoreInterface): def __init__( self, - node=None, - name=None, - session=None, - mtu=1458, - remoteip=None, - _id=None, - localip=None, - ttl=255, - key=None, - start=True, - server=None, - ): + node: "CoreNode" = None, + name: str = None, + session: "Session" = None, + mtu: int = 1458, + remoteip: str = None, + _id: int = None, + localip: str = None, + ttl: int = 255, + key: int = None, + start: bool = True, + server: "DistributedServer" = None, + ) -> None: """ Creates a GreTap instance. @@ -497,7 +533,7 @@ class GreTap(CoreInterface): self.net_client.device_up(self.localname) self.up = True - def shutdown(self): + def shutdown(self) -> None: """ Shutdown logic for a GreTap. @@ -512,7 +548,7 @@ class GreTap(CoreInterface): self.localname = None - def data(self, message_type): + def data(self, message_type: int) -> None: """ Data for a gre tap. @@ -521,7 +557,7 @@ class GreTap(CoreInterface): """ return None - def all_link_data(self, flags): + def all_link_data(self, flags: int) -> List: """ Retrieve link data. diff --git a/daemon/core/nodes/lxd.py b/daemon/core/nodes/lxd.py index 2d7a6d3d..5e5dd927 100644 --- a/daemon/core/nodes/lxd.py +++ b/daemon/core/nodes/lxd.py @@ -3,27 +3,33 @@ import logging import os import time from tempfile import NamedTemporaryFile +from typing import TYPE_CHECKING, Callable, Dict from core import utils +from core.emulator.distributed import DistributedServer from core.emulator.enumerations import NodeTypes from core.errors import CoreCommandError from core.nodes.base import CoreNode +from core.nodes.interface import CoreInterface + +if TYPE_CHECKING: + from core.emulator.session import Session class LxdClient: - def __init__(self, name, image, run): + def __init__(self, name: str, image: str, run: Callable) -> None: self.name = name self.image = image self.run = run self.pid = None - def create_container(self): + def create_container(self) -> int: self.run(f"lxc launch {self.image} {self.name}") data = self.get_info() self.pid = data["state"]["pid"] return self.pid - def get_info(self): + def get_info(self) -> Dict: args = f"lxc list {self.name} --format json" output = self.run(args) data = json.loads(output) @@ -31,27 +37,27 @@ class LxdClient: raise CoreCommandError(-1, args, f"LXC({self.name}) not present") return data[0] - def is_alive(self): + def is_alive(self) -> bool: try: data = self.get_info() return data["state"]["status"] == "Running" except CoreCommandError: return False - def stop_container(self): + def stop_container(self) -> None: self.run(f"lxc delete --force {self.name}") - def create_cmd(self, cmd): + def create_cmd(self, cmd: str) -> str: return f"lxc exec -nT {self.name} -- {cmd}" - def create_ns_cmd(self, cmd): + def create_ns_cmd(self, cmd: str) -> str: return f"nsenter -t {self.pid} -m -u -i -p -n {cmd}" - def check_cmd(self, cmd, wait=True, shell=False): + def check_cmd(self, cmd: str, wait: bool = True, shell: bool = False) -> str: args = self.create_cmd(cmd) return utils.cmd(args, wait=wait, shell=shell) - def copy_file(self, source, destination): + def copy_file(self, source: str, destination: str) -> None: if destination[0] != "/": destination = os.path.join("/root/", destination) @@ -64,15 +70,15 @@ class LxcNode(CoreNode): def __init__( self, - session, - _id=None, - name=None, - nodedir=None, - bootsh="boot.sh", - start=True, - server=None, - image=None, - ): + session: "Session", + _id: int = None, + name: str = None, + nodedir: str = None, + bootsh: str = "boot.sh", + start: bool = True, + server: DistributedServer = None, + image: str = None, + ) -> None: """ Create a LxcNode instance. @@ -91,7 +97,7 @@ class LxcNode(CoreNode): self.image = image super().__init__(session, _id, name, nodedir, bootsh, start, server) - def alive(self): + def alive(self) -> bool: """ Check if the node is alive. @@ -100,7 +106,7 @@ class LxcNode(CoreNode): """ return self.client.is_alive() - def startup(self): + def startup(self) -> None: """ Startup logic. @@ -114,7 +120,7 @@ class LxcNode(CoreNode): self.pid = self.client.create_container() self.up = True - def shutdown(self): + def shutdown(self) -> None: """ Shutdown logic. @@ -129,7 +135,7 @@ class LxcNode(CoreNode): self.client.stop_container() self.up = False - def termcmdstring(self, sh="/bin/sh"): + def termcmdstring(self, sh: str = "/bin/sh") -> str: """ Create a terminal command string. @@ -138,7 +144,7 @@ class LxcNode(CoreNode): """ return f"lxc exec {self.name} -- {sh}" - def privatedir(self, path): + def privatedir(self, path: str) -> None: """ Create a private directory. @@ -147,9 +153,9 @@ class LxcNode(CoreNode): """ logging.info("creating node dir: %s", path) args = f"mkdir -p {path}" - return self.cmd(args) + self.cmd(args) - def mount(self, source, target): + def mount(self, source: str, target: str) -> None: """ Create and mount a directory. @@ -161,7 +167,7 @@ class LxcNode(CoreNode): logging.debug("mounting source(%s) target(%s)", source, target) raise Exception("not supported") - def nodefile(self, filename, contents, mode=0o644): + def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: """ Create a node file with a given mode. @@ -188,7 +194,7 @@ class LxcNode(CoreNode): os.unlink(temp.name) logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode) - def nodefilecopy(self, filename, srcfilename, mode=None): + def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None: """ Copy a file to a node, following symlinks and preserving metadata. Change file mode if specified. @@ -214,7 +220,7 @@ class LxcNode(CoreNode): self.client.copy_file(source, filename) self.cmd(f"chmod {mode:o} {filename}") - def addnetif(self, netif, ifindex): + def addnetif(self, netif: CoreInterface, ifindex: int) -> None: super().addnetif(netif, ifindex) # adding small delay to allow time for adding addresses to work correctly time.sleep(0.5) diff --git a/daemon/core/nodes/netclient.py b/daemon/core/nodes/netclient.py index 8053a0e8..e51285ec 100644 --- a/daemon/core/nodes/netclient.py +++ b/daemon/core/nodes/netclient.py @@ -2,30 +2,17 @@ Clients for dealing with bridge/interface commands. """ import json +from typing import Callable from core.constants import ETHTOOL_BIN, IP_BIN, OVS_BIN, TC_BIN -def get_net_client(use_ovs, run): - """ - Retrieve desired net client for running network commands. - - :param bool use_ovs: True for OVS bridges, False for Linux bridges - :param func run: function used to run net client commands - :return: net client class - """ - if use_ovs: - return OvsNetClient(run) - else: - return LinuxNetClient(run) - - class LinuxNetClient: """ Client for creating Linux bridges and ip interfaces for nodes. """ - def __init__(self, run): + def __init__(self, run: Callable) -> None: """ Create LinuxNetClient instance. @@ -33,7 +20,7 @@ class LinuxNetClient: """ self.run = run - def set_hostname(self, name): + def set_hostname(self, name: str) -> None: """ Set network hostname. @@ -42,7 +29,7 @@ class LinuxNetClient: """ self.run(f"hostname {name}") - def create_route(self, route, device): + def create_route(self, route: str, device: str) -> None: """ Create a new route for a device. @@ -52,7 +39,7 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} route add {route} dev {device}") - def device_up(self, device): + def device_up(self, device: str) -> None: """ Bring a device up. @@ -61,7 +48,7 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} link set {device} up") - def device_down(self, device): + def device_down(self, device: str) -> None: """ Bring a device down. @@ -70,7 +57,7 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} link set {device} down") - def device_name(self, device, name): + def device_name(self, device: str, name: str) -> None: """ Set a device name. @@ -80,7 +67,7 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} link set {device} name {name}") - def device_show(self, device): + def device_show(self, device: str) -> str: """ Show information for a device. @@ -90,7 +77,7 @@ class LinuxNetClient: """ return self.run(f"{IP_BIN} link show {device}") - def get_mac(self, device): + def get_mac(self, device: str) -> str: """ Retrieve MAC address for a given device. @@ -100,7 +87,7 @@ class LinuxNetClient: """ return self.run(f"cat /sys/class/net/{device}/address") - def get_ifindex(self, device): + def get_ifindex(self, device: str) -> str: """ Retrieve ifindex for a given device. @@ -110,7 +97,7 @@ class LinuxNetClient: """ return self.run(f"cat /sys/class/net/{device}/ifindex") - def device_ns(self, device, namespace): + def device_ns(self, device: str, namespace: str) -> None: """ Set netns for a device. @@ -120,7 +107,7 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} link set {device} netns {namespace}") - def device_flush(self, device): + def device_flush(self, device: str) -> None: """ Flush device addresses. @@ -132,7 +119,7 @@ class LinuxNetClient: shell=True, ) - def device_mac(self, device, mac): + def device_mac(self, device: str, mac: str) -> None: """ Set MAC address for a device. @@ -142,7 +129,7 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} link set dev {device} address {mac}") - def delete_device(self, device): + def delete_device(self, device: str) -> None: """ Delete device. @@ -151,7 +138,7 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} link delete {device}") - def delete_tc(self, device): + def delete_tc(self, device: str) -> None: """ Remove traffic control settings for a device. @@ -160,7 +147,7 @@ class LinuxNetClient: """ self.run(f"{TC_BIN} qdisc delete dev {device} root") - def checksums_off(self, interface_name): + def checksums_off(self, interface_name: str) -> None: """ Turns interface checksums off. @@ -169,7 +156,7 @@ class LinuxNetClient: """ self.run(f"{ETHTOOL_BIN} -K {interface_name} rx off tx off") - def create_address(self, device, address, broadcast=None): + def create_address(self, device: str, address: str, broadcast: str = None) -> None: """ Create address for a device. @@ -185,7 +172,7 @@ class LinuxNetClient: else: self.run(f"{IP_BIN} address add {address} dev {device}") - def delete_address(self, device, address): + def delete_address(self, device: str, address: str) -> None: """ Delete an address from a device. @@ -195,7 +182,7 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} address delete {address} dev {device}") - def create_veth(self, name, peer): + def create_veth(self, name: str, peer: str) -> None: """ Create a veth pair. @@ -205,7 +192,9 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} link add name {name} type veth peer name {peer}") - def create_gretap(self, device, address, local, ttl, key): + def create_gretap( + self, device: str, address: str, local: str, ttl: int, key: int + ) -> None: """ Create a GRE tap on a device. @@ -225,7 +214,7 @@ class LinuxNetClient: cmd += f" key {key}" self.run(cmd) - def create_bridge(self, name): + def create_bridge(self, name: str) -> None: """ Create a Linux bridge and bring it up. @@ -238,7 +227,7 @@ class LinuxNetClient: self.run(f"{IP_BIN} link set {name} type bridge mcast_snooping 0") self.device_up(name) - def delete_bridge(self, name): + def delete_bridge(self, name: str) -> None: """ Bring down and delete a Linux bridge. @@ -248,7 +237,7 @@ class LinuxNetClient: self.device_down(name) self.run(f"{IP_BIN} link delete {name} type bridge") - def create_interface(self, bridge_name, interface_name): + def create_interface(self, bridge_name: str, interface_name: str) -> None: """ Create an interface associated with a Linux bridge. @@ -259,7 +248,7 @@ class LinuxNetClient: self.run(f"{IP_BIN} link set dev {interface_name} master {bridge_name}") self.device_up(interface_name) - def delete_interface(self, bridge_name, interface_name): + def delete_interface(self, bridge_name: str, interface_name: str) -> None: """ Delete an interface associated with a Linux bridge. @@ -269,11 +258,12 @@ class LinuxNetClient: """ self.run(f"{IP_BIN} link set dev {interface_name} nomaster") - def existing_bridges(self, _id): + def existing_bridges(self, _id: int) -> bool: """ Checks if there are any existing Linux bridges for a node. :param _id: node id to check bridges for + :return: True if there are existing bridges, False otherwise """ output = self.run(f"{IP_BIN} -j link show type bridge") bridges = json.loads(output) @@ -286,7 +276,7 @@ class LinuxNetClient: return True return False - def disable_mac_learning(self, name): + def disable_mac_learning(self, name: str) -> None: """ Disable mac learning for a Linux bridge. @@ -301,7 +291,7 @@ class OvsNetClient(LinuxNetClient): Client for creating OVS bridges and ip interfaces for nodes. """ - def create_bridge(self, name): + def create_bridge(self, name: str) -> None: """ Create a OVS bridge and bring it up. @@ -314,7 +304,7 @@ class OvsNetClient(LinuxNetClient): self.run(f"{OVS_BIN} set bridge {name} other_config:stp-forward-delay=4") self.device_up(name) - def delete_bridge(self, name): + def delete_bridge(self, name: str) -> None: """ Bring down and delete a OVS bridge. @@ -324,7 +314,7 @@ class OvsNetClient(LinuxNetClient): self.device_down(name) self.run(f"{OVS_BIN} del-br {name}") - def create_interface(self, bridge_name, interface_name): + def create_interface(self, bridge_name: str, interface_name: str) -> None: """ Create an interface associated with a network bridge. @@ -335,7 +325,7 @@ class OvsNetClient(LinuxNetClient): self.run(f"{OVS_BIN} add-port {bridge_name} {interface_name}") self.device_up(interface_name) - def delete_interface(self, bridge_name, interface_name): + def delete_interface(self, bridge_name: str, interface_name: str) -> None: """ Delete an interface associated with a OVS bridge. @@ -345,11 +335,12 @@ class OvsNetClient(LinuxNetClient): """ self.run(f"{OVS_BIN} del-port {bridge_name} {interface_name}") - def existing_bridges(self, _id): + def existing_bridges(self, _id: int) -> bool: """ Checks if there are any existing OVS bridges for a node. :param _id: node id to check bridges for + :return: True if there are existing bridges, False otherwise """ output = self.run(f"{OVS_BIN} list-br") if output: @@ -359,7 +350,7 @@ class OvsNetClient(LinuxNetClient): return True return False - def disable_mac_learning(self, name): + def disable_mac_learning(self, name: str) -> None: """ Disable mac learning for a OVS bridge. @@ -367,3 +358,17 @@ class OvsNetClient(LinuxNetClient): :return: nothing """ self.run(f"{OVS_BIN} set bridge {name} other_config:mac-aging-time=0") + + +def get_net_client(use_ovs: bool, run: Callable) -> LinuxNetClient: + """ + Retrieve desired net client for running network commands. + + :param bool use_ovs: True for OVS bridges, False for Linux bridges + :param func run: function used to run net client commands + :return: net client class + """ + if use_ovs: + return OvsNetClient(run) + else: + return LinuxNetClient(run) diff --git a/daemon/core/nodes/network.py b/daemon/core/nodes/network.py index c9275c48..2241501c 100644 --- a/daemon/core/nodes/network.py +++ b/daemon/core/nodes/network.py @@ -5,18 +5,26 @@ Defines network nodes used within core. import logging import threading import time +from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type import netaddr from core import utils from core.constants import EBTABLES_BIN, TC_BIN -from core.emulator.data import LinkData +from core.emulator.data import LinkData, NodeData from core.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs from core.errors import CoreCommandError, CoreError from core.nodes.base import CoreNetworkBase -from core.nodes.interface import GreTap, Veth +from core.nodes.interface import CoreInterface, GreTap, Veth from core.nodes.netclient import get_net_client +if TYPE_CHECKING: + from core.emulator.distributed import DistributedServer + from core.emulator.session import Session + from core.location.mobility import WirelessModel + + WirelessModelType = Type[WirelessModel] + ebtables_lock = threading.Lock() @@ -32,7 +40,7 @@ class EbtablesQueue: # ebtables atomic_file = "/tmp/pycore.ebtables.atomic" - def __init__(self): + def __init__(self) -> None: """ Initialize the helper class, but don't start the update thread until a WLAN is instantiated. @@ -49,7 +57,7 @@ class EbtablesQueue: # using this queue self.last_update_time = {} - def startupdateloop(self, wlan): + def startupdateloop(self, wlan: "CoreNetwork") -> None: """ Kick off the update loop; only needs to be invoked once. @@ -66,7 +74,7 @@ class EbtablesQueue: self.updatethread.daemon = True self.updatethread.start() - def stopupdateloop(self, wlan): + def stopupdateloop(self, wlan: "CoreNetwork") -> None: """ Kill the update loop thread if there are no more WLANs using it. @@ -88,17 +96,17 @@ class EbtablesQueue: self.updatethread.join() self.updatethread = None - def ebatomiccmd(self, cmd): + def ebatomiccmd(self, cmd: str) -> str: """ Helper for building ebtables atomic file command list. :param str cmd: ebtable command :return: ebtable atomic command - :rtype: list[str] + :rtype: str """ return f"{EBTABLES_BIN} --atomic-file {self.atomic_file} {cmd}" - def lastupdate(self, wlan): + def lastupdate(self, wlan: "CoreNetwork") -> float: """ Return the time elapsed since this WLAN was last updated. @@ -114,7 +122,7 @@ class EbtablesQueue: return elapsed - def updated(self, wlan): + def updated(self, wlan: "CoreNetwork") -> None: """ Keep track of when this WLAN was last updated. @@ -124,7 +132,7 @@ class EbtablesQueue: self.last_update_time[wlan] = time.monotonic() self.updates.remove(wlan) - def updateloop(self): + def updateloop(self) -> None: """ Thread target that looks for WLANs needing update, and rate limits the amount of ebtables activity. Only one userspace program @@ -153,7 +161,7 @@ class EbtablesQueue: time.sleep(self.rate) - def ebcommit(self, wlan): + def ebcommit(self, wlan: "CoreNetwork") -> None: """ Perform ebtables atomic commit using commands built in the self.cmds list. @@ -178,7 +186,7 @@ class EbtablesQueue: except CoreCommandError: logging.exception("error removing atomic file: %s", self.atomic_file) - def ebchange(self, wlan): + def ebchange(self, wlan: "CoreNetwork") -> None: """ Flag a change to the given WLAN's _linked dict, so the ebtables chain will be rebuilt at the next interval. @@ -189,7 +197,7 @@ class EbtablesQueue: if wlan not in self.updates: self.updates.append(wlan) - def buildcmds(self, wlan): + def buildcmds(self, wlan: "CoreNetwork") -> None: """ Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN. @@ -231,7 +239,7 @@ class EbtablesQueue: ebq = EbtablesQueue() -def ebtablescmds(call, cmds): +def ebtablescmds(call: Callable, cmds: List[str]) -> None: """ Run ebtable commands. @@ -252,8 +260,14 @@ class CoreNetwork(CoreNetworkBase): policy = "DROP" def __init__( - self, session, _id=None, name=None, start=True, server=None, policy=None - ): + self, + session: "Session", + _id: int = None, + name: str = None, + start: bool = True, + server: "DistributedServer" = None, + policy: str = None, + ) -> None: """ Creates a LxBrNet instance. @@ -279,7 +293,14 @@ class CoreNetwork(CoreNetworkBase): self.startup() ebq.startupdateloop(self) - def host_cmd(self, args, env=None, cwd=None, wait=True, shell=False): + def host_cmd( + self, + args: str, + env: Dict[str, str] = None, + cwd: str = None, + wait: bool = True, + shell: bool = False, + ) -> str: """ Runs a command that is used to configure and setup the network on the host system and all configured distributed servers. @@ -298,7 +319,7 @@ class CoreNetwork(CoreNetworkBase): self.session.distributed.execute(lambda x: x.remote_cmd(args, env, cwd, wait)) return output - def startup(self): + def startup(self) -> None: """ Linux bridge starup logic. @@ -309,7 +330,7 @@ class CoreNetwork(CoreNetworkBase): self.has_ebtables_chain = False self.up = True - def shutdown(self): + def shutdown(self) -> None: """ Linux bridge shutdown logic. @@ -340,18 +361,18 @@ class CoreNetwork(CoreNetworkBase): del self.session self.up = False - def attach(self, netif): + def attach(self, netif: CoreInterface) -> None: """ Attach a network interface. - :param core.nodes.interface.Veth netif: network interface to attach + :param core.nodes.interface.CoreInterface netif: network interface to attach :return: nothing """ if self.up: netif.net_client.create_interface(self.brname, netif.localname) super().attach(netif) - def detach(self, netif): + def detach(self, netif: CoreInterface) -> None: """ Detach a network interface. @@ -362,7 +383,7 @@ class CoreNetwork(CoreNetworkBase): netif.net_client.delete_interface(self.brname, netif.localname) super().detach(netif) - def linked(self, netif1, netif2): + def linked(self, netif1: CoreInterface, netif2: CoreInterface) -> bool: """ Determine if the provided network interfaces are linked. @@ -391,9 +412,9 @@ class CoreNetwork(CoreNetworkBase): return linked - def unlink(self, netif1, netif2): + def unlink(self, netif1: CoreInterface, netif2: CoreInterface) -> None: """ - Unlink two PyCoreNetIfs, resulting in adding or removing ebtables + Unlink two interfaces, resulting in adding or removing ebtables filtering rules. :param core.nodes.interface.CoreInterface netif1: interface one @@ -407,9 +428,9 @@ class CoreNetwork(CoreNetworkBase): ebq.ebchange(self) - def link(self, netif1, netif2): + def link(self, netif1: CoreInterface, netif2: CoreInterface) -> None: """ - Link two PyCoreNetIfs together, resulting in adding or removing + Link two interfaces together, resulting in adding or removing ebtables filtering rules. :param core.nodes.interface.CoreInterface netif1: interface one @@ -425,19 +446,19 @@ class CoreNetwork(CoreNetworkBase): def linkconfig( self, - netif, - bw=None, - delay=None, - loss=None, - duplicate=None, - jitter=None, - netif2=None, - devname=None, - ): + netif: CoreInterface, + bw: float = None, + delay: float = None, + loss: float = None, + duplicate: float = None, + jitter: float = None, + netif2: float = None, + devname: str = None, + ) -> None: """ Configure link parameters by applying tc queuing disciplines on the interface. - :param core.nodes.interface.Veth netif: interface one + :param core.nodes.interface.CoreInterface netif: interface one :param bw: bandwidth to set to :param delay: packet delay to set to :param loss: packet loss to set to @@ -520,14 +541,14 @@ class CoreNetwork(CoreNetworkBase): netif.host_cmd(cmd) netif.setparam("has_netem", True) - def linknet(self, net): + def linknet(self, net: CoreNetworkBase) -> CoreInterface: """ Link this bridge with another by creating a veth pair and installing each device into each bridge. :param core.nodes.base.CoreNetworkBase net: network to link with :return: created interface - :rtype: core.nodes.interface.Veth + :rtype: core.nodes.interface.CoreInterface """ sessionid = self.session.short_session_id() try: @@ -561,7 +582,7 @@ class CoreNetwork(CoreNetworkBase): netif.othernet = net return netif - def getlinknetif(self, net): + def getlinknetif(self, net: CoreNetworkBase) -> Optional[CoreInterface]: """ Return the interface of that links this net with another net (that were linked using linknet()). @@ -573,10 +594,9 @@ class CoreNetwork(CoreNetworkBase): for netif in self.netifs(): if hasattr(netif, "othernet") and netif.othernet == net: return netif - return None - def addrconfig(self, addrlist): + def addrconfig(self, addrlist: List[str]) -> None: """ Set addresses on the bridge. @@ -598,17 +618,17 @@ class GreTapBridge(CoreNetwork): def __init__( self, - session, - remoteip=None, - _id=None, - name=None, - policy="ACCEPT", - localip=None, - ttl=255, - key=None, - start=True, - server=None, - ): + session: "Session", + remoteip: str = None, + _id: int = None, + name: str = None, + policy: str = "ACCEPT", + localip: str = None, + ttl: int = 255, + key: int = None, + start: bool = True, + server: "DistributedServer" = None, + ) -> None: """ Create a GreTapBridge instance. @@ -647,7 +667,7 @@ class GreTapBridge(CoreNetwork): if start: self.startup() - def startup(self): + def startup(self) -> None: """ Creates a bridge and adds the gretap device to it. @@ -657,7 +677,7 @@ class GreTapBridge(CoreNetwork): if self.gretap: self.attach(self.gretap) - def shutdown(self): + def shutdown(self) -> None: """ Detach the gretap device and remove the bridge. @@ -669,7 +689,7 @@ class GreTapBridge(CoreNetwork): self.gretap = None super().shutdown() - def addrconfig(self, addrlist): + def addrconfig(self, addrlist: List[str]) -> None: """ Set the remote tunnel endpoint. This is a one-time method for creating the GreTap device, which requires the remoteip at startup. @@ -694,7 +714,7 @@ class GreTapBridge(CoreNetwork): ) self.attach(self.gretap) - def setkey(self, key): + def setkey(self, key: int) -> None: """ Set the GRE key used for the GreTap device. This needs to be set prior to instantiating the GreTap device (before addrconfig). @@ -722,17 +742,17 @@ class CtrlNet(CoreNetwork): def __init__( self, - session, - _id=None, - name=None, - prefix=None, - hostid=None, - start=True, - server=None, - assign_address=True, - updown_script=None, - serverintf=None, - ): + session: "Session", + _id: int = None, + name: str = None, + prefix: str = None, + hostid: int = None, + start: bool = True, + server: "DistributedServer" = None, + assign_address: bool = True, + updown_script: str = None, + serverintf: CoreInterface = None, + ) -> None: """ Creates a CtrlNet instance. @@ -756,7 +776,7 @@ class CtrlNet(CoreNetwork): self.serverintf = serverintf super().__init__(session, _id, name, start, server) - def add_addresses(self, index): + def add_addresses(self, index: int) -> None: """ Add addresses used for created control networks, @@ -777,7 +797,7 @@ class CtrlNet(CoreNetwork): net_client = get_net_client(use_ovs, server.remote_cmd) net_client.create_address(self.brname, current) - def startup(self): + def startup(self) -> None: """ Startup functionality for the control network. @@ -806,7 +826,7 @@ class CtrlNet(CoreNetwork): if self.serverintf: self.net_client.create_interface(self.brname, self.serverintf) - def shutdown(self): + def shutdown(self) -> None: """ Control network shutdown. @@ -835,7 +855,7 @@ class CtrlNet(CoreNetwork): super().shutdown() - def all_link_data(self, flags): + def all_link_data(self, flags: int) -> List[LinkData]: """ Do not include CtrlNet in link messages describing this session. @@ -853,11 +873,11 @@ class PtpNet(CoreNetwork): policy = "ACCEPT" - def attach(self, netif): + def attach(self, netif: CoreInterface) -> None: """ Attach a network interface, but limit attachment to two interfaces. - :param core.netns.vif.VEth netif: network interface + :param core.nodes.interface.CoreInterface netif: network interface :return: nothing """ if len(self._netif) >= 2: @@ -866,7 +886,14 @@ class PtpNet(CoreNetwork): ) super().attach(netif) - def data(self, message_type, lat=None, lon=None, alt=None): + def data( + self, + message_type: int, + lat: float = None, + lon: float = None, + alt: float = None, + source: str = None, + ) -> NodeData: """ Do not generate a Node Message for point-to-point links. They are built using a link message instead. @@ -875,12 +902,13 @@ class PtpNet(CoreNetwork): :param float lat: latitude :param float lon: longitude :param float alt: altitude + :param str source: source of node data :return: node data object :rtype: core.emulator.data.NodeData """ return None - def all_link_data(self, flags): + def all_link_data(self, flags: int) -> List[LinkData]: """ Build CORE API TLVs for a point-to-point link. One Link message describes this network. @@ -997,7 +1025,7 @@ class HubNode(CoreNetwork): policy = "ACCEPT" type = "hub" - def startup(self): + def startup(self) -> None: """ Startup for a hub node, that disables mac learning after normal startup. @@ -1018,8 +1046,14 @@ class WlanNode(CoreNetwork): type = "wlan" def __init__( - self, session, _id=None, name=None, start=True, server=None, policy=None - ): + self, + session: "Session", + _id: int = None, + name: str = None, + start: bool = True, + server: "DistributedServer" = None, + policy: str = None, + ) -> None: """ Create a WlanNode instance. @@ -1036,7 +1070,7 @@ class WlanNode(CoreNetwork): self.model = None self.mobility = None - def startup(self): + def startup(self) -> None: """ Startup for a wlan node, that disables mac learning after normal startup. @@ -1045,11 +1079,11 @@ class WlanNode(CoreNetwork): super().startup() self.net_client.disable_mac_learning(self.brname) - def attach(self, netif): + def attach(self, netif: CoreInterface) -> None: """ Attach a network interface. - :param core.nodes.interface.Veth netif: network interface + :param core.nodes.interface.CoreInterface netif: network interface :return: nothing """ super().attach(netif) @@ -1061,7 +1095,7 @@ class WlanNode(CoreNetwork): # invokes any netif.poshook netif.setposition(x, y, z) - def setmodel(self, model, config): + def setmodel(self, model: WirelessModelType, config: Dict[str, str]): """ Sets the mobility and wireless model. @@ -1082,12 +1116,12 @@ class WlanNode(CoreNetwork): self.mobility = model(session=self.session, _id=self.id) self.mobility.update_config(config) - def update_mobility(self, config): + def update_mobility(self, config: Dict[str, str]) -> None: if not self.mobility: raise ValueError(f"no mobility set to update for node({self.id})") self.mobility.update_config(config) - def updatemodel(self, config): + def updatemodel(self, config: Dict[str, str]) -> None: if not self.model: raise ValueError(f"no model set to update for node({self.id})") logging.debug( @@ -1099,7 +1133,7 @@ class WlanNode(CoreNetwork): x, y, z = netif.node.position.get() netif.poshook(netif, x, y, z) - def all_link_data(self, flags): + def all_link_data(self, flags: int) -> List[LinkData]: """ Retrieve all link data. diff --git a/daemon/core/nodes/physical.py b/daemon/core/nodes/physical.py index 1d470b98..a963721f 100644 --- a/daemon/core/nodes/physical.py +++ b/daemon/core/nodes/physical.py @@ -5,20 +5,31 @@ PhysicalNode class for including real systems in the emulated network. import logging import os import threading +from typing import IO, TYPE_CHECKING, List, Optional from core import utils from core.constants import MOUNT_BIN, UMOUNT_BIN +from core.emulator.distributed import DistributedServer from core.emulator.enumerations import NodeTypes from core.errors import CoreCommandError, CoreError -from core.nodes.base import CoreNodeBase -from core.nodes.interface import CoreInterface +from core.nodes.base import CoreNetworkBase, CoreNodeBase +from core.nodes.interface import CoreInterface, Veth from core.nodes.network import CoreNetwork, GreTap +if TYPE_CHECKING: + from core.emulator.session import Session + class PhysicalNode(CoreNodeBase): def __init__( - self, session, _id=None, name=None, nodedir=None, start=True, server=None - ): + self, + session, + _id: int = None, + name: str = None, + nodedir: str = None, + start: bool = True, + server: DistributedServer = None, + ) -> None: super().__init__(session, _id, name, start, server) if not self.server: raise CoreError("physical nodes must be assigned to a remote server") @@ -29,11 +40,11 @@ class PhysicalNode(CoreNodeBase): if start: self.startup() - def startup(self): + def startup(self) -> None: with self.lock: self.makenodedir() - def shutdown(self): + def shutdown(self) -> None: if not self.up: return @@ -47,7 +58,7 @@ class PhysicalNode(CoreNodeBase): self.rmnodedir() - def termcmdstring(self, sh="/bin/sh"): + def termcmdstring(self, sh: str = "/bin/sh") -> str: """ Create a terminal command string. @@ -56,7 +67,7 @@ class PhysicalNode(CoreNodeBase): """ return sh - def sethwaddr(self, ifindex, addr): + def sethwaddr(self, ifindex: int, addr: str) -> None: """ Set hardware address for an interface. @@ -71,7 +82,7 @@ class PhysicalNode(CoreNodeBase): if self.up: self.net_client.device_mac(interface.name, addr) - def addaddr(self, ifindex, addr): + def addaddr(self, ifindex: int, addr: str) -> None: """ Add an address to an interface. @@ -85,9 +96,13 @@ class PhysicalNode(CoreNodeBase): self.net_client.create_address(interface.name, addr) interface.addaddr(addr) - def deladdr(self, ifindex, addr): + def deladdr(self, ifindex: int, addr: str) -> None: """ Delete an address from an interface. + + :param int ifindex: index of interface to delete + :param str addr: address to delete + :return: nothing """ interface = self._netif[ifindex] @@ -99,7 +114,9 @@ class PhysicalNode(CoreNodeBase): if self.up: self.net_client.delete_address(interface.name, str(addr)) - def adoptnetif(self, netif, ifindex, hwaddr, addrlist): + def adoptnetif( + self, netif: CoreInterface, ifindex: int, hwaddr: str, addrlist: List[str] + ) -> None: """ When a link message is received linking this node to another part of the emulation, no new interface is created; instead, adopt the @@ -127,18 +144,17 @@ class PhysicalNode(CoreNodeBase): def linkconfig( self, - netif, - bw=None, - delay=None, - loss=None, - duplicate=None, - jitter=None, - netif2=None, - ): + netif: CoreInterface, + bw: float = None, + delay: float = None, + loss: float = None, + duplicate: float = None, + jitter: float = None, + netif2: CoreInterface = None, + ) -> None: """ - Apply tc queing disciplines using LxBrNet.linkconfig() + Apply tc queing disciplines using linkconfig. """ - # borrow the tc qdisc commands from LxBrNet.linkconfig() linux_bridge = CoreNetwork(session=self.session, start=False) linux_bridge.up = True linux_bridge.linkconfig( @@ -152,7 +168,7 @@ class PhysicalNode(CoreNodeBase): ) del linux_bridge - def newifindex(self): + def newifindex(self) -> int: with self.lock: while self.ifindex in self._netif: self.ifindex += 1 @@ -160,7 +176,14 @@ class PhysicalNode(CoreNodeBase): self.ifindex += 1 return ifindex - def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): + def newnetif( + self, + net: Veth = None, + addrlist: List[str] = None, + hwaddr: str = None, + ifindex: int = None, + ifname: str = None, + ) -> int: logging.info("creating interface") if not addrlist: addrlist = [] @@ -186,7 +209,7 @@ class PhysicalNode(CoreNodeBase): self.adoptnetif(netif, ifindex, hwaddr, addrlist) return ifindex - def privatedir(self, path): + def privatedir(self, path: str) -> None: if path[0] != "/": raise ValueError(f"path not fully qualified: {path}") hostpath = os.path.join( @@ -195,21 +218,21 @@ class PhysicalNode(CoreNodeBase): os.mkdir(hostpath) self.mount(hostpath, path) - def mount(self, source, target): + def mount(self, source: str, target: str) -> None: source = os.path.abspath(source) logging.info("mounting %s at %s", source, target) os.makedirs(target) self.host_cmd(f"{MOUNT_BIN} --bind {source} {target}", cwd=self.nodedir) self._mounts.append((source, target)) - def umount(self, target): + def umount(self, target: str) -> None: logging.info("unmounting '%s'", target) try: self.host_cmd(f"{UMOUNT_BIN} -l {target}", cwd=self.nodedir) except CoreCommandError: logging.exception("unmounting failed for %s", target) - def opennodefile(self, filename, mode="w"): + def opennodefile(self, filename: str, mode: str = "w") -> IO: dirname, basename = os.path.split(filename) if not basename: raise ValueError("no basename for filename: " + filename) @@ -225,13 +248,13 @@ class PhysicalNode(CoreNodeBase): hostfilename = os.path.join(dirname, basename) return open(hostfilename, mode) - def nodefile(self, filename, contents, mode=0o644): + def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None: with self.opennodefile(filename, "w") as node_file: node_file.write(contents) os.chmod(node_file.name, mode) logging.info("created nodefile: '%s'; mode: 0%o", node_file.name, mode) - def cmd(self, args, wait=True): + def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str: return self.host_cmd(args, wait=wait) @@ -244,7 +267,15 @@ class Rj45Node(CoreNodeBase, CoreInterface): apitype = NodeTypes.RJ45.value type = "rj45" - def __init__(self, session, _id=None, name=None, mtu=1500, start=True, server=None): + def __init__( + self, + session: "Session", + _id: int = None, + name: str = None, + mtu: int = 1500, + start: bool = True, + server: DistributedServer = None, + ) -> None: """ Create an RJ45Node instance. @@ -270,7 +301,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): if start: self.startup() - def startup(self): + def startup(self) -> None: """ Set the interface in the up state. @@ -282,7 +313,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): self.net_client.device_up(self.localname) self.up = True - def shutdown(self): + def shutdown(self) -> None: """ Bring the interface down. Remove any addresses and queuing disciplines. @@ -304,18 +335,18 @@ class Rj45Node(CoreNodeBase, CoreInterface): # TODO: issue in that both classes inherited from provide the same method with # different signatures - def attachnet(self, net): + def attachnet(self, net: CoreNetworkBase) -> None: """ Attach a network. - :param core.coreobj.PyCoreNet net: network to attach + :param core.nodes.base.CoreNetworkBase net: network to attach :return: nothing """ CoreInterface.attachnet(self, net) # TODO: issue in that both classes inherited from provide the same method with # different signatures - def detachnet(self): + def detachnet(self) -> None: """ Detach a network. @@ -323,7 +354,14 @@ class Rj45Node(CoreNodeBase, CoreInterface): """ CoreInterface.detachnet(self) - def newnetif(self, net=None, addrlist=None, hwaddr=None, ifindex=None, ifname=None): + def newnetif( + self, + net: CoreNetworkBase = None, + addrlist: List[str] = None, + hwaddr: str = None, + ifindex: int = None, + ifname: str = None, + ) -> int: """ This is called when linking with another node. Since this node represents an interface, we do not create another object here, @@ -359,7 +397,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): return ifindex - def delnetif(self, ifindex): + def delnetif(self, ifindex: int) -> None: """ Delete a network interface. @@ -376,7 +414,9 @@ class Rj45Node(CoreNodeBase, CoreInterface): else: raise ValueError(f"ifindex {ifindex} does not exist") - def netif(self, ifindex, net=None): + def netif( + self, ifindex: int, net: CoreNetworkBase = None + ) -> Optional[CoreInterface]: """ This object is considered the network interface, so we only return self here. This keeps the RJ45Node compatible with @@ -398,20 +438,20 @@ class Rj45Node(CoreNodeBase, CoreInterface): return None - def getifindex(self, netif): + def getifindex(self, netif: CoreInterface) -> Optional[int]: """ Retrieve network interface index. - :param core.nodes.interface.CoreInterface netif: network interface to retrieve index for + :param core.nodes.interface.CoreInterface netif: network interface to retrieve + index for :return: interface index, None otherwise :rtype: int """ if netif != self: return None - return self.ifindex - def addaddr(self, addr): + def addaddr(self, addr: str) -> None: """ Add address to to network interface. @@ -424,7 +464,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): self.net_client.create_address(self.name, addr) CoreInterface.addaddr(self, addr) - def deladdr(self, addr): + def deladdr(self, addr: str) -> None: """ Delete address from network interface. @@ -434,10 +474,9 @@ class Rj45Node(CoreNodeBase, CoreInterface): """ if self.up: self.net_client.delete_address(self.name, str(addr)) - CoreInterface.deladdr(self, addr) - def savestate(self): + def savestate(self) -> None: """ Save the addresses and other interface state before using the interface for emulation purposes. TODO: save/restore the PROMISC flag @@ -464,7 +503,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): continue self.old_addrs.append((items[1], None)) - def restorestate(self): + def restorestate(self) -> None: """ Restore the addresses and other interface state after using it. @@ -482,7 +521,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): if self.old_up: self.net_client.device_up(self.localname) - def setposition(self, x=None, y=None, z=None): + def setposition(self, x: float = None, y: float = None, z: float = None) -> bool: """ Uses setposition from both parent classes. @@ -496,7 +535,7 @@ class Rj45Node(CoreNodeBase, CoreInterface): CoreInterface.setposition(self, x, y, z) return result - def termcmdstring(self, sh): + def termcmdstring(self, sh: str) -> str: """ Create a terminal command string.