diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8061dfe..e934ff1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,18 @@ repos: - - repo: https://github.com/pre-commit/mirrors-isort - rev: v4.3.21 + - repo: https://github.com/timothycrosley/isort + rev: 4.3.21 hooks: - id: isort language_version: python3.7 exclude: '/mibs' - repo: https://github.com/python/black - rev: 19.3b0 + rev: 19.10b0 hooks: - id: black language_version: python3.7 exclude: '/mibs' - repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.8 + rev: 3.7.9 hooks: - id: flake8 additional_dependencies: [ diff --git a/.travis.yml b/.travis.yml index 2166a98..a90f69d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: python - +python: 3.7 jobs: include: - if: branch = master @@ -7,7 +7,6 @@ jobs: env: TOXENV=py27-master after_success: codecov - if: branch = master - python: 3.7 env: TOXENV=py37-master after_success: codecov - if: branch != master @@ -15,16 +14,76 @@ jobs: env: TOXENV=py27-dev after_success: codecov - if: branch != master - python: 3.7 env: TOXENV=py37-dev after_success: codecov - env: TOXENV=build - python: 2.7 - env: TOXENV=pre-commit - python: 3.7 + - stage: Deploy to Test PyPI release + env: TOXENV=build + before_script: sed -i -E "s/^([0-9]+\.[0-9]+\.[0-9]+)$/\1.$TRAVIS_BUILD_NUMBER/" version.txt + deploy: + distributions: skip + skip_cleanup: true + provider: pypi + server: https://test.pypi.org/legacy/ + user: "__token__" + password: + secure: "ihwigIM6U+fqzRPWCljMiThKTJ6PITRS8oeFncLTV0IzHX9I6HHjtOS0tGKQWBtphMH4lYd2mnvGgrWs7A0BpnWUXxxTErk1bRKUS6dL8DDXtoY7UARpO6XnE6baCIXYnA0A2AUOYpx10uEE4pORjdP4UgIylU/Dqn8YFNpModLb9h1vi71BjC6E/HjOihKrP58BNgAuQZlrZtTjWU6QOCDPPSDkEFlzpIiYAYany12H7bnHCv47f9Ppks+43cp2Y5H5XgokJjka1HH6HOvuQtOk9D6tKlHgb0a9Un3yAZVTPjmD44DcmV0DlqSW2Xio8T9WqoWauL+DWFzywSzchcmltkBPyoQLXRD1vfL31N+dYkQ1Vd5HAZ33T5LSsoLlxgW+Y53ZLerCUvMP0699Qad6wJTRY57makkJ5JfmylXtt70bSmhgxskSKpwnPG3A3+vFEIXxgRIw8719yEPdczz/4v9ASPg7Usm7jg3cLuvhZ1PSo9v7rL2zZjdcQj8MsMgKg4uPJKn7N3tEwuX+KGWh8ALU+58C5xilusiaaX4T0P3mestUqBJww133TtmjtHPACTS3sJIKwQ+w/5yeBBjW1apsLBCeW/OcDUgGWxCNpBq3zKMwfuK6lE4XYWwURcAIbGT+CcDgFv0vwEznrc69zwrGa52pX9KGV7tWSQ0=" + on: + branch: dev + - stage: Deploy to PyPI release + env: TOXENV=build + deploy: + distributions: skip + skip_cleanup: true + provider: pypi + user: "__token__" + password: + secure: "ZE5K3MjBqDfun1UOmMs67VgTUCAw2HM+23NqDt5dOsHvKboPuBRdNmo8CIkP7z8hA/ra0M7+jDqLMegsczMEi643kyjL0QzedVJVQ0Xj6F7XPSgjfP0ff/mNAhTCra1VOzXsNVlkhmZXkAUtGoQX3i84Qi3FgUuNMo2t0akoHNf3qZuOCODAWQ1LmxgoF8kS2x0o1mvLpK+4LRuQod7w4tlBnDKczs+LsTeA9KzuV6oMxcbC3zdTFmQy4HzwOrxWp9XvCEfFuVh+5e0DQiz1iNn2vNDlqE1Uv4D2dS0Pkr0nId4iO82RilANGBOVVgK6oq0U+4UfKUr0NEFoLM9vFeevJ8DhTGdPJwqPqry+UkbPsArmDjA6T3ZSnnlb1Lv4cAbAmZXEjdm11XzukXpecTjPG6ZfmWqGcSgAiAVj+vIJ9mAwa87cKt8heEoMEBW/mPcTl0Q+0EcJ606wOuaIAP5KAF8vDcyei2/GUNCYRaI8wR+zyNHL4nSLcOazPhU1M5ETWMm5NSKv2qa52stebRcyqdz7n38yDPIrH9NWWlEghtOeY3YPaVaDLobs30UI46ve/Galy97rVSqwLSTSZ6+/ukawQD4DUhCIYUKuvF+OEKj4WsoXJAps+YJR5WXfFV0xp3rnktU43Ni1HOsDWNx4H4SWSRiqbep9VtLSHfs=" + on: + tags: true + - stage: Create GitHub release + env: TOXENV=build + before_deploy: + - export AUTHOR_EMAIL="$(git log -1 $TRAVIS_COMMIT --pretty="%cE")" + - export AUTHOR_NAME="$(git log -1 $TRAVIS_COMMIT --pretty="%aN")" + - export GIT_TAG="$(cat version.txt | tr -d ' \t\n\r')" + - git config --local user.name $AUTHOR_NAME + - git config --local user.email $AUTHOR_EMAIL + - git tag $GIT_TAG + deploy: + provider: releases + skip_cleanup: true + draft: true + api_key: + secure: "fZmNYqsImMDFjn0rpNMQEXXWn8AxCPV7U+8c2mPKaEfS0jVG6ytyBhKSWxRL16kgm4XlMr3OzCpWlk9aNqTRCN8qNwniIEUfLva4V31g2fBo4a3msQO/HDDve9Us3bo+9AWwy1velMOEE0kV0YzGUD5A/SIWYN8hpxVn/VxaCYXTgxlHOMt++az17yiomzTr+9/g2bJg82BERt5FvGPJFUrDUl0miiGCi7msXW192MQZS6FCC39+sFoM5eYApg8LTNWk7/C5nPKdDiUmKLOa1tVh9gUWfYQMqhN+BCTjr9uVQrpgTjbmju1s9JpdyrvvsZUPwk3em2KjImRJ7N0/si9rVUcGxjKtPnmNkvo5R3gNWAaZ7rsN8wceqIb+9h3Nb6cnjtV1BPnDp7eycYaqxpJ56b/pwJ3sCDx6tlB3mbPN8ggEbFBztcGIYsqkvQ250sx5s8WpEA+bVXN1ZzhG3es6FCvjuJGdHmhRX5LM4mdHiexXmj0+cVg+2kitAx3tG2+xRowA+W7OQBiE/0l48ZywMf43N4uoPKN05ik+wqRQFuHd7FeH213y+7MsT83jY9sv5QD8u2/cJmGA6mBI7KHseFiIhhLO/4PJa+iXNK0Z0arxf2PeDxWdBSqMHA5h1m7LmfEwqxhZMMZQ9V4swLwCEK9jJbfDo07qW24fT2I=" + file_glob: true + file: dist/* + name: cloudshell-snmp $GIT_TAG + target_commitish: master + on: + branch: master + - stage: Check version + language: bash + install: + - git clone https://github.com/$TRAVIS_REPO_SLUG.git $TRAVIS_REPO_SLUG + - cd $TRAVIS_REPO_SLUG + - git checkout -qf $TRAVIS_PULL_REQUEST_BRANCH + script: "! git diff --exit-code --quiet origin/master version.txt" install: - pip install tox - pip install codecov script: tox + +stages: + - name: Check version + if: branch = master AND type = pull_request + - name: Test + - name: Deploy to Test PyPI release + if: branch = dev AND type != pull_request + - name: Create GitHub release + if: branch = master AND type != pull_request + - name: Deploy to PyPI release + if: tag IS present diff --git a/cloudshell/snmp/core/domain/quali_mib_table.py b/cloudshell/snmp/core/domain/quali_mib_table.py index 6f13fe4..97c5552 100644 --- a/cloudshell/snmp/core/domain/quali_mib_table.py +++ b/cloudshell/snmp/core/domain/quali_mib_table.py @@ -34,7 +34,6 @@ def get_columns(self, *names): :param names: list of requested columns names. :return: a partial table containing only the requested columns. """ - names = [n for n in names] return QualiMibTable( self._name, OrderedDict( diff --git a/cloudshell/snmp/core/snmp_service.py b/cloudshell/snmp/core/snmp_service.py index 3957e91..86ecf50 100644 --- a/cloudshell/snmp/core/snmp_service.py +++ b/cloudshell/snmp/core/snmp_service.py @@ -61,6 +61,19 @@ def load_mib_tables(self, mib_list): for mib in mib_list: mib_builder.loadModules(mib) + def translate_oid(self, snmp_oid): + """Translates Raw OID into a human readable identifiers. + + :param str snmp_oid: OID string. like: '1.3.6.1.2.1.1.4.0' + :return translated OID name + :rtype str + """ + result = SnmpResponse( + snmp_oid, None, snmp_engine=self._snmp_engine, logger=self._logger + ) + + return result.mib_id + def set(self, snmp_set_oids): # noqa: A003 """SNMP Set operation. diff --git a/cloudshell/snmp/core/tools/snmp_parameters_helper.py b/cloudshell/snmp/core/tools/snmp_parameters_helper.py index 70547cf..3585b3a 100644 --- a/cloudshell/snmp/core/tools/snmp_parameters_helper.py +++ b/cloudshell/snmp/core/tools/snmp_parameters_helper.py @@ -5,9 +5,6 @@ class SnmpParametersConverter(object): AUTH_PRIV = "authPriv" class PySnmpVersion: - def __init__(self): - pass - V1 = 0 V2 = 1 V3 = 3 @@ -44,7 +41,7 @@ def version(self): def user(self): if not self._user: self._user = self.DEFAULT_USER - if hasattr(self.snmp_parameters, "user"): + if hasattr(self.snmp_parameters, "snmp_user"): self._user = self.snmp_parameters.snmp_user return self._user diff --git a/cloudshell/snmp/core/tools/snmp_security.py b/cloudshell/snmp/core/tools/snmp_security.py index 08963a3..e777e83 100644 --- a/cloudshell/snmp/core/tools/snmp_security.py +++ b/cloudshell/snmp/core/tools/snmp_security.py @@ -12,20 +12,20 @@ def __init__(self, py_snmp_params, logger): self._logger = logger def add_security(self, snmp_engine): - if hasattr(self._py_snmp_params, "snmp_password"): + if hasattr(self._py_snmp_params.snmp_parameters, "snmp_password"): auth_protocol = AUTH_PROTOCOL_MAP.get( self._py_snmp_params.snmp_parameters.snmp_auth_protocol ) priv_protocol = PRIV_PROTOCOL_MAP.get( - self._py_snmp_params.snmp_parameters.snmp_priv_protocol + self._py_snmp_params.snmp_parameters.snmp_private_key_protocol ) config.addV3User( - snmp_engine, - self._py_snmp_params.user, - auth_protocol, - self._py_snmp_params.snmp_parameters.snmp_password, - priv_protocol, - self._py_snmp_params.snmp_parameters.snmp_v3_priv_key, + snmpEngine=snmp_engine, + userName=self._py_snmp_params.user, + authProtocol=auth_protocol, + authKey=self._py_snmp_params.snmp_parameters.snmp_password, + privProtocol=priv_protocol, + privKey=self._py_snmp_params.snmp_parameters.snmp_private_key, ) else: config.addV1System( diff --git a/cloudshell/snmp/mibs/ENTITY-MIB.py b/cloudshell/snmp/mibs/ENTITY-MIB.py index 05e81f4..0dcaff2 100644 --- a/cloudshell/snmp/mibs/ENTITY-MIB.py +++ b/cloudshell/snmp/mibs/ENTITY-MIB.py @@ -14,21 +14,25 @@ # Types + class PhysicalClass(Integer): subtypeSpec = Integer.subtypeSpec+SingleValueConstraint(9,3,7,10,5,2,6,11,1,4,8,12,) namedValues = NamedValues(("other", 1), ("port", 10), ("stack", 11), ("cpu", 12), ("unknown", 2), ("chassis", 3), ("backplane", 4), ("container", 5), ("powerSupply", 6), ("fan", 7), ("sensor", 8), ("module", 9), ) - + + class PhysicalIndex(TextualConvention, Integer32): displayHint = "d" subtypeSpec = Integer32.subtypeSpec+ValueRangeConstraint(1,2147483647) - + + class PhysicalIndexOrZero(TextualConvention, Integer32): displayHint = "d" subtypeSpec = Integer32.subtypeSpec+ValueRangeConstraint(0,2147483647) - + + class SnmpEngineIdOrNone(OctetString): subtypeSpec = OctetString.subtypeSpec+ValueSizeConstraint(0,32) - + # Objects diff --git a/cloudshell/snmp/snmp_parameters.py b/cloudshell/snmp/snmp_parameters.py index 84e8911..e829bfe 100644 --- a/cloudshell/snmp/snmp_parameters.py +++ b/cloudshell/snmp/snmp_parameters.py @@ -1,3 +1,6 @@ +import warnings + + class SnmpParameters(object): class SnmpVersion: def __init__(self): @@ -112,8 +115,50 @@ def __init__( self.snmp_user = snmp_user self.snmp_password = snmp_password self.snmp_private_key = snmp_private_key - self.auth_protocol = auth_protocol - self.private_key_protocol = private_key_protocol + self.snmp_auth_protocol = auth_protocol + self.snmp_private_key_protocol = private_key_protocol + + # For backward compatibility auth_protocol and private_key_protocol + @property + def auth_protocol(self): + warnings.warn( + "auth_protocol is obsolete please use snmp_auth_protocol field instead", + DeprecationWarning, + stacklevel=2, + ) + return self.snmp_auth_protocol + + @auth_protocol.setter + def auth_protocol(self, value): + warnings.warn( + "auth_protocol is obsolete please use snmp_auth_protocol field instead", + DeprecationWarning, + stacklevel=2, + ) + self.snmp_auth_protocol = value + + @property + def private_key_protocol(self): + warnings.warn( + "private_key_protocol is obsolete please " + "use snmp_private_key_protocol " + "field instead", + DeprecationWarning, + stacklevel=2, + ) + + return self.snmp_private_key_protocol + + @private_key_protocol.setter + def private_key_protocol(self, value): + warnings.warn( + "private_key_protocol is obsolete please " + "use snmp_private_key_protocol " + "field instead", + DeprecationWarning, + stacklevel=2, + ) + self.snmp_private_key_protocol = value def validate(self): super(SNMPV3Parameters, self).validate() diff --git a/tests/snmp/core/domain/__init__.py b/tests/snmp/core/domain/__init__.py index e69de29..b36383a 100644 --- a/tests/snmp/core/domain/__init__.py +++ b/tests/snmp/core/domain/__init__.py @@ -0,0 +1,3 @@ +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/tests/snmp/core/tools/__init__.py b/tests/snmp/core/tools/__init__.py index e69de29..b36383a 100644 --- a/tests/snmp/core/tools/__init__.py +++ b/tests/snmp/core/tools/__init__.py @@ -0,0 +1,3 @@ +from pkgutil import extend_path + +__path__ = extend_path(__path__, __name__) diff --git a/tests/snmp/test_cloudshell_snmp.py b/tests/snmp/test_cloudshell_snmp.py new file mode 100644 index 0000000..7c068e1 --- /dev/null +++ b/tests/snmp/test_cloudshell_snmp.py @@ -0,0 +1,128 @@ +import sys +from unittest import TestCase + +from cloudshell.snmp.cloudshell_snmp import Snmp +from cloudshell.snmp.core.tools.snmp_constants import SNMP_RETRIES_COUNT, SNMP_TIMEOUT + +if sys.version_info >= (3, 0): + from unittest.mock import Mock, patch +else: + from mock import Mock, patch + + +class TestSNMP(TestCase): + def test_snmp_init_without_params(self): + # Act + snmp = Snmp() + + # Assert + self.assertEqual(snmp._snmp_timeout, SNMP_TIMEOUT) + self.assertEqual(snmp._snmp_retry_count, SNMP_RETRIES_COUNT) + + def test_snmp_init_with_params(self): + # Assign + timeout = 1 + retries = 2 + + # Act + snmp = Snmp(timeout=timeout, retry_count=retries) + + # Assert + self.assertEqual(snmp._snmp_timeout, timeout) + self.assertEqual(snmp._snmp_retry_count, retries) + + @patch("cloudshell.snmp.cloudshell_snmp.SnmpContextManager") + @patch("cloudshell.snmp.cloudshell_snmp.SnmpContext") + @patch("cloudshell.snmp.cloudshell_snmp.SnmpParametersConverter") + @patch("cloudshell.snmp.cloudshell_snmp.Snmp._get_snmp_engine") + def test_get_snmp_service( + self, mock_get_engine, mock_params_converter, mock_context, mock_context_manager + ): + logger = Mock() + snmp_params = Mock() + snmp = Snmp() + mock_params_converter.return_value.version = 0 + + snmp.get_snmp_service(snmp_parameters=snmp_params, logger=logger) + + mock_params_converter.assert_called_once_with(snmp_params) + mock_get_engine.assert_called_once_with( + mock_params_converter.return_value, logger + ) + mock_context.assert_called_once_with( + snmp_params.context_engine_id, snmp_params.context_name + ) + mock_context_manager.assert_called_once_with( + snmp_engine=mock_get_engine.return_value, + v3_context_engine_id=mock_context.return_value.context_engine_id, + v3_context_name=mock_context.return_value.context_name, + logger=logger, + get_bulk_flag=False, + is_snmp_read_only=mock_params_converter.return_value.is_read_only, + ) + + @patch("cloudshell.snmp.cloudshell_snmp.SnmpContextManager") + @patch("cloudshell.snmp.cloudshell_snmp.SnmpContext") + @patch("cloudshell.snmp.cloudshell_snmp.SnmpParametersConverter") + @patch("cloudshell.snmp.cloudshell_snmp.Snmp._get_snmp_engine") + def test_get_snmp_service_with_get_bulk( + self, mock_get_engine, mock_params_converter, mock_context, mock_context_manager + ): + logger = Mock() + snmp_params = Mock() + snmp = Snmp() + mock_params_converter.return_value.version = 1 + + snmp.get_snmp_service(snmp_parameters=snmp_params, logger=logger) + + mock_params_converter.assert_called_once_with(snmp_params) + mock_get_engine.assert_called_once_with( + mock_params_converter.return_value, logger + ) + mock_context.assert_called_once_with( + snmp_params.context_engine_id, snmp_params.context_name + ) + mock_context_manager.assert_called_once_with( + snmp_engine=mock_get_engine.return_value, + v3_context_engine_id=mock_context.return_value.context_engine_id, + v3_context_name=mock_context.return_value.context_name, + logger=logger, + get_bulk_flag=True, + is_snmp_read_only=mock_params_converter.return_value.is_read_only, + ) + + @patch("cloudshell.snmp.cloudshell_snmp.SnmpSecurity") + @patch("cloudshell.snmp.cloudshell_snmp.SnmpTransport") + @patch("cloudshell.snmp.cloudshell_snmp.engine") + @patch("cloudshell.snmp.cloudshell_snmp.config") + def test_get_snmp_engine( + self, mock_config, mock_engine, mock_transport, mock_security + ): + logger = Mock() + pysnmp_params = Mock() + snmp = Snmp() + + snmp._get_snmp_engine(logger=logger, pysnmp_params=pysnmp_params) + + mock_engine.SnmpEngine.assert_called_once_with() + mock_config.addTargetParams.assert_called_once_with( + mock_engine.SnmpEngine.return_value, + "pms", + pysnmp_params.user, + pysnmp_params.security, + pysnmp_params.version, + ) + mock_transport.assert_called_once_with( + snmp_parameters=pysnmp_params.snmp_parameters, logger=logger + ) + mock_transport.return_value.add_udp_endpoint.assert_called_once_with( + mock_engine.SnmpEngine.return_value, + snmp._snmp_timeout, + snmp._snmp_retry_count, + ) + mock_security.assert_called_once_with( + py_snmp_params=pysnmp_params, logger=logger + ) + mock_security.return_value.add_security.assert_called_once_with( + mock_engine.SnmpEngine.return_value + ) diff --git a/tox.ini b/tox.ini index 5c02ee1..b860be7 100644 --- a/tox.ini +++ b/tox.ini @@ -29,7 +29,9 @@ commands = pre-commit run --all-files --show-diff-on-failure [testenv:build] skip_install = true -commands = python setup.py sdist --format zip +commands = + python setup.py sdist --format zip + python setup.py bdist_wheel --universal [isort] line_length = 88 diff --git a/version.txt b/version.txt index 0c89fc9..cc868b6 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -4.0.0 \ No newline at end of file +4.0.1 \ No newline at end of file