commit fe6d428d56f8c4b830e1ddb33fc1c4dd7b75a381 Author: James Page Date: Wed Mar 11 11:21:46 2020 +0000 Initial refactoring of charm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e1d2dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +build/ +.local/ +.testrepository/ +.tox/ +func-results.json +test-charm/ +**/__pycache__ +.stestr 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/README.md b/README.md new file mode 100644 index 0000000..fb56694 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# Overview + +TrilioVault Horizon Plugin is a plugin of TrilioVault which is installed +on the Openstack and provides TrilioVault UI components. + +# Usage + +TrilioVault Horizon Plugin is a sub-ordinate charm of openstack-dashboard +and relies on services from openstack-dashboard. + +Steps to deploy the charm: + +juju deploy trilio-horizon-plugin + +juju deploy openstack-dashboard + +juju add-relation trilio-horizon-plugin openstack-dashboard + +# Configuration + +python-version: "Openstack base python version(2 or 3)" + +NOTE - Default value is set to "3". Please ensure to update this based on python version since installing + python3 packages on python2 based setup might have unexpected impact. + +TrilioVault Packages are downloaded from the repository added in below config parameter. Please change this only if you wish to download +TrilioVault Packages from a different source. + +triliovault-pkg-source: Repository address of triliovault packages + +# Contact Information + +Trilio Support diff --git a/copyright b/copyright new file mode 100644 index 0000000..2d0b0ec --- /dev/null +++ b/copyright @@ -0,0 +1,16 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 + +Files: * +Copyright: 2018, Trilio +License: Apache-2.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. diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..cb6c9d4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +# Requirements to build the charm +charm-tools +simplejson +flake8 \ No newline at end of file diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..fb56694 --- /dev/null +++ b/src/README.md @@ -0,0 +1,33 @@ +# Overview + +TrilioVault Horizon Plugin is a plugin of TrilioVault which is installed +on the Openstack and provides TrilioVault UI components. + +# Usage + +TrilioVault Horizon Plugin is a sub-ordinate charm of openstack-dashboard +and relies on services from openstack-dashboard. + +Steps to deploy the charm: + +juju deploy trilio-horizon-plugin + +juju deploy openstack-dashboard + +juju add-relation trilio-horizon-plugin openstack-dashboard + +# Configuration + +python-version: "Openstack base python version(2 or 3)" + +NOTE - Default value is set to "3". Please ensure to update this based on python version since installing + python3 packages on python2 based setup might have unexpected impact. + +TrilioVault Packages are downloaded from the repository added in below config parameter. Please change this only if you wish to download +TrilioVault Packages from a different source. + +triliovault-pkg-source: Repository address of triliovault packages + +# Contact Information + +Trilio Support diff --git a/src/config.yaml b/src/config.yaml new file mode 100644 index 0000000..8e314e1 --- /dev/null +++ b/src/config.yaml @@ -0,0 +1,9 @@ +options: + python-version: + type: int + default: 3 + description: Openstack base python version(2 or 3) + triliovault-pkg-source: + type: string + default: "deb [trusted=yes] https://apt.fury.io/triliodata-3-4/ /" + description: Repository address of triliovault packages diff --git a/src/copyright b/src/copyright new file mode 100644 index 0000000..2d0b0ec --- /dev/null +++ b/src/copyright @@ -0,0 +1,16 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 + +Files: * +Copyright: 2018, Trilio +License: Apache-2.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. diff --git a/src/files/trilio/panels/tvault_admin_panel.py b/src/files/trilio/panels/tvault_admin_panel.py new file mode 100644 index 0000000..ea456e9 --- /dev/null +++ b/src/files/trilio/panels/tvault_admin_panel.py @@ -0,0 +1,10 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = "workloads_admin" +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = "admin" +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = "backups-admin" +# Python panel class of the PANEL to be added. +ADD_PANEL = "dashboards.workloads_admin.panel.Workloads_admin" +ADD_INSTALLED_APPS = ["dashboards"] +DISABLED = False diff --git a/src/files/trilio/panels/tvault_admin_panel_group.py b/src/files/trilio/panels/tvault_admin_panel_group.py new file mode 100644 index 0000000..817e416 --- /dev/null +++ b/src/files/trilio/panels/tvault_admin_panel_group.py @@ -0,0 +1,8 @@ +from django.utils.translation import ugettext_lazy as _ + +# The slug of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = "backups-admin" +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = _("Backups-Admin") +# The slug of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = "admin" diff --git a/src/files/trilio/panels/tvault_panel.py b/src/files/trilio/panels/tvault_panel.py new file mode 100644 index 0000000..80f271c --- /dev/null +++ b/src/files/trilio/panels/tvault_panel.py @@ -0,0 +1,9 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = "workloads" +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = "project" +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = "backups" +# Python panel class of the PANEL to be added. +ADD_PANEL = "dashboards.workloads.panel.Workloads" +DISABLED = False diff --git a/src/files/trilio/panels/tvault_panel_group.py b/src/files/trilio/panels/tvault_panel_group.py new file mode 100644 index 0000000..4e73989 --- /dev/null +++ b/src/files/trilio/panels/tvault_panel_group.py @@ -0,0 +1,8 @@ +from django.utils.translation import ugettext_lazy as _ + +# The slug of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = "backups" +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = _("Backups") +# The slug of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = "project" diff --git a/src/files/trilio/panels/tvault_settings_panel.py b/src/files/trilio/panels/tvault_settings_panel.py new file mode 100644 index 0000000..02c32e4 --- /dev/null +++ b/src/files/trilio/panels/tvault_settings_panel.py @@ -0,0 +1,9 @@ +# The slug of the panel to be added to HORIZON_CONFIG. Required. +PANEL = "settings" +# The slug of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = "project" +# The slug of the panel group the PANEL is associated with. +PANEL_GROUP = "backups" +# Python panel class of the PANEL to be added. +ADD_PANEL = "dashboards.settings.panel.Settings" +DISABLED = False diff --git a/src/files/trilio/sync_static.py b/src/files/trilio/sync_static.py new file mode 100644 index 0000000..f3aa29e --- /dev/null +++ b/src/files/trilio/sync_static.py @@ -0,0 +1,17 @@ +import sys +import settings +import subprocess + +ls = settings.INSTALLED_APPS +data = "" + +for app in ls: + if app != "dashboards": + data += "-i " + str(app) + " " + +cmd = ( + "{} /usr/share/openstack-dashboard/manage.py collectstatic" + " --noinput {}".format(sys.executable, data) +) + +subprocess.call(cmd, shell=True) diff --git a/src/files/trilio/templates/workload_admin/index.html b/src/files/trilio/templates/workload_admin/index.html new file mode 100644 index 0000000..89104ce --- /dev/null +++ b/src/files/trilio/templates/workload_admin/index.html @@ -0,0 +1,1799 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %} + {% trans "TrilioVault Nodes and Workload Types" %} +{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("TrilioVault Dashboard") %} + + + + + + + + + + + +{% endblock page_header %} + +{% block main %} +
+ +
+
+
+ loading... +
+
+
+ +
+ +
+ + + +
+ +
+
+
+

