Ejemplo n.º 1
0
    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
Ejemplo n.º 2
0
 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
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
    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."
                    ))
Ejemplo n.º 6
0
 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
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
    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
Ejemplo n.º 9
0
    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)
Ejemplo n.º 10
0
    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
Ejemplo n.º 11
0
 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()
Ejemplo n.º 12
0
    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
Ejemplo n.º 13
0
    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*$')