From d1428c5fc562c6032c2a2ee810df8e07d09766f4 Mon Sep 17 00:00:00 2001 From: Cameron Kolodjski Date: Fri, 22 Apr 2022 15:28:13 +0000 Subject: [PATCH] Create share network panel workflows Implements: bp share-network-subnets Co-Authored-By: Cameron Kolodjski Co-Authored-By: Shkoh Hamasoor Co-Authored-By: melakualehegn Change-Id: I4c539b151cbbf69e4a2a3580906b2a8bd5f3a452 Signed-off-by: Goutham Pacha Ravi --- .gitignore | 1 + manila_ui/api/manila.py | 7 +- manila_ui/api/network.py | 24 --- .../dashboards/admin/share_networks/tables.py | 8 - .../templates/share_networks/_detail.html | 101 ++++++++---- .../dashboards/admin/share_networks/views.py | 16 +- .../project/share_networks/forms.py | 3 +- .../project/share_networks/tables.py | 10 +- .../templates/share_networks/_detail.html | 113 ++++++++----- .../project/share_networks/views.py | 59 ++++--- .../project/share_networks/workflows.py | 150 +++++++++++++++++- manila_ui/tests/api/test_manila.py | 1 + .../dashboards/admin/share_networks/tests.py | 69 +++++--- .../project/share_networks/tests.py | 112 ++++++++----- .../tests/dashboards/project/test_data.py | 46 +++++- ...hare-network-subnets-82ad8c601caf177b.yaml | 8 + 16 files changed, 492 insertions(+), 236 deletions(-) delete mode 100644 manila_ui/api/network.py create mode 100644 releasenotes/notes/bp-share-network-subnets-82ad8c601caf177b.yaml diff --git a/.gitignore b/.gitignore index 19724abb..4ca0afed 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ lib64 pip-log.txt # Unit test / coverage reports +coverage.xml .coverage .tox nosetests.xml diff --git a/manila_ui/api/manila.py b/manila_ui/api/manila.py index a41c9ec8..4cb58079 100644 --- a/manila_ui/api/manila.py +++ b/manila_ui/api/manila.py @@ -28,7 +28,7 @@ from manilaclient import client as manila_client LOG = logging.getLogger(__name__) MANILA_UI_USER_AGENT_REPR = "manila_ui_plugin_for_horizon" -MANILA_VERSION = "2.50" +MANILA_VERSION = "2.51" MANILA_SERVICE_TYPE = "sharev2" # API static values @@ -302,10 +302,11 @@ def share_network_list(request, detailed=False, search_opts=None): def share_network_create(request, neutron_net_id=None, neutron_subnet_id=None, - name=None, description=None): + name=None, description=None, availability_zone=None): return manilaclient(request).share_networks.create( neutron_net_id=neutron_net_id, neutron_subnet_id=neutron_subnet_id, - name=name, description=description) + name=name, description=description, + availability_zone=availability_zone) def share_network_get(request, share_net_id): diff --git a/manila_ui/api/network.py b/manila_ui/api/network.py deleted file mode 100644 index 201d8d76..00000000 --- a/manila_ui/api/network.py +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2015 Mirantis, Inc. -# All Rights Reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from openstack_dashboard.api import neutron - - -def network_list(request): - return neutron.network_list(request) - - -def network_get(request, net_id): - return neutron.network_get(request, net_id) diff --git a/manila_ui/dashboards/admin/share_networks/tables.py b/manila_ui/dashboards/admin/share_networks/tables.py index 3fb89c9e..657b2858 100644 --- a/manila_ui/dashboards/admin/share_networks/tables.py +++ b/manila_ui/dashboards/admin/share_networks/tables.py @@ -21,14 +21,6 @@ class ShareNetworksTable(tables.DataTable): "name", verbose_name=_("Name"), link="horizon:admin:share_networks:share_network_detail") project = tables.Column("project_name", verbose_name=_("Project")) - neutron_net = tables.Column("neutron_net", verbose_name=_("Neutron Net")) - neutron_subnet = tables.Column( - "neutron_subnet", verbose_name=_("Neutron Subnet")) - ip_version = tables.Column("ip_version", verbose_name=_("IP Version")) - network_type = tables.Column( - "network_type", verbose_name=_("Network Type")) - segmentation_id = tables.Column( - "segmentation_id", verbose_name=_("Segmentation Id")) def get_object_display(self, share_network): return share_network.name or str(share_network.id) diff --git a/manila_ui/dashboards/admin/share_networks/templates/share_networks/_detail.html b/manila_ui/dashboards/admin/share_networks/templates/share_networks/_detail.html index 60e43273..b000eb0d 100644 --- a/manila_ui/dashboards/admin/share_networks/templates/share_networks/_detail.html +++ b/manila_ui/dashboards/admin/share_networks/templates/share_networks/_detail.html @@ -1,53 +1,90 @@ {% load i18n sizeformat parse_date %} -

{% trans "Share Network Overview" %}

-
-
+
{% trans "Name" %}
{{ share_network.name }}
-
{% trans "ID" %}
-
{{ share_network.id }}
{% if share_network.description %} -
{% trans "Description" %}
-
{{ share_network.description }}
+
{% trans "Description" %}
+
{{ share_network.description }}
{% endif %} - {% if share_network.share_servers %} -
{% trans "Share Servers" %}
- {% for server in share_network.share_servers %} - {% url 'horizon:admin:share_servers:share_server_detail' server.id as server_url %} -
{{ server.id }}
- {% endfor %} + {% if share_network.created_at %} +
{% trans "Created At" %}
+
{{ share_network.created_at }}
{% endif %} + {% if share_network.updated_at %} +
{% trans "Updated At" %}
+
{{ share_network.updated_at }}
+ {% endif %} +
{% trans "Share Network ID" %}
+
{{ share_network.id }}
+
{% trans "Project ID" %}
+
{{ share_network.project_id }}
-
-