Workloads

+ +
+
+
+
+ loading... +
+
+ {% include "admin/workloads_admin/workloads/workloads.html" %} +
+
+ +
+
+
+

TrilioVault Nodes

+ +
+
+
+
+ loading... +
+
+
+
+ +
+
+
+
+

Contego Services

+ +
+
+
+
+ loading... +
+
+
+
+
+ +
+
+
+

Storage

+
+ +
+
+
+
+ loading... +
+
+
+ + +
+ +
+
+

Audit Log

+
+
+   + +    + + +
+ +
+
+
+
+ loading... +
+
+ +
+ +
+
+
+

Settings

+
+
+ + + + + + + + + +
Click on the following button to change email settings: + + Modify Email Settings + +
Cloud Wide TrilioVault Job Schduler: + + Disable/Enable Job Scheduler + +
+ +
+ +
+
+
+

License

+ +
+ +
+ +
+
+
+
+ loading... +
+
+
+
+ +
+
+
+

Configuration Backup

+ + +
+
+ +
+
+ loading... +
+ +
+
+
+
+ + +
+
+
+

Policy

+ + +
+
+ +
+
+ loading... +
+
+
+
+
+ + +
+
+
+

Usage

+ +
+
+
+ loading... +
+
+
+
+ +
+

+
+
    + +
