From 3eca0062fd1036b9e7e6514de95d43b78c6918d2 Mon Sep 17 00:00:00 2001 From: Frode Nordahl Date: Wed, 9 May 2018 11:09:53 +0200 Subject: [PATCH] Fix generate_asn mask and add unit tests Change-Id: I18776f026ba288a150e783e4102006e690572d6f --- .stestr.conf | 3 ++ interface.yaml | 5 +++ provides.py | 5 +-- test-requirements.txt | 6 +++ tox.ini | 23 ++++++++++ unit_tests/__init__.py | 87 +++++++++++++++++++++++++++++++++++++ unit_tests/test_provides.py | 32 ++++++++++++++ unit_tests/utils.py | 82 ++++++++++++++++++++++++++++++++++ 8 files changed, 239 insertions(+), 4 deletions(-) create mode 100644 .stestr.conf create mode 100644 test-requirements.txt create mode 100644 tox.ini create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/test_provides.py create mode 100644 unit_tests/utils.py diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 0000000..5fcccac --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=./unit_tests +top_dir=./ diff --git a/interface.yaml b/interface.yaml index b6f5f54..2282e48 100644 --- a/interface.yaml +++ b/interface.yaml @@ -2,3 +2,8 @@ name: bgp summary: BGP interface version: 1 repo: https://github.com/openstack-charmers/charm-interface-bgp.git +ignore: + - 'unit_tests' + - '.stestr.conf' + - 'test-requirements.txt' + - 'tox.ini' diff --git a/provides.py b/provides.py index e837180..3d13816 100644 --- a/provides.py +++ b/provides.py @@ -45,16 +45,13 @@ class BGPEndpoint(reactive.Endpoint): 4 200 000 000 - 4 211 081 214 """ asn_base = 4211081215 - mask = netaddr.IPAddress('5.255.255.255') + mask = netaddr.IPAddress('4.255.255.255') unit_ip = netaddr.IPAddress( ch_core.hookenv.unit_get('private-address')) masked_ip = unit_ip & mask asn = asn_base + int(masked_ip) - # XXX: This assert should be removed from code and put in a unit test - assert asn <= 4294967294 - return asn def publish_info(self, asn=None, passive=False, bindings=None): diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..c706224 --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,6 @@ +# Lint and unit test requirements +flake8 +os-testr>=0.4.1 +charms.reactive +mock>=1.2 +coverage>=3.6 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..4c73ef6 --- /dev/null +++ b/tox.ini @@ -0,0 +1,23 @@ +[tox] +skipsdist = True +envlist = pep8,py35 + +[testenv] +setenv = VIRTUAL_ENV={envdir} + PYTHONHASHSEED=0 + TERM=linux +install_command = + pip install {opts} {packages} + +[testenv:py35] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = ostestr {posargs} + +[testenv:pep8] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = flake8 {posargs} . unit_tests + +[testenv:venv] +commands = {posargs} diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 0000000..bb774a0 --- /dev/null +++ b/unit_tests/__init__.py @@ -0,0 +1,87 @@ +# Copyright 2016 Canonical Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import sys +import mock + +# mock out some charmhelpers libraries as they have apt install side effects +apt_pkg = mock.MagicMock() +charmhelpers = mock.MagicMock() +sys.modules['apt_pkg'] = apt_pkg +sys.modules['charmhelpers'] = charmhelpers +sys.modules['charmhelpers.core'] = charmhelpers.core +sys.modules['charmhelpers.core.decorators'] = charmhelpers.core.decorators +sys.modules['charmhelpers.core.hookenv'] = charmhelpers.core.hookenv +sys.modules['charmhelpers.core.host'] = charmhelpers.core.host +sys.modules['charmhelpers.core.templating'] = charmhelpers.core.templating +sys.modules['charmhelpers.core.unitdata'] = charmhelpers.core.unitdata +sys.modules['charmhelpers.contrib'] = charmhelpers.contrib +sys.modules['charmhelpers.contrib.openstack'] = charmhelpers.contrib.openstack +sys.modules['charmhelpers.contrib.openstack.ha'] = ( + charmhelpers.contrib.openstack.ha) +sys.modules['charmhelpers.contrib.openstack.utils'] = ( + charmhelpers.contrib.openstack.utils) +sys.modules['charmhelpers.contrib.openstack.templating'] = ( + charmhelpers.contrib.openstack.templating) +sys.modules['charmhelpers.contrib.openstack.context'] = ( + charmhelpers.contrib.openstack.context) +sys.modules['charmhelpers.contrib.network'] = charmhelpers.contrib.network +sys.modules['charmhelpers.contrib.network.ip'] = ( + charmhelpers.contrib.network.ip) +sys.modules['charmhelpers.fetch'] = charmhelpers.fetch +sys.modules['charmhelpers.cli'] = charmhelpers.cli +sys.modules['charmhelpers.contrib.hahelpers'] = charmhelpers.contrib.hahelpers +sys.modules['charmhelpers.contrib.hahelpers.cluster'] = ( + charmhelpers.contrib.hahelpers.cluster) + +# mock in the openstack releases so that the tests can run +# Note that these don't need to be maintained UNLESS new functionality is for +# later OpenStack releases. +charmhelpers.contrib.openstack.utils.OPENSTACK_RELEASES = ( + 'diablo', + 'essex', + 'folsom', + 'grizzly', + 'havana', + 'icehouse', + 'juno', + 'kilo', + 'liberty', + 'mitaka', + 'newton', + 'ocata', + 'pike', +) + + +def _fake_retry(num_retries, base_delay=0, exc_type=Exception): + def _retry_on_exception_inner_1(f): + def _retry_on_exception_inner_2(*args, **kwargs): + return f(*args, **kwargs) + return _retry_on_exception_inner_2 + return _retry_on_exception_inner_1 + + +mock.patch( + 'charmhelpers.core.decorators.retry_on_exception', + _fake_retry).start() + + +def _fake_cached(f): + return f + + +mock.patch( + 'charmhelpers.core.hookenv.cached', + _fake_cached).start() diff --git a/unit_tests/test_provides.py b/unit_tests/test_provides.py new file mode 100644 index 0000000..2fd9e55 --- /dev/null +++ b/unit_tests/test_provides.py @@ -0,0 +1,32 @@ +# Copyright 2018 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import unit_tests.utils as ut_utils +import provides + + +class TestBGPProvides(ut_utils.BaseTestCase): + def test_generate_asn_min(self): + self.patch_object(provides, 'ch_core') + self.ch_core.hookenv.unit_get.return_value = '0.0.0.0' + endpoint = provides.BGPEndpoint('bgpserver') + asn = endpoint.generate_asn() + self.assertEqual(asn, 4211081215) + + def test_generate_asn_max(self): + self.patch_object(provides, 'ch_core') + self.ch_core.hookenv.unit_get.return_value = '255.255.255.255' + endpoint = provides.BGPEndpoint('bgpserver') + asn = endpoint.generate_asn() + self.assertEqual(asn, 4294967294) diff --git a/unit_tests/utils.py b/unit_tests/utils.py new file mode 100644 index 0000000..e1d0fb6 --- /dev/null +++ b/unit_tests/utils.py @@ -0,0 +1,82 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Note that the unit_tests/__init__.py also mocks out two charmhelpers imports +# that have side effects that try to apt install modules: +# sys.modules['charmhelpers.contrib.openstack.utils'] = mock.MagicMock() +# sys.modules['charmhelpers.contrib.network.ip'] = mock.MagicMock() + +import contextlib +import io +import mock +import unittest + + +@contextlib.contextmanager +def patch_open(): + '''Patch open() to allow mocking both open() itself and the file that is + yielded. + + Yields the mock for "open" and "file", respectively.''' + mock_open = mock.MagicMock(spec=open) + mock_file = mock.MagicMock(spec=io.FileIO) + + @contextlib.contextmanager + def stub_open(*args, **kwargs): + mock_open(*args, **kwargs) + yield mock_file + + with mock.patch('builtins.open', stub_open): + yield mock_open, mock_file + + +class BaseTestCase(unittest.TestCase): + + def setUp(self): + self._patches = {} + self._patches_start = {} + + def tearDown(self): + for k, v in self._patches.items(): + v.stop() + setattr(self, k, None) + self._patches = None + self._patches_start = None + + def patch_object(self, obj, attr, return_value=None, name=None, new=None, + **kwargs): + if name is None: + name = attr + if new is not None: + mocked = mock.patch.object(obj, attr, new=new, **kwargs) + else: + mocked = mock.patch.object(obj, attr, **kwargs) + self._patches[name] = mocked + started = mocked.start() + if new is None: + started.return_value = return_value + self._patches_start[name] = started + setattr(self, name, started) + + def patch(self, item, return_value=None, name=None, new=None, **kwargs): + if name is None: + raise RuntimeError("Must pass 'name' to .patch()") + if new is not None: + mocked = mock.patch(item, new=new, **kwargs) + else: + mocked = mock.patch(item, **kwargs) + self._patches[name] = mocked + started = mocked.start() + if new is None: + started.return_value = return_value + self._patches_start[name] = started + setattr(self, name, started)