Merge "Create share network panel workflows"

This commit is contained in:
Zuul 2024-03-01 19:05:28 +00:00 committed by Gerrit Code Review
commit 946b9d50ba
16 changed files with 492 additions and 236 deletions

1
.gitignore vendored
View File

@ -22,6 +22,7 @@ lib64
pip-log.txt
# Unit test / coverage reports
coverage.xml
.coverage
.tox
nosetests.xml

View File

@ -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):

View File

@ -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)

View File

@ -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)

View File

@ -1,53 +1,90 @@
{% load i18n sizeformat parse_date %}
<h3>{% trans "Share Network Overview" %}</h3>
<div class="detail">
<hr class="header_rule">
<div class="row">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ share_network.name }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ share_network.id }}</dd>
{% if share_network.description %}
<dt>{% trans "Description" %}</dt>
<dd>{{ share_network.description }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ share_network.description }}</dd>
{% endif %}
{% if share_network.share_servers %}
<dt>{% trans "Share Servers" %}</dt>
{% for server in share_network.share_servers %}
{% url 'horizon:admin:share_servers:share_server_detail' server.id as server_url %}
<dd><a href="{{ server_url }}">{{ server.id }}</a></dd>
{% endfor %}
{% if share_network.created_at %}
<dt>{% trans "Created At" %}</dt>
<dd>{{ share_network.created_at }}</dd>
{% endif %}
{% if share_network.updated_at %}
<dt>{% trans "Updated At" %} </dt>
<dd>{{ share_network.updated_at }}</dd>
{% endif %}
<dt>{% trans "Share Network ID" %}</dt>
<dd>{{ share_network.id }}</dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ share_network.project_id }}</dd>
</dl>
</div>
<div class="detail">
<h4>{% trans "Net Details" %}</h4>
<h4>{% trans "Subnets" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Network" %}</dt>
{% if share_network.neutron_net %}
<dd>{{ share_network.neutron_net }}</dd>
<dt>{% trans "Subnet" %}</dt>
<dd>{{ share_network.neutron_subnet}}</dd>
{% endif %}
<dl class="dl-horizontal">
{% for subnet in share_network.share_network_subnets %}
<dl class="dl-horizontal">
<dt>{% trans "Id" %}</dt>
<dd>
{{subnet.id}}
</dd>
{% if subnet.neutron_net != "Unknown" %}
{% url 'horizon:admin:networks:detail' subnet.neutron_net_id as network_detail_url %}
<dt>{% trans "Neutron Network" %}</dt>
<dd>
<a href="{{ network_detail_url }}">{{subnet.neutron_net}}</a>
</dd>
{% endif %}
{% if subnet.neutron_subnet != "Unknown" %}
{% url 'horizon:admin:networks:subnets:detail' subnet.neutron_subnet_id as subnet_detail_url %}
<dt>{% trans "Neutron Subnet" %}</dt>
<dd>
<a href="{{ subnet_detail_url }}">{{subnet.neutron_subnet}}</a>
</dd>
{% endif %}
<dt>{% trans "Availability Zone" %}</dt>
<dd>
{{subnet.availability_zone}}
</dd>
</dl>
{% endfor %}
</dl>
</dl>
</div>
<div class="detail">
<h4>{% trans "Security Services" %}</h4>
<hr class="header_rule">
{% for sec_service in share_network.sec_services %}
{% url 'horizon:admin:security_services:security_service_detail' sec_service.id as sec_service_url%}
<dl class="dl-horizontal">
{% if share_network.share_servers %}
<div class="detail">
<h4>{% trans "Share Servers" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
{% for server in share_network.share_servers %}
<ul>
{% url 'horizon:admin:share_servers:share_server_detail' server.id as server_url %}
<li><a href="{{ server_url }}">{{server.id }}</a></li>
</ul>
{% endfor %}
</dl>
</div>
{% endif %}
{% if share_network.sec_services %}
<div class="detail">
<h4>{% trans "Security Services" %}</h4>
<hr class="header_rule">
{% for sec_service in share_network.sec_services %}
{% url 'horizon:admin:security_services:security_service_detail' sec_service.id as sec_service_url%}
<dl class="dl-horizontal">
<dt>{% trans "Id" %}</dt>
<dd><a href="{{ sec_service_url }}">{{ sec_service.id }}</a></dd>
<dt>{% trans "Name" %}</dt>
<dd><a href="{{ sec_service_url }}">{{ sec_service.name }}</a></dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ sec_service.type }}</dd>
</dl>
<br />
{% endfor %}
</div>
</dl>
<br />
{% endfor %}
</div>
{% endif %}

