daemon: added capability to config services to shadow directory structures, from a given path, or from a local source, files may be templates or a straight copy and can be sourced from node named unique paths for node specific files, also refactored and renamed file creation related functions for nodes
This commit is contained in:
parent
b96dc621cd
commit
bd896d1336
10 changed files with 212 additions and 130 deletions
|
@ -3,8 +3,9 @@ import enum
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from mako import exceptions
|
from mako import exceptions
|
||||||
from mako.lookup import TemplateLookup
|
from mako.lookup import TemplateLookup
|
||||||
|
@ -28,6 +29,14 @@ class ConfigServiceBootError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ShadowDir:
|
||||||
|
path: str
|
||||||
|
src: Optional[str] = None
|
||||||
|
templates: bool = False
|
||||||
|
has_node_paths: bool = False
|
||||||
|
|
||||||
|
|
||||||
class ConfigService(abc.ABC):
|
class ConfigService(abc.ABC):
|
||||||
"""
|
"""
|
||||||
Base class for creating configurable services.
|
Base class for creating configurable services.
|
||||||
|
@ -39,6 +48,9 @@ class ConfigService(abc.ABC):
|
||||||
# time to wait in seconds for determining if service started successfully
|
# time to wait in seconds for determining if service started successfully
|
||||||
validation_timer: int = 5
|
validation_timer: int = 5
|
||||||
|
|
||||||
|
# directories to shadow and copy files from
|
||||||
|
shadow_directories: List[ShadowDir] = []
|
||||||
|
|
||||||
def __init__(self, node: CoreNode) -> None:
|
def __init__(self, node: CoreNode) -> None:
|
||||||
"""
|
"""
|
||||||
Create ConfigService instance.
|
Create ConfigService instance.
|
||||||
|
@ -135,6 +147,7 @@ class ConfigService(abc.ABC):
|
||||||
:raises ConfigServiceBootError: when there is an error starting service
|
:raises ConfigServiceBootError: when there is an error starting service
|
||||||
"""
|
"""
|
||||||
logger.info("node(%s) service(%s) starting...", self.node.name, self.name)
|
logger.info("node(%s) service(%s) starting...", self.node.name, self.name)
|
||||||
|
self.create_shadow_dirs()
|
||||||
self.create_dirs()
|
self.create_dirs()
|
||||||
self.create_files()
|
self.create_files()
|
||||||
wait = self.validation_mode == ConfigServiceMode.BLOCKING
|
wait = self.validation_mode == ConfigServiceMode.BLOCKING
|
||||||
|
@ -169,6 +182,64 @@ class ConfigService(abc.ABC):
|
||||||
self.stop()
|
self.stop()
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
|
def create_shadow_dirs(self) -> None:
|
||||||
|
"""
|
||||||
|
Creates a shadow of a host system directory recursively
|
||||||
|
to be mapped and live within a node.
|
||||||
|
|
||||||
|
:return: nothing
|
||||||
|
:raises CoreError: when there is a failure creating a directory or file
|
||||||
|
"""
|
||||||
|
for shadow_dir in self.shadow_directories:
|
||||||
|
# setup shadow and src paths, using node unique paths when configured
|
||||||
|
shadow_path = Path(shadow_dir.path)
|
||||||
|
if shadow_dir.src is None:
|
||||||
|
src_path = shadow_path
|
||||||
|
else:
|
||||||
|
src_path = Path(shadow_dir.src)
|
||||||
|
if shadow_dir.has_node_paths:
|
||||||
|
src_path = src_path / self.node.name
|
||||||
|
# validate shadow and src paths
|
||||||
|
if not shadow_path.is_absolute():
|
||||||
|
raise CoreError(f"shadow dir({shadow_path}) is not absolute")
|
||||||
|
if not src_path.is_absolute():
|
||||||
|
raise CoreError(f"shadow source dir({src_path}) is not absolute")
|
||||||
|
if not src_path.is_dir():
|
||||||
|
raise CoreError(f"shadow source dir({src_path}) does not exist")
|
||||||
|
# create root of the shadow path within node
|
||||||
|
logger.info(
|
||||||
|
"node(%s) creating shadow directory(%s) src(%s) node paths(%s) "
|
||||||
|
"templates(%s)",
|
||||||
|
self.node.name,
|
||||||
|
shadow_path,
|
||||||
|
src_path,
|
||||||
|
shadow_dir.has_node_paths,
|
||||||
|
shadow_dir.templates,
|
||||||
|
)
|
||||||
|
self.node.create_dir(shadow_path)
|
||||||
|
# find all directories and files to create
|
||||||
|
dir_paths = []
|
||||||
|
file_paths = []
|
||||||
|
for path in src_path.rglob("*"):
|
||||||
|
shadow_src_path = shadow_path / path.relative_to(src_path)
|
||||||
|
if path.is_dir():
|
||||||
|
dir_paths.append(shadow_src_path)
|
||||||
|
else:
|
||||||
|
file_paths.append((path, shadow_src_path))
|
||||||
|
# create all directories within node
|
||||||
|
for path in dir_paths:
|
||||||
|
self.node.create_dir(path)
|
||||||
|
# create all files within node, from templates when configured
|
||||||
|
data = self.data()
|
||||||
|
templates = TemplateLookup(directories=src_path)
|
||||||
|
for path, dst_path in file_paths:
|
||||||
|
if shadow_dir.templates:
|
||||||
|
template = templates.get_template(path.name)
|
||||||
|
rendered = self._render(template, data)
|
||||||
|
self.node.create_file(dst_path, rendered)
|
||||||
|
else:
|
||||||
|
self.node.copy_file(path, dst_path)
|
||||||
|
|
||||||
def create_dirs(self) -> None:
|
def create_dirs(self) -> None:
|
||||||
"""
|
"""
|
||||||
Creates directories for service.
|
Creates directories for service.
|
||||||
|
@ -176,10 +247,11 @@ class ConfigService(abc.ABC):
|
||||||
:return: nothing
|
:return: nothing
|
||||||
:raises CoreError: when there is a failure creating a directory
|
:raises CoreError: when there is a failure creating a directory
|
||||||
"""
|
"""
|
||||||
for directory in self.directories:
|
logger.debug("creating config service directories")
|
||||||
|
for directory in sorted(self.directories):
|
||||||
dir_path = Path(directory)
|
dir_path = Path(directory)
|
||||||
try:
|
try:
|
||||||
self.node.privatedir(dir_path)
|
self.node.create_dir(dir_path)
|
||||||
except (CoreCommandError, CoreError):
|
except (CoreCommandError, CoreError):
|
||||||
raise CoreError(
|
raise CoreError(
|
||||||
f"node({self.node.name}) service({self.name}) "
|
f"node({self.node.name}) service({self.name}) "
|
||||||
|
@ -221,17 +293,21 @@ class ConfigService(abc.ABC):
|
||||||
:return: mapping of files to templates
|
:return: mapping of files to templates
|
||||||
"""
|
"""
|
||||||
templates = {}
|
templates = {}
|
||||||
for name in self.files:
|
for file in self.files:
|
||||||
basename = Path(name).name
|
file_path = Path(file)
|
||||||
if name in self.custom_templates:
|
if file_path.is_absolute():
|
||||||
template = self.custom_templates[name]
|
template_path = str(file_path.relative_to("/"))
|
||||||
template = self.clean_text(template)
|
|
||||||
elif self.templates.has_template(basename):
|
|
||||||
template = self.templates.get_template(basename).source
|
|
||||||
else:
|
else:
|
||||||
template = self.get_text_template(name)
|
template_path = str(file_path)
|
||||||
|
if file in self.custom_templates:
|
||||||
|
template = self.custom_templates[file]
|
||||||
template = self.clean_text(template)
|
template = self.clean_text(template)
|
||||||
templates[name] = template
|
elif self.templates.has_template(template_path):
|
||||||
|
template = self.templates.get_template(template_path).source
|
||||||
|
else:
|
||||||
|
template = self.get_text_template(file)
|
||||||
|
template = self.clean_text(template)
|
||||||
|
templates[file] = template
|
||||||
return templates
|
return templates
|
||||||
|
|
||||||
def create_files(self) -> None:
|
def create_files(self) -> None:
|
||||||
|
@ -241,24 +317,20 @@ class ConfigService(abc.ABC):
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
data = self.data()
|
data = self.data()
|
||||||
for name in self.files:
|
for file in sorted(self.files):
|
||||||
file_path = Path(name)
|
logger.debug(
|
||||||
if name in self.custom_templates:
|
"node(%s) service(%s) template(%s)", self.node.name, self.name, file
|
||||||
text = self.custom_templates[name]
|
)
|
||||||
|
file_path = Path(file)
|
||||||
|
if file in self.custom_templates:
|
||||||
|
text = self.custom_templates[file]
|
||||||
rendered = self.render_text(text, data)
|
rendered = self.render_text(text, data)
|
||||||
elif self.templates.has_template(file_path.name):
|
elif self.templates.has_template(file_path.name):
|
||||||
rendered = self.render_template(file_path.name, data)
|
rendered = self.render_template(file_path.name, data)
|
||||||
else:
|
else:
|
||||||
text = self.get_text_template(name)
|
text = self.get_text_template(file)
|
||||||
rendered = self.render_text(text, data)
|
rendered = self.render_text(text, data)
|
||||||
logger.debug(
|
self.node.create_file(file_path, rendered)
|
||||||
"node(%s) service(%s) template(%s): \n%s",
|
|
||||||
self.node.name,
|
|
||||||
self.name,
|
|
||||||
name,
|
|
||||||
rendered,
|
|
||||||
)
|
|
||||||
self.node.nodefile(file_path, rendered)
|
|
||||||
|
|
||||||
def run_startup(self, wait: bool) -> None:
|
def run_startup(self, wait: bool) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from core.config import Configuration
|
|
||||||
from core.configservice.base import ConfigService, ConfigServiceMode
|
|
||||||
from core.emulator.enumerations import ConfigDataTypes
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleService(ConfigService):
|
|
||||||
name: str = "Simple"
|
|
||||||
group: str = "SimpleGroup"
|
|
||||||
directories: List[str] = ["/etc/quagga", "/usr/local/lib"]
|
|
||||||
files: List[str] = ["test1.sh", "test2.sh"]
|
|
||||||
executables: List[str] = []
|
|
||||||
dependencies: List[str] = []
|
|
||||||
startup: List[str] = []
|
|
||||||
validate: List[str] = []
|
|
||||||
shutdown: List[str] = []
|
|
||||||
validation_mode: ConfigServiceMode = ConfigServiceMode.BLOCKING
|
|
||||||
default_configs: List[Configuration] = [
|
|
||||||
Configuration(id="value1", type=ConfigDataTypes.STRING, label="Text"),
|
|
||||||
Configuration(id="value2", type=ConfigDataTypes.BOOL, label="Boolean"),
|
|
||||||
Configuration(
|
|
||||||
id="value3",
|
|
||||||
type=ConfigDataTypes.STRING,
|
|
||||||
label="Multiple Choice",
|
|
||||||
options=["value1", "value2", "value3"],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
modes: Dict[str, Dict[str, str]] = {
|
|
||||||
"mode1": {"value1": "value1", "value2": "0", "value3": "value2"},
|
|
||||||
"mode2": {"value1": "value2", "value2": "1", "value3": "value3"},
|
|
||||||
"mode3": {"value1": "value3", "value2": "0", "value3": "value1"},
|
|
||||||
}
|
|
||||||
|
|
||||||
def get_text_template(self, name: str) -> str:
|
|
||||||
if name == "test1.sh":
|
|
||||||
return """
|
|
||||||
# sample script 1
|
|
||||||
# node id(${node.id}) name(${node.name})
|
|
||||||
# config: ${config}
|
|
||||||
echo hello
|
|
||||||
"""
|
|
||||||
elif name == "test2.sh":
|
|
||||||
return """
|
|
||||||
# sample script 2
|
|
||||||
# node id(${node.id}) name(${node.name})
|
|
||||||
# config: ${config}
|
|
||||||
echo hello2
|
|
||||||
"""
|
|
|
@ -690,11 +690,11 @@ class Session:
|
||||||
:param data: file data
|
:param data: file data
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
node = self.get_node(node_id, CoreNodeBase)
|
node = self.get_node(node_id, CoreNode)
|
||||||
if src_path is not None:
|
if src_path is not None:
|
||||||
node.addfile(src_path, file_path)
|
node.addfile(src_path, file_path)
|
||||||
elif data is not None:
|
elif data is not None:
|
||||||
node.nodefile(file_path, data)
|
node.create_file(file_path, data)
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -237,7 +237,17 @@ class CoreNodeBase(NodeBase):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
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:
|
||||||
"""
|
"""
|
||||||
Create a node file with a given mode.
|
Create a node file with a given mode.
|
||||||
|
|
||||||
|
@ -248,6 +258,19 @@ class CoreNodeBase(NodeBase):
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@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
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def addfile(self, src_path: Path, file_path: Path) -> None:
|
def addfile(self, src_path: Path, file_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -567,7 +590,7 @@ class CoreNode(CoreNodeBase):
|
||||||
|
|
||||||
# create private directories
|
# create private directories
|
||||||
for dir_path in PRIVATE_DIRS:
|
for dir_path in PRIVATE_DIRS:
|
||||||
self.privatedir(dir_path)
|
self.create_dir(dir_path)
|
||||||
|
|
||||||
def shutdown(self) -> None:
|
def shutdown(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -648,16 +671,20 @@ class CoreNode(CoreNodeBase):
|
||||||
else:
|
else:
|
||||||
return f"ssh -X -f {self.server.host} xterm -e {terminal}"
|
return f"ssh -X -f {self.server.host} xterm -e {terminal}"
|
||||||
|
|
||||||
def privatedir(self, dir_path: Path) -> None:
|
def create_dir(self, dir_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Create a private directory.
|
Create a node private directory.
|
||||||
|
|
||||||
:param dir_path: path to create
|
:param dir_path: path to create
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logger.info("creating private directory: %s", dir_path)
|
if not dir_path.is_absolute():
|
||||||
if not str(dir_path).startswith("/"):
|
|
||||||
raise CoreError(f"private directory path not fully qualified: {dir_path}")
|
raise CoreError(f"private directory path not fully qualified: {dir_path}")
|
||||||
|
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)
|
host_path = self.host_path(dir_path, is_dir=True)
|
||||||
self.host_cmd(f"mkdir -p {host_path}")
|
self.host_cmd(f"mkdir -p {host_path}")
|
||||||
self.mount(host_path, dir_path)
|
self.mount(host_path, dir_path)
|
||||||
|
@ -880,15 +907,39 @@ class CoreNode(CoreNodeBase):
|
||||||
self.host_cmd(f"mkdir -p {directory}")
|
self.host_cmd(f"mkdir -p {directory}")
|
||||||
self.server.remote_put(src_path, file_path)
|
self.server.remote_put(src_path, file_path)
|
||||||
|
|
||||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
def _find_parent_path(self, path: Path) -> Optional[Path]:
|
||||||
"""
|
"""
|
||||||
Create a node file with a given mode.
|
Check if there is an existing mounted parent directory created for this node.
|
||||||
|
|
||||||
:param file_path: name of file to create
|
: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
|
||||||
:param contents: contents of file
|
:param contents: contents of file
|
||||||
:param mode: mode for file
|
:param mode: mode to create file with
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
|
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)
|
host_path = self.host_path(file_path)
|
||||||
directory = host_path.parent
|
directory = host_path.parent
|
||||||
if self.server is None:
|
if self.server is None:
|
||||||
|
@ -901,26 +952,35 @@ class CoreNode(CoreNodeBase):
|
||||||
self.host_cmd(f"mkdir -m {0o755:o} -p {directory}")
|
self.host_cmd(f"mkdir -m {0o755:o} -p {directory}")
|
||||||
self.server.remote_put_temp(host_path, contents)
|
self.server.remote_put_temp(host_path, contents)
|
||||||
self.host_cmd(f"chmod {mode:o} {host_path}")
|
self.host_cmd(f"chmod {mode:o} {host_path}")
|
||||||
logger.debug("node(%s) added file: %s; mode: 0%o", self.name, host_path, mode)
|
|
||||||
|
|
||||||
def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None:
|
def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None:
|
||||||
"""
|
"""
|
||||||
Copy a file to a node, following symlinks and preserving metadata.
|
Copy source file to node host destination, updating the file mode when
|
||||||
Change file mode if specified.
|
provided.
|
||||||
|
|
||||||
:param file_path: file name to copy file to
|
:param src_path: source file to copy
|
||||||
:param src_path: file to copy
|
:param dst_path: node host destination
|
||||||
:param mode: mode to copy to
|
:param mode: file mode
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
host_path = self.host_path(file_path)
|
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)
|
||||||
if self.server is None:
|
if self.server is None:
|
||||||
shutil.copy2(src_path, host_path)
|
shutil.copy2(src_path, host_path)
|
||||||
else:
|
else:
|
||||||
self.server.remote_put(src_path, host_path)
|
self.server.remote_put(src_path, host_path)
|
||||||
if mode is not None:
|
if mode is not None:
|
||||||
self.host_cmd(f"chmod {mode:o} {host_path}")
|
self.host_cmd(f"chmod {mode:o} {host_path}")
|
||||||
logger.info("node(%s) copied file: %s; mode: %s", self.name, host_path, mode)
|
|
||||||
|
|
||||||
|
|
||||||
class CoreNetworkBase(NodeBase):
|
class CoreNetworkBase(NodeBase):
|
||||||
|
|
|
@ -164,7 +164,7 @@ class DockerNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return f"docker exec -it {self.name} bash"
|
return f"docker exec -it {self.name} bash"
|
||||||
|
|
||||||
def privatedir(self, dir_path: str) -> None:
|
def create_dir(self, dir_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Create a private directory.
|
Create a private directory.
|
||||||
|
|
||||||
|
@ -187,7 +187,7 @@ class DockerNode(CoreNode):
|
||||||
logger.debug("mounting source(%s) target(%s)", src_path, target_path)
|
logger.debug("mounting source(%s) target(%s)", src_path, target_path)
|
||||||
raise Exception("not supported")
|
raise Exception("not supported")
|
||||||
|
|
||||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
def create_file(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||||
"""
|
"""
|
||||||
Create a node file with a given mode.
|
Create a node file with a given mode.
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ class DockerNode(CoreNode):
|
||||||
:param mode: mode for file
|
:param mode: mode for file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logger.debug("nodefile filename(%s) mode(%s)", file_path, mode)
|
logger.debug("node(%s) create file(%s) mode(%o)", self.name, file_path, mode)
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp.write(contents.encode("utf-8"))
|
temp.write(contents.encode("utf-8"))
|
||||||
temp.close()
|
temp.close()
|
||||||
|
@ -211,26 +211,26 @@ class DockerNode(CoreNode):
|
||||||
if self.server is not None:
|
if self.server is not None:
|
||||||
self.host_cmd(f"rm -f {temp_path}")
|
self.host_cmd(f"rm -f {temp_path}")
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
logger.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode)
|
|
||||||
|
|
||||||
def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None:
|
def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None:
|
||||||
"""
|
"""
|
||||||
Copy a file to a node, following symlinks and preserving metadata.
|
Copy a file to a node, following symlinks and preserving metadata.
|
||||||
Change file mode if specified.
|
Change file mode if specified.
|
||||||
|
|
||||||
:param file_path: file name to copy file to
|
:param dst_path: file name to copy file to
|
||||||
:param src_path: file to copy
|
:param src_path: file to copy
|
||||||
:param mode: mode to copy to
|
:param mode: mode to copy to
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logger.info(
|
logger.info(
|
||||||
"node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode
|
"node file copy file(%s) source(%s) mode(%o)", dst_path, src_path, mode or 0
|
||||||
)
|
)
|
||||||
self.cmd(f"mkdir -p {file_path.parent}")
|
self.cmd(f"mkdir -p {dst_path.parent}")
|
||||||
if self.server:
|
if self.server:
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp_path = Path(temp.name)
|
temp_path = Path(temp.name)
|
||||||
src_path = temp_path
|
src_path = temp_path
|
||||||
self.server.remote_put(src_path, temp_path)
|
self.server.remote_put(src_path, temp_path)
|
||||||
self.client.copy_file(src_path, file_path)
|
self.client.copy_file(src_path, dst_path)
|
||||||
self.cmd(f"chmod {mode:o} {file_path}")
|
if mode is not None:
|
||||||
|
self.cmd(f"chmod {mode:o} {dst_path}")
|
||||||
|
|
|
@ -140,7 +140,7 @@ class LxcNode(CoreNode):
|
||||||
"""
|
"""
|
||||||
return f"lxc exec {self.name} -- {sh}"
|
return f"lxc exec {self.name} -- {sh}"
|
||||||
|
|
||||||
def privatedir(self, dir_path: Path) -> None:
|
def create_dir(self, dir_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Create a private directory.
|
Create a private directory.
|
||||||
|
|
||||||
|
@ -163,7 +163,7 @@ class LxcNode(CoreNode):
|
||||||
logger.debug("mounting source(%s) target(%s)", src_path, target_path)
|
logger.debug("mounting source(%s) target(%s)", src_path, target_path)
|
||||||
raise Exception("not supported")
|
raise Exception("not supported")
|
||||||
|
|
||||||
def nodefile(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
def create_file(self, file_path: Path, contents: str, mode: int = 0o644) -> None:
|
||||||
"""
|
"""
|
||||||
Create a node file with a given mode.
|
Create a node file with a given mode.
|
||||||
|
|
||||||
|
@ -172,7 +172,7 @@ class LxcNode(CoreNode):
|
||||||
:param mode: mode for file
|
:param mode: mode for file
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logger.debug("nodefile filename(%s) mode(%s)", file_path, mode)
|
logger.debug("node(%s) create file(%s) mode(%o)", self.name, file_path, mode)
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp.write(contents.encode("utf-8"))
|
temp.write(contents.encode("utf-8"))
|
||||||
temp.close()
|
temp.close()
|
||||||
|
@ -189,27 +189,28 @@ class LxcNode(CoreNode):
|
||||||
temp_path.unlink()
|
temp_path.unlink()
|
||||||
logger.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode)
|
logger.debug("node(%s) added file: %s; mode: 0%o", self.name, file_path, mode)
|
||||||
|
|
||||||
def nodefilecopy(self, file_path: Path, src_path: Path, mode: int = None) -> None:
|
def copy_file(self, src_path: Path, dst_path: Path, mode: int = None) -> None:
|
||||||
"""
|
"""
|
||||||
Copy a file to a node, following symlinks and preserving metadata.
|
Copy a file to a node, following symlinks and preserving metadata.
|
||||||
Change file mode if specified.
|
Change file mode if specified.
|
||||||
|
|
||||||
:param file_path: file name to copy file to
|
:param dst_path: file name to copy file to
|
||||||
:param src_path: file to copy
|
:param src_path: file to copy
|
||||||
:param mode: mode to copy to
|
:param mode: mode to copy to
|
||||||
:return: nothing
|
:return: nothing
|
||||||
"""
|
"""
|
||||||
logger.info(
|
logger.info(
|
||||||
"node file copy file(%s) source(%s) mode(%s)", file_path, src_path, mode
|
"node file copy file(%s) source(%s) mode(%o)", dst_path, src_path, mode or 0
|
||||||
)
|
)
|
||||||
self.cmd(f"mkdir -p {file_path.parent}")
|
self.cmd(f"mkdir -p {dst_path.parent}")
|
||||||
if self.server:
|
if self.server:
|
||||||
temp = NamedTemporaryFile(delete=False)
|
temp = NamedTemporaryFile(delete=False)
|
||||||
temp_path = Path(temp.name)
|
temp_path = Path(temp.name)
|
||||||
src_path = temp_path
|
src_path = temp_path
|
||||||
self.server.remote_put(src_path, temp_path)
|
self.server.remote_put(src_path, temp_path)
|
||||||
self.client.copy_file(src_path, file_path)
|
self.client.copy_file(src_path, dst_path)
|
||||||
self.cmd(f"chmod {mode:o} {file_path}")
|
if mode is not None:
|
||||||
|
self.cmd(f"chmod {mode:o} {dst_path}")
|
||||||
|
|
||||||
def add_iface(self, iface: CoreInterface, iface_id: int) -> None:
|
def add_iface(self, iface: CoreInterface, iface_id: int) -> None:
|
||||||
super().add_iface(iface, iface_id)
|
super().add_iface(iface, iface_id)
|
||||||
|
|
|
@ -492,7 +492,7 @@ class CoreServices:
|
||||||
for directory in service.dirs:
|
for directory in service.dirs:
|
||||||
dir_path = Path(directory)
|
dir_path = Path(directory)
|
||||||
try:
|
try:
|
||||||
node.privatedir(dir_path)
|
node.create_dir(dir_path)
|
||||||
except (CoreCommandError, CoreError) as e:
|
except (CoreCommandError, CoreError) as e:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"error mounting private dir '%s' for service '%s': %s",
|
"error mounting private dir '%s' for service '%s': %s",
|
||||||
|
@ -553,7 +553,7 @@ class CoreServices:
|
||||||
src = src.split("\n")[0]
|
src = src.split("\n")[0]
|
||||||
src = utils.expand_corepath(src, node.session, node)
|
src = utils.expand_corepath(src, node.session, node)
|
||||||
# TODO: glob here
|
# TODO: glob here
|
||||||
node.nodefilecopy(file_path, src, mode=0o644)
|
node.copy_file(src, file_path, mode=0o644)
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -750,7 +750,7 @@ class CoreServices:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
cfg = service.generate_config(node, file_name)
|
cfg = service.generate_config(node, file_name)
|
||||||
node.nodefile(file_path, cfg)
|
node.create_file(file_path, cfg)
|
||||||
|
|
||||||
def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None:
|
def service_reconfigure(self, node: CoreNode, service: "CoreService") -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -771,7 +771,7 @@ class CoreServices:
|
||||||
cfg = service.config_data.get(file_name)
|
cfg = service.config_data.get(file_name)
|
||||||
if cfg is None:
|
if cfg is None:
|
||||||
cfg = service.generate_config(node, file_name)
|
cfg = service.generate_config(node, file_name)
|
||||||
node.nodefile(file_path, cfg)
|
node.create_file(file_path, cfg)
|
||||||
|
|
||||||
|
|
||||||
class CoreService:
|
class CoreService:
|
||||||
|
|
|
@ -60,7 +60,7 @@ def patcher(request):
|
||||||
patch_manager.patch_obj(
|
patch_manager.patch_obj(
|
||||||
LinuxNetClient, "get_mac", return_value="00:00:00:00:00:00"
|
LinuxNetClient, "get_mac", return_value="00:00:00:00:00:00"
|
||||||
)
|
)
|
||||||
patch_manager.patch_obj(CoreNode, "nodefile")
|
patch_manager.patch_obj(CoreNode, "create_file")
|
||||||
patch_manager.patch_obj(Session, "write_state")
|
patch_manager.patch_obj(Session, "write_state")
|
||||||
patch_manager.patch_obj(Session, "write_nodes")
|
patch_manager.patch_obj(Session, "write_nodes")
|
||||||
yield patch_manager
|
yield patch_manager
|
||||||
|
|
|
@ -70,7 +70,7 @@ class TestConfigServices:
|
||||||
|
|
||||||
# then
|
# then
|
||||||
directory = Path(MyService.directories[0])
|
directory = Path(MyService.directories[0])
|
||||||
node.privatedir.assert_called_with(directory)
|
node.create_dir.assert_called_with(directory)
|
||||||
|
|
||||||
def test_create_files_custom(self):
|
def test_create_files_custom(self):
|
||||||
# given
|
# given
|
||||||
|
@ -84,7 +84,7 @@ class TestConfigServices:
|
||||||
|
|
||||||
# then
|
# then
|
||||||
file_path = Path(MyService.files[0])
|
file_path = Path(MyService.files[0])
|
||||||
node.nodefile.assert_called_with(file_path, text)
|
node.create_file.assert_called_with(file_path, text)
|
||||||
|
|
||||||
def test_create_files_text(self):
|
def test_create_files_text(self):
|
||||||
# given
|
# given
|
||||||
|
@ -96,7 +96,7 @@ class TestConfigServices:
|
||||||
|
|
||||||
# then
|
# then
|
||||||
file_path = Path(MyService.files[0])
|
file_path = Path(MyService.files[0])
|
||||||
node.nodefile.assert_called_with(file_path, TEMPLATE_TEXT)
|
node.create_file.assert_called_with(file_path, TEMPLATE_TEXT)
|
||||||
|
|
||||||
def test_run_startup(self):
|
def test_run_startup(self):
|
||||||
# given
|
# given
|
||||||
|
|
|
@ -441,10 +441,8 @@ class TestGui:
|
||||||
coretlv.handle_message(message)
|
coretlv.handle_message(message)
|
||||||
|
|
||||||
if not request.config.getoption("mock"):
|
if not request.config.getoption("mock"):
|
||||||
directory = str(file_path.parent)
|
expected_path = node.directory / "var.log/test" / file_path.name
|
||||||
created_directory = directory[1:].replace("/", ".")
|
assert expected_path.exists()
|
||||||
create_path = node.directory / created_directory / file_path.name
|
|
||||||
assert create_path.exists()
|
|
||||||
|
|
||||||
def test_exec_node_tty(self, coretlv: CoreHandler):
|
def test_exec_node_tty(self, coretlv: CoreHandler):
|
||||||
coretlv.dispatch_replies = mock.MagicMock()
|
coretlv.dispatch_replies = mock.MagicMock()
|
||||||
|
|
Loading…
Reference in a new issue