added type hinting for core.nodes

This commit is contained in:
Blake Harnden 2020-01-13 14:08:49 -08:00
parent 4e71759ac9
commit c0fcc91d10
10 changed files with 534 additions and 360 deletions

View file

@ -5,7 +5,7 @@ gRpc client for interfacing with CORE, when gRPC mode is enabled.
import logging import logging
import threading import threading
from contextlib import contextmanager from contextlib import contextmanager
from typing import Any, Callable, Dict, List from typing import Any, Callable, Dict, Generator, List
import grpc import grpc
import netaddr import netaddr
@ -1144,7 +1144,7 @@ class CoreGrpcClient:
self.channel = None self.channel = None
@contextmanager @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. Makes a context manager based connection to the server, will close after context ends.

View file

@ -1,6 +1,6 @@
import logging import logging
from queue import Empty, Queue from queue import Empty, Queue
from typing import List from typing import Iterable
from core.api.grpc import core_pb2 from core.api.grpc import core_pb2
from core.api.grpc.grpcutils import convert_value from core.api.grpc.grpcutils import convert_value
@ -181,7 +181,9 @@ class EventStreamer:
Processes session events to generate grpc events. 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. Create a EventStreamer instance.

View file

@ -6,6 +6,7 @@ import logging
import os import os
import shutil import shutil
import threading import threading
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple
import netaddr import netaddr
@ -15,8 +16,12 @@ from core.emulator.data import LinkData, NodeData
from core.emulator.enumerations import LinkTypes, NodeTypes from core.emulator.enumerations import LinkTypes, NodeTypes
from core.errors import CoreCommandError from core.errors import CoreCommandError
from core.nodes import client from core.nodes import client
from core.nodes.interface import TunTap, Veth from core.nodes.interface import CoreInterface, TunTap, Veth
from core.nodes.netclient import get_net_client 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 _DEFAULT_MTU = 1500
@ -29,9 +34,16 @@ class NodeBase:
apitype = None apitype = None
# TODO: appears start has no usage, verify and remove # 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 core.emulator.session.Session session: CORE session object
:param int _id: id :param int _id: id
@ -63,7 +75,7 @@ class NodeBase:
use_ovs = session.options.get_config("ovs") == "True" use_ovs = session.options.get_config("ovs") == "True"
self.net_client = get_net_client(use_ovs, self.host_cmd) 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. Each object implements its own startup method.
@ -71,7 +83,7 @@ class NodeBase:
""" """
raise NotImplementedError raise NotImplementedError
def shutdown(self): def shutdown(self) -> None:
""" """
Each object implements its own shutdown method. Each object implements its own shutdown method.
@ -79,7 +91,14 @@ class NodeBase:
""" """
raise NotImplementedError 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. Runs a command on the host system or distributed server.
@ -97,7 +116,7 @@ class NodeBase:
else: else:
return self.server.remote_cmd(args, env, cwd, wait) 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. 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) 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. Return an (x,y,z) tuple representing this object's position.
@ -118,7 +137,7 @@ class NodeBase:
""" """
return self.position.get() return self.position.get()
def ifname(self, ifindex): def ifname(self, ifindex: int) -> str:
""" """
Retrieve interface name for index. Retrieve interface name for index.
@ -128,7 +147,7 @@ class NodeBase:
""" """
return self._netif[ifindex].name return self._netif[ifindex].name
def netifs(self, sort=False): def netifs(self, sort: bool = False) -> List[CoreInterface]:
""" """
Retrieve network interfaces, sorted if desired. Retrieve network interfaces, sorted if desired.
@ -141,7 +160,7 @@ class NodeBase:
else: else:
return list(self._netif.values()) return list(self._netif.values())
def numnetif(self): def numnetif(self) -> int:
""" """
Return the attached interface count. Return the attached interface count.
@ -150,7 +169,7 @@ class NodeBase:
""" """
return len(self._netif) return len(self._netif)
def getifindex(self, netif): def getifindex(self, netif: CoreInterface) -> int:
""" """
Retrieve index for an interface. Retrieve index for an interface.
@ -163,7 +182,7 @@ class NodeBase:
return ifindex return ifindex
return -1 return -1
def newifindex(self): def newifindex(self) -> int:
""" """
Create a new interface index. Create a new interface index.
@ -176,7 +195,14 @@ class NodeBase:
self.ifindex += 1 self.ifindex += 1
return ifindex 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. Build a data object for this node.
@ -223,7 +249,7 @@ class NodeBase:
return node_data 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 Build CORE Link data for this object. There is no default
method for PyCoreObjs as PyCoreNodes do not implement this but method for PyCoreObjs as PyCoreNodes do not implement this but
@ -241,7 +267,14 @@ class CoreNodeBase(NodeBase):
Base class for CORE nodes. 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. Create a CoreNodeBase instance.
@ -257,7 +290,7 @@ class CoreNodeBase(NodeBase):
self.nodedir = None self.nodedir = None
self.tmpnodedir = False self.tmpnodedir = False
def makenodedir(self): def makenodedir(self) -> None:
""" """
Create the node directory. Create the node directory.
@ -270,7 +303,7 @@ class CoreNodeBase(NodeBase):
else: else:
self.tmpnodedir = False self.tmpnodedir = False
def rmnodedir(self): def rmnodedir(self) -> None:
""" """
Remove the node directory, unless preserve directory has been set. Remove the node directory, unless preserve directory has been set.
@ -283,7 +316,7 @@ class CoreNodeBase(NodeBase):
if self.tmpnodedir: if self.tmpnodedir:
self.host_cmd(f"rm -rf {self.nodedir}") 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. Add network interface to node and set the network interface index if successful.
@ -296,7 +329,7 @@ class CoreNodeBase(NodeBase):
self._netif[ifindex] = netif self._netif[ifindex] = netif
netif.netindex = ifindex netif.netindex = ifindex
def delnetif(self, ifindex): def delnetif(self, ifindex: int) -> None:
""" """
Delete a network interface Delete a network interface
@ -309,7 +342,7 @@ class CoreNodeBase(NodeBase):
netif.shutdown() netif.shutdown()
del netif del netif
def netif(self, ifindex): def netif(self, ifindex: int) -> Optional[CoreInterface]:
""" """
Retrieve network interface. Retrieve network interface.
@ -322,7 +355,7 @@ class CoreNodeBase(NodeBase):
else: else:
return None return None
def attachnet(self, ifindex, net): def attachnet(self, ifindex: int, net: "CoreNetworkBase") -> None:
""" """
Attach a network. Attach a network.
@ -334,7 +367,7 @@ class CoreNodeBase(NodeBase):
raise ValueError(f"ifindex {ifindex} does not exist") raise ValueError(f"ifindex {ifindex} does not exist")
self._netif[ifindex].attachnet(net) self._netif[ifindex].attachnet(net)
def detachnet(self, ifindex): def detachnet(self, ifindex: int) -> None:
""" """
Detach network interface. Detach network interface.
@ -345,7 +378,7 @@ class CoreNodeBase(NodeBase):
raise ValueError(f"ifindex {ifindex} does not exist") raise ValueError(f"ifindex {ifindex} does not exist")
self._netif[ifindex].detachnet() 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. Set position.
@ -359,7 +392,9 @@ class CoreNodeBase(NodeBase):
for netif in self.netifs(sort=True): for netif in self.netifs(sort=True):
netif.setposition(x, y, z) 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 Given another node or net object, return common networks between
this node and that object. A list of tuples is returned, with each tuple 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(): for netif2 in obj.netifs():
if netif1.net == netif2.net: if netif1.net == netif2.net:
common.append((netif1.net, netif1, netif2)) common.append((netif1.net, netif1, netif2))
return common 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. Runs a command within a node container.
@ -393,7 +427,7 @@ class CoreNodeBase(NodeBase):
""" """
raise NotImplementedError raise NotImplementedError
def termcmdstring(self, sh): def termcmdstring(self, sh: str) -> str:
""" """
Create a terminal command string. Create a terminal command string.
@ -413,14 +447,14 @@ class CoreNode(CoreNodeBase):
def __init__( def __init__(
self, self,
session, session: "Session",
_id=None, _id: int = None,
name=None, name: str = None,
nodedir=None, nodedir: str = None,
bootsh="boot.sh", bootsh: str = "boot.sh",
start=True, start: bool = True,
server=None, server: "DistributedServer" = None,
): ) -> None:
""" """
Create a CoreNode instance. Create a CoreNode instance.
@ -451,7 +485,7 @@ class CoreNode(CoreNodeBase):
if start: if start:
self.startup() 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 Create node network client for running network commands within the nodes
container. container.
@ -461,7 +495,7 @@ class CoreNode(CoreNodeBase):
""" """
return get_net_client(use_ovs, self.cmd) return get_net_client(use_ovs, self.cmd)
def alive(self): def alive(self) -> bool:
""" """
Check if the node is alive. Check if the node is alive.
@ -475,7 +509,7 @@ class CoreNode(CoreNodeBase):
return True return True
def startup(self): def startup(self) -> None:
""" """
Start a new namespace node by invoking the vnoded process that Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set 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/run")
self.privatedir("/var/log") self.privatedir("/var/log")
def shutdown(self): def shutdown(self) -> None:
""" """
Shutdown logic for simple lxc nodes. Shutdown logic for simple lxc nodes.
@ -562,7 +596,7 @@ class CoreNode(CoreNodeBase):
finally: finally:
self.rmnodedir() 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 Runs a command that is used to configure and setup the network within a
node. node.
@ -580,7 +614,7 @@ class CoreNode(CoreNodeBase):
args = self.client.create_cmd(args) args = self.client.create_cmd(args)
return self.server.remote_cmd(args, wait=wait) 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. Create a terminal command string.
@ -593,7 +627,7 @@ class CoreNode(CoreNodeBase):
else: else:
return f"ssh -X -f {self.server.host} xterm -e {terminal}" 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. Create a private directory.
@ -608,7 +642,7 @@ class CoreNode(CoreNodeBase):
self.host_cmd(f"mkdir -p {hostpath}") self.host_cmd(f"mkdir -p {hostpath}")
self.mount(hostpath, path) self.mount(hostpath, path)
def mount(self, source, target): def mount(self, source: str, target: str) -> None:
""" """
Create and mount a directory. Create and mount a directory.
@ -623,7 +657,7 @@ class CoreNode(CoreNodeBase):
self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}") self.cmd(f"{MOUNT_BIN} -n --bind {source} {target}")
self._mounts.append((source, target)) self._mounts.append((source, target))
def newifindex(self): def newifindex(self) -> int:
""" """
Retrieve a new interface index. Retrieve a new interface index.
@ -633,7 +667,7 @@ class CoreNode(CoreNodeBase):
with self.lock: with self.lock:
return super().newifindex() return super().newifindex()
def newveth(self, ifindex=None, ifname=None): def newveth(self, ifindex: int = None, ifname: str = None) -> int:
""" """
Create a new interface. Create a new interface.
@ -690,7 +724,7 @@ class CoreNode(CoreNodeBase):
return ifindex return ifindex
def newtuntap(self, ifindex=None, ifname=None): def newtuntap(self, ifindex: int = None, ifname: str = None) -> int:
""" """
Create a new tunnel tap. Create a new tunnel tap.
@ -720,7 +754,7 @@ class CoreNode(CoreNodeBase):
return ifindex return ifindex
def sethwaddr(self, ifindex, addr): def sethwaddr(self, ifindex: int, addr: str) -> None:
""" """
Set hardware addres for an interface. Set hardware addres for an interface.
@ -735,7 +769,7 @@ class CoreNode(CoreNodeBase):
if self.up: if self.up:
self.node_net_client.device_mac(interface.name, addr) 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. Add interface address.
@ -753,7 +787,7 @@ class CoreNode(CoreNodeBase):
broadcast = "+" broadcast = "+"
self.node_net_client.create_address(interface.name, addr, 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. Delete address from an interface.
@ -772,7 +806,7 @@ class CoreNode(CoreNodeBase):
if self.up: if self.up:
self.node_net_client.delete_address(interface.name, addr) self.node_net_client.delete_address(interface.name, addr)
def ifup(self, ifindex): def ifup(self, ifindex: int) -> None:
""" """
Bring an interface up. Bring an interface up.
@ -783,7 +817,14 @@ class CoreNode(CoreNodeBase):
interface_name = self.ifname(ifindex) interface_name = self.ifname(ifindex)
self.node_net_client.device_up(interface_name) 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. Create a new network interface.
@ -827,7 +868,7 @@ class CoreNode(CoreNodeBase):
self.ifup(ifindex) self.ifup(ifindex)
return ifindex return ifindex
def addfile(self, srcname, filename): def addfile(self, srcname: str, filename: str) -> None:
""" """
Add a file. Add a file.
@ -846,7 +887,7 @@ class CoreNode(CoreNodeBase):
self.host_cmd(f"mkdir -p {directory}") self.host_cmd(f"mkdir -p {directory}")
self.server.remote_put(srcname, filename) 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. 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) dirname = os.path.join(self.nodedir, dirname)
return os.path.join(dirname, basename) 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. 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 "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. Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified. Change file mode if specified.
@ -917,7 +958,14 @@ class CoreNetworkBase(NodeBase):
linktype = LinkTypes.WIRED.value linktype = LinkTypes.WIRED.value
is_emane = False 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. Create a CoreNetworkBase instance.
@ -932,7 +980,7 @@ class CoreNetworkBase(NodeBase):
self._linked = {} self._linked = {}
self._linked_lock = threading.Lock() self._linked_lock = threading.Lock()
def startup(self): def startup(self) -> None:
""" """
Each object implements its own startup method. Each object implements its own startup method.
@ -940,7 +988,7 @@ class CoreNetworkBase(NodeBase):
""" """
raise NotImplementedError raise NotImplementedError
def shutdown(self): def shutdown(self) -> None:
""" """
Each object implements its own shutdown method. Each object implements its own shutdown method.
@ -948,7 +996,7 @@ class CoreNetworkBase(NodeBase):
""" """
raise NotImplementedError raise NotImplementedError
def linknet(self, net): def linknet(self, net: "CoreNetworkBase") -> CoreInterface:
""" """
Link network to another. Link network to another.
@ -958,7 +1006,7 @@ class CoreNetworkBase(NodeBase):
""" """
pass pass
def getlinknetif(self, net): def getlinknetif(self, net: "CoreNetworkBase") -> CoreInterface:
""" """
Return the interface of that links this net with another net. Return the interface of that links this net with another net.
@ -971,7 +1019,7 @@ class CoreNetworkBase(NodeBase):
return netif return netif
return None return None
def attach(self, netif): def attach(self, netif: CoreInterface) -> None:
""" """
Attach network interface. Attach network interface.
@ -984,7 +1032,7 @@ class CoreNetworkBase(NodeBase):
with self._linked_lock: with self._linked_lock:
self._linked[netif] = {} self._linked[netif] = {}
def detach(self, netif): def detach(self, netif: CoreInterface) -> None:
""" """
Detach network interface. Detach network interface.
@ -996,7 +1044,7 @@ class CoreNetworkBase(NodeBase):
with self._linked_lock: with self._linked_lock:
del self._linked[netif] 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 Build link data objects for this network. Each link object describes a link
between this network and a node. between this network and a node.
@ -1004,7 +1052,6 @@ class CoreNetworkBase(NodeBase):
:param int flags: message type :param int flags: message type
:return: list of link data :return: list of link data
:rtype: list[core.data.LinkData] :rtype: list[core.data.LinkData]
""" """
all_links = [] all_links = []
@ -1095,7 +1142,7 @@ class Position:
Helper class for Cartesian coordinate 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. Creates a Position instance.
@ -1108,7 +1155,7 @@ class Position:
self.y = y self.y = y
self.z = z 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. Returns True if the position has actually changed.
@ -1125,7 +1172,7 @@ class Position:
self.z = z self.z = z
return True return True
def get(self): def get(self) -> Tuple[float, float, float]:
""" """
Retrieve x,y,z position. Retrieve x,y,z position.

