2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2019-06-07 16:59:16 +01:00
|
|
|
Defines the base logic for nodes used within core.
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-14 21:35:06 +01:00
|
|
|
import abc
|
2019-04-30 07:31:47 +01:00
|
|
|
import logging
|
2019-10-06 08:06:29 +01:00
|
|
|
import shutil
|
2019-04-30 07:31:47 +01:00
|
|
|
import threading
|
2021-03-19 23:54:24 +00:00
|
|
|
from pathlib import Path
|
2020-05-25 08:16:58 +01:00
|
|
|
from threading import RLock
|
2020-06-19 18:54:58 +01:00
|
|
|
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
|
2019-06-07 16:59:16 +01:00
|
|
|
|
2020-01-07 22:08:29 +00:00
|
|
|
import netaddr
|
|
|
|
|
2019-10-12 00:36:57 +01:00
|
|
|
from core import utils
|
2020-01-23 21:22:47 +00:00
|
|
|
from core.configservice.dependencies import ConfigServiceDependencies
|
2022-01-25 17:13:39 +00:00
|
|
|
from core.emulator.data import InterfaceData, LinkData
|
2020-03-13 06:12:17 +00:00
|
|
|
from core.emulator.enumerations import LinkTypes, MessageFlags, NodeTypes
|
2020-01-18 05:09:51 +00:00
|
|
|
from core.errors import CoreCommandError, CoreError
|
2020-09-09 18:27:06 +01:00
|
|
|
from core.executables import MOUNT, TEST, VNODED
|
2020-05-25 08:16:58 +01:00
|
|
|
from core.nodes.client import VnodeClient
|
2021-12-21 16:59:48 +00:00
|
|
|
from core.nodes.interface import DEFAULT_MTU, CoreInterface, TunTap, Veth
|
2020-01-13 22:08:49 +00:00
|
|
|
from core.nodes.netclient import LinuxNetClient, get_net_client
|
|
|
|
|
2021-04-22 05:09:35 +01:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
if TYPE_CHECKING:
|
|
|
|
from core.emulator.distributed import DistributedServer
|
|
|
|
from core.emulator.session import Session
|
2020-01-18 05:09:51 +00:00
|
|
|
from core.configservice.base import ConfigService
|
2020-05-25 08:16:58 +01:00
|
|
|
from core.services.coreservices import CoreService
|
2020-01-18 05:09:51 +00:00
|
|
|
|
2020-06-18 20:54:36 +01:00
|
|
|
CoreServices = List[Union[CoreService, Type[CoreService]]]
|
2020-01-18 05:09:51 +00:00
|
|
|
ConfigServiceType = Type[ConfigService]
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
PRIVATE_DIRS: List[Path] = [Path("/var/run"), Path("/var/log")]
|
|
|
|
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
class NodeBase(abc.ABC):
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Base class for CORE nodes (nodes and networks)
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
|
2020-05-25 08:16:58 +01:00
|
|
|
apitype: Optional[NodeTypes] = None
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
session: "Session",
|
|
|
|
_id: int = None,
|
|
|
|
name: str = None,
|
|
|
|
server: "DistributedServer" = None,
|
|
|
|
) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-01-13 22:08:49 +00:00
|
|
|
Creates a NodeBase instance.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param session: CORE session object
|
|
|
|
:param _id: id
|
|
|
|
:param name: object name
|
|
|
|
:param server: remote server node
|
2019-10-14 23:43:57 +01:00
|
|
|
will run on, default is None for localhost
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
|
2020-05-25 08:16:58 +01:00
|
|
|
self.session: "Session" = session
|
2019-04-30 07:31:47 +01:00
|
|
|
if _id is None:
|
2020-06-13 04:22:51 +01:00
|
|
|
_id = session.next_node_id()
|
2020-05-25 08:16:58 +01:00
|
|
|
self.id: int = _id
|
2019-04-30 07:31:47 +01:00
|
|
|
if name is None:
|
2019-10-18 18:33:31 +01:00
|
|
|
name = f"o{self.id}"
|
2020-05-25 08:16:58 +01:00
|
|
|
self.name: str = name
|
|
|
|
self.server: "DistributedServer" = server
|
|
|
|
self.type: Optional[str] = None
|
|
|
|
self.services: CoreServices = []
|
2020-06-16 17:30:16 +01:00
|
|
|
self.ifaces: Dict[int, CoreInterface] = {}
|
|
|
|
self.iface_id: int = 0
|
2020-05-25 08:16:58 +01:00
|
|
|
self.canvas: Optional[int] = None
|
|
|
|
self.icon: Optional[str] = None
|
|
|
|
self.position: Position = Position()
|
|
|
|
self.up: bool = False
|
2020-06-26 06:05:10 +01:00
|
|
|
self.net_client: LinuxNetClient = get_net_client(
|
|
|
|
self.session.use_ovs(), self.host_cmd
|
|
|
|
)
|
2019-10-01 20:14:37 +01:00
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2020-01-13 22:08:49 +00:00
|
|
|
def startup(self) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Each object implements its own startup method.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2020-01-13 22:08:49 +00:00
|
|
|
def shutdown(self) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Each object implements its own shutdown method.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def host_cmd(
|
|
|
|
self,
|
|
|
|
args: str,
|
|
|
|
env: Dict[str, str] = None,
|
2021-03-19 23:54:24 +00:00
|
|
|
cwd: Path = None,
|
2020-01-13 22:08:49 +00:00
|
|
|
wait: bool = True,
|
|
|
|
shell: bool = False,
|
|
|
|
) -> str:
|
2019-10-02 05:06:11 +01:00
|
|
|
"""
|
2019-10-19 07:28:09 +01:00
|
|
|
Runs a command on the host system or distributed server.
|
2019-10-02 05:06:11 +01:00
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param args: command to run
|
|
|
|
:param env: environment to run command with
|
|
|
|
:param cwd: directory to run command in
|
|
|
|
:param wait: True to wait for status, False otherwise
|
|
|
|
:param shell: True to use shell, False otherwise
|
2019-10-02 05:06:11 +01:00
|
|
|
:return: combined stdout and stderr
|
2020-01-18 05:12:14 +00:00
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
2019-10-02 05:06:11 +01:00
|
|
|
"""
|
2019-10-11 20:57:37 +01:00
|
|
|
if self.server is None:
|
2019-10-21 18:32:42 +01:00
|
|
|
return utils.cmd(args, env, cwd, wait, shell)
|
2019-10-11 20:57:37 +01:00
|
|
|
else:
|
2019-10-14 23:43:57 +01:00
|
|
|
return self.server.remote_cmd(args, env, cwd, wait)
|
2019-10-02 05:06:11 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def setposition(self, x: float = None, y: float = None, z: float = None) -> bool:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Set the (x,y,z) position of the object.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param x: x position
|
|
|
|
:param y: y position
|
|
|
|
:param z: z position
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: True if position changed, False otherwise
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
return self.position.set(x=x, y=y, z=z)
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def getposition(self) -> Tuple[float, float, float]:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Return an (x,y,z) tuple representing this object's position.
|
|
|
|
|
|
|
|
:return: x,y,z position tuple
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
return self.position.get()
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def get_iface(self, iface_id: int) -> CoreInterface:
|
2020-06-19 18:54:58 +01:00
|
|
|
"""
|
|
|
|
Retrieve interface based on id.
|
|
|
|
|
|
|
|
:param iface_id: id of interface to retrieve
|
|
|
|
:return: interface
|
|
|
|
:raises CoreError: when interface does not exist
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
if iface_id not in self.ifaces:
|
|
|
|
raise CoreError(f"node({self.name}) does not have interface({iface_id})")
|
|
|
|
return self.ifaces[iface_id]
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def get_ifaces(self, control: bool = True) -> List[CoreInterface]:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
Retrieve sorted list of interfaces, optionally do not include control
|
|
|
|
interfaces.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param control: False to exclude control interfaces, included otherwise
|
|
|
|
:return: list of interfaces
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
ifaces = []
|
|
|
|
for iface_id in sorted(self.ifaces):
|
|
|
|
iface = self.ifaces[iface_id]
|
2021-01-13 05:30:48 +00:00
|
|
|
if not control and iface.control:
|
2020-06-16 17:30:16 +01:00
|
|
|
continue
|
|
|
|
ifaces.append(iface)
|
|
|
|
return ifaces
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def get_iface_id(self, iface: CoreInterface) -> int:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
Retrieve id for an interface.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface: interface to get id for
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: interface index if found, -1 otherwise
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
for iface_id, local_iface in self.ifaces.items():
|
|
|
|
if local_iface is iface:
|
|
|
|
return iface_id
|
|
|
|
raise CoreError(f"node({self.name}) does not have interface({iface.name})")
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def next_iface_id(self) -> int:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create a new interface index.
|
|
|
|
|
|
|
|
:return: interface index
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
while self.iface_id in self.ifaces:
|
|
|
|
self.iface_id += 1
|
|
|
|
iface_id = self.iface_id
|
|
|
|
self.iface_id += 1
|
|
|
|
return iface_id
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-19 05:33:28 +01:00
|
|
|
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-16 22:18:19 +01:00
|
|
|
Build link data for this node.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
|
|
|
:param flags: message flags
|
|
|
|
:return: list of link data
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
class CoreNodeBase(NodeBase):
|
|
|
|
"""
|
|
|
|
Base class for CORE nodes.
|
|
|
|
"""
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
session: "Session",
|
|
|
|
_id: int = None,
|
|
|
|
name: str = None,
|
|
|
|
server: "DistributedServer" = None,
|
|
|
|
) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create a CoreNodeBase instance.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param session: CORE session object
|
|
|
|
:param _id: object id
|
|
|
|
:param name: object name
|
|
|
|
:param server: remote server node
|
2019-10-14 23:43:57 +01:00
|
|
|
will run on, default is None for localhost
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-14 17:37:58 +01:00
|
|
|
super().__init__(session, _id, name, server)
|
2020-05-25 08:16:58 +01:00
|
|
|
self.config_services: Dict[str, "ConfigService"] = {}
|
2021-03-20 00:01:22 +00:00
|
|
|
self.directory: Optional[Path] = None
|
2020-05-25 08:16:58 +01:00
|
|
|
self.tmpnodedir: bool = False
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2020-06-14 20:44:51 +01:00
|
|
|
def startup(self) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2020-06-14 20:44:51 +01:00
|
|
|
def shutdown(self) -> None:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2021-09-17 22:34:37 +01:00
|
|
|
def create_dir(self, dir_path: Path) -> None:
|
|
|
|
"""
|
|
|
|
Create a node private directory.
|
|
|
|
|
|
|
|
:param dir_path: path to create
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def create_file(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
2020-06-14 21:35:06 +01:00
|
|
|
"""
|
|
|
|
Create a node file with a given mode.
|
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
:param file_path: name of file to create
|
2020-06-14 21:35:06 +01:00
|
|
|
:param contents: contents of file
|
|
|
|
:param mode: mode for file
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2021-09-17 22:34:37 +01:00
|
|
|
@abc.abstractmethod
|
|
|
|
def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None:
|
|
|
|
"""
|
|
|
|
Copy source file to node host destination, updating the file mode when
|
|
|
|
provided.
|
|
|
|
|
|
|
|
:param src_path: source file to copy
|
|
|
|
:param dst_path: node host destination
|
|
|
|
:param mode: file mode
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2021-03-19 23:54:24 +00:00
|
|
|
def addfile(self, src_path: Path, file_path: Path) -> None:
|
2020-06-14 21:35:06 +01:00
|
|
|
"""
|
|
|
|
Add a file.
|
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
:param src_path: source file path
|
|
|
|
:param file_path: file name to add
|
2020-06-14 21:35:06 +01:00
|
|
|
:return: nothing
|
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
|
|
|
|
"""
|
|
|
|
Runs a command within a node container.
|
|
|
|
|
|
|
|
:param args: command to run
|
|
|
|
:param wait: True to wait for status, False otherwise
|
|
|
|
:param shell: True to use shell, False otherwise
|
|
|
|
:return: combined stdout and stderr
|
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
|
|
|
def termcmdstring(self, sh: str) -> str:
|
|
|
|
"""
|
|
|
|
Create a terminal command string.
|
|
|
|
|
|
|
|
:param sh: shell to execute command in
|
|
|
|
:return: str
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@abc.abstractmethod
|
2020-06-16 17:30:16 +01:00
|
|
|
def new_iface(
|
|
|
|
self, net: "CoreNetworkBase", iface_data: InterfaceData
|
2020-06-14 21:35:06 +01:00
|
|
|
) -> CoreInterface:
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
Create a new interface.
|
2020-06-14 21:35:06 +01:00
|
|
|
|
|
|
|
:param net: network to associate with
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_data: interface data for new interface
|
2020-06-14 21:35:06 +01:00
|
|
|
:return: interface index
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-09-09 18:27:06 +01:00
|
|
|
@abc.abstractmethod
|
|
|
|
def path_exists(self, path: str) -> bool:
|
|
|
|
"""
|
|
|
|
Determines if a file or directory path exists.
|
|
|
|
|
|
|
|
:param path: path to file or directory
|
|
|
|
:return: True if path exists, False otherwise
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
def host_path(self, path: Path, is_dir: bool = False) -> Path:
|
|
|
|
"""
|
|
|
|
Return the name of a node"s file on the host filesystem.
|
|
|
|
|
|
|
|
:param path: path to translate to host path
|
|
|
|
:param is_dir: True if path is a directory path, False otherwise
|
|
|
|
:return: path to file
|
|
|
|
"""
|
|
|
|
if is_dir:
|
|
|
|
directory = str(path).strip("/").replace("/", ".")
|
2021-03-20 00:01:22 +00:00
|
|
|
return self.directory / directory
|
2021-03-19 23:54:24 +00:00
|
|
|
else:
|
|
|
|
directory = str(path.parent).strip("/").replace("/", ".")
|
2021-03-20 00:01:22 +00:00
|
|
|
return self.directory / directory / path.name
|
2021-03-19 23:54:24 +00:00
|
|
|
|
2020-01-27 19:44:00 +00:00
|
|
|
def add_config_service(self, service_class: "ConfigServiceType") -> None:
|
|
|
|
"""
|
|
|
|
Adds a configuration service to the node.
|
|
|
|
|
|
|
|
:param service_class: configuration service class to assign to node
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2020-01-18 05:09:51 +00:00
|
|
|
name = service_class.name
|
|
|
|
if name in self.config_services:
|
|
|
|
raise CoreError(f"node({self.name}) already has service({name})")
|
|
|
|
self.config_services[name] = service_class(self)
|
|
|
|
|
|
|
|
def set_service_config(self, name: str, data: Dict[str, str]) -> None:
|
2020-01-27 19:44:00 +00:00
|
|
|
"""
|
|
|
|
Sets configuration service custom config data.
|
|
|
|
|
|
|
|
:param name: name of configuration service
|
|
|
|
:param data: custom config data to set
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2020-01-18 05:09:51 +00:00
|
|
|
service = self.config_services.get(name)
|
|
|
|
if service is None:
|
|
|
|
raise CoreError(f"node({self.name}) does not have service({name})")
|
|
|
|
service.set_config(data)
|
2020-01-18 00:57:49 +00:00
|
|
|
|
2020-01-23 21:22:47 +00:00
|
|
|
def start_config_services(self) -> None:
|
2020-01-27 19:44:00 +00:00
|
|
|
"""
|
2020-05-25 08:16:58 +01:00
|
|
|
Determines startup paths and starts configuration services, based on their
|
2020-01-27 19:44:00 +00:00
|
|
|
dependency chains.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
startup_paths = ConfigServiceDependencies(self.config_services).startup_paths()
|
|
|
|
for startup_path in startup_paths:
|
|
|
|
for service in startup_path:
|
2020-01-23 21:22:47 +00:00
|
|
|
service.start()
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def makenodedir(self) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create the node directory.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2021-03-20 00:01:22 +00:00
|
|
|
if self.directory is None:
|
|
|
|
self.directory = self.session.directory / f"{self.name}.conf"
|
|
|
|
self.host_cmd(f"mkdir -p {self.directory}")
|
2019-04-30 07:31:47 +01:00
|
|
|
self.tmpnodedir = True
|
|
|
|
else:
|
|
|
|
self.tmpnodedir = False
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def rmnodedir(self) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Remove the node directory, unless preserve directory has been set.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
preserve = self.session.options.get_config("preservedir") == "1"
|
|
|
|
if preserve:
|
|
|
|
return
|
|
|
|
if self.tmpnodedir:
|
2021-03-20 00:01:22 +00:00
|
|
|
self.host_cmd(f"rm -rf {self.directory}")
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def add_iface(self, iface: CoreInterface, iface_id: int) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Add network interface to node and set the network interface index if successful.
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface: network interface to add
|
|
|
|
:param iface_id: interface id
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
if iface_id in self.ifaces:
|
|
|
|
raise CoreError(f"interface({iface_id}) already exists")
|
|
|
|
self.ifaces[iface_id] = iface
|
|
|
|
iface.node_id = iface_id
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def delete_iface(self, iface_id: int) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Delete a network interface
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: interface index to delete
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
if iface_id not in self.ifaces:
|
|
|
|
raise CoreError(f"node({self.name}) interface({iface_id}) does not exist")
|
|
|
|
iface = self.ifaces.pop(iface_id)
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.info("node(%s) removing interface(%s)", self.name, iface.name)
|
2020-06-16 17:30:16 +01:00
|
|
|
iface.detachnet()
|
|
|
|
iface.shutdown()
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def attachnet(self, iface_id: int, net: "CoreNetworkBase") -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Attach a network.
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: interface of index to attach
|
2020-01-16 19:00:57 +00:00
|
|
|
:param net: network to attach
|
2019-09-17 17:33:55 +01:00
|
|
|
:return: nothing
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
iface = self.get_iface(iface_id)
|
|
|
|
iface.attachnet(net)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def detachnet(self, iface_id: int) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Detach network interface.
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: interface id to detach
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
iface = self.get_iface(iface_id)
|
|
|
|
iface.detachnet()
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def setposition(self, x: float = None, y: float = None, z: float = None) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Set position.
|
|
|
|
|
|
|
|
:param x: x position
|
|
|
|
:param y: y position
|
|
|
|
:param z: z position
|
|
|
|
:return: nothing
|
|
|
|
"""
|
2019-10-23 17:51:52 +01:00
|
|
|
changed = super().setposition(x, y, z)
|
2019-04-30 07:31:47 +01:00
|
|
|
if changed:
|
2020-06-16 17:30:16 +01:00
|
|
|
for iface in self.get_ifaces():
|
|
|
|
iface.setposition()
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def commonnets(
|
2020-05-21 08:20:05 +01:00
|
|
|
self, node: "CoreNodeBase", want_ctrl: bool = False
|
|
|
|
) -> List[Tuple["CoreNetworkBase", CoreInterface, CoreInterface]]:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Given another node or net object, return common networks between
|
|
|
|
this node and that object. A list of tuples is returned, with each tuple
|
|
|
|
consisting of (network, interface1, interface2).
|
|
|
|
|
2020-05-21 08:20:05 +01:00
|
|
|
:param node: node to get common network with
|
2019-04-30 07:31:47 +01:00
|
|
|
:param want_ctrl: flag set to determine if control network are wanted
|
|
|
|
:return: tuples of common networks
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
common = []
|
2020-06-16 17:30:16 +01:00
|
|
|
for iface1 in self.get_ifaces(control=want_ctrl):
|
|
|
|
for iface2 in node.get_ifaces():
|
|
|
|
if iface1.net == iface2.net:
|
|
|
|
common.append((iface1.net, iface1, iface2))
|
2019-04-30 07:31:47 +01:00
|
|
|
return common
|
|
|
|
|
|
|
|
|
|
|
|
class CoreNode(CoreNodeBase):
|
|
|
|
"""
|
|
|
|
Provides standard core node logic.
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
|
2020-06-14 17:37:58 +01:00
|
|
|
apitype: NodeTypes = NodeTypes.DEFAULT
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2019-09-10 23:10:24 +01:00
|
|
|
def __init__(
|
2019-10-05 01:33:44 +01:00
|
|
|
self,
|
2020-01-13 22:08:49 +00:00
|
|
|
session: "Session",
|
|
|
|
_id: int = None,
|
|
|
|
name: str = None,
|
2021-03-20 00:01:22 +00:00
|
|
|
directory: Path = None,
|
2020-01-13 22:08:49 +00:00
|
|
|
server: "DistributedServer" = None,
|
|
|
|
) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create a CoreNode instance.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param session: core session instance
|
|
|
|
:param _id: object id
|
|
|
|
:param name: object name
|
2021-03-20 00:01:22 +00:00
|
|
|
:param directory: node directory
|
2020-01-16 19:00:57 +00:00
|
|
|
:param server: remote server node
|
2019-10-14 23:43:57 +01:00
|
|
|
will run on, default is None for localhost
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-14 17:37:58 +01:00
|
|
|
super().__init__(session, _id, name, server)
|
2021-03-20 00:01:22 +00:00
|
|
|
self.directory: Optional[Path] = directory
|
2021-03-19 23:56:54 +00:00
|
|
|
self.ctrlchnlname: Path = self.session.directory / self.name
|
2020-05-25 08:16:58 +01:00
|
|
|
self.client: Optional[VnodeClient] = None
|
|
|
|
self.pid: Optional[int] = None
|
|
|
|
self.lock: RLock = RLock()
|
2021-03-19 23:54:24 +00:00
|
|
|
self._mounts: List[Tuple[Path, Path]] = []
|
2020-06-26 06:05:10 +01:00
|
|
|
self.node_net_client: LinuxNetClient = self.create_node_net_client(
|
|
|
|
self.session.use_ovs()
|
|
|
|
)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def create_node_net_client(self, use_ovs: bool) -> LinuxNetClient:
|
2019-10-14 22:28:18 +01:00
|
|
|
"""
|
2019-10-16 18:14:36 +01:00
|
|
|
Create node network client for running network commands within the nodes
|
|
|
|
container.
|
2019-10-14 22:28:18 +01:00
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param use_ovs: True for OVS bridges, False for Linux bridges
|
2020-01-13 22:08:49 +00:00
|
|
|
:return: node network client
|
2019-10-14 22:28:18 +01:00
|
|
|
"""
|
2019-10-19 07:28:09 +01:00
|
|
|
return get_net_client(use_ovs, self.cmd)
|
2019-10-14 22:28:18 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def alive(self) -> bool:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Check if the node is alive.
|
|
|
|
|
|
|
|
:return: True if node is alive, False otherwise
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
try:
|
2019-10-19 07:28:09 +01:00
|
|
|
self.host_cmd(f"kill -0 {self.pid}")
|
2019-10-06 00:10:01 +01:00
|
|
|
except CoreCommandError:
|
2019-04-30 07:31:47 +01:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def startup(self) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Start a new namespace node by invoking the vnoded process that
|
|
|
|
allocates a new namespace. Bring up the loopback device and set
|
|
|
|
the hostname.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
with self.lock:
|
|
|
|
self.makenodedir()
|
|
|
|
if self.up:
|
|
|
|
raise ValueError("starting a node that is already up")
|
|
|
|
|
|
|
|
# create a new namespace for this node using vnoded
|
2019-10-18 21:20:05 +01:00
|
|
|
vnoded = (
|
2020-06-23 17:35:11 +01:00
|
|
|
f"{VNODED} -v -c {self.ctrlchnlname} -l {self.ctrlchnlname}.log "
|
2019-10-18 21:20:05 +01:00
|
|
|
f"-p {self.ctrlchnlname}.pid"
|
|
|
|
)
|
2021-03-20 00:01:22 +00:00
|
|
|
if self.directory:
|
|
|
|
vnoded += f" -C {self.directory}"
|
2019-04-30 07:31:47 +01:00
|
|
|
env = self.session.get_environment(state=False)
|
|
|
|
env["NODE_NUMBER"] = str(self.id)
|
|
|
|
env["NODE_NAME"] = str(self.name)
|
|
|
|
|
2019-10-19 07:28:09 +01:00
|
|
|
output = self.host_cmd(vnoded, env=env)
|
2019-04-30 07:31:47 +01:00
|
|
|
self.pid = int(output)
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.debug("node(%s) pid: %s", self.name, self.pid)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
|
|
|
# create vnode client
|
2020-05-25 08:16:58 +01:00
|
|
|
self.client = VnodeClient(self.name, self.ctrlchnlname)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
|
|
|
# bring up the loopback interface
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.debug("bringing up loopback interface")
|
2019-10-01 20:14:37 +01:00
|
|
|
self.node_net_client.device_up("lo")
|
2019-04-30 07:31:47 +01:00
|
|
|
|
|
|
|
# set hostname for node
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.debug("setting hostname: %s", self.name)
|
2019-10-01 20:14:37 +01:00
|
|
|
self.node_net_client.set_hostname(self.name)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
|
|
|
# mark node as up
|
|
|
|
self.up = True
|
|
|
|
|
|
|
|
# create private directories
|
2021-03-19 23:54:24 +00:00
|
|
|
for dir_path in PRIVATE_DIRS:
|
2021-09-17 22:34:37 +01:00
|
|
|
self.create_dir(dir_path)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def shutdown(self) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Shutdown logic for simple lxc nodes.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
# nothing to do if node is not up
|
|
|
|
if not self.up:
|
|
|
|
return
|
|
|
|
with self.lock:
|
|
|
|
try:
|
|
|
|
# unmount all targets (NOTE: non-persistent mount namespaces are
|
|
|
|
# removed by the kernel when last referencing process is killed)
|
|
|
|
self._mounts = []
|
|
|
|
# shutdown all interfaces
|
2020-06-16 17:30:16 +01:00
|
|
|
for iface in self.get_ifaces():
|
|
|
|
iface.shutdown()
|
2019-10-06 00:10:01 +01:00
|
|
|
# kill node process if present
|
2019-04-30 07:31:47 +01:00
|
|
|
try:
|
2019-10-19 07:28:09 +01:00
|
|
|
self.host_cmd(f"kill -9 {self.pid}")
|
2019-10-06 00:10:01 +01:00
|
|
|
except CoreCommandError:
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.exception("error killing process")
|
2019-04-30 07:31:47 +01:00
|
|
|
# remove node directory if present
|
|
|
|
try:
|
2019-10-19 07:28:09 +01:00
|
|
|
self.host_cmd(f"rm -rf {self.ctrlchnlname}")
|
2019-10-06 00:10:01 +01:00
|
|
|
except CoreCommandError:
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.exception("error removing node directory")
|
2019-04-30 07:31:47 +01:00
|
|
|
# clear interface data, close client, and mark self and not up
|
2020-06-16 17:30:16 +01:00
|
|
|
self.ifaces.clear()
|
2019-04-30 07:31:47 +01:00
|
|
|
self.client.close()
|
|
|
|
self.up = False
|
|
|
|
except OSError:
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.exception("error during shutdown")
|
2019-04-30 07:31:47 +01:00
|
|
|
finally:
|
|
|
|
self.rmnodedir()
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
|
2019-07-03 00:05:45 +01:00
|
|
|
"""
|
2019-10-02 05:06:11 +01:00
|
|
|
Runs a command that is used to configure and setup the network within a
|
|
|
|
node.
|
2019-07-03 00:05:45 +01:00
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param args: command to run
|
|
|
|
:param wait: True to wait for status, False otherwise
|
|
|
|
:param shell: True to use shell, False otherwise
|
2019-07-03 00:05:45 +01:00
|
|
|
:return: combined stdout and stderr
|
2020-01-18 05:12:14 +00:00
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
2019-07-03 00:05:45 +01:00
|
|
|
"""
|
2019-10-05 01:33:44 +01:00
|
|
|
if self.server is None:
|
2019-12-20 17:57:34 +00:00
|
|
|
return self.client.check_cmd(args, wait=wait, shell=shell)
|
2019-10-05 01:33:44 +01:00
|
|
|
else:
|
2020-07-29 00:13:37 +01:00
|
|
|
args = self.client.create_cmd(args, shell)
|
2019-10-14 23:43:57 +01:00
|
|
|
return self.server.remote_cmd(args, wait=wait)
|
2019-07-03 00:05:45 +01:00
|
|
|
|
2020-09-09 18:27:06 +01:00
|
|
|
def path_exists(self, path: str) -> bool:
|
|
|
|
"""
|
|
|
|
Determines if a file or directory path exists.
|
|
|
|
|
|
|
|
:param path: path to file or directory
|
|
|
|
:return: True if path exists, False otherwise
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
self.cmd(f"{TEST} -e {path}")
|
|
|
|
return True
|
|
|
|
except CoreCommandError:
|
|
|
|
return False
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def termcmdstring(self, sh: str = "/bin/sh") -> str:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create a terminal command string.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param sh: shell to execute command in
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: str
|
|
|
|
"""
|
2019-10-15 22:13:42 +01:00
|
|
|
terminal = self.client.create_cmd(sh)
|
|
|
|
if self.server is None:
|
|
|
|
return terminal
|
|
|
|
else:
|
2019-10-18 20:55:35 +01:00
|
|
|
return f"ssh -X -f {self.server.host} xterm -e {terminal}"
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-09-17 22:34:37 +01:00
|
|
|
def create_dir(self, dir_path: Path) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2021-09-17 22:34:37 +01:00
|
|
|
Create a node private directory.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
:param dir_path: path to create
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2021-09-17 22:34:37 +01:00
|
|
|
if not dir_path.is_absolute():
|
2021-03-19 23:54:24 +00:00
|
|
|
raise CoreError(f"private directory path not fully qualified: {dir_path}")
|
2021-09-17 22:34:37 +01:00
|
|
|
logger.debug("node(%s) creating private directory: %s", self.name, dir_path)
|
|
|
|
parent_path = self._find_parent_path(dir_path)
|
|
|
|
if parent_path:
|
|
|
|
self.host_cmd(f"mkdir -p {parent_path}")
|
|
|
|
else:
|
|
|
|
host_path = self.host_path(dir_path, is_dir=True)
|
|
|
|
self.host_cmd(f"mkdir -p {host_path}")
|
|
|
|
self.mount(host_path, dir_path)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
def mount(self, src_path: Path, target_path: Path) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create and mount a directory.
|
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
:param src_path: source directory to mount
|
|
|
|
:param target_path: target directory to create
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
|
|
|
"""
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path)
|
2021-03-19 23:54:24 +00:00
|
|
|
self.cmd(f"mkdir -p {target_path}")
|
|
|
|
self.cmd(f"{MOUNT} -n --bind {src_path} {target_path}")
|
|
|
|
self._mounts.append((src_path, target_path))
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def next_iface_id(self) -> int:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Retrieve a new interface index.
|
|
|
|
|
|
|
|
:return: new interface index
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
with self.lock:
|
2020-06-16 17:30:16 +01:00
|
|
|
return super().next_iface_id()
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2022-01-08 00:03:45 +00:00
|
|
|
def newveth(self, iface_id: int = None, ifname: str = None, mtu: int = None) -> int:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create a new interface.
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: id for the new interface
|
2020-01-16 19:00:57 +00:00
|
|
|
:param ifname: name for the new interface
|
2021-12-21 16:59:48 +00:00
|
|
|
:param mtu: mtu for interface
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
with self.lock:
|
2022-01-08 00:03:45 +00:00
|
|
|
mtu = mtu if mtu is not None else DEFAULT_MTU
|
2021-12-21 16:59:48 +00:00
|
|
|
iface_id = iface_id if iface_id is not None else self.next_iface_id()
|
|
|
|
ifname = ifname if ifname is not None else f"eth{iface_id}"
|
2019-04-30 07:31:47 +01:00
|
|
|
sessionid = self.session.short_session_id()
|
|
|
|
try:
|
2020-06-16 17:30:16 +01:00
|
|
|
suffix = f"{self.id:x}.{iface_id}.{sessionid}"
|
2019-04-30 07:31:47 +01:00
|
|
|
except TypeError:
|
2020-06-16 17:30:16 +01:00
|
|
|
suffix = f"{self.id}.{iface_id}.{sessionid}"
|
2019-10-18 18:33:31 +01:00
|
|
|
localname = f"veth{suffix}"
|
2021-12-21 16:59:48 +00:00
|
|
|
name = f"{localname}p"
|
2022-01-08 00:03:45 +00:00
|
|
|
veth = Veth(self.session, name, localname, mtu, self.server, self)
|
|
|
|
veth.adopt_node(iface_id, ifname, self.up)
|
2020-06-16 17:30:16 +01:00
|
|
|
return iface_id
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def newtuntap(self, iface_id: int = None, ifname: str = None) -> int:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create a new tunnel tap.
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: interface id
|
2020-01-16 19:00:57 +00:00
|
|
|
:param ifname: interface name
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: interface index
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
with self.lock:
|
2021-12-21 16:59:48 +00:00
|
|
|
iface_id = iface_id if iface_id is not None else self.next_iface_id()
|
|
|
|
ifname = ifname if ifname is not None else f"eth{iface_id}"
|
2019-04-30 07:31:47 +01:00
|
|
|
sessionid = self.session.short_session_id()
|
2020-06-16 17:30:16 +01:00
|
|
|
localname = f"tap{self.id}.{iface_id}.{sessionid}"
|
2019-04-30 07:31:47 +01:00
|
|
|
name = ifname
|
2022-01-08 00:03:45 +00:00
|
|
|
tuntap = TunTap(self.session, name, localname, node=self)
|
|
|
|
if self.up:
|
|
|
|
tuntap.startup()
|
2019-04-30 07:31:47 +01:00
|
|
|
try:
|
2020-06-16 17:30:16 +01:00
|
|
|
self.add_iface(tuntap, iface_id)
|
2021-12-21 16:59:48 +00:00
|
|
|
except CoreError as e:
|
2019-04-30 07:31:47 +01:00
|
|
|
tuntap.shutdown()
|
|
|
|
raise e
|
2020-06-16 17:30:16 +01:00
|
|
|
return iface_id
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-17 07:25:26 +01:00
|
|
|
def set_mac(self, iface_id: int, mac: str) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
Set hardware address for an interface.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: id of interface to set hardware address for
|
2020-06-17 07:25:26 +01:00
|
|
|
:param mac: mac address to set
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
iface = self.get_iface(iface_id)
|
2020-06-17 07:25:26 +01:00
|
|
|
iface.set_mac(mac)
|
2019-04-30 07:31:47 +01:00
|
|
|
if self.up:
|
2020-06-23 17:11:37 +01:00
|
|
|
self.node_net_client.device_mac(iface.name, str(iface.mac))
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-19 18:54:58 +01:00
|
|
|
def add_ip(self, iface_id: int, ip: str) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-19 18:54:58 +01:00
|
|
|
Add an ip address to an interface in the format "10.0.0.1/24".
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: id of interface to add address to
|
2020-06-19 18:54:58 +01:00
|
|
|
:param ip: address to add to interface
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
2020-06-19 18:54:58 +01:00
|
|
|
:raises CoreError: when ip address provided is invalid
|
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
iface = self.get_iface(iface_id)
|
2020-06-19 18:54:58 +01:00
|
|
|
iface.add_ip(ip)
|
2019-04-30 07:31:47 +01:00
|
|
|
if self.up:
|
2020-01-08 21:25:00 +00:00
|
|
|
# ipv4 check
|
2019-10-01 20:14:37 +01:00
|
|
|
broadcast = None
|
2020-06-19 18:54:58 +01:00
|
|
|
if netaddr.valid_ipv4(ip):
|
2019-10-01 20:14:37 +01:00
|
|
|
broadcast = "+"
|
2020-06-19 18:54:58 +01:00
|
|
|
self.node_net_client.create_address(iface.name, ip, broadcast)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-19 18:54:58 +01:00
|
|
|
def remove_ip(self, iface_id: int, ip: str) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-19 18:54:58 +01:00
|
|
|
Remove an ip address from an interface in the format "10.0.0.1/24".
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: id of interface to delete address from
|
2020-06-19 18:54:58 +01:00
|
|
|
:param ip: ip address to remove from interface
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
2020-06-19 18:54:58 +01:00
|
|
|
:raises CoreError: when ip address provided is invalid
|
2019-04-30 07:31:47 +01:00
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
iface = self.get_iface(iface_id)
|
2020-06-19 18:54:58 +01:00
|
|
|
iface.remove_ip(ip)
|
2019-04-30 07:31:47 +01:00
|
|
|
if self.up:
|
2020-06-19 18:54:58 +01:00
|
|
|
self.node_net_client.delete_address(iface.name, ip)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def ifup(self, iface_id: int) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Bring an interface up.
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_id: index of interface to bring up
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
if self.up:
|
2020-06-16 17:30:16 +01:00
|
|
|
iface = self.get_iface(iface_id)
|
|
|
|
self.node_net_client.device_up(iface.name)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def new_iface(
|
|
|
|
self, net: "CoreNetworkBase", iface_data: InterfaceData
|
2020-06-11 21:59:29 +01:00
|
|
|
) -> CoreInterface:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Create a new network interface.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param net: network to associate with
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface_data: interface data for new interface
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: interface index
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
with self.lock:
|
2020-07-01 22:40:19 +01:00
|
|
|
if net.has_custom_iface:
|
|
|
|
return net.custom_iface(self, iface_data)
|
2019-04-30 07:31:47 +01:00
|
|
|
else:
|
2020-12-15 17:34:42 +00:00
|
|
|
iface_id = iface_data.id
|
|
|
|
if iface_id is not None and iface_id in self.ifaces:
|
|
|
|
raise CoreError(
|
|
|
|
f"node({self.name}) already has interface({iface_id})"
|
|
|
|
)
|
2021-12-21 16:59:48 +00:00
|
|
|
iface_id = self.newveth(iface_id, iface_data.name, iface_data.mtu)
|
2020-06-16 17:30:16 +01:00
|
|
|
self.attachnet(iface_id, net)
|
|
|
|
if iface_data.mac:
|
2020-06-17 07:25:26 +01:00
|
|
|
self.set_mac(iface_id, iface_data.mac)
|
2020-07-01 22:40:19 +01:00
|
|
|
for ip in iface_data.get_ips():
|
2020-06-19 19:11:45 +01:00
|
|
|
self.add_ip(iface_id, ip)
|
2020-06-16 17:30:16 +01:00
|
|
|
self.ifup(iface_id)
|
2020-07-01 22:40:19 +01:00
|
|
|
return self.get_iface(iface_id)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
def addfile(self, src_path: Path, file_path: Path) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Add a file.
|
|
|
|
|
2021-03-19 23:54:24 +00:00
|
|
|
:param src_path: source file path
|
|
|
|
:param file_path: file name to add
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
:raises CoreCommandError: when a non-zero exit status occurs
|
|
|
|
"""
|
2021-04-22 05:09:35 +01:00
|
|
|
logger.info("adding file from %s to %s", src_path, file_path)
|
2021-03-19 23:54:24 +00:00
|
|
|
directory = file_path.parent
|
2019-10-06 08:06:29 +01:00
|
|
|
if self.server is None:
|
2019-10-18 18:33:31 +01:00
|
|
|
self.client.check_cmd(f"mkdir -p {directory}")
|
2021-03-19 23:54:24 +00:00
|
|
|
self.client.check_cmd(f"mv {src_path} {file_path}")
|
2019-10-12 00:36:57 +01:00
|
|
|
self.client.check_cmd("sync")
|
2019-10-06 08:06:29 +01:00
|
|
|
else:
|
2019-10-19 07:28:09 +01:00
|
|
|
self.host_cmd(f"mkdir -p {directory}")
|
2021-03-19 23:54:24 +00:00
|
|
|
self.server.remote_put(src_path, file_path)
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-09-17 22:34:37 +01:00
|
|
|
def _find_parent_path(self, path: Path) -> Optional[Path]:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2021-09-17 22:34:37 +01:00
|
|
|
Check if there is an existing mounted parent directory created for this node.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-09-17 22:34:37 +01:00
|
|
|
:param path: existing parent path to use
|
|
|
|
:return: exist parent path if exists, None otherwise
|
|
|
|
"""
|
|
|
|
logger.debug("looking for existing parent: %s", path)
|
|
|
|
existing_path = None
|
|
|
|
for parent in path.parents:
|
|
|
|
node_path = self.host_path(parent, is_dir=True)
|
|
|
|
if node_path == self.directory:
|
|
|
|
break
|
|
|
|
if self.path_exists(str(node_path)):
|
|
|
|
relative_path = path.relative_to(parent)
|
|
|
|
existing_path = node_path / relative_path
|
|
|
|
break
|
|
|
|
return existing_path
|
|
|
|
|
|
|
|
def create_file(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
|
|
|
"""
|
|
|
|
Create file within a node at the given path, using contents and mode.
|
|
|
|
|
|
|
|
:param file_path: desired path for file
|
2020-01-16 19:00:57 +00:00
|
|
|
:param contents: contents of file
|
2021-09-17 22:34:37 +01:00
|
|
|
:param mode: mode to create file with
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2021-09-17 22:34:37 +01:00
|
|
|
logger.debug("node(%s) create file(%s) mode(%o)", self.name, file_path, mode)
|
|
|
|
host_path = self._find_parent_path(file_path)
|
|
|
|
if host_path:
|
|
|
|
self.host_cmd(f"mkdir -p {host_path.parent}")
|
|
|
|
else:
|
|
|
|
host_path = self.host_path(file_path)
|
2021-03-19 23:54:24 +00:00
|
|
|
directory = host_path.parent
|
2019-10-06 08:06:29 +01:00
|
|
|
if self.server is None:
|
2021-03-19 23:54:24 +00:00
|
|
|
if not directory.exists():
|
|
|
|
directory.mkdir(parents=True, mode=0o755)
|
|
|
|
with host_path.open("w") as f:
|
|
|
|
f.write(contents)
|
|
|
|
host_path.chmod(mode)
|
2019-10-06 08:06:29 +01:00
|
|
|
else:
|
2021-03-19 23:54:24 +00:00
|
|
|
self.host_cmd(f"mkdir -m {0o755:o} -p {directory}")
|
|
|
|
self.server.remote_put_temp(host_path, contents)
|
|
|
|
self.host_cmd(f"chmod {mode:o} {host_path}")
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-09-17 22:34:37 +01:00
|
|
|
def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2021-09-17 22:34:37 +01:00
|
|
|
Copy source file to node host destination, updating the file mode when
|
|
|
|
provided.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-09-17 22:34:37 +01:00
|
|
|
:param src_path: source file to copy
|
|
|
|
:param dst_path: node host destination
|
|
|
|
:param mode: file mode
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2021-09-17 22:34:37 +01:00
|
|
|
logger.debug(
|
|
|
|
"node(%s) copying file src(%s) to dst(%s) mode(%o)",
|
|
|
|
self.name,
|
|
|
|
src_path,
|
|
|
|
dst_path,
|
|
|
|
mode or 0,
|
|
|
|
)
|
|
|
|
host_path = self._find_parent_path(dst_path)
|
|
|
|
if host_path:
|
|
|
|
self.host_cmd(f"mkdir -p {host_path.parent}")
|
|
|
|
else:
|
|
|
|
host_path = self.host_path(dst_path)
|
2019-10-06 08:06:29 +01:00
|
|
|
if self.server is None:
|
2021-03-19 23:54:24 +00:00
|
|
|
shutil.copy2(src_path, host_path)
|
2019-10-06 08:06:29 +01:00
|
|
|
else:
|
2021-03-19 23:54:24 +00:00
|
|
|
self.server.remote_put(src_path, host_path)
|
2019-10-14 23:43:57 +01:00
|
|
|
if mode is not None:
|
2021-03-19 23:54:24 +00:00
|
|
|
self.host_cmd(f"chmod {mode:o} {host_path}")
|
2019-04-30 07:31:47 +01:00
|
|
|
|
|
|
|
|
|
|
|
class CoreNetworkBase(NodeBase):
|
|
|
|
"""
|
|
|
|
Base class for networks
|
|
|
|
"""
|
2019-09-10 23:10:24 +01:00
|
|
|
|
2020-06-14 17:37:58 +01:00
|
|
|
linktype: LinkTypes = LinkTypes.WIRED
|
2020-07-01 22:40:19 +01:00
|
|
|
has_custom_iface: bool = False
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def __init__(
|
|
|
|
self,
|
|
|
|
session: "Session",
|
|
|
|
_id: int,
|
|
|
|
name: str,
|
|
|
|
server: "DistributedServer" = None,
|
|
|
|
) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2019-06-07 16:59:16 +01:00
|
|
|
Create a CoreNetworkBase instance.
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2021-12-21 16:59:48 +00:00
|
|
|
:param session: session object
|
2020-01-16 19:00:57 +00:00
|
|
|
:param _id: object id
|
|
|
|
:param name: object name
|
|
|
|
:param server: remote server node
|
2019-10-14 23:43:57 +01:00
|
|
|
will run on, default is None for localhost
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
2020-06-14 17:37:58 +01:00
|
|
|
super().__init__(session, _id, name, server)
|
2021-12-21 16:59:48 +00:00
|
|
|
self.mtu: int = DEFAULT_MTU
|
2021-05-10 23:07:42 +01:00
|
|
|
self.brname: Optional[str] = None
|
2021-05-10 23:18:15 +01:00
|
|
|
self.linked: Dict[CoreInterface, Dict[CoreInterface, bool]] = {}
|
|
|
|
self.linked_lock: threading.Lock = threading.Lock()
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2020-01-13 22:08:49 +00:00
|
|
|
def startup(self) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Each object implements its own startup method.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2020-01-13 22:08:49 +00:00
|
|
|
def shutdown(self) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Each object implements its own shutdown method.
|
|
|
|
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-14 21:35:06 +01:00
|
|
|
@abc.abstractmethod
|
2020-01-13 22:08:49 +00:00
|
|
|
def linknet(self, net: "CoreNetworkBase") -> CoreInterface:
|
2020-01-11 06:37:19 +00:00
|
|
|
"""
|
|
|
|
Link network to another.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param net: network to link with
|
2020-01-11 06:37:19 +00:00
|
|
|
:return: created interface
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2020-06-14 21:35:06 +01:00
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-07-01 22:40:19 +01:00
|
|
|
def custom_iface(self, node: CoreNode, iface_data: InterfaceData) -> CoreInterface:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def get_linked_iface(self, net: "CoreNetworkBase") -> Optional[CoreInterface]:
|
2020-01-11 06:37:19 +00:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
Return the interface that links this net with another net.
|
2020-01-11 06:37:19 +00:00
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param net: interface to get link for
|
2020-01-11 06:37:19 +00:00
|
|
|
:return: interface the provided network is linked to
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
for iface in self.get_ifaces():
|
|
|
|
if iface.othernet == net:
|
|
|
|
return iface
|
2020-01-11 06:37:19 +00:00
|
|
|
return None
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def attach(self, iface: CoreInterface) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Attach network interface.
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface: network interface to attach
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
i = self.next_iface_id()
|
|
|
|
self.ifaces[i] = iface
|
|
|
|
iface.net_id = i
|
2021-05-10 23:18:15 +01:00
|
|
|
with self.linked_lock:
|
|
|
|
self.linked[iface] = {}
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
def detach(self, iface: CoreInterface) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Detach network interface.
|
|
|
|
|
2020-06-16 17:30:16 +01:00
|
|
|
:param iface: network interface to detach
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: nothing
|
|
|
|
"""
|
2020-06-16 17:30:16 +01:00
|
|
|
del self.ifaces[iface.net_id]
|
|
|
|
iface.net_id = None
|
2021-05-10 23:18:15 +01:00
|
|
|
with self.linked_lock:
|
|
|
|
del self.linked[iface]
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-06-19 05:33:28 +01:00
|
|
|
def links(self, flags: MessageFlags = MessageFlags.NONE) -> List[LinkData]:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Build link data objects for this network. Each link object describes a link
|
|
|
|
between this network and a node.
|
2019-09-13 19:07:04 +01:00
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param flags: message type
|
2019-09-13 19:07:04 +01:00
|
|
|
:return: list of link data
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
all_links = []
|
|
|
|
# build a link message from this network node to each node having a
|
|
|
|
# connected interface
|
2020-06-16 17:30:16 +01:00
|
|
|
for iface in self.get_ifaces():
|
2022-01-26 05:39:52 +00:00
|
|
|
unidirectional = 0
|
2020-06-16 17:30:16 +01:00
|
|
|
linked_node = iface.node
|
2019-04-30 07:31:47 +01:00
|
|
|
if linked_node is None:
|
2022-01-26 19:19:30 +00:00
|
|
|
# two layer-2 switches/hubs linked together
|
2020-06-16 17:30:16 +01:00
|
|
|
if not iface.othernet:
|
2019-04-30 07:31:47 +01:00
|
|
|
continue
|
2020-06-16 17:30:16 +01:00
|
|
|
linked_node = iface.othernet
|
2019-04-30 07:31:47 +01:00
|
|
|
if linked_node.id == self.id:
|
|
|
|
continue
|
2022-01-26 05:39:52 +00:00
|
|
|
if iface.local_options != iface.options:
|
|
|
|
unidirectional = 1
|
2022-01-26 19:35:23 +00:00
|
|
|
iface_data = iface.get_data()
|
2019-04-30 07:31:47 +01:00
|
|
|
link_data = LinkData(
|
|
|
|
message_type=flags,
|
2020-06-17 05:53:12 +01:00
|
|
|
type=self.linktype,
|
2019-04-30 07:31:47 +01:00
|
|
|
node1_id=self.id,
|
|
|
|
node2_id=linked_node.id,
|
2022-01-26 05:39:52 +00:00
|
|
|
iface2=iface_data,
|
|
|
|
options=iface.local_options,
|
2019-04-30 07:31:47 +01:00
|
|
|
)
|
2022-01-26 05:39:52 +00:00
|
|
|
link_data.options.unidirectional = unidirectional
|
2019-04-30 07:31:47 +01:00
|
|
|
all_links.append(link_data)
|
2022-01-26 05:39:52 +00:00
|
|
|
if unidirectional:
|
|
|
|
link_data = LinkData(
|
|
|
|
message_type=MessageFlags.NONE,
|
|
|
|
type=self.linktype,
|
|
|
|
node1_id=linked_node.id,
|
|
|
|
node2_id=self.id,
|
|
|
|
options=iface.options,
|
|
|
|
)
|
|
|
|
link_data.options.unidirectional = unidirectional
|
|
|
|
all_links.append(link_data)
|
2019-04-30 07:31:47 +01:00
|
|
|
return all_links
|
|
|
|
|
|
|
|
|
2019-10-23 17:31:07 +01:00
|
|
|
class Position:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Helper class for Cartesian coordinate position
|
|
|
|
"""
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def __init__(self, x: float = None, y: float = None, z: float = None) -> None:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Creates a Position instance.
|
|
|
|
|
|
|
|
:param x: x position
|
|
|
|
:param y: y position
|
|
|
|
:param z: z position
|
|
|
|
"""
|
2020-05-25 08:16:58 +01:00
|
|
|
self.x: float = x
|
|
|
|
self.y: float = y
|
|
|
|
self.z: float = z
|
|
|
|
self.lon: Optional[float] = None
|
|
|
|
self.lat: Optional[float] = None
|
|
|
|
self.alt: Optional[float] = None
|
2019-04-30 07:31:47 +01:00
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def set(self, x: float = None, y: float = None, z: float = None) -> bool:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Returns True if the position has actually changed.
|
|
|
|
|
2020-01-16 19:00:57 +00:00
|
|
|
:param x: x position
|
|
|
|
:param y: y position
|
|
|
|
:param z: z position
|
2019-04-30 07:31:47 +01:00
|
|
|
:return: True if position changed, False otherwise
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
if self.x == x and self.y == y and self.z == z:
|
|
|
|
return False
|
|
|
|
self.x = x
|
|
|
|
self.y = y
|
|
|
|
self.z = z
|
|
|
|
return True
|
|
|
|
|
2020-01-13 22:08:49 +00:00
|
|
|
def get(self) -> Tuple[float, float, float]:
|
2019-04-30 07:31:47 +01:00
|
|
|
"""
|
|
|
|
Retrieve x,y,z position.
|
|
|
|
|
|
|
|
:return: x,y,z position tuple
|
2020-01-17 00:12:01 +00:00
|
|
|
"""
|
2019-04-30 07:31:47 +01:00
|
|
|
return self.x, self.y, self.z
|
2020-03-19 23:40:43 +00:00
|
|
|
|
|
|
|
def set_geo(self, lon: float, lat: float, alt: float) -> None:
|
|
|
|
"""
|
|
|
|
Set geo position lon, lat, alt.
|
|
|
|
|
|
|
|
:param lon: longitude value
|
|
|
|
:param lat: latitude value
|
|
|
|
:param alt: altitude value
|
|
|
|
:return: nothing
|
|
|
|
"""
|
|
|
|
self.lon = lon
|
|
|
|
self.lat = lat
|
|
|
|
self.alt = alt
|
|
|
|
|
|
|
|
def get_geo(self) -> Tuple[float, float, float]:
|
|
|
|
"""
|
|
|
|
Retrieve current geo position lon, lat, alt.
|
|
|
|
|
|
|
|
:return: lon, lat, alt position tuple
|
|
|
|
"""
|
|
|
|
return self.lon, self.lat, self.alt
|