+
+ +
+
+ + + + +
+ +
+
+ + + +{% endblock %} diff --git a/src/files/trilio/templatetags/tvault_filter.py b/src/files/trilio/templatetags/tvault_filter.py new file mode 100644 index 0000000..d937874 --- /dev/null +++ b/src/files/trilio/templatetags/tvault_filter.py @@ -0,0 +1,124 @@ +from django import template +from openstack_dashboard import api +from openstack_dashboard import policy +from datetime import datetime +from django.template.defaultfilters import stringfilter +import pytz + +register = template.Library() + + +@register.filter(name="getusername") +def get_user_name(user_id, request): + user_name = user_id + if policy.check((("identity", "identity:get_user"),), request): + try: + user = api.keystone.user_get(request, user_id) + if user: + user_name = user.username + except Exception: + pass + return user_name + + +@register.filter(name="getprojectname") +def get_project_name(project_id, request): + project_name = project_id + try: + project_info = api.keystone.tenant_get(request, project_id, admin=True) + if project_info: + project_name = project_info.name + except Exception: + pass + return project_name + + +def get_time_zone(request): + tz = "UTC" + try: + tz = request._get_cookies()["django_timezone"] + except Exception: + try: + tz = request.COOKIES["django_timezone"] + except Exception: + pass + + return tz + + +def get_local_time(record_time, input_format, output_format, tz): + """ + Convert and return the date and time - from GMT to local time + """ + try: + if not record_time or record_time is None or record_time == "": + return "" + else: + if not input_format or input_format is None or input_format == "": + input_format = "%Y-%m-%dT%H:%M:%S.%f" + if ( + not output_format or + output_format is None or + output_format == "" + ): + output_format = "%m/%d/%Y %I:%M:%S %p" + + local_time = datetime.strptime(record_time, input_format) + local_tz = pytz.timezone(tz) + from_zone = pytz.timezone("UTC") + local_time = local_time.replace(tzinfo=from_zone) + local_time = local_time.astimezone(local_tz) + local_time = datetime.strftime(local_time, output_format) + return local_time + except Exception: + pass + return record_time + + +@register.filter(name="gettime") +def get_time_for_audit(time_stamp, request): + display_time = time_stamp + try: + time_zone_of_ui = get_time_zone(request) + display_time = get_local_time( + time_stamp, + "%I:%M:%S.%f %p - %m/%d/%Y", + "%I:%M:%S %p - %m/%d/%Y", + time_zone_of_ui, + ) + except Exception: + pass + return display_time + + +@register.filter(name="getsnapshotquantifier") +def display_time_quantifier(seconds): + intervals = ( + ("weeks", 604800), # 60 * 60 * 24 * 7 + ("days", 86400), # 60 * 60 * 24 + ("hours", 3600), # 60 * 60 + ("minutes", 60), + ("seconds", 1), + ) + + result = [] + granularity = 4 + for name, count in intervals: + value = seconds // count + if value: + seconds -= value * count + if value == 1: + name = name.rstrip("s") + result.append("{} {}".format(value, name)) + else: + # Add a blank if we're in the middle of other values + if len(result) > 0: + result.append(None) + return ", ".join([x for x in result[:granularity] if x is not None]) + + +@register.filter(name="custom_split") +@stringfilter +def custom_split(value, key): + key = int(key) + return value.split("_")[key] diff --git a/src/icon.svg b/src/icon.svg new file mode 100644 index 0000000..1ef800d --- /dev/null +++ b/src/icon.svg @@ -0,0 +1,23 @@ + + + + + + + + + + diff --git a/src/layer.yaml b/src/layer.yaml new file mode 100644 index 0000000..d9f679c --- /dev/null +++ b/src/layer.yaml @@ -0,0 +1,7 @@ +includes: + - 'layer:openstack' + - 'interface:dashboard-plugin' +options: + basic: + use_venv: True + include_system_packages: True diff --git a/src/lib/charm/openstack/trilio_horizon_plugin.py b/src/lib/charm/openstack/trilio_horizon_plugin.py new file mode 100644 index 0000000..0ab14dc --- /dev/null +++ b/src/lib/charm/openstack/trilio_horizon_plugin.py @@ -0,0 +1,97 @@ +# Copyright 2020 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 collections +import glob +import os +import shutil +import subprocess + +import charmhelpers.core.hookenv as hookenv +import charmhelpers.fetch as fetch + +import charms_openstack.charm + + +HORIZON_PATH = "/usr/share/openstack-dashboard" +MANAGE_PY = os.path.join(HORIZON_PATH, "manage.py") + + +class TrilioHorizonPluginCharm(charms_openstack.charm.OpenStackCharm): + + service_name = name = "trilio-horizon-plugin" + + # First release supported + release = "stein" + + required_relations = [] + + package_codenames = { + "tvault-horizon-plugin": collections.OrderedDict([("3", "stein")]), + "python3-horizon-plugin": collections.OrderedDict([("3", "stein")]), + } + + def configure_source(self): + with open( + "/etc/apt/sources.list.d/" "trilio-gemfury-sources.list", "w" + ) as tsources: + tsources.write(hookenv.config("triliovault-pkg-source")) + fetch.apt_update(fatal=True) + + @property + def packages(self): + if hookenv.config("python-version") == 2: + return ["python-workloadmgrclient", "tvault-horizon-plugin"] + return ["python3-workloadmgrclient", "python3-tvault-horizon-plugin"] + + # TODO: drop once packaging is updated + def install(self): + self.configure_source() + super().install() + self.copy_files() + self.collectstatic_and_compress() + + # TODO: drop once packaging is updated + def upgrade_charm(self): + super().upgrade_charm() + self.copy_files() + self.collectstatic_and_compress() + + # TODO: drop when package does this + def copy_files(self): + for panel in glob.glob("src/files/trilio/panels/*"): + shutil.copy( + panel, + os.path.join( + HORIZON_PATH, "openstack_dashboard/local/enabled" + ), + ) + for templatetag in glob.glob("src/files/trilio/templatetags/*"): + shutil.copy( + templatetag, + os.path.join(HORIZON_PATH, "openstack_dashboard/templatetags"), + ) + for index_html in glob.glob( + "/usr/lib/python*/**/workloads_admin/index.html", recursive=True + ): + shutil.copy( + "src/files/trilio/templates/workload_admin/index.html", + index_html, + ) + + # TODO: drop when package does this + def collectstatic_and_compress(self): + python = "/usr/bin/python{}".format(hookenv.config("python-version")) + subprocess.check_call([python, MANAGE_PY, "collectstatic"]) + subprocess.check_call([python, MANAGE_PY, "compress", "--force"]) diff --git a/src/metadata.yaml b/src/metadata.yaml new file mode 100644 index 0000000..1e5b468 --- /dev/null +++ b/src/metadata.yaml @@ -0,0 +1,19 @@ +--- +name: trilio-horizon-plugin +summary: TrilioVault Horizon Plugin +maintainer: Trilio Support +description: | + Openstack Dashboard - Horizon plugin for TrilioVault +tags: + - openstack + - storage + - backup + - TVMv3.4 +subordinate: true +series: + - xenial + - bionic +requires: + dashboard-plugin: + interface: dashboard-plugin + scope: container diff --git a/src/reactive/trilio_horizon_plugin_handlers.py b/src/reactive/trilio_horizon_plugin_handlers.py new file mode 100644 index 0000000..80eb530 --- /dev/null +++ b/src/reactive/trilio_horizon_plugin_handlers.py @@ -0,0 +1,19 @@ +# 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_openstack.charm as charm + +# This charm's library contains all of the handler code associated with +# trilio_horizon_plugin +import charm.openstack.trilio_horizon_plugin as trilio_horizon_plugin # noqa + +charm.use_defaults("charm.installed", "config.changed", "update-status") diff --git a/src/test-requirements.txt b/src/test-requirements.txt new file mode 100644 index 0000000..9a0bed8 --- /dev/null +++ b/src/test-requirements.txt @@ -0,0 +1,7 @@ +# Unit test requirements +flake8>=2.2.4,<=2.4.1 +os-testr>=0.4.1 +charms.reactive +mock>=1.2 +coverage>=3.6 +git+https://github.com/openstack/charms.openstack#egg=charms.openstack diff --git a/src/tox.ini b/src/tox.ini new file mode 100644 index 0000000..ef04f42 --- /dev/null +++ b/src/tox.ini @@ -0,0 +1,21 @@ +# tox (https://tox.readthedocs.io/) is a tool for running tests +# in multiple virtualenvs. This configuration file will run the +# test suite on all supported python versions. To use it, "pip install tox" +# and then run "tox" from this directory. + +[tox] +skipsdist = True +envlist = pep8 + +[testenv] +setenv = VIRTUAL_ENV={envdir} + PYTHONHASHSEED=0 + TERM=linux + INTERFACE_PATH={toxinidir}/interfaces + LAYER_PATH={toxinidir}/layers + JUJU_REPOSITORY={toxinidir}/build + +[testenv:pep8] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = flake8 {posargs} reactive diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..422d55b --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,7 @@ +# Unit test requirements +flake8>=2.2.4 +os-testr>=0.4.1 +charms.reactive +mock>=1.2 +coverage>=3.6 +git+https://github.com/openstack/charms.openstack#egg=charms.openstack diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..5b41c1d --- /dev/null +++ b/tox.ini @@ -0,0 +1,92 @@ +# Source charm: ./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] +skipsdist = True +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 + +[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 +install_command = + pip install {opts} {packages} +deps = + -r{toxinidir}/requirements.txt + +[testenv:build] +basepython = python3 +commands = + charm-build --log-level DEBUG -o {toxinidir}/build src {posargs} + +[testenv:py3] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = stestr run --slowest {posargs} + +[testenv:py35] +basepython = python3.5 +deps = -r{toxinidir}/test-requirements.txt +commands = stestr run --slowest {posargs} + +[testenv:py36] +basepython = python3.6 +deps = -r{toxinidir}/test-requirements.txt +commands = stestr run --slowest {posargs} + +[testenv:py37] +basepython = python3.7 +deps = -r{toxinidir}/test-requirements.txt +commands = stestr run --slowest {posargs} + +[testenv:pep8] +basepython = python3 +deps = -r{toxinidir}/test-requirements.txt +commands = flake8 {posargs} src unit_tests + +[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 +setenv = + {[testenv]setenv} + PYTHON=coverage run +commands = + coverage erase + stestr run --slowest {posargs} + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml + coverage report + +[coverage:run] +branch = True +concurrency = multiprocessing +parallel = True +source = + . +omit = + .tox/* + */charmhelpers/* + unit_tests/* + +[testenv:venv] +basepython = python3 +commands = {posargs} + +[flake8] +# E402 ignore necessary for path append before sys module import in actions +ignore = E402,W504 diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 0000000..64c49f5 --- /dev/null +++ b/unit_tests/__init__.py @@ -0,0 +1,9 @@ +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() diff --git a/unit_tests/test_horizon_plugin.py b/unit_tests/test_horizon_plugin.py new file mode 100644 index 0000000..414bc61 --- /dev/null +++ b/unit_tests/test_horizon_plugin.py @@ -0,0 +1,120 @@ +# 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.trilio_horizon_plugin_handlers as handlers + +import charms_openstack.test_utils as test_utils + +_when_args = {} +_when_not_args = {} + + +def mock_hook_factory(d): + def mock_hook(*args, **kwargs): + def inner(f): + # remember what we were passed. Note that we can't actually + # determine the class we're attached to, as the decorator only gets + # the function. + try: + d[f.__name__].append(dict(args=args, kwargs=kwargs)) + except KeyError: + d[f.__name__] = [dict(args=args, kwargs=kwargs)] + return f + + return inner + + return mock_hook + + +class TestDmapiHandlers(test_utils.PatchHelper): + @classmethod + def setUpClass(cls): + cls._patched_when = mock.patch( + "charms.reactive.when", mock_hook_factory(_when_args) + ) + cls._patched_when_started = cls._patched_when.start() + cls._patched_when_not = mock.patch( + "charms.reactive.when_not", mock_hook_factory(_when_not_args) + ) + cls._patched_when_not_started = cls._patched_when_not.start() + # force requires to rerun the mock_hook decorator: + # try except is Python2/Python3 compatibility as Python3 has moved + # reload to importlib. + try: + reload(handlers) + except NameError: + import importlib + + importlib.reload(handlers) + + @classmethod + def tearDownClass(cls): + cls._patched_when.stop() + cls._patched_when_started = None + cls._patched_when = None + cls._patched_when_not.stop() + cls._patched_when_not_started = None + cls._patched_when_not = None + # and fix any breakage we did to the module + try: + reload(handlers) + except NameError: + import importlib + + importlib.reload(handlers) + + 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(self, obj, attr, return_value=None, side_effect=None): + mocked = mock.patch.object(obj, attr) + self._patches[attr] = mocked + started = mocked.start() + started.return_value = return_value + started.side_effect = side_effect + self._patches_start[attr] = started + setattr(self, attr, started) + + def test_registered_hooks(self): + # test that the hooks actually registered the relation expressions that + # are meaningful for this interface: this is to handle regressions. + # The keys are the function names that the hook attaches to. + when_patterns = { + } + when_not_patterns = { + } + # check the when hooks are attached to the expected functions + for t, p in [ + (_when_args, when_patterns), + (_when_not_args, when_not_patterns), + ]: + for f, args in t.items(): + # check that function is in patterns + self.assertTrue(f in p.keys(), "{} not found".format(f)) + # check that the lists are equal + lists = [] + for a in args: + lists += a["args"][:] + self.assertEqual( + sorted(lists), + sorted(p[f]), + "{}: incorrect state registration".format(f), + )