diff --git a/docs/changelog/2022/september.rst b/docs/changelog/2022/september.rst new file mode 100644 index 00000000..1d589760 --- /dev/null +++ b/docs/changelog/2022/september.rst @@ -0,0 +1,17 @@ +-------------------------------------------------------------------------------- + New +-------------------------------------------------------------------------------- + +* connection base + * add option log_propagate to control whether logger for the connection propagates logs to parent + * add option no_pyats_tasklog to prevent Unicon from adding pyats tasklog handler + + +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* mock_device + * Fixed issue with HA mode mock device when asyncssh package is not installed + + diff --git a/docs/changelog_plugins/2022/august.rst b/docs/changelog_plugins/2022/august.rst index c0d17496..aa7513d7 100644 --- a/docs/changelog_plugins/2022/august.rst +++ b/docs/changelog_plugins/2022/august.rst @@ -25,4 +25,6 @@ * iosxe/cat9k * Updated the container shell prompt pattern +* iosxe/cat8k + * Added Reload and HAReload diff --git a/docs/changelog_plugins/2022/september.rst b/docs/changelog_plugins/2022/september.rst new file mode 100644 index 00000000..37820401 --- /dev/null +++ b/docs/changelog_plugins/2022/september.rst @@ -0,0 +1,21 @@ +-------------------------------------------------------------------------------- + Fix +-------------------------------------------------------------------------------- + +* generic + * Added setting for state change prompt retries, default to 3 second wait + * Update sudo regex pattern + * Updated the session data to handle the reoccurring dialog issue + * Update copy error pattern to ignore self-signed certificate failure + * Add handlers for ping options extended_verbose, timestamp_count, record_hops, src_route_type + +* iosxr/ncs5k + * Updated the mock data for ncs5k + +* iosxe + * Added a RELOAD_WAIT to iosxe settings + +* hvrp + * Updated the pattern and setting to support configuration and error detection. + + diff --git a/docs/user_guide/connection.rst b/docs/user_guide/connection.rst index d7d4a013..f6712bfc 100644 --- a/docs/user_guide/connection.rst +++ b/docs/user_guide/connection.rst @@ -815,6 +815,13 @@ Arguments: * **log_stdout**: Boolean option to enable/disable logging to standard output. Default is True. *(Optional)* + * **log_propagate**: Boolean option to enable/disable propagating logs from connection logger + to parent logger (e.g. whether logs for `unicon.N7K-BESTPROD2-SSR-P1.cli.1663541251` logger + should propagate to `unicon` logger). Default is False. *(Optional)* + + * **no_pyats_tasklog**: Boolean option to enable/disable logging to pyats tasklog. Default is False. + *(Optional)* + * **debug**: Boolean option to enable/disable internal debug logging. *(Optional)* diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9f03ecfa..00000000 --- a/setup.cfg +++ /dev/null @@ -1,11 +0,0 @@ -[bumpver] -current_version = "22.7" -version_pattern = "MAJOR.MINOR[.PATCH][PYTAGNUM]" -commit_message = "bump version {old_version} -> {new_version}" -commit = True -push = True - -[bumpver:file_patterns] -src/unicon/plugins/__init__.py = - __version__ = '{pep440_version}' - diff --git a/src/unicon/plugins/__init__.py b/src/unicon/plugins/__init__.py index 62ff9f93..2d6fc009 100644 --- a/src/unicon/plugins/__init__.py +++ b/src/unicon/plugins/__init__.py @@ -1,4 +1,4 @@ -__version__ = '22.8' +__version__ = '22.9' supported_chassis = [ 'single_rp', diff --git a/src/unicon/plugins/generic/patterns.py b/src/unicon/plugins/generic/patterns.py index 1383109e..d492db84 100644 --- a/src/unicon/plugins/generic/patterns.py +++ b/src/unicon/plugins/generic/patterns.py @@ -58,7 +58,7 @@ def __init__(self): self.learn_os_prompt = r'^(.*?([>\$~%]|[^#\s]#|~ #|~/|^admin:|^#)\s?(\x1b\S+)?)$|(^.*This \(D\)RP Node is not ready or active for login \/configuration.*)' - self.sudo_password_prompt = r'^.*\[sudo\] password for .*?:\s*?' + self.sudo_password_prompt = r'^.*(\[sudo\] password for .*?:|This is your UNIX password:)\s*$' # *Sep 6 23:13:38.188: %PNP-6-PNP_SDWAN_STARTED: PnP SDWAN started (7) via (pnp-sdwan-abort-on-cli) by (pid=3, pname=Exec) # *Sep 6 23:18:11.702: %ENVIRONMENTAL-1-ALERT: Temp: Inlet 1, Location: R0, State: Warning, Reading: 45 Celsius diff --git a/src/unicon/plugins/generic/service_implementation.py b/src/unicon/plugins/generic/service_implementation.py index 9b7a1b8e..e28df7d4 100644 --- a/src/unicon/plugins/generic/service_implementation.py +++ b/src/unicon/plugins/generic/service_implementation.py @@ -599,6 +599,8 @@ def __init__(self, connection, context, **kwargs): self.dialog = Dialog(execution_statement_list) self.matched_retries = connection.settings.EXECUTE_MATCHED_RETRIES self.matched_retry_sleep = connection.settings.EXECUTE_MATCHED_RETRY_SLEEP + self.state_change_matched_retries = connection.settings.EXECUTE_STATE_CHANGE_MATCH_RETRIES + self.state_change_matched_retry_sleep = connection.settings.EXECUTE_STATE_CHANGE_MATCH_RETRY_SLEEP def log_service_call(self): pass @@ -684,16 +686,16 @@ def call_service(self, command=[], # noqa: C901 if allow_state_change: dialog.append(Statement( pattern=state.pattern, - matched_retries=matched_retries, - matched_retry_sleep=matched_retry_sleep + matched_retries=self.state_change_matched_retries, + matched_retry_sleep=self.state_change_matched_retry_sleep )) else: dialog.append(Statement( pattern=state.pattern, action=invalid_state_change_action, args={'err_state': state, 'sm': sm}, - matched_retries=matched_retries, - matched_retry_sleep=matched_retry_sleep + matched_retries=self.state_change_matched_retries, + matched_retry_sleep=self.state_change_matched_retry_sleep )) # store the last used dialog, used by unittest @@ -950,11 +952,13 @@ def call_service(self, # noqa: C901 sp.sendline(cmd) self.update_hostname_if_needed([cmd]) self.process_dialog_on_handle(handle, dialog, timeout) + # To handle the session if handle.context.get('config_session_locked'): self.connection.log.warning('Config locked, waiting {} seconds'.format( self.connection.settings.CONFIG_LOCK_RETRY_SLEEP)) sleep(self.connection.settings.CONFIG_LOCK_RETRY_SLEEP) config_transition(handle.state_machine, handle.spawn, handle.context) + handle.context['config_session_locked'] = False sp.sendline(cmd) self.process_dialog_on_handle(handle, dialog, timeout) diff --git a/src/unicon/plugins/generic/service_patterns.py b/src/unicon/plugins/generic/service_patterns.py index 8372b075..f6f26692 100644 --- a/src/unicon/plugins/generic/service_patterns.py +++ b/src/unicon/plugins/generic/service_patterns.py @@ -102,7 +102,7 @@ def __init__(self): self.data_pattern = r'^.*Data pattern \[.+\]\s?: $' self.dfbit_header = r'^.*Set DF bit in IP header(\?)? \[.+\]\s?: $' self.dscp = r'^.*DSCP .*\[.+\]\s?: $' - self.lsrtv = r'^.*Loose, Strict, Record, Timestamp, Verbose\s?\[.+\]\s?: $' + self.lsrtv = r'^.*Loose, Strict, Record, Timestamp, Verbose\s?\[(.+)\]\s?: $' self.qos = r'^.*Include global QOS option\? \[.+\]\s?: $' self.packet = r'^.*Pad packet\? \[.+\]\s?: $' # Range internal dialogs @@ -115,10 +115,11 @@ def __init__(self): self.others = r'^.*\[.+\]\s?: $' # extd_LSRTV patterns self.lsrtv_source = r'^.*Source route: $' - self.lsrtv_hot_count = r'^.*Number of hops \[.*\]: $' - self.lsrtv_timestamp_count = r'^.*Number of timestamps \[.*\]: $}' - self.lsrtv_noroom = r'^.*No room for that option$' - self.lsrtv_invalid_hop = r'^.*Invalid number of hops$' + self.lsrtv_hop_count = r'^.*Number of hops \[.*\]: $' + self.lsrtv_timestamp_count = r'^.*Number of timestamps \[.*\]: $' + self.lsrtv_noroom = r'^.*No room for that option' + self.lsrtv_invalid_hop = r'^.*Invalid number of hops' + self.lsrtv_one_allowed = r'^.*% Only one source route option allowed' # Invalid commands self.invalid_command = r'^.*% *Invalid.*' @@ -155,10 +156,10 @@ def __init__(self): self.tftp_addr =r'^.*Address.*$' self.copy_complete = r'^.*bank [0-9]+' self.copy_error_message = r'fail|timed out|Timed out|Error|Login incorrect|denied|Problem' \ - r'|NOT|Invalid|No memory|Failed|mismatch|Bad|bogus|lose|abort' \ + r'|NOT|Invalid|No memory|Failed(?! to generate persistent self-signed certificate)|mismatch|Bad|bogus|lose|abort' \ r'|Not |too big|exceeds|detected|[Nn]o route to host' \ r'|image is not allowed|Could not resolve|No such' - self.copy_retry_message = r'fail|[Tt]imed out|Error|Problem|NOT|Failed|Bad|bogus|lose|abort|Not |too big|exceeds|detected' + self.copy_retry_message = r'fail|[Tt]imed out|Error|Problem|NOT|Failed(?! to generate persistent self-signed certificate)|Bad|bogus|lose|abort|Not |too big|exceeds|detected' self.copy_continue = r'Are you sure you want to continue connecting ((yes/no)|\((yes/no(/\[fingerprint\])?)?\))?' self.copy_other = r'^.*\[yes\/no\]\s*\?*\s*$' self.remote_param ='ftp:|tftp:|http:|rcp:|scp:' diff --git a/src/unicon/plugins/generic/service_statements.py b/src/unicon/plugins/generic/service_statements.py index 8d1c15fa..190bb99e 100644 --- a/src/unicon/plugins/generic/service_statements.py +++ b/src/unicon/plugins/generic/service_statements.py @@ -119,6 +119,26 @@ def ping_handler_1(spawn, context, send_key): spawn.sendline(context[send_key]) +def lsrtv_handler(spawn, context): + selection = spawn.match.last_match.group(1) + if context.get('extended_verbose') and 'V' not in selection: + spawn.sendline('v') + return + if context.get('timestamp_count') and 'T' not in selection: + spawn.sendline('t') + return + if context.get('record_hops') and 'R' not in selection: + spawn.sendline('r') + return + if context.get('src_route_type', '').lower() == 'loose' and 'L' not in selection: + spawn.sendline('l') + return + if context.get('src_route_type', '').lower() == 'strict' and 'S' not in selection: + spawn.sendline('s') + return + spawn.sendline() + + def send_multicast(spawn, context): if context.get('multicast'): spawn.sendline(context['multicast']) @@ -603,6 +623,39 @@ def config_session_locked_handler(context): loop_continue=True, continue_timer=False) +lsrtv_stmt = Statement(pattern=pat.lsrtv, + action=lsrtv_handler, + loop_continue=True, + continue_timer=False) + +lsrtv_timestamp = Statement(pattern=pat.lsrtv_timestamp_count, + action=ping_handler, + args={'send_key': 'timestamp_count'}, + loop_continue=True, + continue_timer=False) + +lsrtv_hop_count = Statement(pattern=pat.lsrtv_hop_count, + action=ping_handler, + args={'send_key': 'record_hops'}, + loop_continue=True, + continue_timer=False) + +lsrtv_source = Statement(pattern=pat.lsrtv_source, + action=ping_handler, + args={'send_key': 'src_route_addr'}, + loop_continue=True, + continue_timer=False) + +lsrtv_one_allowed = Statement(pattern=pat.lsrtv_one_allowed, + action=ping_invalid_input_handler, + loop_continue=True, + continue_timer=False) + +lsrtv_noroom = Statement(pattern=pat.lsrtv_noroom, + action=ping_invalid_input_handler, + loop_continue=True, + continue_timer=False) + #################################################################### # Traceroute Statements #################################################################### @@ -728,6 +781,11 @@ def config_session_locked_handler(context): extended_ping_dialog_list = [invalid_input, unkonwn_protocol, protocol, transport, mask, address, vcid, tunnel, repeat, size, verbose, interval, packet_timeout, sending_interval, + # error patterns: + lsrtv_noroom, lsrtv_one_allowed, + # the error patterns need to come before lsrtv_stmt + lsrtv_stmt, + lsrtv_timestamp, lsrtv_hop_count, lsrtv_source, output_interface, novell_echo_type, vrf, ext_cmds, sweep_range, range_interval, range_max, range_min, dest_start, interface, dest_end, increment, diff --git a/src/unicon/plugins/generic/settings.py b/src/unicon/plugins/generic/settings.py index defee24a..fe4811e1 100644 --- a/src/unicon/plugins/generic/settings.py +++ b/src/unicon/plugins/generic/settings.py @@ -136,10 +136,14 @@ def __init__(self): self.BULK_CONFIG_CHUNK_LINES = 50 self.BULK_CONFIG_CHUNK_SLEEP = 0.5 - # for execute matched retry on state pattern + # for execute matched retry on statement pattern self.EXECUTE_MATCHED_RETRIES = 1 self.EXECUTE_MATCHED_RETRY_SLEEP = 0.05 + # execute statement match retry for state change patterns + self.EXECUTE_STATE_CHANGE_MATCH_RETRIES = 1 + self.EXECUTE_STATE_CHANGE_MATCH_RETRY_SLEEP = 3 + # User defined login and password prompt pattern. self.LOGIN_PROMPT = None self.PASSWORD_PROMPT = None @@ -275,7 +279,3 @@ def __init__(self): }, } -#TODO -#take addtional dialogs for all service -#move all commands to settings -# diff --git a/src/unicon/plugins/hvrp/patterns.py b/src/unicon/plugins/hvrp/patterns.py index 6f38a013..685c8324 100644 --- a/src/unicon/plugins/hvrp/patterns.py +++ b/src/unicon/plugins/hvrp/patterns.py @@ -22,10 +22,11 @@ def __init__(self): self.password = r'^.*[Pp]assword:' # | # - self.enable_prompt = r'^(.*)\<.*\>$' + self.enable_prompt = r'^(.*)\<%N.*\>$' + # [~HOSTNAME] | # # breaks on [\y\n] # Warning: All the configuration will be saved to the next startup configuration. Continue? [y/n]: - self.config_prompt = r'^\[.*\]' + self.config_prompt = r'^.*\[(~|\*)%N.*\]' # Exit with uncommitted changes? [yes,no] (yes) self.commit_changes_prompt = r'Exit with uncommitted changes? [yes,no] (yes)\s*' diff --git a/src/unicon/plugins/hvrp/settings.py b/src/unicon/plugins/hvrp/settings.py index 955376c2..848614d1 100644 --- a/src/unicon/plugins/hvrp/settings.py +++ b/src/unicon/plugins/hvrp/settings.py @@ -24,3 +24,5 @@ def __init__(self): ] self.HA_INIT_CONFIG_COMMANDS = [] + self.ERROR_PATTERN.append("Error:.*") + self.CONFIGURE_ERROR_PATTERN.append(r'^Error:.*') diff --git a/src/unicon/plugins/iosxe/settings.py b/src/unicon/plugins/iosxe/settings.py index 50f44fdd..c8fe498f 100644 --- a/src/unicon/plugins/iosxe/settings.py +++ b/src/unicon/plugins/iosxe/settings.py @@ -27,6 +27,8 @@ def __init__(self): self.EXECUTE_MATCHED_RETRIES = 1 self.EXECUTE_MATCHED_RETRY_SLEEP = 0.1 + self.RELOAD_WAIT = 300 + self.CONFIG_LOCK_RETRY_SLEEP = 30 self.CONFIG_LOCK_RETRIES = 10 diff --git a/src/unicon/plugins/tests/mock/mock_device_hvrp.py b/src/unicon/plugins/tests/mock/mock_device_hvrp.py index 2d5fb3de..a40f787f 100644 --- a/src/unicon/plugins/tests/mock/mock_device_hvrp.py +++ b/src/unicon/plugins/tests/mock/mock_device_hvrp.py @@ -13,6 +13,7 @@ class MockDeviceHuaweiVrp(MockDevice): def __init__(self, *args, **kwargs): super().__init__(*args, device_os='hvrp', **kwargs) + self.invalid_command_response = "Error: Unrecognized command found " class MockDeviceTcpWrapperHuaweiVrp(MockDeviceTcpWrapper): diff --git a/src/unicon/plugins/tests/mock_data/hvrp/hvrp_mock_data.yaml b/src/unicon/plugins/tests/mock_data/hvrp/hvrp_mock_data.yaml index 48f34b58..d3c0fc3a 100644 --- a/src/unicon/plugins/tests/mock_data/hvrp/hvrp_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/hvrp/hvrp_mock_data.yaml @@ -11,7 +11,7 @@ exec: prompt: "" commands: "display version" : | - "Huawei Versatile Routing Platform Software + Huawei Versatile Routing Platform Software VRP (R) software, Version 5.170 (AR650 V300R019C11SPC200) Copyright (C) 2011-2020 HUAWEI TECH CO., LTD Huawei AR657W Router uptime is 0 week, 0 day, 0 hour, 4 minutes @@ -28,8 +28,14 @@ exec: 4. CPLD0 Version : 103 5. BootROM Version : 1 - " + "screen-length 0 temporary": "Info: The configuration takes effect on the current user terminal interface only." + "undo terminal alarm": "Info: Current alarm terminal is off." + "undo terminal logging": "Info: Current terminal logging is off." + "undo terminal debugging": "Info: Current terminal debugging is off." + "undo terminal monitor": "Info: Current terminal monitor is off." + "system-view": + new_state: config user_access_veri: preface: User Access Verification @@ -42,4 +48,37 @@ user_password: prompt: "Password: " commands: "kpn": - new_state: exec \ No newline at end of file + new_state: exec + +config: + preface: | + Enter system view, return user view with return command. + prompt: "[~ooo-gg-9999zz-99]" + commands: + "bgp 65000": + new_state: bgp_config + "commit": "" + "return": + new_state: exec + +bgp_config: + prompt: "[~ooo-gg-9999zz-99-bgp]" + commands: + "peer 1.1.1.1 as-number 64666": + new_state: bgp_config_uncommitted_change + "commit": "" + "exit": + new_state: config + "return": + new_state: exec + +bgp_config_uncommitted_change: + prompt: "[*ooo-gg-9999zz-99-bgp]" + commands: + "commit": + response: "Committing....done." + timing: + - "0:,0,2,0" + new_state: bgp_config + + diff --git a/src/unicon/plugins/tests/mock_data/ios/ios_mock_data.yaml b/src/unicon/plugins/tests/mock_data/ios/ios_mock_data.yaml index cb9643df..391dcda5 100644 --- a/src/unicon/plugins/tests/mock_data/ios/ios_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/ios/ios_mock_data.yaml @@ -303,6 +303,8 @@ enable: - "3:,0.5" "ping vrf management": new_state: ping_proto_ios_vrf + "ping vrf mgmt": + new_state: ping_proto_ios_verbose "sh redundancy stat | inc my state": my state = 13 -ACTIVE "show redundancy sta | in my": | diff --git a/src/unicon/plugins/tests/mock_data/ios/ios_mock_ping.yaml b/src/unicon/plugins/tests/mock_data/ios/ios_mock_ping.yaml index c850b3db..e239bd4c 100644 --- a/src/unicon/plugins/tests/mock_data/ios/ios_mock_ping.yaml +++ b/src/unicon/plugins/tests/mock_data/ios/ios_mock_ping.yaml @@ -251,3 +251,110 @@ ping_sweep_vrf: Sending 5, 100-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds: !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/3 ms + + +ping_proto_ios_verbose: + prompt: "Protocol [ip]: " + commands: + "": + new_state: ping_target_ip_verbose + +ping_target_ip_verbose: + prompt: "Target IP address: " + commands: + "1.1.1.1": + new_state: ping_count_verbose + +ping_count_verbose: + prompt: "Repeat count [5]: " + commands: + "": + new_state: ping_size_verbose + +ping_size_verbose: + prompt: "Datagram size [100]: " + commands: + "": + new_state: ping_timeout_verbose + +ping_timeout_verbose: + prompt: "Timeout in seconds [2]: " + commands: + "": + new_state: ping_extend_verbose + +ping_extend_verbose: + prompt: "Extended commands? [no]: " + commands: + "y": + new_state: ping_ingress_ping_verbose + +ping_ingress_ping_verbose: + prompt: "Ingress ping [n]: " + commands: + "": + new_state: ping_source_add_or_int_verbose + +ping_source_add_or_int_verbose: + prompt: "Source address or interface: " + commands: + "": + new_state: ping_dscp_value_verbose + +ping_dscp_value_verbose: + prompt: "DSCP Value [0]: " + commands: + "": + new_state: ping_tos_verbose + +ping_tos_verbose: + prompt: "Type of service [0]: " + commands: + "": + new_state: ping_dfbit_verbose + +ping_dfbit_verbose: + prompt: "Set DF bit in IP header? [no]: " + commands: + "": + new_state: ping_validate_data_verbose + +ping_validate_data_verbose: + prompt: "Validate reply data? [no]: " + commands: + "": + new_state: ping_data_pattern_verbose + +ping_data_pattern_verbose: + prompt: "Data pattern [0x0000ABCD]: " + commands: + "": + new_state: ping_lsrtv_verbose + +ping_lsrtv_verbose: + prompt: "Loose, Strict, Record, Timestamp, Verbose[none]: " + commands: + "v": + new_state: ping_lsrtv_v_verbose + +ping_lsrtv_v_verbose: + prompt: "Loose, Strict, Record, Timestamp, Verbose[V]: " + commands: + "": + new_state: ping_sweep_verbose + +ping_sweep_verbose: + prompt: "Sweep range of sizes? [no]: " + commands: + "n": + new_state: enable + response: | + Type escape sequence to abort. + Sending 5, 100-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds: + Reply to request 0 (5 ms) + Reply to request 1 (2 ms) + Reply to request 2 (2 ms) + Reply to request 3 (2 ms) + Reply to request 4 (8 ms) + Success rate is 100 percent (5/5), round-trip min/avg/max = 2/3/8 ms + diff --git a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml index 54534ff3..66a3e62b 100644 --- a/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxe/iosxe_mock_data.yaml @@ -269,6 +269,12 @@ general_enable: new_state: bash_console_switch_standby_rp_active "show log | in BOOTTIME": "*Sep 22 14:46:00.419: %SYS-6-BOOTTIME: Time taken to reboot after reload = 417 seconds" "show log | in PLATFORM_SYS-6-UPTIME": "%PLATFORM_SYS-6-UPTIME: Time taken to initialize system = 364 seconds" + "copy test.cfg running-config": | + Failed to generate persistent self-signed certificate. + Secure server will use temporary self-signed certificate. + Default AP group (default-group) is not configurable. + 4177 bytes copied in 0.160 secs (26106 bytes/sec) + general_config: @@ -1341,3 +1347,29 @@ slow_config_prompt: prompt: "%N(config)#" commands: <<: *general_config_cmds + + +rommon_command_output_not_a_prompt: + prompt: "switch: " + commands: + "notaprompt": + timing: + - 0:,0,2 + response: |2 + Signature verification failed for key# + + "boot": + new_state: general_exec + +rommon_command_output_not_a_prompt2: + prompt: "switch: " + commands: + "notaprompt": + timing: + - 0:1,0,2 + - 1:,10 + response: |2 + Signature verification failed for key# + + "boot": + new_state: general_exec diff --git a/src/unicon/plugins/tests/mock_data/iosxr/iosxr_ncs5k_mock_data.yaml b/src/unicon/plugins/tests/mock_data/iosxr/iosxr_ncs5k_mock_data.yaml index 8688e0bf..82219e8f 100644 --- a/src/unicon/plugins/tests/mock_data/iosxr/iosxr_ncs5k_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/iosxr/iosxr_ncs5k_mock_data.yaml @@ -124,6 +124,13 @@ ncs5k_enable: Template: console Capabilities: Timestamp Enabled Allowed transports are none. + "show platform": | + Thu Sep 1 16:03:41.711 UTC + Node Type State Config state + -------------------------------------------------------------------------------- + 0/RP0/CPU0 N540X-16Z4G8Q2C-A(Active) IOS XR RUN NSHUT + 0/PM0 N540-PSU-FIXED-A OPERATIONAL NSHUT + 0/FT0 N540-X-BB-FAN OPERATIONAL NSHUT "active_install_add": new_state: install_add_commit_1 ncs5k_enable_vty: diff --git a/src/unicon/plugins/tests/mock_data/linux/linux_mock_data.yaml b/src/unicon/plugins/tests/mock_data/linux/linux_mock_data.yaml index 58e8ba19..25b85717 100644 --- a/src/unicon/plugins/tests/mock_data/linux/linux_mock_data.yaml +++ b/src/unicon/plugins/tests/mock_data/linux/linux_mock_data.yaml @@ -174,6 +174,8 @@ exec: /opt "sudo": new_state: sudo_password + "sudo2": + new_state: sudo_password2 "exit": new_state: login @@ -389,6 +391,12 @@ sudo_password: "sudo_password": new_state: sudo +sudo_password2: + prompt: "This is your UNIX password: " + commands: + "sudo_password": + new_state: sudo + sudo: prompt: "Linux# " commands: diff --git a/src/unicon/plugins/tests/test_plugin_hvrp.py b/src/unicon/plugins/tests/test_plugin_hvrp.py index 2c2ab8f1..89d4fbda 100644 --- a/src/unicon/plugins/tests/test_plugin_hvrp.py +++ b/src/unicon/plugins/tests/test_plugin_hvrp.py @@ -5,7 +5,7 @@ from unicon import Connection from unicon.eal.dialogs import Dialog from unicon.mock.mock_device import mockdata_path - +from unicon.core.errors import SubCommandFailure,UniconAuthenticationError with open(os.path.join(mockdata_path, 'hvrp/hvrp_mock_data.yaml'), 'rb') as datafile: mock_data = yaml.safe_load(datafile.read()) @@ -16,8 +16,7 @@ def test_login_connect(self): c = Connection(hostname='ooo-gg-9999zz-99', start=['mock_device_cli --os hvrp --state exec'], os='hvrp', - username='nielsvanhooy', - tacacs_password='kpn') + credentials={'default': {'username': 'nielsvanhooy', 'password': 'kpn'}}) c.connect() self.assertIn('', c.spawn.match.match_output) c.disconnect() @@ -26,8 +25,7 @@ def test_login_connect_ssh(self): c = Connection(hostname='ooo-gg-9999zz-99', start=['mock_device_cli --os hvrp --state connect_ssh'], os='hvrp', - username='nielsvanhooy', - tacacs_password='kpn') + credentials={'default': {'username': 'nielsvanhooy', 'password': 'kpn'}}) c.connect() self.assertIn('', c.spawn.match.match_output) c.disconnect() @@ -36,8 +34,7 @@ def test_login_connect_connectReply(self): c = Connection(hostname='ooo-gg-9999zz-99', start=['mock_device_cli --os hvrp --state exec'], os='hvrp', - username='nielsvanhooy', - tacacs_password='kpn', + credentials={'default': {'username': 'nielsvanhooy', 'password': 'kpn'}}, connect_reply = Dialog([[r'^(.*?)Password:']])) c.connect() self.assertIn("^(.*?)Password:", str(c.connection_provider.get_connection_dialog())) @@ -49,8 +46,7 @@ def test_execute_show_feature(self): c = Connection(hostname='ooo-gg-9999zz-99', start=['mock_device_cli --os hvrp --state exec'], os='hvrp', - username='nielsvanhooy', - tacacs_password='kpn', + credentials={'default': {'username': 'nielsvanhooy', 'password': 'kpn'}}, init_exec_commands=[], init_config_commands=[] ) @@ -61,5 +57,41 @@ def test_execute_show_feature(self): self.assertIn(expected_response, ret) c.disconnect() + def test_execute_unsupported(self): + c = Connection(hostname='ooo-gg-9999zz-99', + start=['mock_device_cli --os hvrp --state exec'], + os='hvrp', + credentials={'default': {'username': 'nielsvanhooy', 'password': 'kpn'}}, + init_exec_commands=[], + init_config_commands=[] + ) + c.connect() + self.assertRaises(SubCommandFailure,c.execute,"display vresion") + c.disconnect() + +class TestHuaweiVrpPluginConfigure(unittest.TestCase): + + def test_config(self): + c = Connection(hostname='ooo-gg-9999zz-99', + start=['mock_device_cli --os hvrp --state exec'], + os='hvrp', + credentials={'default': {'username': 'nielsvanhooy', 'password': 'kpn'}}, + init_config_commands=[] + ) + c.connect() + c.configure(["bgp 65000","peer 1.1.1.1 as-number 64666"]) + c.disconnect() + + def test_unsupported_config(self): + c = Connection(hostname='ooo-gg-9999zz-99', + start=['mock_device_cli --os hvrp --state exec'], + os='hvrp', + credentials={'default': {'username': 'nielsvanhooy', 'password': 'kpn'}}, + init_config_commands=[] + ) + c.connect() + self.assertRaises(SubCommandFailure,c.configure,"bpg 65000") + c.disconnect() + if __name__ == "__main__": unittest.main() diff --git a/src/unicon/plugins/tests/test_plugin_ios.py b/src/unicon/plugins/tests/test_plugin_ios.py index a2757927..0efa0601 100644 --- a/src/unicon/plugins/tests/test_plugin_ios.py +++ b/src/unicon/plugins/tests/test_plugin_ios.py @@ -185,6 +185,44 @@ def test_ping_success_vrf(self): !!!!! Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/1 ms +""".splitlines())) + + def test_ping_options_verbose(self): + c = Connection(hostname='Router', + start=['mock_device_cli --os ios --state exec'], + os='ios', + username='cisco', + tacacs_password='cisco', + enable_password='cisco', + mit=True, + log_buffer=True) + r = c.ping('1.1.1.1', extended_verbose=True, vrf='mgmt') + self.assertEqual(r, "\r\n".join("""ping vrf mgmt +Protocol [ip]: +Target IP address: 1.1.1.1 +Repeat count [5]: +Datagram size [100]: +Timeout in seconds [2]: +Extended commands? [no]: y +Ingress ping [n]: +Source address or interface: +DSCP Value [0]: +Type of service [0]: +Set DF bit in IP header? [no]: +Validate reply data? [no]: +Data pattern [0x0000ABCD]: +Loose, Strict, Record, Timestamp, Verbose[none]: v +Loose, Strict, Record, Timestamp, Verbose[V]: +Sweep range of sizes? [no]: n +Type escape sequence to abort. +Sending 5, 100-byte ICMP Echos to 1.1.1.1, timeout is 2 seconds: +Reply to request 0 (5 ms) +Reply to request 1 (2 ms) +Reply to request 2 (2 ms) +Reply to request 3 (2 ms) +Reply to request 4 (8 ms) +Success rate is 100 percent (5/5), round-trip min/avg/max = 2/3/8 ms + """.splitlines())) diff --git a/src/unicon/plugins/tests/test_plugin_iosxe.py b/src/unicon/plugins/tests/test_plugin_iosxe.py index 11c0370e..19c9d256 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe.py @@ -1106,5 +1106,59 @@ def test_config_transition_learn_hostname(self): c.configure() +class TestRommonCommands(unittest.TestCase): + + def test_rommon_command_false_prompt_avoidance(self): + c = Connection( + hostname='PE1', + start=['mock_device_cli --os iosxe --state rommon_command_output_not_a_prompt --hostname PE1'], + os='iosxe', + learn_hostname=True, + credentials=dict(default=dict(username='cisco', password='cisco')), + settings={'ROMMON_INIT_COMMANDS': ['notaprompt'], 'EXECUTE_STATE_CHANGE_MATCH_RETRY_SLEEP': 3} + ) + try: + c.connect() + finally: + c.disconnect() + + def test_rommon_command_false_prompt(self): + c = Connection( + hostname='PE1', + start=['mock_device_cli --os iosxe --state rommon_command_output_not_a_prompt2 --hostname PE1'], + os='iosxe', + learn_hostname=True, + credentials=dict(default=dict(username='cisco', password='cisco')), + settings={ + 'ROMMON_INIT_COMMANDS': ['notaprompt'], + 'EXECUTE_STATE_CHANGE_MATCH_RETRIES': 0, + 'EXECUTE_STATE_CHANGE_MATCH_RETRY_SLEEP': 1 + }, + debug=True + ) + with self.assertRaisesRegex(UniconConnectionError, + "Expected device to reach 'rommon' state, but landed on 'enable' state."): + try: + c.connect() + finally: + c.disconnect() + + +class TestCopy(unittest.TestCase): + + def test_copy(self): + c = Connection( + hostname='PE1', + start=['mock_device_cli --os iosxe --state general_enable --hostname PE1'], + os='iosxe', + mit=True + ) + c.connect() + try: + c.copy(source='test.cfg', dest='running-config') + finally: + c.disconnect() + + if __name__ == "__main__": unittest.main() diff --git a/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py b/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py index b5b4808b..60ee4e09 100644 --- a/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py +++ b/src/unicon/plugins/tests/test_plugin_iosxe_cat9k.py @@ -520,6 +520,21 @@ def test_no_boot_system(self): d.configure("no boot system") d.disconnect() + def test_no_boot_system_1(self): + d = Connection(hostname='Router', + start=['mock_device_cli --os iosxe --state c9k_enable4'], + os='iosxe', + platform='cat9k', + credentials=dict(default=dict(username='admin', password='cisco')), + settings=dict(POST_DISCONNECT_WAIT_SEC=0, GRACEFUL_DISCONNECT_WAIT_SEC=0.2), + log_buffer=True + ) + d.connect() + d.settings.CONFIG_LOCK_RETRY_SLEEP = 1 + d.configure(["no boot system", + "no boot system"]) + d.disconnect() + class TestIosXeCat9kPluginContainer(unittest.TestCase): diff --git a/src/unicon/plugins/tests/test_plugin_linux.py b/src/unicon/plugins/tests/test_plugin_linux.py index 771a6ca1..eadfc102 100644 --- a/src/unicon/plugins/tests/test_plugin_linux.py +++ b/src/unicon/plugins/tests/test_plugin_linux.py @@ -671,7 +671,11 @@ def test_execute_check_retcode(self): def test_sudo_handler(self): self.c.execute('sudo') self.assertEqual(self.c.spawn.match.match_output, - ' sudo_password\r\nLinux# ') + 'sudo_password\r\nLinux# ') + + self.c.execute('sudo2') + self.assertEqual(self.c.spawn.match.match_output, + 'sudo_password\r\nLinux# ') self.c.context.credentials['sudo']['password'] = 'unknown' with self.assertRaises(unicon.core.errors.SubCommandFailure): @@ -680,7 +684,7 @@ def test_sudo_handler(self): self.c.context.credentials['sudo']['password'] = 'sudo_password' self.c.sudo() self.assertEqual(self.c.spawn.match.match_output, - ' sudo_password\r\nLinux# ') + 'sudo_password\r\nLinux# ') self.c.execute('exit') self.assertEqual(self.c.spawn.match.match_output, 'exit\r\nLinux$ ') @@ -692,7 +696,7 @@ def test_sudo_handler(self): with self.assertRaises(unicon.core.errors.SubCommandFailure): self.c.execute('sudo_invalid') self.assertEqual(self.c.spawn.match.match_output, - ' invalid\r\nSorry, try again.\r\n[sudo] password for cisco:') + 'invalid\r\nSorry, try again.\r\n[sudo] password for cisco: ') @patch.object(unicon.settings.Settings, 'POST_DISCONNECT_WAIT_SEC', 0)