|
|
|
|
@ -19,12 +19,54 @@ from . import tasks
|
|
|
|
|
log = logging.getLogger("shepherd.agent")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core_interface = plugin.PluginInterface()
|
|
|
|
|
|
|
|
|
|
confspec = ConfigSpecification()
|
|
|
|
|
# Relative pathnames here are all relative to "root_dir". `root_dir` itself is relative to
|
|
|
|
|
# the directory the default config is loaded from
|
|
|
|
|
confspec.add_specs({
|
|
|
|
|
"name": StringSpec(helptext="Identifying name for this device"),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
confspec.add_specs(optional=True, spec_dict={
|
|
|
|
|
"root_dir":
|
|
|
|
|
(StringSpec(helptext="Operating directory for shepherd to place working files."
|
|
|
|
|
" Relative to the directory containing the default config file."),
|
|
|
|
|
"./"),
|
|
|
|
|
"custom_config_path":
|
|
|
|
|
StringSpec(helptext="Path to custom config layer TOML file."),
|
|
|
|
|
"compiled_config_path":
|
|
|
|
|
(StringSpec(helptext="Path to custom file Shepherd will generate to show compiled"
|
|
|
|
|
" config that was used and any errors in validation."),
|
|
|
|
|
"compiled-config.toml"),
|
|
|
|
|
"plugin_dir":
|
|
|
|
|
(StringSpec(helptext="Optional directory for Shepherd to look for plugins in."),
|
|
|
|
|
"./shepherd-plugins")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
core_interface.register_confspec(confspec)
|
|
|
|
|
|
|
|
|
|
# Allows plugins to add delay for system time to stabilise
|
|
|
|
|
core_interface.register_hook("wait_for_stable_time")
|
|
|
|
|
|
|
|
|
|
# Allow other modules to add to the core interface (confspec, hooks, interface functions)
|
|
|
|
|
# Having modules modify a confspec after it's registered here is a bit of a hack.
|
|
|
|
|
tasks.register_on(core_interface)
|
|
|
|
|
control.register_on(core_interface)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ plugin.plugin_class
|
|
|
|
|
class Agent():
|
|
|
|
|
"""
|
|
|
|
|
Holds the main state required to run Shepherd Agent
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
|
|
|
|
# Make sure the plugin system uses this instance rather making its own
|
|
|
|
|
core_interface._plugin_obj = self
|
|
|
|
|
|
|
|
|
|
# The config defined by the device (everything before the Control layer)
|
|
|
|
|
self.local_config = None
|
|
|
|
|
# The config actually being used
|
|
|
|
|
@ -38,22 +80,11 @@ class Agent():
|
|
|
|
|
|
|
|
|
|
self.restart_args = None
|
|
|
|
|
|
|
|
|
|
# Setup core interface
|
|
|
|
|
self.core_interface = plugin.PluginInterface()
|
|
|
|
|
self.core_interface.register_confspec(core_confspec())
|
|
|
|
|
self.core_interface.register_function(self.root_dir)
|
|
|
|
|
self.core_interface.register_function(self.device_name)
|
|
|
|
|
# Allows plugins to add delay for system time to stabilise
|
|
|
|
|
self.core_interface.register_hook("wait_for_stable_time")
|
|
|
|
|
|
|
|
|
|
# Allow other modules to add to the core interface (confspec, hooks, interface functions)
|
|
|
|
|
# Having modules modify a confspec after it's registered here is a bit of a hack.
|
|
|
|
|
tasks.register_on(self.core_interface)
|
|
|
|
|
control.register_on(self.core_interface)
|
|
|
|
|
|
|
|
|
|
@ plugin.plugin_function
|
|
|
|
|
def root_dir(self):
|
|
|
|
|
return self.core_config["root_dir"]
|
|
|
|
|
|
|
|
|
|
@ plugin.plugin_function
|
|
|
|
|
def device_name(self):
|
|
|
|
|
return self.core_config["name"]
|
|
|
|
|
|
|
|
|
|
@ -74,9 +105,6 @@ class Agent():
|
|
|
|
|
use_custom_config, control_enabled, new_device_mode]
|
|
|
|
|
|
|
|
|
|
self.control_enabled = control_enabled
|
|
|
|
|
# Because the plugin module caches interfaces, this will then get used when loading
|
|
|
|
|
# config layers and validating them
|
|
|
|
|
plugin.load_plugin_interface("shepherd", self.core_interface)
|
|
|
|
|
|
|
|
|
|
# Compile the config layers
|
|
|
|
|
|
|
|
|
|
@ -84,7 +112,7 @@ class Agent():
|
|
|
|
|
# Pre-seed confman with core confspec to bootstrap 'plugin_dir'. This is required even
|
|
|
|
|
# though 'load_config_layer_and_plugins()' will get the core confspec from the cached
|
|
|
|
|
# interface, as it needs 'plugin_dir' to list the plugins to load first.
|
|
|
|
|
confman.add_confspec("shepherd", self.core_interface._confspec)
|
|
|
|
|
confman.add_confspec("shepherd", core_interface.confspec)
|
|
|
|
|
|
|
|
|
|
compile_local_config(confman, default_config_path, use_custom_config)
|
|
|
|
|
self.local_config = deepcopy(confman.get_config_bundles())
|
|
|
|
|
@ -131,12 +159,13 @@ class Agent():
|
|
|
|
|
self.plugin_interfaces = plugin.init_plugins(self.applied_config)
|
|
|
|
|
# After this point, plugins may already have their own threads running if they created
|
|
|
|
|
# them during init
|
|
|
|
|
self.interface_functions = self.core_interface.plugins
|
|
|
|
|
self.interface_functions = core_interface.plugins
|
|
|
|
|
|
|
|
|
|
cmd_runner = control.CommandRunner(self.interface_functions)
|
|
|
|
|
core_update_state = control.CoreUpdateState(cmd_runner.cmd_reader,
|
|
|
|
|
cmd_runner.cmd_result_writer)
|
|
|
|
|
core_update_state.set_static_state(self.local_config, self.applied_config, core_confspec())
|
|
|
|
|
core_update_state.set_static_state(
|
|
|
|
|
self.local_config, self.applied_config, core_interface.confspec)
|
|
|
|
|
|
|
|
|
|
plugin_update_states = {name: iface._update_state
|
|
|
|
|
for name, iface in self.plugin_interfaces.items()}
|
|
|
|
|
@ -155,7 +184,7 @@ class Agent():
|
|
|
|
|
|
|
|
|
|
# TODO Any time stabilisation or waiting for Control
|
|
|
|
|
|
|
|
|
|
tasks.start_tasks(self.core_interface, task_session)
|
|
|
|
|
tasks.start_tasks(core_interface, task_session)
|
|
|
|
|
|
|
|
|
|
# tasks.init_tasks(self.core_config) # seperate tasks.start?
|
|
|
|
|
|
|
|
|
|
@ -270,37 +299,6 @@ def compile_remote_config(confman):
|
|
|
|
|
log.warning("No cached Shepherd Control config layer available.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Relative pathnames here are all relative to "root_dir". `root_dir` itself is relative to
|
|
|
|
|
# the directory the default config is loaded from
|
|
|
|
|
def core_confspec():
|
|
|
|
|
"""
|
|
|
|
|
Returns the core config specification
|
|
|
|
|
"""
|
|
|
|
|
confspec = ConfigSpecification()
|
|
|
|
|
|
|
|
|
|
confspec.add_specs({
|
|
|
|
|
"name": StringSpec(helptext="Identifying name for this device"),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
confspec.add_specs(optional=True, spec_dict={
|
|
|
|
|
"root_dir":
|
|
|
|
|
(StringSpec(helptext="Operating directory for shepherd to place working files."
|
|
|
|
|
" Relative to the directory containing the default config file."),
|
|
|
|
|
"./"),
|
|
|
|
|
"custom_config_path":
|
|
|
|
|
StringSpec(helptext="Path to custom config layer TOML file."),
|
|
|
|
|
"compiled_config_path":
|
|
|
|
|
(StringSpec(helptext="Path to custom file Shepherd will generate to show compiled"
|
|
|
|
|
" config that was used and any errors in validation."),
|
|
|
|
|
"compiled-config.toml"),
|
|
|
|
|
"plugin_dir":
|
|
|
|
|
(StringSpec(helptext="Optional directory for Shepherd to look for plugins in."),
|
|
|
|
|
"./shepherd-plugins")
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return confspec
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def resolve_core_conf_paths(core_conf, relative_dir):
|
|
|
|
|
"""
|
|
|
|
|
Set the cwd to ``root_dir`` and resolve other core config paths relative to that.
|
|
|
|
|
|