View File

@ -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(

View File

@ -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),

View File

@ -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)

View File

@ -1,65 +1,90 @@
{% load i18n sizeformat parse_date %}
<h3>{% trans "Share Network Overview" %}</h3>
<div class="detail">
<hr class="header_rule">
<div class="row">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ share_network.name }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ share_network.id }}</dd>
{% if share_network.description %}
<dt>{% trans "Description" %}</dt>
<dd>{{ share_network.description }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ share_network.description }}</dd>
{% endif %}
{% if share_network.created_at %}
<dt>{% trans "Created At" %}</dt>
<dd>{{ share_network.created_at }}</dd>
{% endif %}
{% if share_network.updated_at %}
<dt>{% trans "Updated At" %} </dt>
<dd>{{ share_network.updated_at }}</dd>
{% endif %}
<dt>{% trans "Share Network ID" %}</dt>
<dd>{{ share_network.id }}</dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ share_network.project_id }}</dd>
</dl>
</div>
<div class="detail">
<h4>{% trans "Network Details" %}</h4>
<h4>{% trans "Subnets" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Network" %}</dt>
{% if share_network.neutron_net %}
<dd>{{ share_network.neutron_net }}</dd>
<dt>{% trans "Subnet" %}</dt>
<dd>{{ share_network.neutron_subnet}}</dd>
{% endif %}
<dl class="dl-horizontal">
{% for subnet in share_network.share_network_subnets %}
<dl class="dl-horizontal">
<dt>{% trans "Id" %}</dt>
<dd>
{{subnet.id}}
</dd>
{% if subnet.neutron_net != "Unknown" %}
{% url 'horizon:project:networks:detail' subnet.neutron_net_id as network_detail_url %}
<dt>{% trans "Neutron Network" %}</dt>
<dd>
<a href="{{ network_detail_url }}">{{subnet.neutron_net}}</a>
</dd>
{% endif %}
{% if subnet.neutron_subnet != "Unknown" %}
{% url 'horizon:project:networks:subnets:detail' subnet.neutron_subnet_id as subnet_detail_url %}
<dt>{% trans "Neutron Subnet" %}</dt>
<dd>
<a href="{{ subnet_detail_url }}">{{subnet.neutron_subnet}}</a>
</dd>
{% endif %}
<dt>{% trans "Availability Zone" %}</dt>
<dd>
{{subnet.availability_zone}}
</dd>
</dl>
{% endfor %}
</dl>
</dl>
</div>
{% if share_network.share_servers %}
<div class="detail">
<h4>{% trans "Share Servers" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
{% for server in share_network.share_servers %}
<ul>
{% url 'horizon:admin:share_servers:share_server_detail' server.id as server_url %}
<li><a href="{{ server_url }}">{{ server.id }}</a></li>
</ul>
{% endfor %}
</dl>
</div>
<div class="detail">
<h4>{% trans "Share Servers" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
{% for server in share_network.share_servers %}
<ul>
{% url 'horizon:admin:share_servers:share_server_detail' server.id as server_url %}
<li><a href="{{ server_url }}">{{server.id }}</a></li>
</ul>
{% endfor %}
</dl>
</div>
{% endif %}
{% if share_network.sec_services %}
<div class="detail">
<h4>{% trans "Security Services" %}</h4>
<hr class="header_rule">
{% for sec_service in share_network.sec_services %}
{% url 'horizon:project:security_services:security_service_detail' sec_service.id as sec_service_url%}
<div class="detail">
<h4>{% trans "Security Services" %}</h4>
<hr class="header_rule">
{% for sec_service in share_network.sec_services %}
{% url 'horizon:project:security_services:security_service_detail' sec_service.id as sec_service_url%}
<dl class="dl-horizontal">
<dt>{% trans "Id" %}</dt>
<dd><a href="{{ sec_service_url }}">{{ sec_service.id }}</a></dd>
<dt>{% trans "Name" %}</dt>
<dd><a href="{{ sec_service_url }}">{{ sec_service.name }}</a></dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ sec_service.type }}</dd>
<dt>{% trans "Id" %}</dt>
<dd><a href="{{ sec_service_url }}">{{ sec_service.id }}</a></dd>
<dt>{% trans "Name" %}</dt>
<dd><a href="{{ sec_service_url }}">{{ sec_service.name }}</a></dd>
<dt>{% trans "Type" %}</dt>
<dd>{{ sec_service.type }}</dd>
</dl>
<br />
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}

