+{% 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 "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