First cut
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.tox
|
||||
.stestr/
|
||||
__pycache__
|
3
.stestr.conf
Normal file
3
.stestr.conf
Normal file
@@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
test_path=./unit_tests
|
||||
top_dir=./
|
0
interface_ceph_iscsi_admin_access/__init__.py
Normal file
0
interface_ceph_iscsi_admin_access/__init__.py
Normal file
111
interface_ceph_iscsi_admin_access/admin_access.py
Normal file
111
interface_ceph_iscsi_admin_access/admin_access.py
Normal file
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# 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.
|
||||
|
||||
from ops.framework import (
|
||||
StoredState,
|
||||
EventBase,
|
||||
ObjectEvents,
|
||||
EventSource,
|
||||
Object)
|
||||
|
||||
|
||||
class CephISCSIAdminAccessEvent(EventBase):
|
||||
pass
|
||||
|
||||
|
||||
class CephISCSIAdminAccessEvents(ObjectEvents):
|
||||
admin_access_ready = EventSource(CephISCSIAdminAccessEvent)
|
||||
admin_access_request = EventSource(CephISCSIAdminAccessEvent)
|
||||
|
||||
|
||||
class CephISCSIAdminAccessRequires(Object):
|
||||
|
||||
on = CephISCSIAdminAccessEvents()
|
||||
_stored = StoredState()
|
||||
|
||||
def __init__(self, charm, relation_name):
|
||||
super().__init__(charm, relation_name)
|
||||
self.relation_name = relation_name
|
||||
self.framework.observe(
|
||||
charm.on[self.relation_name].relation_changed,
|
||||
self._on_relation_changed)
|
||||
|
||||
def get_user_creds(self):
|
||||
creds = []
|
||||
for relation in self.framework.model.relations[self.relation_name]:
|
||||
app_data = relation.data[relation.app]
|
||||
for unit in relation.units:
|
||||
unit_data = relation.data[unit]
|
||||
cred_data = {
|
||||
'name': unit_data.get('name'),
|
||||
'host': unit_data.get('host'),
|
||||
'username': app_data.get('username'),
|
||||
'password': app_data.get('password'),
|
||||
'scheme': unit_data.get('scheme'),
|
||||
'port': unit_data.get('port')}
|
||||
if all(cred_data.values()):
|
||||
creds.append(cred_data)
|
||||
creds = sorted(creds, key=lambda k: k['host'])
|
||||
return creds
|
||||
|
||||
def _on_relation_changed(self, event):
|
||||
"""Handle the relation-changed event."""
|
||||
if self.get_user_creds():
|
||||
self.on.admin_access_ready.emit()
|
||||
|
||||
|
||||
class CephISCSIAdminAccessProvides(Object):
|
||||
|
||||
on = CephISCSIAdminAccessEvents()
|
||||
_stored = StoredState()
|
||||
|
||||
def __init__(self, charm, relation_name):
|
||||
super().__init__(charm, relation_name)
|
||||
self.relation_name = relation_name
|
||||
self.framework.observe(
|
||||
charm.on[self.relation_name].relation_joined,
|
||||
self._on_relation_joined)
|
||||
|
||||
def get_admin_access_requests(self):
|
||||
usernames = [
|
||||
f"{r.name}-{r.id}"
|
||||
for r in self.framework.model.relations[self.relation_name]]
|
||||
return usernames
|
||||
|
||||
def _on_relation_joined(self, event):
|
||||
"""Handle the relation-changed event."""
|
||||
if self.get_admin_access_requests():
|
||||
self.on.admin_access_request.emit()
|
||||
|
||||
def publish_gateway(self, name, username, password, scheme, port=5000):
|
||||
for relation in self.framework.model.relations[self.relation_name]:
|
||||
if self.model.unit.is_leader():
|
||||
relation.data[self.model.app]['username'] = username
|
||||
relation.data[self.model.app]['password'] = password
|
||||
binding = self.framework.model.get_binding(relation)
|
||||
relation.data[self.model.unit]['name'] = name
|
||||
relation.data[self.model.unit]['scheme'] = scheme
|
||||
relation.data[self.model.unit]['port'] = str(port)
|
||||
relation.data[self.model.unit]['host'] = str(
|
||||
binding.network.bind_address)
|
||||
|
||||
@property
|
||||
def client_addresses(self):
|
||||
addressees = []
|
||||
for relation in self.framework.model.relations[self.relation_name]:
|
||||
for unit in relation.units:
|
||||
addressees.append(relation.data[unit]['ingress-address'])
|
||||
return sorted(addressees)
|
18
setup.cfg
Normal file
18
setup.cfg
Normal file
@@ -0,0 +1,18 @@
|
||||
[metadata]
|
||||
name = interface_ceph_iscsi_admin_access
|
||||
summary = Charm interface for Ceph iSCSI admin access using Operator Framework
|
||||
version = 0.0.1.dev1
|
||||
description-file =
|
||||
README.rst
|
||||
author = OpenStack Charmers
|
||||
author-email = openstack-charmers@lists.ubuntu.com
|
||||
url = https://github.com/openstack-charmers/ops-interface-ceph-iscsi-admin-access
|
||||
classifier =
|
||||
Development Status :: 2 - Pre-Alpha
|
||||
Intended Audience :: Developers
|
||||
Topic :: System
|
||||
Topic :: System :: Installation/Setup
|
||||
opic :: System :: Software Distribution
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.5
|
||||
License :: OSI Approved :: Apache Software License
|
38
setup.py
Normal file
38
setup.py
Normal file
@@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# 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.
|
||||
|
||||
"""Module used to setup the interface_ceph_client framework."""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
version = "0.0.1.dev1"
|
||||
install_require = [
|
||||
'charmhelpers',
|
||||
'ops',
|
||||
]
|
||||
|
||||
tests_require = [
|
||||
'tox >= 2.3.1',
|
||||
]
|
||||
|
||||
setup(
|
||||
license='Apache-2.0: http://www.apache.org/licenses/LICENSE-2.0',
|
||||
packages=find_packages(exclude=["unit_tests"]),
|
||||
zip_safe=False,
|
||||
install_requires=install_require,
|
||||
)
|
9
test-requirements.txt
Normal file
9
test-requirements.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# Lint and unit test requirements
|
||||
flake8
|
||||
stestr>=2.2.0
|
||||
mock>=1.2
|
||||
coverage>=3.6
|
||||
# Install netifaces as its a horrible charmhelpers lazy import
|
||||
netifaces
|
||||
charmhelpers
|
||||
git+https://github.com/canonical/operator.git#egg=ops
|
59
tox.ini
Normal file
59
tox.ini
Normal file
@@ -0,0 +1,59 @@
|
||||
[tox]
|
||||
skipsdist = True
|
||||
envlist = pep8,py3
|
||||
# NOTE(beisner): Avoid build/test env pollution by not enabling sitepackages.
|
||||
sitepackages = False
|
||||
# NOTE(beisner): Avoid false positives by not skipping missing interpreters.
|
||||
skip_missing_interpreters = False
|
||||
|
||||
[testenv]
|
||||
setenv = VIRTUAL_ENV={envdir}
|
||||
PYTHONHASHSEED=0
|
||||
TERM=linux
|
||||
install_command =
|
||||
pip install {opts} {packages}
|
||||
|
||||
[testenv:py3]
|
||||
basepython = python3
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = stestr run {posargs}
|
||||
|
||||
[testenv:pep8]
|
||||
basepython = python3
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
commands = flake8 {posargs} . unit_tests
|
||||
|
||||
[testenv:cover]
|
||||
# Technique based heavily upon
|
||||
# https://github.com/openstack/nova/blob/master/tox.ini
|
||||
basepython = python3
|
||||
deps = -r{toxinidir}/test-requirements.txt
|
||||
setenv =
|
||||
{[testenv]setenv}
|
||||
PYTHON=coverage run
|
||||
commands =
|
||||
coverage erase
|
||||
stestr run {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
|
0
unit_tests/__init__.py
Normal file
0
unit_tests/__init__.py
Normal file
257
unit_tests/test_interface_iscsi_admin_access.py
Normal file
257
unit_tests/test_interface_iscsi_admin_access.py
Normal file
@@ -0,0 +1,257 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# 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 ops.testing import Harness, _TestingModelBackend
|
||||
from ops.charm import CharmBase
|
||||
from ops import framework, model
|
||||
|
||||
from interface_ceph_iscsi_admin_access.admin_access import (
|
||||
CephISCSIAdminAccessRequires,
|
||||
CephISCSIAdminAccessProvides)
|
||||
|
||||
|
||||
class TestCephISCSIAdminAccessRequires(unittest.TestCase):
|
||||
|
||||
class MyCharm(CharmBase):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.seen_events = []
|
||||
self.iscsi_user = CephISCSIAdminAccessRequires(
|
||||
self,
|
||||
'iscsi-dashboard')
|
||||
self.framework.observe(
|
||||
self.iscsi_user.on.admin_access_ready,
|
||||
self._log_event)
|
||||
|
||||
def _log_event(self, event):
|
||||
self.seen_events.append(type(event).__name__)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.harness = Harness(
|
||||
self.MyCharm,
|
||||
meta='''
|
||||
name: my-charm
|
||||
requires:
|
||||
iscsi-dashboard:
|
||||
interface: admin-access
|
||||
'''
|
||||
)
|
||||
|
||||
def test_init(self):
|
||||
self.harness.begin()
|
||||
self.assertEqual(
|
||||
self.harness.charm.iscsi_user.relation_name,
|
||||
'iscsi-dashboard')
|
||||
|
||||
def add_iscsi_relation(self, iscsi_app_name='ceph-iscsi',
|
||||
complete=True):
|
||||
rel_id = self.harness.add_relation(
|
||||
'iscsi-dashboard',
|
||||
iscsi_app_name)
|
||||
self.harness.add_relation_unit(
|
||||
rel_id,
|
||||
'{}/0'.format(iscsi_app_name))
|
||||
if complete:
|
||||
self.complete_relation(rel_id, iscsi_app_name)
|
||||
return rel_id
|
||||
|
||||
def complete_relation(self, rel_id, iscsi_app_name='ceph-iscsi'):
|
||||
unit_name = '{}/0'.format(iscsi_app_name)
|
||||
self.harness.update_relation_data(
|
||||
rel_id,
|
||||
unit_name,
|
||||
{
|
||||
'name': unit_name.replace('/', '-'),
|
||||
'host': '{}1.foo'.format(iscsi_app_name),
|
||||
'scheme': 'http',
|
||||
'port': '23'})
|
||||
self.harness.update_relation_data(
|
||||
rel_id,
|
||||
iscsi_app_name,
|
||||
{
|
||||
'username': 'admin',
|
||||
'password': 'password'})
|
||||
|
||||
def test_add_iscsi_dashboard_relation(self):
|
||||
self.harness.begin()
|
||||
self.harness.set_leader()
|
||||
rel_id = self.add_iscsi_relation(complete=False)
|
||||
self.assertEqual(
|
||||
self.harness.charm.seen_events,
|
||||
[])
|
||||
self.complete_relation(rel_id)
|
||||
self.assertEqual(
|
||||
self.harness.charm.seen_events,
|
||||
['CephISCSIAdminAccessEvent'])
|
||||
|
||||
def test_get_user_creds(self):
|
||||
self.harness.begin()
|
||||
self.harness.set_leader()
|
||||
expect_east = {
|
||||
'host': 'ceph-iscsi-east1.foo',
|
||||
'name': 'ceph-iscsi-east-0',
|
||||
'password': 'password',
|
||||
'port': '23',
|
||||
'scheme': 'http',
|
||||
'username': 'admin'}
|
||||
expect_west = {
|
||||
'host': 'ceph-iscsi-west1.foo',
|
||||
'name': 'ceph-iscsi-west-0',
|
||||
'password': 'password',
|
||||
'port': '23',
|
||||
'scheme': 'http',
|
||||
'username': 'admin'}
|
||||
self.add_iscsi_relation('ceph-iscsi-east')
|
||||
self.assertEqual(
|
||||
self.harness.charm.iscsi_user.get_user_creds(),
|
||||
[expect_east])
|
||||
self.add_iscsi_relation('ceph-iscsi-west')
|
||||
self.assertEqual(
|
||||
self.harness.charm.iscsi_user.get_user_creds(),
|
||||
[expect_east, expect_west])
|
||||
|
||||
|
||||
class TestCephISCSIAdminAccessProvides(unittest.TestCase):
|
||||
|
||||
class MyCharm(CharmBase):
|
||||
|
||||
def __init__(self, *args):
|
||||
super().__init__(*args)
|
||||
self.seen_events = []
|
||||
|
||||
self.admin_access = CephISCSIAdminAccessProvides(
|
||||
self,
|
||||
'admin-access')
|
||||
self.framework.observe(
|
||||
self.admin_access.on.admin_access_request,
|
||||
self._log_event)
|
||||
|
||||
def _log_event(self, event):
|
||||
self.seen_events.append(type(event).__name__)
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.harness = Harness(
|
||||
self.MyCharm,
|
||||
meta='''
|
||||
name: ceph-iscsi
|
||||
provides:
|
||||
admin-access:
|
||||
interface: admin-access
|
||||
'''
|
||||
)
|
||||
|
||||
# BEGIN: Workaround until network_get is implemented
|
||||
class _TestingOPSModelBackend(_TestingModelBackend):
|
||||
|
||||
def network_get(self, endpoint_name, relation_id=None):
|
||||
network_data = {
|
||||
'bind-addresses': [{
|
||||
'interface-name': 'eth0',
|
||||
'addresses': [{
|
||||
'cidr': '10.0.0.0/24',
|
||||
'value': '10.0.0.10'}]}],
|
||||
'ingress-addresses': ['10.0.0.10'],
|
||||
'egress-subnets': ['10.0.0.0/24']}
|
||||
return network_data
|
||||
|
||||
self.harness._backend = _TestingOPSModelBackend(
|
||||
self.harness._unit_name, self.harness._meta)
|
||||
self.harness._model = model.Model(
|
||||
self.harness._meta,
|
||||
self.harness._backend)
|
||||
self.harness._framework = framework.Framework(
|
||||
":memory:",
|
||||
self.harness._charm_dir,
|
||||
self.harness._meta,
|
||||
self.harness._model)
|
||||
# END Workaround
|
||||
|
||||
def test_init(self):
|
||||
self.harness.begin()
|
||||
self.assertEqual(
|
||||
self.harness.charm.admin_access.relation_name,
|
||||
'admin-access')
|
||||
|
||||
def add_admin_access_relation(self, ingress_address,
|
||||
app_name='ceph-dashboard'):
|
||||
unit_name = '{}/0'.format(app_name)
|
||||
rel_id = self.harness.add_relation(
|
||||
'admin-access',
|
||||
app_name)
|
||||
self.harness.add_relation_unit(
|
||||
rel_id,
|
||||
unit_name)
|
||||
self.harness.update_relation_data(
|
||||
rel_id,
|
||||
unit_name,
|
||||
{'ingress-address': ingress_address})
|
||||
return rel_id
|
||||
|
||||
def test_get_admin_access_requests(self):
|
||||
self.harness.begin()
|
||||
self.add_admin_access_relation('10.0.0.12')
|
||||
self.add_admin_access_relation('10.0.0.12', 'ceph-client')
|
||||
self.assertEqual(
|
||||
self.harness.charm.admin_access.get_admin_access_requests(),
|
||||
['admin-access-0', 'admin-access-1'])
|
||||
|
||||
def test_client_addresses(self):
|
||||
self.harness.begin()
|
||||
self.add_admin_access_relation('10.0.0.12')
|
||||
self.add_admin_access_relation('192.168.9.34', 'ceph-client')
|
||||
self.assertEqual(
|
||||
self.harness.charm.admin_access.client_addresses,
|
||||
['10.0.0.12', '192.168.9.34'])
|
||||
|
||||
def test_publish_gateway(self):
|
||||
self.harness.begin()
|
||||
self.harness.set_leader()
|
||||
rel_id1 = self.add_admin_access_relation('10.0.0.12')
|
||||
rel_id2 = self.add_admin_access_relation('192.168.9.34', 'ceph-client')
|
||||
self.harness.charm.admin_access.publish_gateway(
|
||||
'foo',
|
||||
'admin',
|
||||
'password',
|
||||
'http',
|
||||
'5001')
|
||||
unit_data_expect = {
|
||||
'host': '10.0.0.10',
|
||||
'name': 'foo',
|
||||
'port': '5001',
|
||||
'scheme': 'http'}
|
||||
app_data_expect = {
|
||||
'password': 'password',
|
||||
'username': 'admin'}
|
||||
self.assertEqual(
|
||||
self.harness.get_relation_data(rel_id1, 'ceph-iscsi/0'),
|
||||
unit_data_expect)
|
||||
self.assertEqual(
|
||||
self.harness.get_relation_data(rel_id1, 'ceph-iscsi'),
|
||||
app_data_expect)
|
||||
self.assertEqual(
|
||||
self.harness.get_relation_data(rel_id2, 'ceph-iscsi/0'),
|
||||
unit_data_expect)
|
||||
self.assertEqual(
|
||||
self.harness.get_relation_data(rel_id2, 'ceph-iscsi'),
|
||||
app_data_expect)
|
Reference in New Issue
Block a user