diff --git a/daemon/core/api/grpc/grpcutils.py b/daemon/core/api/grpc/grpcutils.py index 429aacd0..88092fe3 100644 --- a/daemon/core/api/grpc/grpcutils.py +++ b/daemon/core/api/grpc/grpcutils.py @@ -134,7 +134,7 @@ def create_nodes( def create_links( session: Session, link_protos: List[core_pb2.Link] -) -> Tuple[List[NodeBase], List[Type[Exception]]]: +) -> Tuple[List[NodeBase], List[Exception]]: """ Create links using a thread pool and wait for completion. diff --git a/daemon/core/config.py b/daemon/core/config.py index e8e73300..b117ce54 100644 --- a/daemon/core/config.py +++ b/daemon/core/config.py @@ -4,8 +4,111 @@ Common support for configurable CORE objects. import logging from collections import OrderedDict +from typing import TYPE_CHECKING, Dict, List, Tuple, Type, Union +from core.emane.nodes import EmaneNet from core.emulator.data import ConfigData +from core.emulator.enumerations import ConfigDataTypes +from core.nodes.network import WlanNode + +if TYPE_CHECKING: + from core.location.mobility import WirelessModel + + +class ConfigGroup: + """ + Defines configuration group tabs used for display by ConfigurationOptions. + """ + + def __init__(self, name: str, start: int, stop: int) -> None: + """ + Creates a ConfigGroup object. + + :param str name: configuration group display name + :param int start: configurations start index for this group + :param int stop: configurations stop index for this group + """ + self.name = name + self.start = start + self.stop = stop + + +class Configuration: + """ + Represents a configuration options. + """ + + def __init__( + self, + _id: str, + _type: ConfigDataTypes, + label: str = None, + default: str = "", + options: List[str] = None, + ) -> None: + """ + Creates a Configuration object. + + :param str _id: unique name for configuration + :param core.enumerations.ConfigDataTypes _type: configuration data type + :param str label: configuration label for display + :param str default: default value for configuration + :param list options: list options if this is a configuration with a combobox + """ + self.id = _id + self.type = _type + self.default = default + if not options: + options = [] + self.options = options + if not label: + label = _id + self.label = label + + def __str__(self): + return f"{self.__class__.__name__}(id={self.id}, type={self.type}, default={self.default}, options={self.options})" + + +class ConfigurableOptions: + """ + Provides a base for defining configuration options within CORE. + """ + + name = None + bitmap = None + options = [] + + @classmethod + def configurations(cls) -> List[Configuration]: + """ + Provides the configurations for this class. + + :return: configurations + :rtype: list[Configuration] + """ + return cls.options + + @classmethod + def config_groups(cls) -> List[ConfigGroup]: + """ + Defines how configurations are grouped. + + :return: configuration group definition + :rtype: list[ConfigGroup] + """ + return [ConfigGroup("Options", 1, len(cls.configurations()))] + + @classmethod + def default_values(cls) -> Dict[str, str]: + """ + Provides an ordered mapping of configuration keys to default values. + + :return: ordered configuration mapping default values + :rtype: OrderedDict + """ + return OrderedDict( + [(config.id, config.default) for config in cls.configurations()] + ) class ConfigShim: @@ -14,7 +117,7 @@ class ConfigShim: """ @classmethod - def str_to_dict(cls, key_values): + def str_to_dict(cls, key_values: str) -> Dict[str, str]: """ Converts a TLV key/value string into an ordered mapping. @@ -30,7 +133,7 @@ class ConfigShim: return values @classmethod - def groups_to_str(cls, config_groups): + def groups_to_str(cls, config_groups: List[ConfigGroup]) -> str: """ Converts configuration groups to a TLV formatted string. @@ -47,7 +150,14 @@ class ConfigShim: return "|".join(group_strings) @classmethod - def config_data(cls, flags, node_id, type_flags, configurable_options, config): + def config_data( + cls, + flags: int, + node_id: int, + type_flags: int, + configurable_options: ConfigurableOptions, + config: Dict[str, str], + ) -> ConfigData: """ Convert this class to a Config API message. Some TLVs are defined by the class, but node number, conf type flags, and values must @@ -102,50 +212,22 @@ class ConfigShim: ) -class Configuration: - """ - Represents a configuration options. - """ - - def __init__(self, _id, _type, label=None, default="", options=None): - """ - Creates a Configuration object. - - :param str _id: unique name for configuration - :param core.enumerations.ConfigDataTypes _type: configuration data type - :param str label: configuration label for display - :param str default: default value for configuration - :param list options: list options if this is a configuration with a combobox - """ - self.id = _id - self.type = _type - self.default = default - if not options: - options = [] - self.options = options - if not label: - label = _id - self.label = label - - def __str__(self): - return f"{self.__class__.__name__}(id={self.id}, type={self.type}, default={self.default}, options={self.options})" - - class ConfigurableManager: """ - Provides convenience methods for storing and retrieving configuration options for nodes. + Provides convenience methods for storing and retrieving configuration options for + nodes. """ _default_node = -1 _default_type = _default_node - def __init__(self): + def __init__(self) -> None: """ Creates a ConfigurableManager object. """ self.node_configurations = {} - def nodes(self): + def nodes(self) -> List[int]: """ Retrieves the ids of all node configurations known by this manager. @@ -154,7 +236,7 @@ class ConfigurableManager: """ return [x for x in self.node_configurations if x != self._default_node] - def config_reset(self, node_id=None): + def config_reset(self, node_id: int = None) -> None: """ Clears all configurations or configuration for a specific node. @@ -166,7 +248,13 @@ class ConfigurableManager: elif node_id in self.node_configurations: self.node_configurations.pop(node_id) - def set_config(self, _id, value, node_id=_default_node, config_type=_default_type): + def set_config( + self, + _id: str, + value: str, + node_id: int = _default_node, + config_type: str = _default_type, + ) -> None: """ Set a specific configuration value for a node and configuration type. @@ -180,7 +268,12 @@ class ConfigurableManager: node_type_configs = node_configs.setdefault(config_type, OrderedDict()) node_type_configs[_id] = value - def set_configs(self, config, node_id=_default_node, config_type=_default_type): + def set_configs( + self, + config: Dict[str, str], + node_id: int = _default_node, + config_type: str = _default_type, + ) -> None: """ Set configurations for a node and configuration type. @@ -196,8 +289,12 @@ class ConfigurableManager: node_configs[config_type] = config def get_config( - self, _id, node_id=_default_node, config_type=_default_type, default=None - ): + self, + _id: str, + node_id: int = _default_node, + config_type: str = _default_type, + default: str = None, + ) -> str: """ Retrieves a specific configuration for a node and configuration type. @@ -214,7 +311,9 @@ class ConfigurableManager: result = node_type_configs.get(_id, default) return result - def get_configs(self, node_id=_default_node, config_type=_default_type): + def get_configs( + self, node_id: int = _default_node, config_type: str = _default_type + ) -> Dict[str, str]: """ Retrieve configurations for a node and configuration type. @@ -229,7 +328,7 @@ class ConfigurableManager: result = node_configs.get(config_type) return result - def get_all_configs(self, node_id=_default_node): + def get_all_configs(self, node_id: int = _default_node) -> List[Dict[str, str]]: """ Retrieve all current configuration types for a node. @@ -240,72 +339,12 @@ class ConfigurableManager: return self.node_configurations.get(node_id) -class ConfigGroup: - """ - Defines configuration group tabs used for display by ConfigurationOptions. - """ - - def __init__(self, name, start, stop): - """ - Creates a ConfigGroup object. - - :param str name: configuration group display name - :param int start: configurations start index for this group - :param int stop: configurations stop index for this group - """ - self.name = name - self.start = start - self.stop = stop - - -class ConfigurableOptions: - """ - Provides a base for defining configuration options within CORE. - """ - - name = None - bitmap = None - options = [] - - @classmethod - def configurations(cls): - """ - Provides the configurations for this class. - - :return: configurations - :rtype: list[Configuration] - """ - return cls.options - - @classmethod - def config_groups(cls): - """ - Defines how configurations are grouped. - - :return: configuration group definition - :rtype: list[ConfigGroup] - """ - return [ConfigGroup("Options", 1, len(cls.configurations()))] - - @classmethod - def default_values(cls): - """ - Provides an ordered mapping of configuration keys to default values. - - :return: ordered configuration mapping default values - :rtype: OrderedDict - """ - return OrderedDict( - [(config.id, config.default) for config in cls.configurations()] - ) - - class ModelManager(ConfigurableManager): """ Helps handle setting models for nodes and managing their model configurations. """ - def __init__(self): + def __init__(self) -> None: """ Creates a ModelManager object. """ @@ -313,7 +352,9 @@ class ModelManager(ConfigurableManager): self.models = {} self.node_models = {} - def set_model_config(self, node_id, model_name, config=None): + def set_model_config( + self, node_id: int, model_name: str, config: Dict[str, str] = None + ) -> None: """ Set configuration data for a model. @@ -341,7 +382,7 @@ class ModelManager(ConfigurableManager): # set configuration self.set_configs(model_config, node_id=node_id, config_type=model_name) - def get_model_config(self, node_id, model_name): + def get_model_config(self, node_id: int, model_name: str) -> Dict[str, str]: """ Retrieve configuration data for a model. @@ -363,7 +404,12 @@ class ModelManager(ConfigurableManager): return config - def set_model(self, node, model_class, config=None): + def set_model( + self, + node: Union[WlanNode, EmaneNet], + model_class: "WirelessModel", + config: Dict[str, str] = None, + ) -> None: """ Set model and model configuration for node. @@ -379,7 +425,9 @@ class ModelManager(ConfigurableManager): config = self.get_model_config(node.id, model_class.name) node.setmodel(model_class, config) - def get_models(self, node): + def get_models( + self, node: Union[WlanNode, EmaneNet] + ) -> List[Tuple[Type, Dict[str, str]]]: """ Return a list of model classes and values for a net if one has been configured. This is invoked when exporting a session to XML. diff --git a/daemon/core/emulator/session.py b/daemon/core/emulator/session.py index 5cff849b..36fd76f2 100644 --- a/daemon/core/emulator/session.py +++ b/daemon/core/emulator/session.py @@ -1611,14 +1611,14 @@ class Session: self.add_remove_control_interface(node=node, remove=False) self.services.boot_services(node) - def boot_nodes(self) -> List[ServiceBootError]: + def boot_nodes(self) -> List[Exception]: """ Invoke the boot() procedure for all nodes and send back node messages to the GUI for node messages that had the status request flag. :return: service boot exceptions - :rtype: list[core.services.coreservices.ServiceBootError] + :rtype: list[Exception] """ with self._nodes_lock: funcs = [] diff --git a/daemon/core/errors.py b/daemon/core/errors.py index f5c38b5b..319bd190 100644 --- a/daemon/core/errors.py +++ b/daemon/core/errors.py @@ -9,7 +9,7 @@ class CoreCommandError(subprocess.CalledProcessError): Used when encountering internal CORE command errors. """ - def __str__(self): + def __str__(self) -> str: return ( f"Command({self.cmd}), Status({self.returncode}):\n" f"stdout: {self.output}\nstderr: {self.stderr}" diff --git a/daemon/core/utils.py b/daemon/core/utils.py index cf394a15..8c94c717 100644 --- a/daemon/core/utils.py +++ b/daemon/core/utils.py @@ -15,15 +15,33 @@ import random import shlex import sys from subprocess import PIPE, STDOUT, Popen +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Tuple, + Type, + Union, +) import netaddr from core.errors import CoreCommandError, CoreError +if TYPE_CHECKING: + from core.emulator.session import Session + from core.nodes.base import CoreNode + DEVNULL = open(os.devnull, "wb") -def execute_file(path, exec_globals=None, exec_locals=None): +def execute_file( + path: str, exec_globals: Dict[str, str] = None, exec_locals: Dict[str, str] = None +) -> None: """ Provides an alternative way to run execfile to be compatible for both python2/3. @@ -41,7 +59,7 @@ def execute_file(path, exec_globals=None, exec_locals=None): exec(data, exec_globals, exec_locals) -def hashkey(value): +def hashkey(value: Union[str, int]) -> int: """ Provide a consistent hash that can be used in place of the builtin hash, that no longer behaves consistently @@ -57,7 +75,7 @@ def hashkey(value): return int(hashlib.sha256(value).hexdigest(), 16) -def _detach_init(): +def _detach_init() -> None: """ Fork a child process and exit. @@ -69,7 +87,7 @@ def _detach_init(): os.setsid() -def _valid_module(path, file_name): +def _valid_module(path: str, file_name: str) -> bool: """ Check if file is a valid python module. @@ -91,7 +109,7 @@ def _valid_module(path, file_name): return True -def _is_class(module, member, clazz): +def _is_class(module: Any, member: Type, clazz: Type) -> bool: """ Validates if a module member is a class and an instance of a CoreService. @@ -113,7 +131,7 @@ def _is_class(module, member, clazz): return True -def close_onexec(fd): +def close_onexec(fd: int) -> None: """ Close on execution of a shell process. @@ -124,7 +142,7 @@ def close_onexec(fd): fcntl.fcntl(fd, fcntl.F_SETFD, fdflags | fcntl.FD_CLOEXEC) -def which(command, required): +def which(command: str, required: bool) -> str: """ Find location of desired executable within current PATH. @@ -146,7 +164,7 @@ def which(command, required): return found_path -def make_tuple(obj): +def make_tuple(obj: Any) -> Tuple[Any]: """ Create a tuple from an object, or return the object itself. @@ -160,7 +178,7 @@ def make_tuple(obj): return (obj,) -def make_tuple_fromstr(s, value_type): +def make_tuple_fromstr(s: str, value_type: Callable) -> Tuple[Any]: """ Create a tuple from a string. @@ -179,7 +197,7 @@ def make_tuple_fromstr(s, value_type): return tuple(value_type(i) for i in values) -def mute_detach(args, **kwargs): +def mute_detach(args: List[str], **kwargs: Dict[str, Any]) -> int: """ Run a muted detached process by forking it. @@ -195,7 +213,13 @@ def mute_detach(args, **kwargs): return Popen(args, **kwargs).pid -def cmd(args, env=None, cwd=None, wait=True, shell=False): +def cmd( + args: str, + env: Dict[str, str] = None, + cwd: str = None, + wait: bool = True, + shell: bool = False, +) -> str: """ Execute a command on the host and return a tuple containing the exit status and result string. stderr output is folded into the stdout result string. @@ -227,7 +251,7 @@ def cmd(args, env=None, cwd=None, wait=True, shell=False): raise CoreCommandError(-1, args) -def file_munge(pathname, header, text): +def file_munge(pathname: str, header: str, text: str) -> None: """ Insert text at the end of a file, surrounded by header comments. @@ -245,7 +269,7 @@ def file_munge(pathname, header, text): append_file.write(f"# END {header}\n") -def file_demunge(pathname, header): +def file_demunge(pathname: str, header: str) -> None: """ Remove text that was inserted in a file surrounded by header comments. @@ -273,7 +297,9 @@ def file_demunge(pathname, header): write_file.write("".join(lines)) -def expand_corepath(pathname, session=None, node=None): +def expand_corepath( + pathname: str, session: "Session" = None, node: "CoreNode" = None +) -> str: """ Expand a file path given session information. @@ -296,7 +322,7 @@ def expand_corepath(pathname, session=None, node=None): return pathname -def sysctl_devname(devname): +def sysctl_devname(devname: str) -> Optional[str]: """ Translate a device name to the name used with sysctl. @@ -309,7 +335,7 @@ def sysctl_devname(devname): return devname.replace(".", "/") -def load_config(filename, d): +def load_config(filename: str, d: Dict[str, str]) -> None: """ Read key=value pairs from a file, into a dict. Skip comments; strip newline characters and spacing. @@ -332,7 +358,7 @@ def load_config(filename, d): logging.exception("error reading file to dict: %s", filename) -def load_classes(path, clazz): +def load_classes(path: str, clazz: Type) -> List[Type]: """ Dynamically load classes for use within CORE. @@ -375,7 +401,7 @@ def load_classes(path, clazz): return classes -def load_logging_config(config_path): +def load_logging_config(config_path: str) -> None: """ Load CORE logging configuration file. @@ -387,7 +413,9 @@ def load_logging_config(config_path): logging.config.dictConfig(log_config) -def threadpool(funcs, workers=10): +def threadpool( + funcs: List[Tuple[Callable, Iterable[Any], Dict[Any, Any]]], workers: int = 10 +) -> Tuple[List[Any], List[Exception]]: """ Run provided functions, arguments, and keywords within a threadpool collecting results and exceptions. @@ -413,7 +441,7 @@ def threadpool(funcs, workers=10): return results, exceptions -def random_mac(): +def random_mac() -> str: """ Create a random mac address using Xen OID 00:16:3E. @@ -427,7 +455,7 @@ def random_mac(): return str(mac) -def validate_mac(value): +def validate_mac(value: str) -> str: """ Validate mac and return unix formatted version. @@ -443,7 +471,7 @@ def validate_mac(value): raise CoreError(f"invalid mac address {value}: {e}") -def validate_ip(value): +def validate_ip(value: str) -> str: """ Validate ip address with prefix and return formatted version.