{% trans "Net Details" %}

+

{% trans "Subnets" %}


-
{% trans "Network" %}
- {% if share_network.neutron_net %} -
{{ share_network.neutron_net }}
-
{% trans "Subnet" %}
-
{{ share_network.neutron_subnet}}
- {% endif %} +
+ {% for subnet in share_network.share_network_subnets %} +
+
{% trans "Id" %}
+
+ {{subnet.id}} +
+ {% if subnet.neutron_net != "Unknown" %} + {% url 'horizon:admin:networks:detail' subnet.neutron_net_id as network_detail_url %} +
{% trans "Neutron Network" %}
+
+ {{subnet.neutron_net}} +
+ {% endif %} + {% if subnet.neutron_subnet != "Unknown" %} + {% url 'horizon:admin:networks:subnets:detail' subnet.neutron_subnet_id as subnet_detail_url %} +
{% trans "Neutron Subnet" %}
+
+ {{subnet.neutron_subnet}} +
+ {% endif %} +
{% trans "Availability Zone" %}
+
+ {{subnet.availability_zone}} +
+
+ {% endfor %} +
- -
-

{% trans "Security Services" %}

-
- {% for sec_service in share_network.sec_services %} - {% url 'horizon:admin:security_services:security_service_detail' sec_service.id as sec_service_url%} -
+{% if share_network.share_servers %} +
+

{% trans "Share Servers" %}

+
+
+ {% for server in share_network.share_servers %} +
    +{% url 'horizon:admin:share_servers:share_server_detail' server.id as server_url %} +
  • {{server.id }}
  • +
+ {% endfor %} +
+
+{% endif %} +{% if share_network.sec_services %} +
+

{% trans "Security Services" %}

