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:
Liam Young 2021-09-13 16:03:42 +00:00
parent 616059437d
commit 3a34e4b558
6 changed files with 77 additions and 236 deletions

View File

@ -24,7 +24,7 @@ requires:
certificates:
interface: tls-certificates
loadbalancer:
interface: api-endpoints
interface: openstack-loadbalancer
alertmanager-service:
interface: http
prometheus:

View File

@ -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://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-openstack-loadbalancer#egg=interface_openstack_loadbalancer

View File

@ -18,6 +18,7 @@ from typing import List, Union, Tuple
import base64
import interface_tls_certificates.ca_client as ca_client
import interface_openstack_loadbalancer.loadbalancer as ops_lb_interface
import re
import secrets
import socket
@ -27,7 +28,6 @@ import tenacity
import ops_openstack.plugins.classes
import interface_ceph_iscsi_admin_access.admin_access as admin_access
import interface_dashboard
import interface_api_endpoints
import interface_grafana_dashboard
import interface_http
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_PORT = 8443
DASH_DIR = Path('src/dashboards')
LB_SERVICE_NAME = "ceph-dashboard"
class CharmCephOption():
"""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.framework.observe(
self.ca_client.on.ca_available,
self._on_ca_available)
self._configure_dashboard)
self.framework.observe(
self.ca_client.on.tls_server_config_ready,
self._configure_dashboard)
@ -189,16 +190,9 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
self.framework.observe(
self.on.delete_user_action,
self._delete_user_action)
self.ingress = interface_api_endpoints.APIEndpointsRequires(
self.ingress = ops_lb_interface.OSLoadbalancerRequires(
self,
'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'}]})
'loadbalancer')
self.grafana_dashboard = \
interface_grafana_dashboard.GrafanaDashboardProvides(
self,
@ -218,8 +212,23 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
self.framework.observe(
self.prometheus.on.http_ready,
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)
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:
"""Register all dashboards with grafana"""
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]['secret_key'])
def _on_ca_available(self, _) -> None:
def request_certificates(self) -> None:
"""Request TLS certificates."""
if not self.ca_client.is_joined:
logging.debug(
"Cannot request certificates, relation not present.")
return
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']:
binding = self.model.get_binding(binding_name)
addresses.add(binding.network.ingress_address)
@ -390,6 +414,7 @@ class CephDashboardCharm(ops_openstack.core.OSBaseCharm):
def _configure_dashboard(self, _) -> None:
"""Configure dashboard"""
self.request_certificates()
if not self.mon.mons_ready:
logging.info("Not configuring dashboard, mons not ready")
return

View File

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

View File

@ -15,6 +15,7 @@
# limitations under the License.
import base64
import json
import unittest
import sys
@ -385,6 +386,9 @@ class TestCephDashboardCharmBase(CharmTestCase):
_gethostname.return_value = 'server1'
cert_rel_id = self.harness.add_relation('certificates', 'vault')
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.set_leader()
self.harness.charm.TLS_CERT_PATH = mock_TLS_CERT_PATH
@ -401,6 +405,40 @@ class TestCephDashboardCharmBase(CharmTestCase):
self.harness.add_relation_unit(
cert_rel_id,
'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(
cert_rel_id,
'vault/0',

View File

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