From 7aff370e799dcea2960a195471b79abdd5a2a043 Mon Sep 17 00:00:00 2001 From: Liam Young Date: Thu, 26 Jul 2018 12:45:29 +0000 Subject: [PATCH] Add docstrings, unit tests etc --- .gitignore | 5 + .testr.conf | 8 + interface.yaml | 13 +- provides.py | 28 -- requires.py | 117 +++++- test-requirements.txt | 6 + tox.ini | 33 ++ unit_tests/__init__.py | 0 .../__pycache__/__init__.cpython-36.pyc | Bin 0 -> 161 bytes .../__pycache__/test_requires.cpython-36.pyc | Bin 0 -> 10165 bytes unit_tests/test_requires.py | 342 ++++++++++++++++++ 11 files changed, 502 insertions(+), 50 deletions(-) create mode 100644 .gitignore create mode 100644 .testr.conf delete mode 100644 provides.py create mode 100644 test-requirements.txt create mode 100644 tox.ini create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/__pycache__/__init__.cpython-36.pyc create mode 100644 unit_tests/__pycache__/test_requires.cpython-36.pyc create mode 100644 unit_tests/test_requires.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9faca0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.tox +.testrepository +.unit-state.db +.stestr/ +__pycache__/ diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..801646b --- /dev/null +++ b/.testr.conf @@ -0,0 +1,8 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ + ${PYTHON:-python} -m subunit.run discover -t ./ ./unit_tests $LISTOPT $IDOPTION + +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/interface.yaml b/interface.yaml index 288a47e..30ae922 100644 --- a/interface.yaml +++ b/interface.yaml @@ -1,4 +1,13 @@ name: nova-compute -summary: Controller <-> compute interface +summary: Interface for joining compute resource to a controller. version: 1 -maintainer: "" +maintainer: OpenStack Charmers +ignore: + - 'unit_tests' + - 'Makefile' + - '.testr.conf' + - 'test-requirements.txt' + - 'tox.ini' + - '.gitignore' + - '.gitreview' + - '.unit-state.db' diff --git a/provides.py b/provides.py deleted file mode 100644 index 152368a..0000000 --- a/provides.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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. - -from charms.reactive import set_flag, clear_flag -from charms.reactive import Endpoint -from charms.reactive import when_any, when_not, when - - -class NovaComputeProvides(Endpoint): - - @when_not('endpoint.{endpoint_name}.joined') - def broken(self): - clear_flag(self.expand_name('endpoint.{endpoint_name}.new-request')) - clear_flag(self.expand_name('{endpoint_name}.connected')) - - @when('endpoint.{endpoint_name}.joined') - def joined(self): - set_flag(self.expand_name('{endpoint_name}.connected')) - diff --git a/requires.py b/requires.py index 9667f0c..b922043 100644 --- a/requires.py +++ b/requires.py @@ -10,65 +10,142 @@ # See the License for the specific language governing permissions and # limitations under the License. -import socket from urllib.parse import urlparse import uuid -from charmhelpers.core import hookenv - from charms.reactive import set_flag, clear_flag from charms.reactive import Endpoint -from charms.reactive import when_any, when_not, when +from charms.reactive import when_not, when class NovaComputeRequires(Endpoint): @when('endpoint.{endpoint_name}.changed') def data_changed(self): - #set_flag(self.expand_name('{endpoint_name}.available')) - pass + """Set flag to indicate to charm relation data has changed.""" + set_flag(self.expand_name('{endpoint_name}.available')) @when_not('endpoint.{endpoint_name}.joined') def broken(self): + """Remove flag to indicate to charm relation has gone..""" clear_flag(self.expand_name('{endpoint_name}.available')) @when('endpoint.{endpoint_name}.joined') def joined(self): + """Set flag to indicate to charm relation has been joined.""" set_flag(self.expand_name('{endpoint_name}.connected')) + def set_network_data(self, neutron_url, neutron_plugin=None, + network_manager=None, enable_security_groups=True): + """Send compute nodes data relating to network setup. - def set_network_data(self, neutron_url, neutron_plugin=None, network_manager=None, enable_security_groups=True): + :param neutron_url: URL for network API service + :type neutron_url: str + :param neutron_plugin: Neutron plugin to use + :type neutron_plugin: str + :param network_manager: Network Manager + :type network_manager: str + :param enable_security_groups: Whether to enable security groups + :type enable_security_group: bool + """ o = urlparse(neutron_url) if enable_security_groups: security_groups = 'yes' else: security_groups = 'no' - for relation in self.relations: - relation.to_publish_raw['quantum_host'] = o.hostname - relation.to_publish_raw['quantum_plugin'] = neutron_plugin or 'ovs' - relation.to_publish_raw['quantum_port'] = o.port - relation.to_publish_raw['quantum_security_groups'] = security_groups - relation.to_publish_raw['quantum_url'] = neutron_url - relation.to_publish_raw['network_manager'] = network_manager or 'neutron' + for r in self.relations: + r.to_publish_raw['quantum_host'] = o.hostname + r.to_publish_raw['quantum_plugin'] = neutron_plugin or 'ovs' + r.to_publish_raw['quantum_port'] = o.port + r.to_publish_raw['quantum_security_groups'] = security_groups + r.to_publish_raw['quantum_url'] = neutron_url + r.to_publish_raw['network_manager'] = network_manager or 'neutron' def set_console_data(self, serial_console_base_url, enable_serial_console): - for relation in self.relations: - relation.to_publish_raw['serial_console_base_url'] = serial_console_base_url - relation.to_publish_raw['enable_serial_console'] = enable_serial_console + """Send compute nodes data relating to serial console access. - def trigger_remote_restart(self): - trigger = uuid.uuid1() + :param serial_console_base_url: URL for accessing the serial console. + :type serial_console_base_url: str + :param enable_serial_console: Whether to enable the serial console + :type enable_serial_console: bool + """ + for r in self.relations: + r.to_publish_raw[ + 'serial_console_base_url'] = serial_console_base_url + r.to_publish_raw['enable_serial_console'] = enable_serial_console + + def trigger_remote_restart(self, restart_key=None): + """Trigger a restart of services on the remote application. + + :param restart_key: Key to send to remote service, restarts are + triggered when the key changes. + :type restart_key: str + """ + if not restart_key: + restart_key = uuid.uuid1() for relation in self.relations: - relation.to_publish_raw['restart_trigger'] = trigger + relation.to_publish_raw['restart_trigger'] = restart_key def set_region(self, region): + """Send compute nodes region information. + + :param region: Region compute nodes will belong to. + :type region: str + """ for relation in self.relations: relation.to_publish_raw['region'] = region def set_volume_data(self, volume_service): + """Send compute nodes volume information. + + :param volume_service: Name of volume service to use, eg cinder + :type volume_service: str + """ for relation in self.relations: relation.to_publish_raw['volume_service'] = volume_service def set_ec2_data(self, ec2_host): + """Send compute nodes ec2 information. + + :param ec2_host: Name of ec2_host. + :type ec2_host: str + """ for relation in self.relations: relation.to_publish_raw['ec2_host'] = ec2_host + + def collect_ssh_keys(self, application_name=None): + """Query related units and collect ssh artifacts. + + :param application_name: Only return artifacts from units of this + applicationa. + :type application_name: str + :returns: {APP_NAME: {UNIT_NAME: {pupkey1:.., hostkey1:...}}} + :rtype: dict + """ + ssh_keys = {} + for rel in self.relations: + if application_name and application_name != rel.application_name: + continue + ssh_keys[rel.application_name] = {} + for unit in rel.units: + nova_ssh_pub_key = unit.received.get('nova_ssh_public_key') + ssh_pub_key = unit.received.get('ssh_public_key') + if nova_ssh_pub_key and ssh_pub_key: + ssh_keys[rel.application_name][unit.unit_name] = { + 'nova_ssh_pub_key': nova_ssh_pub_key, + 'hostname': unit.received.get('hostname'), + 'private-address': unit.received.get( + 'private-address'), + 'ssh_pub_key': ssh_pub_key} + return ssh_keys + + def send_ssh_keys(self, relation, settings): + """Publish the provided ssh settings on the given relation + + :param relation: Relation to publish settings on. + :type relation: charms.reactive.endpoints.Relation + :param settings: SSH settings to publish. + :type settings: dict + """ + for key, value in settings.items(): + relation.to_publish_raw[key] = value 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..6ffc056 --- /dev/null +++ b/tox.ini @@ -0,0 +1,33 @@ +[tox] +skipsdist = True +envlist = pep8,py35 +skip_missing_interpreters = True + +[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:py36] +basepython = python3.6 +deps = -r{toxinidir}/test-requirements.txt +commands = ostestr {posargs} + +[testenv:pep8] +basepython = python2.7 +deps = -r{toxinidir}/test-requirements.txt +commands = flake8 {posargs} . unit_tests + +[testenv:venv] +commands = {posargs} + +[flake8] +# E402 ignore necessary for path append before sys module import in actions +ignore = E402 diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/unit_tests/__pycache__/__init__.cpython-36.pyc b/unit_tests/__pycache__/__init__.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f54192da2f39d2dcc79d304aacd7f1084998fb6c GIT binary patch literal 161 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