diff --git a/README.md b/README.md index af4d8d85..f40fd4f8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Overview +# Overview This principle charm provides the OpenStack Neutron API service which was previously provided by the nova-cloud-controller charm. @@ -59,7 +59,7 @@ of the above hostnames may be set. The charm will throw an exception in the following circumstances: If neither 'vip' nor 'dns-ha' is set and the charm is related to hacluster If both 'vip' and 'dns-ha' are set as they are mutually -exclusive. If 'dns-ha' is set and none of the +exclusive. If 'dns-ha' is set and none of the os-{admin,internal,public}-hostname(s) are set # Restrictions @@ -91,6 +91,34 @@ list of configured nameservers. For more information refer to the OpenStack documentation on [DNS Integration](https://docs.openstack.org/ocata/networking-guide/config-dns-int.html). +# External DNS for Cloud Guests + +To add support for DNS record auto-generation when Neutron ports and +floating IPs are created the charm needs a relation with designate charm: + + juju deploy designate + juju add-relation neutron-api designate + +In order to enable the creation of reverse lookup (PTR) records, enable +"allow-reverse-dns-lookup" charm option: + + juju config neutron-api allow-reverse-dns-lookup=True + +and configure the following charm options: + + juju config neutron-api ipv4-ptr-zone-prefix-size= + juju config neutron-api ipv6-ptr-zone-prefix-size= + +For example, if prefix sizes of your IPv4 and IPv6 subnets are +"24" (e.g. "192.168.0.0/24") and "64" (e.g. "fdcd:06ca:e498:216b::/64") +respectively, configure the charm options as follows: + + juju config neutron-api ipv4-ptr-zone-prefix-size=24 + juju config neutron-api ipv6-ptr-zone-prefix-size=64 + +For more information refer to the OpenStack documentation on +[DNS Integration](https://docs.openstack.org/ocata/networking-guide/config-dns-int.html) + # Network Space support This charm supports the use of Juju Network Spaces, allowing the charm @@ -119,7 +147,7 @@ bundle configuration: internal: internal-space shared-db: internal-space -NOTE: Spaces must be configured in the underlying provider prior to +NOTE: Spaces must be configured in the underlying provider prior to attempting to use them. NOTE: Existing deployments using os-*-network configuration options @@ -155,7 +183,7 @@ repository docs/index.txt, "Config Format" section: https://bitbucket.org/ianb/pastedeploy Classes in loadwsgi.py contain config_prefixes that can be used for -middleware types - these are the prefixes the charm code validates +middleware types - these are the prefixes the charm code validates passed data against: https://bitbucket.org/ianb/pastedeploy/src/4b27133a2a7db58b213ae55b580039c11d2055c0/paste/deploy/loadwsgi.py?at=default&fileviewer=file-view-default diff --git a/config.yaml b/config.yaml index 526811c2..87f6f33b 100755 --- a/config.yaml +++ b/config.yaml @@ -692,3 +692,25 @@ options: Repository from which to install Calico packages. If set, must be a PPA URL, of the form ppa:somecustom/ppa. Changing this value after installation will force an immediate software upgrade. + reverse-dns-lookup: + type: boolean + default: False + description: | + A boolean value specifying whether to enable or not the creation of + reverse lookup (PTR) records. + . + NOTE: Use only when relating neutron-api charm to designate charm. + ipv4-ptr-zone-prefix-size: + type: int + default: 24 + description: | + The size in bits of the prefix for the IPv4 reverse lookup (PTR) zones. + . + NOTE: Use only when "reverse-dns-lookup" option is set to "True". + ipv6-ptr-zone-prefix-size: + type: int + default: 64 + description: | + The size in bits of the prefix for the IPv6 reverse lookup (PTR) zones. + . + NOTE: Use only when "reverse-dns-lookup" option is set to "True". diff --git a/hooks/external-dns-relation-broken b/hooks/external-dns-relation-broken new file mode 120000 index 00000000..1fb10fd5 --- /dev/null +++ b/hooks/external-dns-relation-broken @@ -0,0 +1 @@ +neutron_api_hooks.py \ No newline at end of file diff --git a/hooks/external-dns-relation-changed b/hooks/external-dns-relation-changed new file mode 120000 index 00000000..1fb10fd5 --- /dev/null +++ b/hooks/external-dns-relation-changed @@ -0,0 +1 @@ +neutron_api_hooks.py \ No newline at end of file diff --git a/hooks/external-dns-relation-departed b/hooks/external-dns-relation-departed new file mode 120000 index 00000000..1fb10fd5 --- /dev/null +++ b/hooks/external-dns-relation-departed @@ -0,0 +1 @@ +neutron_api_hooks.py \ No newline at end of file diff --git a/hooks/external-dns-relation-joined b/hooks/external-dns-relation-joined new file mode 120000 index 00000000..1fb10fd5 --- /dev/null +++ b/hooks/external-dns-relation-joined @@ -0,0 +1 @@ +neutron_api_hooks.py \ No newline at end of file diff --git a/hooks/neutron_api_context.py b/hooks/neutron_api_context.py index 8a3b5e2d..4b7ae3fe 100644 --- a/hooks/neutron_api_context.py +++ b/hooks/neutron_api_context.py @@ -400,15 +400,23 @@ class NeutronCCContext(context.NeutronContext): if vni_ranges: ctxt['vni_ranges'] = ','.join(vni_ranges.split()) - extension_drivers = [] - if config('enable-ml2-port-security'): - extension_drivers.append(EXTENSION_DRIVER_PORT_SECURITY) + enable_dns_extension_driver = False dns_domain = get_dns_domain() if dns_domain: - extension_drivers.append(EXTENSION_DRIVER_DNS) + enable_dns_extension_driver = True ctxt['dns_domain'] = dns_domain + if cmp_release >= 'mitaka': + for rid in relation_ids('external-dns'): + if related_units(rid): + enable_dns_extension_driver = True + + extension_drivers = [] + if config('enable-ml2-port-security'): + extension_drivers.append(EXTENSION_DRIVER_PORT_SECURITY) + if enable_dns_extension_driver: + extension_drivers.append(EXTENSION_DRIVER_DNS) if is_qos_requested_and_valid(): extension_drivers.append(EXTENSION_DRIVER_QOS) @@ -707,3 +715,25 @@ class NeutronAMQPContext(context.AMQPContext): context = super(NeutronAMQPContext, self).__call__() context['notification_topics'] = ','.join(NOTIFICATION_TOPICS) return context + + +class DesignateContext(context.OSContextGenerator): + interfaces = ['external-dns'] + + def __call__(self): + ctxt = {} + for rid in relation_ids('external-dns'): + if related_units(rid): + for unit in related_units(rid): + rdata = relation_get(rid=rid, unit=unit) + ctxt['designate_endpoint'] = rdata.get('endpoint') + if ctxt.get('designate_endpoint') is not None: + ctxt['enable_designate'] = True + allow_reverse_dns_lookup = config('reverse-dns-lookup') + ctxt['allow_reverse_dns_lookup'] = allow_reverse_dns_lookup + if allow_reverse_dns_lookup: + ctxt['ipv4_ptr_zone_prefix_size'] = ( + config('ipv4-ptr-zone-prefix-size')) + ctxt['ipv6_ptr_zone_prefix_size'] = ( + config('ipv6-ptr-zone-prefix-size')) + return ctxt diff --git a/hooks/neutron_api_hooks.py b/hooks/neutron_api_hooks.py index e7573fae..09ca2e34 100755 --- a/hooks/neutron_api_hooks.py +++ b/hooks/neutron_api_hooks.py @@ -669,6 +669,15 @@ def midonet_changed(): CONFIGS.write_all() +@hooks.hook('external-dns-relation-joined', + 'external-dns-relation-changed', + 'external-dns-relation-departed', + 'external-dns-relation-broken') +@restart_on_change(restart_map()) +def designate_changed(): + CONFIGS.write_all() + + @hooks.hook('update-status') @harden() @harden() diff --git a/hooks/neutron_api_utils.py b/hooks/neutron_api_utils.py index 125a5902..199329b8 100755 --- a/hooks/neutron_api_utils.py +++ b/hooks/neutron_api_utils.py @@ -193,7 +193,8 @@ BASE_RESOURCE_MAP = OrderedDict([ context.BindHostContext(), context.WorkerConfigContext(), context.InternalEndpointContext('neutron-common'), - context.MemcacheContext()], + context.MemcacheContext(), + neutron_api_context.DesignateContext()], }), (NEUTRON_DEFAULT, { 'services': ['neutron-server'], diff --git a/metadata.yaml b/metadata.yaml index f7e99999..fa9ddd2d 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -50,6 +50,8 @@ requires: interface: etcd-proxy midonet: interface: midonet + external-dns: + interface: designate peers: cluster: interface: neutron-api-ha diff --git a/templates/mitaka/neutron.conf b/templates/mitaka/neutron.conf index fd855508..12cc75b8 100644 --- a/templates/mitaka/neutron.conf +++ b/templates/mitaka/neutron.conf @@ -69,6 +69,10 @@ notify_nova_on_port_data_changes = True global_physnet_mtu = {{ global_physnet_mtu }} {% endif -%} +{% if enable_designate -%} +external_dns_driver = designate +{% endif -%} + {% include "section-zeromq" %} [quotas] @@ -109,3 +113,7 @@ root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf lock_path = $state_path/lock {% include "parts/section-nova" %} + +{% if enable_designate -%} +{% include "parts/section-designate" %} +{% endif -%} diff --git a/templates/newton/neutron.conf b/templates/newton/neutron.conf index a70569dc..c7f39bf7 100644 --- a/templates/newton/neutron.conf +++ b/templates/newton/neutron.conf @@ -69,6 +69,10 @@ notify_nova_on_port_data_changes = True global_physnet_mtu = {{ global_physnet_mtu }} {% endif -%} +{% if enable_designate -%} +external_dns_driver = designate +{% endif -%} + {% include "section-zeromq" %} [quotas] @@ -110,6 +114,10 @@ lock_path = $state_path/lock {% include "parts/section-nova" %} +{% if enable_designate -%} +{% include "parts/section-designate" %} +{% endif -%} + [service_providers] service_provider = LOADBALANCERV2:Haproxy:neutron_lbaas.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default service_provider = VPN:strongswan:neutron_vpnaas.services.vpn.service_drivers.ipsec.IPsecVPNDriver:default diff --git a/templates/parts/section-designate b/templates/parts/section-designate new file mode 100644 index 00000000..aaeb97b6 --- /dev/null +++ b/templates/parts/section-designate @@ -0,0 +1,11 @@ +[designate] +url = {{ designate_endpoint }}/v2 +admin_auth_url = {{ auth_protocol }}://{{ auth_host }}:{{ auth_port }} +admin_username = {{ admin_user }} +admin_password = {{ admin_password }} +admin_tenant_name = {{ admin_tenant_name }} +allow_reverse_dns_lookup = {{ allow_reverse_dns_lookup }} +{% if allow_reverse_dns_lookup -%} +ipv4_ptr_zone_prefix_size = {{ ipv4_ptr_zone_prefix_size }} +ipv6_ptr_zone_prefix_size = {{ ipv6_ptr_zone_prefix_size }} +{% endif -%} diff --git a/templates/pike/neutron.conf b/templates/pike/neutron.conf index c51527ba..b3d5ed05 100644 --- a/templates/pike/neutron.conf +++ b/templates/pike/neutron.conf @@ -69,6 +69,10 @@ notify_nova_on_port_data_changes = True global_physnet_mtu = {{ global_physnet_mtu }} {% endif -%} +{% if enable_designate -%} +external_dns_driver = designate +{% endif -%} + {% include "section-zeromq" %} [quotas] @@ -110,5 +114,9 @@ lock_path = $state_path/lock {% include "parts/section-nova" %} +{% if enable_designate -%} +{% include "parts/section-designate" %} +{% endif -%} + [service_providers] service_provider = LOADBALANCERV2:Haproxy:neutron_lbaas.drivers.haproxy.plugin_driver.HaproxyOnHostPluginDriver:default diff --git a/unit_tests/test_neutron_api_context.py b/unit_tests/test_neutron_api_context.py index b82796f8..8fa56fb2 100644 --- a/unit_tests/test_neutron_api_context.py +++ b/unit_tests/test_neutron_api_context.py @@ -1114,3 +1114,50 @@ class MidonetContextTest(CharmTestCase): 'midonet_api_port': '8080'} self.assertEqual(expect, ctxt) + + +class DesignateContextTest(CharmTestCase): + + def setUp(self): + super(DesignateContextTest, self).setUp(context, TO_PATCH) + self.relation_get.side_effect = self.test_relation.get + + def tearDown(self): + super(DesignateContextTest, self).tearDown() + + def test_designate_no_related_units(self): + self.related_units.return_value = [] + ctxt = context.DesignateContext()() + expect = {} + + self.assertEquals(expect, ctxt) + + def test_designate_related_units_no_reverse_dns_lookup(self): + self.config.side_effect = self.test_config.get + self.related_units.return_value = ['unit1'] + self.relation_ids.return_value = ['rid1'] + self.test_relation.set({'endpoint': 'http://1.1.1.1:9001'}) + self.test_config.set('reverse-dns-lookup', False) + ctxt = context.DesignateContext()() + expect = {'enable_designate': True, + 'designate_endpoint': 'http://1.1.1.1:9001', + 'allow_reverse_dns_lookup': False} + + self.assertEquals(expect, ctxt) + + def test_designate_related_units_and_reverse_dns_lookup(self): + self.config.side_effect = self.test_config.get + self.related_units.return_value = ['unit1'] + self.relation_ids.return_value = ['rid1'] + self.test_relation.set({'endpoint': 'http://1.1.1.1:9001'}) + self.test_config.set('reverse-dns-lookup', True) + self.test_config.set('ipv4-ptr-zone-prefix-size', 24) + self.test_config.set('ipv6-ptr-zone-prefix-size', 64) + ctxt = context.DesignateContext()() + expect = {'enable_designate': True, + 'designate_endpoint': 'http://1.1.1.1:9001', + 'allow_reverse_dns_lookup': True, + 'ipv4_ptr_zone_prefix_size': 24, + 'ipv6_ptr_zone_prefix_size': 64} + + self.assertEquals(expect, ctxt) diff --git a/unit_tests/test_neutron_api_hooks.py b/unit_tests/test_neutron_api_hooks.py index a0008a68..47a7f79f 100644 --- a/unit_tests/test_neutron_api_hooks.py +++ b/unit_tests/test_neutron_api_hooks.py @@ -965,3 +965,15 @@ class NeutronAPIHooksTests(CharmTestCase): self.assertTrue(self.CONFIGS.register.called) self.CONFIGS.write.assert_any_call('/etc/init/etcd.conf') self.CONFIGS.write.assert_any_call('/etc/default/etcd') + + def test_designate_peer_joined(self): + self.test_relation.set({ + 'endpoint': 'http://1.2.3.4:9001', + }) + self.relation_ids.side_effect = self._fake_relids + self._call_hook('external-dns-relation-joined') + self.assertTrue(self.CONFIGS.write.called_with(NEUTRON_CONF)) + + def test_designate_peer_departed(self): + self._call_hook('external-dns-relation-departed') + self.assertTrue(self.CONFIGS.write.called_with(NEUTRON_CONF))