From 60cd90ebf4694a89951b4cd5d762dd00311f1c18 Mon Sep 17 00:00:00 2001 From: "QUALISYSTEMS\\nahum-t" Date: Sun, 6 Sep 2026 17:50:12 +0300 Subject: [PATCH] Adopt cloudshell-snmp 6.0 / cloudshell-snmp-autoload 3.0 (pysnmp 7) - requirements: cloudshell-snmp >=6.0,<7.0, cloudshell-snmp-autoload >=3.0,<4.0; sibling bumps forced by install conflicts (cloudshell-cli 4.x pins cryptography<37 vs snmp 6.0's >=43): cli >=5.0,<6, shell-flows >=2.0.3,<3.0, connectivity-flow >=3.1.3,<4.0, shell-standards >=2.0,<3.0; standard-telnetlib for py3.13 (telnetlib removed from stdlib, still imported by cloudshell-cli) - autoload: GenericSNMPAutoload 3.0 takes resource_model in the constructor and discover(supported_os) only; Huawei override of discover() dropped (base flow covers it), flow updated accordingly - snmp handler: EnableDisableSnmpConfigurator 6.0 signature (explicit snmp_parameters + enable/disable flags); enable-snmp flow uses the renamed SNMPV3Parameters fields (snmp_auth_protocol, snmp_private_key_protocol) - configuration/firmware flows: shell-flows 2.x URL objects (RemoteURL/BasicLocalUrl) replace the removed UrlParser; ConfigurationType/RestoreMethod enums; append restore rejected via SUPPORTED_RESTORE_METHODS - connectivity flow: connectivity-flow 3.x API (ParseConnectivityRequestService, _set_vlan/_remove_vlan); vlan flows remain the previous no-op stubs - packaging/CI: version 4.1.0, python_requires>=3.9 + 3.9-3.13 classifiers, tox py39, drop sdist --format zip and bdist --universal, pre-commit hooks bumped (isort 5.13 / black 24.8 / flake8 7.1), workflow renamed package-tox-py-39 and moved to Python 3.9 Co-Authored-By: Claude Fable 5 --- ...-packages-ci.yml => package-tox-py-39.yml} | 22 +-- .gitignore | 4 +- .pre-commit-config.yaml | 17 +-- .../autoload/huawei_generic_snmp_autoload.py | 47 +------ .../add_remove_vlan_actions.py | 6 +- .../huawei/flows/huawei_autoload_flow.py | 12 +- .../huawei/flows/huawei_configuration_flow.py | 127 ++++++++---------- .../huawei/flows/huawei_connectivity_flow.py | 55 +++++--- .../huawei/flows/huawei_enable_snmp_flow.py | 8 +- .../huawei/flows/huawei_load_firmware_flow.py | 49 ++++--- cloudshell/huawei/snmp/huawei_snmp_handler.py | 9 +- requirements.txt | 14 +- setup.py | 9 ++ test_requirements.txt | 3 +- tox.ini | 11 +- version.txt | 2 +- 16 files changed, 185 insertions(+), 210 deletions(-) rename .github/workflows/{py2-py3-packages-ci.yml => package-tox-py-39.yml} (93%) diff --git a/.github/workflows/py2-py3-packages-ci.yml b/.github/workflows/package-tox-py-39.yml similarity index 93% rename from .github/workflows/py2-py3-packages-ci.yml rename to .github/workflows/package-tox-py-39.yml index 22d0af6..045beeb 100644 --- a/.github/workflows/py2-py3-packages-ci.yml +++ b/.github/workflows/package-tox-py-39.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.7] + python-version: ["3.9"] steps: - name: Checkout code uses: actions/checkout@v2 @@ -56,10 +56,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python 3.7 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: "3.9" - name: Install dependencies run: | python -m pip install pip -U @@ -74,10 +74,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python 3.7 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: "3.9" - name: Install dependencies run: | python -m pip install pip -U @@ -107,10 +107,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python 3.7 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: "3.9" - name: Install dependencies run: | python -m pip install pip -U @@ -134,10 +134,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python 3.7 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: "3.9" - name: Install dependencies run: | python -m pip install pip -U @@ -170,10 +170,10 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Set up Python 3.7 + - name: Set up Python 3.9 uses: actions/setup-python@v2 with: - python-version: 3.7 + python-version: "3.9" - name: Install dependencies run: | python -m pip install pip -U diff --git a/.gitignore b/.gitignore index 9d5aa40..cc60298 100644 --- a/.gitignore +++ b/.gitignore @@ -62,4 +62,6 @@ target/ .ipynb_checkpoints .pypirc -.idea/ \ No newline at end of file +.idea/ +# virtualenvs +.venv*/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dd2ed0b..8d51036 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,16 @@ repos: - - repo: https://github.com/timothycrosley/isort - rev: 5.6.4 + - repo: https://github.com/pycqa/isort + rev: 5.13.2 hooks: - id: isort - language_version: python3.7 exclude: '/mibs' - - repo: https://github.com/python/black - rev: 20.8b1 + - repo: https://github.com/psf/black + rev: 24.8.0 hooks: - id: black - language_version: python3.7 exclude: '/mibs' - - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.4 + - repo: https://github.com/pycqa/flake8 + rev: 7.1.1 hooks: - id: flake8 additional_dependencies: [ @@ -22,5 +20,4 @@ repos: flake8-print, flake8-eradicate, ] - language_version: python3.7 - exclude: '/mibs' \ No newline at end of file + exclude: '/mibs' diff --git a/cloudshell/huawei/autoload/huawei_generic_snmp_autoload.py b/cloudshell/huawei/autoload/huawei_generic_snmp_autoload.py index 865f15f..e043ed6 100644 --- a/cloudshell/huawei/autoload/huawei_generic_snmp_autoload.py +++ b/cloudshell/huawei/autoload/huawei_generic_snmp_autoload.py @@ -1,51 +1,8 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from cloudshell.snmp.autoload.generic_snmp_autoload import ( - GeneralAutoloadError, - GenericSNMPAutoload, - log_autoload_details, -) +from cloudshell.snmp.autoload.generic_snmp_autoload import GenericSNMPAutoload class HuaweiGenericSNMPAutoload(GenericSNMPAutoload): - def __init__(self, snmp_handler, logger): - super(HuaweiGenericSNMPAutoload, self).__init__(snmp_handler, logger) - - def discover( - self, supported_os, resource_model, validate_module_id_by_port_name=False - ): - """General entry point for autoload. - - Read device structure and attributes: chassis, modules, submodules, ports, - port-channels and power supplies - :type resource_model: cloudshell.shell.standards.autoload_generic_models.GenericResourceModel # noqa: E501 - :param str supported_os: - :param bool validate_module_id_by_port_name: - :return: AutoLoadDetails object - """ - self.entity_table_service.validate_module_id_by_port_name = ( - validate_module_id_by_port_name - ) - if not resource_model: - return - self._resource_model = resource_model - if not self.system_info_service.is_valid_device_os(supported_os): - raise GeneralAutoloadError("Unsupported device OS") - - self.logger.info("*" * 70) - self.logger.info("Start SNMP discovery process .....") - self.system_info_service.fill_attributes(resource_model) - - entity_chassis_tree_dict = self.entity_table_service.chassis_structure_dict - - if entity_chassis_tree_dict: - self._build_structure(entity_chassis_tree_dict.values(), resource_model) - self._get_port_channels(resource_model) - - autoload_details = resource_model.build( - filter_empty_modules=True, use_new_unique_id=True - ) - - log_autoload_details(self.logger, autoload_details) - return autoload_details + """Huawei SNMP autoload based on the generic implementation.""" diff --git a/cloudshell/huawei/command_actions/add_remove_vlan_actions.py b/cloudshell/huawei/command_actions/add_remove_vlan_actions.py index f072e78..e640054 100644 --- a/cloudshell/huawei/command_actions/add_remove_vlan_actions.py +++ b/cloudshell/huawei/command_actions/add_remove_vlan_actions.py @@ -30,7 +30,7 @@ def create_vlan_range(self, start_vlan, end_vlan): ).execute_command(start_vlan=start_vlan, end_vlan=end_vlan) def set_vlan_to_interface(self, vlan, port_mode, qnq): - """ """ + """ """ if qnq: CommandTemplateExecutor( @@ -52,7 +52,7 @@ def set_vlan_to_interface(self, vlan, port_mode, qnq): ).execute_command(vlan=vlan) def set_vlan_range_to_interface(self, start_vlan, end_vlan, port_mode): - """ """ + """ """ if port_mode == "trunk": CommandTemplateExecutor( @@ -72,7 +72,7 @@ def activate_port(self): ).execute_command() def activate_port_mode(self): - """ """ + """ """ CommandTemplateExecutor( self._cli_service, add_remove_vlan.START_PORT_MODE diff --git a/cloudshell/huawei/flows/huawei_autoload_flow.py b/cloudshell/huawei/flows/huawei_autoload_flow.py index 2b66aaa..9866ea2 100644 --- a/cloudshell/huawei/flows/huawei_autoload_flow.py +++ b/cloudshell/huawei/flows/huawei_autoload_flow.py @@ -14,7 +14,7 @@ class HuaweiSnmpAutoloadFlow(AbstractAutoloadFlow): MIBS_FOLDER = os.path.join(os.path.dirname(__file__), os.pardir, "mibs") def __init__(self, logger, snmp_handler): - super(HuaweiSnmpAutoloadFlow, self).__init__(logger) + super().__init__(logger) self._snmp_handler = snmp_handler def _autoload_flow(self, supported_os, resource_model): @@ -26,8 +26,10 @@ def _autoload_flow(self, supported_os, resource_model): "HUAWEI-TC-MIB", ] ) - snmp_autoload = HuaweiGenericSNMPAutoload(snmp_service, self._logger) - - return snmp_autoload.discover( - supported_os, resource_model, validate_module_id_by_port_name=False + snmp_autoload = HuaweiGenericSNMPAutoload( + snmp_handler=snmp_service, + logger=self._logger, + resource_model=resource_model, ) + + return snmp_autoload.discover(supported_os) diff --git a/cloudshell/huawei/flows/huawei_configuration_flow.py b/cloudshell/huawei/flows/huawei_configuration_flow.py index 025fd6d..4255936 100644 --- a/cloudshell/huawei/flows/huawei_configuration_flow.py +++ b/cloudshell/huawei/flows/huawei_configuration_flow.py @@ -1,8 +1,12 @@ #!/usr/bin/python # -*- coding: utf-8 -*- -from cloudshell.shell.flows.configuration.basic_flow import AbstractConfigurationFlow -from cloudshell.shell.flows.utils.networking_utils import UrlParser +from cloudshell.shell.flows.configuration.basic_flow import ( + AbstractConfigurationFlow, + ConfigurationType, + RestoreMethod, +) +from cloudshell.shell.flows.utils.url import RemoteURL from cloudshell.huawei.command_actions.save_restore_actions import SaveRestoreActions from cloudshell.huawei.command_actions.system_actions import SystemActions @@ -10,12 +14,14 @@ class HuaweiConfigurationFlow(AbstractConfigurationFlow): + SUPPORTED_RESTORE_METHODS = {RestoreMethod.OVERRIDE} + def __init__(self, cli_handler, resource_config, logger): - super(HuaweiConfigurationFlow, self).__init__(logger, resource_config) + super().__init__(logger, resource_config) self._cli_handler = cli_handler @property - def _file_system(self): + def file_system(self): """Determine device file system type.""" with self._cli_handler.get_cli_service( self._cli_handler.enable_mode @@ -25,115 +31,92 @@ def _file_system(self): return startup_config_filename.split(":")[0] - def _save_flow(self, folder_path, configuration_type, vrf_management_name=None): + def _save_flow(self, file_dst_url, configuration_type, vrf_management_name=None): """Execute flow which save selected file to the provided destination. - :param folder_path: destination path where file will be saved + :param file_dst_url: destination URL where file will be saved :param configuration_type: source file, which will be saved :param vrf_management_name: Virtual Routing and Forwarding Name :return: saved configuration file name """ - if not configuration_type.endswith("-config"): - configuration_type += "-config" - - if configuration_type not in ["running-config", "startup-config"]: - raise HuaweiSaveRestoreException( - "Device doesn't support saving '{}' configuration type".format( - configuration_type - ), - ) - - url = UrlParser().parse_url(folder_path) - with self._cli_handler.get_cli_service( self._cli_handler.enable_mode ) as enable_session: system_action = SystemActions(enable_session, self._logger) save_action = SaveRestoreActions(enable_session, self._logger) - if configuration_type == "running-config": + if configuration_type == ConfigurationType.RUNNING: src_file = "quali_run_config.cfg" save_action.save_runninig_config(dst_file=src_file) else: src_file = system_action.display_startup_config() - scheme = url.get(UrlParser.SCHEME).lower() + scheme = file_dst_url.scheme.lower().rstrip(":/") - if (not scheme or scheme == self.file_system) and src_file != folder_path: - save_action.copy_file(src_file=src_file, dst_file=folder_path) - elif scheme in ["ftp", "tftp"]: + if isinstance(file_dst_url, RemoteURL): + if scheme not in ["ftp", "tftp"]: + raise HuaweiSaveRestoreException( + "Unsupported backup protocol {scheme}. " + "Supported types are ftp, tftp or " + "local({file_system})".format( + scheme=scheme, file_system=self.file_system + ) + ) save_action.put_file( - server_address=url.get(UrlParser.HOSTNAME), + server_address=file_dst_url.host, src_file=src_file, - dst_file=url.get(UrlParser.FILENAME), + dst_file=file_dst_url.filename, ) else: - raise HuaweiSaveRestoreException( - "Unsupported backup protocol {scheme}. " - "Supported types are ftp, tftp of local({file_system})".format( - scheme=scheme, file_system=self.file_system - ) - ) + dst_file = file_dst_url.url + if src_file != dst_file: + save_action.copy_file(src_file=src_file, dst_file=dst_file) def _restore_flow( - self, path, configuration_type, restore_method, vrf_management_name + self, config_path, configuration_type, restore_method, vrf_management_name ): - """Execute flow which save selected file to the provided destination. + """Execute flow which restores selected file to the provided destination. - :param path: the path to the configuration file, including the configuration - file name + :param config_path: the URL of the configuration file, including the + configuration file name :param restore_method: the restore method to use when restoring the configuration file. Possible Values are append and override :param configuration_type: the configuration type to restore. Possible values are startup and running :param vrf_management_name: Virtual Routing and Forwarding Name """ - if not configuration_type: - configuration_type = "running-config" - elif "-config" not in configuration_type: - configuration_type = configuration_type.lower() + "-config" - - if configuration_type not in ["running-config", "startup-config"]: + if restore_method != RestoreMethod.OVERRIDE: raise HuaweiSaveRestoreException( - "Device doesn't support restoring '{}' configuration type".format( - configuration_type - ) + "Huawei do no yet support append operations on configuration files" ) - if not restore_method: - restore_method = "override" - - url = UrlParser().parse_url(path) - with self._cli_handler.get_cli_service( self._cli_handler.enable_mode ) as enable_session: system_action = SystemActions(enable_session, self._logger) restore_action = SaveRestoreActions(enable_session, self._logger) - if restore_method == "override": + if isinstance(config_path, RemoteURL): + scheme = config_path.scheme.lower() + if scheme not in ["ftp", "tftp"]: + raise HuaweiSaveRestoreException( + "Unsupported restore protocol {scheme}. " + "Supported types are ftp, tftp or " + "local({file_system})".format( + scheme=scheme, file_system=self.file_system + ) + ) dst_file = "{file_system}:/{file_name}".format( - file_system=self.file_system, file_name=url.get(UrlParser.FILENAME) + file_system=self.file_system, file_name=config_path.filename ) - - scheme = url.get(UrlParser.SCHEME).lower() - if not scheme or scheme == self.file_system: - restore_action.setup_startup_config(path) - elif scheme in ["ftp", "tftp"]: - restore_action.get_file( - server_address=url.get(UrlParser.HOSTNAME), - src_file="{path}/{file}".format( - path=url.get(UrlParser.PATH).rstrip("/"), - file=url.get(UrlParser.FILENAME), - ), - dst_file=dst_file, - ) - restore_action.setup_startup_config(dst_file) - - if configuration_type == "running-config": - system_action.reboot() - - else: - raise HuaweiSaveRestoreException( - "Huawei do no yet support append operations on configuration files" + restore_action.get_file( + server_address=config_path.host, + src_file=config_path.path.lstrip("/"), + dst_file=dst_file, ) + restore_action.setup_startup_config(dst_file) + else: + restore_action.setup_startup_config(config_path.url) + + if configuration_type == ConfigurationType.RUNNING: + system_action.reboot() diff --git a/cloudshell/huawei/flows/huawei_connectivity_flow.py b/cloudshell/huawei/flows/huawei_connectivity_flow.py index 79236c3..2fed390 100644 --- a/cloudshell/huawei/flows/huawei_connectivity_flow.py +++ b/cloudshell/huawei/flows/huawei_connectivity_flow.py @@ -2,6 +2,15 @@ # -*- coding: utf-8 -*- from cloudshell.shell.flows.connectivity.basic_flow import AbstractConnectivityFlow +from cloudshell.shell.flows.connectivity.models.connectivity_model import ( + ConnectivityActionModel, +) +from cloudshell.shell.flows.connectivity.models.driver_response import ( + ConnectivityActionResult, +) +from cloudshell.shell.flows.connectivity.parse_request_service import ( + ParseConnectivityRequestService, +) class HuaweiConnectivityFlow(AbstractConnectivityFlow): @@ -12,40 +21,50 @@ def __init__( support_vlan_range_str=False, support_multi_vlan_str=False, ): - super(HuaweiConnectivityFlow, self).__init__(logger) + parse_connectivity_service = ParseConnectivityRequestService( + is_vlan_range_supported=support_vlan_range_str, + is_multi_vlan_supported=support_multi_vlan_str, + ) + super().__init__(parse_connectivity_service, logger) self._cli_handler = cli_handler - self.IS_VLAN_RANGE_SUPPORTED = support_vlan_range_str - self.IS_MULTI_VLAN_SUPPORTED = support_multi_vlan_str - def _add_vlan_flow(self, vlan_range, port_mode, full_name, qnq, c_tag, vm_uid): - """Configures VLANs on multiple ports or port-channels. + def _set_vlan(self, action: ConnectivityActionModel) -> ConnectivityActionResult: + vlan_range = action.connection_params.vlan_id + port_mode = action.connection_params.mode.value + full_name = action.action_target.name + qnq = action.connection_params.vlan_service_attrs.qnq + c_tag = action.connection_params.vlan_service_attrs.ctag + self._add_vlan_flow(vlan_range, port_mode, full_name, qnq, c_tag) + return ConnectivityActionResult.success_result( + action, f"VLAN(s) {vlan_range} configuration completed" + ) + + def _remove_vlan(self, action: ConnectivityActionModel) -> ConnectivityActionResult: + vlan_range = action.connection_params.vlan_id + full_name = action.action_target.name + self._remove_vlan_flow(vlan_range, full_name) + return ConnectivityActionResult.success_result( + action, f"VLAN(s) {vlan_range} removal completed" + ) + + def _add_vlan_flow(self, vlan_range, port_mode, full_name, qnq, c_tag): + """Configure VLANs on multiple ports or port-channels. :param vlan_range: VLAN or VLAN range :param port_mode: mode which will be configured on port. Possible Values are trunk and access - :param port_name: full port name + :param full_name: full port name :param qnq: :param c_tag: :return: """ pass - def _remove_vlan_flow(self, vlan_range, full_name, port_mode, vm_uid): + def _remove_vlan_flow(self, vlan_range, full_name): """Remove configuration of VLANs on multiple ports or port-channels. :param vlan_range: VLAN or VLAN range :param full_name: full port name - :param port_mode: mode which will be configured on port. - Possible Values are trunk and access - :return: - """ - pass - - def _remove_all_vlan_flow(self, full_name, vm_uid): - """Remove configuration of VLANs on multiple ports or port-channels. - - :param port_name: full port name - Possible Values are trunk and access :return: """ pass diff --git a/cloudshell/huawei/flows/huawei_enable_snmp_flow.py b/cloudshell/huawei/flows/huawei_enable_snmp_flow.py index 2634b7d..0d886c8 100644 --- a/cloudshell/huawei/flows/huawei_enable_snmp_flow.py +++ b/cloudshell/huawei/flows/huawei_enable_snmp_flow.py @@ -56,21 +56,21 @@ def enable_flow(self, snmp_parameters): else: raise HuaweiSNMPException("Wrong SNMPv3 parameters") - auth_protocol = self.ENCRYPTION.get(snmp_parameters.auth_protocol) + auth_protocol = self.ENCRYPTION.get(snmp_parameters.snmp_auth_protocol) if not auth_protocol: raise HuaweiSNMPException( "Wrong authentication protocol ({}) provided".format( - snmp_parameters.auth_protocol + snmp_parameters.snmp_auth_protocol ) ) private_key_protocol = self.ENCRYPTION.get( - snmp_parameters.private_key_protocol + snmp_parameters.snmp_private_key_protocol ) if not private_key_protocol: raise HuaweiSNMPException( "Wrong privacy key protocol ({}) provided".format( - snmp_parameters.private_key_protocol + snmp_parameters.snmp_private_key_protocol ) ) diff --git a/cloudshell/huawei/flows/huawei_load_firmware_flow.py b/cloudshell/huawei/flows/huawei_load_firmware_flow.py index 45e0178..8f51792 100644 --- a/cloudshell/huawei/flows/huawei_load_firmware_flow.py +++ b/cloudshell/huawei/flows/huawei_load_firmware_flow.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- from cloudshell.shell.flows.firmware.basic_flow import AbstractFirmwareFlow -from cloudshell.shell.flows.utils.networking_utils import UrlParser +from cloudshell.shell.flows.utils.url import RemoteURL from cloudshell.huawei.command_actions.firmware_actions import FirmwareActions from cloudshell.huawei.command_actions.save_restore_actions import SaveRestoreActions @@ -11,22 +11,22 @@ class HuaweiLoadFirmwareFlow(AbstractFirmwareFlow): - FILE_TYPE = "flash" + FILE_SYSTEM = "flash" - def __init__(self, cli_handler, logger): - super(HuaweiLoadFirmwareFlow, self).__init__(logger) + def __init__(self, cli_handler, logger, resource_config): + super().__init__(logger, resource_config) self._cli_handler = cli_handler - def _load_firmware_flow(self, path, vrf_management_name, timeout): + def _load_firmware_flow(self, firmware_url, vrf_management_name, timeout): """Load a firmware onto the device. - :param path: The path to the firmware file, including the firmware file name + :param firmware_url: The URL of the firmware file, including the firmware + file name :param vrf_management_name: Virtual Routing and Forwarding Name :param timeout: :return: """ - url = UrlParser().parse_url(path) - firmware_file_name = url.get(UrlParser.FILENAME) + firmware_file_name = firmware_url.filename if not firmware_file_name: raise HuaweiFirmwareException("Unable to find firmware file") @@ -38,30 +38,29 @@ def _load_firmware_flow(self, path, vrf_management_name, timeout): firmware_actions = FirmwareActions(config_session, self._logger) system_actions = SystemActions(config_session, self._logger) - scheme = url.get(UrlParser.SCHEME).lower() - if not scheme: - dst_file = "{file_system}:/{file_path}".format( - file_system=self.FILE_SYSTEM, file_path=path.lstrip("/") - ) - elif scheme == self.FILE_SYSTEM: - dst_file = path - elif scheme in ["ftp", "tftp"]: + if isinstance(firmware_url, RemoteURL): + scheme = firmware_url.scheme.lower() + if scheme not in ["ftp", "tftp"]: + raise HuaweiFirmwareException( + "Unsupported protocol. " + "Updating firmware possible from tftp, " + "ftp or local storage({}) only".format(self.FILE_SYSTEM) + ) dst_file = "{file_system}:/{file_name}".format( file_system=self.FILE_SYSTEM, file_name=firmware_file_name ) config_actions.get_file( - server_address=url.get(UrlParser.HOSTNAME), - src_file="{path}/{file}".format( - path=url.get(UrlParser.PATH), file=firmware_file_name - ), + server_address=firmware_url.host, + src_file=firmware_url.path.lstrip("/"), dst_file=dst_file, ) else: - raise HuaweiFirmwareException( - "Unsupported protocol. " - "Updating firmware possible from tftp, " - "ftp or local storage({}) only".format(self.FILE_SYSTEM) - ) + dst_file = firmware_url.url + if not firmware_url.scheme: + dst_file = "{file_system}:/{file_path}".format( + file_system=self.FILE_SYSTEM, + file_path=firmware_url.path.lstrip("/"), + ) firmware_actions.update_firmware(firmware_file=dst_file) system_actions.reboot() diff --git a/cloudshell/huawei/snmp/huawei_snmp_handler.py b/cloudshell/huawei/snmp/huawei_snmp_handler.py index 0df562d..fd45571 100644 --- a/cloudshell/huawei/snmp/huawei_snmp_handler.py +++ b/cloudshell/huawei/snmp/huawei_snmp_handler.py @@ -4,6 +4,7 @@ EnableDisableSnmpConfigurator, EnableDisableSnmpFlowInterface, ) +from cloudshell.snmp.snmp_parameters import get_snmp_parameters_from_config from cloudshell.huawei.flows.huawei_disable_snmp_flow import HuaweiDisableSnmpFlow from cloudshell.huawei.flows.huawei_enable_snmp_flow import HuaweiEnableSnmpFlow @@ -33,6 +34,10 @@ class HuaweiSnmpHandler(EnableDisableSnmpConfigurator): def __init__(self, resource_config, logger, cli_handler): self.cli_handler = cli_handler enable_disable_snmp_flow = HuaweiEnableDisableSnmpFlow(self.cli_handler, logger) - super(HuaweiSnmpHandler, self).__init__( - enable_disable_snmp_flow, resource_config, logger + super().__init__( + enable_disable_snmp_flow=enable_disable_snmp_flow, + snmp_parameters=get_snmp_parameters_from_config(resource_config), + enable_snmp=resource_config.enable_snmp, + disable_snmp=resource_config.disable_snmp, + logger=logger, ) diff --git a/requirements.txt b/requirements.txt index c8a2908..fc2ecbe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ tftpy -cloudshell-shell-flows>=1.0,<2 -cloudshell-shell-connectivity-flow>=2.0,<3.0 -cloudshell-cli>=4.0,<5 -cloudshell-snmp>=4.0,<5 -cloudshell-snmp-autoload>=1.0,<2.0 -functools32; python_version <= '2.7' -cloudshell-shell-standards~=1.2 \ No newline at end of file +cloudshell-shell-flows>=2.0.3,<3.0 +cloudshell-shell-connectivity-flow>=3.1.3,<4.0 +cloudshell-cli>=5.0,<6 +cloudshell-snmp>=6.0,<7.0 +cloudshell-snmp-autoload>=3.0,<4.0 +cloudshell-shell-standards>=2.0,<3.0 +standard-telnetlib;python_version>='3.13' diff --git a/setup.py b/setup.py index c2537b9..0688869 100644 --- a/setup.py +++ b/setup.py @@ -22,4 +22,13 @@ version=version_from_file, description="QualiSystems networking Huawei specific package", include_package_data=True, + python_requires=">=3.9", + classifiers=[ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + ], ) diff --git a/test_requirements.txt b/test_requirements.txt index a5f34ff..9955dec 100644 --- a/test_requirements.txt +++ b/test_requirements.txt @@ -1,3 +1,2 @@ -mock; python_version <= '2.7' pytest -pytest-cov \ No newline at end of file +pytest-cov diff --git a/tox.ini b/tox.ini index 422d8de..8ecdd5b 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] envlist = - py{37}-{master,dev} + py{39}-{master,dev} pre-commit build distshare = dist @@ -26,9 +26,12 @@ commands = pre-commit run --all-files --show-diff-on-failure [testenv:build] skip_install = true +deps = + setuptools + wheel commands = - python setup.py -q sdist --format zip - python setup.py -q bdist_wheel --universal + python setup.py -q sdist + python setup.py -q bdist_wheel [isort] profile=black @@ -39,5 +42,5 @@ skip = mibs max-line-length = 88 ;we don't need have docstrings in every func, class and package ;and W503 is not PEP 8 compliant -ignore = D100,D101,D102,D103,D104,D105,D106,D107,D401,W503,E203 +ignore = D100,D101,D102,D103,D104,D105,D106,D107,D401,D419,W503,E203 exclude = mibs diff --git a/version.txt b/version.txt index 1454f6e..ee74734 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.1 +4.1.0