def differences_between_devices_descriptions(cls, already_device_name, requested_device_def): """ Checks if two device description are the same. :param already_device_name: Name of device already created by Moler :param requested_device_def: Description od device provided to create. The name is the same as above. :return: Empty string if descriptions are the same, if not the string with differences. """ already_created_device = cls.get_device(already_device_name, establish_connection=False) already_device_def = copy_dict( DeviceFactory._devices_params[already_created_device.name], True) different_msg = "" already_full_class = already_device_def['class_fullname'] current_full_class = requested_device_def['DEVICE_CLASS'] if already_full_class == current_full_class: default_hops = dict() already_hops = already_device_def['constructor_parameters'][ 'sm_params'].get('CONNECTION_HOPS', default_hops) current_hops = requested_device_def.get('CONNECTION_HOPS', default_hops) diff = compare_objects(already_hops, current_hops) if diff: different_msg = "Device '{}' already created with SM parameters: '{}' but now requested with SM" \ " params: {}. \nDiff: {}".format(already_device_name, already_hops, current_hops, diff) else: different_msg = "Device '{}' already created as instance of class '{}' and now requested as instance of " \ "class '{}'".format(already_device_name, already_full_class, current_full_class) return different_msg
def get_cmd(self, cmd_name, cmd_params=None, check_state=True, for_state=None): """ Returns instance of command connected with the device. :param cmd_name: name of commands, name of class (without package), for example "cd". :param cmd_params: dict with command parameters. :param check_state: if True then before execute of command the state of device will be check if the same as when command was created. If False the device state is not checked. :param for_state: if None then command object for current state is returned, otherwise object for for_state is returned. :return: Instance of command """ cmd_params = copy_dict(cmd_params) if "prompt" not in cmd_params: cmd_params["prompt"] = self.get_prompt() cmd = self.get_observer(observer_name=cmd_name, observer_type=TextualDevice.cmds, observer_exception=CommandWrongState, check_state=check_state, for_state=for_state, **cmd_params) assert isinstance(cmd, CommandTextualGeneric) return cmd
def test_unix_remote_proxy_pc_device_multiple_prompts( device_connection, unix_remote_proxy_pc_output): unix_remote_proxy_pc_changed_output = copy_dict( unix_remote_proxy_pc_output, deep_copy=True) combined_line = "moler_bash#" for src_state in unix_remote_proxy_pc_output.keys(): for cmd_string in unix_remote_proxy_pc_output[src_state].keys(): combined_line = "{} {}".format( combined_line, unix_remote_proxy_pc_output[src_state][cmd_string]) for src_state in unix_remote_proxy_pc_changed_output.keys(): for cmd_string in unix_remote_proxy_pc_changed_output[src_state].keys( ): unix_remote_proxy_pc_changed_output[src_state][ cmd_string] = combined_line unix_remote_proxy_pc = get_device( name="UNIX_REMOTE_PROXY_PC", connection=device_connection, device_output=unix_remote_proxy_pc_changed_output, test_file_path=__file__) assert unix_remote_proxy_pc._check_all_prompts_on_line is True assert unix_remote_proxy_pc._prompts_event.check_against_all_prompts is True with pytest.raises(MolerException) as exception: iterate_over_device_states(device=unix_remote_proxy_pc, max_no_of_threads=0) assert "More than 1 prompt match the same line" in str(exception.value)
def test_copy_dict(): from moler.helpers import copy_dict src = {'a': 1} dst = copy_dict(src, deep_copy=True) assert src == dst dst['a'] = 2 assert src != dst
def __init__(self, connection, password, cmd_object=None, cmd_class_name=None, cmd_params=None, prompt=None, newline_chars=None, runner=None, encrypt_password=True): """ Constructs object for Unix command sudo. :param connection: Moler connection to device, terminal when command is executed. :param password: password for sudo. :param cmd_object: object of command. Pass this object or cmd_class_name. :param cmd_class_name: full (with package) class name. Pass this name or cmd_object. :param cmd_params: params for cmd_class_name. If cmd_object is passed this parameter is ignored. :param prompt: prompt (on system where command runs). :param newline_chars: Characters to split lines - list. :param runner: Runner to run command. :param encrypt_password: If True then * will be in logs when password is sent, otherwise plain text. """ super(Sudo, self).__init__(connection=connection, prompt=prompt, newline_chars=newline_chars, runner=runner) self.password = password self.cmd_object = cmd_object self.encrypt_password = encrypt_password self._sent_sudo_password = False self._sent_command_string = False self.newline_seq = "\n" if cmd_object and cmd_class_name: self.set_exception( CommandFailure( self, "both 'cmd_object' and 'cmd_class_name' parameters provided. Please specify only one. " )) return if cmd_class_name: params = copy_dict(cmd_params) params["connection"] = connection params['prompt'] = prompt params["newline_chars"] = newline_chars try: self.cmd_object = create_object_from_name( cmd_class_name, params) except Exception as ex: self.set_exception(ex) else: if not self.cmd_object: self.set_exception( CommandFailure( self, "Neither 'cmd_class_name' nor 'cmd_object' was provided to Sudo constructor. Please specific parameter." ))
def _parameters_without_timeout(cls, parameters): """ Remove timeout from observable parameters. :param parameters: observable parameters. :return: new parameters without timeout. """ if 'timeout' in parameters: parameters = copy_dict(src=parameters, deep_copy=True) del parameters['timeout'] return parameters
def load_config(config=None, from_env_var=None, *args, **kwargs): """ Load Moler's configuration from config file. Load Moler's configuration from config file. :param config: either dict or config filename directly provided (overwrites 'from_env_var' if both given). :type config: dict or str :param from_env_var: name of environment variable storing config filename (file is in YAML format) :return: None """ global loaded_config add_devices_only = False from moler.device import DeviceFactory if config is not None and isinstance(config, dict): config = copy_dict(config, deep_copy=True) if "NOT_LOADED_YET" in loaded_config: loaded_config = [config] elif not DeviceFactory.was_any_device_deleted() and configs_are_same( config_list=loaded_config, config_to_find=config): return else: # Config was already loaded and now we have to add new devices. add_devices_only = True if not configs_are_same(config_list=loaded_config, config_to_find=config): loaded_config.append(config) wrong_type_config = not isinstance( config, six.string_types) and not isinstance(config, dict) if config is None and from_env_var is None: raise WrongUsage( "Provide either 'config' or 'from_env_var' parameter (none given)." ) elif (not from_env_var and wrong_type_config) or (config and wrong_type_config): raise WrongUsage( "Unsupported config type: '{}'. Allowed are: 'dict' or 'str' holding config filename (file is in YAML format)." .format(type(config))) if not config: if from_env_var not in os.environ: raise KeyError( "Environment variable '{}' is not set".format(from_env_var)) path = os.environ[from_env_var] config = read_yaml_configfile(path) elif isinstance(config, six.string_types): path = config config = read_yaml_configfile(path) # TODO: check schema if add_devices_only is False: load_logger_from_config(config) load_connection_from_config(config) load_device_from_config(config=config, add_only=add_devices_only)
def get_event(self, event_name, event_params=None, check_state=True): """ Return instance of event connected with the device. :param event_name: name of event, name of class (without package). :param event_params: dict with event parameters. :param check_state: if True then before execute of event the state of device will be check if the same as when event was created. If False the device state is not checked. :return: """ event_params = copy_dict(event_params) event = self.get_observer(observer_name=event_name, observer_type=TextualDevice.events, observer_exception=EventWrongState, check_state=check_state, **event_params) return event
def _build_command_object(self): """ Builds command object from passed parameters to sudo command. :return: None """ self._validate_passed_object_or_command_parameters() if self.cmd_object: return else: params = copy_dict(self.cmd_params) params["connection"] = self.connection params['prompt'] = self._re_prompt params["newline_chars"] = self._newline_chars self.cmd_object = create_object_from_name(self.cmd_class_name, params)
def _validate_start(self, *args, **kwargs): """ Validates internal data before start. :param args: args passed to super _validate_start :param kwargs: kwargs passed to super _validate_start :return: None. :raises: CommandFailure if error in command settings. """ super(Sudo, self)._validate_start(*args, **kwargs) if self.cmd_object and self.cmd_class_name: # _validate_start is called before running command on connection, so we raise exception instead # of setting it raise CommandFailure( self, "both 'cmd_object' and 'cmd_class_name' parameters provided. Please specify only one." ) if self.cmd_class_name: params = copy_dict(self.cmd_params) params["connection"] = self.connection params['prompt'] = self._re_prompt params["newline_chars"] = self._newline_chars self.cmd_object = create_object_from_name(self.cmd_class_name, params) else: if not self.cmd_object: # _validate_start is called before running command on connection, so we raise exception # instead of setting it raise CommandFailure( self, "Neither 'cmd_class_name' nor 'cmd_object' was provided to Sudo constructor." "Please specific parameter.") if self.cmd_object and self.cmd_object.done(): # _validate_start is called before running command on connection, so we raise exception # instead of setting it raise CommandFailure( self, "Not allowed to run again the embeded command (embeded command is done): {}." .format(self.cmd_object)) self.ret_required = self.cmd_object.ret_required if self.timeout_from_embedded_command: self.timeout = self.cmd_object.timeout
def _parse_prompts(self, line): current_ret = None for prompt_regex in sorted(self.compiled_prompts_regex.keys(), key=attrgetter('pattern')): if self._regex_helper.search_compiled(prompt_regex, line): current_ret = { 'line': line, 'prompt_regex': prompt_regex.pattern, 'state': self.compiled_prompts_regex[prompt_regex], 'time': datetime.datetime.now() } if self.check_against_all_prompts: self._ret_list_matched.append(copy_dict(current_ret)) else: break if current_ret: if self.check_against_all_prompts: current_ret['list_matched'] = self._ret_list_matched self._ret_list_matched = list() self.event_occurred(event_data=current_ret) raise ParsingDone()
def __init__(self, sm_params=None, name=None, io_connection=None, io_type=None, variant=None, io_constructor_kwargs=None, initial_state=None, lazy_cmds_events=False): """ Create Device communicating over io_connection CAUTION: Device owns (takes over ownership) of connection. It will be open when device "is born" and close when device "dies". :param sm_params: dict with parameters of state machine for device :param name: name of device :param io_connection: External-IO connection having embedded moler-connection :param io_type: type of connection - tcp, udp, ssh, telnet, ... :param variant: connection implementation variant, ex. 'threaded', 'twisted', 'asyncio', ... (if not given then default one is taken) :param io_constructor_kwargs: additional parameter into constructor of selected connection type (if not given then default one is taken) :param initial_state: name of initial state. State machine tries to enter this state just after creation. :param lazy_cmds_events: set False to load all commands and events when device is initialized, set True to load commands and events when they are required for the first time. """ super(TextualDevice, self).__init__() if io_constructor_kwargs is None: io_constructor_kwargs = dict() sm_params = copy_dict(sm_params, deep_copy=True) io_constructor_kwargs = copy_dict(io_constructor_kwargs, deep_copy=True) self.initial_state = initial_state if initial_state is not None else "NOT_CONNECTED" self.states = [TextualDevice.not_connected] self.goto_states_triggers = [] self._name = name self.device_data_logger = None self.timeout_keep_state = 10 # Timeout for background goto state after unexpected state change. self.lazy_cmds_events = lazy_cmds_events # Set True to lazy load commands and events. # Below line will modify self extending it with methods and attributes od StateMachine # For eg. it will add attribute self.state self.SM = StateMachine(model=self, states=self.states, initial=TextualDevice.not_connected, auto_transitions=False, queued=True) self._state_hops = dict() self._state_prompts = dict() self._state_prompts_lock = threading.Lock() self._reverse_state_prompts_dict = dict() self._prompts_event = None self._kept_state = None self._configurations = dict() self._newline_chars = dict() # key is state, value is chars to send as newline if io_connection: self.io_connection = io_connection else: self.io_connection = get_connection(io_type=io_type, variant=variant, **io_constructor_kwargs) self.io_connection.name = self.name self.io_connection.moler_connection.name = self.name self.logger = logging.getLogger('moler.connection.{}'.format(self.name)) self.configure_logger(name=self.name, propagate=False) self._prepare_transitions() self._prepare_state_hops() self._configure_state_machine(sm_params) self._prepare_newline_chars() # TODO: Need test to ensure above sentence for all connection self.io_connection.notify(callback=self.on_connection_made, when="connection_made") self.io_connection.notify(callback=self.on_connection_lost, when="connection_lost") self._cmdnames_available_in_state = dict() self._eventnames_available_in_state = dict() self._default_prompt = re.compile(r'^[^<]*[\$|%|#|>|~]\s*$') self._neighbour_devices = None self._established = False msg = "Created device '{}' as instance of class '{}.{}'.".format( self.name, self.__class__.__module__, self.__class__.__name__, ) self._log(level=logging.DEBUG, msg=msg) self._public_name = None self._warning_was_sent = False self._goto_state_lock = threading.Lock() self._goto_state_thread_manipulation_lock = threading.Lock() self._queue_states = queue.Queue() self._thread_for_goto_state = None self.SM.state_change_log_callable = self._log self.SM.current_state_callable = self._get_current_state
def __init__(self, sm_params=None, name=None, io_connection=None, io_type=None, variant=None, io_constructor_kwargs={}, initial_state=None): """ Create Device communicating over io_connection CAUTION: Device owns (takes over ownership) of connection. It will be open when device "is born" and close when device "dies". :param sm_params: dict with parameters of state machine for device :param name: name of device :param io_connection: External-IO connection having embedded moler-connection :param io_type: type of connection - tcp, udp, ssh, telnet, ... :param variant: connection implementation variant, ex. 'threaded', 'twisted', 'asyncio', ... (if not given then default one is taken) :param io_constructor_kwargs: additional parameter into constructor of selected connection type (if not given then default one is taken) :param initial_state: name of initial state. State machine tries to enter this state just after creation. """ sm_params = copy_dict(sm_params, deep_copy=True) io_constructor_kwargs = copy_dict(io_constructor_kwargs, deep_copy=True) self.initial_state = initial_state if initial_state is not None else "NOT_CONNECTED" self.states = [TextualDevice.not_connected] self.goto_states_triggers = [] self._name = name self.device_data_logger = None # Below line will modify self extending it with methods and atributes od StateMachine # For eg. it will add attribute self.state self.SM = StateMachine(model=self, states=self.states, initial=TextualDevice.not_connected, auto_transitions=False, queued=True) self._state_hops = {} self._state_prompts = {} self._prompts_events = {} self._configurations = dict() self._newline_chars = dict( ) # key is state, value is chars to send as newline if io_connection: self.io_connection = io_connection else: self.io_connection = get_connection(io_type=io_type, variant=variant, **io_constructor_kwargs) self.io_connection.name = self.name self.io_connection.moler_connection.name = self.name self.logger = logging.getLogger('moler.connection.{}'.format( self.name)) self.configure_logger(name=self.name, propagate=False) self._prepare_transitions() self._prepare_state_hops() self._configure_state_machine(sm_params) self._prepare_newline_chars() # TODO: Need test to ensure above sentence for all connection self.io_connection.notify(callback=self.on_connection_made, when="connection_made") self.io_connection.notify(callback=self.on_connection_lost, when="connection_lost") self.io_connection.open() self._cmdnames_available_in_state = dict() self._eventnames_available_in_state = dict() self._collect_cmds_for_state_machine() self._collect_events_for_state_machine() self._run_prompts_observers() self._default_prompt = re.compile(r'^[^<]*[\$|%|#|>|~]\s*$')