Move to Operator Framework

Move the charm to an Operator Framework Charm

Signed-off-by: Billy Olsen <billy.olsen@gmail.com>
This commit is contained in:
Billy Olsen 2022-08-31 20:43:02 -07:00
parent 9f9a9dd6cb
commit d8019ba12d
35 changed files with 475 additions and 428 deletions

1
.gitignore vendored
View File

@ -13,4 +13,5 @@ xenial/
.stestr .stestr
__pycache__ __pycache__
func-results.json func-results.json
.idea
*.charm *.charm

3
.jujuignore Normal file
View File

@ -0,0 +1,3 @@
/venv
*.py[cod]
*.charm

View File

@ -1 +0,0 @@
src/README.md

18
README.md Normal file
View File

@ -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/).

View File

@ -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.

View File

@ -1,30 +1,22 @@
type: charm 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/* .
bases: bases:
- build-on: - build-on:
- name: ubuntu - name: "ubuntu"
channel: "22.04" channel: "20.04"
architectures: architectures:
- amd64 - amd64
run-on: run-on:
- name: ubuntu - name: "ubuntu"
channel: "22.04" channel: "20.04"
architectures: [amd64, s390x, ppc64el, arm64] 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]

View File

@ -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')

View File

@ -1 +0,0 @@
src/metadata.yaml

26
metadata.yaml Normal file
View File

@ -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 <openstack-charmers@lists.ubuntu.com>
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

View File

@ -3,28 +3,5 @@
# choices of *requirements.txt files for OpenStack Charms: # choices of *requirements.txt files for OpenStack Charms:
# https://github.com/openstack-charmers/release-tools # 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, ops
# 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'

View File

@ -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/).

60
src/charm.py Executable file
View File

@ -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)

View File

@ -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

View File

View File

@ -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

View File

@ -1,15 +0,0 @@
name: ironic-dashboard
summary: Openstack Ironic Dashboard
maintainer: OpenStack Charmers <openstack-charmers@lists.ubuntu.com>
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

View File

@ -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()

View File

@ -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

View File

@ -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}

View File

@ -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

View File

@ -3,52 +3,13 @@
# choices of *requirements.txt files for OpenStack Charms: # choices of *requirements.txt files for OpenStack Charms:
# https://github.com/openstack-charmers/release-tools # 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 coverage
# https://github.com/mtreinish/stestr/issues/145 flake8
cliff<3.0.0 stestr
# Dependencies of stestr. Newer versions use keywords that didn't exist in # Functional Test Requirements (let Zaza's dependencies solve all dependencies here!)
# python 3.5 yet (e.g. "ModuleNotFoundError") git+https://github.com/openstack-charmers/zaza.git#egg=zaza
importlib-metadata<3.0.0; python_version < '3.6' git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack
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

50
tox.ini
View File

@ -6,35 +6,17 @@
[tox] [tox]
skipsdist = True skipsdist = True
envlist = pep8,py3 envlist = pep8, py3
# NOTE: Avoid build/test env pollution by not enabling sitepackages. # NOTE: Avoid build/test env pollution by not enabling sitepackages.
sitepackages = False sitepackages = False
# NOTE: Avoid false positives by not skipping missing interpreters. # NOTE: Avoid false positives by not skipping missing interpreters.
skip_missing_interpreters = False 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] [testenv]
setenv = VIRTUAL_ENV={envdir} setenv = VIRTUAL_ENV={envdir}
PYTHONHASHSEED=0 PYTHONHASHSEED=0
TERM=linux TERM=linux
LAYER_PATH={toxinidir}/layers passenv = http_proxy https_proxy
INTERFACE_PATH={toxinidir}/interfaces
JUJU_REPOSITORY={toxinidir}/build
passenv = http_proxy https_proxy INTERFACE_PATH LAYER_PATH JUJU_REPOSITORY
install_command = install_command =
{toxinidir}/pip.sh install {opts} {packages} {toxinidir}/pip.sh install {opts} {packages}
allowlist_externals = allowlist_externals =
@ -47,22 +29,12 @@ deps =
[testenv:build] [testenv:build]
basepython = python3 basepython = python3
deps = -r{toxinidir}/build-requirements.txt deps = -r{toxinidir}/test-requirements.txt
commands = commands =
charmcraft clean charmcraft clean
charmcraft -v build charmcraft -v pack
{toxinidir}/rename.sh {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] [testenv:py3]
basepython = python3 basepython = python3
deps = -r{toxinidir}/test-requirements.txt deps = -r{toxinidir}/test-requirements.txt
@ -91,27 +63,21 @@ commands = stestr run --slowest {posargs}
[testenv:pep8] [testenv:pep8]
basepython = python3 basepython = python3
deps = flake8==3.9.2 deps = flake8==3.9.2
charm-tools==2.8.3
commands = flake8 {posargs} src unit_tests commands = flake8 {posargs} src unit_tests
[testenv:func-target] [testenv:func-target]
# Hack to get functional tests working in the charmcraft
# world. We should fix this.
basepython = python3 basepython = python3
passenv = HOME TERM CS_* OS_* TEST_* passenv = HOME TERM CS_* OS_* TEST_*
deps = -r{toxinidir}/src/test-requirements.txt deps = -r{toxinidir}/test-requirements.txt
changedir = {toxinidir}/src
commands = commands =
bash -c "if [ ! -f ../*.charm ]; then echo 'Charm does not exist. Run tox -e build'; exit 1; fi" bash -c "if [ ! -f ../*.charm ]; then echo 'Charm does not exist. Run tox -e build'; exit 1; fi"
tox --version functest-run-suite --keep-model --bundle {posargs}
tox -e func-target {posargs}
[testenv:cover] [testenv:cover]
# Technique based heavily upon # Technique based heavily upon
# https://github.com/openstack/nova/blob/master/tox.ini # https://github.com/openstack/nova/blob/master/tox.ini
basepython = python3 basepython = python3
deps = -r{toxinidir}/requirements.txt deps = -r{toxinidir}/test-requirements.txt
-r{toxinidir}/test-requirements.txt
setenv = setenv =
{[testenv]setenv} {[testenv]setenv}
PYTHON=coverage run PYTHON=coverage run
@ -131,8 +97,8 @@ source =
. .
omit = omit =
.tox/* .tox/*
*/charmhelpers/*
unit_tests/* unit_tests/*
lib/*
[testenv:venv] [testenv:venv]
basepython = python3 basepython = python3

View File

@ -12,11 +12,5 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import sys import ops.testing
ops.testing.SIMULATE_CAN_CONNECT = False
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()

39
unit_tests/test_charm.py Normal file
View File

@ -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)

View File

@ -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'])