View file

@ -13,7 +13,7 @@ class VnodeClient:
Provides client functionality for interacting with a virtual node. 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. Create a VnodeClient instance.
@ -23,7 +23,7 @@ class VnodeClient:
self.name = name self.name = name
self.ctrlchnlname = ctrlchnlname self.ctrlchnlname = ctrlchnlname
def _verify_connection(self): def _verify_connection(self) -> None:
""" """
Checks that the vcmd client is properly connected. Checks that the vcmd client is properly connected.
@ -33,7 +33,7 @@ class VnodeClient:
if not self.connected(): if not self.connected():
raise IOError("vcmd not connected") raise IOError("vcmd not connected")
def connected(self): def connected(self) -> bool:
""" """
Check if node is connected or not. Check if node is connected or not.
@ -42,7 +42,7 @@ class VnodeClient:
""" """
return True return True
def close(self): def close(self) -> None:
""" """
Close the client connection. Close the client connection.
@ -50,10 +50,10 @@ class VnodeClient:
""" """
pass pass
def create_cmd(self, args): def create_cmd(self, args: str) -> str:
return f"{VCMD_BIN} -c {self.ctrlchnlname} -- {args}" 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. Run command and return exit status and combined stdout and stderr.

View file

@ -2,22 +2,27 @@ import json
import logging import logging
import os import os
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Callable, Dict
from core import utils from core import utils
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes from core.emulator.enumerations import NodeTypes
from core.errors import CoreCommandError from core.errors import CoreCommandError
from core.nodes.base import CoreNode 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: class DockerClient:
def __init__(self, name, image, run): def __init__(self, name: str, image: str, run: Callable) -> None:
self.name = name self.name = name
self.image = image self.image = image
self.run = run self.run = run
self.pid = None self.pid = None
def create_container(self): def create_container(self) -> str:
self.run( self.run(
f"docker run -td --init --net=none --hostname {self.name} --name {self.name} " 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" f"--sysctl net.ipv6.conf.all.disable_ipv6=0 {self.image} /bin/bash"
@ -25,7 +30,7 @@ class DockerClient:
self.pid = self.get_pid() self.pid = self.get_pid()
return self.pid return self.pid
def get_info(self): def get_info(self) -> Dict:
args = f"docker inspect {self.name}" args = f"docker inspect {self.name}"
output = self.run(args) output = self.run(args)
data = json.loads(output) data = json.loads(output)
@ -33,35 +38,35 @@ class DockerClient:
raise CoreCommandError(-1, args, f"docker({self.name}) not present") raise CoreCommandError(-1, args, f"docker({self.name}) not present")
return data[0] return data[0]
def is_alive(self): def is_alive(self) -> bool:
try: try:
data = self.get_info() data = self.get_info()
return data["State"]["Running"] return data["State"]["Running"]
except CoreCommandError: except CoreCommandError:
return False return False
def stop_container(self): def stop_container(self) -> None:
self.run(f"docker rm -f {self.name}") 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) logging.info("docker cmd output: %s", cmd)
return utils.cmd(f"docker exec {self.name} {cmd}", wait=wait, shell=shell) 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}" 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}" args = f"nsenter -t {self.pid} -u -i -p -n {cmd}"
return utils.cmd(args, wait=wait) return utils.cmd(args, wait=wait)
def get_pid(self): def get_pid(self) -> str:
args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}" args = f"docker inspect -f '{{{{.State.Pid}}}}' {self.name}"
output = self.run(args) output = self.run(args)
self.pid = output self.pid = output
logging.debug("node(%s) pid: %s", self.name, self.pid) logging.debug("node(%s) pid: %s", self.name, self.pid)
return output 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}" args = f"docker cp {source} {self.name}:{destination}"
return self.run(args) return self.run(args)
@ -71,15 +76,15 @@ class DockerNode(CoreNode):
def __init__( def __init__(
self, self,
session, session: "Session",
_id=None, _id: int = None,
name=None, name: str = None,
nodedir=None, nodedir: str = None,
bootsh="boot.sh", bootsh: str = "boot.sh",
start=True, start: bool = True,
server=None, server: DistributedServer = None,
image=None image: str = None
): ) -> None:
""" """
Create a DockerNode instance. Create a DockerNode instance.
@ -98,7 +103,7 @@ class DockerNode(CoreNode):
self.image = image self.image = image
super().__init__(session, _id, name, nodedir, bootsh, start, server) 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 Create node network client for running network commands within the nodes
container. container.
@ -108,7 +113,7 @@ class DockerNode(CoreNode):
""" """
return get_net_client(use_ovs, self.nsenter_cmd) return get_net_client(use_ovs, self.nsenter_cmd)
def alive(self): def alive(self) -> bool:
""" """
Check if the node is alive. Check if the node is alive.
@ -117,7 +122,7 @@ class DockerNode(CoreNode):
""" """
return self.client.is_alive() return self.client.is_alive()
def startup(self): def startup(self) -> None:
""" """
Start a new namespace node by invoking the vnoded process that Start a new namespace node by invoking the vnoded process that
allocates a new namespace. Bring up the loopback device and set 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.pid = self.client.create_container()
self.up = True self.up = True
def shutdown(self): def shutdown(self) -> None:
""" """
Shutdown logic. Shutdown logic.
@ -148,7 +153,7 @@ class DockerNode(CoreNode):
self.client.stop_container() self.client.stop_container()
self.up = False 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: if self.server is None:
args = self.client.create_ns_cmd(args) args = self.client.create_ns_cmd(args)
return utils.cmd(args, wait=wait, shell=shell) return utils.cmd(args, wait=wait, shell=shell)
@ -156,7 +161,7 @@ class DockerNode(CoreNode):
args = self.client.create_ns_cmd(args) args = self.client.create_ns_cmd(args)
return self.server.remote_cmd(args, wait=wait) 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. Create a terminal command string.
@ -165,7 +170,7 @@ class DockerNode(CoreNode):
""" """
return f"docker exec -it {self.name} bash" return f"docker exec -it {self.name} bash"
def privatedir(self, path): def privatedir(self, path: str) -> None:
""" """
Create a private directory. Create a private directory.
@ -176,7 +181,7 @@ class DockerNode(CoreNode):
args = f"mkdir -p {path}" args = f"mkdir -p {path}"
self.cmd(args) self.cmd(args)
def mount(self, source, target): def mount(self, source: str, target: str) -> None:
""" """
Create and mount a directory. Create and mount a directory.
@ -188,7 +193,7 @@ class DockerNode(CoreNode):
logging.debug("mounting source(%s) target(%s)", source, target) logging.debug("mounting source(%s) target(%s)", source, target)
raise Exception("not supported") 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. 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 "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. Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified. Change file mode if specified.

