LB relation updates
* Ensure vip passed back from lb is included in certificate request * Pull interface from its own repo Change-Id: Ib9fa865c115d54591483db6679dbeb645b3e353f
This commit is contained in:
parent
616059437d
commit
3a34e4b558
@ -24,7 +24,7 @@ requires:
|
|||||||
certificates:
|
certificates:
|
||||||
interface: tls-certificates
|
interface: tls-certificates
|
||||||
loadbalancer:
|
loadbalancer:
|
||||||
interface: api-endpoints
|
interface: openstack-loadbalancer
|
||||||
alertmanager-service:
|
alertmanager-service:
|
||||||
interface: http
|
interface: http
|
||||||
prometheus:
|
prometheus:
|
||||||
|
@ -5,3 +5,4 @@ git+https://opendev.org/openstack/charm-ops-openstack#egg=ops_openstack
|
|||||||
#git+https://opendev.org/openstack/charm-ops-interface-tls-certificates#egg=interface_tls_certificates
|
#git+https://opendev.org/openstack/charm-ops-interface-tls-certificates#egg=interface_tls_certificates
|
||||||
git+https://github.com/gnuoy/ops-interface-tls-certificates@no-exception-for-inflight-request#egg=interface_tls_certificates
|
git+https://github.com/gnuoy/ops-interface-tls-certificates@no-exception-for-inflight-request#egg=interface_tls_certificates
|
||||||
git+https://github.com/openstack-charmers/ops-interface-ceph-iscsi-admin-access#egg=interface_ceph_iscsi_admin_access
|
git+https://github.com/openstack-charmers/ops-interface-ceph-iscsi-admin-access#egg=interface_ceph_iscsi_admin_access
|
||||||
|
git+https://github.com/openstack-charmers/ops-interface-openstack-loadbalancer#egg=interface_openstack_loadbalancer
|
||||||
|
49
src/charm.py
49
src/charm.py
@ -18,6 +18,7 @@ from typing import List, Union, Tuple
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import interface_tls_certificates.ca_client as ca_client
|
import interface_tls_certificates.ca_client as ca_client
|
||||||
|
import interface_openstack_loadbalancer.loadbalancer as ops_lb_interface
|
||||||
import re
|
import re
|
||||||
import secrets
|
import secrets
|
||||||
import socket
|
import socket
|
||||||
@ -27,7 +28,6 @@ import tenacity
|
|||||||
import ops_openstack.plugins.classes
|
import ops_openstack.plugins.classes
|
||||||
import interface_ceph_iscsi_admin_access.admin_access as admin_access
|
import interface_ceph_iscsi_admin_access.admin_access as admin_access
|
||||||
import interface_dashboard
|
import interface_dashboard
|
||||||
import interface_api_endpoints
|
|
||||||
import interface_grafana_dashboard
|
import interface_grafana_dashboard
|
||||||
import interface_http
|
import interface_http
|
||||||
import interface_radosgw_user
|
import interface_radosgw_user
|
||||||
@ -57,6 +57,7 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
|
|||||||
TLS_CHARM_CA_CERT_PATH = TLS_CA_CERT_DIR / 'charm_config_juju_ca_cert.crt'
|
TLS_CHARM_CA_CERT_PATH = TLS_CA_CERT_DIR / 'charm_config_juju_ca_cert.crt'
|
||||||
TLS_PORT = 8443
|
TLS_PORT = 8443
|
||||||
DASH_DIR = Path('src/dashboards')
|
DASH_DIR = Path('src/dashboards')
|
||||||
|
LB_SERVICE_NAME = "ceph-dashboard"
|
||||||
|
|
||||||
class CharmCephOption():
|
class CharmCephOption():
|
||||||
"""Manage a charm option to ceph command to manage that option"""
|
"""Manage a charm option to ceph command to manage that option"""
|
||||||
@ -175,7 +176,7 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
|
|||||||
self._configure_dashboard)
|
self._configure_dashboard)
|
||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.ca_client.on.ca_available,
|
self.ca_client.on.ca_available,
|
||||||
self._on_ca_available)
|
self._configure_dashboard)
|
||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.ca_client.on.tls_server_config_ready,
|
self.ca_client.on.tls_server_config_ready,
|
||||||
self._configure_dashboard)
|
self._configure_dashboard)
|
||||||
@ -189,16 +190,9 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
|
|||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.on.delete_user_action,
|
self.on.delete_user_action,
|
||||||
self._delete_user_action)
|
self._delete_user_action)
|
||||||
self.ingress = interface_api_endpoints.APIEndpointsRequires(
|
self.ingress = ops_lb_interface.OSLoadbalancerRequires(
|
||||||
self,
|
self,
|
||||||
'loadbalancer',
|
'loadbalancer')
|
||||||
{
|
|
||||||
'endpoints': [{
|
|
||||||
'service-type': 'ceph-dashboard',
|
|
||||||
'frontend-port': self.TLS_PORT,
|
|
||||||
'backend-port': self.TLS_PORT,
|
|
||||||
'backend-ip': self._get_bind_ip(),
|
|
||||||
'check-type': 'httpd'}]})
|
|
||||||
self.grafana_dashboard = \
|
self.grafana_dashboard = \
|
||||||
interface_grafana_dashboard.GrafanaDashboardProvides(
|
interface_grafana_dashboard.GrafanaDashboardProvides(
|
||||||
self,
|
self,
|
||||||
@ -218,8 +212,23 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
|
|||||||
self.framework.observe(
|
self.framework.observe(
|
||||||
self.prometheus.on.http_ready,
|
self.prometheus.on.http_ready,
|
||||||
self._configure_dashboard)
|
self._configure_dashboard)
|
||||||
|
self.framework.observe(
|
||||||
|
self.ingress.on.lb_relation_ready,
|
||||||
|
self._request_loadbalancer)
|
||||||
|
self.framework.observe(
|
||||||
|
self.ingress.on.lb_configured,
|
||||||
|
self._configure_dashboard)
|
||||||
self._stored.set_default(is_started=False)
|
self._stored.set_default(is_started=False)
|
||||||
|
|
||||||
|
def _request_loadbalancer(self, _) -> None:
|
||||||
|
"""Send request to create loadbalancer"""
|
||||||
|
self.ingress.request_loadbalancer(
|
||||||
|
self.LB_SERVICE_NAME,
|
||||||
|
self.TLS_PORT,
|
||||||
|
self.TLS_PORT,
|
||||||
|
self._get_bind_ip(),
|
||||||
|
'httpd')
|
||||||
|
|
||||||
def _register_dashboards(self) -> None:
|
def _register_dashboards(self) -> None:
|
||||||
"""Register all dashboards with grafana"""
|
"""Register all dashboards with grafana"""
|
||||||
for dash_file in self.DASH_DIR.glob("*.json"):
|
for dash_file in self.DASH_DIR.glob("*.json"):
|
||||||
@ -273,9 +282,24 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
|
|||||||
creds[0]['access_key'],
|
creds[0]['access_key'],
|
||||||
creds[0]['secret_key'])
|
creds[0]['secret_key'])
|
||||||
|
|
||||||
def _on_ca_available(self, _) -> None:
|
def request_certificates(self) -> None:
|
||||||
"""Request TLS certificates."""
|
"""Request TLS certificates."""
|
||||||
|
if not self.ca_client.is_joined:
|
||||||
|
logging.debug(
|
||||||
|
"Cannot request certificates, relation not present.")
|
||||||
|
return
|
||||||
addresses = set()
|
addresses = set()
|
||||||
|
if self.ingress.relations:
|
||||||
|
lb_response = self.ingress.get_frontend_data()
|
||||||
|
if lb_response:
|
||||||
|
lb_config = lb_response[self.LB_SERVICE_NAME]
|
||||||
|
addresses.update(
|
||||||
|
[i for d in lb_config.values() for i in d['ip']])
|
||||||
|
else:
|
||||||
|
logging.debug(
|
||||||
|
("Defering certificate request until loadbalancer has "
|
||||||
|
"responded."))
|
||||||
|
return
|
||||||
for binding_name in ['public']:
|
for binding_name in ['public']:
|
||||||
binding = self.model.get_binding(binding_name)
|
binding = self.model.get_binding(binding_name)
|
||||||
addresses.add(binding.network.ingress_address)
|
addresses.add(binding.network.ingress_address)
|
||||||
@ -390,6 +414,7 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
|
|||||||
|
|
||||||
def _configure_dashboard(self, _) -> None:
|
def _configure_dashboard(self, _) -> None:
|
||||||
"""Configure dashboard"""
|
"""Configure dashboard"""
|
||||||
|
self.request_certificates()
|
||||||
if not self.mon.mons_ready:
|
if not self.mon.mons_ready:
|
||||||
logging.info("Not configuring dashboard, mons not ready")
|
logging.info("Not configuring dashboard, mons not ready")
|
||||||
return
|
return
|
||||||
|
@ -1,64 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
from ops.framework import (
|
|
||||||
StoredState,
|
|
||||||
EventBase,
|
|
||||||
ObjectEvents,
|
|
||||||
EventSource,
|
|
||||||
Object)
|
|
||||||
|
|
||||||
|
|
||||||
class EndpointDataEvent(EventBase):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class APIEndpointsEvents(ObjectEvents):
|
|
||||||
ep_ready = EventSource(EndpointDataEvent)
|
|
||||||
|
|
||||||
|
|
||||||
class APIEndpointsRequires(Object):
|
|
||||||
|
|
||||||
on = APIEndpointsEvents()
|
|
||||||
_stored = StoredState()
|
|
||||||
|
|
||||||
def __init__(self, charm, relation_name, config_dict):
|
|
||||||
super().__init__(charm, relation_name)
|
|
||||||
self.config_dict = config_dict
|
|
||||||
self.relation_name = relation_name
|
|
||||||
self.framework.observe(
|
|
||||||
charm.on[self.relation_name].relation_changed,
|
|
||||||
self._on_relation_changed)
|
|
||||||
|
|
||||||
def _on_relation_changed(self, event):
|
|
||||||
"""Handle the relation-changed event."""
|
|
||||||
event.relation.data[self.model.unit]['endpoints'] = json.dumps(
|
|
||||||
self.config_dict['endpoints'])
|
|
||||||
|
|
||||||
def update_config(self, config_dict):
|
|
||||||
"""Allow for updates to relation."""
|
|
||||||
self.config_dict = config_dict
|
|
||||||
relation = self.model.get_relation(self.relation_name)
|
|
||||||
if relation:
|
|
||||||
relation.data[self.model.unit]['endpoints'] = json.dumps(
|
|
||||||
self.config_dict['endpoints'])
|
|
||||||
|
|
||||||
|
|
||||||
class APIEndpointsProvides(Object):
|
|
||||||
|
|
||||||
on = APIEndpointsEvents()
|
|
||||||
_stored = StoredState()
|
|
||||||
|
|
||||||
def __init__(self, charm):
|
|
||||||
super().__init__(charm, "loadbalancer")
|
|
||||||
# Observe the relation-changed hook event and bind
|
|
||||||
# self.on_relation_changed() to handle the event.
|
|
||||||
self.framework.observe(
|
|
||||||
charm.on["loadbalancer"].relation_changed,
|
|
||||||
self._on_relation_changed)
|
|
||||||
self.charm = charm
|
|
||||||
|
|
||||||
def _on_relation_changed(self, event):
|
|
||||||
"""Handle a change to the loadbalancer relation."""
|
|
||||||
self.on.ep_ready.emit()
|
|
@ -15,6 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import json
|
||||||
import unittest
|
import unittest
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
@ -385,6 +386,9 @@ class TestCephDashboardCharmBase(CharmTestCase):
|
|||||||
_gethostname.return_value = 'server1'
|
_gethostname.return_value = 'server1'
|
||||||
cert_rel_id = self.harness.add_relation('certificates', 'vault')
|
cert_rel_id = self.harness.add_relation('certificates', 'vault')
|
||||||
dash_rel_id = self.harness.add_relation('dashboard', 'ceph-mon')
|
dash_rel_id = self.harness.add_relation('dashboard', 'ceph-mon')
|
||||||
|
lb_rel_id = self.harness.add_relation(
|
||||||
|
'loadbalancer',
|
||||||
|
'openstack-loadbalancer')
|
||||||
self.harness.begin()
|
self.harness.begin()
|
||||||
self.harness.set_leader()
|
self.harness.set_leader()
|
||||||
self.harness.charm.TLS_CERT_PATH = mock_TLS_CERT_PATH
|
self.harness.charm.TLS_CERT_PATH = mock_TLS_CERT_PATH
|
||||||
@ -401,6 +405,40 @@ class TestCephDashboardCharmBase(CharmTestCase):
|
|||||||
self.harness.add_relation_unit(
|
self.harness.add_relation_unit(
|
||||||
cert_rel_id,
|
cert_rel_id,
|
||||||
'vault/0')
|
'vault/0')
|
||||||
|
self.harness.add_relation_unit(
|
||||||
|
lb_rel_id,
|
||||||
|
'openstack-loadbalancer/0')
|
||||||
|
# If lb relation is present but has not responded then certs should
|
||||||
|
# not have been requested yet.
|
||||||
|
self.assertEqual(
|
||||||
|
self.harness.get_relation_data(
|
||||||
|
cert_rel_id,
|
||||||
|
'ceph-dashboard/0'),
|
||||||
|
{})
|
||||||
|
self.harness.update_relation_data(
|
||||||
|
lb_rel_id,
|
||||||
|
'openstack-loadbalancer',
|
||||||
|
{
|
||||||
|
'frontends': json.dumps(
|
||||||
|
{
|
||||||
|
'ceph-dashboard': {
|
||||||
|
'admin': {
|
||||||
|
'ip': ['10.20.0.101'],
|
||||||
|
'port': 8443,
|
||||||
|
'protocol': 'http'},
|
||||||
|
'internal': {
|
||||||
|
'ip': ['10.30.0.101'],
|
||||||
|
'port': 8443,
|
||||||
|
'protocol': 'http'},
|
||||||
|
'public': {
|
||||||
|
'ip': ['10.10.0.101'],
|
||||||
|
'port': 8443,
|
||||||
|
'protocol': 'http'}}})})
|
||||||
|
self.assertNotEqual(
|
||||||
|
self.harness.get_relation_data(
|
||||||
|
cert_rel_id,
|
||||||
|
'ceph-dashboard/0'),
|
||||||
|
{})
|
||||||
self.harness.update_relation_data(
|
self.harness.update_relation_data(
|
||||||
cert_rel_id,
|
cert_rel_id,
|
||||||
'vault/0',
|
'vault/0',
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
#!/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 copy
|
|
||||||
import json
|
|
||||||
import unittest
|
|
||||||
import sys
|
|
||||||
sys.path.append('lib') # noqa
|
|
||||||
sys.path.append('src') # noqa
|
|
||||||
from ops.testing import Harness
|
|
||||||
from ops.charm import CharmBase
|
|
||||||
import interface_api_endpoints
|
|
||||||
|
|
||||||
|
|
||||||
class TestAPIEndpointsRequires(unittest.TestCase):
|
|
||||||
|
|
||||||
class MyCharm(CharmBase):
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
super().__init__(*args)
|
|
||||||
self.seen_events = []
|
|
||||||
self.ingress = interface_api_endpoints.APIEndpointsRequires(
|
|
||||||
self,
|
|
||||||
'loadbalancer',
|
|
||||||
{
|
|
||||||
'endpoints': [{
|
|
||||||
'service-type': 'ceph-dashboard',
|
|
||||||
'frontend-port': 8443,
|
|
||||||
'backend-port': 8443,
|
|
||||||
'backend-ip': '10.0.0.10',
|
|
||||||
'check-type': 'httpd'}]})
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.harness = Harness(
|
|
||||||
self.MyCharm,
|
|
||||||
meta='''
|
|
||||||
name: my-charm
|
|
||||||
requires:
|
|
||||||
loadbalancer:
|
|
||||||
interface: api-endpoints
|
|
||||||
'''
|
|
||||||
)
|
|
||||||
self.eps = [{
|
|
||||||
'service-type': 'ceph-dashboard',
|
|
||||||
'frontend-port': 8443,
|
|
||||||
'backend-port': 8443,
|
|
||||||
'backend-ip': '10.0.0.10',
|
|
||||||
'check-type': 'httpd'}]
|
|
||||||
|
|
||||||
def add_loadbalancer_relation(self):
|
|
||||||
rel_id = self.harness.add_relation(
|
|
||||||
'loadbalancer',
|
|
||||||
'service-loadbalancer')
|
|
||||||
self.harness.add_relation_unit(
|
|
||||||
rel_id,
|
|
||||||
'service-loadbalancer/0')
|
|
||||||
self.harness.update_relation_data(
|
|
||||||
rel_id,
|
|
||||||
'service-loadbalancer/0',
|
|
||||||
{'ingress-address': '10.0.0.3'})
|
|
||||||
return rel_id
|
|
||||||
|
|
||||||
def test_init(self):
|
|
||||||
self.harness.begin()
|
|
||||||
self.assertEqual(
|
|
||||||
self.harness.charm.ingress.config_dict,
|
|
||||||
{'endpoints': self.eps})
|
|
||||||
self.assertEqual(
|
|
||||||
self.harness.charm.ingress.relation_name,
|
|
||||||
'loadbalancer')
|
|
||||||
|
|
||||||
def test__on_relation_changed(self):
|
|
||||||
self.harness.begin()
|
|
||||||
rel_id = self.add_loadbalancer_relation()
|
|
||||||
rel_data = self.harness.get_relation_data(
|
|
||||||
rel_id,
|
|
||||||
'my-charm/0')
|
|
||||||
self.assertEqual(
|
|
||||||
rel_data['endpoints'],
|
|
||||||
json.dumps(self.eps))
|
|
||||||
|
|
||||||
def test_update_config(self):
|
|
||||||
self.harness.begin()
|
|
||||||
rel_id = self.add_loadbalancer_relation()
|
|
||||||
new_eps = copy.deepcopy(self.eps)
|
|
||||||
new_eps.append({
|
|
||||||
'service-type': 'ceph-dashboard',
|
|
||||||
'frontend-port': 9443,
|
|
||||||
'backend-port': 9443,
|
|
||||||
'backend-ip': '10.0.0.10',
|
|
||||||
'check-type': 'https'})
|
|
||||||
self.harness.charm.ingress.update_config(
|
|
||||||
{'endpoints': new_eps})
|
|
||||||
rel_data = self.harness.get_relation_data(
|
|
||||||
rel_id,
|
|
||||||
'my-charm/0')
|
|
||||||
self.assertEqual(
|
|
||||||
rel_data['endpoints'],
|
|
||||||
json.dumps(new_eps))
|
|
||||||
|
|
||||||
|
|
||||||
class TestAPIEndpointsProvides(unittest.TestCase):
|
|
||||||
|
|
||||||
class MyCharm(CharmBase):
|
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
super().__init__(*args)
|
|
||||||
self.seen_events = []
|
|
||||||
self.api_eps = interface_api_endpoints.APIEndpointsProvides(self)
|
|
||||||
self.framework.observe(
|
|
||||||
self.api_eps.on.ep_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
|
|
||||||
provides:
|
|
||||||
loadbalancer:
|
|
||||||
interface: api-endpoints
|
|
||||||
'''
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_on_changed(self):
|
|
||||||
self.harness.begin()
|
|
||||||
# No MonReadyEvent as relation is absent
|
|
||||||
self.assertEqual(
|
|
||||||
self.harness.charm.seen_events,
|
|
||||||
[])
|
|
||||||
rel_id = self.harness.add_relation('loadbalancer', 'ceph-dashboard')
|
|
||||||
self.harness.add_relation_unit(
|
|
||||||
rel_id,
|
|
||||||
'ceph-dashboard/0')
|
|
||||||
self.harness.update_relation_data(
|
|
||||||
rel_id,
|
|
||||||
'ceph-dashboard/0',
|
|
||||||
{'ingress-address': '10.0.0.3'})
|
|
||||||
self.assertEqual(
|
|
||||||
self.harness.charm.seen_events,
|
|
||||||
['EndpointDataEvent'])
|
|
Loading…
Reference in New Issue
Block a user