Initial refactoring of charm
This commit is contained in:
commit
fe6d428d56
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
build/
|
||||
.local/
|
||||
.testrepository/
|
||||
.tox/
|
||||
func-results.json
|
||||
test-charm/
|
||||
**/__pycache__
|
||||
.stestr
|
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
test_path=./unit_tests
|
||||
top_dir=./
|
33
README.md
Normal file
33
README.md
Normal file
@ -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 <support@trilio.com>
|
16
copyright
Normal file
16
copyright
Normal file
@ -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.
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
||||
# Requirements to build the charm
|
||||
charm-tools
|
||||
simplejson
|
||||
flake8
|
33
src/README.md
Normal file
33
src/README.md
Normal file
@ -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 <support@trilio.com>
|
9
src/config.yaml
Normal file
9
src/config.yaml
Normal file
@ -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
|
16
src/copyright
Normal file
16
src/copyright
Normal file
@ -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.
|
10
src/files/trilio/panels/tvault_admin_panel.py
Normal file
10
src/files/trilio/panels/tvault_admin_panel.py
Normal file
@ -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
|
8
src/files/trilio/panels/tvault_admin_panel_group.py
Normal file
8
src/files/trilio/panels/tvault_admin_panel_group.py
Normal file
@ -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"
|
9
src/files/trilio/panels/tvault_panel.py
Normal file
9
src/files/trilio/panels/tvault_panel.py
Normal file
@ -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
|
8
src/files/trilio/panels/tvault_panel_group.py
Normal file
8
src/files/trilio/panels/tvault_panel_group.py
Normal file
@ -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"
|
9
src/files/trilio/panels/tvault_settings_panel.py
Normal file
9
src/files/trilio/panels/tvault_settings_panel.py
Normal file
@ -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
|
17
src/files/trilio/sync_static.py
Normal file
17
src/files/trilio/sync_static.py
Normal file
@ -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)
|
1799
src/files/trilio/templates/workload_admin/index.html
Normal file
1799
src/files/trilio/templates/workload_admin/index.html
Normal file
File diff suppressed because it is too large
Load Diff
124
src/files/trilio/templatetags/tvault_filter.py
Normal file
124
src/files/trilio/templatetags/tvault_filter.py
Normal file
@ -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]
|
23
src/icon.svg
Normal file
23
src/icon.svg
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1000 1000" style="enable-background:new 0 0 1000 1000;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#77BC1F;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
</style>
|
||||
<circle class="st0" cx="500" cy="500.9" r="492.5"/>
|
||||
<g>
|
||||
<path class="st1" d="M500.1,799.9c79.9,0,153.9-30.2,208.3-85c55-55.4,85-131.1,84.5-213.1c0-167.8-128.6-299.2-292.7-299.2h-33.6
|
||||
v23.1h33.6c151.2,0,269.5,121.3,269.5,276.2c0.6,75.8-27,145.7-77.7,196.8c-50.1,50.3-118.2,78-191.9,78
|
||||
c-153.7,0-269.5-118.3-269.5-275.1c0-68.6,23.7-133.8,66.9-183.7l-16.6-16.5c-47.4,54.4-73.5,125.4-73.5,200.3
|
||||
C207.4,671.7,333.2,799.9,500.1,799.9"/>
|
||||
<path class="st1" d="M500.1,846c92.2,0,177.5-34.8,240.4-98.1c63.4-63.9,98-151.3,97.3-246c0-192.8-147.7-344.4-336.1-345.4h-12.5
|
||||
V48.3L332.2,215l157.1,166.7v-110h10.8c125.8,0,224.4,101,224.4,230c0.4,63.2-22.5,121.5-64.7,163.9c-41.6,41.9-98.4,65-159.7,65
|
||||
c-128,0-224.4-98.4-224.4-229c0-56.2,19.2-109.8,54.2-151.3L313.3,334c-39.3,45.8-60.8,105.3-60.8,167.7
|
||||
c0,143.7,106.4,252.1,247.6,252.1c67.6,0,130.2-25.5,176.1-71.9c46.5-46.9,71.9-110.8,71.4-180.2c0-141.9-108.7-253-247.5-253
|
||||
h-33.9v74.9L364,215l102.2-108.5v73.1h33.9c176.5,0,314.7,141.6,314.7,322.4c0.7,88.6-31.6,170.1-90.7,229.7
|
||||
c-58.4,58.8-137.9,91.2-224,91.2c-179.4,0-314.7-138-314.7-321c0-80.9,28.2-157.6,79.5-216.3l-16.5-16.5
|
||||
c-55.6,63-86.2,145.6-86.2,232.8C162.2,698.1,307.5,846,500.1,846"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
7
src/layer.yaml
Normal file
7
src/layer.yaml
Normal file
@ -0,0 +1,7 @@
|
||||
includes:
|
||||
- 'layer:openstack'
|
||||
- 'interface:dashboard-plugin'
|
||||
options:
|
||||
basic:
|
||||
use_venv: True
|
||||
include_system_packages: True
|
97
src/lib/charm/openstack/trilio_horizon_plugin.py
Normal file
97
src/lib/charm/openstack/trilio_horizon_plugin.py
Normal file
@ -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"])
|
19
src/metadata.yaml
Normal file
19
src/metadata.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
name: trilio-horizon-plugin
|
||||
summary: TrilioVault Horizon Plugin
|
||||
maintainer: Trilio Support <support@trilio.io>
|
||||
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
|
19
src/reactive/trilio_horizon_plugin_handlers.py
Normal file
19
src/reactive/trilio_horizon_plugin_handlers.py
Normal file
@ -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")
|
7
src/test-requirements.txt
Normal file
7
src/test-requirements.txt
Normal file
@ -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
|
21
src/tox.ini
Normal file
21
src/tox.ini
Normal file
@ -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
|
7
test-requirements.txt
Normal file
7
test-requirements.txt
Normal file
@ -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
|
92
tox.ini
Normal file
92
tox.ini
Normal file
@ -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
|
9
unit_tests/__init__.py
Normal file
9
unit_tests/__init__.py
Normal file
@ -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()
|
120
unit_tests/test_horizon_plugin.py
Normal file
120
unit_tests/test_horizon_plugin.py
Normal file
@ -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),
|
||||
)
|
Loading…
Reference in New Issue
Block a user