From d8019ba12d1d707faa276ad3812664a272a7df3b Mon Sep 17 00:00:00 2001 From: Billy Olsen Date: Wed, 31 Aug 2022 20:43:02 -0700 Subject: [PATCH] Move to Operator Framework Move the charm to an Operator Framework Charm Signed-off-by: Billy Olsen --- .gitignore | 1 + .jujuignore | 3 + README.md | 19 +- build-requirements.txt | 7 - charmcraft.yaml | 46 ++- lib/charms/openstack/v0/horizon_plugin.py | 291 ++++++++++++++++++ metadata.yaml | 27 +- requirements.txt | 25 +- src/README.md | 18 -- src/charm.py | 60 ++++ src/layer.yaml | 17 - src/lib/__init__.py | 0 src/lib/charm/__init__.py | 0 src/lib/charm/openstack/__init__.py | 0 src/lib/charm/openstack/ironic_dashboard.py | 42 --- src/metadata.yaml | 15 - src/reactive/__init__.py | 0 src/reactive/ironic_dashboard_handlers.py | 41 --- src/test-requirements.txt | 12 - src/tox.ini | 61 ---- src/wheelhouse.txt | 3 - test-requirements.txt | 53 +--- .../tests => tests}/bundles/focal-ussuri.yaml | 0 .../bundles/focal-victoria.yaml | 0 .../bundles/focal-wallaby.yaml | 0 {src/tests => tests}/bundles/focal-xena.yaml | 0 {src/tests => tests}/bundles/focal-yoga.yaml | 0 {src/tests => tests}/bundles/jammy-yoga.yaml | 0 {src/tests => tests}/bundles/jammy-zed.yaml | 0 {src/tests => tests}/bundles/kinetic-zed.yaml | 0 {src/tests => tests}/tests.yaml | 0 tox.ini | 50 +-- unit_tests/__init__.py | 10 +- unit_tests/test_charm.py | 39 +++ unit_tests/test_ironic_dashboard_handlers.py | 63 ---- 35 files changed, 475 insertions(+), 428 deletions(-) create mode 100644 .jujuignore mode change 120000 => 100644 README.md delete mode 100644 build-requirements.txt create mode 100644 lib/charms/openstack/v0/horizon_plugin.py mode change 120000 => 100644 metadata.yaml delete mode 100644 src/README.md create mode 100755 src/charm.py delete mode 100644 src/layer.yaml delete mode 100644 src/lib/__init__.py delete mode 100644 src/lib/charm/__init__.py delete mode 100644 src/lib/charm/openstack/__init__.py delete mode 100644 src/lib/charm/openstack/ironic_dashboard.py delete mode 100644 src/metadata.yaml delete mode 100644 src/reactive/__init__.py delete mode 100644 src/reactive/ironic_dashboard_handlers.py delete mode 100644 src/test-requirements.txt delete mode 100644 src/tox.ini delete mode 100644 src/wheelhouse.txt rename {src/tests => tests}/bundles/focal-ussuri.yaml (100%) rename {src/tests => tests}/bundles/focal-victoria.yaml (100%) rename {src/tests => tests}/bundles/focal-wallaby.yaml (100%) rename {src/tests => tests}/bundles/focal-xena.yaml (100%) rename {src/tests => tests}/bundles/focal-yoga.yaml (100%) rename {src/tests => tests}/bundles/jammy-yoga.yaml (100%) rename {src/tests => tests}/bundles/jammy-zed.yaml (100%) rename {src/tests => tests}/bundles/kinetic-zed.yaml (100%) rename {src/tests => tests}/tests.yaml (100%) create mode 100644 unit_tests/test_charm.py delete mode 100644 unit_tests/test_ironic_dashboard_handlers.py diff --git a/.gitignore b/.gitignore index 31de773..62ecc68 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ xenial/ .stestr __pycache__ func-results.json +.idea *.charm diff --git a/.jujuignore b/.jujuignore new file mode 100644 index 0000000..6ccd559 --- /dev/null +++ b/.jujuignore @@ -0,0 +1,3 @@ +/venv +*.py[cod] +*.charm diff --git a/README.md b/README.md deleted file mode 120000 index 351df1d..0000000 --- a/README.md +++ /dev/null @@ -1 +0,0 @@ -src/README.md \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5657be7 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Overview + +This charm provides the Ironic Dashboard plugin for use with the OpenStack Dashboard. + +# Usage + +Example minimal deploy: + + juju deploy openstack-dashboard --channel yoga/stable + juju deploy ironic-dashboard + juju add-relation \ + openstack-dashboard:dashboard-plugin ironic-dashboard:dashboard + +# Bugs + +Please report bugs on [Launchpad](https://bugs.launchpad.net/charm-ironic-dashboard/+filebug). + +For general questions please refer to the OpenStack [Charm Guide](https://docs.openstack.org/charm-guide/latest/). diff --git a/build-requirements.txt b/build-requirements.txt deleted file mode 100644 index b6d2452..0000000 --- a/build-requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -# NOTES(lourot): -# * We don't install charmcraft via pip anymore because it anyway spins up a -# container and scp the system's charmcraft snap inside it. So the charmcraft -# snap is necessary on the system anyway. -# * `tox -e build` successfully validated with charmcraft 1.2.1 - -cffi==1.14.6; python_version < '3.6' # cffi 1.15.0 drops support for py35. diff --git a/charmcraft.yaml b/charmcraft.yaml index cec0acb..94ee915 100644 --- a/charmcraft.yaml +++ b/charmcraft.yaml @@ -1,30 +1,22 @@ -type: charm - -parts: - charm: - build-packages: - - tox - - git - - python3-dev - override-build: | - apt-get install ca-certificates -y - tox -e build-reactive - override-stage: | - echo "Copying charm to staging area: $CRAFT_STAGE" - NAME=$(ls $CRAFT_PART_BUILD/build/builds) - cp -r $CRAFT_PART_BUILD/build/builds/$NAME/* $CRAFT_STAGE/ - override-prime: | - # For some reason, the normal priming chokes on the fact that there's a - # hooks directory. - cp -r $CRAFT_STAGE/* . - +type: "charm" bases: - build-on: - - name: ubuntu - channel: "22.04" - architectures: - - amd64 + - name: "ubuntu" + channel: "20.04" + architectures: + - amd64 run-on: - - name: ubuntu - channel: "22.04" - architectures: [amd64, s390x, ppc64el, arm64] + - name: "ubuntu" + channel: "20.04" + architectures: [amd64, s390x, ppc64el, arm64] + +#bases: +# - build-on: +# - name: ubuntu +# channel: "22.04" +# architectures: +# - amd64 +# run-on: +# - name: ubuntu +# channel: "22.04" +# architectures: [amd64, s390x, ppc64el, arm64] diff --git a/lib/charms/openstack/v0/horizon_plugin.py b/lib/charms/openstack/v0/horizon_plugin.py new file mode 100644 index 0000000..0ebeb92 --- /dev/null +++ b/lib/charms/openstack/v0/horizon_plugin.py @@ -0,0 +1,291 @@ +# Copyright 2022 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. + +"""# Horizon Plugin library. + +This library facilitates interactions with the 'classic' OpenStack Dashboard +Machine Charm. This library is used to provide configuration and dependent +package information for this plugin. + +Horizon Plugin charms should be created as subordinate charms. These +subordinate charms will run inside the same machine or LXD container that the +horizon service is installed in. The plugin charm should avoid installing any +debian packages directly, as there are complications with upgrade ordering and +compatibility when the OpenStack Dashboard charm is upgraded via an +openstack-upgrade action. + +Instead, to ensure consistency, the plugin charm will ask the parent to +install the dependent packages and provide any settings that need to be set +in the Horizon service's local_settings.py configuration file. The data +provided by the subordinate charm is passed verbatim to the local_settings.py. + +# Usage + +Charms seeking to provide a horizon plugin should import the `HorizonPlugin` +class from this library. The simplest scenario is to just instantiate the +`HorizonPlugin` object and provide the set of debian packages which should be +installed, as below: + + from charms.openstack.v0.horizon_plugin import HorizonPlugin + + class AwesomeHorizonPluginFoo(CharmBase): + def __init__(self, *args): + super().__init__(*args) + ... + self.plugin = HorizonPlugin(self, install_packages=['plugin-foo']) + ... + +The first argument ('self') to `HorizonPlugin` is always a reference to the +horizon plugin charm. + +The `HorizonPlugin` class provides the information from the OpenStack Horizon +Dashboard charm as well, in the event that they are needed within the plugin. +The plugin charm is provided information regarding the installed OpenStack +release, the bin directory path and the location that the dashboard is +installed to. The plugin charm can access these values as attributes on the +`HorizonPlugin` object. + + ... + logger.debug(f'Current release is {self.plugin.release} + ... + +Note, these values are only returned when the relation between plugin charm +and the principal charm exists and the joined events have occurred in the +principal charm. +""" +import json +import logging + +from typing import List, Optional + +import ops.charm +from ops.framework import ( + EventBase, + EventSource, + Object, + ObjectEvents, +) +from ops.model import Relation + +# The unique Charmhub library identifier, never change it +LIBID = "TBD" + +# Increment this major API version when introducing breaking changes +LIBAPI = 0 + +# Increment this PATCH version before using `charmcraft publish-lib` or reset +# to 0 if you are raising the major API version +LIBPATCH = 1 + +logger = logging.getLogger(__name__) + + +class HorizonConnectedEvent(EventBase): + """Raised when the OpenStack Horizon Dashboard is connected.""" + pass + + +class HorizonAvailableEvent(EventBase): + """Raised when the OpenStack Horizon Dashboard is available.""" + + +class HorizonGoneAwayEvent(EventBase): + """Raised when the OpenStack Horizon Dashboard is no longer available.""" + pass + + +class HorizonEvents(ObjectEvents): + """ObjectEvents class used to provide the `on` for Dashboard events.""" + + connected = EventSource(HorizonConnectedEvent) + available = EventSource(HorizonAvailableEvent) + goneaway = EventSource(HorizonGoneAwayEvent) + + +class HorizonPlugin(Object): + """The client-side API for the OpenStack dashboard interface. + + The DashboardRequires object provides the client-side API for the dashboard + interface. Charms that require access to the OpenStack dashboard in order + to provide a plugin extension can use this library to indicate that new + plugins are available. + """ + + on = HorizonEvents() + + def __init__(self, charm: ops.charm.CharmBase, + relation_name: str = 'dashboard', + install_packages: List[str] = None, + conflicting_packages: Optional[List[str]] = None, + local_settings: str = "", + priority: str = None,): + super().__init__(charm, relation_name) + self.charm = charm + self.relation_name = relation_name + self.install_packages = install_packages + self.conflicting_packages = conflicting_packages + self.local_settings = local_settings + self.priority = priority + + self.framework.observe( + self.charm.on[relation_name].relation_joined, + self._on_dashboard_relation_joined, + ) + self.framework.observe( + self.charm.on[relation_name].relation_changed, + self._on_dashboard_relation_changed, + ) + self.framework.observe( + self.charm.on[relation_name].relation_broken, + self._on_dashboard_relation_broken, + ) + + def _on_dashboard_relation_joined(self, event: EventBase): + """Handles relation joined events for the dashboard relation. + + When the dashboard relation joins, the local plugin info will be + published to the openstack dashboard charm. + + :param event: the event + :type event: EventBase + :return: None + """ + logging.debug(f'{self.relation_name} relation has joined') + self.publish_plugin_info( + self.local_settings, + self.priority, + self.install_packages, + self.conflicting_packages, + event.relation, + ) + self.on.connected.emit() + + def _on_dashboard_relation_changed(self, event: EventBase): + """Handles relation changed events for the dashboard relation. + + When the dashboard relation changes, it may be indicating that there's + a new OpenStack release or that some other element has changed. + """ + logging.debug(f'{self.relation_name} relation has changed') + self.on.available.emit() + + def _on_dashboard_relation_broken(self, event: EventBase): + """Handles relation departed events for the dashboard relation. + + When the dashboard relation is departed it means the unit/application + is being removed. + + :param event: the event + :type event: EventBase + return: None + """ + logging.debug(f'{self.relation_name} relation has departed') + self.on.goneaway.emit() + + @property + def _relation(self) -> Relation: + """The shared-db relation.""" + return self.framework.model.get_relation(self.relation_name) + + def publish_plugin_info( + self, + local_settings: str, + priority: str, + install_packages: Optional[List[str]] = None, + conflicting_packages: Optional[List[str]] = None, + relation: Optional[Relation] = None, + ) -> None: + """Publish information regarding the plugin to the provider. + + Publishes the dashboard plugin information to the principle charm. + The principle charm does the installation and maintenance of the + packages so that it can also manage the upgrades of these packages. + + :param local_settings: a string to be placed into the + local_settings.py. Note it is placed verbatim into the + local_settings.py configuration file + :type local_settings: str + :param priority: Value used by the principal charm to order the + configuration blobs when multiple plugin subordinates are present + :param install_packages: a list of packages that should be installed + for this plugin + :type install_packages: Optional[List[str]] + :param conflicting_packages: a list of packages that conflict with + this plugin + :type conflicting_packages: Optional[List[str]] + :param relation: the relation to set the data on + :type relation: Relation + :return: None + :rtype: None + """ + rel = relation or self._relation + # If this is called when there isn't a relation, then just return + if not rel: + return + + data = rel.data[self.charm.unit] + data['local-settings'] = local_settings + if priority: + data['priority'] = priority + if install_packages: + data['install-packages'] = json.dumps(install_packages) + if conflicting_packages: + data['conflicting-packages'] = json.dumps(conflicting_packages) + + rel.data[self.charm.unit].update(data) + + def _get_remote_data(self, key: str) -> Optional[str]: + """Returns the value for the given key from the relation data. + + As long as *one* of the related units can provide the requested data, + then that data is returned. + + :param key: the key of the value to retrieve from the relation. + :type key: str + :return: the value of the relation data + :rtype: Optional[str] + """ + relation = self._relation + if not relation: + return None + + for unit in relation.units: + value = relation.data[unit].get(key) + if value: + return value + + return None + + @property + def openstack_dir(self) -> Optional[str]: + """Retrieves the openstack_dir property from the principal charm + + :return: openstack_dir property from principal charm + :rtype: Optional[str] + """ + return self._get_remote_data('openstack_dir') + + @property + def bin_path(self) -> Optional[str]: + """Retrives the bin_path property from the principal charm + + :return: bin_path property from the principal charm""" + return self._get_remote_data('bin_path') + + @property + def release(self) -> Optional[str]: + """Retrives the release property from the principal charm + + :return: release property from the principal charm""" + return self._get_remote_data('release') diff --git a/metadata.yaml b/metadata.yaml deleted file mode 120000 index 0768683..0000000 --- a/metadata.yaml +++ /dev/null @@ -1 +0,0 @@ -src/metadata.yaml \ No newline at end of file diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 0000000..5b858c5 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,26 @@ +# Copyright 2022 Canonical Ltd. +# See LICENSE file for licensing details + +name: ironic-dashboard +display-name: Ironic Dashboard +summary: Horizon plugin for Ironic Bare Metal services +maintainers: + - OpenStack Charmers +description: | + The Ironic Dashboard provides the plugin necessary to + manage bare metal instances from the Horizon Dashboard. +tags: +- openstack + +series: +- focal +- jammy + +# This charm is a machine charm which is intended to be inside +# the same container/machine as the openstack-dashboard. +subordinate: true + +requires: + dashboard: + interface: dashboard-plugin + scope: container diff --git a/requirements.txt b/requirements.txt index 7755b95..8a71f48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,28 +3,5 @@ # choices of *requirements.txt files for OpenStack Charms: # https://github.com/openstack-charmers/release-tools # -# NOTE(lourot): This might look like a duplication of test-requirements.txt but -# some tox targets use only test-requirements.txt whereas charm-build uses only -# requirements.txt -setuptools<50.0.0 # https://github.com/pypa/setuptools/commit/04e3df22df840c6bb244e9b27bc56750c44b7c85 -# NOTE: newer versions of cryptography require a Rust compiler to build, -# see -# * https://github.com/openstack-charmers/zaza/issues/421 -# * https://mail.python.org/pipermail/cryptography-dev/2021-January/001003.html -# -cryptography<3.4 - -# Build requirements -cffi==1.14.6; python_version < '3.6' # cffi 1.15.0 drops support for py35. -git+https://github.com/juju/charm-tools#egg=charm-tools - -simplejson - -# Newer versions use keywords that didn't exist in python 3.5 yet (e.g. -# "ModuleNotFoundError") -# NOTE(lourot): This might look like a duplication of test-requirements.txt but -# some tox targets use only test-requirements.txt whereas charm-build uses only -# requirements.txt -importlib-metadata<3.0.0; python_version < '3.6' -importlib-resources<3.0.0; python_version < '3.6' +ops diff --git a/src/README.md b/src/README.md deleted file mode 100644 index 7b690d5..0000000 --- a/src/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Overview - -This subordinate charm provides the Ironic Dashboard plugin for use with the OpenStack Dashboard. - -# Usage - -Example minimal deploy: - - juju deploy openstack-dashboard --channel yoga/stable - juju deploy ironic-dashboard - juju add-relation \ - openstack-dashboard:dashboard-plugin ironic-dashboard:dashboard - -# Bugs - -Please report bugs on [Launchpad](https://bugs.launchpad.net/charm-ironic-dashboard/+filebug). - -For general questions please refer to the OpenStack [Charm Guide](https://docs.openstack.org/charm-guide/latest/). diff --git a/src/charm.py b/src/charm.py new file mode 100755 index 0000000..4c08823 --- /dev/null +++ b/src/charm.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +# +# Copyright 2022 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. + +"""An Operator Charm for deploying the Ironic Dashboard in Charmed OpenStack. +""" + +import logging + +from ops.charm import CharmBase +from ops.main import main +from ops.model import ActiveStatus + +from charms.openstack.v0.horizon_plugin import ( + HorizonPlugin, + HorizonAvailableEvent, +) + + +logger = logging.getLogger(__name__) + + +class IronicDashboardCharm(CharmBase): + """ + + """ + + def __init__(self, *args): + super().__init__(*args) + self.dashboard = HorizonPlugin( + self, install_packages=['python3-ironic-ui'], + ) + self.framework.observe( + self.dashboard.on.available, self._dashboard_available + ) + + def _dashboard_available(self, event: HorizonAvailableEvent): + """Invoked when the dashboard is now available. + + :param event: the DashboardAvailableEvent to indicate the dashboard + is now available. + :type event: HorizonAvailableEvent + """ + self.model.unit.status = ActiveStatus('ready') + + +if __name__ == "__main__": + main(IronicDashboardCharm) diff --git a/src/layer.yaml b/src/layer.yaml deleted file mode 100644 index 3e81a27..0000000 --- a/src/layer.yaml +++ /dev/null @@ -1,17 +0,0 @@ -includes: - - layer:openstack - - interface:dashboard-plugin -options: - basic: - use_venv: True - include_system_packages: False -repo: https://github.com/openstack/charm-ironic-dashboard -config: - deletes: - - debug - - verbose - - use-internal-endpoints - - use-syslog - - ssl_ca - - ssl_cert - - ssl_key diff --git a/src/lib/__init__.py b/src/lib/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/charm/__init__.py b/src/lib/charm/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/charm/openstack/__init__.py b/src/lib/charm/openstack/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/lib/charm/openstack/ironic_dashboard.py b/src/lib/charm/openstack/ironic_dashboard.py deleted file mode 100644 index 05a9230..0000000 --- a/src/lib/charm/openstack/ironic_dashboard.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2021 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 glob -import os - -import charms_openstack.adapters -import charms_openstack.charm - - -class IronicDashboardCharm(charms_openstack.charm.OpenStackCharm): - release = 'ussuri' - name = 'ironic-dashboard' - packages = ['python3-ironic-ui'] - python_version = 3 - adapters_class = charms_openstack.adapters.OpenStackRelationAdapters - required_relations = ['dashboard'] - - def enable_ui_plugin(self): - source = '/etc/openstack-dashboard/enabled' - destination = \ - '/usr/lib/python3/dist-packages/openstack_dashboard/local/enabled' - plugin_files = glob.glob('{}/*_container_infra_*.py'.format(source)) - for plugin_file in plugin_files: - dest_file = os.path.join( - destination, os.path.basename(plugin_file)) - try: - os.symlink(plugin_file, dest_file) - except FileExistsError: - # plugin file is already enabled - continue diff --git a/src/metadata.yaml b/src/metadata.yaml deleted file mode 100644 index 3bd8f6f..0000000 --- a/src/metadata.yaml +++ /dev/null @@ -1,15 +0,0 @@ -name: ironic-dashboard -summary: Openstack Ironic Dashboard -maintainer: OpenStack Charmers -description: | - This is the dashboard for the OpenStack Bare Metal service, Ironic. -tags: -- openstack -series: -- focal -- jammy -subordinate: true -requires: - dashboard: - interface: dashboard-plugin - scope: container diff --git a/src/reactive/__init__.py b/src/reactive/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/reactive/ironic_dashboard_handlers.py b/src/reactive/ironic_dashboard_handlers.py deleted file mode 100644 index cd64362..0000000 --- a/src/reactive/ironic_dashboard_handlers.py +++ /dev/null @@ -1,41 +0,0 @@ -# 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 charms.reactive as reactive - -import charms_openstack.bus -import charms_openstack.charm as charm - -charms_openstack.bus.discover() - -# Use the charms.openstack defaults for common states and hooks -charm.use_defaults( - 'charm.installed', - 'config.changed', - 'update-status', - 'upgrade-charm') - - -@reactive.when('dashboard.available') -def dashboard_available(): - """Relation to OpenStack Dashboard principal charm complete. - """ - with charm.provide_charm_instance() as ironic_dashboard_charm: - dashboard_relation = reactive.endpoint_from_flag('dashboard.available') - dashboard_relation.publish_plugin_info( - "", None, - conflicting_packages=ironic_dashboard_charm.purge_packages, - install_packages=ironic_dashboard_charm.packages) - ironic_dashboard_charm.enable_ui_plugin() - ironic_dashboard_charm.assess_status() diff --git a/src/test-requirements.txt b/src/test-requirements.txt deleted file mode 100644 index 9c7afb7..0000000 --- a/src/test-requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# This file is managed centrally by release-tools and should not be modified -# within individual charm repos. See the 'global' dir contents for available -# choices of *requirements.txt files for OpenStack Charms: -# https://github.com/openstack-charmers/release-tools -# - -# Need tox to be available from tox... inception yes, but its a workaround for now -tox - -# Functional Test Requirements (let Zaza's dependencies solve all dependencies here!) -git+https://github.com/openstack-charmers/zaza.git#egg=zaza -git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack diff --git a/src/tox.ini b/src/tox.ini deleted file mode 100644 index b40d295..0000000 --- a/src/tox.ini +++ /dev/null @@ -1,61 +0,0 @@ -# Source charm (with zaza): ./src/tox.ini -# This file is managed centrally by release-tools and should not be modified -# within individual charm repos. See the 'global' dir contents for available -# choices of tox.ini for OpenStack Charms: -# https://github.com/openstack-charmers/release-tools - -[tox] -envlist = pep8 -skipsdist = True -# NOTE: Avoid build/test env pollution by not enabling sitepackages. -sitepackages = False -# NOTE: Avoid false positives by not skipping missing interpreters. -skip_missing_interpreters = False -# NOTES: -# * We avoid the new dependency resolver by pinning pip < 20.3, see -# https://github.com/pypa/pip/issues/9187 -# * Pinning dependencies requires tox >= 3.2.0, see -# https://tox.readthedocs.io/en/latest/config.html#conf-requires -# * It is also necessary to pin virtualenv as a newer virtualenv would still -# lead to fetching the latest pip in the func* tox targets, see -# https://stackoverflow.com/a/38133283 -requires = pip < 20.3 - virtualenv < 20.0 -# NOTE: https://wiki.canonical.com/engineering/OpenStack/InstallLatestToxOnOsci -minversion = 3.18.0 - -[testenv] -setenv = VIRTUAL_ENV={envdir} - PYTHONHASHSEED=0 -allowlist_externals = juju -passenv = HOME TERM CS_* OS_* TEST_* -deps = -r{toxinidir}/test-requirements.txt -install_command = - pip install {opts} {packages} - -[testenv:pep8] -basepython = python3 -commands = charm-proof - -[testenv:func-noop] -basepython = python3 -commands = - functest-run-suite --help - -[testenv:func] -basepython = python3 -commands = - functest-run-suite --keep-model - -[testenv:func-smoke] -basepython = python3 -commands = - functest-run-suite --keep-model --smoke - -[testenv:func-target] -basepython = python3 -commands = - functest-run-suite --keep-model --bundle {posargs} - -[testenv:venv] -commands = {posargs} diff --git a/src/wheelhouse.txt b/src/wheelhouse.txt deleted file mode 100644 index a548375..0000000 --- a/src/wheelhouse.txt +++ /dev/null @@ -1,3 +0,0 @@ -git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack - -git+https://github.com/juju/charm-helpers.git#egg=charmhelpers diff --git a/test-requirements.txt b/test-requirements.txt index a11a7d0..126abb6 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,52 +3,13 @@ # choices of *requirements.txt files for OpenStack Charms: # https://github.com/openstack-charmers/release-tools # -pyparsing<3.0.0 # aodhclient is pinned in zaza and needs pyparsing < 3.0.0, but cffi also needs it, so pin here. -cffi==1.14.6; python_version < '3.6' # cffi 1.15.0 drops support for py35. -setuptools<50.0.0 # https://github.com/pypa/setuptools/commit/04e3df22df840c6bb244e9b27bc56750c44b7c85 -stestr>=2.2.0 +-r requirements.txt -# Dependency of stestr. Workaround for -# https://github.com/mtreinish/stestr/issues/145 -cliff<3.0.0 +coverage +flake8 +stestr -# Dependencies of stestr. Newer versions use keywords that didn't exist in -# python 3.5 yet (e.g. "ModuleNotFoundError") -importlib-metadata<3.0.0; python_version < '3.6' -importlib-resources<3.0.0; python_version < '3.6' - -# Some Zuul nodes sometimes pull newer versions of these dependencies which -# dropped support for python 3.5: -osprofiler<2.7.0;python_version<'3.6' -stevedore<1.31.0;python_version<'3.6' -debtcollector<1.22.0;python_version<'3.6' -oslo.utils<=3.41.0;python_version<'3.6' - -requests>=2.18.4 -charms.reactive - -# Newer mock seems to have some syntax which is newer than python3.5 (e.g. -# f'{something}' -mock>=1.2,<4.0.0; python_version < '3.6' -mock>=1.2; python_version >= '3.6' - -nose>=1.3.7 -coverage>=3.6 -git+https://github.com/openstack/charms.openstack.git#egg=charms.openstack -# -# Revisit for removal / mock improvement: -# -# NOTE(lourot): newer versions of cryptography require a Rust compiler to build, -# see -# * https://github.com/openstack-charmers/zaza/issues/421 -# * https://mail.python.org/pipermail/cryptography-dev/2021-January/001003.html -# -netifaces # vault -psycopg2-binary # vault -tenacity # vault -pbr==5.6.0 # vault -cryptography<3.4 # vault, keystone-saml-mellon -lxml # keystone-saml-mellon -hvac # vault, barbican-vault -psutil # cinder-lvm +# Functional Test Requirements (let Zaza's dependencies solve all dependencies here!) +git+https://github.com/openstack-charmers/zaza.git#egg=zaza +git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack diff --git a/src/tests/bundles/focal-ussuri.yaml b/tests/bundles/focal-ussuri.yaml similarity index 100% rename from src/tests/bundles/focal-ussuri.yaml rename to tests/bundles/focal-ussuri.yaml diff --git a/src/tests/bundles/focal-victoria.yaml b/tests/bundles/focal-victoria.yaml similarity index 100% rename from src/tests/bundles/focal-victoria.yaml rename to tests/bundles/focal-victoria.yaml diff --git a/src/tests/bundles/focal-wallaby.yaml b/tests/bundles/focal-wallaby.yaml similarity index 100% rename from src/tests/bundles/focal-wallaby.yaml rename to tests/bundles/focal-wallaby.yaml diff --git a/src/tests/bundles/focal-xena.yaml b/tests/bundles/focal-xena.yaml similarity index 100% rename from src/tests/bundles/focal-xena.yaml rename to tests/bundles/focal-xena.yaml diff --git a/src/tests/bundles/focal-yoga.yaml b/tests/bundles/focal-yoga.yaml similarity index 100% rename from src/tests/bundles/focal-yoga.yaml rename to tests/bundles/focal-yoga.yaml diff --git a/src/tests/bundles/jammy-yoga.yaml b/tests/bundles/jammy-yoga.yaml similarity index 100% rename from src/tests/bundles/jammy-yoga.yaml rename to tests/bundles/jammy-yoga.yaml diff --git a/src/tests/bundles/jammy-zed.yaml b/tests/bundles/jammy-zed.yaml similarity index 100% rename from src/tests/bundles/jammy-zed.yaml rename to tests/bundles/jammy-zed.yaml diff --git a/src/tests/bundles/kinetic-zed.yaml b/tests/bundles/kinetic-zed.yaml similarity index 100% rename from src/tests/bundles/kinetic-zed.yaml rename to tests/bundles/kinetic-zed.yaml diff --git a/src/tests/tests.yaml b/tests/tests.yaml similarity index 100% rename from src/tests/tests.yaml rename to tests/tests.yaml diff --git a/tox.ini b/tox.ini index e22fe48..b725802 100644 --- a/tox.ini +++ b/tox.ini @@ -6,35 +6,17 @@ [tox] skipsdist = True -envlist = pep8,py3 +envlist = pep8, py3 # NOTE: Avoid build/test env pollution by not enabling sitepackages. sitepackages = False # NOTE: Avoid false positives by not skipping missing interpreters. skip_missing_interpreters = False -# NOTES: -# * We avoid the new dependency resolver by pinning pip < 20.3, see -# https://github.com/pypa/pip/issues/9187 -# * Pinning dependencies requires tox >= 3.2.0, see -# https://tox.readthedocs.io/en/latest/config.html#conf-requires -# * It is also necessary to pin virtualenv as a newer virtualenv would still -# lead to fetching the latest pip in the func* tox targets, see -# https://stackoverflow.com/a/38133283 -requires = - pip < 20.3 - virtualenv < 20.0 - setuptools<50.0.0 - -# NOTE: https://wiki.canonical.com/engineering/OpenStack/InstallLatestToxOnOsci -minversion = 3.18.0 [testenv] setenv = VIRTUAL_ENV={envdir} PYTHONHASHSEED=0 TERM=linux - LAYER_PATH={toxinidir}/layers - INTERFACE_PATH={toxinidir}/interfaces - JUJU_REPOSITORY={toxinidir}/build -passenv = http_proxy https_proxy INTERFACE_PATH LAYER_PATH JUJU_REPOSITORY +passenv = http_proxy https_proxy install_command = {toxinidir}/pip.sh install {opts} {packages} allowlist_externals = @@ -47,22 +29,12 @@ deps = [testenv:build] basepython = python3 -deps = -r{toxinidir}/build-requirements.txt +deps = -r{toxinidir}/test-requirements.txt commands = charmcraft clean - charmcraft -v build + charmcraft -v pack {toxinidir}/rename.sh -[testenv:build-reactive] -basepython = python3 -commands = - charm-build --log-level DEBUG --use-lock-file-branches -o {toxinidir}/build/builds src {posargs} - -[testenv:add-build-lock-file] -basepython = python3 -commands = - charm-build --log-level DEBUG --write-lock-file -o {toxinidir}/build/builds src {posargs} - [testenv:py3] basepython = python3 deps = -r{toxinidir}/test-requirements.txt @@ -91,27 +63,21 @@ commands = stestr run --slowest {posargs} [testenv:pep8] basepython = python3 deps = flake8==3.9.2 - charm-tools==2.8.3 commands = flake8 {posargs} src unit_tests [testenv:func-target] -# Hack to get functional tests working in the charmcraft -# world. We should fix this. basepython = python3 passenv = HOME TERM CS_* OS_* TEST_* -deps = -r{toxinidir}/src/test-requirements.txt -changedir = {toxinidir}/src +deps = -r{toxinidir}/test-requirements.txt commands = bash -c "if [ ! -f ../*.charm ]; then echo 'Charm does not exist. Run tox -e build'; exit 1; fi" - tox --version - tox -e func-target {posargs} + functest-run-suite --keep-model --bundle {posargs} [testenv:cover] # Technique based heavily upon # https://github.com/openstack/nova/blob/master/tox.ini basepython = python3 -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt +deps = -r{toxinidir}/test-requirements.txt setenv = {[testenv]setenv} PYTHON=coverage run @@ -131,8 +97,8 @@ source = . omit = .tox/* - */charmhelpers/* unit_tests/* + lib/* [testenv:venv] basepython = python3 diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py index b23b405..be3982c 100644 --- a/unit_tests/__init__.py +++ b/unit_tests/__init__.py @@ -12,11 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -import sys - -sys.path.append('src') -sys.path.append('src/lib') - -# Mock out charmhelpers so that we can test without it. -import charms_openstack.test_mocks # noqa -charms_openstack.test_mocks.mock_charmhelpers() +import ops.testing +ops.testing.SIMULATE_CAN_CONNECT = False diff --git a/unit_tests/test_charm.py b/unit_tests/test_charm.py new file mode 100644 index 0000000..ae31eb9 --- /dev/null +++ b/unit_tests/test_charm.py @@ -0,0 +1,39 @@ +# Copyright 2021 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 unittest +import sys + +sys.path.append('lib') # noqa +sys.path.append('src') # noqa + +from charm import IronicDashboardCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = Harness(IronicDashboardCharm) + self.harness.begin() + self.addCleanup(self.harness.cleanup) + + def test_add_relation(self): + rel_id = self.harness.add_relation('dashboard', 'openstack-dashboard') + self.harness.add_relation_unit(rel_id, "openstack-dashboard/0") + self.harness.update_relation_data(rel_id, 'openstack-dashboard', { + 'release': 'testing' + }) + assert isinstance(self.harness.charm.model.unit.status, + ActiveStatus) diff --git a/unit_tests/test_ironic_dashboard_handlers.py b/unit_tests/test_ironic_dashboard_handlers.py deleted file mode 100644 index 92e73e4..0000000 --- a/unit_tests/test_ironic_dashboard_handlers.py +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright 2021 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 mock - -import reactive.ironic_dashboard_handlers as handlers - -import charms_openstack.test_utils as test_utils - - -class TestRegisteredHooks(test_utils.TestRegisteredHooks): - - def test_hooks(self): - defaults = [ - 'charm.installed', - 'config.changed', - 'update-status', - 'upgrade-charm'] - hook_set = { - 'when': { - 'dashboard_available': ( - 'dashboard.available',), - }, - } - # test that the hooks were registered via the - # reactive.barbican_handlers - self.registered_hooks_test_helper(handlers, hook_set, defaults) - - -class TestIronicDashboardHandlers(test_utils.PatchHelper): - - def setUp(self): - super().setUp() - self.ironic_dashboard_charm = mock.MagicMock() - self.patch_object(handlers.charm, 'provide_charm_instance', - new=mock.MagicMock()) - self.provide_charm_instance().__enter__.return_value = \ - self.ironic_dashboard_charm - self.provide_charm_instance().__exit__.return_value = None - self.patch('charms.reactive.endpoint_from_flag') - - def test_dashboard_available(self): - mock_flag = mock.Mock() - self.endpoint_from_flag.return_value = mock_flag - self.ironic_dashboard_charm.purge_packages = ['n1'] - self.ironic_dashboard_charm.packages = ['p1', 'p2'] - handlers.dashboard_available() - self.ironic_dashboard_charm.enable_ui_plugin.assert_called_once_with() - self.ironic_dashboard_charm.assess_status.assert_called_once_with() - mock_flag.publish_plugin_info.assert_called_once_with( - "", None, conflicting_packages=['n1'], - install_packages=['p1', 'p2'])