Enable use of OVN for plumbing to octavia units

Depends-On: Ia304cd25dbdd130338837f149a79efe59681f794
Change-Id: I9ef24e5256ad32e7593a601fb9b79a04747d0a2e
This commit is contained in:
Frode Nordahl 2020-01-17 12:32:56 +01:00
parent a7c57aa732
commit c1e86dcb4a
No known key found for this signature in database
GPG Key ID: 6A5D59A3BA48373F
11 changed files with 267 additions and 33 deletions

View File

@ -44,7 +44,7 @@ def configure_resources(*args):
'unit.')
if not reactive.all_flags_set('identity-service.available',
'neutron-api.available',
'neutron-openvswitch.connected',
'sdn-subordinate.available',
'amqp.available'):
return ch_core.hookenv.action_fail('all required relations not '
'available, please defer action'

View File

@ -6,6 +6,7 @@ includes:
- interface:keystone
- interface:neutron-load-balancer
- interface:neutron-plugin
- interface:ovsdb-subordinate
options:
basic:
use_venv: True

View File

@ -125,6 +125,20 @@ def init_neutron_client(keystone_session):
)
def is_extension_enabled(neutron_client, ext_alias):
"""Check for presence of Neutron extension
:param neutron_client:
:type neutron_client:
:returns: True if Neutron lists extension, False otherwise
:rtype: bool
"""
for extension in neutron_client.list_extensions().get('extensions', []):
if extension.get('alias') == ext_alias:
return True
return False
def get_nova_flavor(identity_service):
"""Get or create private Nova flavor for use with Octavia.
@ -192,7 +206,7 @@ def create_nova_keypair(identity_service, amp_key_name):
def get_hm_port(identity_service, local_unit_name, local_unit_address,
ovs_hostname=None):
host_id=None):
"""Get or create a per unit Neutron port for Octavia Health Manager.
A side effect of calling this function is that a port is created if one
@ -205,8 +219,8 @@ def get_hm_port(identity_service, local_unit_name, local_unit_address,
:param local_unit_address: DNS resolvable IP address of unit, used to
build Neutron port ``binding:host_id``
:type local_unit_address: str
:param ovs_hostname: Hostname used by neutron-openvswitch
:type ovs_hostname: Option[None,str]
:param host_id: Identifier used by SDN for binding the port
:type host_id: Option[None,str]
:returns: Port details extracted from result of call to
neutron_client.list_ports or neutron_client.create_port
:rtype: dict
@ -261,8 +275,7 @@ def get_hm_port(identity_service, local_unit_name, local_unit_address,
# avoid race with OVS agent attempting to bind port
# before it is created in the local units OVSDB
'admin_state_up': False,
'binding:host_id': (ovs_hostname or
socket.gethostname()),
'binding:host_id': host_id or socket.gethostname(),
# NOTE(fnordahl): device_owner has special meaning
# for Neutron [0], and things may break if set to
# an arbritary value. Using a value known by Neutron
@ -350,7 +363,7 @@ def toggle_hm_port(identity_service, local_unit_name, enabled=True):
nc.update_port(port['id'], {'port': {'admin_state_up': enabled}})
def setup_hm_port(identity_service, octavia_charm, ovs_hostname=None):
def setup_hm_port(identity_service, octavia_charm, host_id=None):
"""Create a per unit Neutron and OVS port for Octavia Health Manager.
This is used to plug the unit into the overlay network for direct
@ -361,8 +374,8 @@ def setup_hm_port(identity_service, octavia_charm, ovs_hostname=None):
:type identity_service: RelationBase class
:param ocataiva_charm: charm instance
:type octavia_charm: OctaviaCharm class instance
:param ovs_hostname: Hostname used by neutron-openvswitch
:type ovs_hostname: Option[None,str]
:param host_id: Identifier used by SDN for binding the port
:type host_id: Option[None,str]
:retruns: True on change to local unit, False otherwise
:rtype: bool
:raises: api_crud.APIUnavailable, api_crud.DuplicateResource
@ -372,7 +385,7 @@ def setup_hm_port(identity_service, octavia_charm, ovs_hostname=None):
identity_service,
octavia_charm.local_unit_name,
octavia_charm.local_address,
ovs_hostname=ovs_hostname)
host_id=host_id)
if not hm_port:
ch_core.hookenv.log('No network tagged with `charm-octavia` '
'exists, deferring port setup awaiting '
@ -546,14 +559,19 @@ def get_mgmt_network(identity_service, create=True):
router = None
if n_resp < 1 and create:
try:
resp = nc.create_router(
{
'router': {
'name': octavia.OCTAVIA_MGMT_NAME_PREFIX,
'distributed': False,
}
})
body = {
'router': {
'name': octavia.OCTAVIA_MGMT_NAME_PREFIX,
},
}
# NOTE(fnordahl): Using the ``distributed`` key in a request to
# Neutron is an error when the DVR extension is not enabled.
if is_extension_enabled(nc, 'dvr'):
# NOTE(fnordahl): When DVR is enabled we want to use a
# centralized router to support assigning addresses with IPv6
# RA. LP: #1843557
body['router'].update({'distributed': False})
resp = nc.create_router(body)
router = resp['router']
nc.add_tag('routers', router['id'], 'charm-octavia')
ch_core.hookenv.log('Created router {}'.format(router['id']),

View File

@ -326,7 +326,7 @@ class OctaviaCharm(ch_plugins.PolicydOverridePlugin,
services = ['apache2', 'octavia-health-manager', 'octavia-housekeeping',
'octavia-worker']
required_relations = ['shared-db', 'amqp', 'identity-service',
'neutron-openvswitch']
'sdn-subordinate']
restart_map = {
OCTAVIA_MGMT_INTF_CONF: services + ['systemd-networkd'],
OCTAVIA_CONF: services,
@ -371,12 +371,6 @@ class OctaviaCharm(ch_plugins.PolicydOverridePlugin,
workload status.
"""
states_to_check = super().states_to_check(required_relations)
override_relation = 'neutron-openvswitch'
if override_relation in states_to_check:
states_to_check[override_relation] = [
("{}.connected".format(override_relation),
"blocked",
"'{}' missing".format(override_relation))]
if not leadership.leader_get('amp-boot-network-list'):
if not reactive.is_flag_set('config.default.create-mgmt-network'):
# we are configured to not create required resources and they

View File

@ -28,6 +28,9 @@ requires:
neutron-openvswitch:
interface: neutron-plugin
scope: container
ovsdb-subordinate:
interface: ovsdb-subordinate
scope: container
resources:
policyd-override:
type: file

View File

@ -41,6 +41,20 @@ charm.use_defaults(
)
@reactive.when_any('neutron-openvswitch.connected',
'ovsdb-subordinate.available')
def sdn_joined():
reactive.set_flag('sdn-subordinate.connected')
reactive.set_flag('sdn-subordinate.available')
@reactive.when_none('neutron-openvswitch.connected',
'ovsdb-subordinate.available')
def sdn_broken():
reactive.clear_flag('sdn-subordinate.available')
reactive.clear_flag('sdn-subordinate.connected')
@reactive.when('identity-service.connected')
def setup_endpoint_connection(keystone):
"""Custom register endpoint function for Octavia.
@ -90,7 +104,7 @@ def setup_neutron_lbaas_proxy():
@reactive.when('identity-service.available')
@reactive.when('neutron-api.available')
@reactive.when('neutron-openvswitch.connected')
@reactive.when('sdn-subordinate.available')
# Neutron API calls will consistently fail as long as AMQP is unavailable
@reactive.when('amqp.available')
def setup_hm_port():
@ -100,7 +114,9 @@ def setup_hm_port():
communication with the octavia managed load balancer instances running
within the deployed cloud.
"""
ovs = reactive.endpoint_from_flag('neutron-openvswitch.connected')
neutron_ovs = reactive.endpoint_from_flag('neutron-openvswitch.connected')
ovsdb = reactive.endpoint_from_flag('ovsdb-subordinate.available')
host_id = neutron_ovs.host() if neutron_ovs else ovsdb.chassis_name
with charm.provide_charm_instance() as octavia_charm:
identity_service = reactive.endpoint_from_flag(
'identity-service.available')
@ -108,7 +124,7 @@ def setup_hm_port():
if api_crud.setup_hm_port(
identity_service,
octavia_charm,
ovs_hostname=ovs.host()):
host_id=host_id):
# trigger config render to make systemd-networkd bring up
# automatic IP configuration of the new port right now.
reactive.set_flag('config.changed')

View File

@ -0,0 +1,171 @@
series: bionic
relations:
- - glance:image-service
- nova-cloud-controller:image-service
- - glance:image-service
- nova-compute:image-service
- - mysql:shared-db
- glance:shared-db
- - mysql:shared-db
- keystone:shared-db
- - mysql:shared-db
- neutron-api:shared-db
- - mysql:shared-db
- nova-cloud-controller:shared-db
- - mysql:shared-db
- octavia:shared-db
- - keystone:identity-service
- glance:identity-service
- - keystone:identity-service
- nova-cloud-controller:identity-service
- - keystone:identity-service
- neutron-api:identity-service
- - keystone:identity-service
- octavia:identity-service
- - nova-compute:cloud-compute
- nova-cloud-controller:cloud-compute
- - rabbitmq-server:amqp
- neutron-api:amqp
- - rabbitmq-server:amqp
- glance:amqp
- - rabbitmq-server:amqp
- nova-cloud-controller:amqp
- - rabbitmq-server:amqp
- nova-compute:amqp
- - rabbitmq-server:amqp
- octavia:amqp
- - neutron-api:neutron-api
- nova-cloud-controller:neutron-api
- - neutron-api:neutron-load-balancer
- octavia:neutron-api
- - glance-simplestreams-sync:juju-info
- octavia-diskimage-retrofit:juju-info
- - keystone:identity-service
- glance-simplestreams-sync:identity-service
- - rabbitmq-server:amqp
- glance-simplestreams-sync:amqp
- - keystone:identity-credentials
- octavia-diskimage-retrofit:identity-credentials
- [ placement, mysql ]
- [ placement, keystone ]
- [ placement, nova-cloud-controller ]
- - neutron-api-plugin-ovn:neutron-plugin
- neutron-api:neutron-plugin-api-subordinate
- - vault:shared-db
- mysql:shared-db
- - ovn-central:certificates
- vault:certificates
- - ovn-central:ovsdb-cms
- neutron-api-plugin-ovn:ovsdb-cms
- - neutron-api:certificates
- vault:certificates
- - ovn-chassis:nova-compute
- nova-compute:neutron-plugin
- - ovn-chassis:ovsdb-subordinate
- octavia:ovsdb-subordinate
- - ovn-chassis:certificates
- vault:certificates
- - ovn-chassis:ovsdb
- ovn-central:ovsdb
- - vault:certificates
- neutron-api-plugin-ovn:certificates
- - vault:certificates
- glance:certificates
- - vault:certificates
- keystone:certificates
- - vault:certificates
- nova-cloud-controller:certificates
- - vault:certificates
- placement:certificates
- - vault:certificates
- octavia:certificates
- - vault:certificates
- glance-simplestreams-sync:certificates
- - hacluster-octavia:ha
- octavia:ha
applications:
glance:
charm: cs:~openstack-charmers-next/glance
num_units: 1
options:
openstack-origin: cloud:bionic-train
keystone:
charm: cs:~openstack-charmers-next/keystone
num_units: 1
options:
openstack-origin: cloud:bionic-train
mysql:
constraints: mem=3072M
charm: cs:~openstack-charmers-next/percona-cluster
num_units: 1
neutron-api:
constraints: cores=4
charm: cs:~openstack-charmers-next/neutron-api
num_units: 1
options:
openstack-origin: cloud:bionic-train
debug: True
flat-network-providers: physnet1
neutron-security-groups: True
manage-neutron-plugin-legacy-mode: False
nova-cloud-controller:
constraints: mem=3072M
charm: cs:~openstack-charmers-next/nova-cloud-controller
num_units: 1
options:
openstack-origin: cloud:bionic-train
debug: True
network-manager: Neutron
nova-compute:
constraints: mem=10240M
charm: cs:~openstack-charmers-next/nova-compute
num_units: 2
options:
openstack-origin: cloud:bionic-train
debug: True
hacluster-octavia:
series: bionic
charm: cs:~openstack-charmers-next/hacluster
octavia:
series: bionic
charm: ../../../octavia
num_units: 3
options:
openstack-origin: cloud:bionic-train
debug: True
spare-pool-size: 2
loadbalancer-topology: 'ACTIVE_STANDBY'
rabbitmq-server:
charm: cs:~openstack-charmers-next/rabbitmq-server
num_units: 1
glance-simplestreams-sync:
charm: cs:~openstack-charmers-next/glance-simplestreams-sync
num_units: 1
options:
source: ppa:simplestreams-dev/trunk
use_swift: False
octavia-diskimage-retrofit:
charm: cs:~openstack-charmers-next/octavia-diskimage-retrofit
options:
amp-image-tag: 'octavia-amphora'
retrofit-uca-pocket: train
placement:
charm: cs:~openstack-charmers-next/placement
num_units: 1
constraints: mem=1G
options:
openstack-origin: cloud:bionic-train
debug: true
neutron-api-plugin-ovn:
charm: cs:~openstack-charmers-next/neutron-api-plugin-ovn
ovn-central:
constraints: mem=3072M
charm: cs:~openstack-charmers-next/ovn-central
num_units: 3
options:
source: cloud:bionic-train
ovn-chassis:
charm: cs:~openstack-charmers-next/ovn-chassis
vault:
charm: cs:~openstack-charmers-next/vault
num_units: 1

View File

@ -0,0 +1 @@
ha.j2

View File

@ -1,9 +1,11 @@
charm_name: octavia
gate_bundles:
- bionic-train-ha-ovn
- bionic-train-ha
- bionic-stein-ha
- bionic-rocky-ha
smoke_bundles:
- bionic-train-ha-ovn
- bionic-train-ha
comment: |
Move `disco-stein` backup to dev pending LP: #1841599
@ -22,6 +24,15 @@ target_deploy_status:
vault:
workload-status: blocked
workload-status-message: Vault needs to be initialized
neutron-api-plugin-ovn:
workload-status: waiting
workload-status-message: "'ovsdb-cms' incomplete"
ovn-central:
workload-status: blocked
workload-status-message: "'certificates' missing"
ovn-chassis:
workload-status: blocked
workload-status-message: "'certificates' missing"
configure:
- zaza.openstack.charm_tests.vault.setup.auto_initialize
- zaza.openstack.charm_tests.glance.setup.add_lts_image
@ -37,7 +48,6 @@ comment: |
The repetition of ``prepare_payload_instance`` is intentional and gives the
test multiple backends to feed Octavia.
tests:
- zaza.openstack.charm_tests.neutron_openvswitch.tests.NeutronOpenvSwitchOverlayTest
- zaza.openstack.charm_tests.octavia.tests.LBAASv2Test
- zaza.openstack.charm_tests.octavia.tests.CharmOperationTest
- zaza.openstack.charm_tests.policyd.tests.OctaviaTests

View File

@ -185,7 +185,7 @@ class TestAPICrud(test_utils.PatchHelper):
result = api_crud.get_hm_port(identity_service,
'fake-unit-name',
'192.0.2.42',
ovs_hostname='fake-unit-name.fqdn')
host_id='fake-unit-name.fqdn')
nc.create_port.assert_called_once_with(
{
'port': {
@ -239,7 +239,7 @@ class TestAPICrud(test_utils.PatchHelper):
identity_service,
octavia_charm.local_unit_name,
octavia_charm.local_address,
ovs_hostname=None)
host_id=None)
self.check_output.assert_called_with(
['ip', 'link', 'show', api_crud.octavia.OCTAVIA_MGMT_INTF],
stderr=-2, universal_newlines=True)
@ -300,6 +300,8 @@ class TestAPICrud(test_utils.PatchHelper):
{'security_group': {'id': self.secgrp_uuid}},
{'security_group': {'id': self.health_secgrp_uuid}},
]
self.patch_object(api_crud, 'is_extension_enabled')
self.is_extension_enabled.return_value = True
result = api_crud.get_mgmt_network(identity_service)
nc.list_networks.assert_called_once_with(tags=resource_tag)
nc.create_network.assert_called_once_with({

View File

@ -51,7 +51,7 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks):
'setup_hm_port': (
'identity-service.available',
'neutron-api.available',
'neutron-openvswitch.connected',
'sdn-subordinate.available',
'amqp.available',),
'update_controller_ip_port_list': (
'leadership.is_leader',
@ -61,6 +61,14 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks):
'setup_endpoint_connection': (
'identity-service.connected',),
},
'when_any': {
'sdn_joined': ('neutron-openvswitch.connected',
'ovsdb-subordinate.available'),
},
'when_none': {
'sdn_broken': ('neutron-openvswitch.connected',
'ovsdb-subordinate.available'),
},
'when_not': {
'init_db': ('db.synced',),
'cluster_connected': ('ha.available',),
@ -125,8 +133,18 @@ class TestOctaviaHandlers(test_utils.PatchHelper):
self.setup_hm_port.assert_called_with(
self.endpoint_from_flag(),
self.octavia_charm,
ovs_hostname=self.endpoint_from_flag().host())
host_id=self.endpoint_from_flag().host())
self.set_flag.assert_called_once_with('config.changed')
self.setup_hm_port.reset_mock()
ovsdb_subordinate = mock.MagicMock()
identity_service = mock.MagicMock()
self.endpoint_from_flag.side_effect = [
None, ovsdb_subordinate, identity_service]
handlers.setup_hm_port()
self.setup_hm_port.assert_called_with(
identity_service,
self.octavia_charm,
host_id=ovsdb_subordinate.chassis_name)
def test_update_controller_ip_port_list(self):
self.patch('charms.reactive.endpoint_from_flag', 'endpoint_from_flag')