From 7c439073f39010ad3ac937b8c9726da0f27976b7 Mon Sep 17 00:00:00 2001 From: Monty Taylor Date: Sat, 2 Apr 2016 09:09:54 -0500 Subject: [PATCH] Flesh out netowrk config list Add support for indicating default_interface. Also, add some validation and normalization code, some interface methods and, shockingly, documentation. Change-Id: Ib45b68894585ac02821d5d2376510fd7a8e8ee40 --- README.rst | 2 +- doc/source/index.rst | 1 + doc/source/network-config.rst | 47 +++++++++++++++++++ os_client_config/cloud_config.py | 24 ++++++++++ os_client_config/config.py | 40 +++++++++++++++- os_client_config/tests/test_config.py | 36 +++++++++++++- .../notes/network-list-e6e9dafdd8446263.yaml | 5 +- 7 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 doc/source/network-config.rst diff --git a/README.rst b/README.rst index 15f4bf0..97a1584 100644 --- a/README.rst +++ b/README.rst @@ -277,7 +277,7 @@ To support this, the region list can actually be a list of dicts, and any setting that can be set at the cloud level can be overridden for that region. -:: +.. code-block:: yaml clouds: internap: diff --git a/doc/source/index.rst b/doc/source/index.rst index bf667b7..f7263c9 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -6,6 +6,7 @@ vendor-support contributing installation + network-config api-reference releasenotes diff --git a/doc/source/network-config.rst b/doc/source/network-config.rst new file mode 100644 index 0000000..9bbbf9d --- /dev/null +++ b/doc/source/network-config.rst @@ -0,0 +1,47 @@ +============== +Network Config +============== + +There are several different qualities that networks in OpenStack might have +that might not be able to be automatically inferred from the available +metadata. To help users navigate more complex setups, `os-client-config` +allows configuring a list of network metadata. + +.. code-block:: yaml + + clouds: + amazing: + networks: + - name: blue + routes_externally: true + - name: purple + routes_externally: true + default_interface: true + - name: green + routes_externally: false + - name: purple + routes_externally: false + nat_destination: true + +Every entry must have a name field, which can hold either the name or the id +of the network. + +`routes_externally` is a boolean field that labels the network as handling +north/south traffic off of the cloud. In a public cloud this might be thought +of as the "public" network, but in private clouds it's possible it might +be an RFC1918 address. In either case, it's provides IPs to servers that +things not on the cloud can use. This value defaults to `false`, which +indicates only servers on the same network can talk to it. + +`default_interface` is a boolean field that indicates that the network is the +one that programs should use. It defaults to false. An example of needing to +use this value is a cloud with two private networks, and where a user is +running ansible in one of the servers to talk to other servers on the private +network. Because both networks are private, there would otherwise be no way +to determine which one should be used for the traffic. + +`nat_destination` is a boolean field that indicates which network floating +ips should be attached to. It defaults to false. Normally this can be inferred +by looking for a network that has subnets that have a gateway_ip. But it's +possible to have more than one network that satisfies that condition, so the +user might want to tell programs which one to pick. diff --git a/os_client_config/cloud_config.py b/os_client_config/cloud_config.py index b19607e..e63bd12 100644 --- a/os_client_config/cloud_config.py +++ b/os_client_config/cloud_config.py @@ -438,3 +438,27 @@ class CloudConfig(object): if resource not in expiration: return default return float(expiration[resource]) + + def get_external_networks(self): + """Get list of network names for external networks.""" + return [ + net['name'] for net in self._openstack_config['networks'] + if net['routes_externally']] + + def get_internal_networks(self): + """Get list of network names for internal networks.""" + return [ + net['name'] for net in self._openstack_config['networks'] + if not net['routes_externally']] + + def get_default_network(self): + """Get network used for default interactions.""" + for net in self._openstack_config['networks']: + if net['default_interface']: + return net + + def get_nat_destination(self): + """Get network used for NAT destination.""" + for net in self._openstack_config['networks']: + if net['nat_destination']: + return net diff --git a/os_client_config/config.py b/os_client_config/config.py index 98870f6..2dd49c3 100644 --- a/os_client_config/config.py +++ b/os_client_config/config.py @@ -83,6 +83,8 @@ def set_default(key, value): def get_boolean(value): + if value is None: + return False if type(value) is bool: return value if value.lower() == 'true': @@ -486,10 +488,37 @@ class OpenStackConfig(object): or 'project_id' in cloud['auth'] or 'project_name' in cloud['auth']) + def _validate_networks(self, networks, key): + value = None + for net in networks: + if value and net[key]: + raise exceptions.OpenStackConfigException( + "Duplicate network entries for {key}: {net1} and {net2}." + " Only one network can be flagged with {key}".format( + key=key, + net1=value['name'], + net2=net['name'])) + if not value and net[key]: + value = net + def _fix_backwards_networks(self, cloud): # Leave the external_network and internal_network keys in the # dict because consuming code might be expecting them. - networks = cloud.get('networks', []) + networks = [] + # Normalize existing network entries + for net in cloud.get('networks', []): + name = net.get('name') + if not name: + raise exceptions.OpenStackConfigException( + 'Entry in network list is missing required field "name".') + network = dict( + name=name, + routes_externally=get_boolean(net.get('routes_externally')), + nat_destination=get_boolean(net.get('nat_destination')), + default_interface=get_boolean(net.get('default_interface')), + ) + networks.append(network) + for key in ('external_network', 'internal_network'): external = key.startswith('external') if key in cloud and 'networks' in cloud: @@ -505,7 +534,14 @@ class OpenStackConfig(object): key=key, name=cloud[key], external=external)) networks.append(dict( name=cloud[key], - routes_externally=external)) + routes_externally=external, + nat_destination=not external, + default_interface=external)) + + # Validate that we don't have duplicates + self._validate_networks(networks, 'nat_destination') + self._validate_networks(networks, 'default_interface') + cloud['networks'] = networks return cloud diff --git a/os_client_config/tests/test_config.py b/os_client_config/tests/test_config.py index 0db1457..7c2bec0 100644 --- a/os_client_config/tests/test_config.py +++ b/os_client_config/tests/test_config.py @@ -779,8 +779,40 @@ class TestBackwardsCompatibility(base.TestCase): 'external_network': 'public', 'internal_network': 'private', 'networks': [ - {'name': 'public', 'routes_externally': True}, - {'name': 'private', 'routes_externally': False}, + {'name': 'public', 'routes_externally': True, + 'nat_destination': False, 'default_interface': True}, + {'name': 'private', 'routes_externally': False, + 'nat_destination': True, 'default_interface': False}, ] } self.assertEqual(expected, result) + + def test_normalize_network(self): + c = config.OpenStackConfig(config_files=[self.cloud_yaml], + vendor_files=[self.vendor_yaml]) + cloud = { + 'networks': [ + {'name': 'private'} + ] + } + result = c._fix_backwards_networks(cloud) + expected = { + 'networks': [ + {'name': 'private', 'routes_externally': False, + 'nat_destination': False, 'default_interface': False}, + ] + } + self.assertEqual(expected, result) + + def test_single_default_interface(self): + c = config.OpenStackConfig(config_files=[self.cloud_yaml], + vendor_files=[self.vendor_yaml]) + cloud = { + 'networks': [ + {'name': 'blue', 'default_interface': True}, + {'name': 'purple', 'default_interface': True}, + ] + } + self.assertRaises( + exceptions.OpenStackConfigException, + c._fix_backwards_networks, cloud) diff --git a/releasenotes/notes/network-list-e6e9dafdd8446263.yaml b/releasenotes/notes/network-list-e6e9dafdd8446263.yaml index 8375488..8f793c2 100644 --- a/releasenotes/notes/network-list-e6e9dafdd8446263.yaml +++ b/releasenotes/notes/network-list-e6e9dafdd8446263.yaml @@ -3,9 +3,8 @@ features: - Support added for configuring metadata about networks for a cloud in a list of dicts, rather than in the external_network and internal_network entries. The dicts - support a name and a routes_externally field, as well as - any other arbitrary metadata needed by consuming - applications. + support a name, a routes_externally field, a nat_destination + field and a default_interface field. deprecations: - external_network and internal_network are deprecated and should be replaced with the list of network dicts.