View File

@ -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))

View File

@ -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):

View File

@ -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)

View File

@ -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, "<dd>%s</dd>" % share_net.name, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % share_net.id, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % network.name_or_id, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % subnet.name_or_id, 1, 200)
for sub in share_network_subnets:
self.assertContains(res, "<a href=\"/admin/networks"
"/%s/detail\">%s</a>" % (
sub['neutron_net_id'],
network.name), 1, 200)
self.assertContains(res, "<a href=\"/admin/security_services"
"/%s\">%s</a>" % (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, "<h1>Share Network Details: %s</h1>"
@ -108,10 +132,19 @@ class ShareNetworksTests(test.BaseAdminViewTests):
1, 200)
self.assertContains(res, "<dd>%s</dd>" % share_net.name, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % share_net.id, 1, 200)
self.assertContains(res, "<dd>Unknown</dd>", 2, 200)
self.assertNotContains(res, "<dd>%s</dd>" % share_net.neutron_net_id)
self.assertNotContains(res,
"<dd>%s</dd>" % share_net.neutron_subnet_id)
for sub in share_network_subnets:
self.assertNotContains(res, "<dd>%s</dd>" % sub['neutron_net_id'])
self.assertNotContains(res,
"<dd>%s</dd>" % 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, "<a href=\"/admin/security_services"
"/%s\">%s</a>" % (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)

View File

@ -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, "<h1>Share Network Details: %s</h1>"
% share_net.name,
1, 200)
self.assertContains(res, "<dd>%s</dd>" % share_net.name, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % share_net.id, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % network.name_or_id, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % subnet.name_or_id, 1, 200)
for sub in share_network_subnets:
self.assertContains(res, "<a href=\"/project/networks"
"/%s/detail\">%s</a>" % (
sub['neutron_net_id'],
network.name), 1, 200)
self.assertContains(res, "<a href=\"/project/networks/subnets"
"/%s/detail\">%s</a>" % (
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, "<a href=\"/project/security_services"
"/%s\">%s</a>" % (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, "<h1>Share Network Details: %s</h1>"
@ -161,10 +190,19 @@ class ShareNetworksViewTests(test.TestCase):
1, 200)
self.assertContains(res, "<dd>%s</dd>" % share_net.name, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % share_net.id, 1, 200)
self.assertContains(res, "<dd>Unknown</dd>", 2, 200)
self.assertNotContains(res, "<dd>%s</dd>" % share_net.neutron_net_id)
self.assertNotContains(res,
"<dd>%s</dd>" % share_net.neutron_subnet_id)
for sub in share_network_subnets:
self.assertNotContains(res, "<dd>%s</dd>" % sub['neutron_net_id'])
self.assertNotContains(res,
"<dd>%s</dd>" % 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, "<a href=\"/project/security_services"
"/%s\">%s</a>" % (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

View File

@ -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),

View File

@ -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