From c9864a4fa4597763ef69f88cd43bfac62253a1d8 Mon Sep 17 00:00:00 2001 From: Marcin Usielski Date: Tue, 22 Oct 2024 12:15:20 +0000 Subject: [PATCH] ProxyPC - Juniper --- moler/device/juniper_ex3.py | 59 ++++++ moler/device/junipergeneric.py | 5 +- moler/device/junipergeneric3.py | 237 +++++++++++++++++++++++ moler/device/proxy_pc3.py | 302 ++++++++++++++++++++++++++++++ moler/device/unixremote3.py | 2 +- test/device/test_SM_juniper_ex.py | 32 +++- test/resources/device_config.yml | 38 ++++ 7 files changed, 666 insertions(+), 9 deletions(-) create mode 100644 moler/device/juniper_ex3.py create mode 100644 moler/device/junipergeneric3.py create mode 100644 moler/device/proxy_pc3.py diff --git a/moler/device/juniper_ex3.py b/moler/device/juniper_ex3.py new file mode 100644 index 000000000..0aab2e814 --- /dev/null +++ b/moler/device/juniper_ex3.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +""" +JuniperEX module. +""" + +__author__ = 'Sylwester Golonka, Jakub Kupiec, Marcin Usielski' +__copyright__ = 'Copyright (C) 2019-2024, Nokia' +__email__ = 'sylwester.golonka@nokia.com, jakub.kupiec@nokia.com, marcin.usielski@nokia.com' + +from moler.device.junipergeneric3 import JuniperGeneric3 +from moler.helpers import call_base_class_method_with_same_name + + +@call_base_class_method_with_same_name +class JuniperEX3(JuniperGeneric3): + r""" + Juniperex device class. + + + :: + + + Example of device in yaml configuration file: + - with PROXY_PC: + JUNIPER_EX_PROXY_PC: + DEVICE_CLASS: moler.device.juniper_ex3.JuniperEX3 + CONNECTION_HOPS: + PROXY_PC: + CLI: + execute_command: ssh + command_params: + host: cli_host + login: cli_login + password: password + UNIX_LOCAL: + PROXY_PC: + execute_command: ssh + command_params: + expected_prompt: "proxy_pc#" + host: proxy_pc_host + login: proxy_pc_login + password: password + CLI: + PROXY_PC: + execute_command: exit + command_params: + expected_prompt: "proxy_pc#" + - without PROXY_PC: + JUNIPER_EX: + DEVICE_CLASS: moler.device.juniper_ex3.JuniperEX3 + CONNECTION_HOPS: + UNIX_LOCAL: + CLI: + execute_command: ssh # default value + command_params: + host: cli_host + login: cli_login + password: password + """ diff --git a/moler/device/junipergeneric.py b/moler/device/junipergeneric.py index 05f4a71d1..375dba8f6 100644 --- a/moler/device/junipergeneric.py +++ b/moler/device/junipergeneric.py @@ -124,8 +124,10 @@ def _get_default_sm_configuration_without_proxy_pc(self): JuniperGeneric.unix_local: { # to "execute_command": "exit", # using command "command_params": { # with parameters - "expected_prompt": r'^moler_bash#' + "expected_prompt": r'^moler_bash#', + "target_newline": "\n", }, + "required_command_params": [], }, JuniperGeneric.configure: { "execute_command": "configure", @@ -313,6 +315,7 @@ def _prepare_state_hops_without_proxy_pc(self): JuniperGeneric.configure: JuniperGeneric.cli, }, JuniperGeneric.unix_local_root: { + JuniperGeneric.not_connected: JuniperGeneric.unix_local, JuniperGeneric.cli: JuniperGeneric.unix_local, JuniperGeneric.configure: JuniperGeneric.unix_local, }, diff --git a/moler/device/junipergeneric3.py b/moler/device/junipergeneric3.py new file mode 100644 index 000000000..93c490b1c --- /dev/null +++ b/moler/device/junipergeneric3.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- +""" +Juniper Generic module. +""" + +__author__ = 'Sylwester Golonka, Jakub Kupiec, Marcin Usielski' +__copyright__ = 'Copyright (C) 2019-2024, Nokia' +__email__ = 'sylwester.golonka@nokia.com, jakub.kupiec@nokia.com, marcin.usielski@nokia.com' + +import logging +from abc import ABCMeta +from six import add_metaclass +from moler.device.proxy_pc3 import ProxyPc3 +from moler.helpers import call_base_class_method_with_same_name, mark_to_call_base_class_method_with_same_name + + +@call_base_class_method_with_same_name +@add_metaclass(ABCMeta) +class JuniperGeneric3(ProxyPc3): + """Junipergeneric device class.""" + + cli = "CLI" + configure = "CONFIGURE" + + def __init__(self, sm_params, name=None, io_connection=None, io_type=None, variant=None, + io_constructor_kwargs=None, initial_state=None, lazy_cmds_events=False): + """ + Create unix device communicating over io_connection. + + :param sm_params: params with machine state description. + :param name: name of device. + :param io_connection: External-IO connection having embedded moler-connection + :param io_type: External-IO connection connection type + :param variant: External-IO connection variant + :param io_constructor_kwargs: additional parameters for constructor of selected io_type + :param initial_state: Initial state for device + :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. + """ + sm_params = sm_params.copy() + initial_state = initial_state if initial_state is not None else JuniperGeneric3.cli + super(JuniperGeneric3, self).__init__(sm_params=sm_params, name=name, io_connection=io_connection, + io_type=io_type, variant=variant, + io_constructor_kwargs=io_constructor_kwargs, + initial_state=initial_state, lazy_cmds_events=lazy_cmds_events) + self.logger = logging.getLogger('moler.juniper') + + @mark_to_call_base_class_method_with_same_name + def _get_default_sm_configuration_with_proxy_pc(self): + """ + Return State Machine default configuration with proxy_pc state. + + :return: default sm configuration with proxy_pc state. + """ + config = { + JuniperGeneric3.connection_hops: { + JuniperGeneric3.proxy_pc: { # from + JuniperGeneric3.cli: { # to + "execute_command": "ssh", # using command + "command_params": { # with parameters + "set_timeout": None, + "expected_prompt": "^admin@switch>" + }, + "required_command_params": [ + "host", + "login", + "password", + ] + } + }, + JuniperGeneric3.cli: { # from + JuniperGeneric3.proxy_pc: { # to + "execute_command": "exit", # using command + "command_params": { # with parameters + }, + "required_command_params": [ + "expected_prompt" + ] + }, + JuniperGeneric3.configure: { + "execute_command": "configure", + "command_params": { + "expected_prompt": "^admin@switch#" + } + } + }, + JuniperGeneric3.configure: { # from + JuniperGeneric3.cli: { # to + "execute_command": "exit_configure", # using command + "command_params": { # with parameters + "expected_prompt": "^admin@switch>" + } + }, + } + + } + } + return config + + @mark_to_call_base_class_method_with_same_name + def _prepare_transitions_with_proxy_pc(self): + """ + Prepare transitions to change states with proxy_pc state. + + :return: transitions with proxy_pc state. + """ + + transitions = { + JuniperGeneric3.proxy_pc: { + JuniperGeneric3.cli: { + "action": [ + "_execute_command_to_change_state" + ], + }, + }, + JuniperGeneric3.cli: { + + JuniperGeneric3.proxy_pc: { + "action": [ + "_execute_command_to_change_state" + ], + }, + JuniperGeneric3.configure: { + "action": [ + "_execute_command_to_change_state" + ], + }, + }, + JuniperGeneric3.configure: { + JuniperGeneric3.cli: { + "action": [ + "_execute_command_to_change_state" + ], + } + } + } + return transitions + + @mark_to_call_base_class_method_with_same_name + def _prepare_state_prompts_with_proxy_pc(self): + """ + Prepare textual prompt for each state for State Machine with proxy_pc state. + + :return: textual prompt for each state with proxy_pc state. + """ + state_prompts = { + JuniperGeneric3.cli: + self._configurations[JuniperGeneric3.connection_hops][JuniperGeneric3.proxy_pc][ + JuniperGeneric3.cli][ + "command_params"]["expected_prompt"], + JuniperGeneric3.configure: + self._configurations[JuniperGeneric3.connection_hops][JuniperGeneric3.cli][JuniperGeneric3.configure][ + "command_params"]["expected_prompt"] + } + return state_prompts + + @mark_to_call_base_class_method_with_same_name + def _prepare_state_prompts_without_proxy_pc(self): + """ + Prepare textual prompt for each state for State Machine without proxy_pc state. + + :return: textual prompt for each state without proxy_pc state. + """ + state_prompts = { + JuniperGeneric3.cli: + self._configurations[JuniperGeneric3.connection_hops][JuniperGeneric3.unix_local][JuniperGeneric3.cli][ + "command_params"]["expected_prompt"], + JuniperGeneric3.configure: + self._configurations[JuniperGeneric3.connection_hops][JuniperGeneric3.cli][JuniperGeneric3.configure][ + "command_params"]["expected_prompt"] + } + return state_prompts + + @mark_to_call_base_class_method_with_same_name + def _prepare_state_hops_with_proxy_pc(self): + """ + Prepare non direct transitions for each state for State Machine with proxy_pc state. + + :return: non direct transitions for each state with proxy_pc state. + """ + state_hops = { + JuniperGeneric3.not_connected: { + JuniperGeneric3.cli: JuniperGeneric3.unix_local, + JuniperGeneric3.configure: JuniperGeneric3.unix_local, + }, + JuniperGeneric3.cli: { + JuniperGeneric3.not_connected: JuniperGeneric3.proxy_pc, + JuniperGeneric3.unix_local: JuniperGeneric3.proxy_pc, + JuniperGeneric3.unix_local_root: JuniperGeneric3.proxy_pc, + }, + JuniperGeneric3.configure: { + JuniperGeneric3.unix_local: JuniperGeneric3.cli, + JuniperGeneric3.proxy_pc: JuniperGeneric3.cli, + JuniperGeneric3.not_connected: JuniperGeneric3.cli, + JuniperGeneric3.unix_local_root: JuniperGeneric3.cli + }, + JuniperGeneric3.unix_local: { + JuniperGeneric3.cli: JuniperGeneric3.proxy_pc, + JuniperGeneric3.configure: JuniperGeneric3.proxy_pc, + }, + JuniperGeneric3.unix_local_root: { + JuniperGeneric3.not_connected: JuniperGeneric3.unix_local, + JuniperGeneric3.cli: JuniperGeneric3.unix_local, + JuniperGeneric3.configure: JuniperGeneric3.unix_local, + }, + JuniperGeneric3.proxy_pc: { + JuniperGeneric3.not_connected: JuniperGeneric3.unix_local, + JuniperGeneric3.configure: JuniperGeneric3.cli, + } + } + return state_hops + + def _get_packages_for_state(self, state, observer): + """ + Get available packages contain cmds and events for each state. + + :param state: device state. + :param observer: observer type, available: cmd, events + :return: available cmds or events for specific device state. + """ + available = super(JuniperGeneric3, self)._get_packages_for_state(state, observer) + + if not available: + if state == JuniperGeneric3.cli: + available = { + JuniperGeneric3.cmds: ['moler.cmd.unix', 'moler.cmd.juniper.cli', 'moler.cmd.juniper_ex.cli'], + JuniperGeneric3.events: ['moler.events.unix', 'moler.events.juniper', 'moler.events.juniper_ex']} + elif state == JuniperGeneric3.configure: + available = { + JuniperGeneric3.cmds: ['moler.events.unix', 'moler.cmd.juniper.configure', + 'moler.cmd.juniper_ex.configure'], + JuniperGeneric3.events: ['moler.events.unix', 'moler.events.juniper', 'moler.events.juniper_ex']} + + if available: + return available[observer] + + return available diff --git a/moler/device/proxy_pc3.py b/moler/device/proxy_pc3.py new file mode 100644 index 000000000..1fb5b261c --- /dev/null +++ b/moler/device/proxy_pc3.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- +""" +Moler's device has 2 main responsibilities: +- be the factory that returns commands of that device +- be the state machine that controls which commands may run in given state +""" + +__author__ = 'Marcin Usielski' +__copyright__ = 'Copyright (C) 2024, Nokia' +__email__ = 'marcin.usielski@nokia.com' +import six +import abc +import logging + +from moler.device.unixlocal import UnixLocal +from moler.helpers import remove_state_from_sm, remove_state_hops_from_sm + + +@six.add_metaclass(abc.ABCMeta) +class ProxyPc3(UnixLocal): + proxy_pc = "PROXY_PC" + + def __init__(self, sm_params, name=None, io_connection=None, io_type=None, variant=None, io_constructor_kwargs=None, + initial_state=None, lazy_cmds_events=False): + """ + Create Unix device communicating over io_connection + :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. + """ + initial_state = initial_state if initial_state is not None else ProxyPc3.unix_local + self._use_proxy_pc = self._is_proxy_pc_in_sm_params(sm_params, ProxyPc3.proxy_pc) + super(ProxyPc3, self).__init__(name=name, io_connection=io_connection, + io_type=io_type, variant=variant, + io_constructor_kwargs=io_constructor_kwargs, + sm_params=sm_params, initial_state=initial_state, + lazy_cmds_events=lazy_cmds_events) + self._log(level=logging.WARNING, msg="Experimental device. May be deleted at any moment. Please don't use it in your scripts.") + + + def _prepare_sm_data(self, sm_params): + self._prepare_dicts_for_sm(sm_params=sm_params) + + self._prepare_newline_chars() + self._send_transitions_to_sm(self._stored_transitions) + + + def _prepare_transitions(self): + """ + Prepare transitions to change states. + :return: None. + """ + + stored_is_proxy_pc = self._use_proxy_pc + self._use_proxy_pc = True + super(ProxyPc3, self)._prepare_transitions() + self._use_proxy_pc = stored_is_proxy_pc + transitions = self._prepare_transitions_with_proxy_pc() + self._add_transitions(transitions=transitions) + + def _prepare_dicts_for_sm(self, sm_params): + """ + Prepare transitions to change states. + :return: None. + """ + + self._prepare_transitions() + transitions = self._stored_transitions + state_hops = self._prepare_state_hops_with_proxy_pc() + + default_sm_configurations = self._get_default_sm_configuration() + + if not self._use_proxy_pc: + (connection_hops, transitions) = remove_state_from_sm( + source_sm=default_sm_configurations[ProxyPc3.connection_hops], + source_transitions=transitions, + state_to_remove=ProxyPc3.proxy_pc, + ) + state_hops = remove_state_hops_from_sm( + source_hops=state_hops, state_to_remove=ProxyPc3.proxy_pc + ) + default_sm_configurations[ProxyPc3.connection_hops] = connection_hops + + self._stored_transitions = transitions + self._update_dict(self._state_hops, state_hops) + + self._configurations = self._prepare_sm_configuration( + default_sm_configurations, sm_params + ) + self._overwrite_prompts() + self._validate_device_configuration() + self._prepare_state_prompts() + + + def _overwrite_prompts(self) -> None: + """ + Method to overwrite prompts in commands. + :return: None + """ + + def _get_default_sm_configuration(self): + """ + Create State Machine default configuration. + :return: default sm configuration. + """ + config = super(ProxyPc3, self)._get_default_sm_configuration() + default_config = self._get_default_sm_configuration_with_proxy_pc() + + self._update_dict(config, default_config) + return config + + def _get_default_sm_configuration_with_proxy_pc(self): + """ + Return State Machine default configuration with proxy_pc state. + :return: default sm configuration with proxy_pc state. + """ + config = { + ProxyPc3.connection_hops: { + ProxyPc3.unix_local: { # from + ProxyPc3.proxy_pc: { # to + "execute_command": "ssh", # using command + "command_params": { # with parameters + "target_newline": "\n" + }, + "required_command_params": [ + "host", + "login", + "password", + "expected_prompt" + ] + }, + }, + ProxyPc3.proxy_pc: { # from + ProxyPc3.unix_local: { # to + "execute_command": "exit", # using command + "command_params": { # with parameters + "target_newline": "\n", + "expected_prompt": r'^moler_bash#', + }, + "required_command_params": [ + ] + } + }, + } + } + return config + + def _prepare_transitions(self): + """ + Prepare transitions to change states. + :return: None. + """ + + stored_is_proxy_pc = self._use_proxy_pc + self._use_proxy_pc = True + super(ProxyPc3, self)._prepare_transitions() + self._use_proxy_pc = stored_is_proxy_pc + transitions = self._prepare_transitions_with_proxy_pc() + self._add_transitions(transitions=transitions) + + def _prepare_transitions_with_proxy_pc(self): + """ + Prepare transitions to change states with proxy_pc state. + :return: transitions with proxy_pc state. + """ + transitions = { + ProxyPc3.unix_local: { + ProxyPc3.proxy_pc: { + "action": [ + "_execute_command_to_change_state" + ], + } + }, + ProxyPc3.proxy_pc: { + ProxyPc3.unix_local: { + "action": [ + "_execute_command_to_change_state" + ], + }, + }, + } + return transitions + + def _prepare_state_prompts(self): + """ + Prepare textual prompt for each state. + :return: None. + """ + super(ProxyPc3, self)._prepare_state_prompts() + + if self._use_proxy_pc: + state_prompts = self._prepare_state_prompts_with_proxy_pc() + else: + state_prompts = self._prepare_state_prompts_without_proxy_pc() + + self._update_dict(self._state_prompts, state_prompts) + + def _prepare_state_prompts_with_proxy_pc(self): + """ + Prepare textual prompt for each state for State Machine with proxy_pc state. + :return: textual prompt for each state with proxy_pc state. + """ + state_prompts = { + ProxyPc3.proxy_pc: + self._configurations[ProxyPc3.connection_hops][ProxyPc3.unix_local][ProxyPc3.proxy_pc][ + "command_params"]["expected_prompt"], + ProxyPc3.unix_local: + self._configurations[ProxyPc3.connection_hops][ProxyPc3.proxy_pc][ProxyPc3.unix_local][ + "command_params"]["expected_prompt"], + } + return state_prompts + + def _prepare_state_prompts_without_proxy_pc(self): + """ + Prepare textual prompt for each state for State Machine without proxy_pc state. + :return: textual prompt for each state without proxy_pc state. + """ + state_prompts = {} + return state_prompts + + def _prepare_newline_chars(self): + """ + Prepare newline char for each state. + :return: None. + """ + super(ProxyPc3, self)._prepare_newline_chars() + + if self._use_proxy_pc: + newline_chars = self._prepare_newline_chars_with_proxy_pc() + else: + newline_chars = self._prepare_newline_chars_without_proxy_pc() + + self._update_dict(self._newline_chars, newline_chars) + + def _prepare_newline_chars_with_proxy_pc(self): + """ + Prepare newline char for each state for State Machine with proxy_pc state. + :return: newline char for each state with proxy_pc state. + """ + newline_chars = { + ProxyPc3.proxy_pc: + self._configurations[ProxyPc3.connection_hops][ProxyPc3.unix_local][ProxyPc3.proxy_pc][ + "command_params"]["target_newline"], + ProxyPc3.unix_local: + self._configurations[ProxyPc3.connection_hops][ProxyPc3.proxy_pc][ProxyPc3.unix_local][ + "command_params"]["target_newline"], + } + return newline_chars + + def _prepare_newline_chars_without_proxy_pc(self): + """ + Prepare newline char for each state for State Machine without proxy_pc state. + :return: newline char for each state without proxy_pc state. + """ + newline_chars = {} + return newline_chars + + def _prepare_state_hops_with_proxy_pc(self): + """ + Prepare non direct transitions for each state for State Machine with proxy_pc state. + :return: non direct transitions for each state with proxy_pc state. + """ + state_hops = { + UnixLocal.not_connected: { + ProxyPc3.proxy_pc: ProxyPc3.unix_local, + }, + UnixLocal.unix_local_root: { + ProxyPc3.proxy_pc: ProxyPc3.unix_local, + ProxyPc3.not_connected: ProxyPc3.unix_local, + }, + ProxyPc3.proxy_pc: { + ProxyPc3.not_connected: ProxyPc3.unix_local, + ProxyPc3.unix_local_root: ProxyPc3.unix_local, + }, + } + return state_hops + + def _get_packages_for_state(self, state, observer): + """ + Get available packages contain cmds and events for each state. + :param state: device state. + :param observer: observer type, available: cmd, events + :return: available cmds or events for specific device state. + """ + available = super(ProxyPc3, self)._get_packages_for_state(state, observer) + + if not available: + if state == ProxyPc3.proxy_pc: + available = {UnixLocal.cmds: ['moler.cmd.unix'], + UnixLocal.events: ['moler.events.shared', 'moler.events.unix']} + if available: + return available[observer] + + return available diff --git a/moler/device/unixremote3.py b/moler/device/unixremote3.py index 76018e76b..fdf4ed774 100644 --- a/moler/device/unixremote3.py +++ b/moler/device/unixremote3.py @@ -170,7 +170,7 @@ def _get_default_sm_configuration(self): Create State Machine default configuration. :return: default sm configuration. """ - config = super(ProxyPc, self)._get_default_sm_configuration() + config = super(UnixRemote3, self)._get_default_sm_configuration() default_config = self._get_default_sm_configuration_with_proxy_pc() self._update_dict(config, default_config) diff --git a/test/device/test_SM_juniper_ex.py b/test/device/test_SM_juniper_ex.py index 0a3ea050f..3cbb9b470 100644 --- a/test/device/test_SM_juniper_ex.py +++ b/test/device/test_SM_juniper_ex.py @@ -1,23 +1,41 @@ -__author__ = 'Michal Ernst' -__copyright__ = 'Copyright (C) 2018-2019, Nokia' -__email__ = 'michal.ernst@nokia.com' +__author__ = 'Michal Ernst, Marcin Usielski' +__copyright__ = 'Copyright (C) 2018-2024, Nokia' +__email__ = 'michal.ernst@nokia.com, marcin.usielski@nokia.com' import pytest +from moler.device import DeviceFactory from moler.util.devices_SM import iterate_over_device_states, get_device -def test_juniper_ex_device(device_connection, juniper_ex_output): - juniper_ex = get_device(name="JUNIPER_EX", connection=device_connection, device_output=juniper_ex_output, +junipers = ['JUNIPER_EX', 'JUNIPER_EX3'] +junipers_proxy = ['JUNIPER_EX_PROXY_PC', 'JUNIPER_EX_PROXY_PC3'] + +@pytest.mark.parametrize("device_name", junipers) +def test_juniper_ex_device(device_name, device_connection, juniper_ex_output): + juniper_ex = get_device(name=device_name, connection=device_connection, device_output=juniper_ex_output, test_file_path=__file__) iterate_over_device_states(device=juniper_ex) -def test_juniper_ex_proxy_pc_device(device_connection, juniper_ex_proxy_pc_output): - juniper_ex_proxy_pc = get_device(name="JUNIPER_EX_PROXY_PC", connection=device_connection, +@pytest.mark.parametrize("device_name", junipers_proxy) +def test_juniper_ex_proxy_pc_device(device_name, device_connection, juniper_ex_proxy_pc_output): + juniper_ex_proxy_pc = get_device(name=device_name, connection=device_connection, device_output=juniper_ex_proxy_pc_output, test_file_path=__file__) iterate_over_device_states(device=juniper_ex_proxy_pc) +@pytest.mark.parametrize("devices", [junipers_proxy, junipers]) +def test_unix_sm_identity(devices): + dev0 = DeviceFactory.get_device(name=devices[0]) + dev1 = DeviceFactory.get_device(name=devices[1]) + + assert dev0._stored_transitions == dev1._stored_transitions + assert dev0._state_hops == dev1._state_hops + assert dev0._state_prompts == dev1._state_prompts + assert dev0._configurations == dev1._configurations + assert dev0._newline_chars == dev1._newline_chars + + @pytest.fixture def juniper_ex_output(): output = { diff --git a/test/resources/device_config.yml b/test/resources/device_config.yml index b9f5640c8..35a1d9844 100644 --- a/test/resources/device_config.yml +++ b/test/resources/device_config.yml @@ -175,6 +175,44 @@ DEVICES: command_params: expected_prompt: "proxy_pc#" + JUNIPER_EX3: + DEVICE_CLASS: moler.device.juniper_ex3.JuniperEX3 + INITIAL_STATE: UNIX_LOCAL + CONNECTION_HOPS: + UNIX_LOCAL: + CLI: + execute_command: ssh # default value + command_params: + host: cli_host + login: cli_login + password: password + + JUNIPER_EX_PROXY_PC3: + DEVICE_CLASS: moler.device.juniper_ex3.JuniperEX3 + INITIAL_STATE: UNIX_LOCAL + CONNECTION_HOPS: + PROXY_PC: + CLI: + execute_command: ssh + command_params: + host: cli_host + login: cli_login + password: password + UNIX_LOCAL: + PROXY_PC: + execute_command: ssh + command_params: + expected_prompt: "proxy_pc#" + host: proxy_pc_host + login: proxy_pc_login + password: password + set_timeout: null + CLI: + PROXY_PC: + execute_command: exit + command_params: + expected_prompt: "proxy_pc#" + SCPI: DEVICE_CLASS: moler.device.scpi.Scpi INITIAL_STATE: UNIX_LOCAL