+
+ {% for sec_service in share_network.sec_services %} +{% url 'horizon:admin:security_services:security_service_detail' sec_service.id as sec_service_url%} +
{% trans "Id" %}
{{ sec_service.id }}
{% trans "Name" %}
{{ sec_service.name }}
{% trans "Type" %}
{{ sec_service.type }}
-
-
- {% endfor %} -
+
+
+ {% endfor %} +
+{% endif %} diff --git a/manila_ui/dashboards/admin/share_networks/views.py b/manila_ui/dashboards/admin/share_networks/views.py index 13a7dbeb..3345b778 100644 --- a/manila_ui/dashboards/admin/share_networks/views.py +++ b/manila_ui/dashboards/admin/share_networks/views.py @@ -21,8 +21,6 @@ from django.utils.translation import gettext_lazy as _ from horizon import exceptions from horizon import tables from horizon.utils import memoized -from openstack_dashboard.api import base -from openstack_dashboard.api import neutron from manila_ui.api import manila from manila_ui.dashboards.admin.share_networks import tables as sn_tables @@ -42,19 +40,7 @@ class ShareNetworksView(tables.MultiTableView): def get_share_networks_data(self): try: share_networks = manila.share_network_list( - self.request, detailed=True, search_opts={'all_tenants': True}) - if base.is_service_enabled(self.request, 'network'): - neutron_net_names = dict( - (net.id, net.name) - for net in neutron.network_list(self.request)) - neutron_subnet_names = dict( - (net.id, net.name) - for net in neutron.subnet_list(self.request)) - for sn in share_networks: - sn.neutron_net = neutron_net_names.get( - sn.neutron_net_id) or sn.neutron_net_id or "-" - sn.neutron_subnet = neutron_subnet_names.get( - sn.neutron_subnet_id) or sn.neutron_subnet_id or "-" + self.request, detailed=True, search_opts={"all_tenants": True}) except Exception: share_networks = [] exceptions.handle( diff --git a/manila_ui/dashboards/project/share_networks/forms.py b/manila_ui/dashboards/project/share_networks/forms.py index fa4facc3..1d68dfbe 100644 --- a/manila_ui/dashboards/project/share_networks/forms.py +++ b/manila_ui/dashboards/project/share_networks/forms.py @@ -22,7 +22,6 @@ from openstack_dashboard.api import base from openstack_dashboard.api import neutron from manila_ui.api import manila -from manila_ui.api import network from manila_ui.dashboards import utils @@ -34,8 +33,8 @@ class Create(forms.SelfHandlingForm): def __init__(self, request, *args, **kwargs): super(Create, self).__init__(request, *args, **kwargs) self.neutron_enabled = base.is_service_enabled(request, 'network') - net_choices = network.network_list(request) if self.neutron_enabled: + net_choices = neutron.network_list(request) self.fields['neutron_net_id'] = forms.ChoiceField( choices=[(' ', ' ')] + [(utils.transform_dashed_name(choice.id), diff --git a/manila_ui/dashboards/project/share_networks/tables.py b/manila_ui/dashboards/project/share_networks/tables.py index e538d2e5..38d76ba8 100644 --- a/manila_ui/dashboards/project/share_networks/tables.py +++ b/manila_ui/dashboards/project/share_networks/tables.py @@ -109,14 +109,8 @@ class ShareNetworksTable(tables.DataTable): name = tables.WrappingColumn( "name", verbose_name=_("Name"), link="horizon:project:share_networks:share_network_detail") - neutron_net = tables.Column("neutron_net", verbose_name=_("Neutron Net")) - neutron_subnet = tables.Column( - "neutron_subnet", verbose_name=_("Neutron Subnet")) - ip_version = tables.Column("ip_version", verbose_name=_("IP Version")) - network_type = tables.Column( - "network_type", verbose_name=_("Network Type")) - segmentation_id = tables.Column( - "segmentation_id", verbose_name=_("Segmentation Id")) + description = tables.WrappingColumn( + "description", verbose_name=_("Description")) def get_object_display(self, share_network): return share_network.name or str(share_network.id) diff --git a/manila_ui/dashboards/project/share_networks/templates/share_networks/_detail.html b/manila_ui/dashboards/project/share_networks/templates/share_networks/_detail.html index af009757..fca09c0d 100644 --- a/manila_ui/dashboards/project/share_networks/templates/share_networks/_detail.html +++ b/manila_ui/dashboards/project/share_networks/templates/share_networks/_detail.html @@ -1,65 +1,90 @@ {% load i18n sizeformat parse_date %} -

{% trans "Share Network Overview" %}

-
-
+
{% trans "Name" %}
{{ share_network.name }}
-
{% trans "ID" %}
-
{{ share_network.id }}
{% if share_network.description %} -
{% trans "Description" %}
-
{{ share_network.description }}
+
{% trans "Description" %}
+
{{ share_network.description }}
{% endif %} + {% if share_network.created_at %} +
{% trans "Created At" %}
+
{{ share_network.created_at }}
+ {% endif %} + {% if share_network.updated_at %} +
{% trans "Updated At" %}
+
{{ share_network.updated_at }}
+ {% endif %} +
{% trans "Share Network ID" %}
+
{{ share_network.id }}
+
{% trans "Project ID" %}
+
{{ share_network.project_id }}
-
-

{% trans "Network Details" %}

+

{% trans "Subnets" %}


-
{% trans "Network" %}
- {% if share_network.neutron_net %} -
{{ share_network.neutron_net }}
-
{% trans "Subnet" %}
-
{{ share_network.neutron_subnet}}
- {% endif %} +
+ {% for subnet in share_network.share_network_subnets %} +
+
{% trans "Id" %}
+
+ {{subnet.id}} +
+ {% if subnet.neutron_net != "Unknown" %} + {% url 'horizon:project:networks:detail' subnet.neutron_net_id as network_detail_url %} +
{% trans "Neutron Network" %}
+
+ {{subnet.neutron_net}} +
+ {% endif %} + {% if subnet.neutron_subnet != "Unknown" %} + {% url 'horizon:project:networks:subnets:detail' subnet.neutron_subnet_id as subnet_detail_url %} +
{% trans "Neutron Subnet" %}
+
+ {{subnet.neutron_subnet}} +
+ {% endif %} +
{% trans "Availability Zone" %}
+
+ {{subnet.availability_zone}} +
+
+ {% endfor %} +
- - {% if share_network.share_servers %} -
-

{% trans "Share Servers" %}

-
-
- {% for server in share_network.share_servers %} -
    - {% url 'horizon:admin:share_servers:share_server_detail' server.id as server_url %} -
  • {{ server.id }}
  • -
- {% endfor %} -
-
+
+

{% trans "Share Servers" %}

+
+
+ {% for server in share_network.share_servers %} +
    +{% url 'horizon:admin:share_servers:share_server_detail' server.id as server_url %} +
  • {{server.id }}
  • +
+ {% endfor %} +
+
{% endif %} - - {% if share_network.sec_services %} -
-

{% trans "Security Services" %}

-
- {% for sec_service in share_network.sec_services %} - {% url 'horizon:project:security_services:security_service_detail' sec_service.id as sec_service_url%} +
+

{% trans "Security Services" %}

+
+ {% for sec_service in share_network.sec_services %} +{% url 'horizon:project:security_services:security_service_detail' sec_service.id as sec_service_url%}
-
{% trans "Id" %}
-
{{ sec_service.id }}
-
{% trans "Name" %}
-
{{ sec_service.name }}
-
{% trans "Type" %}
-
{{ sec_service.type }}
+
{% trans "Id" %}
+
{{ sec_service.id }}
+
{% trans "Name" %}
+
{{ sec_service.name }}
+
{% trans "Type" %}
+
{{ sec_service.type }}

- {% endfor %} -
+ {% endfor %} +
{% endif %} diff --git a/manila_ui/dashboards/project/share_networks/views.py b/manila_ui/dashboards/project/share_networks/views.py index 3e8e5ba6..7e0a2a1b 100644 --- a/manila_ui/dashboards/project/share_networks/views.py +++ b/manila_ui/dashboards/project/share_networks/views.py @@ -15,7 +15,6 @@ from django.urls import reverse_lazy from django.utils.translation import gettext_lazy as _ from horizon import exceptions -from horizon import forms from horizon import tables from horizon import tabs from horizon.utils import memoized @@ -24,7 +23,6 @@ from openstack_dashboard.api import base from openstack_dashboard.api import neutron from manila_ui.api import manila -from manila_ui.dashboards.project.share_networks import forms as sn_forms from manila_ui.dashboards.project.share_networks import tables as sn_tables from manila_ui.dashboards.project.share_networks import tabs as sn_tabs import manila_ui.dashboards.project.share_networks.workflows as sn_workflows @@ -43,16 +41,6 @@ class ShareNetworksView(tables.MultiTableView): try: share_networks = manila.share_network_list( self.request, detailed=True) - if base.is_service_enabled(self.request, 'network'): - neutron_net_names = dict((net.id, net.name) for net in - neutron.network_list(self.request)) - neutron_subnet_names = dict((net.id, net.name) for net in - neutron.subnet_list(self.request)) - for sn in share_networks: - sn.neutron_net = neutron_net_names.get( - sn.neutron_net_id) or sn.neutron_net_id or "-" - sn.neutron_subnet = neutron_subnet_names.get( - sn.neutron_subnet_id) or sn.neutron_subnet_id or "-" except Exception: share_networks = [] exceptions.handle( @@ -75,13 +63,9 @@ class Update(workflows.WorkflowView): return context -class Create(forms.ModalFormView): - form_class = sn_forms.Create +class Create(workflows.WorkflowView): + workflow_class = sn_workflows.CreateShareNetworkWorkflow form_id = "create_share_network" - template_name = 'project/share_networks/create.html' - modal_header = _("Create Share Network") - modal_id = "create_share_network_modal" - submit_label = _("Create") submit_url = reverse_lazy( "horizon:project:share_networks:share_network_create") success_url = reverse_lazy("horizon:project:share_networks:index") @@ -110,19 +94,32 @@ class Detail(tabs.TabView): share_net_id = self.kwargs['share_network_id'] share_net = manila.share_network_get(self.request, share_net_id) if base.is_service_enabled(self.request, 'network'): - try: - share_net.neutron_net = neutron.network_get( - self.request, share_net.neutron_net_id).name_or_id - except ( - neutron.neutron_client.exceptions.NeutronClientException): - share_net.neutron_net = _("Unknown") - try: - share_net.neutron_subnet = neutron.subnet_get( - self.request, share_net.neutron_subnet_id).name_or_id - except ( - neutron.neutron_client.exceptions.NeutronClientException): - share_net.neutron_subnet = _("Unknown") - + for subnet in share_net.share_network_subnets: + # Neutron Net ID + try: + subnet["neutron_net"] = neutron.network_get( + self.request, subnet["neutron_net_id"]).name_or_id + except ( + neutron.neutron_client.exceptions + .NeutronClientException + ): + subnet["neutron_net"] = _("Unknown") + # Neutron Subnet ID + try: + subnet["neutron_subnet"] = neutron.subnet_get( + self.request, + subnet["neutron_subnet_id"]).name_or_id + except ( + neutron.neutron_client.exceptions + .NeutronClientException + ): + subnet["neutron_subnet"] = _("Unknown") + # List all azs if availability_zone is None + availability_zones = manila.availability_zone_list(self.request) + az_list = ", ".join([az.name for az in availability_zones]) + for subnet in share_net.share_network_subnets: + if subnet["availability_zone"] is None: + subnet["availability_zone"] = az_list share_net.sec_services = ( manila.share_network_security_service_list( self.request, share_net_id)) diff --git a/manila_ui/dashboards/project/share_networks/workflows.py b/manila_ui/dashboards/project/share_networks/workflows.py index 44db3ee5..171a6067 100644 --- a/manila_ui/dashboards/project/share_networks/workflows.py +++ b/manila_ui/dashboards/project/share_networks/workflows.py @@ -12,12 +12,160 @@ from django.utils.translation import gettext_lazy as _ - from horizon import exceptions from horizon import forms +from horizon import messages from horizon import workflows +from openstack_dashboard import api +from openstack_dashboard.api import base from manila_ui.api import manila +from manila_ui.dashboards import utils + + +class CreateShareNetworkInfoAction(workflows.Action): + share_network_name = forms.CharField( + max_length=255, label=_("Name"), required=True) + share_network_description = forms.CharField( + widget=forms.Textarea, label=_("Description"), required=False) + + class Meta(object): + name = ("Share Network") + + +class CreateShareNetworkInfoStep(workflows.Step): + action_class = CreateShareNetworkInfoAction + contributes = ("share_network_description", + "share_network_name") + + +class AddShareNetworkSubnetAction(workflows.MembershipAction): + + availability_zone = forms.ChoiceField( + required=False, + label=_('Availability Zone'), + widget=forms.ThemableSelectWidget(attrs={ + 'data-availability_zone': _('Availability Zone')})) + + neutron_net_id = forms.ChoiceField( + required=False, + label=_('Neutron Net'), + widget=forms.ThemableSelectWidget(attrs={ + 'class': 'switchable', + 'data-slug': 'neutron_net_id', + 'data-neutron_net_id': _('Neutron Net')})) + + class Meta(object): + name = _("Subnet") + help_text = _("Specify an Availability Zone or an existing subnet. " + "If no details are specified, " + "then a default subnet with a null Availability " + "Zone will be created automatically.") + + def __init__(self, request, context, *args, **kwargs): + super().__init__(request, context, *args, **kwargs) + + self.fields['availability_zone'].choices = ( + self.get_availability_zone_choices(request) + ) + self.neutron_enabled = base.is_service_enabled(request, 'network') + if self.neutron_enabled: + try: + self.fields['neutron_net_id'].choices, networks = ( + self.get_neutron_net_id_choices(request) + ) + except Exception: + msg = _('Unable to initialize neutron networks.') + exceptions.handle(request, msg) + try: + self.get_neutron_subnet_id_choices(request, networks) + except Exception: + msg = _('Unable to initialize neutron subnets.') + exceptions.handle(request, msg) + + def get_availability_zone_choices(self, request): + availability_zone_choices = [('', _('None'))] + + for availability_zone in manila.availability_zone_list(request): + availability_zone_choices.append( + (availability_zone.id, availability_zone.name) + ) + return availability_zone_choices + + def get_neutron_net_id_choices(self, request): + net_choices = [('', _('None'))] + + networks = api.neutron.network_list(request) + for network in networks: + net_choices.append((utils.transform_dashed_name(network.id), + network.name_or_id)) + return net_choices, networks + + def get_neutron_subnet_id_choices(self, request, networks): + for net in networks: + subnet_field_name = ( + 'subnet-choices-%s' % utils.transform_dashed_name(net.id)) + data_net_id = ( + 'data-neutron_net_id-%s' % utils.transform_dashed_name(net.id)) + subnet_field = forms.ChoiceField( + required=False, + choices=(), + label=_('Neutron Subnet'), + widget=forms.ThemableSelectWidget(attrs={ + 'class': 'switched', + 'data-switch-on': 'neutron_net_id', + data_net_id: _('Neutron Subnet')})) + self.fields[subnet_field_name] = subnet_field + subnet_choices = api.neutron.subnet_list(request, + network_id=net.id) + self.fields[subnet_field_name].choices = [ + (choice.id, choice.name_or_id) + for choice in subnet_choices] + + def hide_neutron_subnet_id_choices(self): + self.fields['neutron_subnet_id'].choices = [] + self.fields['neutron_subnet_id'].widget = forms.HiddenInput() + + +class AddShareNetworkSubnetStep(workflows.Step): + action_class = AddShareNetworkSubnetAction + contributes = ("neutron_net_id", "neutron_subnet_id", "availability_zone") + + +class CreateShareNetworkWorkflow(workflows.Workflow): + slug = "create_share_network" + name = _("Create Share Network") + finalize_button_name = _("Create Share Network") + success_message = _('Created share network "%s".') + failure_message = _('Unable to create share network "%s".') + success_url = 'horizon:project:share_networks:index' + default_steps = (CreateShareNetworkInfoStep, AddShareNetworkSubnetStep) + wizard = True + + def handle(self, request, context): + try: + data = request.POST + send_data = {'name': context['share_network_name']} + if context['share_network_description']: + send_data['description'] = context['share_network_description'] + neutron_net_id = context.get('neutron_net_id') + if neutron_net_id: + send_data['neutron_net_id'] = utils.transform_dashed_name( + neutron_net_id.strip()) + subnet_key = ( + 'subnet-choices-%s' % neutron_net_id.strip() + ) + if data.get(subnet_key) is not None: + send_data['neutron_subnet_id'] = data.get(subnet_key) + if context['availability_zone']: + send_data['availability_zone'] = context['availability_zone'] + share_network = manila.share_network_create(request, **send_data) + messages.success(request, _('Successfully created share' + ' network: %s') % send_data['name']) + return share_network + except Exception: + exceptions.handle(request, _('Unable to create share network.')) + return False class UpdateShareNetworkInfoAction(workflows.Action): diff --git a/manila_ui/tests/api/test_manila.py b/manila_ui/tests/api/test_manila.py index ee1d037f..c4c1b80b 100644 --- a/manila_ui/tests/api/test_manila.py +++ b/manila_ui/tests/api/test_manila.py @@ -412,6 +412,7 @@ class ManilaApiTests(base.APITestCase): "description": None, "neutron_net_id": None, "neutron_subnet_id": None, + "availability_zone": None } expected_kwargs.update(**kwargs) diff --git a/manila_ui/tests/dashboards/admin/share_networks/tests.py b/manila_ui/tests/dashboards/admin/share_networks/tests.py index 217fd193..c298ae3f 100644 --- a/manila_ui/tests/dashboards/admin/share_networks/tests.py +++ b/manila_ui/tests/dashboards/admin/share_networks/tests.py @@ -18,6 +18,7 @@ from horizon import exceptions as horizon_exceptions from neutronclient.client import exceptions from openstack_dashboard.api import keystone as api_keystone from openstack_dashboard.api import neutron as api_neutron +from oslo_utils import timeutils from unittest import mock from manila_ui.api import manila as api_manila @@ -39,8 +40,15 @@ class ShareNetworksTests(test.BaseAdminViewTests): # Reset taken list of projects to avoid test interference utils.PROJECTS = {} + class FakeAZ(object): + def __init__(self, name, id): + self.name = name + self.id = id + self.created_at = timeutils.utcnow() + def test_detail_view(self): share_net = test_data.active_share_network + share_network_subnets = share_net.share_network_subnets sec_service = test_data.sec_service self.mock_object( api_manila, "share_server_list", mock.Mock(return_value=[])) @@ -55,6 +63,10 @@ class ShareNetworksTests(test.BaseAdminViewTests): api_neutron, "network_get", mock.Mock(return_value=network)) self.mock_object( api_neutron, "subnet_get", mock.Mock(return_value=subnet)) + self.mock_object( + api_manila, "availability_zone_list", + mock.Mock(return_value=[self.FakeAZ('fake_az', 'fake_az')]) + ) url = reverse('horizon:admin:share_networks:share_network_detail', args=[share_net.id]) @@ -65,11 +77,23 @@ class ShareNetworksTests(test.BaseAdminViewTests): 1, 200) self.assertContains(res, "
%s
" % share_net.name, 1, 200) self.assertContains(res, "
%s
" % share_net.id, 1, 200) - self.assertContains(res, "
%s
" % network.name_or_id, 1, 200) - self.assertContains(res, "
%s
" % subnet.name_or_id, 1, 200) + for sub in share_network_subnets: + self.assertContains(res, "%s" % ( + sub['neutron_net_id'], + network.name), 1, 200) self.assertContains(res, "%s" % (sec_service.id, sec_service.name), 1, 200) + network_get_calls = [mock.call(mock.ANY, sub['neutron_net_id'] + ) for sub in share_network_subnets] + subnet_get_calls = [mock.call(mock.ANY, sub['neutron_subnet_id'] + ) for sub in share_network_subnets] + + api_neutron.network_get.assert_has_calls(network_get_calls, + any_order=True) + api_neutron.subnet_get.assert_has_calls(subnet_get_calls, + any_order=True) self.assertNoMessages() api_manila.share_network_security_service_list.assert_called_once_with( mock.ANY, share_net.id) @@ -77,14 +101,11 @@ class ShareNetworksTests(test.BaseAdminViewTests): mock.ANY, search_opts={'share_network_id': share_net.id}) api_manila.share_network_get.assert_called_once_with( mock.ANY, share_net.id) - api_neutron.network_get.assert_called_once_with( - mock.ANY, share_net.neutron_net_id) - api_neutron.subnet_get.assert_called_once_with( - mock.ANY, share_net.neutron_subnet_id) def test_detail_view_network_not_found(self): share_net = test_data.active_share_network sec_service = test_data.sec_service + share_network_subnets = share_net.share_network_subnets url = reverse('horizon:admin:share_networks:share_network_detail', args=[share_net.id]) self.mock_object( @@ -100,7 +121,10 @@ class ShareNetworksTests(test.BaseAdminViewTests): self.mock_object( api_neutron, "subnet_get", mock.Mock( side_effect=exceptions.NeutronClientException('fake', 500))) - + self.mock_object( + api_manila, "availability_zone_list", + mock.Mock(return_value=[]) + ) res = self.client.get(url) self.assertContains(res, "

Share Network Details: %s

" @@ -108,10 +132,19 @@ class ShareNetworksTests(test.BaseAdminViewTests): 1, 200) self.assertContains(res, "
%s
" % share_net.name, 1, 200) self.assertContains(res, "
%s
" % share_net.id, 1, 200) - self.assertContains(res, "
Unknown
", 2, 200) - self.assertNotContains(res, "
%s
" % share_net.neutron_net_id) - self.assertNotContains(res, - "
%s
" % share_net.neutron_subnet_id) + for sub in share_network_subnets: + self.assertNotContains(res, "
%s
" % sub['neutron_net_id']) + self.assertNotContains(res, + "
%s
" % sub['neutron_subnet_id']) + network_get_calls = [mock.call(mock.ANY, sub['neutron_net_id'] + ) for sub in share_network_subnets] + subnet_get_calls = [mock.call(mock.ANY, sub['neutron_subnet_id'] + ) for sub in share_network_subnets] + + api_neutron.network_get.assert_has_calls(network_get_calls, + any_order=True) + api_neutron.subnet_get.assert_has_calls(subnet_get_calls, + any_order=True) self.assertContains(res, "%s" % (sec_service.id, sec_service.name), 1, 200) @@ -122,10 +155,6 @@ class ShareNetworksTests(test.BaseAdminViewTests): mock.ANY, search_opts={'share_network_id': share_net.id}) api_manila.share_network_get.assert_called_once_with( mock.ANY, share_net.id) - api_neutron.network_get.assert_called_once_with( - mock.ANY, share_net.neutron_net_id) - api_neutron.subnet_get.assert_called_once_with( - mock.ANY, share_net.neutron_subnet_id) def test_detail_view_with_exception(self): url = reverse('horizon:admin:share_networks:share_network_detail', @@ -143,10 +172,6 @@ class ShareNetworksTests(test.BaseAdminViewTests): def test_delete_share_network(self): share_network = test_data.inactive_share_network formData = {'action': 'share_networks__delete__%s' % share_network.id} - self.mock_object( - api_neutron, "network_list", mock.Mock(return_value=[])) - self.mock_object( - api_neutron, "subnet_list", mock.Mock(return_value=[])) self.mock_object(api_manila, "share_network_delete") self.mock_object( api_manila, "share_network_list", @@ -156,11 +181,9 @@ class ShareNetworksTests(test.BaseAdminViewTests): res = self.client.post(INDEX_URL, formData) + self.assertRedirectsNoFollow(res, INDEX_URL) api_keystone.tenant_list.assert_called_once_with(mock.ANY) api_manila.share_network_delete.assert_called_once_with( - mock.ANY, test_data.inactive_share_network.id) + mock.ANY, share_network.id) api_manila.share_network_list.assert_called_once_with( mock.ANY, detailed=True, search_opts={'all_tenants': True}) - api_neutron.network_list.assert_called_once_with(mock.ANY) - api_neutron.subnet_list.assert_called_once_with(mock.ANY) - self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/manila_ui/tests/dashboards/project/share_networks/tests.py b/manila_ui/tests/dashboards/project/share_networks/tests.py index bc54c6a6..a9a65f8d 100644 --- a/manila_ui/tests/dashboards/project/share_networks/tests.py +++ b/manila_ui/tests/dashboards/project/share_networks/tests.py @@ -16,10 +16,10 @@ from django.urls import reverse from neutronclient.client import exceptions from openstack_auth import policy from openstack_dashboard import api +from oslo_utils import timeutils from unittest import mock from manila_ui.api import manila as api_manila -from manila_ui.api import network as api_manila_network from manila_ui.dashboards import utils from manila_ui.tests.dashboards.project import test_data from manila_ui.tests import helpers as test @@ -28,40 +28,55 @@ INDEX_URL = reverse('horizon:project:share_networks:index') class ShareNetworksViewTests(test.TestCase): + class FakeAZ(object): + def __init__(self, name, id): + self.name = name + self.id = id + self.created_at = timeutils.utcnow() def test_create_share_network(self): share_net = test_data.active_share_network + url = reverse('horizon:project:share_networks:share_network_create') neutron_net_id = self.networks.first().id + sanitized_net_id = utils.transform_dashed_name(neutron_net_id) formData = { - 'name': 'new_share_network', - 'description': 'This is test share network', + 'share_network_name': 'new_share_network', + 'share_network_description': 'This is test share network', 'method': 'CreateForm', 'neutron_net_id': utils.transform_dashed_name(neutron_net_id), + 'availability_zone': 'fake_az', + f'subnet-choices-{sanitized_net_id}': + self.networks.first().subnets[0].id, } - for net in self.networks.list(): - sanitized_net_id = utils.transform_dashed_name(net.id) - subnet_choices_field = 'subnet-choices-%s' % sanitized_net_id - formData[subnet_choices_field] = net.subnets[0].id + self.mock_object( api.neutron, "subnet_list", mock.Mock(return_value=self.subnets.list())) self.mock_object( - api_manila_network, "network_list", + api.neutron, "network_list", mock.Mock(return_value=self.networks.list())) self.mock_object( api_manila, "share_network_create", mock.Mock(return_value=share_net)) + self.mock_object( + api_manila, "availability_zone_list", + mock.Mock(return_value=[self.FakeAZ('fake_az', 'fake_az')]) + ) - self.client.post(url, formData) + res = self.client.post(url, formData) - sanitized_neutron_net_field = formData[ - 'subnet-choices-%s' % utils.transform_dashed_name(neutron_net_id)] + self.assertNoFormErrors(res) + self.assertMessageCount(error=0, warning=0) + self.assertRedirectsNoFollow(res, INDEX_URL) api_manila.share_network_create.assert_called_once_with( - mock.ANY, name=formData['name'], neutron_net_id=neutron_net_id, - neutron_subnet_id=sanitized_neutron_net_field, - description=formData['description']) - api_manila_network.network_list.assert_called_once_with(mock.ANY) + mock.ANY, name=formData['share_network_name'], + neutron_net_id=neutron_net_id, + neutron_subnet_id=self.networks.first().subnets[0].id, + description=formData['share_network_description'], + availability_zone='fake_az') + api_manila.availability_zone_list.assert_called_once_with(mock.ANY) + api.neutron.network_list.assert_called_once_with(mock.ANY) api.neutron.subnet_list.assert_has_calls([ mock.call(mock.ANY, network_id=network.id) for network in self.networks.list() @@ -75,12 +90,6 @@ class ShareNetworksViewTests(test.TestCase): api_manila, "share_network_list", mock.Mock(return_value=[ test_data.active_share_network, share_network])) - self.mock_object( - api.neutron, "network_list", - mock.Mock(return_value=self.networks.list())) - self.mock_object( - api.neutron, "subnet_list", - mock.Mock(return_value=self.subnets.list())) res = self.client.post(INDEX_URL, formData) @@ -89,12 +98,11 @@ class ShareNetworksViewTests(test.TestCase): mock.ANY, detailed=True) api_manila.share_network_delete.assert_called_once_with( mock.ANY, share_network.id) - api.neutron.network_list.assert_called_once_with(mock.ANY) - api.neutron.subnet_list.assert_called_once_with(mock.ANY) def test_detail_view(self): share_net = test_data.active_share_network sec_service = test_data.sec_service + share_network_subnets = share_net.share_network_subnets self.mock_object( api_manila, "share_server_list", mock.Mock(return_value=[])) self.mock_object( @@ -104,40 +112,58 @@ class ShareNetworksViewTests(test.TestCase): mock.Mock(return_value=[sec_service])) network = self.networks.first() subnet = self.subnets.first() + self.mock_object( api.neutron, "network_get", mock.Mock(return_value=network)) self.mock_object( api.neutron, "subnet_get", mock.Mock(return_value=subnet)) + self.mock_object( + api_manila, "availability_zone_list", + mock.Mock(return_value=[self.FakeAZ('fake_az', 'fake_az')]) + ) url = reverse('horizon:project:share_networks:share_network_detail', args=[share_net.id]) res = self.client.get(url) + self.assertNoMessages() self.assertContains(res, "

Share Network Details: %s

" % share_net.name, 1, 200) self.assertContains(res, "
%s
" % share_net.name, 1, 200) self.assertContains(res, "
%s
" % share_net.id, 1, 200) - self.assertContains(res, "
%s
" % network.name_or_id, 1, 200) - self.assertContains(res, "
%s
" % subnet.name_or_id, 1, 200) + for sub in share_network_subnets: + self.assertContains(res, "%s" % ( + sub['neutron_net_id'], + network.name), 1, 200) + self.assertContains(res, "%s" % ( + sub['neutron_subnet_id'], + subnet['name']), 1, 200) + network_get_calls = [mock.call(mock.ANY, sub['neutron_net_id']) + for sub in share_network_subnets] + subnet_get_calls = [mock.call(mock.ANY, sub['neutron_subnet_id']) + for sub in share_network_subnets] + + api.neutron.network_get.assert_has_calls(network_get_calls, + any_order=True) + api.neutron.subnet_get.assert_has_calls(subnet_get_calls, + any_order=True) self.assertContains(res, "%s" % (sec_service.id, sec_service.name), 1, 200) - self.assertNoMessages() api_manila.share_network_security_service_list.assert_called_once_with( mock.ANY, share_net.id) api_manila.share_server_list.assert_called_once_with( mock.ANY, search_opts={'share_network_id': share_net.id}) api_manila.share_network_get.assert_called_once_with( mock.ANY, share_net.id) - api.neutron.network_get.assert_called_once_with( - mock.ANY, share_net.neutron_net_id) - api.neutron.subnet_get.assert_called_once_with( - mock.ANY, share_net.neutron_subnet_id) def test_detail_view_network_not_found(self): share_net = test_data.active_share_network sec_service = test_data.sec_service + share_network_subnets = share_net.share_network_subnets url = reverse('horizon:project:share_networks:share_network_detail', args=[share_net.id]) self.mock_object( @@ -153,7 +179,10 @@ class ShareNetworksViewTests(test.TestCase): self.mock_object( api.neutron, "subnet_get", mock.Mock( side_effect=exceptions.NeutronClientException('fake', 500))) - + self.mock_object( + api_manila, "availability_zone_list", + mock.Mock(return_value=[]) + ) res = self.client.get(url) self.assertContains(res, "

Share Network Details: %s

" @@ -161,10 +190,19 @@ class ShareNetworksViewTests(test.TestCase): 1, 200) self.assertContains(res, "
%s
" % share_net.name, 1, 200) self.assertContains(res, "
%s
" % share_net.id, 1, 200) - self.assertContains(res, "
Unknown
", 2, 200) - self.assertNotContains(res, "
%s
" % share_net.neutron_net_id) - self.assertNotContains(res, - "
%s
" % share_net.neutron_subnet_id) + for sub in share_network_subnets: + self.assertNotContains(res, "
%s
" % sub['neutron_net_id']) + self.assertNotContains(res, + "
%s
" % sub['neutron_subnet_id']) + network_get_calls = [mock.call(mock.ANY, sub['neutron_net_id'] + ) for sub in share_network_subnets] + subnet_get_calls = [mock.call(mock.ANY, sub['neutron_subnet_id'] + ) for sub in share_network_subnets] + + api.neutron.network_get.assert_has_calls(network_get_calls, + any_order=True) + api.neutron.subnet_get.assert_has_calls(subnet_get_calls, + any_order=True) self.assertContains(res, "%s" % (sec_service.id, sec_service.name), 1, 200) @@ -175,10 +213,6 @@ class ShareNetworksViewTests(test.TestCase): mock.ANY, search_opts={'share_network_id': share_net.id}) api_manila.share_network_get.assert_called_once_with( mock.ANY, share_net.id) - api.neutron.network_get.assert_called_once_with( - mock.ANY, share_net.neutron_net_id) - api.neutron.subnet_get.assert_called_once_with( - mock.ANY, share_net.neutron_subnet_id) def test_update_share_network(self): share_net = test_data.inactive_share_network diff --git a/manila_ui/tests/dashboards/project/test_data.py b/manila_ui/tests/dashboards/project/test_data.py index 8cbc4e7b..f603c23c 100644 --- a/manila_ui/tests/dashboards/project/test_data.py +++ b/manila_ui/tests/dashboards/project/test_data.py @@ -279,12 +279,46 @@ inactive_share_network = share_networks.ShareNetwork( active_share_network = share_networks.ShareNetwork( share_networks.ShareNetworkManager(FakeAPIClient), - {'id': '7f3d1c33-8d00-4511-29df-a2def31f3b5d', - 'name': 'test_share_net', - 'description': 'test share network', - 'status': 'ACTIVE', - 'neutron_net_id': 'fake_neutron_net_id', - 'neutron_subnet_id': 'fake_neutron_subnet_id'}) + { + "id": "1324e7d3-fba8-45e4-bb37-b59c12eb06dc", + "name": "net_my1", + "project_id": "16e1ab15c35a457e9c2b2aa189f544e1", + "created_at": "2019-10-02T17:49:43.000000", + "description": "This is test share network", + "security_service_update_support": True, + "status": "active", + "share_network_subnets": [ + { + "id": "e4db03dc-6041-4c6a-a8f9-80bb4141a1eb", + "availability_zone": None, + "created_at": "2019-10-02T17:49:43.000000", + "updated_at": "2019-10-03T12:17:39.000000", + "segmentation_id": None, + "neutron_net_id": "62187648-6617-4509-a780-ffc973a7fe43", + "neutron_subnet_id": "2276888a-27c1-47c2-82a0-ea33050128b5", + "ip_version": 4, + "cidr": "172.24.5.0/24", + "network_type": "flat", + "mtu": 1500, + "gateway": "172.24.5.1", + }, + { + "id": "e4db03dc-6041-4c6a-a8f9-80bb4141a1en", + "availability_zone": "manila-zone-0", + "created_at": "2019-10-02T17:49:43.000000", + "updated_at": "2019-10-03T12:17:39.000000", + "segmentation_id": None, + "neutron_net_id": "62187648-6617-4509-a780-ffc973a7f333", + "neutron_subnet_id": "2276888a-27c1-47c2-82a0-ea3305012905", + "ip_version": 4, + "cidr": "172.24.5.0/24", + "network_type": "flat", + "mtu": 1500, + "gateway": "172.24.5.1", + }, + ], + } +) sec_service = security_services.SecurityService( security_services.SecurityServiceManager(FakeAPIClient), diff --git a/releasenotes/notes/bp-share-network-subnets-82ad8c601caf177b.yaml b/releasenotes/notes/bp-share-network-subnets-82ad8c601caf177b.yaml new file mode 100644 index 00000000..d56e50e6 --- /dev/null +++ b/releasenotes/notes/bp-share-network-subnets-82ad8c601caf177b.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Switched share network creation to a two-step + workflow. We now allow configuring an availability + zone with the initial network subnet associated with + the share network. Share network detail panels have + been updated accordingly as well