View file

@ -4,18 +4,31 @@ virtual ethernet classes that implement the interfaces available under Linux.
import logging import logging
import time import time
from typing import TYPE_CHECKING, Callable, Dict, List, Tuple
from core import utils from core import utils
from core.errors import CoreCommandError from core.errors import CoreCommandError
from core.nodes.netclient import get_net_client 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: class CoreInterface:
""" """
Base class for network interfaces. 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. Creates a CoreInterface instance.
@ -50,7 +63,14 @@ class CoreInterface:
use_ovs = session.options.get_config("ovs") == "True" use_ovs = session.options.get_config("ovs") == "True"
self.net_client = get_net_client(use_ovs, self.host_cmd) 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. Runs a command on the host system or distributed server.
@ -68,7 +88,7 @@ class CoreInterface:
else: else:
return self.server.remote_cmd(args, env, cwd, wait) return self.server.remote_cmd(args, env, cwd, wait)
def startup(self): def startup(self) -> None:
""" """
Startup method for the interface. Startup method for the interface.
@ -76,7 +96,7 @@ class CoreInterface:
""" """
pass pass
def shutdown(self): def shutdown(self) -> None:
""" """
Shutdown method for the interface. Shutdown method for the interface.
@ -84,7 +104,7 @@ class CoreInterface:
""" """
pass pass
def attachnet(self, net): def attachnet(self, net: "CoreNetworkBase") -> None:
""" """
Attach network. Attach network.
@ -98,7 +118,7 @@ class CoreInterface:
net.attach(self) net.attach(self)
self.net = net self.net = net
def detachnet(self): def detachnet(self) -> None:
""" """
Detach from a network. Detach from a network.
@ -107,7 +127,7 @@ class CoreInterface:
if self.net is not None: if self.net is not None:
self.net.detach(self) self.net.detach(self)
def addaddr(self, addr): def addaddr(self, addr: str) -> None:
""" """
Add address. Add address.
@ -117,7 +137,7 @@ class CoreInterface:
addr = utils.validate_ip(addr) addr = utils.validate_ip(addr)
self.addrlist.append(addr) self.addrlist.append(addr)
def deladdr(self, addr): def deladdr(self, addr: str) -> None:
""" """
Delete address. Delete address.
@ -126,7 +146,7 @@ class CoreInterface:
""" """
self.addrlist.remove(addr) self.addrlist.remove(addr)
def sethwaddr(self, addr): def sethwaddr(self, addr: str) -> None:
""" """
Set hardware address. Set hardware address.
@ -136,7 +156,7 @@ class CoreInterface:
addr = utils.validate_mac(addr) addr = utils.validate_mac(addr)
self.hwaddr = 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. Retrieve a parameter from the, or None if the parameter does not exist.
@ -145,7 +165,7 @@ class CoreInterface:
""" """
return self._params.get(key) return self._params.get(key)
def getparams(self): def getparams(self) -> List[Tuple[str, float]]:
""" """
Return (key, value) pairs for parameters. Return (key, value) pairs for parameters.
""" """
@ -154,7 +174,7 @@ class CoreInterface:
parameters.append((k, self._params[k])) parameters.append((k, self._params[k]))
return parameters 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. Set a parameter value, returns True if the parameter has changed.
@ -174,7 +194,7 @@ class CoreInterface:
self._params[key] = value self._params[key] = value
return True return True
def swapparams(self, name): def swapparams(self, name: str) -> None:
""" """
Swap out parameters dict for name. If name does not exist, Swap out parameters dict for name. If name does not exist,
intialize it. This is for supporting separate upstream/downstream intialize it. This is for supporting separate upstream/downstream
@ -189,7 +209,7 @@ class CoreInterface:
self._params = getattr(self, name) self._params = getattr(self, name)
setattr(self, name, tmp) setattr(self, name, tmp)
def setposition(self, x, y, z): def setposition(self, x: float, y: float, z: float) -> None:
""" """
Dispatch position hook handler. Dispatch position hook handler.
@ -200,7 +220,7 @@ class CoreInterface:
""" """
self.poshook(self, x, y, z) self.poshook(self, x, y, z)
def __lt__(self, other): def __lt__(self, other: "CoreInterface") -> bool:
""" """
Used for comparisons of this object. Used for comparisons of this object.
@ -217,8 +237,15 @@ class Veth(CoreInterface):
""" """
def __init__( 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. Creates a VEth instance.
@ -239,7 +266,7 @@ class Veth(CoreInterface):
if start: if start:
self.startup() self.startup()
def startup(self): def startup(self) -> None:
""" """
Interface startup logic. Interface startup logic.
@ -250,7 +277,7 @@ class Veth(CoreInterface):
self.net_client.device_up(self.localname) self.net_client.device_up(self.localname)
self.up = True self.up = True
def shutdown(self): def shutdown(self) -> None:
""" """
Interface shutdown logic. Interface shutdown logic.
@ -280,8 +307,15 @@ class TunTap(CoreInterface):
""" """
def __init__( 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. Create a TunTap instance.
@ -301,7 +335,7 @@ class TunTap(CoreInterface):
if start: if start:
self.startup() self.startup()
def startup(self): def startup(self) -> None:
""" """
Startup logic for a tunnel tap. Startup logic for a tunnel tap.
@ -315,7 +349,7 @@ class TunTap(CoreInterface):
# self.install() # self.install()
self.up = True self.up = True
def shutdown(self): def shutdown(self) -> None:
""" """
Shutdown functionality for a tunnel tap. Shutdown functionality for a tunnel tap.
@ -331,7 +365,9 @@ class TunTap(CoreInterface):
self.up = False 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. Wait for func() to return zero with exponential backoff.
@ -362,7 +398,7 @@ class TunTap(CoreInterface):
return result return result
def waitfordevicelocal(self): def waitfordevicelocal(self) -> None:
""" """
Check for presence of a local device - tap device may not Check for presence of a local device - tap device may not
appear right away waits appear right away waits
@ -381,7 +417,7 @@ class TunTap(CoreInterface):
self.waitfor(localdevexists) 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. Check for presence of a node device - tap device may not appear right away waits.
@ -412,7 +448,7 @@ class TunTap(CoreInterface):
else: else:
raise RuntimeError("node device failed to exist") 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 Install this TAP into its namespace. This is not done from the
startup() method but called at a later time when a userspace 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_name(self.localname, self.name)
self.node.node_net_client.device_up(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. Set interface addresses based on self.addrlist.
@ -448,18 +484,18 @@ class GreTap(CoreInterface):
def __init__( def __init__(
self, self,
node=None, node: "CoreNode" = None,
name=None, name: str = None,
session=None, session: "Session" = None,
mtu=1458, mtu: int = 1458,
remoteip=None, remoteip: str = None,
_id=None, _id: int = None,
localip=None, localip: str = None,
ttl=255, ttl: int = 255,
key=None, key: int = None,
start=True, start: bool = True,
server=None, server: "DistributedServer" = None,
): ) -> None:
""" """
Creates a GreTap instance. Creates a GreTap instance.
@ -497,7 +533,7 @@ class GreTap(CoreInterface):
self.net_client.device_up(self.localname) self.net_client.device_up(self.localname)
self.up = True self.up = True
def shutdown(self): def shutdown(self) -> None:
""" """
Shutdown logic for a GreTap. Shutdown logic for a GreTap.
@ -512,7 +548,7 @@ class GreTap(CoreInterface):
self.localname = None self.localname = None
def data(self, message_type): def data(self, message_type: int) -> None:
""" """
Data for a gre tap. Data for a gre tap.
@ -521,7 +557,7 @@ class GreTap(CoreInterface):
""" """
return None return None
def all_link_data(self, flags): def all_link_data(self, flags: int) -> List:
""" """
Retrieve link data. Retrieve link data.

View file

@ -3,27 +3,33 @@ import logging
import os import os
import time import time
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from typing import TYPE_CHECKING, Callable, Dict
from core import utils from core import utils
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes from core.emulator.enumerations import NodeTypes
from core.errors import CoreCommandError from core.errors import CoreCommandError
from core.nodes.base import CoreNode from core.nodes.base import CoreNode
from core.nodes.interface import CoreInterface
if TYPE_CHECKING:
from core.emulator.session import Session
class LxdClient: class LxdClient:
def __init__(self, name, image, run): def __init__(self, name: str, image: str, run: Callable) -> None:
self.name = name self.name = name
self.image = image self.image = image
self.run = run self.run = run
self.pid = None self.pid = None
def create_container(self): def create_container(self) -> int:
self.run(f"lxc launch {self.image} {self.name}") self.run(f"lxc launch {self.image} {self.name}")
data = self.get_info() data = self.get_info()
self.pid = data["state"]["pid"] self.pid = data["state"]["pid"]
return self.pid return self.pid
def get_info(self): def get_info(self) -> Dict:
args = f"lxc list {self.name} --format json" args = f"lxc list {self.name} --format json"
output = self.run(args) output = self.run(args)
data = json.loads(output) data = json.loads(output)
@ -31,27 +37,27 @@ class LxdClient:
raise CoreCommandError(-1, args, f"LXC({self.name}) not present") raise CoreCommandError(-1, args, f"LXC({self.name}) not present")
return data[0] return data[0]
def is_alive(self): def is_alive(self) -> bool:
try: try:
data = self.get_info() data = self.get_info()
return data["state"]["status"] == "Running" return data["state"]["status"] == "Running"
except CoreCommandError: except CoreCommandError:
return False return False
def stop_container(self): def stop_container(self) -> None:
self.run(f"lxc delete --force {self.name}") 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}" 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}" 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) args = self.create_cmd(cmd)
return utils.cmd(args, wait=wait, shell=shell) 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] != "/": if destination[0] != "/":
destination = os.path.join("/root/", destination) destination = os.path.join("/root/", destination)
@ -64,15 +70,15 @@ class LxcNode(CoreNode):
def __init__( def __init__(
self, self,
session, session: "Session",
_id=None, _id: int = None,
name=None, name: str = None,
nodedir=None, nodedir: str = None,
bootsh="boot.sh", bootsh: str = "boot.sh",
start=True, start: bool = True,
server=None, server: DistributedServer = None,
image=None, image: str = None,
): ) -> None:
""" """
Create a LxcNode instance. Create a LxcNode instance.
@ -91,7 +97,7 @@ class LxcNode(CoreNode):
self.image = image self.image = image
super().__init__(session, _id, name, nodedir, bootsh, start, server) super().__init__(session, _id, name, nodedir, bootsh, start, server)
def alive(self): def alive(self) -> bool:
""" """
Check if the node is alive. Check if the node is alive.
@ -100,7 +106,7 @@ class LxcNode(CoreNode):
""" """
return self.client.is_alive() return self.client.is_alive()
def startup(self): def startup(self) -> None:
""" """
Startup logic. Startup logic.
@ -114,7 +120,7 @@ class LxcNode(CoreNode):
self.pid = self.client.create_container() self.pid = self.client.create_container()
self.up = True self.up = True
def shutdown(self): def shutdown(self) -> None:
""" """
Shutdown logic. Shutdown logic.
@ -129,7 +135,7 @@ class LxcNode(CoreNode):
self.client.stop_container() self.client.stop_container()
self.up = False self.up = False
def termcmdstring(self, sh="/bin/sh"): def termcmdstring(self, sh: str = "/bin/sh") -> str:
""" """
Create a terminal command string. Create a terminal command string.
@ -138,7 +144,7 @@ class LxcNode(CoreNode):
""" """
return f"lxc exec {self.name} -- {sh}" return f"lxc exec {self.name} -- {sh}"
def privatedir(self, path): def privatedir(self, path: str) -> None:
""" """
Create a private directory. Create a private directory.
@ -147,9 +153,9 @@ class LxcNode(CoreNode):
""" """
logging.info("creating node dir: %s", path) logging.info("creating node dir: %s", path)
args = f"mkdir -p {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. Create and mount a directory.
@ -161,7 +167,7 @@ class LxcNode(CoreNode):
logging.debug("mounting source(%s) target(%s)", source, target) logging.debug("mounting source(%s) target(%s)", source, target)
raise Exception("not supported") 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. Create a node file with a given mode.
@ -188,7 +194,7 @@ class LxcNode(CoreNode):
os.unlink(temp.name) os.unlink(temp.name)
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode) 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. Copy a file to a node, following symlinks and preserving metadata.
Change file mode if specified. Change file mode if specified.
@ -214,7 +220,7 @@ class LxcNode(CoreNode):
self.client.copy_file(source, filename) self.client.copy_file(source, filename)
self.cmd(f"chmod {mode:o} {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) super().addnetif(netif, ifindex)
# adding small delay to allow time for adding addresses to work correctly # adding small delay to allow time for adding addresses to work correctly
time.sleep(0.5) time.sleep(0.5)

View file

@ -2,30 +2,17 @@
Clients for dealing with bridge/interface commands. Clients for dealing with bridge/interface commands.
""" """
import json import json
from typing import Callable
from core.constants import ETHTOOL_BIN, IP_BIN, OVS_BIN, TC_BIN 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: class LinuxNetClient:
""" """
Client for creating Linux bridges and ip interfaces for nodes. Client for creating Linux bridges and ip interfaces for nodes.
""" """
def __init__(self, run): def __init__(self, run: Callable) -> None:
""" """
Create LinuxNetClient instance. Create LinuxNetClient instance.
@ -33,7 +20,7 @@ class LinuxNetClient:
""" """
self.run = run self.run = run
def set_hostname(self, name): def set_hostname(self, name: str) -> None:
""" """
Set network hostname. Set network hostname.
@ -42,7 +29,7 @@ class LinuxNetClient:
""" """
self.run(f"hostname {name}") 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. Create a new route for a device.
@ -52,7 +39,7 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} route add {route} dev {device}") 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. Bring a device up.
@ -61,7 +48,7 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} link set {device} up") 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. Bring a device down.
@ -70,7 +57,7 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} link set {device} down") 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. Set a device name.
@ -80,7 +67,7 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} link set {device} name {name}") 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. Show information for a device.
@ -90,7 +77,7 @@ class LinuxNetClient:
""" """
return self.run(f"{IP_BIN} link show {device}") 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. Retrieve MAC address for a given device.
@ -100,7 +87,7 @@ class LinuxNetClient:
""" """
return self.run(f"cat /sys/class/net/{device}/address") 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. Retrieve ifindex for a given device.
@ -110,7 +97,7 @@ class LinuxNetClient:
""" """
return self.run(f"cat /sys/class/net/{device}/ifindex") 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. Set netns for a device.
@ -120,7 +107,7 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} link set {device} netns {namespace}") 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. Flush device addresses.
@ -132,7 +119,7 @@ class LinuxNetClient:
shell=True, shell=True,
) )
def device_mac(self, device, mac): def device_mac(self, device: str, mac: str) -> None:
""" """
Set MAC address for a device. Set MAC address for a device.
@ -142,7 +129,7 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} link set dev {device} address {mac}") 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. Delete device.
@ -151,7 +138,7 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} link delete {device}") 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. Remove traffic control settings for a device.
@ -160,7 +147,7 @@ class LinuxNetClient:
""" """
self.run(f"{TC_BIN} qdisc delete dev {device} root") 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. Turns interface checksums off.
@ -169,7 +156,7 @@ class LinuxNetClient:
""" """
self.run(f"{ETHTOOL_BIN} -K {interface_name} rx off tx off") 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. Create address for a device.
@ -185,7 +172,7 @@ class LinuxNetClient:
else: else:
self.run(f"{IP_BIN} address add {address} dev {device}") 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. Delete an address from a device.
@ -195,7 +182,7 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} address delete {address} dev {device}") 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. Create a veth pair.
@ -205,7 +192,9 @@ class LinuxNetClient:
""" """
self.run(f"{IP_BIN} link add name {name} type veth peer name {peer}") 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. Create a GRE tap on a device.
@ -225,7 +214,7 @@ class LinuxNetClient:
cmd += f" key {key}" cmd += f" key {key}"
self.run(cmd) self.run(cmd)
def create_bridge(self, name): def create_bridge(self, name: str) -> None:
""" """
Create a Linux bridge and bring it up. 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.run(f"{IP_BIN} link set {name} type bridge mcast_snooping 0")
self.device_up(name) self.device_up(name)
def delete_bridge(self, name): def delete_bridge(self, name: str) -> None:
""" """
Bring down and delete a Linux bridge. Bring down and delete a Linux bridge.
@ -248,7 +237,7 @@ class LinuxNetClient:
self.device_down(name) self.device_down(name)
self.run(f"{IP_BIN} link delete {name} type bridge") 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. 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.run(f"{IP_BIN} link set dev {interface_name} master {bridge_name}")
self.device_up(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 Linux bridge. 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") 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. Checks if there are any existing Linux bridges for a node.
:param _id: node id to check bridges for :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") output = self.run(f"{IP_BIN} -j link show type bridge")
bridges = json.loads(output) bridges = json.loads(output)
@ -286,7 +276,7 @@ class LinuxNetClient:
return True return True
return False return False
def disable_mac_learning(self, name): def disable_mac_learning(self, name: str) -> None:
""" """
Disable mac learning for a Linux bridge. Disable mac learning for a Linux bridge.
@ -301,7 +291,7 @@ class OvsNetClient(LinuxNetClient):
Client for creating OVS bridges and ip interfaces for nodes. 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. 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.run(f"{OVS_BIN} set bridge {name} other_config:stp-forward-delay=4")
self.device_up(name) self.device_up(name)
def delete_bridge(self, name): def delete_bridge(self, name: str) -> None:
""" """
Bring down and delete a OVS bridge. Bring down and delete a OVS bridge.
@ -324,7 +314,7 @@ class OvsNetClient(LinuxNetClient):
self.device_down(name) self.device_down(name)
self.run(f"{OVS_BIN} del-br {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. 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.run(f"{OVS_BIN} add-port {bridge_name} {interface_name}")
self.device_up(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. 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}") 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. Checks if there are any existing OVS bridges for a node.
:param _id: node id to check bridges for :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") output = self.run(f"{OVS_BIN} list-br")
if output: if output:
@ -359,7 +350,7 @@ class OvsNetClient(LinuxNetClient):
return True return True
return False return False
def disable_mac_learning(self, name): def disable_mac_learning(self, name: str) -> None:
""" """
Disable mac learning for a OVS bridge. Disable mac learning for a OVS bridge.
@ -367,3 +358,17 @@ class OvsNetClient(LinuxNetClient):
:return: nothing :return: nothing
""" """
self.run(f"{OVS_BIN} set bridge {name} other_config:mac-aging-time=0") 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)

View file

@ -5,18 +5,26 @@ Defines network nodes used within core.
import logging import logging
import threading import threading
import time import time
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
import netaddr import netaddr
from core import utils from core import utils
from core.constants import EBTABLES_BIN, TC_BIN 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.emulator.enumerations import LinkTypes, NodeTypes, RegisterTlvs
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNetworkBase 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 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() ebtables_lock = threading.Lock()
@ -32,7 +40,7 @@ class EbtablesQueue:
# ebtables # ebtables
atomic_file = "/tmp/pycore.ebtables.atomic" atomic_file = "/tmp/pycore.ebtables.atomic"
def __init__(self): def __init__(self) -> None:
""" """
Initialize the helper class, but don't start the update thread Initialize the helper class, but don't start the update thread
until a WLAN is instantiated. until a WLAN is instantiated.
@ -49,7 +57,7 @@ class EbtablesQueue:
# using this queue # using this queue
self.last_update_time = {} 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. Kick off the update loop; only needs to be invoked once.
@ -66,7 +74,7 @@ class EbtablesQueue:
self.updatethread.daemon = True self.updatethread.daemon = True
self.updatethread.start() 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. Kill the update loop thread if there are no more WLANs using it.
@ -88,17 +96,17 @@ class EbtablesQueue:
self.updatethread.join() self.updatethread.join()
self.updatethread = None self.updatethread = None
def ebatomiccmd(self, cmd): def ebatomiccmd(self, cmd: str) -> str:
""" """
Helper for building ebtables atomic file command list. Helper for building ebtables atomic file command list.
:param str cmd: ebtable command :param str cmd: ebtable command
:return: ebtable atomic command :return: ebtable atomic command
:rtype: list[str] :rtype: str
""" """
return f"{EBTABLES_BIN} --atomic-file {self.atomic_file} {cmd}" 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. Return the time elapsed since this WLAN was last updated.
@ -114,7 +122,7 @@ class EbtablesQueue:
return elapsed return elapsed
def updated(self, wlan): def updated(self, wlan: "CoreNetwork") -> None:
""" """
Keep track of when this WLAN was last updated. Keep track of when this WLAN was last updated.
@ -124,7 +132,7 @@ class EbtablesQueue:
self.last_update_time[wlan] = time.monotonic() self.last_update_time[wlan] = time.monotonic()
self.updates.remove(wlan) self.updates.remove(wlan)
def updateloop(self): def updateloop(self) -> None:
""" """
Thread target that looks for WLANs needing update, and Thread target that looks for WLANs needing update, and
rate limits the amount of ebtables activity. Only one userspace program rate limits the amount of ebtables activity. Only one userspace program
@ -153,7 +161,7 @@ class EbtablesQueue:
time.sleep(self.rate) 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. Perform ebtables atomic commit using commands built in the self.cmds list.
@ -178,7 +186,7 @@ class EbtablesQueue:
except CoreCommandError: except CoreCommandError:
logging.exception("error removing atomic file: %s", self.atomic_file) 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 Flag a change to the given WLAN's _linked dict, so the ebtables
chain will be rebuilt at the next interval. chain will be rebuilt at the next interval.
@ -189,7 +197,7 @@ class EbtablesQueue:
if wlan not in self.updates: if wlan not in self.updates:
self.updates.append(wlan) 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. Inspect a _linked dict from a wlan, and rebuild the ebtables chain for that WLAN.
@ -231,7 +239,7 @@ class EbtablesQueue:
ebq = EbtablesQueue() ebq = EbtablesQueue()
def ebtablescmds(call, cmds): def ebtablescmds(call: Callable, cmds: List[str]) -> None:
""" """
Run ebtable commands. Run ebtable commands.
@ -252,8 +260,14 @@ class CoreNetwork(CoreNetworkBase):
policy = "DROP" policy = "DROP"
def __init__( 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. Creates a LxBrNet instance.
@ -279,7 +293,14 @@ class CoreNetwork(CoreNetworkBase):
self.startup() self.startup()
ebq.startupdateloop(self) 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 Runs a command that is used to configure and setup the network on the host
system and all configured distributed servers. 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)) self.session.distributed.execute(lambda x: x.remote_cmd(args, env, cwd, wait))
return output return output
def startup(self): def startup(self) -> None:
""" """
Linux bridge starup logic. Linux bridge starup logic.
@ -309,7 +330,7 @@ class CoreNetwork(CoreNetworkBase):
self.has_ebtables_chain = False self.has_ebtables_chain = False
self.up = True self.up = True
def shutdown(self): def shutdown(self) -> None:
""" """
Linux bridge shutdown logic. Linux bridge shutdown logic.
@ -340,18 +361,18 @@ class CoreNetwork(CoreNetworkBase):
del self.session del self.session
self.up = False self.up = False
def attach(self, netif): def attach(self, netif: CoreInterface) -> None:
""" """
Attach a network interface. 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 :return: nothing
""" """
if self.up: if self.up:
netif.net_client.create_interface(self.brname, netif.localname) netif.net_client.create_interface(self.brname, netif.localname)
super().attach(netif) super().attach(netif)
def detach(self, netif): def detach(self, netif: CoreInterface) -> None:
""" """
Detach a network interface. Detach a network interface.
@ -362,7 +383,7 @@ class CoreNetwork(CoreNetworkBase):
netif.net_client.delete_interface(self.brname, netif.localname) netif.net_client.delete_interface(self.brname, netif.localname)
super().detach(netif) super().detach(netif)
def linked(self, netif1, netif2): def linked(self, netif1: CoreInterface, netif2: CoreInterface) -> bool:
""" """
Determine if the provided network interfaces are linked. Determine if the provided network interfaces are linked.
@ -391,9 +412,9 @@ class CoreNetwork(CoreNetworkBase):
return linked 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. filtering rules.
:param core.nodes.interface.CoreInterface netif1: interface one :param core.nodes.interface.CoreInterface netif1: interface one
@ -407,9 +428,9 @@ class CoreNetwork(CoreNetworkBase):
ebq.ebchange(self) 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. ebtables filtering rules.
:param core.nodes.interface.CoreInterface netif1: interface one :param core.nodes.interface.CoreInterface netif1: interface one
@ -425,19 +446,19 @@ class CoreNetwork(CoreNetworkBase):
def linkconfig( def linkconfig(
self, self,
netif, netif: CoreInterface,
bw=None, bw: float = None,
delay=None, delay: float = None,
loss=None, loss: float = None,
duplicate=None, duplicate: float = None,
jitter=None, jitter: float = None,
netif2=None, netif2: float = None,
devname=None, devname: str = None,
): ) -> None:
""" """
Configure link parameters by applying tc queuing disciplines on the interface. 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 bw: bandwidth to set to
:param delay: packet delay to set to :param delay: packet delay to set to
:param loss: packet loss to set to :param loss: packet loss to set to
@ -520,14 +541,14 @@ class CoreNetwork(CoreNetworkBase):
netif.host_cmd(cmd) netif.host_cmd(cmd)
netif.setparam("has_netem", True) 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 Link this bridge with another by creating a veth pair and installing
each device into each bridge. each device into each bridge.
:param core.nodes.base.CoreNetworkBase net: network to link with :param core.nodes.base.CoreNetworkBase net: network to link with
:return: created interface :return: created interface
:rtype: core.nodes.interface.Veth :rtype: core.nodes.interface.CoreInterface
""" """
sessionid = self.session.short_session_id() sessionid = self.session.short_session_id()
try: try:
@ -561,7 +582,7 @@ class CoreNetwork(CoreNetworkBase):
netif.othernet = net netif.othernet = net
return netif return netif
def getlinknetif(self, net): def getlinknetif(self, net: CoreNetworkBase) -> Optional[CoreInterface]:
""" """
Return the interface of that links this net with another net Return the interface of that links this net with another net
(that were linked using linknet()). (that were linked using linknet()).
@ -573,10 +594,9 @@ class CoreNetwork(CoreNetworkBase):
for netif in self.netifs(): for netif in self.netifs():
if hasattr(netif, "othernet") and netif.othernet == net: if hasattr(netif, "othernet") and netif.othernet == net:
return netif return netif
return None return None
def addrconfig(self, addrlist): def addrconfig(self, addrlist: List[str]) -> None:
""" """
Set addresses on the bridge. Set addresses on the bridge.
@ -598,17 +618,17 @@ class GreTapBridge(CoreNetwork):
def __init__( def __init__(
self, self,
session, session: "Session",
remoteip=None, remoteip: str = None,
_id=None, _id: int = None,
name=None, name: str = None,
policy="ACCEPT", policy: str = "ACCEPT",
localip=None, localip: str = None,
ttl=255, ttl: int = 255,
key=None, key: int = None,
start=True, start: bool = True,
server=None, server: "DistributedServer" = None,
): ) -> None:
""" """
Create a GreTapBridge instance. Create a GreTapBridge instance.
@ -647,7 +667,7 @@ class GreTapBridge(CoreNetwork):
if start: if start:
self.startup() self.startup()
def startup(self): def startup(self) -> None:
""" """
Creates a bridge and adds the gretap device to it. Creates a bridge and adds the gretap device to it.
@ -657,7 +677,7 @@ class GreTapBridge(CoreNetwork):
if self.gretap: if self.gretap:
self.attach(self.gretap) self.attach(self.gretap)
def shutdown(self): def shutdown(self) -> None:
""" """
Detach the gretap device and remove the bridge. Detach the gretap device and remove the bridge.
@ -669,7 +689,7 @@ class GreTapBridge(CoreNetwork):
self.gretap = None self.gretap = None
super().shutdown() 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 Set the remote tunnel endpoint. This is a one-time method for
creating the GreTap device, which requires the remoteip at startup. creating the GreTap device, which requires the remoteip at startup.
@ -694,7 +714,7 @@ class GreTapBridge(CoreNetwork):
) )
self.attach(self.gretap) 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 Set the GRE key used for the GreTap device. This needs to be set
prior to instantiating the GreTap device (before addrconfig). prior to instantiating the GreTap device (before addrconfig).
@ -722,17 +742,17 @@ class CtrlNet(CoreNetwork):
def __init__( def __init__(
self, self,
session, session: "Session",
_id=None, _id: int = None,
name=None, name: str = None,
prefix=None, prefix: str = None,
hostid=None, hostid: int = None,
start=True, start: bool = True,
server=None, server: "DistributedServer" = None,
assign_address=True, assign_address: bool = True,
updown_script=None, updown_script: str = None,
serverintf=None, serverintf: CoreInterface = None,
): ) -> None:
""" """
Creates a CtrlNet instance. Creates a CtrlNet instance.
@ -756,7 +776,7 @@ class CtrlNet(CoreNetwork):
self.serverintf = serverintf self.serverintf = serverintf
super().__init__(session, _id, name, start, server) 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, 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 = get_net_client(use_ovs, server.remote_cmd)
net_client.create_address(self.brname, current) net_client.create_address(self.brname, current)
def startup(self): def startup(self) -> None:
""" """
Startup functionality for the control network. Startup functionality for the control network.
@ -806,7 +826,7 @@ class CtrlNet(CoreNetwork):
if self.serverintf: if self.serverintf:
self.net_client.create_interface(self.brname, self.serverintf) self.net_client.create_interface(self.brname, self.serverintf)
def shutdown(self): def shutdown(self) -> None:
""" """
Control network shutdown. Control network shutdown.
@ -835,7 +855,7 @@ class CtrlNet(CoreNetwork):
super().shutdown() 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. Do not include CtrlNet in link messages describing this session.
@ -853,11 +873,11 @@ class PtpNet(CoreNetwork):
policy = "ACCEPT" policy = "ACCEPT"
def attach(self, netif): def attach(self, netif: CoreInterface) -> None:
""" """
Attach a network interface, but limit attachment to two interfaces. 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 :return: nothing
""" """
if len(self._netif) >= 2: if len(self._netif) >= 2:
@ -866,7 +886,14 @@ class PtpNet(CoreNetwork):
) )
super().attach(netif) 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 Do not generate a Node Message for point-to-point links. They are
built using a link message instead. built using a link message instead.
@ -875,12 +902,13 @@ class PtpNet(CoreNetwork):
:param float lat: latitude :param float lat: latitude
:param float lon: longitude :param float lon: longitude
:param float alt: altitude :param float alt: altitude
:param str source: source of node data
:return: node data object :return: node data object
:rtype: core.emulator.data.NodeData :rtype: core.emulator.data.NodeData
""" """
return None 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 Build CORE API TLVs for a point-to-point link. One Link message
describes this network. describes this network.
@ -997,7 +1025,7 @@ class HubNode(CoreNetwork):
policy = "ACCEPT" policy = "ACCEPT"
type = "hub" type = "hub"
def startup(self): def startup(self) -> None:
""" """
Startup for a hub node, that disables mac learning after normal startup. Startup for a hub node, that disables mac learning after normal startup.
@ -1018,8 +1046,14 @@ class WlanNode(CoreNetwork):
type = "wlan" type = "wlan"
def __init__( 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. Create a WlanNode instance.
@ -1036,7 +1070,7 @@ class WlanNode(CoreNetwork):
self.model = None self.model = None
self.mobility = None self.mobility = None
def startup(self): def startup(self) -> None:
""" """
Startup for a wlan node, that disables mac learning after normal startup. Startup for a wlan node, that disables mac learning after normal startup.
@ -1045,11 +1079,11 @@ class WlanNode(CoreNetwork):
super().startup() super().startup()
self.net_client.disable_mac_learning(self.brname) self.net_client.disable_mac_learning(self.brname)
def attach(self, netif): def attach(self, netif: CoreInterface) -> None:
""" """
Attach a network interface. Attach a network interface.
:param core.nodes.interface.Veth netif: network interface :param core.nodes.interface.CoreInterface netif: network interface
:return: nothing :return: nothing
""" """
super().attach(netif) super().attach(netif)
@ -1061,7 +1095,7 @@ class WlanNode(CoreNetwork):
# invokes any netif.poshook # invokes any netif.poshook
netif.setposition(x, y, z) 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. Sets the mobility and wireless model.
@ -1082,12 +1116,12 @@ class WlanNode(CoreNetwork):
self.mobility = model(session=self.session, _id=self.id) self.mobility = model(session=self.session, _id=self.id)
self.mobility.update_config(config) self.mobility.update_config(config)
def update_mobility(self, config): def update_mobility(self, config: Dict[str, str]) -> None:
if not self.mobility: if not self.mobility:
raise ValueError(f"no mobility set to update for node({self.id})") raise ValueError(f"no mobility set to update for node({self.id})")
self.mobility.update_config(config) self.mobility.update_config(config)
def updatemodel(self, config): def updatemodel(self, config: Dict[str, str]) -> None:
if not self.model: if not self.model:
raise ValueError(f"no model set to update for node({self.id})") raise ValueError(f"no model set to update for node({self.id})")
logging.debug( logging.debug(
@ -1099,7 +1133,7 @@ class WlanNode(CoreNetwork):
x, y, z = netif.node.position.get() x, y, z = netif.node.position.get()
netif.poshook(netif, x, y, z) 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. Retrieve all link data.

View file

@ -5,20 +5,31 @@ PhysicalNode class for including real systems in the emulated network.
import logging import logging
import os import os
import threading import threading
from typing import IO, TYPE_CHECKING, List, Optional
from core import utils from core import utils
from core.constants import MOUNT_BIN, UMOUNT_BIN from core.constants import MOUNT_BIN, UMOUNT_BIN
from core.emulator.distributed import DistributedServer
from core.emulator.enumerations import NodeTypes from core.emulator.enumerations import NodeTypes
from core.errors import CoreCommandError, CoreError from core.errors import CoreCommandError, CoreError
from core.nodes.base import CoreNodeBase from core.nodes.base import CoreNetworkBase, CoreNodeBase
from core.nodes.interface import CoreInterface from core.nodes.interface import CoreInterface, Veth
from core.nodes.network import CoreNetwork, GreTap from core.nodes.network import CoreNetwork, GreTap
if TYPE_CHECKING:
from core.emulator.session import Session
class PhysicalNode(CoreNodeBase): class PhysicalNode(CoreNodeBase):
def __init__( 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) super().__init__(session, _id, name, start, server)
if not self.server: if not self.server:
raise CoreError("physical nodes must be assigned to a remote server") raise CoreError("physical nodes must be assigned to a remote server")
@ -29,11 +40,11 @@ class PhysicalNode(CoreNodeBase):
if start: if start:
self.startup() self.startup()
def startup(self): def startup(self) -> None:
with self.lock: with self.lock:
self.makenodedir() self.makenodedir()
def shutdown(self): def shutdown(self) -> None:
if not self.up: if not self.up:
return return
@ -47,7 +58,7 @@ class PhysicalNode(CoreNodeBase):
self.rmnodedir() self.rmnodedir()
def termcmdstring(self, sh="/bin/sh"): def termcmdstring(self, sh: str = "/bin/sh") -> str:
""" """
Create a terminal command string. Create a terminal command string.
@ -56,7 +67,7 @@ class PhysicalNode(CoreNodeBase):
""" """
return sh return sh
def sethwaddr(self, ifindex, addr): def sethwaddr(self, ifindex: int, addr: str) -> None:
""" """
Set hardware address for an interface. Set hardware address for an interface.
@ -71,7 +82,7 @@ class PhysicalNode(CoreNodeBase):
if self.up: if self.up:
self.net_client.device_mac(interface.name, addr) 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. Add an address to an interface.
@ -85,9 +96,13 @@ class PhysicalNode(CoreNodeBase):
self.net_client.create_address(interface.name, addr) self.net_client.create_address(interface.name, addr)
interface.addaddr(addr) interface.addaddr(addr)
def deladdr(self, ifindex, addr): def deladdr(self, ifindex: int, addr: str) -> None:
""" """
Delete an address from an interface. 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] interface = self._netif[ifindex]
@ -99,7 +114,9 @@ class PhysicalNode(CoreNodeBase):
if self.up: if self.up:
self.net_client.delete_address(interface.name, str(addr)) 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 When a link message is received linking this node to another part of
the emulation, no new interface is created; instead, adopt the the emulation, no new interface is created; instead, adopt the
@ -127,18 +144,17 @@ class PhysicalNode(CoreNodeBase):
def linkconfig( def linkconfig(
self, self,
netif, netif: CoreInterface,
bw=None, bw: float = None,
delay=None, delay: float = None,
loss=None, loss: float = None,
duplicate=None, duplicate: float = None,
jitter=None, jitter: float = None,
netif2=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 = CoreNetwork(session=self.session, start=False)
linux_bridge.up = True linux_bridge.up = True
linux_bridge.linkconfig( linux_bridge.linkconfig(
@ -152,7 +168,7 @@ class PhysicalNode(CoreNodeBase):
) )
del linux_bridge del linux_bridge
def newifindex(self): def newifindex(self) -> int:
with self.lock: with self.lock:
while self.ifindex in self._netif: while self.ifindex in self._netif:
self.ifindex += 1 self.ifindex += 1
@ -160,7 +176,14 @@ class PhysicalNode(CoreNodeBase):
self.ifindex += 1 self.ifindex += 1
return ifindex 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") logging.info("creating interface")
if not addrlist: if not addrlist:
addrlist = [] addrlist = []
@ -186,7 +209,7 @@ class PhysicalNode(CoreNodeBase):
self.adoptnetif(netif, ifindex, hwaddr, addrlist) self.adoptnetif(netif, ifindex, hwaddr, addrlist)
return ifindex return ifindex
def privatedir(self, path): def privatedir(self, path: str) -> None:
if path[0] != "/": if path[0] != "/":
raise ValueError(f"path not fully qualified: {path}") raise ValueError(f"path not fully qualified: {path}")
hostpath = os.path.join( hostpath = os.path.join(
@ -195,21 +218,21 @@ class PhysicalNode(CoreNodeBase):
os.mkdir(hostpath) os.mkdir(hostpath)
self.mount(hostpath, path) self.mount(hostpath, path)
def mount(self, source, target): def mount(self, source: str, target: str) -> None:
source = os.path.abspath(source) source = os.path.abspath(source)
logging.info("mounting %s at %s", source, target) logging.info("mounting %s at %s", source, target)
os.makedirs(target) os.makedirs(target)
self.host_cmd(f"{MOUNT_BIN} --bind {source} {target}", cwd=self.nodedir) self.host_cmd(f"{MOUNT_BIN} --bind {source} {target}", cwd=self.nodedir)
self._mounts.append((source, target)) self._mounts.append((source, target))
def umount(self, target): def umount(self, target: str) -> None:
logging.info("unmounting '%s'", target) logging.info("unmounting '%s'", target)
try: try:
self.host_cmd(f"{UMOUNT_BIN} -l {target}", cwd=self.nodedir) self.host_cmd(f"{UMOUNT_BIN} -l {target}", cwd=self.nodedir)
except CoreCommandError: except CoreCommandError:
logging.exception("unmounting failed for %s", target) 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) dirname, basename = os.path.split(filename)
if not basename: if not basename:
raise ValueError("no basename for filename: " + filename) raise ValueError("no basename for filename: " + filename)
@ -225,13 +248,13 @@ class PhysicalNode(CoreNodeBase):
hostfilename = os.path.join(dirname, basename) hostfilename = os.path.join(dirname, basename)
return open(hostfilename, mode) 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: with self.opennodefile(filename, "w") as node_file:
node_file.write(contents) node_file.write(contents)
os.chmod(node_file.name, mode) os.chmod(node_file.name, mode)
logging.info("created nodefile: '%s'; mode: 0%o", 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) return self.host_cmd(args, wait=wait)
@ -244,7 +267,15 @@ class Rj45Node(CoreNodeBase, CoreInterface):
apitype = NodeTypes.RJ45.value apitype = NodeTypes.RJ45.value
type = "rj45" 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. Create an RJ45Node instance.
@ -270,7 +301,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
if start: if start:
self.startup() self.startup()
def startup(self): def startup(self) -> None:
""" """
Set the interface in the up state. Set the interface in the up state.
@ -282,7 +313,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
self.net_client.device_up(self.localname) self.net_client.device_up(self.localname)
self.up = True self.up = True
def shutdown(self): def shutdown(self) -> None:
""" """
Bring the interface down. Remove any addresses and queuing Bring the interface down. Remove any addresses and queuing
disciplines. disciplines.
@ -304,18 +335,18 @@ class Rj45Node(CoreNodeBase, CoreInterface):
# TODO: issue in that both classes inherited from provide the same method with # TODO: issue in that both classes inherited from provide the same method with
# different signatures # different signatures
def attachnet(self, net): def attachnet(self, net: CoreNetworkBase) -> None:
""" """
Attach a network. Attach a network.
:param core.coreobj.PyCoreNet net: network to attach :param core.nodes.base.CoreNetworkBase net: network to attach
:return: nothing :return: nothing
""" """
CoreInterface.attachnet(self, net) CoreInterface.attachnet(self, net)
# TODO: issue in that both classes inherited from provide the same method with # TODO: issue in that both classes inherited from provide the same method with
# different signatures # different signatures
def detachnet(self): def detachnet(self) -> None:
""" """
Detach a network. Detach a network.
@ -323,7 +354,14 @@ class Rj45Node(CoreNodeBase, CoreInterface):
""" """
CoreInterface.detachnet(self) 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 This is called when linking with another node. Since this node
represents an interface, we do not create another object here, represents an interface, we do not create another object here,
@ -359,7 +397,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
return ifindex return ifindex
def delnetif(self, ifindex): def delnetif(self, ifindex: int) -> None:
""" """
Delete a network interface. Delete a network interface.
@ -376,7 +414,9 @@ class Rj45Node(CoreNodeBase, CoreInterface):
else: else:
raise ValueError(f"ifindex {ifindex} does not exist") 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 This object is considered the network interface, so we only
return self here. This keeps the RJ45Node compatible with return self here. This keeps the RJ45Node compatible with
@ -398,20 +438,20 @@ class Rj45Node(CoreNodeBase, CoreInterface):
return None return None
def getifindex(self, netif): def getifindex(self, netif: CoreInterface) -> Optional[int]:
""" """
Retrieve network interface index. 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 :return: interface index, None otherwise
:rtype: int :rtype: int
""" """
if netif != self: if netif != self:
return None return None
return self.ifindex return self.ifindex
def addaddr(self, addr): def addaddr(self, addr: str) -> None:
""" """
Add address to to network interface. Add address to to network interface.
@ -424,7 +464,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
self.net_client.create_address(self.name, addr) self.net_client.create_address(self.name, addr)
CoreInterface.addaddr(self, addr) CoreInterface.addaddr(self, addr)
def deladdr(self, addr): def deladdr(self, addr: str) -> None:
""" """
Delete address from network interface. Delete address from network interface.
@ -434,10 +474,9 @@ class Rj45Node(CoreNodeBase, CoreInterface):
""" """
if self.up: if self.up:
self.net_client.delete_address(self.name, str(addr)) self.net_client.delete_address(self.name, str(addr))
CoreInterface.deladdr(self, addr) CoreInterface.deladdr(self, addr)
def savestate(self): def savestate(self) -> None:
""" """
Save the addresses and other interface state before using the Save the addresses and other interface state before using the
interface for emulation purposes. TODO: save/restore the PROMISC flag interface for emulation purposes. TODO: save/restore the PROMISC flag
@ -464,7 +503,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
continue continue
self.old_addrs.append((items[1], None)) self.old_addrs.append((items[1], None))
def restorestate(self): def restorestate(self) -> None:
""" """
Restore the addresses and other interface state after using it. Restore the addresses and other interface state after using it.
@ -482,7 +521,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
if self.old_up: if self.old_up:
self.net_client.device_up(self.localname) 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. Uses setposition from both parent classes.
@ -496,7 +535,7 @@ class Rj45Node(CoreNodeBase, CoreInterface):
CoreInterface.setposition(self, x, y, z) CoreInterface.setposition(self, x, y, z)
return result return result
def termcmdstring(self, sh): def termcmdstring(self, sh: str) -> str:
""" """
Create a terminal command string. Create a terminal command string.