daemon: refactoring to remove usage of os.path where possible and pathlib.Path instead
This commit is contained in:
parent
d0a55dd471
commit
1c970bbe00
38 changed files with 520 additions and 606 deletions
|
@ -3,9 +3,9 @@ Defines the base logic for nodes used within core.
|
|||
"""
|
||||
import abc
|
||||
import logging
|
||||
import os
|
||||
import shutil
|
||||
import threading
|
||||
from pathlib import Path
|
||||
from threading import RLock
|
||||
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
|
||||
|
||||
|
@ -30,6 +30,8 @@ if TYPE_CHECKING:
|
|||
CoreServices = List[Union[CoreService, Type[CoreService]]]
|
||||
ConfigServiceType = Type[ConfigService]
|
||||
|
||||
PRIVATE_DIRS: List[Path] = [Path("/var/run"), Path("/var/log")]
|
||||
|
||||
|
||||
class NodeBase(abc.ABC):
|
||||
"""
|
||||
|
@ -97,7 +99,7 @@ class NodeBase(abc.ABC):
|
|||
self,
|
||||
args: str,
|
||||
env: Dict[str, str] = None,
|
||||
cwd: str = None,
|
||||
cwd: Path = None,
|
||||
wait: bool = True,
|
||||
shell: bool = False,
|
||||
) -> str:
|
||||
|
@ -221,7 +223,7 @@ class CoreNodeBase(NodeBase):
|
|||
"""
|
||||
super().__init__(session, _id, name, server)
|
||||
self.config_services: Dict[str, "ConfigService"] = {}
|
||||
self.nodedir: Optional[str] = None
|
||||
self.nodedir: Optional[Path] = None
|
||||
self.tmpnodedir: bool = False
|
||||
|
||||
@abc.abstractmethod
|
||||
|
@ -233,11 +235,11 @@ class CoreNodeBase(NodeBase):
|
|||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
|
||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||
"""
|
||||
Create a node file with a given mode.
|
||||
|
||||
:param filename: name of file to create
|
||||
:param file_path: name of file to create
|
||||
:param contents: contents of file
|
||||
:param mode: mode for file
|
||||
:return: nothing
|
||||
|
@ -245,12 +247,12 @@ class CoreNodeBase(NodeBase):
|
|||
raise NotImplementedError
|
||||
|
||||
@abc.abstractmethod
|
||||
def addfile(self, srcname: str, filename: str) -> None:
|
||||
def addfile(self, src_path: Path, file_path: Path) -> None:
|
||||
"""
|
||||
Add a file.
|
||||
|
||||
:param srcname: source file name
|
||||
:param filename: file name to add
|
||||
:param src_path: source file path
|
||||
:param file_path: file name to add
|
||||
:return: nothing
|
||||
:raises CoreCommandError: when a non-zero exit status occurs
|
||||
"""
|
||||
|
@ -302,6 +304,21 @@ class CoreNodeBase(NodeBase):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
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("/", ".")
|
||||
return self.nodedir / directory
|
||||
else:
|
||||
directory = str(path.parent).strip("/").replace("/", ".")
|
||||
return self.nodedir / directory / path.name
|
||||
|
||||
def add_config_service(self, service_class: "ConfigServiceType") -> None:
|
||||
"""
|
||||
Adds a configuration service to the node.
|
||||
|
@ -346,7 +363,7 @@ class CoreNodeBase(NodeBase):
|
|||
:return: nothing
|
||||
"""
|
||||
if self.nodedir is None:
|
||||
self.nodedir = os.path.join(self.session.session_dir, self.name + ".conf")
|
||||
self.nodedir = self.session.session_dir / f"{self.name}.conf"
|
||||
self.host_cmd(f"mkdir -p {self.nodedir}")
|
||||
self.tmpnodedir = True
|
||||
else:
|
||||
|
@ -458,7 +475,7 @@ class CoreNode(CoreNodeBase):
|
|||
session: "Session",
|
||||
_id: int = None,
|
||||
name: str = None,
|
||||
nodedir: str = None,
|
||||
nodedir: Path = None,
|
||||
server: "DistributedServer" = None,
|
||||
) -> None:
|
||||
"""
|
||||
|
@ -472,14 +489,12 @@ class CoreNode(CoreNodeBase):
|
|||
will run on, default is None for localhost
|
||||
"""
|
||||
super().__init__(session, _id, name, server)
|
||||
self.nodedir: Optional[str] = nodedir
|
||||
self.ctrlchnlname: str = os.path.abspath(
|
||||
os.path.join(self.session.session_dir, self.name)
|
||||
)
|
||||
self.nodedir: Optional[Path] = nodedir
|
||||
self.ctrlchnlname: Path = self.session.session_dir / self.name
|
||||
self.client: Optional[VnodeClient] = None
|
||||
self.pid: Optional[int] = None
|
||||
self.lock: RLock = RLock()
|
||||
self._mounts: List[Tuple[str, str]] = []
|
||||
self._mounts: List[Tuple[Path, Path]] = []
|
||||
self.node_net_client: LinuxNetClient = self.create_node_net_client(
|
||||
self.session.use_ovs()
|
||||
)
|
||||
|
@ -549,8 +564,8 @@ class CoreNode(CoreNodeBase):
|
|||
self.up = True
|
||||
|
||||
# create private directories
|
||||
self.privatedir("/var/run")
|
||||
self.privatedir("/var/log")
|
||||
for dir_path in PRIVATE_DIRS:
|
||||
self.privatedir(dir_path)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
|
@ -561,29 +576,24 @@ class CoreNode(CoreNodeBase):
|
|||
# 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
|
||||
for iface in self.get_ifaces():
|
||||
iface.shutdown()
|
||||
|
||||
# kill node process if present
|
||||
try:
|
||||
self.host_cmd(f"kill -9 {self.pid}")
|
||||
except CoreCommandError:
|
||||
logging.exception("error killing process")
|
||||
|
||||
# remove node directory if present
|
||||
try:
|
||||
self.host_cmd(f"rm -rf {self.ctrlchnlname}")
|
||||
except CoreCommandError:
|
||||
logging.exception("error removing node directory")
|
||||
|
||||
# clear interface data, close client, and mark self and not up
|
||||
self.ifaces.clear()
|
||||
self.client.close()
|
||||
|
@ -636,35 +646,32 @@ class CoreNode(CoreNodeBase):
|
|||
else:
|
||||
return f"ssh -X -f {self.server.host} xterm -e {terminal}"
|
||||
|
||||
def privatedir(self, path: str) -> None:
|
||||
def privatedir(self, dir_path: Path) -> None:
|
||||
"""
|
||||
Create a private directory.
|
||||
|
||||
:param path: path to create
|
||||
:param dir_path: path to create
|
||||
:return: nothing
|
||||
"""
|
||||
if path[0] != "/":
|
||||
raise ValueError(f"path not fully qualified: {path}")
|
||||
hostpath = os.path.join(
|
||||
self.nodedir, os.path.normpath(path).strip("/").replace("/", ".")
|
||||
)
|
||||
self.host_cmd(f"mkdir -p {hostpath}")
|
||||
self.mount(hostpath, path)
|
||||
if not str(dir_path).startswith("/"):
|
||||
raise CoreError(f"private directory path not fully qualified: {dir_path}")
|
||||
host_path = self.host_path(dir_path, is_dir=True)
|
||||
self.host_cmd(f"mkdir -p {host_path}")
|
||||
self.mount(host_path, dir_path)
|
||||
|
||||
def mount(self, source: str, target: str) -> None:
|
||||
def mount(self, src_path: Path, target_path: Path) -> None:
|
||||
"""
|
||||
Create and mount a directory.
|
||||
|
||||
:param source: source directory to mount
|
||||
:param target: target directory to create
|
||||
:param src_path: source directory to mount
|
||||
:param target_path: target directory to create
|
||||
:return: nothing
|
||||
:raises CoreCommandError: when a non-zero exit status occurs
|
||||
"""
|
||||
source = os.path.abspath(source)
|
||||
logging.debug("node(%s) mounting: %s at %s", self.name, source, target)
|
||||
self.cmd(f"mkdir -p {target}")
|
||||
self.cmd(f"{MOUNT} -n --bind {source} {target}")
|
||||
self._mounts.append((source, target))
|
||||
logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path)
|
||||
self.cmd(f"mkdir -p {target_path}")
|
||||
self.cmd(f"{MOUNT} -n --bind {src_path} {target_path}")
|
||||
self._mounts.append((src_path, target_path))
|
||||
|
||||
def next_iface_id(self) -> int:
|
||||
"""
|
||||
|
@ -851,86 +858,66 @@ class CoreNode(CoreNodeBase):
|
|||
self.ifup(iface_id)
|
||||
return self.get_iface(iface_id)
|
||||
|
||||
def addfile(self, srcname: str, filename: str) -> None:
|
||||
def addfile(self, src_path: Path, file_path: Path) -> None:
|
||||
"""
|
||||
Add a file.
|
||||
|
||||
:param srcname: source file name
|
||||
:param filename: file name to add
|
||||
:param src_path: source file path
|
||||
:param file_path: file name to add
|
||||
:return: nothing
|
||||
:raises CoreCommandError: when a non-zero exit status occurs
|
||||
"""
|
||||
logging.info("adding file from %s to %s", srcname, filename)
|
||||
directory = os.path.dirname(filename)
|
||||
logging.info("adding file from %s to %s", src_path, file_path)
|
||||
directory = file_path.parent
|
||||
if self.server is None:
|
||||
self.client.check_cmd(f"mkdir -p {directory}")
|
||||
self.client.check_cmd(f"mv {srcname} {filename}")
|
||||
self.client.check_cmd(f"mv {src_path} {file_path}")
|
||||
self.client.check_cmd("sync")
|
||||
else:
|
||||
self.host_cmd(f"mkdir -p {directory}")
|
||||
self.server.remote_put(srcname, filename)
|
||||
self.server.remote_put(src_path, file_path)
|
||||
|
||||
def hostfilename(self, filename: str) -> str:
|
||||
"""
|
||||
Return the name of a node"s file on the host filesystem.
|
||||
|
||||
:param filename: host file name
|
||||
:return: path to file
|
||||
"""
|
||||
dirname, basename = os.path.split(filename)
|
||||
if not basename:
|
||||
raise ValueError(f"no basename for filename: {filename}")
|
||||
if dirname and dirname[0] == "/":
|
||||
dirname = dirname[1:]
|
||||
dirname = dirname.replace("/", ".")
|
||||
dirname = os.path.join(self.nodedir, dirname)
|
||||
return os.path.join(dirname, basename)
|
||||
|
||||
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
|
||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||
"""
|
||||
Create a node file with a given mode.
|
||||
|
||||
:param filename: name of file to create
|
||||
:param file_path: name of file to create
|
||||
:param contents: contents of file
|
||||
:param mode: mode for file
|
||||
:return: nothing
|
||||
"""
|
||||
hostfilename = self.hostfilename(filename)
|
||||
dirname, _basename = os.path.split(hostfilename)
|
||||
host_path = self.host_path(file_path)
|
||||
directory = host_path.parent
|
||||
if self.server is None:
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname, mode=0o755)
|
||||
with open(hostfilename, "w") as open_file:
|
||||
open_file.write(contents)
|
||||
os.chmod(open_file.name, mode)
|
||||
if not directory.exists():
|
||||
directory.mkdir(parents=True, mode=0o755)
|
||||
with host_path.open("w") as f:
|
||||
f.write(contents)
|
||||
host_path.chmod(mode)
|
||||
else:
|
||||
self.host_cmd(f"mkdir -m {0o755:o} -p {dirname}")
|
||||
self.server.remote_put_temp(hostfilename, contents)
|
||||
self.host_cmd(f"chmod {mode:o} {hostfilename}")
|
||||
logging.debug(
|
||||
"node(%s) added file: %s; mode: 0%o", self.name, hostfilename, mode
|
||||
)
|
||||
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}")
|
||||
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, host_path, mode)
|
||||
|
||||
def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None:
|
||||
def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None:
|
||||
"""
|
||||
Copy a file to a node, following symlinks and preserving metadata.
|
||||
Change file mode if specified.
|
||||
|
||||
:param filename: file name to copy file to
|
||||
:param srcfilename: file to copy
|
||||
:param file_path: file name to copy file to
|
||||
:param src_path: file to copy
|
||||
:param mode: mode to copy to
|
||||
:return: nothing
|
||||
"""
|
||||
hostfilename = self.hostfilename(filename)
|
||||
host_path = self.host_path(file_path)
|
||||
if self.server is None:
|
||||
shutil.copy2(srcfilename, hostfilename)
|
||||
shutil.copy2(src_path, host_path)
|
||||
else:
|
||||
self.server.remote_put(srcfilename, hostfilename)
|
||||
self.server.remote_put(src_path, host_path)
|
||||
if mode is not None:
|
||||
self.host_cmd(f"chmod {mode:o} {hostfilename}")
|
||||
logging.info(
|
||||
"node(%s) copied file: %s; mode: %s", self.name, hostfilename, mode
|
||||
)
|
||||
self.host_cmd(f"chmod {mode:o} {host_path}")
|
||||
logging.info("node(%s) copied file: %s; mode: %s", self.name, host_path, mode)
|
||||
|
||||
|
||||
class CoreNetworkBase(NodeBase):
|
||||
|
|
|
@ -3,6 +3,7 @@ client.py: implementation of the VnodeClient class for issuing commands
|
|||
over a control channel to the vnoded process running in a network namespace.
|
||||
The control channel can be accessed via calls using the vcmd shell.
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
from core import utils
|
||||
from core.executables import BASH, VCMD
|
||||
|
@ -13,7 +14,7 @@ class VnodeClient:
|
|||
Provides client functionality for interacting with a virtual node.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, ctrlchnlname: str) -> None:
|
||||
def __init__(self, name: str, ctrlchnlname: Path) -> None:
|
||||
"""
|
||||
Create a VnodeClient instance.
|
||||
|
||||
|
@ -21,7 +22,7 @@ class VnodeClient:
|
|||
:param ctrlchnlname: control channel name
|
||||
"""
|
||||
self.name: str = name
|
||||
self.ctrlchnlname: str = ctrlchnlname
|
||||
self.ctrlchnlname: Path = ctrlchnlname
|
||||
|
||||
def _verify_connection(self) -> None:
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
||||
|
||||
|
@ -63,8 +63,8 @@ class DockerClient:
|
|||
logging.debug("node(%s) pid: %s", self.name, self.pid)
|
||||
return output
|
||||
|
||||
def copy_file(self, source: str, destination: str) -> str:
|
||||
args = f"docker cp {source} {self.name}:{destination}"
|
||||
def copy_file(self, src_path: Path, dst_path: Path) -> str:
|
||||
args = f"docker cp {src_path} {self.name}:{dst_path}"
|
||||
return self.run(args)
|
||||
|
||||
|
||||
|
@ -162,77 +162,73 @@ class DockerNode(CoreNode):
|
|||
"""
|
||||
return f"docker exec -it {self.name} bash"
|
||||
|
||||
def privatedir(self, path: str) -> None:
|
||||
def privatedir(self, dir_path: str) -> None:
|
||||
"""
|
||||
Create a private directory.
|
||||
|
||||
:param path: path to create
|
||||
:param dir_path: path to create
|
||||
:return: nothing
|
||||
"""
|
||||
logging.debug("creating node dir: %s", path)
|
||||
args = f"mkdir -p {path}"
|
||||
logging.debug("creating node dir: %s", dir_path)
|
||||
args = f"mkdir -p {dir_path}"
|
||||
self.cmd(args)
|
||||
|
||||
def mount(self, source: str, target: str) -> None:
|
||||
def mount(self, src_path: str, target_path: str) -> None:
|
||||
"""
|
||||
Create and mount a directory.
|
||||
|
||||
:param source: source directory to mount
|
||||
:param target: target directory to create
|
||||
:param src_path: source directory to mount
|
||||
:param target_path: target directory to create
|
||||
:return: nothing
|
||||
:raises CoreCommandError: when a non-zero exit status occurs
|
||||
"""
|
||||
logging.debug("mounting source(%s) target(%s)", source, target)
|
||||
logging.debug("mounting source(%s) target(%s)", src_path, target_path)
|
||||
raise Exception("not supported")
|
||||
|
||||
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
|
||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||
"""
|
||||
Create a node file with a given mode.
|
||||
|
||||
:param filename: name of file to create
|
||||
:param file_path: name of file to create
|
||||
:param contents: contents of file
|
||||
:param mode: mode for file
|
||||
:return: nothing
|
||||
"""
|
||||
logging.debug("nodefile filename(%s) mode(%s)", filename, mode)
|
||||
directory = os.path.dirname(filename)
|
||||
logging.debug("nodefile filename(%s) mode(%s)", file_path, mode)
|
||||
temp = NamedTemporaryFile(delete=False)
|
||||
temp.write(contents.encode("utf-8"))
|
||||
temp.close()
|
||||
|
||||
if directory:
|
||||
temp_path = Path(temp.name)
|
||||
directory = file_path.name
|
||||
if str(directory) != ".":
|
||||
self.cmd(f"mkdir -m {0o755:o} -p {directory}")
|
||||
if self.server is not None:
|
||||
self.server.remote_put(temp.name, temp.name)
|
||||
self.client.copy_file(temp.name, filename)
|
||||
self.cmd(f"chmod {mode:o} {filename}")
|
||||
self.server.remote_put(temp_path, temp_path)
|
||||
self.client.copy_file(temp_path, file_path)
|
||||
self.cmd(f"chmod {mode:o} {file_path}")
|
||||
if self.server is not None:
|
||||
self.host_cmd(f"rm -f {temp.name}")
|
||||
os.unlink(temp.name)
|
||||
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode)
|
||||
self.host_cmd(f"rm -f {temp_path}")
|
||||
temp_path.unlink()
|
||||
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode)
|
||||
|
||||
def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None:
|
||||
def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None:
|
||||
"""
|
||||
Copy a file to a node, following symlinks and preserving metadata.
|
||||
Change file mode if specified.
|
||||
|
||||
:param filename: file name to copy file to
|
||||
:param srcfilename: file to copy
|
||||
:param file_path: file name to copy file to
|
||||
:param src_path: file to copy
|
||||
:param mode: mode to copy to
|
||||
:return: nothing
|
||||
"""
|
||||
logging.info(
|
||||
"node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode
|
||||
"node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode
|
||||
)
|
||||
directory = os.path.dirname(filename)
|
||||
self.cmd(f"mkdir -p {directory}")
|
||||
|
||||
if self.server is None:
|
||||
source = srcfilename
|
||||
else:
|
||||
self.cmd(f"mkdir -p {file_path.parent}")
|
||||
if self.server:
|
||||
temp = NamedTemporaryFile(delete=False)
|
||||
source = temp.name
|
||||
self.server.remote_put(source, temp.name)
|
||||
|
||||
self.client.copy_file(source, filename)
|
||||
self.cmd(f"chmod {mode:o} {filename}")
|
||||
temp_path = Path(temp.name)
|
||||
src_path = temp_path
|
||||
self.server.remote_put(src_path, temp_path)
|
||||
self.client.copy_file(src_path, file_path)
|
||||
self.cmd(f"chmod {mode:o} {file_path}")
|
||||
|
|
|
@ -4,6 +4,7 @@ virtual ethernet classes that implement the interfaces available under Linux.
|
|||
|
||||
import logging
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
import netaddr
|
||||
|
@ -79,7 +80,7 @@ class CoreInterface:
|
|||
self,
|
||||
args: str,
|
||||
env: Dict[str, str] = None,
|
||||
cwd: str = None,
|
||||
cwd: Path = None,
|
||||
wait: bool = True,
|
||||
shell: bool = False,
|
||||
) -> str:
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
from typing import TYPE_CHECKING, Callable, Dict, Optional
|
||||
|
||||
|
@ -57,11 +57,10 @@ class LxdClient:
|
|||
args = self.create_cmd(cmd)
|
||||
return utils.cmd(args, wait=wait, shell=shell)
|
||||
|
||||
def copy_file(self, source: str, destination: str) -> None:
|
||||
if destination[0] != "/":
|
||||
destination = os.path.join("/root/", destination)
|
||||
|
||||
args = f"lxc file push {source} {self.name}/{destination}"
|
||||
def copy_file(self, src_path: Path, dst_path: Path) -> None:
|
||||
if not str(dst_path).startswith("/"):
|
||||
dst_path = Path("/root/") / dst_path
|
||||
args = f"lxc file push {src_path} {self.name}/{dst_path}"
|
||||
self.run(args)
|
||||
|
||||
|
||||
|
@ -139,81 +138,76 @@ class LxcNode(CoreNode):
|
|||
"""
|
||||
return f"lxc exec {self.name} -- {sh}"
|
||||
|
||||
def privatedir(self, path: str) -> None:
|
||||
def privatedir(self, dir_path: Path) -> None:
|
||||
"""
|
||||
Create a private directory.
|
||||
|
||||
:param path: path to create
|
||||
:param dir_path: path to create
|
||||
:return: nothing
|
||||
"""
|
||||
logging.info("creating node dir: %s", path)
|
||||
args = f"mkdir -p {path}"
|
||||
logging.info("creating node dir: %s", dir_path)
|
||||
args = f"mkdir -p {dir_path}"
|
||||
self.cmd(args)
|
||||
|
||||
def mount(self, source: str, target: str) -> None:
|
||||
def mount(self, src_path: Path, target_path: Path) -> None:
|
||||
"""
|
||||
Create and mount a directory.
|
||||
|
||||
:param source: source directory to mount
|
||||
:param target: target directory to create
|
||||
:param src_path: source directory to mount
|
||||
:param target_path: target directory to create
|
||||
:return: nothing
|
||||
:raises CoreCommandError: when a non-zero exit status occurs
|
||||
"""
|
||||
logging.debug("mounting source(%s) target(%s)", source, target)
|
||||
logging.debug("mounting source(%s) target(%s)", src_path, target_path)
|
||||
raise Exception("not supported")
|
||||
|
||||
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
|
||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||
"""
|
||||
Create a node file with a given mode.
|
||||
|
||||
:param filename: name of file to create
|
||||
:param file_path: name of file to create
|
||||
:param contents: contents of file
|
||||
:param mode: mode for file
|
||||
:return: nothing
|
||||
"""
|
||||
logging.debug("nodefile filename(%s) mode(%s)", filename, mode)
|
||||
|
||||
directory = os.path.dirname(filename)
|
||||
logging.debug("nodefile filename(%s) mode(%s)", file_path, mode)
|
||||
temp = NamedTemporaryFile(delete=False)
|
||||
temp.write(contents.encode("utf-8"))
|
||||
temp.close()
|
||||
|
||||
if directory:
|
||||
temp_path = Path(temp.name)
|
||||
directory = file_path.parent
|
||||
if str(directory) != ".":
|
||||
self.cmd(f"mkdir -m {0o755:o} -p {directory}")
|
||||
if self.server is not None:
|
||||
self.server.remote_put(temp.name, temp.name)
|
||||
self.client.copy_file(temp.name, filename)
|
||||
self.cmd(f"chmod {mode:o} {filename}")
|
||||
self.server.remote_put(temp_path, temp_path)
|
||||
self.client.copy_file(temp_path, file_path)
|
||||
self.cmd(f"chmod {mode:o} {file_path}")
|
||||
if self.server is not None:
|
||||
self.host_cmd(f"rm -f {temp.name}")
|
||||
os.unlink(temp.name)
|
||||
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, filename, mode)
|
||||
self.host_cmd(f"rm -f {temp_path}")
|
||||
temp_path.unlink()
|
||||
logging.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode)
|
||||
|
||||
def nodefilecopy(self, filename: str, srcfilename: str, mode: int = None) -> None:
|
||||
def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None:
|
||||
"""
|
||||
Copy a file to a node, following symlinks and preserving metadata.
|
||||
Change file mode if specified.
|
||||
|
||||
:param filename: file name to copy file to
|
||||
:param srcfilename: file to copy
|
||||
:param file_path: file name to copy file to
|
||||
:param src_path: file to copy
|
||||
:param mode: mode to copy to
|
||||
:return: nothing
|
||||
"""
|
||||
logging.info(
|
||||
"node file copy file(%s) source(%s) mode(%s)", filename, srcfilename, mode
|
||||
"node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode
|
||||
)
|
||||
directory = os.path.dirname(filename)
|
||||
self.cmd(f"mkdir -p {directory}")
|
||||
|
||||
if self.server is None:
|
||||
source = srcfilename
|
||||
else:
|
||||
self.cmd(f"mkdir -p {file_path.parent}")
|
||||
if self.server:
|
||||
temp = NamedTemporaryFile(delete=False)
|
||||
source = temp.name
|
||||
self.server.remote_put(source, temp.name)
|
||||
|
||||
self.client.copy_file(source, filename)
|
||||
self.cmd(f"chmod {mode:o} {filename}")
|
||||
temp_path = Path(temp.name)
|
||||
src_path = temp_path
|
||||
self.server.remote_put(src_path, temp_path)
|
||||
self.client.copy_file(src_path, file_path)
|
||||
self.cmd(f"chmod {mode:o} {file_path}")
|
||||
|
||||
def add_iface(self, iface: CoreInterface, iface_id: int) -> None:
|
||||
super().add_iface(iface, iface_id)
|
||||
|
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import math
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Type
|
||||
|
||||
import netaddr
|
||||
|
@ -292,7 +293,7 @@ class CoreNetwork(CoreNetworkBase):
|
|||
self,
|
||||
args: str,
|
||||
env: Dict[str, str] = None,
|
||||
cwd: str = None,
|
||||
cwd: Path = None,
|
||||
wait: bool = True,
|
||||
shell: bool = False,
|
||||
) -> str:
|
||||
|
@ -333,9 +334,7 @@ class CoreNetwork(CoreNetworkBase):
|
|||
"""
|
||||
if not self.up:
|
||||
return
|
||||
|
||||
ebq.stopupdateloop(self)
|
||||
|
||||
try:
|
||||
self.net_client.delete_bridge(self.brname)
|
||||
if self.has_ebtables_chain:
|
||||
|
@ -346,11 +345,9 @@ class CoreNetwork(CoreNetworkBase):
|
|||
ebtablescmds(self.host_cmd, cmds)
|
||||
except CoreCommandError:
|
||||
logging.exception("error during shutdown")
|
||||
|
||||
# removes veth pairs used for bridge-to-bridge connections
|
||||
for iface in self.get_ifaces():
|
||||
iface.shutdown()
|
||||
|
||||
self.ifaces.clear()
|
||||
self._linked.clear()
|
||||
del self.session
|
||||
|
@ -389,10 +386,8 @@ class CoreNetwork(CoreNetworkBase):
|
|||
# check if the network interfaces are attached to this network
|
||||
if self.ifaces[iface1.net_id] != iface1:
|
||||
raise ValueError(f"inconsistency for interface {iface1.name}")
|
||||
|
||||
if self.ifaces[iface2.net_id] != iface2:
|
||||
raise ValueError(f"inconsistency for interface {iface2.name}")
|
||||
|
||||
try:
|
||||
linked = self._linked[iface1][iface2]
|
||||
except KeyError:
|
||||
|
@ -403,7 +398,6 @@ class CoreNetwork(CoreNetworkBase):
|
|||
else:
|
||||
raise Exception(f"unknown policy: {self.policy.value}")
|
||||
self._linked[iface1][iface2] = linked
|
||||
|
||||
return linked
|
||||
|
||||
def unlink(self, iface1: CoreInterface, iface2: CoreInterface) -> None:
|
||||
|
|
|
@ -3,9 +3,9 @@ PhysicalNode class for including real systems in the emulated network.
|
|||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from typing import IO, TYPE_CHECKING, List, Optional, Tuple
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||
|
||||
from core.emulator.data import InterfaceData, LinkOptions
|
||||
from core.emulator.distributed import DistributedServer
|
||||
|
@ -26,15 +26,15 @@ class PhysicalNode(CoreNodeBase):
|
|||
session: "Session",
|
||||
_id: int = None,
|
||||
name: str = None,
|
||||
nodedir: str = None,
|
||||
nodedir: Path = None,
|
||||
server: DistributedServer = None,
|
||||
) -> None:
|
||||
super().__init__(session, _id, name, server)
|
||||
if not self.server:
|
||||
raise CoreError("physical nodes must be assigned to a remote server")
|
||||
self.nodedir: Optional[str] = nodedir
|
||||
self.nodedir: Optional[Path] = nodedir
|
||||
self.lock: threading.RLock = threading.RLock()
|
||||
self._mounts: List[Tuple[str, str]] = []
|
||||
self._mounts: List[Tuple[Path, Path]] = []
|
||||
|
||||
def startup(self) -> None:
|
||||
with self.lock:
|
||||
|
@ -44,15 +44,12 @@ class PhysicalNode(CoreNodeBase):
|
|||
def shutdown(self) -> None:
|
||||
if not self.up:
|
||||
return
|
||||
|
||||
with self.lock:
|
||||
while self._mounts:
|
||||
_source, target = self._mounts.pop(-1)
|
||||
self.umount(target)
|
||||
|
||||
_, target_path = self._mounts.pop(-1)
|
||||
self.umount(target_path)
|
||||
for iface in self.get_ifaces():
|
||||
iface.shutdown()
|
||||
|
||||
self.rmnodedir()
|
||||
|
||||
def path_exists(self, path: str) -> bool:
|
||||
|
@ -186,55 +183,40 @@ class PhysicalNode(CoreNodeBase):
|
|||
self.adopt_iface(iface, iface_id, iface_data.mac, ips)
|
||||
return iface
|
||||
|
||||
def privatedir(self, path: str) -> None:
|
||||
if path[0] != "/":
|
||||
raise ValueError(f"path not fully qualified: {path}")
|
||||
hostpath = os.path.join(
|
||||
self.nodedir, os.path.normpath(path).strip("/").replace("/", ".")
|
||||
)
|
||||
os.mkdir(hostpath)
|
||||
self.mount(hostpath, path)
|
||||
def privatedir(self, dir_path: Path) -> None:
|
||||
if not str(dir_path).startswith("/"):
|
||||
raise CoreError(f"private directory path not fully qualified: {dir_path}")
|
||||
host_path = self.host_path(dir_path, is_dir=True)
|
||||
self.host_cmd(f"mkdir -p {host_path}")
|
||||
self.mount(host_path, dir_path)
|
||||
|
||||
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} --bind {source} {target}", cwd=self.nodedir)
|
||||
self._mounts.append((source, target))
|
||||
def mount(self, src_path: Path, target_path: Path) -> None:
|
||||
logging.debug("node(%s) mounting: %s at %s", self.name, src_path, target_path)
|
||||
self.cmd(f"mkdir -p {target_path}")
|
||||
self.host_cmd(f"{MOUNT} --bind {src_path} {target_path}", cwd=self.nodedir)
|
||||
self._mounts.append((src_path, target_path))
|
||||
|
||||
def umount(self, target: str) -> None:
|
||||
logging.info("unmounting '%s'", target)
|
||||
def umount(self, target_path: Path) -> None:
|
||||
logging.info("unmounting '%s'", target_path)
|
||||
try:
|
||||
self.host_cmd(f"{UMOUNT} -l {target}", cwd=self.nodedir)
|
||||
self.host_cmd(f"{UMOUNT} -l {target_path}", cwd=self.nodedir)
|
||||
except CoreCommandError:
|
||||
logging.exception("unmounting failed for %s", target)
|
||||
logging.exception("unmounting failed for %s", target_path)
|
||||
|
||||
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)
|
||||
|
||||
if dirname and dirname[0] == "/":
|
||||
dirname = dirname[1:]
|
||||
|
||||
dirname = dirname.replace("/", ".")
|
||||
dirname = os.path.join(self.nodedir, dirname)
|
||||
if not os.path.isdir(dirname):
|
||||
os.makedirs(dirname, mode=0o755)
|
||||
|
||||
hostfilename = os.path.join(dirname, basename)
|
||||
return open(hostfilename, mode)
|
||||
|
||||
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
|
||||
with self.opennodefile(filename, "w") as f:
|
||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||
host_path = self.host_path(file_path)
|
||||
directory = host_path.parent
|
||||
if not directory.is_dir():
|
||||
directory.mkdir(parents=True, mode=0o755)
|
||||
with host_path.open("w") as f:
|
||||
f.write(contents)
|
||||
os.chmod(f.name, mode)
|
||||
logging.info("created nodefile: '%s'; mode: 0%o", f.name, mode)
|
||||
host_path.chmod(mode)
|
||||
logging.info("created nodefile: '%s'; mode: 0%o", host_path, mode)
|
||||
|
||||
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
|
||||
return self.host_cmd(args, wait=wait)
|
||||
|
||||
def addfile(self, srcname: str, filename: str) -> None:
|
||||
def addfile(self, src_path: str, file_path: str) -> None:
|
||||
raise CoreError("physical node does not support addfile")
|
||||
|
||||
|
||||
|
@ -464,10 +446,10 @@ class Rj45Node(CoreNodeBase):
|
|||
def termcmdstring(self, sh: str) -> str:
|
||||
raise CoreError("rj45 does not support terminal commands")
|
||||
|
||||
def addfile(self, srcname: str, filename: str) -> None:
|
||||
def addfile(self, src_path: str, file_path: str) -> None:
|
||||
raise CoreError("rj45 does not support addfile")
|
||||
|
||||
def nodefile(self, filename: str, contents: str, mode: int = 0o644) -> None:
|
||||
def nodefile(self, file_path: str, contents: str, mode: int = 0o644) -> None:
|
||||
raise CoreError("rj45 does not support nodefile")
|
||||
|
||||
def cmd(self, args: str, wait: bool = True, shell: bool = False) -> str:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue