Allow Juju AZ context information to be used

The change adds an option to the charm to use JUJU_AVAILABILITY_ZONE
environment variable set by Juju for the hook environment based on the
underlying provider's availability zone information for a given machine.

This information is used to configure the availability_zone setting for
Neutron DHCP and L3 agents specifically because they support it
and for other agents (because both neutron.conf and agent-specific
configuration files are loaded) such as metadata agents and lbaas
agents.

Additionally, a setting is added to allow changing the default
availability zone because 'nova' is a default value coming from the
Neutron defaults for agents.

Change-Id: I94303aa70ee3adc6ace0f9af1e7c4f5c0edbcdb5
Closes-Bug: #1796068
This commit is contained in:
Dmitrii Shcherbakov 2018-10-03 14:36:52 +01:00
parent 501bf14eb8
commit 71c0120d21
7 changed files with 211 additions and 5 deletions

View File

@ -271,3 +271,31 @@ options:
Only supported in OpenStack Newton and higher. For deployments of Queens or Only supported in OpenStack Newton and higher. For deployments of Queens or
later this value is ignored. Please set the corresponding value in the later this value is ignored. Please set the corresponding value in the
nova-cloud-controller charm. nova-cloud-controller charm.
default-availability-zone:
type: string
default: 'nova'
description: |
Default availability zone to use for agents (l3, dhcp) on this machine.
If this option is not set, the default availability zone 'nova' is used.
If customize-failure-domain is set to True, it will override this option
only if an AZ is set by the Juju provider. If JUJU_AVAILABILITY_ZONE is
not set, the value specified by this option will be used regardless of
customize-failure-domain's setting.
.
NOTE: Router and Network objects have a property called
availability_zone_hints which can be used to restrict dnsmasq
and router namespace placement by DHCP and L3 agents to specific
neutron availability zones. Neutron AZs are not tied to Nova AZs but
their names can match.
.
customize-failure-domain:
type: boolean
default: False
description: |
Juju propagates availability zone information to charms from the
underlying machine provider such as MAAS and this option allows the
charm to use JUJU_AVAILABILITY_ZONE to set default_availability_zone
for Neutron agents (DHCP and L3 agents). This option overrides the
default-availability-zone charm config setting only when the Juju
provider sets JUJU_AVAILABILITY_ZONE.

View File

@ -19,9 +19,10 @@ from charmhelpers.contrib.openstack.utils import (
os_release, os_release,
CompareOpenStackReleases, CompareOpenStackReleases,
) )
from charmhelpers.contrib.hahelpers.cluster import( from charmhelpers.contrib.hahelpers.cluster import (
eligible_leader eligible_leader
) )
from charmhelpers.contrib.network.ip import ( from charmhelpers.contrib.network.ip import (
get_address_in_network, get_address_in_network,
get_host_ip, get_host_ip,
@ -48,6 +49,11 @@ CORE_PLUGIN = {
} }
def _get_availability_zone():
from neutron_utils import get_availability_zone as get_az
return get_az()
def core_plugin(): def core_plugin():
return CORE_PLUGIN[config('plugin')] return CORE_PLUGIN[config('plugin')]
@ -122,6 +128,7 @@ class NeutronGatewayContext(NeutronAPIContext):
'report_interval': api_settings['report_interval'], 'report_interval': api_settings['report_interval'],
'enable_metadata_network': config('enable-metadata-network'), 'enable_metadata_network': config('enable-metadata-network'),
'enable_isolated_metadata': config('enable-isolated-metadata'), 'enable_isolated_metadata': config('enable-isolated-metadata'),
'availability_zone': _get_availability_zone(),
} }
ctxt['local_ip'] = get_local_ip() ctxt['local_ip'] = get_local_ip()
@ -197,6 +204,7 @@ class NovaMetadataContext(OSContextGenerator):
ctxt['nova_metadata_protocol'] = 'http' ctxt['nova_metadata_protocol'] = 'http'
return ctxt return ctxt
SHARED_SECRET = "/etc/{}/secret.txt" SHARED_SECRET = "/etc/{}/secret.txt"

View File

@ -87,6 +87,7 @@ from copy import deepcopy
def valid_plugin(): def valid_plugin():
return config('plugin') in CORE_PLUGIN return config('plugin') in CORE_PLUGIN
NEUTRON_COMMON = 'neutron-common' NEUTRON_COMMON = 'neutron-common'
VERSION_PACKAGE = NEUTRON_COMMON VERSION_PACKAGE = NEUTRON_COMMON
@ -301,6 +302,7 @@ def determine_l3ha_packages():
def use_l3ha(): def use_l3ha():
return NeutronAPIContext()()['enable_l3ha'] return NeutronAPIContext()()['enable_l3ha']
EXT_PORT_CONF = '/etc/init/ext-port.conf' EXT_PORT_CONF = '/etc/init/ext-port.conf'
PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf' PHY_NIC_MTU_CONF = '/etc/init/os-charm-phy-nic-mtu.conf'
STOPPED_SERVICES = ['os-charm-phy-nic-mtu', 'ext-port'] STOPPED_SERVICES = ['os-charm-phy-nic-mtu', 'ext-port']
@ -1050,6 +1052,7 @@ def configure_apparmor():
for profile in profiles: for profile in profiles:
context.AppArmorContext(profile).setup_aa_profile() context.AppArmorContext(profile).setup_aa_profile()
VENDORDATA_FILE = '/etc/nova/vendor_data.json' VENDORDATA_FILE = '/etc/nova/vendor_data.json'
@ -1062,3 +1065,10 @@ def write_vendordata(vdata):
with open(VENDORDATA_FILE, 'w') as vdata_file: with open(VENDORDATA_FILE, 'w') as vdata_file:
vdata_file.write(json.dumps(json_vdata, sort_keys=True, indent=2)) vdata_file.write(json.dumps(json_vdata, sort_keys=True, indent=2))
return True return True
def get_availability_zone():
use_juju_az = config('customize-failure-domain')
juju_az = os.environ.get('JUJU_AVAILABILITY_ZONE')
return (juju_az if use_juju_az and juju_az
else config('default-availability-zone'))

View File

@ -16,6 +16,7 @@ rpc_response_timeout = {{ rpc_response_timeout }}
[agent] [agent]
root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf
report_interval = {{ report_interval }} report_interval = {{ report_interval }}
{% include "parts/agent" %}
{% include "section-rabbitmq-oslo" %} {% include "section-rabbitmq-oslo" %}

3
templates/parts/agent Normal file
View File

@ -0,0 +1,3 @@
{% if availability_zone -%}
availability_zone = {{ availability_zone }}
{% endif -%}

View File

@ -147,11 +147,12 @@ class TestNeutronGatewayContext(CharmTestCase):
self.config.side_effect = self.test_config.get self.config.side_effect = self.test_config.get
self.maxDiff = None self.maxDiff = None
@patch('neutron_utils.config')
@patch('charmhelpers.contrib.openstack.context.relation_get') @patch('charmhelpers.contrib.openstack.context.relation_get')
@patch('charmhelpers.contrib.openstack.context.related_units') @patch('charmhelpers.contrib.openstack.context.related_units')
@patch('charmhelpers.contrib.openstack.context.relation_ids') @patch('charmhelpers.contrib.openstack.context.relation_ids')
@patch.object(neutron_contexts, 'get_shared_secret') @patch.object(neutron_contexts, 'get_shared_secret')
def test_all(self, _secret, _rids, _runits, _rget): def test_all(self, _secret, _rids, _runits, _rget, mock_config):
rdata = {'l2-population': 'True', rdata = {'l2-population': 'True',
'enable-dvr': 'True', 'enable-dvr': 'True',
'overlay-network-type': 'gre', 'overlay-network-type': 'gre',
@ -169,6 +170,14 @@ class TestNeutronGatewayContext(CharmTestCase):
self.test_config.set('vlan-ranges', self.test_config.set('vlan-ranges',
'physnet1:1000:2000 physnet2:2001:3000') 'physnet1:1000:2000 physnet2:2001:3000')
self.test_config.set('flat-network-providers', 'physnet3 physnet4') self.test_config.set('flat-network-providers', 'physnet3 physnet4')
def config_side_effect(key):
return {
'customize-failure-domain': False,
'default-availability-zone': 'nova',
}[key]
mock_config.side_effect = config_side_effect
self.network_get_primary_address.side_effect = NotImplementedError self.network_get_primary_address.side_effect = NotImplementedError
self.unit_get.return_value = '10.5.0.1' self.unit_get.return_value = '10.5.0.1'
# Provided by neutron-api relation # Provided by neutron-api relation
@ -204,14 +213,17 @@ class TestNeutronGatewayContext(CharmTestCase):
'dnsmasq_flags': { 'dnsmasq_flags': {
'dhcp-userclass': 'set:ipxe,iPXE', 'dhcp-userclass': 'set:ipxe,iPXE',
'dhcp-match': 'set:ipxe,175' 'dhcp-match': 'set:ipxe,175'
} },
'availability_zone': 'nova',
}) })
@patch('neutron_utils.config')
@patch('charmhelpers.contrib.openstack.context.relation_get') @patch('charmhelpers.contrib.openstack.context.relation_get')
@patch('charmhelpers.contrib.openstack.context.related_units') @patch('charmhelpers.contrib.openstack.context.related_units')
@patch('charmhelpers.contrib.openstack.context.relation_ids') @patch('charmhelpers.contrib.openstack.context.relation_ids')
@patch.object(neutron_contexts, 'get_shared_secret') @patch.object(neutron_contexts, 'get_shared_secret')
def test_all_network_spaces(self, _secret, _rids, _runits, _rget): def test_all_network_spaces(self, _secret, _rids, _runits, _rget,
mock_config):
rdata = {'l2-population': 'True', rdata = {'l2-population': 'True',
'enable-dvr': 'True', 'enable-dvr': 'True',
'overlay-network-type': 'gre', 'overlay-network-type': 'gre',
@ -228,6 +240,14 @@ class TestNeutronGatewayContext(CharmTestCase):
self.test_config.set('vlan-ranges', self.test_config.set('vlan-ranges',
'physnet1:1000:2000 physnet2:2001:3000') 'physnet1:1000:2000 physnet2:2001:3000')
self.test_config.set('flat-network-providers', 'physnet3 physnet4') self.test_config.set('flat-network-providers', 'physnet3 physnet4')
def config_side_effect(key):
return {
'customize-failure-domain': False,
'default-availability-zone': 'nova',
}[key]
mock_config.side_effect = config_side_effect
self.network_get_primary_address.return_value = '192.168.20.2' self.network_get_primary_address.return_value = '192.168.20.2'
self.unit_get.return_value = '10.5.0.1' self.unit_get.return_value = '10.5.0.1'
# Provided by neutron-api relation # Provided by neutron-api relation
@ -263,7 +283,8 @@ class TestNeutronGatewayContext(CharmTestCase):
'dnsmasq_flags': { 'dnsmasq_flags': {
'dhcp-userclass': 'set:ipxe,iPXE', 'dhcp-userclass': 'set:ipxe,iPXE',
'dhcp-match': 'set:ipxe,175' 'dhcp-match': 'set:ipxe,175'
} },
'availability_zone': 'nova',
}) })
@patch('charmhelpers.contrib.openstack.context.relation_get') @patch('charmhelpers.contrib.openstack.context.relation_get')
@ -293,6 +314,84 @@ class TestNeutronGatewayContext(CharmTestCase):
self.assertTrue(ctxt['enable_isolated_metadata']) self.assertTrue(ctxt['enable_isolated_metadata'])
self.assertTrue(ctxt['enable_metadata_network']) self.assertTrue(ctxt['enable_metadata_network'])
@patch('neutron_utils.config')
@patch('os.environ.get')
@patch('charmhelpers.contrib.openstack.context.relation_get')
@patch('charmhelpers.contrib.openstack.context.related_units')
@patch('charmhelpers.contrib.openstack.context.relation_ids')
@patch.object(neutron_contexts, 'get_shared_secret')
def test_availability_zone_no_juju_with_env(self, _secret, _rids,
_runits, _rget,
mock_get,
mock_config):
def environ_get_side_effect(key):
return {
'JUJU_AVAILABILITY_ZONE': 'az1',
'PATH': 'foobar',
}[key]
mock_get.side_effect = environ_get_side_effect
def config_side_effect(key):
return {
'customize-failure-domain': False,
'default-availability-zone': 'nova',
}[key]
mock_config.side_effect = config_side_effect
context = neutron_contexts.NeutronGatewayContext()
self.assertEqual(
'nova', context()['availability_zone'])
@patch('neutron_utils.config')
@patch('os.environ.get')
@patch('charmhelpers.contrib.openstack.context.relation_get')
@patch('charmhelpers.contrib.openstack.context.related_units')
@patch('charmhelpers.contrib.openstack.context.relation_ids')
@patch.object(neutron_contexts, 'get_shared_secret')
def test_availability_zone_no_juju_no_env(self, _secret, _rids,
_runits, _rget,
mock_get, mock_config):
def environ_get_side_effect(key):
return {
'JUJU_AVAILABILITY_ZONE': '',
'PATH': 'foobar',
}[key]
mock_get.side_effect = environ_get_side_effect
def config_side_effect(key):
return {
'customize-failure-domain': False,
'default-availability-zone': 'nova',
}[key]
mock_config.side_effect = config_side_effect
context = neutron_contexts.NeutronGatewayContext()
self.assertEqual(
'nova', context()['availability_zone'])
@patch('neutron_utils.config')
@patch('os.environ.get')
@patch('charmhelpers.contrib.openstack.context.relation_get')
@patch('charmhelpers.contrib.openstack.context.related_units')
@patch('charmhelpers.contrib.openstack.context.relation_ids')
@patch.object(neutron_contexts, 'get_shared_secret')
def test_availability_zone_juju(self, _secret, _rids,
_runits, _rget,
mock_get, mock_config):
def environ_get_side_effect(key):
return {
'JUJU_AVAILABILITY_ZONE': 'az1',
'PATH': 'foobar',
}[key]
mock_get.side_effect = environ_get_side_effect
mock_config.side_effect = self.test_config.get
self.test_config.set('customize-failure-domain', True)
context = neutron_contexts.NeutronGatewayContext()
self.assertEqual(
'az1', context()['availability_zone'])
class TestSharedSecret(CharmTestCase): class TestSharedSecret(CharmTestCase):

View File

@ -865,6 +865,62 @@ class TestNeutronUtils(CharmTestCase):
with patch_open() as (_open, _file): with patch_open() as (_open, _file):
self.assertEqual(neutron_utils.write_vendordata(_jdata), False) self.assertEqual(neutron_utils.write_vendordata(_jdata), False)
@patch.object(neutron_utils.os.environ, 'get')
def test_get_az_customize_with_env(self, os_environ_get_mock):
self.config.side_effect = self.test_config.get
self.test_config.set('customize-failure-domain', True)
self.test_config.set('default-availability-zone', 'nova')
def os_environ_get_side_effect(key):
return {
'JUJU_AVAILABILITY_ZONE': 'az1',
}[key]
os_environ_get_mock.side_effect = os_environ_get_side_effect
az = neutron_utils.get_availability_zone()
self.assertEqual('az1', az)
@patch.object(neutron_utils.os.environ, 'get')
def test_get_az_customize_without_env(self, os_environ_get_mock):
self.config.side_effect = self.test_config.get
self.test_config.set('customize-failure-domain', True)
self.test_config.set('default-availability-zone', 'mynova')
def os_environ_get_side_effect(key):
return {
'JUJU_AVAILABILITY_ZONE': '',
}[key]
os_environ_get_mock.side_effect = os_environ_get_side_effect
az = neutron_utils.get_availability_zone()
self.assertEqual('mynova', az)
@patch.object(neutron_utils.os.environ, 'get')
def test_get_az_no_customize_without_env(self, os_environ_get_mock):
self.config.side_effect = self.test_config.get
self.test_config.set('customize-failure-domain', False)
self.test_config.set('default-availability-zone', 'nova')
def os_environ_get_side_effect(key):
return {
'JUJU_AVAILABILITY_ZONE': '',
}[key]
os_environ_get_mock.side_effect = os_environ_get_side_effect
az = neutron_utils.get_availability_zone()
self.assertEqual('nova', az)
@patch.object(neutron_utils.os.environ, 'get')
def test_get_az_no_customize_with_env(self, os_environ_get_mock):
self.config.side_effect = self.test_config.get
self.test_config.set('customize-failure-domain', False)
self.test_config.set('default-availability-zone', 'nova')
def os_environ_get_side_effect(key):
return {
'JUJU_AVAILABILITY_ZONE': 'az1',
}[key]
os_environ_get_mock.side_effect = os_environ_get_side_effect
az = neutron_utils.get_availability_zone()
self.assertEqual('nova', az)
network_context = { network_context = {
'service_username': 'foo', 'service_username': 'foo',
'service_password': 'bar', 'service_password': 'bar',
@ -893,6 +949,7 @@ class DummyExternalPortContext():
def __call__(self): def __call__(self):
return self.return_value return self.return_value
cluster1 = ['cluster1-machine1.internal'] cluster1 = ['cluster1-machine1.internal']
cluster2 = ['cluster2-machine1.internal', 'cluster2-machine2.internal' cluster2 = ['cluster2-machine1.internal', 'cluster2-machine2.internal'
'cluster2-machine3.internal'] 'cluster2-machine3.internal']