daemon: refactoring to remove usage of os.path where possible and pathlib.Path instead

This commit is contained in:
Blake Harnden 2021-03-19 16:54:24 -07:00
parent d0a55dd471
commit 1c970bbe00
38 changed files with 520 additions and 606 deletions

View file

@ -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):

View file

@ -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:
"""

View file

@ -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}")

View file

@ -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:

View file

@ -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)

View file

@ -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:

View file

@ -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: