From b5896d6fbbcd0d16e7c3d26c50426585e008d20e Mon Sep 17 00:00:00 2001 From: Trygve Vea Date: Sun, 22 Oct 2017 10:51:21 +0000 Subject: Implement setting availability zone hint on network creation If the 'network_availability_zone'-extension is enabled, this patch adds a field to the Create Network-workflow named 'Availability Zone Hints'. Change-Id: Ic4206d5765c2d01afedf0e64f9aa50ffce76b661 Closes-Bug: #1725617 --- .../dashboards/admin/networks/forms.py | 22 ++++++ .../dashboards/admin/networks/tests.py | 69 ++++++++++++++++++ .../dashboards/admin/networks/workflows.py | 2 +- .../dashboards/project/networks/tests.py | 85 ++++++++++++++++++++-- .../dashboards/project/networks/workflows.py | 24 +++++- 5 files changed, 195 insertions(+), 7 deletions(-) diff --git a/openstack_dashboard/dashboards/admin/networks/forms.py b/openstack_dashboard/dashboards/admin/networks/forms.py index ae5dbca..b95bff2 100644 --- a/openstack_dashboard/dashboards/admin/networks/forms.py +++ b/openstack_dashboard/dashboards/admin/networks/forms.py @@ -139,6 +139,12 @@ class CreateNetwork(forms.SelfHandlingForm): }), initial=True, required=False) + az_hints = forms.MultipleChoiceField( + label=_("Availability Zone Hints"), + required=False, + help_text=_("Availability zones where the DHCP agents may be " + "scheduled. Leaving this unset is equivalent to " + "selecting all availability zones")) @classmethod def _instantiate(cls, request, *args, **kwargs): @@ -237,6 +243,20 @@ class CreateNetwork(forms.SelfHandlingForm): else: self.fields['network_type'].choices = network_type_choices + try: + if api.neutron.is_extension_supported(request, + 'network_availability_zone'): + zones = api.neutron.list_availability_zones( + self.request, 'network', 'available') + self.fields['az_hints'].choices = [(zone['name'], zone['name']) + for zone in zones] + else: + del self.fields['az_hints'] + except Exception: + msg = _('Failed to get availability zone list.') + messages.warning(request, msg) + del self.fields['az_hints'] + def _hide_provider_network_type(self): self.fields['network_type'].widget = forms.HiddenInput() self.fields['physical_network'].widget = forms.HiddenInput() @@ -261,6 +281,8 @@ class CreateNetwork(forms.SelfHandlingForm): if network_type in self.nettypes_with_seg_id: params['provider:segmentation_id'] = ( data['segmentation_id']) + if 'az_hints' in data and data['az_hints']: + params['availability_zone_hints'] = data['az_hints'] network = api.neutron.network_create(request, **params) LOG.debug('Network %s was successfully created.', data['name']) return network diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py index f3e4312..34f64af 100644 --- a/openstack_dashboard/dashboards/admin/networks/tests.py +++ b/openstack_dashboard/dashboards/admin/networks/tests.py @@ -464,6 +464,9 @@ class NetworkTests(test.BaseAdminViewTests): api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider').\ MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + MultipleTimes().AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ MultipleTimes().AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -486,6 +489,57 @@ class NetworkTests(test.BaseAdminViewTests): self.assertRedirectsNoFollow(res, INDEX_URL) @test.create_stubs({api.neutron: ('network_create', + 'is_extension_supported', + 'list_availability_zones', + 'subnetpool_list'), + api.keystone: ('tenant_list',)}) + def test_network_create_post_with_az(self): + tenants = self.tenants.list() + tenant_id = self.tenants.first().id + network = self.networks.first() + + api.keystone.tenant_list(IsA(http.HttpRequest))\ + .AndReturn([tenants, False]) + params = {'name': network.name, + 'tenant_id': tenant_id, + 'admin_state_up': network.admin_state_up, + 'router:external': True, + 'shared': True, + 'provider:network_type': 'local', + 'with_subnet': False, + 'az_hints': ['nova']} + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider').\ + MultipleTimes().AndReturn(True) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + MultipleTimes().AndReturn(True) + api.neutron.list_availability_zones(IsA(http.HttpRequest), + "network", "available")\ + .AndReturn(self.neutron_availability_zones.list()) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'subnet_allocation').\ + MultipleTimes().AndReturn(True) + api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ + AndReturn(self.subnetpools.list()) + api.neutron.network_create(IsA(http.HttpRequest), **params)\ + .AndReturn(network) + + self.mox.ReplayAll() + + form_data = {'tenant_id': tenant_id, + 'name': network.name, + 'admin_state': network.admin_state_up, + 'external': True, + 'shared': True, + 'network_type': 'local', + 'availability_zone_hints': ['nova']} + url = reverse('horizon:admin:networks:create') + res = self.client.post(url, form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, INDEX_URL) + + @test.create_stubs({api.neutron: ('network_create', 'subnet_create', 'is_extension_supported', 'subnetpool_list'), @@ -509,6 +563,9 @@ class NetworkTests(test.BaseAdminViewTests): api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider').\ MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + MultipleTimes().AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ MultipleTimes().AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -552,6 +609,9 @@ class NetworkTests(test.BaseAdminViewTests): api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider').\ MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + MultipleTimes().AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ MultipleTimes().AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -581,6 +641,9 @@ class NetworkTests(test.BaseAdminViewTests): api.keystone.tenant_list( IsA(http.HttpRequest) ).MultipleTimes().AndReturn([tenants, False]) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + MultipleTimes().AndReturn(False) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider')\ .MultipleTimes().AndReturn(True) @@ -611,6 +674,9 @@ class NetworkTests(test.BaseAdminViewTests): IsA(http.HttpRequest) ).MultipleTimes().AndReturn([tenants, False]) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + MultipleTimes().AndReturn(False) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider').\ MultipleTimes().AndReturn(True) self.mox.ReplayAll() @@ -642,6 +708,9 @@ class NetworkTests(test.BaseAdminViewTests): IsA(http.HttpRequest) ).MultipleTimes().AndReturn([tenants, False]) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + MultipleTimes().AndReturn(False) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider')\ .MultipleTimes().AndReturn(True) diff --git a/openstack_dashboard/dashboards/admin/networks/workflows.py b/openstack_dashboard/dashboards/admin/networks/workflows.py index ff98b33..d8925ae 100644 --- a/openstack_dashboard/dashboards/admin/networks/workflows.py +++ b/openstack_dashboard/dashboards/admin/networks/workflows.py @@ -44,7 +44,7 @@ class CreateNetworkInfoAction(network_workflows.CreateNetworkInfoAction): class CreateNetworkInfo(network_workflows.CreateNetworkInfo): action_class = CreateNetworkInfoAction - contributes = ("net_name", "admin_state", "with_subnet") + contributes = ("net_name", "admin_state", "with_subnet", "az_hints") def __init__(self, workflow): self.contributes = tuple(workflow.create_network_form.fields.keys()) diff --git a/openstack_dashboard/dashboards/project/networks/tests.py b/openstack_dashboard/dashboards/project/networks/tests.py index ac097fe..bd2c860 100644 --- a/openstack_dashboard/dashboards/project/networks/tests.py +++ b/openstack_dashboard/dashboards/project/networks/tests.py @@ -363,6 +363,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'subnetpool_list')}) def test_network_create_get(self): api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -389,6 +392,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'admin_state_up': network.admin_state_up, 'shared': False} api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -411,6 +417,43 @@ class NetworkTests(test.TestCase, NetworkStubMixin): @test.create_stubs({api.neutron: ('network_create', 'is_extension_supported', + 'list_availability_zones', + 'subnetpool_list')}) + def test_network_create_post_with_az(self): + network = self.networks.first() + params = {'name': network.name, + 'admin_state_up': network.admin_state_up, + 'shared': False, + 'az_hints': ['nova']} + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(True) + api.neutron.list_availability_zones(IsA(http.HttpRequest), + "network", "available")\ + .AndReturn(self.neutron_availability_zones.list()) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'subnet_allocation').\ + AndReturn(True) + api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ + AndReturn(self.subnetpools.list()) + api.neutron.network_create(IsA(http.HttpRequest), + **params).AndReturn(network) + self.mox.ReplayAll() + + form_data = {'net_name': network.name, + 'admin_state': network.admin_state_up, + 'shared': False, + 'with_subnet': False, + 'availability_zone_hints': ['nova']} + form_data.update(form_data_no_subnet()) + url = reverse('horizon:project:networks:create') + res = self.client.post(url, form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, INDEX_URL) + + @test.create_stubs({api.neutron: ('network_create', + 'is_extension_supported', 'subnetpool_list')}) def test_network_create_post_with_shared(self): network = self.networks.first() @@ -418,6 +461,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'admin_state_up': network.admin_state_up, 'shared': True} api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -459,6 +505,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): subnet.ip_version = 4 subnet_params['ip_version'] = subnet.ip_version api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -493,6 +542,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'shared': False, 'admin_state_up': network.admin_state_up} api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -516,16 +568,16 @@ class NetworkTests(test.TestCase, NetworkStubMixin): @test.create_stubs({api.neutron: ('network_create', 'is_extension_supported', 'subnetpool_list')}) - def test_network_create_post_with_subnet_network_exception( - self, - test_with_subnetpool=False, - ): + def test_network_create_post_with_subnet_network_exception(self): network = self.networks.first() subnet = self.subnets.first() params = {'name': network.name, 'shared': False, 'admin_state_up': network.admin_state_up} api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -557,6 +609,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'shared': False, 'admin_state_up': network.admin_state_up} api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -593,6 +648,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): network = self.networks.first() subnet = self.subnets.first() api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -628,6 +686,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): network = self.networks.first() subnet = self.subnets.first() api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -666,6 +727,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): subnet = self.subnets.first() api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -708,6 +772,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): subnet_v6 = self.subnets.list()[4] api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -758,6 +825,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): 'enable_dhcp': subnet.enable_dhcp} api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -792,6 +862,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): subnet = self.subnets.first() api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ @@ -828,7 +901,9 @@ class NetworkTests(test.TestCase, NetworkStubMixin): ): network = self.networks.first() subnet = self.subnets.first() - + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'network_availability_zone').\ + AndReturn(False) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'subnet_allocation').\ AndReturn(True) diff --git a/openstack_dashboard/dashboards/project/networks/workflows.py b/openstack_dashboard/dashboards/project/networks/workflows.py index 8cde19e..58a3974 100644 --- a/openstack_dashboard/dashboards/project/networks/workflows.py +++ b/openstack_dashboard/dashboards/project/networks/workflows.py @@ -58,12 +58,31 @@ class CreateNetworkInfoAction(workflows.Action): }), initial=True, required=False) + az_hints = forms.MultipleChoiceField( + label=_("Availability Zone Hints"), + required=False, + help_text=_("Availability zones where the DHCP agents may be " + "scheduled. Leaving this unset is equivalent to " + "selecting all availability zones")) def __init__(self, request, *args, **kwargs): super(CreateNetworkInfoAction, self).__init__(request, *args, **kwargs) if not policy.check((("network", "create_network:shared"),), request): self.fields['shared'].widget = forms.HiddenInput() + try: + if api.neutron.is_extension_supported(request, + 'network_availability_zone'): + zones = api.neutron.list_availability_zones( + self.request, 'network', 'available') + self.fields['az_hints'].choices = [(zone['name'], zone['name']) + for zone in zones] + else: + del self.fields['az_hints'] + except Exception: + msg = _('Failed to get availability zone list.') + messages.warning(request, msg) + del self.fields['az_hints'] class Meta(object): name = _("Network") @@ -74,7 +93,8 @@ class CreateNetworkInfoAction(workflows.Action): class CreateNetworkInfo(workflows.Step): action_class = CreateNetworkInfoAction - contributes = ("net_name", "admin_state", "with_subnet", "shared") + contributes = ("net_name", "admin_state", "with_subnet", "shared", + "az_hints") class CreateSubnetInfoAction(workflows.Action): @@ -463,6 +483,8 @@ class CreateNetwork(workflows.Workflow): params = {'name': data['net_name'], 'admin_state_up': data['admin_state'], 'shared': data['shared']} + if 'az_hints' in data and data['az_hints']: + params['availability_zone_hints'] = data['az_hints'] network = api.neutron.network_create(request, **params) self.context['net_id'] = network.id LOG.debug('Network "%s" was successfully created.', -- cgit v1.1