Add support for loadbalancer interface
This adds support for the new loadbalancer interface which is intended to allow for load balancer / ingress endpoint providers, such as the cloud integrator charms, to provide a load balancer address upon request. The initial use-case for this is using Vault in Azure, where it is difficult or impossible to use a VIP or floating IP type approach for HA Vault; instead, this will allow a relation to the azure-integrator charm which will provide a native Azure LB which Vault can then advertise. Change-Id: I5e0738429d47625c23bfe71c86df6266a3ea364b
This commit is contained in:
parent
8923bc9f86
commit
7f4c95b5b4
@ -49,7 +49,8 @@ options:
|
||||
Virtual IP to use api traffic. You can provide up to two addresses
|
||||
configured on the access or external bindings. If neither binding
|
||||
is used then you can only provide one address that must be configured
|
||||
on the default space.
|
||||
on the default space. Mutually exclusive with the dns-ha-access-record
|
||||
config option or lb-provider relation.
|
||||
channel:
|
||||
type: string
|
||||
default: stable
|
||||
@ -59,8 +60,8 @@ options:
|
||||
type: string
|
||||
default:
|
||||
description: |
|
||||
DNS record to use for DNS HA with MAAS. Do not use vip setting
|
||||
if this is set.
|
||||
DNS record to use for DNS HA with MAAS. Mutually exclusive with the
|
||||
vip config option or lb-provider relation.
|
||||
totally-unsecure-auto-unlock:
|
||||
type: boolean
|
||||
default: false
|
||||
|
@ -32,6 +32,11 @@ requires:
|
||||
ha:
|
||||
interface: hacluster
|
||||
scope: container
|
||||
lb-provider:
|
||||
# Optional relation to a load balancer provider. Mutually exclusive
|
||||
# with the vip or dns-ha-access-record config options.
|
||||
interface: loadbalancer
|
||||
limit: 1
|
||||
provides:
|
||||
nrpe-external-master:
|
||||
interface: nrpe-external-master
|
||||
|
@ -66,6 +66,7 @@ from charms.reactive import (
|
||||
|
||||
from charms.reactive.relations import (
|
||||
endpoint_from_flag,
|
||||
endpoint_from_name,
|
||||
)
|
||||
|
||||
from charms.reactive.flags import (
|
||||
@ -283,6 +284,7 @@ def upgrade_charm():
|
||||
remove_state('configured')
|
||||
remove_state('vault.nrpe.configured')
|
||||
remove_state('vault.ssl.configured')
|
||||
remove_state('vault.requested-lb')
|
||||
|
||||
|
||||
@when_not("is-update-status-hook")
|
||||
@ -560,10 +562,29 @@ def configure_secrets_backend():
|
||||
clear_flag('secrets.refresh')
|
||||
|
||||
|
||||
@when('endpoint.lb-provider.available')
|
||||
@when('leadership.is_leader')
|
||||
@when_not('vault.requested-lb')
|
||||
def request_lb():
|
||||
lb_provider = endpoint_from_name('lb-provider')
|
||||
req = lb_provider.get_request('vault')
|
||||
req.protocol = req.protocols.tcp
|
||||
req.port_mapping = {8220: 8220}
|
||||
lb_provider.send_request(req)
|
||||
set_flag('vault.requested-lb')
|
||||
|
||||
|
||||
@when('vault.requested-lb')
|
||||
@when_not('endpoint.lb-provider.available')
|
||||
def clear_requested_lb():
|
||||
clear_flag('vault.requested-lb')
|
||||
|
||||
|
||||
@when_not("is-update-status-hook")
|
||||
@when('secrets.connected')
|
||||
def send_vault_url_and_ca():
|
||||
secrets = endpoint_from_flag('secrets.connected')
|
||||
lb_provider = endpoint_from_name('lb-provider')
|
||||
vault_url_external = None
|
||||
hostname = config('hostname')
|
||||
vip = vault.get_vip()
|
||||
@ -580,6 +601,16 @@ def send_vault_url_and_ca():
|
||||
log("VIP is set but ha.available is not yet set, skipping "
|
||||
"send_vault_url_and_ca.", level=DEBUG)
|
||||
return
|
||||
elif lb_provider.has_response:
|
||||
response = lb_provider.get_response('vault')
|
||||
if response.error:
|
||||
log('Load balancer failed, skipping: '
|
||||
'{}'.format(response.error_message or response.error_fields),
|
||||
level=ERROR)
|
||||
return
|
||||
vault_url = vault.get_api_url(address=response.address)
|
||||
vault_url_external = vault_url
|
||||
lb_provider.ack_response(response)
|
||||
else:
|
||||
vault_url = vault.get_api_url()
|
||||
vault_url_external = vault.get_api_url(binding='external')
|
||||
@ -694,6 +725,15 @@ def _assess_status():
|
||||
status_set('blocked',
|
||||
'vip and dns-ha-access-record configured')
|
||||
return
|
||||
if is_flag_set('config.lb_vip.invalid'):
|
||||
status_set('blocked',
|
||||
'lb-provider and vip are mutually exclusive')
|
||||
return
|
||||
if is_flag_set('config.lb_dns.invalid'):
|
||||
status_set('blocked',
|
||||
'lb-provider and dns-ha-access-record are '
|
||||
'mutually exclusive')
|
||||
return
|
||||
|
||||
if unitdata.kv().get('charm.vault.series-upgrading'):
|
||||
status_set("blocked",
|
||||
@ -756,6 +796,20 @@ def _assess_status():
|
||||
status_set('blocked', 'Vault cannot authorize approle')
|
||||
return
|
||||
|
||||
lb_provider = endpoint_from_name('lb-provider')
|
||||
is_leader = is_flag_set('leadership.is_leader')
|
||||
if is_leader and lb_provider.is_available:
|
||||
if not lb_provider.has_response:
|
||||
status_set('waiting', 'Waiting for load balancer')
|
||||
return
|
||||
response = lb_provider.get_response('vault')
|
||||
if response.error:
|
||||
status_set('blocked',
|
||||
'Load balancer failed: '
|
||||
'{}'.format(response.error_message or
|
||||
response.error_fields))
|
||||
return
|
||||
|
||||
has_ca = is_flag_set('charm.vault.ca.ready')
|
||||
has_cert_reqs = is_flag_set('certificates.certs.requested')
|
||||
if has_cert_reqs and not has_ca:
|
||||
|
@ -10,3 +10,5 @@ psutil
|
||||
git+https://opendev.org/openstack/charms.openstack.git#egg=charms.openstack
|
||||
|
||||
git+https://github.com/juju/charm-helpers.git#egg=charmhelpers
|
||||
|
||||
loadbalancer-interface
|
||||
|
@ -52,6 +52,7 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
||||
self.patches = [
|
||||
'config',
|
||||
'endpoint_from_flag',
|
||||
'endpoint_from_name',
|
||||
'is_state',
|
||||
'log',
|
||||
'network_get_primary_address',
|
||||
@ -78,6 +79,9 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
||||
self.kv = mock.MagicMock()
|
||||
self.kv.get.return_value = False
|
||||
self.unitdata.kv.return_value = self.kv
|
||||
self.endpoint_from_name().is_available = False
|
||||
self.endpoint_from_name().has_response = False
|
||||
self.patch_object(handlers.vault.hookenv, 'charm_dir', 'src')
|
||||
|
||||
def test_ssl_available(self):
|
||||
self.assertFalse(handlers.ssl_available({
|
||||
@ -1095,3 +1099,56 @@ class TestHandlers(unit_tests.test_utils.CharmTestCase):
|
||||
assert service_running.call_count == 2
|
||||
set_flag.assert_called_once_with('started')
|
||||
prepare_vault.assert_called_once_with()
|
||||
|
||||
def test_loadbalancer(self):
|
||||
self.is_flag_set.return_value = False
|
||||
self.patch_object(handlers.vault, 'get_vip', return_value=None)
|
||||
mock_secrets = self.endpoint_from_flag()
|
||||
lb_provider = self.endpoint_from_name()
|
||||
lb_provider.has_response = True
|
||||
response = lb_provider.get_response()
|
||||
|
||||
response.success = False
|
||||
handlers.send_vault_url_and_ca()
|
||||
self.assertFalse(mock_secrets.publish_url.called)
|
||||
|
||||
response.error = None
|
||||
response.address = 'loadbalancer'
|
||||
handlers.send_vault_url_and_ca()
|
||||
lb_provider.ack_response.assert_called_with(response)
|
||||
mock_secrets.publish_url.assert_has_calls([
|
||||
call(vault_url='http://loadbalancer:8200',
|
||||
remote_binding='access'),
|
||||
call(vault_url='http://loadbalancer:8200',
|
||||
remote_binding='external'),
|
||||
])
|
||||
|
||||
@patch.object(handlers, 'leader_get')
|
||||
@patch.object(handlers, 'client_approle_authorized')
|
||||
@patch.object(handlers, '_assess_interface_groups')
|
||||
@patch.object(handlers.vault, 'get_vault_health')
|
||||
def test_assess_status_loadbalancer(self, get_vault_health,
|
||||
_assess_interface_groups,
|
||||
_client_approle_authorized,
|
||||
_leader_get):
|
||||
self.is_flag_set.return_value = False
|
||||
get_vault_health.return_value = self._health_response
|
||||
self.endpoint_from_name().is_available = True
|
||||
self.endpoint_from_name().has_response = False
|
||||
handlers._assess_status()
|
||||
self.status_set.assert_called_with(
|
||||
'active', mock.ANY
|
||||
)
|
||||
self.is_flag_set.side_effect = lambda f: f == 'leadership.is_leader'
|
||||
handlers._assess_status()
|
||||
self.status_set.assert_called_with(
|
||||
'waiting', 'Waiting for load balancer'
|
||||
)
|
||||
|
||||
self.endpoint_from_name().has_response = True
|
||||
self.endpoint_from_name().get_response().error = True
|
||||
self.endpoint_from_name().get_response().error_message = 'just because'
|
||||
handlers._assess_status()
|
||||
self.status_set.assert_called_with(
|
||||
'blocked', 'Load balancer failed: just because'
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user