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 threading
from contextlib import contextmanager
from typing import Any, Callable, Dict, List
from typing import Any, Callable, Dict, Generator, List
import grpc
import netaddr
@ -1144,7 +1144,7 @@ class CoreGrpcClient:
self.channel = None
@contextmanager
def context_connect(self) -> None:
def context_connect(self) -> Generator:
"""
Makes a context manager based connection to the server, will close after context ends.

View file

@ -1,6 +1,6 @@
import logging
from queue import Empty, Queue
from typing import List
from typing import Iterable
from core.api.grpc import core_pb2
from core.api.grpc.grpcutils import convert_value
@ -181,7 +181,9 @@ class EventStreamer:
Processes session events to generate grpc events.
"""
def __init__(self, session: Session, event_types: List[core_pb2.EventType]) -> None:
def __init__(
self, session: Session, event_types: Iterable[core_pb2.EventType]
) -> None:
"""
Create a EventStreamer instance.

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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