From c9bfc45844ecbd00b0050aaa0042d4799b1a691a Mon Sep 17 00:00:00 2001 From: Trygve Vea Date: Tue, 21 Nov 2017 19:58:18 +0100 Subject: [PATCH] Implement availability_zone_hints for networks and routers. Adds an optional parameter to the create_network and create_router-methods, for use with availability zone-scheduling of network agents. Change-Id: Ifb93a10415dc676f5cc56b5315f2dff24fc395b8 --- ...ility_zone_extension-675c2460ebb50a09.yaml | 8 ++++ shade/openstackcloud.py | 27 +++++++++++- shade/tests/unit/test_network.py | 40 ++++++++++++++++++ shade/tests/unit/test_router.py | 42 +++++++++++++++++++ 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/neutron_availability_zone_extension-675c2460ebb50a09.yaml diff --git a/releasenotes/notes/neutron_availability_zone_extension-675c2460ebb50a09.yaml b/releasenotes/notes/neutron_availability_zone_extension-675c2460ebb50a09.yaml new file mode 100644 index 000000000..80e52f328 --- /dev/null +++ b/releasenotes/notes/neutron_availability_zone_extension-675c2460ebb50a09.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + availability_zone_hints now accepted for create_network() when + network_availability_zone extension is enabled on target cloud. + - | + availability_zone_hints now accepted for create_router() when + router_availability_zone extension is enabled on target cloud. diff --git a/shade/openstackcloud.py b/shade/openstackcloud.py index 651dc0d1f..3a0ad8685 100644 --- a/shade/openstackcloud.py +++ b/shade/openstackcloud.py @@ -3383,7 +3383,8 @@ class OpenStackCloud( return True def create_network(self, name, shared=False, admin_state_up=True, - external=False, provider=None, project_id=None): + external=False, provider=None, project_id=None, + availability_zone_hints=None): """Create a network. :param string name: Name of the network being created. @@ -3395,6 +3396,7 @@ class OpenStackCloud( { 'network_type': 'vlan', 'segmentation_id': 'vlan1' } :param string project_id: Specify the project ID this network will be created on (admin-only). + :param list availability_zone_hints: A list of availability zone hints. :returns: The network object. :raises: OpenStackCloudException on operation error. @@ -3410,6 +3412,16 @@ class OpenStackCloud( if project_id is not None: network['tenant_id'] = project_id + if availability_zone_hints is not None: + if not isinstance(availability_zone_hints, list): + raise OpenStackCloudException( + "Parameter 'availability_zone_hints' must be a list") + if not self._has_neutron_extension('network_availability_zone'): + raise OpenStackCloudUnavailableExtension( + 'network_availability_zone extension is not available on ' + 'target cloud') + network['availability_zone_hints'] = availability_zone_hints + if provider: if not isinstance(provider, dict): raise OpenStackCloudException( @@ -4245,7 +4257,8 @@ class OpenStackCloud( def create_router(self, name=None, admin_state_up=True, ext_gateway_net_id=None, enable_snat=None, - ext_fixed_ips=None, project_id=None): + ext_fixed_ips=None, project_id=None, + availability_zone_hints=None): """Create a logical router. :param string name: The router name. @@ -4263,6 +4276,7 @@ class OpenStackCloud( } ] :param string project_id: Project ID for the router. + :param list availability_zone_hints: A list of availability zone hints. :returns: The router object. :raises: OpenStackCloudException on operation error. @@ -4279,6 +4293,15 @@ class OpenStackCloud( ) if ext_gw_info: router['external_gateway_info'] = ext_gw_info + if availability_zone_hints is not None: + if not isinstance(availability_zone_hints, list): + raise OpenStackCloudException( + "Parameter 'availability_zone_hints' must be a list") + if not self._has_neutron_extension('router_availability_zone'): + raise OpenStackCloudUnavailableExtension( + 'router_availability_zone extension is not available on ' + 'target cloud') + router['availability_zone_hints'] = availability_zone_hints data = self._network_client.post( "/routers.json", json={"router": router}, diff --git a/shade/tests/unit/test_network.py b/shade/tests/unit/test_network.py index 96af721bf..bf7275d3f 100644 --- a/shade/tests/unit/test_network.py +++ b/shade/tests/unit/test_network.py @@ -47,6 +47,16 @@ class TestNetwork(base.RequestsMockTestCase): 'mtu': 0 } + network_availability_zone_extension = { + "alias": "network_availability_zone", + "updated": "2015-01-01T10:00:00-00:00", + "description": "Availability zone support for router.", + "links": [], + "name": "Network Availability Zone" + } + + enabled_neutron_extensions = [network_availability_zone_extension] + def test_list_networks(self): net1 = {'id': '1', 'name': 'net1'} net2 = {'id': '2', 'name': 'net2'} @@ -151,6 +161,27 @@ class TestNetwork(base.RequestsMockTestCase): self.assertEqual(mock_new_network_rep, network) self.assert_calls() + def test_create_network_with_availability_zone_hints(self): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'extensions.json']), + json={'extensions': self.enabled_neutron_extensions}), + dict(method='POST', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'networks.json']), + json={'network': self.mock_new_network_rep}, + validate=dict( + json={'network': { + 'admin_state_up': True, + 'name': 'netname', + 'availability_zone_hints': ['nova']}})) + ]) + network = self.cloud.create_network("netname", + availability_zone_hints=['nova']) + self.assertEqual(self.mock_new_network_rep, network) + self.assert_calls() + def test_create_network_provider_ignored_value(self): provider_opts = {'physical_network': 'mynet', 'network_type': 'vlan', @@ -180,6 +211,15 @@ class TestNetwork(base.RequestsMockTestCase): self.assertEqual(mock_new_network_rep, network) self.assert_calls() + def test_create_network_wrong_availability_zone_hints_type(self): + azh_opts = "invalid" + with testtools.ExpectedException( + shade.OpenStackCloudException, + "Parameter 'availability_zone_hints' must be a list" + ): + self.cloud.create_network("netname", + availability_zone_hints=azh_opts) + def test_create_network_provider_wrong_type(self): provider_opts = "invalid" with testtools.ExpectedException( diff --git a/shade/tests/unit/test_router.py b/shade/tests/unit/test_router.py index f969b909a..ee5f20feb 100644 --- a/shade/tests/unit/test_router.py +++ b/shade/tests/unit/test_router.py @@ -14,6 +14,7 @@ # limitations under the License. import copy +import testtools from shade import exc from shade.tests.unit import base @@ -52,6 +53,16 @@ class TestRouter(base.RequestsMockTestCase): 'request_ids': ['req-f1b0b1b4-ae51-4ef9-b371-0cc3c3402cf7'] } + router_availability_zone_extension = { + "alias": "router_availability_zone", + "updated": "2015-01-01T10:00:00-00:00", + "description": "Availability zone support for router.", + "links": [], + "name": "Router Availability Zone" + } + + enabled_neutron_extensions = [router_availability_zone_extension] + def test_get_router(self): self.register_uris([ dict(method='GET', @@ -112,6 +123,27 @@ class TestRouter(base.RequestsMockTestCase): project_id=new_router_tenant_id) self.assert_calls() + def test_create_router_with_availability_zone_hints(self): + self.register_uris([ + dict(method='GET', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'extensions.json']), + json={'extensions': self.enabled_neutron_extensions}), + dict(method='POST', + uri=self.get_mock_url( + 'network', 'public', append=['v2.0', 'routers.json']), + json={'router': self.mock_router_rep}, + validate=dict( + json={'router': { + 'name': self.router_name, + 'admin_state_up': True, + 'availability_zone_hints': ['nova']}})) + ]) + self.cloud.create_router( + name=self.router_name, admin_state_up=True, + availability_zone_hints=['nova']) + self.assert_calls() + def test_create_router_with_enable_snat_True(self): """Do not send enable_snat when same as neutron default.""" self.register_uris([ @@ -145,6 +177,16 @@ class TestRouter(base.RequestsMockTestCase): name=self.router_name, admin_state_up=True, enable_snat=False) self.assert_calls() + def test_create_router_wrong_availability_zone_hints_type(self): + azh_opts = "invalid" + with testtools.ExpectedException( + exc.OpenStackCloudException, + "Parameter 'availability_zone_hints' must be a list" + ): + self.cloud.create_router( + name=self.router_name, admin_state_up=True, + availability_zone_hints=azh_opts) + def test_add_router_interface(self): self.register_uris([ dict(method='PUT',