From 6dcc671543549b8f0135bb03f31c332f070aebec Mon Sep 17 00:00:00 2001 From: Valeriy Ponomaryov Date: Fri, 12 May 2017 20:55:24 +0300 Subject: [PATCH] Refactor admin dashboard Separating tabs of single panel to separate panels. It will reduce amount of redundant calls, because it is very unlikely that someone will need all tabs at once. Also, it is current approach that is used by core projects. Change-Id: I874253e0e9a35ede8239bc1bdf0a330a44ade413 Partially-Implements BluePrint create-share-panel-group --- README.rst | 2 +- .../admin/security_services/__init__.py | 0 .../admin/security_services/forms.py | 0 .../admin/security_services/panel.py | 30 + .../admin/security_services/tables.py | 68 ++ .../admin/security_services/tabs.py | 32 + .../templates/security_services/_detail.html | 37 + .../templates/security_services/detail.html | 11 + .../templates/security_services/index.html | 11 + .../admin/security_services/urls.py | 27 + .../admin/security_services/views.py | 55 ++ .../admin/share_instances/__init__.py | 0 .../dashboards/admin/share_instances/forms.py | 0 .../dashboards/admin/share_instances/panel.py | 28 + .../admin/share_instances/tables.py | 93 +++ .../dashboards/admin/share_instances/tabs.py | 32 + .../templates/share_instances/_detail.html} | 6 +- .../templates/share_instances/detail.html} | 0 .../templates/share_instances/index.html | 11 + .../dashboards/admin/share_instances/urls.py | 27 + .../dashboards/admin/share_instances/views.py | 87 +++ .../admin/share_networks/__init__.py | 0 .../dashboards/admin/share_networks/forms.py | 0 .../dashboards/admin/share_networks/panel.py | 28 + .../dashboards/admin/share_networks/tables.py | 50 ++ .../dashboards/admin/share_networks/tabs.py | 32 + .../templates/share_networks/_detail.html | 56 ++ .../templates/share_networks/detail.html | 11 + .../templates/share_networks/index.html | 11 + .../dashboards/admin/share_networks/urls.py | 27 + .../dashboards/admin/share_networks/views.py | 69 ++ .../admin/share_servers/__init__.py | 0 .../dashboards/admin/share_servers/forms.py | 0 .../dashboards/admin/share_servers/panel.py | 28 + .../dashboards/admin/share_servers/tables.py | 118 +++ .../dashboards/admin/share_servers/tabs.py | 32 + .../templates/share_servers/_detail.html} | 6 +- .../templates/share_servers/detail.html | 11 + .../templates/share_servers/index.html | 11 + .../dashboards/admin/share_servers/urls.py | 27 + .../dashboards/admin/share_servers/views.py | 91 +++ .../admin/share_snapshots/__init__.py | 0 .../dashboards/admin/share_snapshots/forms.py | 0 .../dashboards/admin/share_snapshots/panel.py | 28 + .../admin/share_snapshots/tables.py | 128 ++++ .../dashboards/admin/share_snapshots/tabs.py | 32 + .../templates/share_snapshots/_detail.html | 66 ++ .../templates/share_snapshots/detail.html} | 0 .../templates/share_snapshots/index.html | 11 + .../dashboards/admin/share_snapshots/urls.py | 27 + .../dashboards/admin/share_snapshots/views.py | 63 ++ .../dashboards/admin/share_types/__init__.py | 0 .../dashboards/admin/share_types/forms.py | 133 ++++ .../dashboards/admin/share_types/panel.py | 30 + .../dashboards/admin/share_types/tables.py | 111 +++ .../templates/share_types/_create.html} | 0 .../share_types/_manage_access.html} | 6 +- .../templates/share_types/_update.html} | 0 .../templates/share_types/create.html} | 2 +- .../templates/share_types/index.html | 11 + .../templates/share_types/manage_access.html} | 2 +- .../templates/share_types/update.html} | 2 +- .../dashboards/admin/share_types/urls.py | 35 + .../dashboards/admin/share_types/views.py | 119 ++++ .../{shares => share_types}/workflows.py | 2 +- manila_ui/dashboards/admin/shares/forms.py | 206 +----- manila_ui/dashboards/admin/shares/panel.py | 9 +- manila_ui/dashboards/admin/shares/tables.py | 496 +------------ manila_ui/dashboards/admin/shares/tabs.py | 243 +------ .../shares/templates/shares/_detail.html | 100 +++ .../shares/_snapshot_detail_overview.html | 33 - .../admin/shares/templates/shares/index.html | 2 +- manila_ui/dashboards/admin/shares/urls.py | 85 ++- manila_ui/dashboards/admin/shares/views.py | 211 +----- .../dashboards/admin/{shares => }/utils.py | 0 manila_ui/dashboards/project/shares/panel.py | 6 +- .../project/shares/share_networks/tables.py | 2 +- .../project/shares/share_networks/tabs.py | 2 +- .../_80_manila_admin_add_share_panel_group.py | 6 + ..._add_shares_panel_to_share_panel_group.py} | 4 +- ...re_snapshots_panel_to_share_panel_group.py | 18 + ..._share_types_panel_to_share_panel_group.py | 18 + ...are_networks_panel_to_share_panel_group.py | 18 + ...ity_services_panel_to_share_panel_group.py | 19 + ...hare_servers_panel_to_share_panel_group.py | 18 + ...re_instances_panel_to_share_panel_group.py | 18 + .../admin/security_services/__init__.py | 0 .../admin/security_services/tests.py | 95 +++ .../admin/share_instances/__init__.py | 0 .../dashboards/admin/share_instances/tests.py | 127 ++++ .../admin/share_networks/__init__.py | 0 .../dashboards/admin/share_networks/tests.py | 166 +++++ .../admin/share_servers/__init__.py | 0 .../dashboards/admin/share_servers/tests.py | 135 ++++ .../admin/share_snapshots/__init__.py | 0 .../dashboards/admin/share_snapshots/tests.py | 151 ++++ .../dashboards/admin/share_types/__init__.py | 0 .../admin/share_types/test_forms.py | 254 +++++++ .../dashboards/admin/share_types/tests.py | 88 +++ .../dashboards/admin/shares/test_forms.py | 234 ------ .../tests/dashboards/admin/shares/tests.py | 669 +----------------- manila_ui/tests/test_data/keystone_data.py | 6 +- ...p-to-admin-dashboard-ef6a02243c0e776c.yaml | 12 + 103 files changed, 3337 insertions(+), 2087 deletions(-) create mode 100644 manila_ui/dashboards/admin/security_services/__init__.py create mode 100644 manila_ui/dashboards/admin/security_services/forms.py create mode 100644 manila_ui/dashboards/admin/security_services/panel.py create mode 100644 manila_ui/dashboards/admin/security_services/tables.py create mode 100644 manila_ui/dashboards/admin/security_services/tabs.py create mode 100644 manila_ui/dashboards/admin/security_services/templates/security_services/_detail.html create mode 100644 manila_ui/dashboards/admin/security_services/templates/security_services/detail.html create mode 100644 manila_ui/dashboards/admin/security_services/templates/security_services/index.html create mode 100644 manila_ui/dashboards/admin/security_services/urls.py create mode 100644 manila_ui/dashboards/admin/security_services/views.py create mode 100644 manila_ui/dashboards/admin/share_instances/__init__.py create mode 100644 manila_ui/dashboards/admin/share_instances/forms.py create mode 100644 manila_ui/dashboards/admin/share_instances/panel.py create mode 100644 manila_ui/dashboards/admin/share_instances/tables.py create mode 100644 manila_ui/dashboards/admin/share_instances/tabs.py rename manila_ui/dashboards/admin/{shares/templates/shares/_detail_share_instance.html => share_instances/templates/share_instances/_detail.html} (85%) rename manila_ui/dashboards/admin/{shares/templates/shares/share_instance_detail.html => share_instances/templates/share_instances/detail.html} (100%) create mode 100644 manila_ui/dashboards/admin/share_instances/templates/share_instances/index.html create mode 100644 manila_ui/dashboards/admin/share_instances/urls.py create mode 100644 manila_ui/dashboards/admin/share_instances/views.py create mode 100644 manila_ui/dashboards/admin/share_networks/__init__.py create mode 100644 manila_ui/dashboards/admin/share_networks/forms.py create mode 100644 manila_ui/dashboards/admin/share_networks/panel.py create mode 100644 manila_ui/dashboards/admin/share_networks/tables.py create mode 100644 manila_ui/dashboards/admin/share_networks/tabs.py create mode 100644 manila_ui/dashboards/admin/share_networks/templates/share_networks/_detail.html create mode 100644 manila_ui/dashboards/admin/share_networks/templates/share_networks/detail.html create mode 100644 manila_ui/dashboards/admin/share_networks/templates/share_networks/index.html create mode 100644 manila_ui/dashboards/admin/share_networks/urls.py create mode 100644 manila_ui/dashboards/admin/share_networks/views.py create mode 100644 manila_ui/dashboards/admin/share_servers/__init__.py create mode 100644 manila_ui/dashboards/admin/share_servers/forms.py create mode 100644 manila_ui/dashboards/admin/share_servers/panel.py create mode 100644 manila_ui/dashboards/admin/share_servers/tables.py create mode 100644 manila_ui/dashboards/admin/share_servers/tabs.py rename manila_ui/dashboards/admin/{shares/templates/shares/_detail_share_server.html => share_servers/templates/share_servers/_detail.html} (83%) create mode 100644 manila_ui/dashboards/admin/share_servers/templates/share_servers/detail.html create mode 100644 manila_ui/dashboards/admin/share_servers/templates/share_servers/index.html create mode 100644 manila_ui/dashboards/admin/share_servers/urls.py create mode 100644 manila_ui/dashboards/admin/share_servers/views.py create mode 100644 manila_ui/dashboards/admin/share_snapshots/__init__.py create mode 100644 manila_ui/dashboards/admin/share_snapshots/forms.py create mode 100644 manila_ui/dashboards/admin/share_snapshots/panel.py create mode 100644 manila_ui/dashboards/admin/share_snapshots/tables.py create mode 100644 manila_ui/dashboards/admin/share_snapshots/tabs.py create mode 100644 manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/_detail.html rename manila_ui/dashboards/admin/{shares/templates/shares/detail_share_server.html => share_snapshots/templates/share_snapshots/detail.html} (100%) create mode 100644 manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/index.html create mode 100644 manila_ui/dashboards/admin/share_snapshots/urls.py create mode 100644 manila_ui/dashboards/admin/share_snapshots/views.py create mode 100644 manila_ui/dashboards/admin/share_types/__init__.py create mode 100644 manila_ui/dashboards/admin/share_types/forms.py create mode 100644 manila_ui/dashboards/admin/share_types/panel.py create mode 100644 manila_ui/dashboards/admin/share_types/tables.py rename manila_ui/dashboards/admin/{shares/templates/shares/_create_share_type.html => share_types/templates/share_types/_create.html} (100%) rename manila_ui/dashboards/admin/{shares/templates/shares/_manage_share_type_access.html => share_types/templates/share_types/_manage_access.html} (82%) rename manila_ui/dashboards/admin/{shares/templates/shares/_update_share_type.html => share_types/templates/share_types/_update.html} (100%) rename manila_ui/dashboards/admin/{shares/templates/shares/create_share_type.html => share_types/templates/share_types/create.html} (70%) create mode 100644 manila_ui/dashboards/admin/share_types/templates/share_types/index.html rename manila_ui/dashboards/admin/{shares/templates/shares/manage_share_type_access.html => share_types/templates/share_types/manage_access.html} (69%) rename manila_ui/dashboards/admin/{shares/templates/shares/update_share_type.html => share_types/templates/share_types/update.html} (70%) create mode 100644 manila_ui/dashboards/admin/share_types/urls.py create mode 100644 manila_ui/dashboards/admin/share_types/views.py rename manila_ui/dashboards/admin/{shares => share_types}/workflows.py (98%) create mode 100644 manila_ui/dashboards/admin/shares/templates/shares/_detail.html delete mode 100644 manila_ui/dashboards/admin/shares/templates/shares/_snapshot_detail_overview.html rename manila_ui/dashboards/admin/{shares => }/utils.py (100%) create mode 100644 manila_ui/local/enabled/_80_manila_admin_add_share_panel_group.py rename manila_ui/local/enabled/{_90_manila_admin_shares.py => _9010_manila_admin_add_shares_panel_to_share_panel_group.py} (92%) create mode 100644 manila_ui/local/enabled/_9020_manila_admin_add_share_snapshots_panel_to_share_panel_group.py create mode 100644 manila_ui/local/enabled/_9030_manila_admin_add_share_types_panel_to_share_panel_group.py create mode 100644 manila_ui/local/enabled/_9040_manila_admin_add_share_networks_panel_to_share_panel_group.py create mode 100644 manila_ui/local/enabled/_9050_manila_admin_add_security_services_panel_to_share_panel_group.py create mode 100644 manila_ui/local/enabled/_9060_manila_admin_add_share_servers_panel_to_share_panel_group.py create mode 100644 manila_ui/local/enabled/_9070_manila_admin_add_share_instances_panel_to_share_panel_group.py create mode 100644 manila_ui/tests/dashboards/admin/security_services/__init__.py create mode 100644 manila_ui/tests/dashboards/admin/security_services/tests.py create mode 100644 manila_ui/tests/dashboards/admin/share_instances/__init__.py create mode 100644 manila_ui/tests/dashboards/admin/share_instances/tests.py create mode 100644 manila_ui/tests/dashboards/admin/share_networks/__init__.py create mode 100644 manila_ui/tests/dashboards/admin/share_networks/tests.py create mode 100644 manila_ui/tests/dashboards/admin/share_servers/__init__.py create mode 100644 manila_ui/tests/dashboards/admin/share_servers/tests.py create mode 100644 manila_ui/tests/dashboards/admin/share_snapshots/__init__.py create mode 100644 manila_ui/tests/dashboards/admin/share_snapshots/tests.py create mode 100644 manila_ui/tests/dashboards/admin/share_types/__init__.py create mode 100644 manila_ui/tests/dashboards/admin/share_types/test_forms.py create mode 100644 manila_ui/tests/dashboards/admin/share_types/tests.py create mode 100644 releasenotes/notes/add-share-panel-group-to-admin-dashboard-ef6a02243c0e776c.yaml diff --git a/README.rst b/README.rst index 0e630eb2..905532b2 100644 --- a/README.rst +++ b/README.rst @@ -57,7 +57,7 @@ Install Manila UI with all dependencies in your virtual environment:: And enable it in Horizon:: - cp ../manila-ui/manila_ui/local/enabled/_90_manila_*.py openstack_dashboard/local/enabled + cp ../manila-ui/manila_ui/local/enabled/_*.py openstack_dashboard/local/enabled cp ../manila-ui/manila_ui/local/local_settings.d/_90_manila_*.py openstack_dashboard/local/local_settings.d diff --git a/manila_ui/dashboards/admin/security_services/__init__.py b/manila_ui/dashboards/admin/security_services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/security_services/forms.py b/manila_ui/dashboards/admin/security_services/forms.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/security_services/panel.py b/manila_ui/dashboards/admin/security_services/panel.py new file mode 100644 index 00000000..a8c9262a --- /dev/null +++ b/manila_ui/dashboards/admin/security_services/panel.py @@ -0,0 +1,30 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.dashboards.admin import dashboard + + +class SecurityServices(horizon.Panel): + name = _("Security Services") + slug = 'security_services' + permissions = ( + 'openstack.services.share', + ) + + +dashboard.Admin.register(SecurityServices) diff --git a/manila_ui/dashboards/admin/security_services/tables.py b/manila_ui/dashboards/admin/security_services/tables.py new file mode 100644 index 00000000..d352b16e --- /dev/null +++ b/manila_ui/dashboards/admin/security_services/tables.py @@ -0,0 +1,68 @@ +# 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 django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy +from horizon import tables + +from manila_ui.api import manila + + +class DeleteSecurityService(tables.DeleteAction): + policy_rules = (("share", "security_service:delete"),) + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Security Service", + u"Delete Security Services", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Security Service", + u"Deleted Security Services", + count + ) + + def delete(self, request, obj_id): + manila.security_service_delete(request, obj_id) + + +class SecurityServicesTable(tables.DataTable): + name = tables.WrappingColumn( + "name", verbose_name=_("Name"), + link="horizon:admin:security_services:security_service_detail") + project = tables.Column("project_name", verbose_name=_("Project")) + dns_ip = tables.Column("dns_ip", verbose_name=_("DNS IP")) + server = tables.Column("server", verbose_name=_("Server")) + domain = tables.Column("domain", verbose_name=_("Domain")) + user = tables.Column("user", verbose_name=_("Sid")) + + def get_object_display(self, security_service): + return security_service.name + + def get_object_id(self, security_service): + return str(security_service.id) + + class Meta(object): + name = "security_services" + verbose_name = _("Security Services") + table_actions = ( + tables.NameFilterAction, + DeleteSecurityService, + ) + row_actions = ( + DeleteSecurityService, + ) diff --git a/manila_ui/dashboards/admin/security_services/tabs.py b/manila_ui/dashboards/admin/security_services/tabs.py new file mode 100644 index 00000000..37612d05 --- /dev/null +++ b/manila_ui/dashboards/admin/security_services/tabs.py @@ -0,0 +1,32 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ +from horizon import tabs + + +class SecurityServiceOverviewTab(tabs.Tab): + name = _("Security Service Overview") + slug = "security_service_overview_tab" + template_name = "admin/security_services/_detail.html" + + def get_context_data(self, request): + return {"sec_service": self.tab_group.kwargs["sec_service"]} + + +class SecurityServiceDetailTabs(tabs.TabGroup): + slug = "security_service_details" + tabs = ( + SecurityServiceOverviewTab, + ) diff --git a/manila_ui/dashboards/admin/security_services/templates/security_services/_detail.html b/manila_ui/dashboards/admin/security_services/templates/security_services/_detail.html new file mode 100644 index 00000000..846c8775 --- /dev/null +++ b/manila_ui/dashboards/admin/security_services/templates/security_services/_detail.html @@ -0,0 +1,37 @@ +{% load i18n sizeformat parse_date %} + +

{% trans "Security Service Overview" %}

+
+
+
+
{% trans "Type" %}
+
{{ sec_service.type }}
+
{% trans "Name" %}
+
{{ sec_service.name }}
+
{% trans "ID" %}
+
{{ sec_service.id }}
+ {% if sec_service.description %} +
{% trans "Description" %}
+
{{ sec_service.description }}
+ {% endif %} +
{% trans "Created at" %}
+
{{ sec_service.created_at|parse_date }}
+ +
+
+ +
+

{% trans "Security Service Details" %}

+
+
+
{% trans "DNS IP" %}
+
{{ sec_service.dns_ip }}
+
{% trans "Server" %}
+
{{ sec_service.server }}
+
{% trans "Domain" %}
+
{{ sec_service.domain }}
+
{% trans "User" %}
+
{{ sec_service.user }}
+
+
+ diff --git a/manila_ui/dashboards/admin/security_services/templates/security_services/detail.html b/manila_ui/dashboards/admin/security_services/templates/security_services/detail.html new file mode 100644 index 00000000..e43eeb07 --- /dev/null +++ b/manila_ui/dashboards/admin/security_services/templates/security_services/detail.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Security Service Details" %}{% endblock %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/security_services/templates/security_services/index.html b/manila_ui/dashboards/admin/security_services/templates/security_services/index.html new file mode 100644 index 00000000..dd0bdfc2 --- /dev/null +++ b/manila_ui/dashboards/admin/security_services/templates/security_services/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Shares" %}{% endblock %} + +{% block main %} +
+
+ {{ security_services_table.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/security_services/urls.py b/manila_ui/dashboards/admin/security_services/urls.py new file mode 100644 index 00000000..49f3b83e --- /dev/null +++ b/manila_ui/dashboards/admin/security_services/urls.py @@ -0,0 +1,27 @@ +# 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 django.conf import urls + +from manila_ui.dashboards.admin.security_services import views + + +urlpatterns = [ + urls.url( + r'^$', + views.SecurityServicesView.as_view(), + name='index'), + urls.url( + r'^(?P[^/]+)$', + views.SecurityServiceDetailView.as_view(), + name='security_service_detail'), +] diff --git a/manila_ui/dashboards/admin/security_services/views.py b/manila_ui/dashboards/admin/security_services/views.py new file mode 100644 index 00000000..b2f6914e --- /dev/null +++ b/manila_ui/dashboards/admin/security_services/views.py @@ -0,0 +1,55 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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. + +""" +Admin views for managing security services. +""" + +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions +from horizon import tables +from horizon.utils import memoized + +from manila_ui.api import manila +import manila_ui.dashboards.admin.security_services.tables as ss_tables +import manila_ui.dashboards.admin.security_services.tabs as ss_tabs +from manila_ui.dashboards.admin import utils +import manila_ui.dashboards.project.shares.security_services.views as ss_views + + +class SecurityServicesView(tables.MultiTableView): + table_classes = ( + ss_tables.SecurityServicesTable, + ) + template_name = "admin/security_services/index.html" + page_title = _("Security Services") + + @memoized.memoized_method + def get_security_services_data(self): + try: + security_services = manila.security_service_list( + self.request, search_opts={'all_tenants': True}) + utils.set_project_name_to_objects(self.request, security_services) + except Exception: + security_services = [] + exceptions.handle( + self.request, _("Unable to retrieve security services")) + return security_services + + +class SecurityServiceDetailView(ss_views.Detail): + tab_group_class = ss_tabs.SecurityServiceDetailTabs + template_name = "admin/security_services/detail.html" + redirect_url = reverse_lazy('horizon:admin:security_services:index') diff --git a/manila_ui/dashboards/admin/share_instances/__init__.py b/manila_ui/dashboards/admin/share_instances/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_instances/forms.py b/manila_ui/dashboards/admin/share_instances/forms.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_instances/panel.py b/manila_ui/dashboards/admin/share_instances/panel.py new file mode 100644 index 00000000..6c7af92f --- /dev/null +++ b/manila_ui/dashboards/admin/share_instances/panel.py @@ -0,0 +1,28 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ +import horizon +from openstack_dashboard.dashboards.admin import dashboard + + +class ShareInstances(horizon.Panel): + name = _("Share Instances") + slug = 'share_instances' + permissions = ( + 'openstack.services.share', + ) + + +dashboard.Admin.register(ShareInstances) diff --git a/manila_ui/dashboards/admin/share_instances/tables.py b/manila_ui/dashboards/admin/share_instances/tables.py new file mode 100644 index 00000000..cde6bd7a --- /dev/null +++ b/manila_ui/dashboards/admin/share_instances/tables.py @@ -0,0 +1,93 @@ +# 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 django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from horizon import tables +import six + + +class ShareInstancesTable(tables.DataTable): + STATUS_CHOICES = ( + ("available", True), + ("creating", None), + ("deleting", None), + ("error", False), + ("error_deleting", False), + ) + STATUS_DISPLAY_CHOICES = ( + ("available", u"Available"), + ("creating", u"Creating"), + ("deleting", u"Deleting"), + ("error", u"Error"), + ("error_deleting", u"Error deleting"), + ) + uuid = tables.Column( + "id", verbose_name=_("ID"), + link="horizon:admin:share_instances:share_instance_detail") + host = tables.Column("host", verbose_name=_("Host")) + status = tables.Column( + "status", + verbose_name=_("Status"), + status=True, + status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + availability_zone = tables.Column( + "availability_zone", verbose_name=_("Availability Zone")) + + class Meta(object): + name = "share_instances" + verbose_name = _("Share Instances") + status_columns = ("status", ) + table_actions = ( + tables.NameFilterAction,) + multi_select = False + + def get_share_network_link(share_instance): + if getattr(share_instance, 'share_network_id', None): + return reverse("horizon:admin:share_networks:share_network_detail", + args=(share_instance.share_network_id,)) + else: + return None + + def get_share_server_link(share_instance): + if getattr(share_instance, 'share_server_id', None): + return reverse("horizon:admin:share_servers:share_server_detail", + args=(share_instance.share_server_id,)) + else: + return None + + def get_share_link(share_instance): + if getattr(share_instance, 'share_id', None): + return reverse("horizon:admin:shares:detail", + args=(share_instance.share_id,)) + else: + return None + + share_net_id = tables.Column( + "share_network_id", + verbose_name=_("Share Network"), + link=get_share_network_link) + share_server_id = tables.Column( + "share_server_id", + verbose_name=_("Share Server Id"), + link=get_share_server_link) + share_id = tables.Column( + "share_id", + verbose_name=_("Share ID"), + link=get_share_link) + + def get_object_display(self, share_instance): + return six.text_type(share_instance.id) + + def get_object_id(self, share_instance): + return six.text_type(share_instance.id) diff --git a/manila_ui/dashboards/admin/share_instances/tabs.py b/manila_ui/dashboards/admin/share_instances/tabs.py new file mode 100644 index 00000000..93b63682 --- /dev/null +++ b/manila_ui/dashboards/admin/share_instances/tabs.py @@ -0,0 +1,32 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ +from horizon import tabs + + +class ShareInstanceOverviewTab(tabs.Tab): + name = _("Share Instance Overview") + slug = "share_instance_overview_tab" + template_name = "admin/share_instances/_detail.html" + + def get_context_data(self, request): + return {"share_instance": self.tab_group.kwargs["share_instance"]} + + +class ShareInstanceDetailTabs(tabs.TabGroup): + slug = "share_instance_details" + tabs = ( + ShareInstanceOverviewTab, + ) diff --git a/manila_ui/dashboards/admin/shares/templates/shares/_detail_share_instance.html b/manila_ui/dashboards/admin/share_instances/templates/share_instances/_detail.html similarity index 85% rename from manila_ui/dashboards/admin/shares/templates/shares/_detail_share_instance.html rename to manila_ui/dashboards/admin/share_instances/templates/share_instances/_detail.html index f441a0db..c2a6525e 100644 --- a/manila_ui/dashboards/admin/shares/templates/shares/_detail_share_instance.html +++ b/manila_ui/dashboards/admin/share_instances/templates/share_instances/_detail.html @@ -29,16 +29,16 @@
{% trans "Availability Zone" %}
{{ share_instance.availability_zone}}
{% trans "Share ID" %}
- {% url 'horizon:project:shares:detail' share_instance.share_id as share_url %} + {% url 'horizon:admin:shares:detail' share_instance.share_id as share_url %}
{{ share_instance.share_id }}
{% if share_instance.share_network_id %}
{% trans "Share network" %}
- {% url 'horizon:project:shares:share_network_detail' share_instance.share_network_id as sn_url%} + {% url 'horizon:admin:share_networks:share_network_detail' share_instance.share_network_id as sn_url%}
{{ share_instance.share_network_id }}
{% endif %} {% if share_instance.share_server_id %}
{% trans "Share server" %}
- {% url 'horizon:admin:shares:share_server_detail' share_instance.share_server_id as share_server_url%} + {% url 'horizon:admin:share_servers:share_server_detail' share_instance.share_server_id as share_server_url%}
{{ share_instance.share_server_id }}
{% endif %}
{% trans "Created" %}
diff --git a/manila_ui/dashboards/admin/shares/templates/shares/share_instance_detail.html b/manila_ui/dashboards/admin/share_instances/templates/share_instances/detail.html similarity index 100% rename from manila_ui/dashboards/admin/shares/templates/shares/share_instance_detail.html rename to manila_ui/dashboards/admin/share_instances/templates/share_instances/detail.html diff --git a/manila_ui/dashboards/admin/share_instances/templates/share_instances/index.html b/manila_ui/dashboards/admin/share_instances/templates/share_instances/index.html new file mode 100644 index 00000000..499c8e76 --- /dev/null +++ b/manila_ui/dashboards/admin/share_instances/templates/share_instances/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Share Instances" %}{% endblock %} + +{% block main %} +
+
+ {{ share_instances_table.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/share_instances/urls.py b/manila_ui/dashboards/admin/share_instances/urls.py new file mode 100644 index 00000000..03a673ae --- /dev/null +++ b/manila_ui/dashboards/admin/share_instances/urls.py @@ -0,0 +1,27 @@ +# 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 django.conf import urls + +from manila_ui.dashboards.admin.share_instances import views + + +urlpatterns = [ + urls.url( + r'^$', + views.ShareInstancesView.as_view(), + name='index'), + urls.url( + r'^(?P[^/]+)$', + views.ShareInstanceDetailView.as_view(), + name='share_instance_detail'), +] diff --git a/manila_ui/dashboards/admin/share_instances/views.py b/manila_ui/dashboards/admin/share_instances/views.py new file mode 100644 index 00000000..6f8011a6 --- /dev/null +++ b/manila_ui/dashboards/admin/share_instances/views.py @@ -0,0 +1,87 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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. + +""" +Admin views for managing share instances. +""" + +from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions +from horizon import tables +from horizon import tabs +from horizon.utils import memoized + +from manila_ui.api import manila +from manila_ui.dashboards.admin.share_instances import tables as si_tables +from manila_ui.dashboards.admin.share_instances import tabs as si_tabs +from manila_ui.dashboards import utils as ui_utils + + +class ShareInstancesView(tables.MultiTableView): + table_classes = ( + si_tables.ShareInstancesTable, + ) + template_name = "admin/share_instances/index.html" + page_title = _("Share Instances") + + @memoized.memoized_method + def get_share_instances_data(self): + try: + share_instances = manila.share_instance_list(self.request) + except Exception: + share_instances = [] + exceptions.handle( + self.request, _("Unable to retrieve share instances.")) + return share_instances + + +class ShareInstanceDetailView(tabs.TabView): + tab_group_class = si_tabs.ShareInstanceDetailTabs + template_name = 'admin/share_instances/detail.html' + + def get_context_data(self, **kwargs): + context = super(self.__class__, self).get_context_data(**kwargs) + share_instance = self.get_data() + context["share_instance"] = share_instance + context["page_title"] = ( + _("Share Instance Details: %s") % share_instance.id) + return context + + @memoized.memoized_method + def get_data(self): + try: + share_instance_id = self.kwargs['share_instance_id'] + share_instance = manila.share_instance_get( + self.request, share_instance_id) + share_instance.export_locations = ( + manila.share_instance_export_location_list( + self.request, share_instance_id)) + export_locations = [ + exp['path'] for exp in share_instance.export_locations + ] + share_instance.el_size = ui_utils.calculate_longest_str_size( + export_locations) + return share_instance + except Exception: + redirect = reverse('horizon:admin:share_instances:index') + exceptions.handle( + self.request, + _('Unable to retrieve share instance details.'), + redirect=redirect) + + def get_tabs(self, request, *args, **kwargs): + share_instance = self.get_data() + return self.tab_group_class( + request, share_instance=share_instance, **kwargs) diff --git a/manila_ui/dashboards/admin/share_networks/__init__.py b/manila_ui/dashboards/admin/share_networks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_networks/forms.py b/manila_ui/dashboards/admin/share_networks/forms.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_networks/panel.py b/manila_ui/dashboards/admin/share_networks/panel.py new file mode 100644 index 00000000..1148862d --- /dev/null +++ b/manila_ui/dashboards/admin/share_networks/panel.py @@ -0,0 +1,28 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ +import horizon +from openstack_dashboard.dashboards.admin import dashboard + + +class ShareNetworks(horizon.Panel): + name = _("Share Networks") + slug = 'share_networks' + permissions = ( + 'openstack.services.share', + ) + + +dashboard.Admin.register(ShareNetworks) diff --git a/manila_ui/dashboards/admin/share_networks/tables.py b/manila_ui/dashboards/admin/share_networks/tables.py new file mode 100644 index 00000000..18fbc01f --- /dev/null +++ b/manila_ui/dashboards/admin/share_networks/tables.py @@ -0,0 +1,50 @@ +# 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 django.utils.translation import ugettext_lazy as _ +from horizon import tables +import six + +import manila_ui.dashboards.project.shares.share_networks.tables as sn_tables + + +class ShareNetworksTable(tables.DataTable): + name = tables.WrappingColumn( + "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 six.text_type(share_network.id) + + def get_object_id(self, share_network): + return six.text_type(share_network.id) + + class Meta(object): + name = "share_networks" + verbose_name = _("Share Networks") + table_actions = ( + tables.NameFilterAction, + sn_tables.Delete, + ) + row_class = sn_tables.UpdateRow + row_actions = ( + sn_tables.Delete, + ) diff --git a/manila_ui/dashboards/admin/share_networks/tabs.py b/manila_ui/dashboards/admin/share_networks/tabs.py new file mode 100644 index 00000000..cea494ed --- /dev/null +++ b/manila_ui/dashboards/admin/share_networks/tabs.py @@ -0,0 +1,32 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ +from horizon import tabs + + +class ShareNetworkOverviewTab(tabs.Tab): + name = _("Share Network Overview") + slug = "share_network_overview_tab" + template_name = "admin/share_networks/_detail.html" + + def get_context_data(self, request): + return {"share_network": self.tab_group.kwargs["share_network"]} + + +class ShareNetworkDetailTabs(tabs.TabGroup): + slug = "share_network_details" + tabs = ( + ShareNetworkOverviewTab, + ) 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 new file mode 100644 index 00000000..e0089661 --- /dev/null +++ b/manila_ui/dashboards/admin/share_networks/templates/share_networks/_detail.html @@ -0,0 +1,56 @@ +{% 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 }}
+ {% 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 %} + {% endif %} +
+
+ +
+

{% trans "Net Details" %}

+
+
+
{% trans "Network" %}
+ {% if share_network.neutron_net %} +
{{ share_network.neutron_net }}
+
{% trans "Subnet" %}
+
{{ share_network.neutron_subnet}}
+ {% endif %} + {% if share_network.nova_net %} +
{{ share_network.nova_net }}
+ {% endif %} +
+
+ +
+

{% 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 %} +
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 new file mode 100644 index 00000000..208776dc --- /dev/null +++ b/manila_ui/dashboards/admin/share_networks/templates/share_networks/detail.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Share Network Details" %}{% endblock %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/share_networks/templates/share_networks/index.html b/manila_ui/dashboards/admin/share_networks/templates/share_networks/index.html new file mode 100644 index 00000000..a9285d56 --- /dev/null +++ b/manila_ui/dashboards/admin/share_networks/templates/share_networks/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Share Networks" %}{% endblock %} + +{% block main %} +
+
+ {{ share_networks_table.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/share_networks/urls.py b/manila_ui/dashboards/admin/share_networks/urls.py new file mode 100644 index 00000000..2f612157 --- /dev/null +++ b/manila_ui/dashboards/admin/share_networks/urls.py @@ -0,0 +1,27 @@ +# 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 django.conf import urls + +from manila_ui.dashboards.admin.share_networks import views + + +urlpatterns = [ + urls.url( + r'^$', + views.ShareNetworksView.as_view(), + name='index'), + urls.url( + r'^(?P[^/]+)$', + views.ShareNetworkDetailView.as_view(), + name='share_network_detail'), +] diff --git a/manila_ui/dashboards/admin/share_networks/views.py b/manila_ui/dashboards/admin/share_networks/views.py new file mode 100644 index 00000000..7a2842e2 --- /dev/null +++ b/manila_ui/dashboards/admin/share_networks/views.py @@ -0,0 +1,69 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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. + +""" +Admin views for managing share networks. +""" + +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_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 +from manila_ui.dashboards.admin.share_networks import tabs as sn_tabs +from manila_ui.dashboards.admin import utils +import manila_ui.dashboards.project.shares.share_networks.views as p_views + + +class ShareNetworksView(tables.MultiTableView): + table_classes = ( + sn_tables.ShareNetworksTable, + ) + template_name = "admin/share_networks/index.html" + page_title = _("Share Networks") + + @memoized.memoized_method + 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 "-" + except Exception: + share_networks = [] + exceptions.handle( + self.request, _("Unable to retrieve share networks")) + utils.set_project_name_to_objects(self.request, share_networks) + return share_networks + + +class ShareNetworkDetailView(p_views.Detail): + tab_group_class = sn_tabs.ShareNetworkDetailTabs + template_name = "admin/share_networks/detail.html" + redirect_url = reverse_lazy("horizon:admin:share_networks:index") diff --git a/manila_ui/dashboards/admin/share_servers/__init__.py b/manila_ui/dashboards/admin/share_servers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_servers/forms.py b/manila_ui/dashboards/admin/share_servers/forms.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_servers/panel.py b/manila_ui/dashboards/admin/share_servers/panel.py new file mode 100644 index 00000000..0bdf49a4 --- /dev/null +++ b/manila_ui/dashboards/admin/share_servers/panel.py @@ -0,0 +1,28 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ +import horizon +from openstack_dashboard.dashboards.admin import dashboard + + +class ShareServers(horizon.Panel): + name = _("Share Servers") + slug = 'share_servers' + permissions = ( + 'openstack.services.share', + ) + + +dashboard.Admin.register(ShareServers) diff --git a/manila_ui/dashboards/admin/share_servers/tables.py b/manila_ui/dashboards/admin/share_servers/tables.py new file mode 100644 index 00000000..c35f4b89 --- /dev/null +++ b/manila_ui/dashboards/admin/share_servers/tables.py @@ -0,0 +1,118 @@ +# 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 django.core.urlresolvers import reverse +from django.template.defaultfilters import title # noqa +from django.utils.translation import pgettext_lazy +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy +from horizon import tables +import six + +from manila_ui.api import manila + + +class DeleteShareServer(tables.DeleteAction): + policy_rules = (("share", "share_server:delete"),) + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Share Server", + u"Delete Share Server", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Share Server", + u"Deleted Share Server", + count + ) + + def delete(self, request, obj_id): + manila.share_server_delete(request, obj_id) + + def allowed(self, request, share_serv): + if share_serv: + share_search_opts = {'share_server_id': share_serv.id} + shares_list = manila.share_list( + request, search_opts=share_search_opts) + if shares_list: + return False + return share_serv.status not in ("deleting", "creating") + return True + + +class UpdateShareServerRow(tables.Row): + ajax = True + + def get_data(self, request, share_serv_id): + share_serv = manila.share_server_get(request, share_serv_id) + return share_serv + + +class ShareServersTable(tables.DataTable): + STATUS_CHOICES = ( + ("active", True), + ("deleting", None), + ("creating", None), + ("error", False), + ) + STATUS_DISPLAY_CHOICES = ( + ("in-use", pgettext_lazy("Current status of share server", u"In-use")), + ("active", pgettext_lazy("Current status of share server", u"Active")), + ("creating", pgettext_lazy("Current status of share server", + u"Creating")), + ("error", pgettext_lazy("Current status of share server", + u"Error")), + ) + uid = tables.Column( + "id", verbose_name=_("Id"), + link="horizon:admin:share_servers:share_server_detail") + host = tables.Column("host", verbose_name=_("Host")) + project = tables.Column("project_name", verbose_name=_("Project")) + + def get_share_server_link(share_serv): + if hasattr(share_serv, 'share_network_id'): + return reverse("horizon:admin:share_networks:share_network_detail", + args=(share_serv.share_network_id,)) + else: + return None + + share_net_name = tables.Column( + "share_network_name", verbose_name=_("Share Network"), + link=get_share_server_link) + status = tables.Column( + "status", verbose_name=_("Status"), + status=True, filters=(title,), status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + + def get_object_display(self, share_server): + return six.text_type(share_server.id) + + def get_object_id(self, share_server): + return six.text_type(share_server.id) + + class Meta(object): + name = "share_servers" + status_columns = ["status"] + verbose_name = _("Share Server") + table_actions = ( + tables.NameFilterAction, + DeleteShareServer, + ) + row_class = UpdateShareServerRow + row_actions = ( + DeleteShareServer, + ) diff --git a/manila_ui/dashboards/admin/share_servers/tabs.py b/manila_ui/dashboards/admin/share_servers/tabs.py new file mode 100644 index 00000000..4eb7d428 --- /dev/null +++ b/manila_ui/dashboards/admin/share_servers/tabs.py @@ -0,0 +1,32 @@ +# Copyright 2014 OpenStack Foundation +# +# 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 django.utils.translation import ugettext_lazy as _ +from horizon import tabs + + +class ShareServerOverviewTab(tabs.Tab): + name = _("Share Server Overview") + slug = "share_server_overview_tab" + template_name = "admin/share_servers/_detail.html" + + def get_context_data(self, request): + return {"share_server": self.tab_group.kwargs["share_server"]} + + +class ShareServerDetailTabs(tabs.TabGroup): + slug = "share_server_details" + tabs = ( + ShareServerOverviewTab, + ) diff --git a/manila_ui/dashboards/admin/shares/templates/shares/_detail_share_server.html b/manila_ui/dashboards/admin/share_servers/templates/share_servers/_detail.html similarity index 83% rename from manila_ui/dashboards/admin/shares/templates/shares/_detail_share_server.html rename to manila_ui/dashboards/admin/share_servers/templates/share_servers/_detail.html index 60cefc40..4208614b 100644 --- a/manila_ui/dashboards/admin/shares/templates/shares/_detail_share_server.html +++ b/manila_ui/dashboards/admin/share_servers/templates/share_servers/_detail.html @@ -12,7 +12,7 @@
{{ share_server.host}}
{% trans "Share Network" %}
{% if share_server.share_network_id %} - {% url 'horizon:admin:shares:share_network_detail' share_server.share_network_id as share_net_url %} + {% url 'horizon:admin:share_networks:share_network_detail' share_server.share_network_id as share_net_url %}
{{ share_server.share_network_name }}
{% else %}
{{ share_server.share_network_name }}
@@ -30,9 +30,9 @@

{% trans "Specs" %}


- {% for key in share_server.backend_details %} + {% for key, value in share_server.backend_details.items %}
{{ key }}
-
{{ share_server.backend_details|get_item:key }}
+
{{ value }}
{% endfor %}
diff --git a/manila_ui/dashboards/admin/share_servers/templates/share_servers/detail.html b/manila_ui/dashboards/admin/share_servers/templates/share_servers/detail.html new file mode 100644 index 00000000..12ddf882 --- /dev/null +++ b/manila_ui/dashboards/admin/share_servers/templates/share_servers/detail.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Share Server Details" %}{% endblock %} + +{% block main %} +
+
+ {{ tab_group.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/share_servers/templates/share_servers/index.html b/manila_ui/dashboards/admin/share_servers/templates/share_servers/index.html new file mode 100644 index 00000000..9daa0fe4 --- /dev/null +++ b/manila_ui/dashboards/admin/share_servers/templates/share_servers/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Share Servers" %}{% endblock %} + +{% block main %} +
+
+ {{ share_servers_table.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/share_servers/urls.py b/manila_ui/dashboards/admin/share_servers/urls.py new file mode 100644 index 00000000..0618e19c --- /dev/null +++ b/manila_ui/dashboards/admin/share_servers/urls.py @@ -0,0 +1,27 @@ +# 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 django.conf import urls + +from manila_ui.dashboards.admin.share_servers import views + + +urlpatterns = [ + urls.url( + r'^$', + views.ShareServersView.as_view(), + name='index'), + urls.url( + r'^(?P[^/]+)$', + views.ShareServerDetailView.as_view(), + name='share_server_detail'), +] diff --git a/manila_ui/dashboards/admin/share_servers/views.py b/manila_ui/dashboards/admin/share_servers/views.py new file mode 100644 index 00000000..d55484d3 --- /dev/null +++ b/manila_ui/dashboards/admin/share_servers/views.py @@ -0,0 +1,91 @@ +# Copyright 2012 Nebula, Inc. +# +# 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. + +""" +Admin views for managing share servers. +""" + +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions +from horizon import tables +from horizon import tabs +from horizon.utils import memoized + +from manila_ui.api import manila +from manila_ui.dashboards.admin.share_servers import tables as ss_tables +from manila_ui.dashboards.admin.share_servers import tabs as ss_tabs +from manila_ui.dashboards.admin import utils + + +class ShareServersView(tables.MultiTableView): + table_classes = ( + ss_tables.ShareServersTable, + ) + template_name = "admin/share_servers/index.html" + page_title = _("Share Servers") + + @memoized.memoized_method + def get_share_servers_data(self): + try: + share_servers = manila.share_server_list(self.request) + except Exception: + share_servers = [] + exceptions.handle( + self.request, _("Unable to retrieve share servers")) + utils.set_project_name_to_objects(self.request, share_servers) + return share_servers + + +class ShareServerDetailView(tabs.TabView): + tab_group_class = ss_tabs.ShareServerDetailTabs + template_name = "admin/share_servers/detail.html" + redirect_url = reverse_lazy('horizon:admin:share_servers:index') + + def get_context_data(self, **kwargs): + context = super(self.__class__, self).get_context_data(**kwargs) + share_server = self.get_data() + share_server_display_name = share_server.id + context["share_server"] = share_server + context["share_server_display_name"] = share_server_display_name + context["page_title"] = _("Share Server Details: %(server_name)s") % { + 'server_name': share_server_display_name} + return context + + @memoized.memoized_method + def get_data(self): + try: + share_serv_id = self.kwargs['share_server_id'] + share_serv = manila.share_server_get(self.request, share_serv_id) + share_search_opts = {'share_server_id': share_serv.id} + shares_list = manila.share_list( + self.request, search_opts=share_search_opts) + for share in shares_list: + share.name_or_id = share.name or share.id + share_serv.shares_list = shares_list + if not hasattr(share_serv, 'share_network_id'): + share_serv.share_network_id = None + except Exception: + redirect = reverse('horizon:admin:share_servers:index') + exceptions.handle( + self.request, + _('Unable to retrieve share server details.'), + redirect=redirect) + return share_serv + + def get_tabs(self, request, *args, **kwargs): + share_server = self.get_data() + return self.tab_group_class( + request, share_server=share_server, **kwargs) diff --git a/manila_ui/dashboards/admin/share_snapshots/__init__.py b/manila_ui/dashboards/admin/share_snapshots/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_snapshots/forms.py b/manila_ui/dashboards/admin/share_snapshots/forms.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_snapshots/panel.py b/manila_ui/dashboards/admin/share_snapshots/panel.py new file mode 100644 index 00000000..f0eba34b --- /dev/null +++ b/manila_ui/dashboards/admin/share_snapshots/panel.py @@ -0,0 +1,28 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ +import horizon +from openstack_dashboard.dashboards.admin import dashboard + + +class ShareSnapshots(horizon.Panel): + name = _("Share Snapshots") + slug = 'share_snapshots' + permissions = ( + 'openstack.services.share', + ) + + +dashboard.Admin.register(ShareSnapshots) diff --git a/manila_ui/dashboards/admin/share_snapshots/tables.py b/manila_ui/dashboards/admin/share_snapshots/tables.py new file mode 100644 index 00000000..7316198b --- /dev/null +++ b/manila_ui/dashboards/admin/share_snapshots/tables.py @@ -0,0 +1,128 @@ +# 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 django.core.urlresolvers import reverse +from django.template.defaultfilters import title # noqa +from django.utils.translation import pgettext_lazy +from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy +from horizon import exceptions +from horizon import tables + +from manila_ui.api import manila +from manila_ui.dashboards.project.shares.shares import tables as shares_tables +import manila_ui.dashboards.project.shares.snapshots.tables as ss_tables + + +def get_size(share): + return _("%sGiB") % share.size + + +class ShareSnapshotShareNameColumn(tables.Column): + def get_link_url(self, snapshot): + return reverse(self.link, args=(snapshot.share_id,)) + + +class DeleteShareSnapshot(tables.DeleteAction): + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Snapshot", + u"Delete Snapshots", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Snapshot", + u"Deleted Snapshots", + count + ) + + def get_policy_target(self, request, datum=None): + project_id = None + if datum: + project_id = getattr(datum, "project_id", None) + return {"project_id": project_id} + + def delete(self, request, obj_id): + obj = self.table.get_object_by_id(obj_id) + name = self.table.get_object_display(obj) + try: + manila.share_snapshot_delete(request, obj_id) + except Exception: + msg = _('Unable to delete snapshot "%s". One or more shares ' + 'depend on it.') + exceptions.check_message(["snapshots", "dependent"], msg % name) + raise + + def allowed(self, request, snapshot=None): + if snapshot: + return snapshot.status.upper() in shares_tables.DELETABLE_STATES + return True + + +class ShareSnapshotsTable(tables.DataTable): + STATUS_CHOICES = ( + ("in-use", True), + ("available", True), + ("creating", None), + ("error", False), + ) + STATUS_DISPLAY_CHOICES = ( + ("in-use", pgettext_lazy("Current status of snapshot", u"In-use")), + ("available", + pgettext_lazy("Current status of snapshot", u"Available")), + ("creating", pgettext_lazy("Current status of snapshot", u"Creating")), + ("error", pgettext_lazy("Current status of snapshot", u"Error")), + ) + name = tables.WrappingColumn( + "name", verbose_name=_("Name"), + link="horizon:admin:share_snapshots:share_snapshot_detail") + description = tables.Column( + "description", + verbose_name=_("Description"), + truncate=40) + size = tables.Column( + get_size, + verbose_name=_("Size"), + attrs={'data-type': 'size'}) + status = tables.Column( + "status", + filters=(title,), + verbose_name=_("Status"), + status=True, + status_choices=STATUS_CHOICES, + display_choices=STATUS_DISPLAY_CHOICES) + source = ShareSnapshotShareNameColumn( + "share", + verbose_name=_("Source"), + link="horizon:admin:shares:detail") + + def get_object_display(self, obj): + return obj.name + + class Meta(object): + name = "share_snapshots" + verbose_name = _("Share Snapshots") + status_columns = ["status"] + row_class = ss_tables.UpdateRow + table_actions = ( + tables.NameFilterAction, + DeleteShareSnapshot, + ) + row_actions = ( + DeleteShareSnapshot, + ) diff --git a/manila_ui/dashboards/admin/share_snapshots/tabs.py b/manila_ui/dashboards/admin/share_snapshots/tabs.py new file mode 100644 index 00000000..a7ee72ec --- /dev/null +++ b/manila_ui/dashboards/admin/share_snapshots/tabs.py @@ -0,0 +1,32 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ +from horizon import tabs + + +class ShareSnapshotOverviewTab(tabs.Tab): + name = _("Share Snapshot Overview") + slug = "share_snapshot_overview_tab" + template_name = "admin/share_snapshots/_detail.html" + + def get_context_data(self, request): + return {"snapshot": self.tab_group.kwargs["snapshot"]} + + +class SnapshotDetailTabs(tabs.TabGroup): + slug = "share_snapshot_details" + tabs = ( + ShareSnapshotOverviewTab, + ) diff --git a/manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/_detail.html b/manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/_detail.html new file mode 100644 index 00000000..9eb0401b --- /dev/null +++ b/manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/_detail.html @@ -0,0 +1,66 @@ +{% load i18n sizeformat parse_date %} + +

{% trans "Share Snapshot Overview" %}

+
+
+
+
{% trans "Name" %}
+
{{ snapshot.name }}
+
{% trans "ID" %}
+
{{ snapshot.id }}
+ {% url 'horizon:admin:shares:detail' snapshot.share_id as share_url %} +
{% trans "Source" %}
+
{{ snapshot.share_name_or_id }}
+ {% if snapshot.description %} +
{% trans "Description" %}
+
{{ snapshot.description }}
+ {% endif %} +
{% trans "Status" %}
+
{{ snapshot.status|capfirst }}
+ {% if snapshot.export_locations %} +
{% trans "Export locations" %}
+ {% for el in snapshot.export_locations %} +

+

Path: + +
+ {% if el.is_admin_only != None %} +
Is admin only: {{ el.is_admin_only }}
+ {% endif %} + {% if el.share_snapshot_instance_id %} +
Snapshot Replica ID: {{ el.share_snapshot_instance_id }}
+ {% endif %} +

+ {% endfor %} + {% endif %} +
+
+ +{% if snapshot.rules != None %} +
+

{% trans "Access Rules" %}

+
+
+ {% for rule in snapshot.rules %} +
{{ rule.access_type }}
+

+

Access to: {{ rule.access_to }}
+
Status: {{ rule.state }}
+

+ {% endfor %} +
+
+{% endif %} + +
+

{% trans "Specs" %}

+
+
+
{% trans "Size" %}
+
{{ snapshot.size }} {% trans "GiB" %}
+
{% trans "Created" %}
+
{{ snapshot.created_at|parse_date }}
+
+
diff --git a/manila_ui/dashboards/admin/shares/templates/shares/detail_share_server.html b/manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/detail.html similarity index 100% rename from manila_ui/dashboards/admin/shares/templates/shares/detail_share_server.html rename to manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/detail.html diff --git a/manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/index.html b/manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/index.html new file mode 100644 index 00000000..4fb51061 --- /dev/null +++ b/manila_ui/dashboards/admin/share_snapshots/templates/share_snapshots/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Shares" %}{% endblock %} + +{% block main %} +
+
+ {{ share_snapshots_table.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/share_snapshots/urls.py b/manila_ui/dashboards/admin/share_snapshots/urls.py new file mode 100644 index 00000000..71c66df1 --- /dev/null +++ b/manila_ui/dashboards/admin/share_snapshots/urls.py @@ -0,0 +1,27 @@ +# 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 django.conf import urls + +from manila_ui.dashboards.admin.share_snapshots import views + + +urlpatterns = [ + urls.url( + r'^$', + views.ShareSnapshotsView.as_view(), + name='index'), + urls.url( + r'^(?P[^/]+)$', + views.ShareSnapshotDetailView.as_view(), + name='share_snapshot_detail'), +] diff --git a/manila_ui/dashboards/admin/share_snapshots/views.py b/manila_ui/dashboards/admin/share_snapshots/views.py new file mode 100644 index 00000000..61868802 --- /dev/null +++ b/manila_ui/dashboards/admin/share_snapshots/views.py @@ -0,0 +1,63 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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. + +""" +Admin views for managing share snapshots. +""" + +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions +from horizon import tables +from horizon.utils import memoized + +from manila_ui.api import manila +from manila_ui.dashboards.admin.share_snapshots import tables as ss_tables +from manila_ui.dashboards.admin.share_snapshots import tabs as ss_tabs +from manila_ui.dashboards.admin import utils +import manila_ui.dashboards.project.shares.snapshots.views as snapshot_views + + +class ShareSnapshotsView(tables.MultiTableView): + table_classes = ( + ss_tables.ShareSnapshotsTable, + ) + template_name = "admin/share_snapshots/index.html" + page_title = _("Share Snapshots") + + @memoized.memoized_method + def get_share_snapshots_data(self): + snapshots = [] + try: + snapshots = manila.share_snapshot_list( + self.request, search_opts={'all_tenants': True}) + shares = manila.share_list(self.request) + share_names = dict([(share.id, share.name or share.id) + for share in shares]) + for snapshot in snapshots: + snapshot.share = share_names.get(snapshot.share_id) + except Exception: + msg = _("Unable to retrieve share snapshot list.") + exceptions.handle(self.request, msg) + + # Gather our projects to correlate against IDs + utils.set_project_name_to_objects(self.request, snapshots) + + return snapshots + + +class ShareSnapshotDetailView(snapshot_views.SnapshotDetailView): + tab_group_class = ss_tabs.SnapshotDetailTabs + template_name = "admin/share_snapshots/detail.html" + redirect_url = reverse_lazy("horizon:admin:share_snapshots:index") diff --git a/manila_ui/dashboards/admin/share_types/__init__.py b/manila_ui/dashboards/admin/share_types/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/dashboards/admin/share_types/forms.py b/manila_ui/dashboards/admin/share_types/forms.py new file mode 100644 index 00000000..edee5ef4 --- /dev/null +++ b/manila_ui/dashboards/admin/share_types/forms.py @@ -0,0 +1,133 @@ +# Copyright (c) 2014 NetApp, 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 django.conf import settings +from django.forms import ValidationError # noqa +from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions +from horizon import forms +from horizon import messages + +from manila_ui.api import manila +from manila_ui.dashboards import utils + + +ST_EXTRA_SPECS_FORM_ATTRS = { + "rows": 5, + "cols": 40, + "style": "height: 135px; width: 100%;", # in case 'rows' not picked up +} + + +class CreateShareType(forms.SelfHandlingForm): + name = forms.CharField(max_length="255", label=_("Name"), required=True) + spec_driver_handles_share_servers = forms.ChoiceField( + label=_("Driver handles share servers"), required=True, + choices=(('False', 'False'), ('True', 'True'))) + extra_specs = forms.CharField( + required=False, label=_("Extra specs"), + widget=forms.widgets.Textarea(attrs=ST_EXTRA_SPECS_FORM_ATTRS)) + is_public = forms.BooleanField( + label=_("Public"), required=False, initial=True, + help_text=("Defines whether this share type is available for all " + "or not. List of allowed tenants should be set " + "separately.")) + + def __init__(self, *args, **kwargs): + super(CreateShareType, self).__init__(*args, **kwargs) + manila_features = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {}) + self.enable_public_share_type_creation = manila_features.get( + 'enable_public_share_type_creation', True) + if not self.enable_public_share_type_creation: + self.fields.pop('is_public') + + def handle(self, request, data): + try: + spec_dhss = data['spec_driver_handles_share_servers'].lower() + allowed_dhss_values = ('true', 'false') + if spec_dhss not in allowed_dhss_values: + msg = _("Improper value set to required extra spec " + "'spec_driver_handles_share_servers'. " + "Allowed values are %s. " + "Case insensitive.") % allowed_dhss_values + raise ValidationError(message=msg) + + set_dict, unset_list = utils.parse_str_meta(data['extra_specs']) + if unset_list: + msg = _("Expected only pairs of key=value.") + raise ValidationError(message=msg) + + is_public = (self.enable_public_share_type_creation + and data["is_public"]) + share_type = manila.share_type_create( + request, data["name"], spec_dhss, is_public=is_public) + if set_dict: + manila.share_type_set_extra_specs( + request, share_type.id, set_dict) + + msg = _("Successfully created share type: %s") % share_type.name + messages.success(request, msg) + return True + except ValidationError as e: + # handle error without losing dialog + self.api_error(e.messages[0]) + return False + except Exception: + exceptions.handle(request, _('Unable to create share type.')) + return False + + +class UpdateShareType(forms.SelfHandlingForm): + + def __init__(self, *args, **kwargs): + super(UpdateShareType, self).__init__(*args, **kwargs) + # NOTE(vponomaryov): parse existing extra specs + # to str view for textarea html element + es_str = "" + for k, v in self.initial["extra_specs"].iteritems(): + es_str += "%s=%s\r\n" % (k, v) + self.initial["extra_specs"] = es_str + + extra_specs = forms.CharField( + required=False, label=_("Extra specs"), + widget=forms.widgets.Textarea(attrs=ST_EXTRA_SPECS_FORM_ATTRS)) + + def handle(self, request, data): + try: + set_dict, unset_list = utils.parse_str_meta(data['extra_specs']) + if set_dict: + manila.share_type_set_extra_specs( + request, self.initial["id"], set_dict) + if unset_list: + get = manila.share_type_get_extra_specs( + request, self.initial["id"]) + + # NOTE(vponomaryov): skip keys that are already unset + to_unset = set(unset_list).intersection(set(get.keys())) + if to_unset: + manila.share_type_unset_extra_specs( + request, self.initial["id"], to_unset) + msg = _("Successfully updated extra specs for share type '%s'.") + msg = msg % self.initial['name'] + messages.success(request, msg) + return True + except ValidationError as e: + # handle error without losing dialog + self.api_error(e.messages[0]) + return False + except Exception: + msg = _("Unable to update extra_specs for share type.") + exceptions.handle(request, msg) + return False diff --git a/manila_ui/dashboards/admin/share_types/panel.py b/manila_ui/dashboards/admin/share_types/panel.py new file mode 100644 index 00000000..165bcb23 --- /dev/null +++ b/manila_ui/dashboards/admin/share_types/panel.py @@ -0,0 +1,30 @@ +# Copyright 2017 Mirantis, Inc. +# +# 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 django.utils.translation import ugettext_lazy as _ + +import horizon + +from openstack_dashboard.dashboards.admin import dashboard + + +class ShareTypes(horizon.Panel): + name = _("Share Types") + slug = 'share_types' + permissions = ( + 'openstack.services.share', + ) + + +dashboard.Admin.register(ShareTypes) diff --git a/manila_ui/dashboards/admin/share_types/tables.py b/manila_ui/dashboards/admin/share_types/tables.py new file mode 100644 index 00000000..d4c43d6e --- /dev/null +++ b/manila_ui/dashboards/admin/share_types/tables.py @@ -0,0 +1,111 @@ +# 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 django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ungettext_lazy +from horizon import tables +import six + +from manila_ui.api import manila + + +def get_size(share): + return _("%sGiB") % share.size + + +class CreateShareType(tables.LinkAction): + name = "create" + verbose_name = _("Create Share Type") + url = "horizon:admin:share_types:create_type" + classes = ("ajax-modal", "btn-create") + icon = "plus" + + +class DeleteShareType(tables.DeleteAction): + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Delete Share Type", + u"Delete Share Types", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Deleted Share Type", + u"Deleted Share Types", + count + ) + + def delete(self, request, obj_id): + manila.share_type_delete(request, obj_id) + + +class ManageShareTypeAccess(tables.LinkAction): + name = "manage" + verbose_name = _("Manage Share Type Access") + url = "horizon:admin:share_types:manage_share_type_access" + classes = ("ajax-modal", "btn-create") + + def allowed(self, request, obj_id): + st = manila.share_type_get(request, obj_id) + # Enable it only for private share types + return not st.is_public + + def get_policy_target(self, request, datum=None): + project_id = None + if datum: + project_id = getattr(datum, "os-share-tenant-attr:tenant_id", None) + return {"project_id": project_id} + + +class UpdateShareType(tables.LinkAction): + name = "update share type" + verbose_name = _("Update Share Type") + url = "horizon:admin:share_types:update_type" + classes = ("ajax-modal", "btn-create") + + def get_policy_target(self, request, datum=None): + project_id = None + if datum: + project_id = getattr(datum, "os-share-tenant-attr:tenant_id", None) + return {"project_id": project_id} + + +class ShareTypesTable(tables.DataTable): + name = tables.WrappingColumn("name", verbose_name=_("Name")) + extra_specs = tables.Column("extra_specs", verbose_name=_("Extra specs"), ) + visibility = tables.Column( + "is_public", verbose_name=_("Visibility"), + filters=(lambda d: 'public' if d is True else 'private', ), + ) + + def get_object_display(self, share_type): + return share_type.name + + def get_object_id(self, share_type): + return six.text_type(share_type.id) + + class Meta(object): + name = "share_types" + verbose_name = _("Share Types") + table_actions = ( + tables.NameFilterAction, + CreateShareType, + DeleteShareType, + ) + row_actions = ( + UpdateShareType, + ManageShareTypeAccess, + DeleteShareType, + ) diff --git a/manila_ui/dashboards/admin/shares/templates/shares/_create_share_type.html b/manila_ui/dashboards/admin/share_types/templates/share_types/_create.html similarity index 100% rename from manila_ui/dashboards/admin/shares/templates/shares/_create_share_type.html rename to manila_ui/dashboards/admin/share_types/templates/share_types/_create.html diff --git a/manila_ui/dashboards/admin/shares/templates/shares/_manage_share_type_access.html b/manila_ui/dashboards/admin/share_types/templates/share_types/_manage_access.html similarity index 82% rename from manila_ui/dashboards/admin/shares/templates/shares/_manage_share_type_access.html rename to manila_ui/dashboards/admin/share_types/templates/share_types/_manage_access.html index 41678dc3..cac72a09 100644 --- a/manila_ui/dashboards/admin/shares/templates/shares/_manage_share_type_access.html +++ b/manila_ui/dashboards/admin/share_types/templates/share_types/_manage_access.html @@ -2,9 +2,9 @@ {% load i18n %} {% block form_id %}{% endblock %} -{% block form_action %}{% url 'horizon:admin:shares:manage_share_type_access' share_type.id %}{% endblock %} +{% block form_action %}{% url 'horizon:admin:share_types:manage_share_type_access' share_type.id %}{% endblock %} -{% block modal_id %}add_security_service_modal{% endblock %} +{% block modal_id %}manage_share_type_access_modal{% endblock %} {% block modal-header %}{% trans "Manage Share Type Access" %}{% endblock %} @@ -55,5 +55,5 @@ {% block modal-footer %} - {% trans "Cancel" %} + {% trans "Cancel" %} {% endblock %} diff --git a/manila_ui/dashboards/admin/shares/templates/shares/_update_share_type.html b/manila_ui/dashboards/admin/share_types/templates/share_types/_update.html similarity index 100% rename from manila_ui/dashboards/admin/shares/templates/shares/_update_share_type.html rename to manila_ui/dashboards/admin/share_types/templates/share_types/_update.html diff --git a/manila_ui/dashboards/admin/shares/templates/shares/create_share_type.html b/manila_ui/dashboards/admin/share_types/templates/share_types/create.html similarity index 70% rename from manila_ui/dashboards/admin/shares/templates/shares/create_share_type.html rename to manila_ui/dashboards/admin/share_types/templates/share_types/create.html index 956261ad..3b30cabd 100644 --- a/manila_ui/dashboards/admin/shares/templates/shares/create_share_type.html +++ b/manila_ui/dashboards/admin/share_types/templates/share_types/create.html @@ -3,5 +3,5 @@ {% block title %}{% trans "Create Share Type" %}{% endblock %} {% block main %} - {% include 'admin/shares/_create_share_type.html' %} + {% include 'admin/share_types/_create.html' %} {% endblock %} diff --git a/manila_ui/dashboards/admin/share_types/templates/share_types/index.html b/manila_ui/dashboards/admin/share_types/templates/share_types/index.html new file mode 100644 index 00000000..c8aa4a23 --- /dev/null +++ b/manila_ui/dashboards/admin/share_types/templates/share_types/index.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Share Types" %}{% endblock %} + +{% block main %} +
+
+ {{ share_types_table.render }} +
+
+{% endblock %} diff --git a/manila_ui/dashboards/admin/shares/templates/shares/manage_share_type_access.html b/manila_ui/dashboards/admin/share_types/templates/share_types/manage_access.html similarity index 69% rename from manila_ui/dashboards/admin/shares/templates/shares/manage_share_type_access.html rename to manila_ui/dashboards/admin/share_types/templates/share_types/manage_access.html index 8988efaf..ab49d172 100644 --- a/manila_ui/dashboards/admin/shares/templates/shares/manage_share_type_access.html +++ b/manila_ui/dashboards/admin/share_types/templates/share_types/manage_access.html @@ -3,5 +3,5 @@ {% block title %}{% trans "Manage Share Type Access" %}{% endblock %} {% block main %} - {% include 'admin/shares/_manage_share_type_access.html' %} + {% include 'admin/share_types/_manage_access.html' %} {% endblock %} diff --git a/manila_ui/dashboards/admin/shares/templates/shares/update_share_type.html b/manila_ui/dashboards/admin/share_types/templates/share_types/update.html similarity index 70% rename from manila_ui/dashboards/admin/shares/templates/shares/update_share_type.html rename to manila_ui/dashboards/admin/share_types/templates/share_types/update.html index 74f495b5..6b84da79 100644 --- a/manila_ui/dashboards/admin/shares/templates/shares/update_share_type.html +++ b/manila_ui/dashboards/admin/share_types/templates/share_types/update.html @@ -3,5 +3,5 @@ {% block title %}{% trans "Update Share Type" %}{% endblock %} {% block main %} - {% include 'admin/shares/_update_share_type.html' %} + {% include 'admin/share_types/_update.html' %} {% endblock %} diff --git a/manila_ui/dashboards/admin/share_types/urls.py b/manila_ui/dashboards/admin/share_types/urls.py new file mode 100644 index 00000000..30e4081c --- /dev/null +++ b/manila_ui/dashboards/admin/share_types/urls.py @@ -0,0 +1,35 @@ +# 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 django.conf import urls + +from manila_ui.dashboards.admin.share_types import views + + +urlpatterns = [ + urls.url( + r'^$', + views.ShareTypesView.as_view(), + name='index'), + urls.url( + r'^create_type$', + views.CreateShareTypeView.as_view(), + name='create_type'), + urls.url( + r'^update_type/(?P[^/]+)/extra_specs$', + views.UpdateShareTypeView.as_view(), + name='update_type'), + urls.url( + r'^manage_share_type_access/(?P[^/]+)$', + views.ManageShareTypeAccessView.as_view(), + name='manage_share_type_access'), +] diff --git a/manila_ui/dashboards/admin/share_types/views.py b/manila_ui/dashboards/admin/share_types/views.py new file mode 100644 index 00000000..93370df8 --- /dev/null +++ b/manila_ui/dashboards/admin/share_types/views.py @@ -0,0 +1,119 @@ +# 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. + +""" +Admin views for managing share types. +""" + +from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ +from horizon import exceptions +from horizon import forms +from horizon import tables +from horizon.utils import memoized +from horizon import workflows + +from manila_ui.api import manila +from manila_ui.dashboards.admin.share_types import forms as project_forms +from manila_ui.dashboards.admin.share_types import tables as st_tables +import manila_ui.dashboards.admin.share_types.workflows as st_workflows +from manila_ui.dashboards import utils as common_utils + + +class ShareTypesView(tables.MultiTableView): + table_classes = ( + st_tables.ShareTypesTable, + ) + template_name = "admin/share_types/index.html" + page_title = _("Share Types") + + @memoized.memoized_method + def get_share_types_data(self): + try: + share_types = manila.share_type_list(self.request) + except Exception: + exceptions.handle( + self.request, _('Unable to retrieve share types.')) + return [] + # Convert dict with extra specs to friendly view + for st in share_types: + st.extra_specs = common_utils.metadata_to_str( + st.extra_specs, 8, 45) + return share_types + + +class CreateShareTypeView(forms.ModalFormView): + form_class = project_forms.CreateShareType + form_id = "create_share_type" + template_name = 'admin/share_types/create.html' + modal_header = _("Create Share Type") + submit_label = _("Create") + submit_url = reverse_lazy("horizon:admin:share_types:create_type") + success_url = 'horizon:admin:share_types:index' + page_title = _("Create Share Type") + + def get_success_url(self): + return reverse(self.success_url) + + +class ManageShareTypeAccessView(workflows.WorkflowView): + workflow_class = st_workflows.ManageShareTypeAccessWorkflow + template_name = "admin/share_types/manage_access.html" + success_url = 'horizon:project:share_types:index' + page_title = _("Manage Share Type Access") + + def get_initial(self): + return {'id': self.kwargs["share_type_id"]} + + def get_context_data(self, **kwargs): + context = super(ManageShareTypeAccessView, self).get_context_data( + **kwargs) + context['id'] = self.kwargs['share_type_id'] + return context + + +class UpdateShareTypeView(forms.ModalFormView): + form_class = project_forms.UpdateShareType + form_id = "update_share_type" + template_name = "admin/share_types/update.html" + modal_header = _("Update Share Type") + modal_id = "update_share_type_modal" + submit_label = _("Update") + submit_url = "horizon:admin:share_types:update_type" + success_url = reverse_lazy("horizon:admin:share_types:index") + page_title = _("Update Share Type") + + def get_object(self): + if not hasattr(self, "_object"): + st_id = self.kwargs["share_type_id"] + try: + self._object = manila.share_type_get(self.request, st_id) + except Exception: + msg = _("Unable to retrieve share_type.") + url = reverse("horizon:admin:share_types:index") + exceptions.handle(self.request, msg, redirect=url) + return self._object + + def get_context_data(self, **kwargs): + context = super(UpdateShareTypeView, self).get_context_data(**kwargs) + args = (self.get_object().id,) + context['submit_url'] = reverse(self.submit_url, args=args) + return context + + def get_initial(self): + share_type = self.get_object() + return { + "id": self.kwargs["share_type_id"], + "name": share_type.name, + "extra_specs": share_type.extra_specs, + } diff --git a/manila_ui/dashboards/admin/shares/workflows.py b/manila_ui/dashboards/admin/share_types/workflows.py similarity index 98% rename from manila_ui/dashboards/admin/shares/workflows.py rename to manila_ui/dashboards/admin/share_types/workflows.py index 9d8f2185..3a6124fb 100644 --- a/manila_ui/dashboards/admin/shares/workflows.py +++ b/manila_ui/dashboards/admin/share_types/workflows.py @@ -92,7 +92,7 @@ class ManageShareTypeAccessWorkflow(workflows.Workflow): finalize_button_name = _("Manage Share Type Access") success_message = _('Updated access for share type "%s".') failure_message = _('Unable to update access for share type "%s".') - success_url = 'horizon:admin:shares:index' + success_url = 'horizon:admin:share_types:index' default_steps = (AddProjectStep, ) def format_status_message(self, message): diff --git a/manila_ui/dashboards/admin/shares/forms.py b/manila_ui/dashboards/admin/shares/forms.py index 5ed8862b..2d407ff4 100644 --- a/manila_ui/dashboards/admin/shares/forms.py +++ b/manila_ui/dashboards/admin/shares/forms.py @@ -17,25 +17,23 @@ from django.conf import settings from django.core.urlresolvers import reverse from django.forms import ValidationError # noqa from django.utils.translation import ugettext_lazy as _ -from oslo_utils import strutils -import six - from horizon import exceptions from horizon import forms from horizon import messages +from oslo_utils import strutils +import six from manila_ui.api import manila from manila_ui.dashboards import utils -from openstack_dashboard.api import keystone -from openstack_dashboard.api import neutron - -ST_EXTRA_SPECS_FORM_ATTRS = { - "rows": 5, - "cols": 40, - "style": "height: 135px; width: 100%;", # in case 'rows' not picked up -} +def _get_id_if_name_empty(data): + result = data.get('name', None) + if not result: + result = data.get('id') + if not result: + result = '' + return result class MigrationStart(forms.SelfHandlingForm): @@ -310,189 +308,3 @@ class UnmanageShare(forms.SelfHandlingForm): except Exception: exceptions.handle(request, _("Unable to unmanage share.")) return False - - -class CreateShareType(forms.SelfHandlingForm): - name = forms.CharField(max_length="255", label=_("Name"), required=True) - spec_driver_handles_share_servers = forms.ChoiceField( - label=_("Driver handles share servers"), required=True, - choices=(('False', 'False'), ('True', 'True'))) - extra_specs = forms.CharField( - required=False, label=_("Extra specs"), - widget=forms.widgets.Textarea(attrs=ST_EXTRA_SPECS_FORM_ATTRS)) - is_public = forms.BooleanField( - label=_("Public"), required=False, initial=True, - help_text=("Defines whether this share type is available for all " - "or not. List of allowed tenants should be set " - "separately.")) - - def __init__(self, *args, **kwargs): - super(CreateShareType, self).__init__(*args, **kwargs) - - manila_features = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {}) - self.enable_public_share_type_creation = manila_features.get( - 'enable_public_share_type_creation', True) - if not self.enable_public_share_type_creation: - self.fields.pop('is_public') - - def handle(self, request, data): - try: - spec_dhss = data['spec_driver_handles_share_servers'].lower() - allowed_dhss_values = ('true', 'false') - if spec_dhss not in allowed_dhss_values: - msg = _("Improper value set to required extra spec " - "'spec_driver_handles_share_servers'. " - "Allowed values are %s. " - "Case insensitive.") % allowed_dhss_values - raise ValidationError(message=msg) - - set_dict, unset_list = utils.parse_str_meta(data['extra_specs']) - if unset_list: - msg = _("Expected only pairs of key=value.") - raise ValidationError(message=msg) - - is_public = (self.enable_public_share_type_creation - and data["is_public"]) - share_type = manila.share_type_create( - request, data["name"], spec_dhss, is_public=is_public) - if set_dict: - manila.share_type_set_extra_specs( - request, share_type.id, set_dict) - - msg = _("Successfully created share type: %s") % share_type.name - messages.success(request, msg) - return True - except ValidationError as e: - # handle error without losing dialog - self.api_error(e.messages[0]) - return False - except Exception: - exceptions.handle(request, _('Unable to create share type.')) - return False - - -class UpdateShareType(forms.SelfHandlingForm): - - def __init__(self, *args, **kwargs): - super(UpdateShareType, self).__init__(*args, **kwargs) - # NOTE(vponomaryov): parse existing extra specs - # to str view for textarea html element - es_str = "" - for k, v in self.initial["extra_specs"].iteritems(): - es_str += "%s=%s\r\n" % (k, v) - self.initial["extra_specs"] = es_str - - extra_specs = forms.CharField( - required=False, label=_("Extra specs"), - widget=forms.widgets.Textarea(attrs=ST_EXTRA_SPECS_FORM_ATTRS)) - - def handle(self, request, data): - try: - set_dict, unset_list = utils.parse_str_meta(data['extra_specs']) - if set_dict: - manila.share_type_set_extra_specs( - request, self.initial["id"], set_dict) - if unset_list: - get = manila.share_type_get_extra_specs( - request, self.initial["id"]) - - # NOTE(vponomaryov): skip keys that are already unset - to_unset = set(unset_list).intersection(set(get.keys())) - if to_unset: - manila.share_type_unset_extra_specs( - request, self.initial["id"], to_unset) - msg = _("Successfully updated extra specs for share type '%s'.") - msg = msg % self.initial['name'] - messages.success(request, msg) - return True - except ValidationError as e: - # handle error without losing dialog - self.api_error(e.messages[0]) - return False - except Exception: - msg = _("Unable to update extra_specs for share type.") - exceptions.handle(request, msg) - return False - - -class CreateSecurityService(forms.SelfHandlingForm): - name = forms.CharField(max_length="255", label=_("Name")) - dns_ip = forms.CharField(max_length="15", label=_("DNS IP")) - server = forms.CharField(max_length="255", label=_("Server")) - domain = forms.CharField(max_length="255", label=_("Domain")) - user = forms.CharField(max_length="255", label=_("User")) - password = forms.CharField(max_length="255", label=_("Password")) - type = forms.ChoiceField(choices=(("", ""), - ("active_directory", "Active Directory"), - ("ldap", "LDAP"), - ("kerberos", "Kerberos")), - label=_("Type")) - description = forms.CharField(widget=forms.Textarea, - label=_("Description"), required=False) - - def handle(self, request, data): - try: - # Remove any new lines in the public key - security_service = manila.security_service_create( - request, **data) - messages.success(request, - _('Successfully created security service: %s') - % data['name']) - return security_service - except Exception: - exceptions.handle(request, - _('Unable to create security service.')) - return False - - -class CreateShareNetworkForm(forms.SelfHandlingForm): - name = forms.CharField(max_length="255", label=_("Name")) - neutron_net_id = forms.ChoiceField(choices=(), label=_("Neutron Net ID")) - neutron_subnet_id = forms.ChoiceField(choices=(), - label=_("Neutron Subnet ID")) - # security_service = forms.MultipleChoiceField( - # widget=forms.SelectMultiple, - # label=_("Security Service")) - project = forms.ChoiceField(choices=(), label=_("Project")) - description = forms.CharField(widget=forms.Textarea, - label=_("Description"), required=False) - - def __init__(self, request, *args, **kwargs): - super(CreateShareNetworkForm, self).__init__( - request, *args, **kwargs) - net_choices = neutron.network_list(request) - subnet_choices = neutron.subnet_list(request) - self.fields['neutron_net_id'].choices = [(' ', ' ')] + \ - [(choice.id, choice.name_or_id) - for choice in net_choices] - self.fields['neutron_subnet_id'].choices = [(' ', ' ')] + \ - [(choice.id, - choice.name_or_id) for - choice in subnet_choices] - tenants, has_more = keystone.tenant_list(request) - self.fields['project'].choices = [(' ', ' ')] + \ - [(choice.id, - choice.name) for - choice in tenants] - - def handle(self, request, data): - try: - # Remove any new lines in the public key - share_network = manila.share_network_create(request, **data) - messages.success(request, - _('Successfully created share network: %s') - % data['name']) - return share_network - except Exception: - exceptions.handle(request, - _('Unable to create share network.')) - return False - - -def _get_id_if_name_empty(data): - result = data.get('name', None) - if not result: - result = data.get('id') - if not result: - result = '' - return result diff --git a/manila_ui/dashboards/admin/shares/panel.py b/manila_ui/dashboards/admin/shares/panel.py index fa044fb8..8e5f7e9d 100644 --- a/manila_ui/dashboards/admin/shares/panel.py +++ b/manila_ui/dashboards/admin/shares/panel.py @@ -1,3 +1,5 @@ +# Copyright 2017 Mirantis, Inc. +# # 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 @@ -11,15 +13,16 @@ # under the License. from django.utils.translation import ugettext_lazy as _ - import horizon - from openstack_dashboard.dashboards.admin import dashboard class Shares(horizon.Panel): name = _("Shares") slug = "shares" - permissions = ('openstack.services.share',) + permissions = ( + 'openstack.services.share', + ) + dashboard.Admin.register(Shares) diff --git a/manila_ui/dashboards/admin/shares/tables.py b/manila_ui/dashboards/admin/shares/tables.py index f4adabc0..78de7b17 100644 --- a/manila_ui/dashboards/admin/shares/tables.py +++ b/manila_ui/dashboards/admin/shares/tables.py @@ -10,77 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. -import six - from django.core.urlresolvers import reverse -from django.template.defaultfilters import title # noqa -from django.utils.translation import pgettext_lazy from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import ungettext_lazy - -from horizon import exceptions from horizon import tables + from manila_ui.api import manila -from manila_ui.dashboards.project.shares.share_networks \ - import tables as share_networks_tables from manila_ui.dashboards.project.shares.shares import tables as shares_tables -from manila_ui.dashboards.project.shares.snapshots \ - import tables as snapshot_tables - - -def get_size(share): - return _("%sGiB") % share.size - - -class CreateShareType(tables.LinkAction): - name = "create" - verbose_name = _("Create Share Type") - url = "horizon:admin:shares:create_type" - classes = ("ajax-modal", "btn-create") - icon = "plus" - policy_rules = (("share", "share_extension:types_manage"),) - - -class DeleteShareType(tables.DeleteAction): - policy_rules = (("share", "share_extension:types_manage"),) - - @staticmethod - def action_present(count): - return ungettext_lazy( - u"Delete Share Type", - u"Delete Share Types", - count - ) - - @staticmethod - def action_past(count): - return ungettext_lazy( - u"Deleted Share Type", - u"Deleted Share Types", - count - ) - - def delete(self, request, obj_id): - manila.share_type_delete(request, obj_id) - - -class ManageShareTypeAccess(tables.LinkAction): - name = "manage" - verbose_name = _("Manage Share Type Access") - url = "horizon:admin:shares:manage_share_type_access" - classes = ("ajax-modal", "btn-create") - policy_rules = (("share", "share_extension:share_type_access"),) - - def allowed(self, request, obj_id): - st = manila.share_type_get(request, obj_id) - # Enable it only for private share types - return not st.is_public - - def get_policy_target(self, request, datum=None): - project_id = None - if datum: - project_id = getattr(datum, "os-share-tenant-attr:tenant_id", None) - return {"project_id": project_id} class MigrationStartAction(tables.LinkAction): @@ -171,46 +106,6 @@ class UnmanageShareAction(tables.LinkAction): return False -class UpdateShareType(tables.LinkAction): - name = "update share type" - verbose_name = _("Update Share Type") - url = "horizon:admin:shares:update_type" - classes = ("ajax-modal", "btn-create") - policy_rules = (("share", "share_extension:types_manage"),) - - def get_policy_target(self, request, datum=None): - project_id = None - if datum: - project_id = getattr(datum, "os-share-tenant-attr:tenant_id", None) - return {"project_id": project_id} - - -class ShareTypesTable(tables.DataTable): - name = tables.WrappingColumn("name", verbose_name=_("Name")) - extra_specs = tables.Column("extra_specs", verbose_name=_("Extra specs"), ) - visibility = tables.Column( - "is_public", verbose_name=_("Visibility"), - filters=(lambda d: 'public' if d is True else 'private', ),) - - def get_object_display(self, share_type): - return share_type.name - - def get_object_id(self, share_type): - return str(share_type.id) - - class Meta(object): - name = "share_types" - verbose_name = _("Share Types") - table_actions = ( - tables.NameFilterAction, - CreateShareType, - DeleteShareType) - row_actions = ( - UpdateShareType, - ManageShareTypeAccess, - DeleteShareType) - - class ManageReplicas(tables.LinkAction): name = "manage_replicas" verbose_name = _("Manage Replicas") @@ -232,14 +127,15 @@ class SharesTable(shares_tables.SharesTable): def get_share_server_link(share): if getattr(share, 'share_server_id', None): - return reverse("horizon:admin:shares:share_server_detail", + return reverse("horizon:admin:share_servers:share_server_detail", args=(share.share_server_id,)) else: return None - share_server = tables.Column("share_server_id", - verbose_name=_("Share Server"), - link=get_share_server_link) + share_server = tables.Column( + "share_server_id", + verbose_name=_("Share Server"), + link=get_share_server_link) class Meta(object): name = "shares" @@ -249,7 +145,8 @@ class SharesTable(shares_tables.SharesTable): table_actions = ( tables.NameFilterAction, ManageShareAction, - shares_tables.DeleteShare) + shares_tables.DeleteShare, + ) row_actions = ( ManageReplicas, MigrationStartAction, @@ -257,382 +154,9 @@ class SharesTable(shares_tables.SharesTable): MigrationGetProgressAction, MigrationCancelAction, UnmanageShareAction, - shares_tables.DeleteShare) + shares_tables.DeleteShare, + ) columns = ( 'tenant', 'host', 'name', 'size', 'status', 'visibility', 'share_type', 'protocol', 'share_server', ) - - -class SnapshotShareNameColumn(tables.Column): - def get_link_url(self, snapshot): - return reverse(self.link, args=(snapshot.share_id,)) - - -class DeleteSnapshot(tables.DeleteAction): - policy_rules = (("snapshot", "snapshot:delete"),) - - @staticmethod - def action_present(count): - return ungettext_lazy( - u"Delete Snapshot", - u"Delete Snapshots", - count - ) - - @staticmethod - def action_past(count): - return ungettext_lazy( - u"Deleted Snapshot", - u"Deleted Snapshots", - count - ) - - def get_policy_target(self, request, datum=None): - project_id = None - if datum: - project_id = getattr(datum, "project_id", None) - return {"project_id": project_id} - - def delete(self, request, obj_id): - obj = self.table.get_object_by_id(obj_id) - name = self.table.get_object_display(obj) - try: - manila.share_snapshot_delete(request, obj_id) - except Exception: - msg = _('Unable to delete snapshot "%s". One or more shares ' - 'depend on it.') - exceptions.check_message(["snapshots", "dependent"], msg % name) - raise - - def allowed(self, request, snapshot=None): - if snapshot: - return snapshot.status.upper() in shares_tables.DELETABLE_STATES - return True - - -class SnapshotsTable(tables.DataTable): - STATUS_CHOICES = ( - ("in-use", True), - ("available", True), - ("creating", None), - ("error", False), - ) - STATUS_DISPLAY_CHOICES = ( - ("in-use", pgettext_lazy("Current status of snapshot", u"In-use")), - ("available", pgettext_lazy("Current status of snapshot", - u"Available")), - ("creating", pgettext_lazy("Current status of snapshot", u"Creating")), - ("error", pgettext_lazy("Current status of snapshot", u"Error")), - ) - name = tables.WrappingColumn( - "name", verbose_name=_("Name"), - link="horizon:admin:shares:snapshot-detail") - description = tables.Column("description", - verbose_name=_("Description"), - truncate=40) - size = tables.Column(get_size, - verbose_name=_("Size"), - attrs={'data-type': 'size'}) - status = tables.Column("status", - filters=(title,), - verbose_name=_("Status"), - status=True, - status_choices=STATUS_CHOICES, - display_choices=STATUS_DISPLAY_CHOICES) - source = SnapshotShareNameColumn("share", - verbose_name=_("Source"), - link="horizon:admin:shares:detail") - - def get_object_display(self, obj): - return obj.name - - class Meta(object): - name = "snapshots" - verbose_name = _("Snapshots") - status_columns = ["status"] - row_class = snapshot_tables.UpdateRow - table_actions = ( - tables.NameFilterAction, - DeleteSnapshot) - row_actions = ( - DeleteSnapshot,) - - -class DeleteSecurityService(tables.DeleteAction): - policy_rules = (("share", "security_service:delete"),) - - @staticmethod - def action_present(count): - return ungettext_lazy( - u"Delete Security Service", - u"Delete Security Services", - count - ) - - @staticmethod - def action_past(count): - return ungettext_lazy( - u"Deleted Security Service", - u"Deleted Security Services", - count - ) - - def delete(self, request, obj_id): - manila.security_service_delete(request, obj_id) - - -class DeleteShareServer(tables.DeleteAction): - policy_rules = (("share", "share_server:delete"),) - - @staticmethod - def action_present(count): - return ungettext_lazy( - u"Delete Share Server", - u"Delete Share Server", - count - ) - - @staticmethod - def action_past(count): - return ungettext_lazy( - u"Deleted Share Server", - u"Deleted Share Server", - count - ) - - def delete(self, request, obj_id): - manila.share_server_delete(request, obj_id) - - def allowed(self, request, share_serv): - if share_serv: - share_search_opts = {'share_server_id': share_serv.id} - shares_list = manila.share_list(request, - search_opts=share_search_opts) - if shares_list: - return False - return share_serv.status not in ["deleting", "creating"] - return True - - -class SecurityServiceTable(tables.DataTable): - name = tables.WrappingColumn( - "name", verbose_name=_("Name"), - link="horizon:admin:shares:security_service_detail") - project = tables.Column("project_name", verbose_name=_("Project")) - dns_ip = tables.Column("dns_ip", verbose_name=_("DNS IP")) - server = tables.Column("server", verbose_name=_("Server")) - domain = tables.Column("domain", verbose_name=_("Domain")) - user = tables.Column("user", verbose_name=_("Sid")) - - def get_object_display(self, security_service): - return security_service.name - - def get_object_id(self, security_service): - return str(security_service.id) - - class Meta(object): - name = "security_services" - verbose_name = _("Security Services") - table_actions = ( - tables.NameFilterAction, - DeleteSecurityService) - row_actions = ( - DeleteSecurityService,) - - -class UpdateShareServerRow(tables.Row): - ajax = True - - def get_data(self, request, share_serv_id): - share_serv = manila.share_server_get(request, share_serv_id) - return share_serv - - -class NovaShareNetworkTable(tables.DataTable): - name = tables.WrappingColumn( - "name", verbose_name=_("Name"), - link="horizon:admin:shares:share_network_detail") - project = tables.Column("project_name", verbose_name=_("Project")) - nova_net = tables.Column("nova_net", verbose_name=_("Nova Net")) - 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) - - def get_object_id(self, share_network): - return str(share_network.id) - - class Meta(object): - name = "share_networks" - verbose_name = _("Share Networks") - table_actions = ( - tables.NameFilterAction, - share_networks_tables.Delete) - row_class = share_networks_tables.UpdateRow - row_actions = ( - share_networks_tables.Delete,) - - -class NeutronShareNetworkTable(tables.DataTable): - name = tables.WrappingColumn( - "name", verbose_name=_("Name"), - link="horizon:project:shares: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) - - def get_object_id(self, share_network): - return str(share_network.id) - - class Meta(object): - name = "share_networks" - verbose_name = _("Share Networks") - table_actions = ( - tables.NameFilterAction, - share_networks_tables.Delete) - row_class = share_networks_tables.UpdateRow - row_actions = ( - share_networks_tables.Delete,) - - -class ShareServerTable(tables.DataTable): - STATUS_CHOICES = ( - ("active", True), - ("deleting", None), - ("creating", None), - ("error", False), - ) - STATUS_DISPLAY_CHOICES = ( - ("in-use", pgettext_lazy("Current status of share server", u"In-use")), - ("active", pgettext_lazy("Current status of share server", u"Active")), - ("creating", pgettext_lazy("Current status of share server", - u"Creating")), - ("error", pgettext_lazy("Current status of share server", - u"Error")), - ) - uid = tables.Column("id", verbose_name=_("Id"), - link="horizon:admin:shares:share_server_detail") - host = tables.Column("host", verbose_name=_("Host")) - project = tables.Column("project_name", verbose_name=_("Project")) - - def get_share_server_link(share_serv): - if hasattr(share_serv, 'share_network_id'): - return reverse("horizon:admin:shares:share_network_detail", - args=(share_serv.share_network_id,)) - else: - return None - - share_net_name = tables.Column("share_network_name", - verbose_name=_("Share Network"), - link=get_share_server_link) - status = tables.Column("status", verbose_name=_("Status"), - status=True, filters=(title,), - status_choices=STATUS_CHOICES, - display_choices=STATUS_DISPLAY_CHOICES) - - def get_object_display(self, share_server): - return six.text_type(share_server.id) - - def get_object_id(self, share_server): - return six.text_type(share_server.id) - - class Meta(object): - name = "share_servers" - status_columns = ["status"] - verbose_name = _("Share Server") - table_actions = ( - tables.NameFilterAction, - DeleteShareServer) - row_class = UpdateShareServerRow - row_actions = ( - DeleteShareServer,) - - -class ShareInstancesTable(tables.DataTable): - STATUS_CHOICES = ( - ("available", True), - ("creating", None), - ("deleting", None), - ("error", False), - ("error_deleting", False), - ) - STATUS_DISPLAY_CHOICES = ( - ("available", u"Available"), - ("creating", u"Creating"), - ("deleting", u"Deleting"), - ("error", u"Error"), - ("error_deleting", u"Error deleting"), - ) - uuid = tables.Column( - "id", verbose_name=_("ID"), - link="horizon:admin:shares:share_instance_detail") - host = tables.Column("host", verbose_name=_("Host")) - status = tables.Column("status", - verbose_name=_("Status"), - status=True, - status_choices=STATUS_CHOICES, - display_choices=STATUS_DISPLAY_CHOICES) - availability_zone = tables.Column( - "availability_zone", verbose_name=_("Availability Zone")) - - class Meta(object): - name = "share_instances" - verbose_name = _("Share Instances") - status_columns = ("status", ) - table_actions = ( - tables.NameFilterAction,) - multi_select = False - - def get_share_network_link(share_instance): - if getattr(share_instance, 'share_network_id', None): - return reverse("horizon:admin:shares:share_network_detail", - args=(share_instance.share_network_id,)) - else: - return None - - def get_share_server_link(share_instance): - if getattr(share_instance, 'share_server_id', None): - return reverse("horizon:admin:shares:share_server_detail", - args=(share_instance.share_server_id,)) - else: - return None - - def get_share_link(share_instance): - if getattr(share_instance, 'share_id', None): - return reverse("horizon:project:shares:detail", - args=(share_instance.share_id,)) - else: - return None - - share_net_id = tables.Column( - "share_network_id", - verbose_name=_("Share Network"), - link=get_share_network_link) - share_server_id = tables.Column( - "share_server_id", - verbose_name=_("Share Server Id"), - link=get_share_server_link) - share_id = tables.Column( - "share_id", - verbose_name=_("Share ID"), - link=get_share_link) - - def get_object_display(self, share_instance): - return six.text_type(share_instance.id) - - def get_object_id(self, share_instance): - return six.text_type(share_instance.id) diff --git a/manila_ui/dashboards/admin/shares/tabs.py b/manila_ui/dashboards/admin/shares/tabs.py index b1827598..26ea2cb5 100644 --- a/manila_ui/dashboards/admin/shares/tabs.py +++ b/manila_ui/dashboards/admin/shares/tabs.py @@ -1,4 +1,4 @@ -# Copyright 2014 OpenStack Foundation +# Copyright 2017 Mirantis, Inc. # # 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 @@ -13,241 +13,20 @@ # under the License. from django.utils.translation import ugettext_lazy as _ - -from horizon import exceptions from horizon import tabs -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.admin.shares import tables -from manila_ui.dashboards.admin.shares import utils -from manila_ui.dashboards import utils as common_utils - - -class SnapshotsTab(tabs.TableTab): - table_classes = (tables.SnapshotsTable, ) - name = _("Snapshots") - slug = "snapshots_tab" - template_name = "horizon/common/_detail_table.html" - - def _set_id_if_nameless(self, snapshots): - for snap in snapshots: - if not snap.name: - snap.name = snap.id - - def get_snapshots_data(self): - snapshots = [] - try: - snapshots = manila.share_snapshot_list( - self.request, search_opts={'all_tenants': True}) - shares = manila.share_list(self.request) - share_names = dict([(share.id, share.name or share.id) - for share in shares]) - for snapshot in snapshots: - snapshot.share = share_names.get(snapshot.share_id) - except Exception: - msg = _("Unable to retrieve snapshot list.") - exceptions.handle(self.request, msg) - - # Gather our projects to correlate against IDs - utils.set_project_name_to_objects(self.request, snapshots) - - return snapshots - - -class SharesTab(tabs.TableTab): - table_classes = (tables.SharesTable, ) - name = _("Shares") - slug = "shares_tab" - template_name = "horizon/common/_detail_table.html" - - def get_shares_data(self): - shares = [] - try: - shares = manila.share_list( - self.request, search_opts={'all_tenants': True}) - snapshots = manila.share_snapshot_list( - self.request, detailed=True, search_opts={'all_tenants': True}) - share_ids_with_snapshots = [] - for snapshot in snapshots: - share_ids_with_snapshots.append(snapshot.to_dict()['share_id']) - for share in shares: - if share.to_dict()['id'] in share_ids_with_snapshots: - setattr(share, 'has_snapshot', True) - else: - setattr(share, 'has_snapshot', False) - except Exception: - exceptions.handle( - self.request, _('Unable to retrieve share list.')) - - # Gather our projects to correlate against IDs - utils.set_project_name_to_objects(self.request, shares) - - return shares - - -class ShareTypesTab(tabs.TableTab): - table_classes = (tables.ShareTypesTable, ) - name = _("Share Types") - slug = "share_types_tab" - template_name = "horizon/common/_detail_table.html" - - def get_share_types_data(self): - try: - share_types = manila.share_type_list(self.request) - except Exception: - share_types = [] - exceptions.handle(self.request, - _("Unable to retrieve share types")) - # Convert dict with extra specs to friendly view - for st in share_types: - st.extra_specs = common_utils.metadata_to_str( - st.extra_specs, 8, 45) - return share_types - - -class SecurityServiceTab(tabs.TableTab): - table_classes = (tables.SecurityServiceTable,) - name = _("Security Services") - slug = "security_services_tab" - template_name = "horizon/common/_detail_table.html" - - def get_security_services_data(self): - try: - security_services = manila.security_service_list( - self.request, search_opts={'all_tenants': True}) - except Exception: - security_services = [] - exceptions.handle(self.request, - _("Unable to retrieve security services")) - - utils.set_project_name_to_objects(self.request, security_services) - return security_services - - -class ShareNetworkTab(tabs.TableTab): - name = _("Share Networks") - slug = "share_networks_tab" - template_name = "horizon/common/_detail_table.html" - - def __init__(self, tab_group, request): - if base.is_service_enabled(request, 'network'): - self.table_classes = (tables.NeutronShareNetworkTable,) - else: - self.table_classes = (tables.NovaShareNetworkTable,) - super(ShareNetworkTab, self).__init__(tab_group, request) - - 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 "-" - else: - nova_net_names = dict( - [(net.id, net.label) - for net in network.network_list(self.request)]) - for sn in share_networks: - sn.nova_net = nova_net_names.get( - sn.nova_net_id) or sn.nova_net_id or "-" - except Exception: - share_networks = [] - exceptions.handle(self.request, - _("Unable to retrieve share networks")) - utils.set_project_name_to_objects(self.request, share_networks) - return share_networks - - -class ShareInstancesTab(tabs.TableTab): - table_classes = (tables.ShareInstancesTable,) - name = _("Share Instances") - slug = "share_instances_tab" - template_name = "horizon/common/_detail_table.html" - - def get_share_instances_data(self): - try: - share_instances = manila.share_instance_list(self.request) - except Exception: - share_instances = [] - exceptions.handle( - self.request, _("Unable to retrieve share instances.")) - return share_instances - - -class ShareInstanceOverviewTab(tabs.Tab): - name = _("Overview") - slug = "overview" - template_name = ("admin/shares/_detail_share_instance.html") +class ShareOverviewTab(tabs.Tab): + name = _("Share Overview") + slug = "share_overview_tab" + template_name = "admin/shares/_detail.html" def get_context_data(self, request): - return {"share_instance": self.tab_group.kwargs['share_instance']} + return {"share": self.tab_group.kwargs["share"]} -class ShareInstanceDetailTabs(tabs.TabGroup): - slug = "share_instance_details" - tabs = (ShareInstanceOverviewTab,) - - -class ShareServerTab(tabs.TableTab): - table_classes = (tables.ShareServerTable,) - name = _("Share Servers") - slug = "share_servers_tab" - template_name = "horizon/common/_detail_table.html" - - def get_share_servers_data(self): - try: - share_servers = manila.share_server_list( - self.request) - except Exception: - share_servers = [] - exceptions.handle(self.request, - _("Unable to retrieve share servers")) - utils.set_project_name_to_objects(self.request, share_servers) - return share_servers - - -class ShareServerOverviewTab(tabs.Tab): - name = _("Overview") - slug = "overview" - template_name = ("admin/shares/_detail_share_server.html") - - def get_context_data(self, request): - return {"share_server": self.tab_group.kwargs['share_server']} - - -class ShareServerDetailTabs(tabs.TabGroup): - slug = "share_server_details" - tabs = (ShareServerOverviewTab,) - - -class ShareTabs(tabs.TabGroup): - slug = "share_tabs" - tabs = (SharesTab, SnapshotsTab, ShareNetworkTab, SecurityServiceTab, - ShareTypesTab, ShareServerTab, ShareInstancesTab) - sticky = True - - -class SnapshotOverviewTab(tabs.Tab): - name = _("Snapshot Overview") - slug = "snapshot_overview_tab" - template_name = ("admin/shares/" - "_snapshot_detail_overview.html") - - def get_context_data(self, request): - return {"snapshot": self.tab_group.kwargs['snapshot']} - - -class SnapshotDetailTabs(tabs.TabGroup): - slug = "snapshot_details" - tabs = (SnapshotOverviewTab,) +class ShareDetailTabs(tabs.TabGroup): + slug = "share_details" + tabs = ( + ShareOverviewTab, + ) diff --git a/manila_ui/dashboards/admin/shares/templates/shares/_detail.html b/manila_ui/dashboards/admin/shares/templates/shares/_detail.html new file mode 100644 index 00000000..a31ec270 --- /dev/null +++ b/manila_ui/dashboards/admin/shares/templates/shares/_detail.html @@ -0,0 +1,100 @@ +{% load i18n sizeformat parse_date %} + +

{% trans "Share Overview" %}

+
+
+
+
{% trans "Name" %}
+
{{ share.name }}
+
{% trans "ID" %}
+
{{ share.id }}
+ {% if share.description %} +
{% trans "Description" %}
+
{{ share.description }}
+ {% endif %} +
{% trans "Status" %}
+
{{ share.status|capfirst }}
+
{% trans "Export locations" %}
+ {% for el in share.export_locations %} +

+

Path: + +
+
Preferred: {{ el.preferred }}
+ {% if el.is_admin_only == True or el.is_admin_only == False %} +
Is admin only: {{ el.is_admin_only }}
+ {% endif %} + {% if el.share_instance_id %} +
Share Replica ID: {{ el.share_instance_id }}
+ {% endif %} +

+ {% endfor %} + {% if share.snapshot_id %} +
{% trans "Snapshot ID" %}
+ {% url 'horizon:admin:share_snapshots:snapshot_detail' share.snapshot_id as snapshot_url%} +
{{ share.snapshot_id }}
+ {% endif %} +
{% trans "Visibility" %}
+ {% if share.is_public == True %} +
{{ 'public' }}
+ {% else %} +
{{ 'private' }}
+
{% trans "Availability zone" %}
+
{{ share.availability_zone }}
+ {% endif %} + +
{% trans "Size" %}
+
{{ share.size }} {% trans "GiB" %}
+
{% trans "Protocol" %}
+
{{ share.share_proto }}
+ {% if share.share_type %} +
{% trans "Share type" %}
+

+

Name: {{ share.share_type_name }}
+
ID: {{ share.share_type }}
+

+ {% endif %} + {% if share.share_network_id %} +
{% trans "Share network" %}
+ {% url 'horizon:admin:share_networks:share_network_detail' share.share_network_id as sn_url%} +
{{ share.share_network_id }}
+ {% endif %} +
{% trans "Mount snapshot support" %}
+
{{ share.mount_snapshot_support }}
+
{% trans "Created" %}
+
{{ share.created_at|parse_date }}
+
{% trans "Host" %}
+
{{ share.host }}
+
{% trans "Task state" %}
+
{{ share.task_state }}
+
+
+ +
+

{% trans "Access Rules" %}

+
+
+ {% for rule in share.rules %} +
{{ rule.access_type }}
+

+

Access to: {{ rule.access_to }}
+
Access Level: {{ rule.access_level }}
+
Status: {{ rule.state }}
+
Access Key: {{ rule.access_key }}
+

+ {% endfor %} +
+
+ +
+

{% trans "Metadata" %}

+
+
+ {% for key, value in share.metadata.items %} +
{{ key }}
+
{{ value }}
+ {% endfor %} +
+
diff --git a/manila_ui/dashboards/admin/shares/templates/shares/_snapshot_detail_overview.html b/manila_ui/dashboards/admin/shares/templates/shares/_snapshot_detail_overview.html deleted file mode 100644 index f01ac552..00000000 --- a/manila_ui/dashboards/admin/shares/templates/shares/_snapshot_detail_overview.html +++ /dev/null @@ -1,33 +0,0 @@ -{% load i18n sizeformat parse_date %} - -

{% trans "Snapshot Overview" %}

- -
-

{% trans "Info" %}

-
-
-
{% trans "Name" %}
-
{{ snapshot.name }}
-
{% trans "ID" %}
-
{{ snapshot.id }}
-
{% trans "Source" %}
-
{{ snapshot.share_id }}
- {% if snapshot.description %} -
{% trans "Description" %}
-
{{ snapshot.description }}
- {% endif %} -
{% trans "Status" %}
-
{{ snapshot.status|capfirst }}
-
-
- -
-

{% trans "Specs" %}

-
-
-
{% trans "Size" %}
-
{{ snapshot.size }} {% trans "GiB" %}
-
{% trans "Created" %}
-
{{ snapshot.created_at|parse_date }}
-
-
diff --git a/manila_ui/dashboards/admin/shares/templates/shares/index.html b/manila_ui/dashboards/admin/shares/templates/shares/index.html index 0685ed0c..91ed2a2e 100644 --- a/manila_ui/dashboards/admin/shares/templates/shares/index.html +++ b/manila_ui/dashboards/admin/shares/templates/shares/index.html @@ -5,7 +5,7 @@ {% block main %}
- {{ tab_group.render }} + {{ shares_table.render }}
{% endblock %} diff --git a/manila_ui/dashboards/admin/shares/urls.py b/manila_ui/dashboards/admin/shares/urls.py index 72cbcfad..15f3355f 100644 --- a/manila_ui/dashboards/admin/shares/urls.py +++ b/manila_ui/dashboards/admin/shares/urls.py @@ -10,75 +10,72 @@ # License for the specific language governing permissions and limitations # under the License. -from django.conf.urls import url # noqa +from django.conf import urls from manila_ui.api import manila from manila_ui.dashboards.admin.shares.replicas import views as replica_views from manila_ui.dashboards.admin.shares import views + urlpatterns = [ - url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^(?P[^/]+)/$', views.DetailView.as_view(), name='detail'), - url(r'^snapshots/(?P[^/]+)$', - views.SnapshotDetailView.as_view(), - name='snapshot-detail'), - url(r'^share_networks/(?P[^/]+)$', - views.ShareNetworkDetailView.as_view(), - name='share_network_detail'), - url(r'^security_services/(?P[^/]+)$', - views.SecurityServiceDetailView.as_view(), - name='security_service_detail'), - url(r'^create_type$', views.CreateShareTypeView.as_view(), - name='create_type'), - url(r'^manage_share_type_access/(?P[^/]+)$', - views.ManageShareTypeAccessView.as_view(), - name='manage_share_type_access'), - url(r'^update_type/(?P[^/]+)/extra_specs$', - views.UpdateShareTypeView.as_view(), - name='update_type'), - url(r'^share_servers/(?P[^/]+)$', - views.ShareServDetail.as_view(), - name='share_server_detail'), - url(r'^\?tab=share_tabs__share_servers_tab$', views.IndexView.as_view(), - name='share_servers_tab'), - url(r'^share_instances/(?P[^/]+)$', - views.ShareInstanceDetailView.as_view(), - name='share_instance_detail'), - url(r'^\?tab=share_tabs__share_instances_tab$', views.IndexView.as_view(), - name='share_instances_tab'), - url(r'^manage$', views.ManageShareView.as_view(), name='manage'), - url(r'^unmanage/(?P[^/]+)$', views.UnmanageShareView.as_view(), + urls.url( + r'^$', + views.SharesView.as_view(), + name='index'), + urls.url( + r'^(?P[^/]+)/$', + views.DetailView.as_view(), + name='detail'), + urls.url( + r'^manage$', + views.ManageShareView.as_view(), + name='manage'), + urls.url( + r'^unmanage/(?P[^/]+)$', + views.UnmanageShareView.as_view(), name='unmanage'), ] if manila.is_replication_enabled(): urlpatterns.extend([ - url(r'^(?P[^/]+)/replicas/$', + urls.url( + r'^(?P[^/]+)/replicas/$', replica_views.ManageReplicasView.as_view(), name='manage_replicas'), - url(r'^replica/(?P[^/]+)$', + urls.url( + r'^replica/(?P[^/]+)$', replica_views.DetailReplicaView.as_view(), name='replica_detail'), - url(r'^replica/(?P[^/]+)/resync_replica$', + urls.url( + r'^replica/(?P[^/]+)/resync_replica$', replica_views.ResyncReplicaView.as_view(), name='resync_replica'), - url(r'^replica/(?P[^/]+)/reset_replica_status$', + urls.url( + r'^replica/(?P[^/]+)/reset_replica_status$', replica_views.ResetReplicaStatusView.as_view(), name='reset_replica_status'), - url(r'^replica/(?P[^/]+)/reset_replica_state$', + urls.url( + r'^replica/(?P[^/]+)/reset_replica_state$', replica_views.ResetReplicaStateView.as_view(), name='reset_replica_state'), ]) if manila.is_migration_enabled(): urlpatterns.extend([ - url(r'^migration_start/(?P[^/]+)$', - views.MigrationStartView.as_view(), name='migration_start'), - url(r'^migration_complete/(?P[^/]+)$', - views.MigrationCompleteView.as_view(), name='migration_complete'), - url(r'^migration_cancel/(?P[^/]+)$', - views.MigrationCancelView.as_view(), name='migration_cancel'), - url(r'^migration_get_progress/(?P[^/]+)$', + urls.url( + r'^migration_start/(?P[^/]+)$', + views.MigrationStartView.as_view(), + name='migration_start'), + urls.url( + r'^migration_complete/(?P[^/]+)$', + views.MigrationCompleteView.as_view(), + name='migration_complete'), + urls.url( + r'^migration_cancel/(?P[^/]+)$', + views.MigrationCancelView.as_view(), + name='migration_cancel'), + urls.url( + r'^migration_get_progress/(?P[^/]+)$', views.MigrationGetProgressView.as_view(), name='migration_get_progress'), ]) diff --git a/manila_ui/dashboards/admin/shares/views.py b/manila_ui/dashboards/admin/shares/views.py index 53f6166a..ae3f7870 100644 --- a/manila_ui/dashboards/admin/shares/views.py +++ b/manila_ui/dashboards/admin/shares/views.py @@ -19,37 +19,54 @@ Admin views for managing shares. from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse_lazy from django.utils.translation import ugettext_lazy as _ - from horizon import exceptions from horizon import forms -from horizon import tabs +from horizon import tables from horizon.utils import memoized -from horizon import workflows from manila_ui.api import manila from manila_ui.dashboards.admin.shares import forms as project_forms -from manila_ui.dashboards.admin.shares import tabs as project_tabs -import manila_ui.dashboards.admin.shares.workflows as share_workflows -from manila_ui.dashboards.project.shares.security_services import \ - views as ss_views -from manila_ui.dashboards.project.shares.share_networks import \ - views as sn_views +from manila_ui.dashboards.admin.shares import tables as s_tables +from manila_ui.dashboards.admin.shares import tabs as s_tabs +from manila_ui.dashboards.admin import utils from manila_ui.dashboards.project.shares.shares import views as share_views -from manila_ui.dashboards.project.shares.snapshots import \ - views as snapshot_views -from manila_ui.dashboards import utils as ui_utils -from manila_ui.utils import filters - -filters = (filters.get_item,) -class IndexView(tabs.TabbedTableView, share_views.ShareTableMixIn): - tab_group_class = project_tabs.ShareTabs +class SharesView(tables.MultiTableView, share_views.ShareTableMixIn): + table_classes = ( + s_tables.SharesTable, + ) template_name = "admin/shares/index.html" page_title = _("Shares") + @memoized.memoized_method + def get_shares_data(self): + shares = [] + try: + shares = manila.share_list( + self.request, search_opts={'all_tenants': True}) + snapshots = manila.share_snapshot_list( + self.request, detailed=True, search_opts={'all_tenants': True}) + share_ids_with_snapshots = [] + for snapshot in snapshots: + share_ids_with_snapshots.append(snapshot.to_dict()['share_id']) + for share in shares: + if share.to_dict()['id'] in share_ids_with_snapshots: + setattr(share, 'has_snapshot', True) + else: + setattr(share, 'has_snapshot', False) + except Exception: + exceptions.handle( + self.request, _('Unable to retrieve share list.')) + + # Gather our projects to correlate against IDs + utils.set_project_name_to_objects(self.request, shares) + + return shares + class DetailView(share_views.DetailView): + tab_group_class = s_tabs.ShareDetailTabs template_name = "admin/shares/detail.html" def get_context_data(self, **kwargs): @@ -59,18 +76,6 @@ class DetailView(share_views.DetailView): return context -class SecurityServiceDetailView(ss_views.Detail): - redirect_url = reverse_lazy('horizon:admin:shares:index') - - -class ShareNetworkDetailView(sn_views.Detail): - redirect_url = reverse_lazy('horizon:admin:shares:index') - - -class SnapshotDetailView(snapshot_views.SnapshotDetailView): - redirect_url = reverse_lazy('horizon:admin:shares:index') - - class ManageShareView(forms.ModalFormView): form_class = project_forms.ManageShare template_name = 'admin/shares/manage_share.html' @@ -271,151 +276,3 @@ class UnmanageShareView(forms.ModalFormView): 'name': share.name, 'host': getattr(share, "host"), } - - -class CreateShareTypeView(forms.ModalFormView): - form_class = project_forms.CreateShareType - form_id = "create_share_type" - template_name = 'admin/shares/create_share_type.html' - modal_header = _("Create Share Type") - submit_label = _("Create") - submit_url = reverse_lazy("horizon:admin:shares:create_type") - success_url = 'horizon:admin:shares:index' - page_title = _("Create Share Type") - - def get_success_url(self): - return reverse(self.success_url) - - -class ManageShareTypeAccessView(workflows.WorkflowView): - workflow_class = share_workflows.ManageShareTypeAccessWorkflow - template_name = "admin/shares/manage_share_type_access.html" - success_url = 'horizon:project:shares:index' - page_title = _("Manage Share Type Access") - - def get_initial(self): - return {'id': self.kwargs["share_type_id"]} - - def get_context_data(self, **kwargs): - context = super(ManageShareTypeAccessView, self).get_context_data( - **kwargs) - context['id'] = self.kwargs['share_type_id'] - return context - - -class UpdateShareTypeView(forms.ModalFormView): - form_class = project_forms.UpdateShareType - form_id = "update_share_type" - template_name = "admin/shares/update_share_type.html" - modal_header = _("Update Share Type") - modal_id = "update_share_type_modal" - submit_label = _("Update") - submit_url = "horizon:admin:shares:update_type" - success_url = reverse_lazy("horizon:admin:shares:index") - page_title = _("Update Share Type") - - def get_object(self): - if not hasattr(self, "_object"): - st_id = self.kwargs["share_type_id"] - try: - self._object = manila.share_type_get(self.request, st_id) - except Exception: - msg = _("Unable to retrieve share_type.") - url = reverse("horizon:admin:shares:index") - exceptions.handle(self.request, msg, redirect=url) - return self._object - - def get_context_data(self, **kwargs): - context = super(UpdateShareTypeView, self).get_context_data(**kwargs) - args = (self.get_object().id,) - context['submit_url'] = reverse(self.submit_url, args=args) - return context - - def get_initial(self): - share_type = self.get_object() - return { - "id": self.kwargs["share_type_id"], - "name": share_type.name, - "extra_specs": share_type.extra_specs, - } - - -class ShareServDetail(tabs.TabView): - tab_group_class = project_tabs.ShareServerDetailTabs - template_name = 'admin/shares/detail_share_server.html' - - def get_context_data(self, **kwargs): - context = super(ShareServDetail, self).get_context_data(**kwargs) - share_server = self.get_data() - share_server_display_name = share_server.id - context["share_server"] = share_server - context["share_server_display_name"] = share_server_display_name - context["page_title"] = _("Share Server Details: %(server_name)s") % { - 'server_name': share_server_display_name} - return context - - @memoized.memoized_method - def get_data(self): - try: - share_serv_id = self.kwargs['share_server_id'] - share_serv = manila.share_server_get(self.request, share_serv_id) - share_search_opts = {'share_server_id': share_serv.id} - shares_list = manila.share_list(self.request, - search_opts=share_search_opts) - for share in shares_list: - share.name_or_id = share.name or share.id - share_serv.shares_list = shares_list - if not hasattr(share_serv, 'share_network_id'): - share_serv.share_network_id = None - - except Exception: - redirect = reverse('horizon:admin:shares:index') - exceptions.handle(self.request, - _('Unable to retrieve share server details.'), - redirect=redirect) - return share_serv - - def get_tabs(self, request, *args, **kwargs): - share_server = self.get_data() - return self.tab_group_class(request, share_server=share_server, - **kwargs) - - -class ShareInstanceDetailView(tabs.TabView): - tab_group_class = project_tabs.ShareInstanceDetailTabs - template_name = 'admin/shares/share_instance_detail.html' - - def get_context_data(self, **kwargs): - context = super(self.__class__, self).get_context_data(**kwargs) - share_instance = self.get_data() - context["share_instance"] = share_instance - context["page_title"] = ( - _("Share Instance Details: %s") % share_instance.id) - return context - - @memoized.memoized_method - def get_data(self): - try: - share_instance_id = self.kwargs['share_instance_id'] - share_instance = manila.share_instance_get( - self.request, share_instance_id) - share_instance.export_locations = ( - manila.share_instance_export_location_list( - self.request, share_instance_id)) - export_locations = [ - exp['path'] for exp in share_instance.export_locations - ] - share_instance.el_size = ui_utils.calculate_longest_str_size( - export_locations) - return share_instance - except Exception: - redirect = reverse('horizon:admin:shares:index') - exceptions.handle( - self.request, - _('Unable to retrieve share instance details.'), - redirect=redirect) - - def get_tabs(self, request, *args, **kwargs): - share_instance = self.get_data() - return self.tab_group_class( - request, share_instance=share_instance, **kwargs) diff --git a/manila_ui/dashboards/admin/shares/utils.py b/manila_ui/dashboards/admin/utils.py similarity index 100% rename from manila_ui/dashboards/admin/shares/utils.py rename to manila_ui/dashboards/admin/utils.py diff --git a/manila_ui/dashboards/project/shares/panel.py b/manila_ui/dashboards/project/shares/panel.py index f45c5336..2516bd76 100644 --- a/manila_ui/dashboards/project/shares/panel.py +++ b/manila_ui/dashboards/project/shares/panel.py @@ -13,16 +13,16 @@ # under the License. from django.utils.translation import ugettext_lazy as _ - import horizon - from openstack_dashboard.dashboards.project import dashboard class Shares(horizon.Panel): name = _("Shares") slug = 'shares' - permissions = ('openstack.services.share', ) + permissions = ( + 'openstack.services.share', + ) dashboard.Project.register(Shares) diff --git a/manila_ui/dashboards/project/shares/share_networks/tables.py b/manila_ui/dashboards/project/shares/share_networks/tables.py index 5c648f1e..1ac8a747 100644 --- a/manila_ui/dashboards/project/shares/share_networks/tables.py +++ b/manila_ui/dashboards/project/shares/share_networks/tables.py @@ -132,7 +132,7 @@ class NovaShareNetworkTable(tables.DataTable): Delete) -class NeutronShareNetworkTable(tables.DataTable): +class ShareNetworksTable(tables.DataTable): STATUS_CHOICES = ( ("ACTIVE", True), ("INACTIVE", True), diff --git a/manila_ui/dashboards/project/shares/share_networks/tabs.py b/manila_ui/dashboards/project/shares/share_networks/tabs.py index 2c35850f..4b1d0ed1 100644 --- a/manila_ui/dashboards/project/shares/share_networks/tabs.py +++ b/manila_ui/dashboards/project/shares/share_networks/tabs.py @@ -32,7 +32,7 @@ class ShareNetworkTab(tabs.TableTab): def __init__(self, tab_group, request): if base.is_service_enabled(request, 'network'): - self.table_classes = (share_net_tables.NeutronShareNetworkTable,) + self.table_classes = (share_net_tables.ShareNetworksTable,) else: self.table_classes = (share_net_tables.NovaShareNetworkTable,) super(ShareNetworkTab, self).__init__(tab_group, request) diff --git a/manila_ui/local/enabled/_80_manila_admin_add_share_panel_group.py b/manila_ui/local/enabled/_80_manila_admin_add_share_panel_group.py new file mode 100644 index 00000000..c839b00a --- /dev/null +++ b/manila_ui/local/enabled/_80_manila_admin_add_share_panel_group.py @@ -0,0 +1,6 @@ +# The slug of the panel group to be added to HORIZON_CONFIG. Required. +PANEL_GROUP = 'share' +# The display name of the PANEL_GROUP. Required. +PANEL_GROUP_NAME = 'Share' +# The slug of the dashboard the PANEL_GROUP associated with. Required. +PANEL_GROUP_DASHBOARD = 'admin' diff --git a/manila_ui/local/enabled/_90_manila_admin_shares.py b/manila_ui/local/enabled/_9010_manila_admin_add_shares_panel_to_share_panel_group.py similarity index 92% rename from manila_ui/local/enabled/_90_manila_admin_shares.py rename to manila_ui/local/enabled/_9010_manila_admin_add_shares_panel_to_share_panel_group.py index 92bf11a6..2e376de4 100644 --- a/manila_ui/local/enabled/_90_manila_admin_shares.py +++ b/manila_ui/local/enabled/_9010_manila_admin_add_shares_panel_to_share_panel_group.py @@ -1,4 +1,4 @@ -# Copyright 2016 Mirantis Inc. +# Copyright 2017 Mirantis Inc. # # 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 @@ -13,6 +13,6 @@ # under the License. PANEL_DASHBOARD = 'admin' -PANEL_GROUP = 'admin' +PANEL_GROUP = 'share' PANEL = 'shares' ADD_PANEL = 'manila_ui.dashboards.admin.shares.panel.Shares' diff --git a/manila_ui/local/enabled/_9020_manila_admin_add_share_snapshots_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9020_manila_admin_add_share_snapshots_panel_to_share_panel_group.py new file mode 100644 index 00000000..b000a306 --- /dev/null +++ b/manila_ui/local/enabled/_9020_manila_admin_add_share_snapshots_panel_to_share_panel_group.py @@ -0,0 +1,18 @@ +# Copyright 2017 Mirantis Inc. +# +# 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. + +PANEL_DASHBOARD = 'admin' +PANEL_GROUP = 'share' +PANEL = 'share_snapshots' +ADD_PANEL = 'manila_ui.dashboards.admin.share_snapshots.panel.ShareSnapshots' diff --git a/manila_ui/local/enabled/_9030_manila_admin_add_share_types_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9030_manila_admin_add_share_types_panel_to_share_panel_group.py new file mode 100644 index 00000000..09116d08 --- /dev/null +++ b/manila_ui/local/enabled/_9030_manila_admin_add_share_types_panel_to_share_panel_group.py @@ -0,0 +1,18 @@ +# Copyright 2017 Mirantis Inc. +# +# 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. + +PANEL_DASHBOARD = 'admin' +PANEL_GROUP = 'share' +PANEL = 'share_types' +ADD_PANEL = 'manila_ui.dashboards.admin.share_types.panel.ShareTypes' diff --git a/manila_ui/local/enabled/_9040_manila_admin_add_share_networks_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9040_manila_admin_add_share_networks_panel_to_share_panel_group.py new file mode 100644 index 00000000..e5f635e2 --- /dev/null +++ b/manila_ui/local/enabled/_9040_manila_admin_add_share_networks_panel_to_share_panel_group.py @@ -0,0 +1,18 @@ +# Copyright 2017 Mirantis Inc. +# +# 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. + +PANEL_DASHBOARD = 'admin' +PANEL_GROUP = 'share' +PANEL = 'share_networks' +ADD_PANEL = 'manila_ui.dashboards.admin.share_networks.panel.ShareNetworks' diff --git a/manila_ui/local/enabled/_9050_manila_admin_add_security_services_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9050_manila_admin_add_security_services_panel_to_share_panel_group.py new file mode 100644 index 00000000..d2d200e7 --- /dev/null +++ b/manila_ui/local/enabled/_9050_manila_admin_add_security_services_panel_to_share_panel_group.py @@ -0,0 +1,19 @@ +# Copyright 2017 Mirantis Inc. +# +# 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. + +PANEL_DASHBOARD = 'admin' +PANEL_GROUP = 'share' +PANEL = 'security_services' +ADD_PANEL = ( + 'manila_ui.dashboards.admin.security_services.panel.SecurityServices') diff --git a/manila_ui/local/enabled/_9060_manila_admin_add_share_servers_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9060_manila_admin_add_share_servers_panel_to_share_panel_group.py new file mode 100644 index 00000000..cd9451e2 --- /dev/null +++ b/manila_ui/local/enabled/_9060_manila_admin_add_share_servers_panel_to_share_panel_group.py @@ -0,0 +1,18 @@ +# Copyright 2017 Mirantis Inc. +# +# 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. + +PANEL_DASHBOARD = 'admin' +PANEL_GROUP = 'share' +PANEL = 'share_servers' +ADD_PANEL = 'manila_ui.dashboards.admin.share_servers.panel.ShareServers' diff --git a/manila_ui/local/enabled/_9070_manila_admin_add_share_instances_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9070_manila_admin_add_share_instances_panel_to_share_panel_group.py new file mode 100644 index 00000000..29be136e --- /dev/null +++ b/manila_ui/local/enabled/_9070_manila_admin_add_share_instances_panel_to_share_panel_group.py @@ -0,0 +1,18 @@ +# Copyright 2017 Mirantis Inc. +# +# 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. + +PANEL_DASHBOARD = 'admin' +PANEL_GROUP = 'share' +PANEL = 'share_instances' +ADD_PANEL = 'manila_ui.dashboards.admin.share_instances.panel.ShareInstances' diff --git a/manila_ui/tests/dashboards/admin/security_services/__init__.py b/manila_ui/tests/dashboards/admin/security_services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/tests/dashboards/admin/security_services/tests.py b/manila_ui/tests/dashboards/admin/security_services/tests.py new file mode 100644 index 00000000..464f6495 --- /dev/null +++ b/manila_ui/tests/dashboards/admin/security_services/tests.py @@ -0,0 +1,95 @@ +# Copyright (c) 2014 NetApp, Inc. +# Copyright (c) 2015 Mirantis, Inc. +# +# 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 django.core.urlresolvers import reverse +from horizon import exceptions as horizon_exceptions +import mock +from openstack_dashboard.api import keystone as api_keystone + +from manila_ui.api import manila as api_manila +from manila_ui.dashboards.admin import utils +from manila_ui.tests.dashboards.project.shares import test_data +from manila_ui.tests import helpers as test +from manila_ui.tests.test_data import keystone_data + +INDEX_URL = reverse('horizon:admin:security_services:index') + + +class SecurityServicesTests(test.BaseAdminViewTests): + + def setUp(self): + super(self.__class__, self).setUp() + self.mock_object( + api_keystone, "tenant_list", + mock.Mock(return_value=(keystone_data.projects, None))) + # Reset taken list of projects to avoid test interference + utils.PROJECTS = {} + + def test_detail_view(self): + sec_service = test_data.sec_service + self.mock_object( + api_manila, "security_service_get", + mock.Mock(return_value=sec_service)) + url = reverse( + 'horizon:admin:security_services:security_service_detail', + args=[sec_service.id]) + + res = self.client.get(url) + + self.assertContains(res, "

Security Service Details: %s

" + % sec_service.name, + 1, 200) + self.assertContains(res, "
%s
" % sec_service.name, 1, 200) + self.assertContains(res, "
%s
" % sec_service.id, 1, 200) + self.assertContains(res, "
%s
" % sec_service.user, 1, 200) + self.assertContains(res, "
%s
" % sec_service.server, 1, 200) + self.assertContains(res, "
%s
" % sec_service.dns_ip, 1, 200) + self.assertContains(res, "
%s
" % sec_service.domain, 1, 200) + self.assertNoMessages() + api_manila.security_service_get.assert_called_once_with( + mock.ANY, sec_service.id) + + def test_detail_view_with_exception(self): + url = reverse( + 'horizon:admin:security_services:security_service_detail', + args=[test_data.sec_service.id]) + self.mock_object( + api_manila, "security_service_get", + mock.Mock(side_effect=horizon_exceptions.NotFound(404))) + + res = self.client.get(url) + + self.assertRedirectsNoFollow(res, INDEX_URL) + api_manila.security_service_get.assert_called_once_with( + mock.ANY, test_data.sec_service.id) + + def test_delete_security_service(self): + security_service = test_data.sec_service + formData = { + 'action': 'security_services__delete__%s' % security_service.id, + } + self.mock_object(api_manila, "security_service_delete") + self.mock_object( + api_manila, "security_service_list", + mock.Mock(return_value=[test_data.sec_service])) + + res = self.client.post(INDEX_URL, formData) + + api_keystone.tenant_list.assert_called_once_with(mock.ANY) + api_manila.security_service_delete.assert_called_once_with( + mock.ANY, test_data.sec_service.id) + api_manila.security_service_list.assert_called_once_with( + mock.ANY, search_opts={'all_tenants': True}) + self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/manila_ui/tests/dashboards/admin/share_instances/__init__.py b/manila_ui/tests/dashboards/admin/share_instances/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/tests/dashboards/admin/share_instances/tests.py b/manila_ui/tests/dashboards/admin/share_instances/tests.py new file mode 100644 index 00000000..26470811 --- /dev/null +++ b/manila_ui/tests/dashboards/admin/share_instances/tests.py @@ -0,0 +1,127 @@ +# Copyright (c) 2014 NetApp, Inc. +# Copyright (c) 2015 Mirantis, Inc. +# +# 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 django.core.urlresolvers import reverse +from horizon import exceptions as horizon_exceptions +import mock +from openstack_dashboard.api import keystone as api_keystone + +from manila_ui.api import manila as api_manila +from manila_ui.tests.dashboards.project.shares import test_data +from manila_ui.tests import helpers as test + +INDEX_URL = reverse('horizon:admin:share_instances:index') + + +class ShareInstanceTests(test.BaseAdminViewTests): + + def test_list_share_instances(self): + share_instances = [ + test_data.share_instance, + test_data.share_instance_no_ss, + ] + self.mock_object( + api_manila, "share_instance_list", + mock.Mock(return_value=share_instances)) + self.mock_object( + api_keystone, "tenant_list", mock.Mock(return_value=([], None))) + + res = self.client.get(INDEX_URL) + + self.assertContains(res, "

Share Instances

") + self.assertContains( + res, + '%s' % ( + share_instances[0].share_server_id, + share_instances[0].share_server_id), + 1, 200) + self.assertContains( + res, + '%s' % ( + share_instances[0].share_network_id, + share_instances[0].share_network_id), + 1, 200) + for si in share_instances: + self.assertContains( + res, '%s' % ( + si.id, si.id)) + self.assertContains(res, si.host) + self.assertContains(res, si.availability_zone) + self.assertContains( + res, + '%s' % ( + si.share_id, si.share_id), + 1, 200) + api_manila.share_instance_list.assert_called_once_with(mock.ANY) + self.assertEqual(0, api_keystone.tenant_list.call_count) + + def test_detail_view_share_instance(self): + share_instance = test_data.share_instance + share_id = share_instance.share_id + ss_id = share_instance.share_server_id + url = reverse('horizon:admin:share_instances:share_instance_detail', + args=[share_instance.id]) + self.mock_object( + api_manila, "share_instance_get", + mock.Mock(return_value=share_instance)) + self.mock_object( + api_manila, "share_instance_export_location_list", + mock.Mock(return_value=test_data.export_locations)) + + res = self.client.get(url) + + self.assertContains( + res, "

Share Instance Details: %s

" % share_instance.id, + 1, 200) + self.assertContains(res, "
%s
" % share_instance.id, 1, 200) + self.assertContains(res, "
Available
", 1, 200) + self.assertContains(res, "
%s
" % share_instance.host, 1, 200) + self.assertContains( + res, "
%s
" % share_instance.availability_zone, 1, 200) + self.assertContains( + res, + "
%s
" % ( + share_id, share_id), + 1, 200) + self.assertContains( + res, + "
%s
" % ( + share_instance.share_network_id, + share_instance.share_network_id), + 1, 200) + self.assertContains( + res, + "
%s
" % ( + ss_id, ss_id), + 1, 200) + self.assertNoMessages() + api_manila.share_instance_get.assert_called_once_with( + mock.ANY, share_instance.id) + api_manila.share_instance_export_location_list.assert_called_once_with( + mock.ANY, share_instance.id) + + def test_detail_view_share_instance_with_exception(self): + share_instance = test_data.share_instance + url = reverse('horizon:admin:share_instances:share_instance_detail', + args=[share_instance.id]) + self.mock_object( + api_manila, "share_instance_get", + mock.Mock(side_effect=horizon_exceptions.NotFound(404))) + + res = self.client.get(url) + + self.assertRedirectsNoFollow(res, INDEX_URL) + api_manila.share_instance_get.assert_called_once_with( + mock.ANY, share_instance.id) diff --git a/manila_ui/tests/dashboards/admin/share_networks/__init__.py b/manila_ui/tests/dashboards/admin/share_networks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/tests/dashboards/admin/share_networks/tests.py b/manila_ui/tests/dashboards/admin/share_networks/tests.py new file mode 100644 index 00000000..e5b090b4 --- /dev/null +++ b/manila_ui/tests/dashboards/admin/share_networks/tests.py @@ -0,0 +1,166 @@ +# Copyright (c) 2014 NetApp, Inc. +# Copyright (c) 2015 Mirantis, Inc. +# +# 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 django.core.urlresolvers import reverse +from horizon import exceptions as horizon_exceptions +import mock +from neutronclient.client import exceptions +from openstack_dashboard.api import keystone as api_keystone +from openstack_dashboard.api import neutron as api_neutron + +from manila_ui.api import manila as api_manila +from manila_ui.dashboards.admin import utils +from manila_ui.tests.dashboards.project.shares import test_data +from manila_ui.tests import helpers as test +from manila_ui.tests.test_data import keystone_data + +INDEX_URL = reverse('horizon:admin:share_networks:index') + + +class ShareNetworksTests(test.BaseAdminViewTests): + + def setUp(self): + super(self.__class__, self).setUp() + self.mock_object( + api_keystone, "tenant_list", + mock.Mock(return_value=(keystone_data.projects, None))) + # Reset taken list of projects to avoid test interference + utils.PROJECTS = {} + + def test_detail_view(self): + share_net = test_data.active_share_network + sec_service = test_data.sec_service + self.mock_object( + api_manila, "share_server_list", mock.Mock(return_value=[])) + self.mock_object( + api_manila, "share_network_get", mock.Mock(return_value=share_net)) + self.mock_object( + api_manila, "share_network_security_service_list", + 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)) + url = reverse('horizon:admin:share_networks:share_network_detail', + args=[share_net.id]) + + res = self.client.get(url) + + 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) + 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 + url = reverse('horizon:admin:share_networks:share_network_detail', + args=[share_net.id]) + self.mock_object( + api_manila, "share_server_list", mock.Mock(return_value=[])) + self.mock_object( + api_manila, "share_network_get", mock.Mock(return_value=share_net)) + self.mock_object( + api_manila, "share_network_security_service_list", + mock.Mock(return_value=[sec_service])) + self.mock_object( + api_neutron, "network_get", mock.Mock( + side_effect=exceptions.NeutronClientException('fake', 500))) + self.mock_object( + api_neutron, "subnet_get", mock.Mock( + side_effect=exceptions.NeutronClientException('fake', 500))) + + res = self.client.get(url) + + 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, "
Unknown
", 2, 200) + self.assertNotContains(res, "
%s
" % share_net.neutron_net_id) + self.assertNotContains(res, + "
%s
" % share_net.neutron_subnet_id) + 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_with_exception(self): + url = reverse('horizon:admin:share_networks:share_network_detail', + args=[test_data.active_share_network.id]) + self.mock_object( + api_manila, "share_network_get", + mock.Mock(side_effect=horizon_exceptions.NotFound(404))) + + res = self.client.get(url) + + self.assertRedirectsNoFollow(res, INDEX_URL) + api_manila.share_network_get.assert_called_once_with( + mock.ANY, test_data.active_share_network.id) + + 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", + mock.Mock(return_value=[ + test_data.active_share_network, + test_data.inactive_share_network])) + + res = self.client.post(INDEX_URL, formData) + + 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) + 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/admin/share_servers/__init__.py b/manila_ui/tests/dashboards/admin/share_servers/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/tests/dashboards/admin/share_servers/tests.py b/manila_ui/tests/dashboards/admin/share_servers/tests.py new file mode 100644 index 00000000..56ec6a24 --- /dev/null +++ b/manila_ui/tests/dashboards/admin/share_servers/tests.py @@ -0,0 +1,135 @@ +# Copyright (c) 2014 NetApp, Inc. +# Copyright (c) 2015 Mirantis, Inc. +# +# 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 django.core.urlresolvers import reverse +from horizon import exceptions as horizon_exceptions +import mock +from openstack_dashboard.api import keystone as api_keystone + +from manila_ui.api import manila as api_manila +from manila_ui.tests.dashboards.project.shares import test_data +from manila_ui.tests import helpers as test + +INDEX_URL = reverse('horizon:admin:share_servers:index') + + +class ShareServerTests(test.BaseAdminViewTests): + + def test_list_share_servers(self): + share_servers = [ + test_data.share_server, + test_data.share_server_errored, + ] + projects = [ + type('FakeProject', (object, ), + {'id': s.project_id, 'name': '%s_name' % s.project_id}) + for s in share_servers + ] + projects_dict = {p.id: p for p in projects} + self.mock_object( + api_manila, "share_server_list", + mock.Mock(return_value=share_servers)) + self.mock_object( + api_manila, "share_list", + mock.Mock(side_effect=[ + [], [test_data.share], [test_data.nameless_share]])) + self.mock_object( + api_keystone, "tenant_list", + mock.Mock(return_value=(projects, None))) + + res = self.client.get(INDEX_URL) + + self.assertContains(res, "

Share Servers

") + for share_server in share_servers: + self.assertContains( + res, + '%s' % ( + share_server.id, share_server.id), + 1, 200) + self.assertContains(res, share_server.host, 1, 200) + self.assertContains( + res, projects_dict[share_server.project_id].name, 1, 200) + self.assertContains( + res, + '%s' % ( + share_server.share_network_id, + share_server.share_network), + 1, 200) + api_manila.share_list.assert_has_calls([ + mock.call( + mock.ANY, search_opts={'share_server_id': share_server.id}) + for share_server in share_servers + ]) + api_manila.share_server_list.assert_called_once_with(mock.ANY) + self.assertEqual(1, api_keystone.tenant_list.call_count) + + def test_detail_view_share_server(self): + share_server = test_data.share_server + shares = [test_data.share, test_data.nameless_share] + url = reverse( + 'horizon:admin:share_servers:share_server_detail', + args=[share_server.id]) + self.mock_object( + api_manila, "share_server_get", + mock.Mock(return_value=share_server)) + self.mock_object( + api_manila, "share_list", mock.Mock(return_value=shares)) + + res = self.client.get(url) + + self.assertContains( + res, "

Share Server Details: %s

" % share_server.id, + 1, 200) + self.assertContains(res, "
%s
" % share_server.id, 1, 200) + self.assertContains(res, "
Active
", 1, 200) + self.assertContains(res, "
%s
" % share_server.host, 1, 200) + self.assertContains( + res, + "
%s
" % ( + share_server.share_network_id, + share_server.share_network_name), + 1, 200) + self.assertContains( + res, + "
%s
" % ( + shares[0].id, shares[0].name), + 1, 200) + self.assertContains( + res, + "
%s
" % ( + shares[1].id, shares[1].id), + 1, 200) + for k, v in share_server.backend_details.items(): + self.assertContains(res, "
%s
" % k) + self.assertContains(res, "
%s
" % v) + self.assertNoMessages() + api_manila.share_server_get.assert_called_once_with( + mock.ANY, share_server.id) + api_manila.share_list.assert_called_once_with( + mock.ANY, search_opts={"share_server_id": share_server.id}) + + def test_detail_view_share_server_with_exception(self): + share_server = test_data.share_server + url = reverse('horizon:admin:share_servers:share_server_detail', + args=[share_server.id]) + self.mock_object( + api_manila, "share_server_get", + mock.Mock(side_effect=horizon_exceptions.NotFound(404))) + + res = self.client.get(url) + + self.assertRedirectsNoFollow(res, INDEX_URL) + api_manila.share_server_get.assert_called_once_with( + mock.ANY, share_server.id) diff --git a/manila_ui/tests/dashboards/admin/share_snapshots/__init__.py b/manila_ui/tests/dashboards/admin/share_snapshots/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/tests/dashboards/admin/share_snapshots/tests.py b/manila_ui/tests/dashboards/admin/share_snapshots/tests.py new file mode 100644 index 00000000..35394ef9 --- /dev/null +++ b/manila_ui/tests/dashboards/admin/share_snapshots/tests.py @@ -0,0 +1,151 @@ +# Copyright (c) 2014 NetApp, Inc. +# Copyright (c) 2015 Mirantis, Inc. +# +# 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 django.core.urlresolvers import reverse +from horizon import exceptions as horizon_exceptions +import mock +from openstack_dashboard.api import keystone as api_keystone + +from manila_ui.api import manila as api_manila +from manila_ui.dashboards.admin import utils +from manila_ui.tests.dashboards.project.shares import test_data +from manila_ui.tests import helpers as test +from manila_ui.tests.test_data import keystone_data + +INDEX_URL = reverse('horizon:admin:share_snapshots:index') + + +class SnapshotsTests(test.BaseAdminViewTests): + + def setUp(self): + super(self.__class__, self).setUp() + self.mock_object( + api_keystone, "tenant_list", + mock.Mock(return_value=(keystone_data.projects, None))) + # Reset taken list of projects to avoid test interference + utils.PROJECTS = {} + + def test_detail_view(self): + snapshot = test_data.snapshot + share = test_data.share + url = reverse('horizon:admin:share_snapshots:share_snapshot_detail', + args=[snapshot.id]) + self.mock_object( + api_manila, "share_snapshot_get", mock.Mock(return_value=snapshot)) + self.mock_object( + api_manila, "share_get", mock.Mock(return_value=share)) + + res = self.client.get(url) + + self.assertContains(res, "

Snapshot Details: %s

" + % snapshot.name, + 1, 200) + self.assertContains(res, "
%s
" % snapshot.name, 1, 200) + self.assertContains(res, "
%s
" % snapshot.id, 1, 200) + self.assertContains(res, + "
%s
" % + (snapshot.share_id, share.name), 1, 200) + self.assertContains(res, "
%s GiB
" % snapshot.size, 1, 200) + self.assertNoMessages() + api_manila.share_get.assert_called_once_with(mock.ANY, share.id) + api_manila.share_snapshot_get.assert_called_once_with( + mock.ANY, snapshot.id) + + def test_detail_view_with_mount_support(self): + snapshot = test_data.snapshot_mount_support + rules = [test_data.ip_rule, test_data.user_rule, test_data.cephx_rule] + export_locations = test_data.admin_snapshot_export_locations + share = test_data.share_mount_snapshot + url = reverse('horizon:admin:share_snapshots:share_snapshot_detail', + args=[snapshot.id]) + self.mock_object( + api_manila, "share_snapshot_get", mock.Mock(return_value=snapshot)) + self.mock_object( + api_manila, "share_snapshot_rules_list", mock.Mock( + return_value=rules)) + self.mock_object( + api_manila, "share_snap_export_location_list", mock.Mock( + return_value=export_locations)) + self.mock_object( + api_manila, "share_get", mock.Mock(return_value=share)) + + res = self.client.get(url) + + self.assertContains(res, "

Snapshot Details: %s

" + % snapshot.name, + 1, 200) + self.assertContains(res, "
%s
" % snapshot.name, 1, 200) + self.assertContains(res, "
%s
" % snapshot.id, 1, 200) + self.assertContains(res, + "
%s
" % + (snapshot.share_id, share.name), 1, 200) + self.assertContains(res, "
%s GiB
" % snapshot.size, 1, 200) + for el in export_locations: + self.assertContains(res, "value=\"%s\"" % el.path, 1, 200) + self.assertContains( + res, "
Is admin only: %s
" % el.is_admin_only, + 1, 200) + self.assertContains( + res, ("
Snapshot Replica ID: %s
" % + el.share_snapshot_instance_id), 1, 200) + for rule in rules: + self.assertContains(res, "
%s
" % rule.access_type, 1, 200) + self.assertContains( + res, "
Access to: %s
" % rule.access_to, + 1, 200) + self.assertContains( + res, "
Status: active
", len(rules), 200) + self.assertNoMessages() + api_manila.share_get.assert_called_once_with(mock.ANY, share.id) + api_manila.share_snapshot_get.assert_called_once_with( + mock.ANY, snapshot.id) + api_manila.share_snapshot_rules_list.assert_called_once_with( + mock.ANY, snapshot.id) + api_manila.share_snap_export_location_list.assert_called_once_with( + mock.ANY, snapshot) + + def test_detail_view_with_exception(self): + url = reverse('horizon:admin:share_snapshots:share_snapshot_detail', + args=[test_data.snapshot.id]) + self.mock_object( + api_manila, "share_snapshot_get", + mock.Mock(side_effect=horizon_exceptions.NotFound(404))) + + res = self.client.get(url) + + self.assertRedirectsNoFollow(res, INDEX_URL) + api_manila.share_snapshot_get.assert_called_once_with( + mock.ANY, test_data.snapshot.id) + + def test_delete_snapshot(self): + share = test_data.share + snapshot = test_data.snapshot + formData = {'action': 'share_snapshots__delete__%s' % snapshot.id} + self.mock_object(api_manila, "share_snapshot_delete") + self.mock_object( + api_manila, "share_snapshot_list", + mock.Mock(return_value=[snapshot])) + self.mock_object( + api_manila, "share_list", mock.Mock(return_value=[share])) + + res = self.client.post(INDEX_URL, formData) + + api_keystone.tenant_list.assert_called_once_with(mock.ANY) + api_manila.share_snapshot_delete.assert_called_once_with( + mock.ANY, test_data.snapshot.id) + api_manila.share_snapshot_list.assert_called_once_with( + mock.ANY, search_opts={'all_tenants': True}) + api_manila.share_list.assert_called_once_with(mock.ANY) + self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/manila_ui/tests/dashboards/admin/share_types/__init__.py b/manila_ui/tests/dashboards/admin/share_types/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/manila_ui/tests/dashboards/admin/share_types/test_forms.py b/manila_ui/tests/dashboards/admin/share_types/test_forms.py new file mode 100644 index 00000000..49509892 --- /dev/null +++ b/manila_ui/tests/dashboards/admin/share_types/test_forms.py @@ -0,0 +1,254 @@ +# 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. + +import ddt +from django.core.handlers import wsgi +from django import forms as django_forms +from horizon import forms as horizon_forms +import mock + +from manila_ui.dashboards.admin.share_types import forms +from manila_ui.tests import helpers as base + + +@ddt.ddt +class ManilaDashboardsAdminSharesUpdateShareTypeFormTests(base.APITestCase): + + def setUp(self): + super(self.__class__, self).setUp() + FAKE_ENVIRON = {'REQUEST_METHOD': 'GET', 'wsgi.input': 'fake_input'} + self.request = wsgi.WSGIRequest(FAKE_ENVIRON) + + def _get_form(self, initial): + kwargs = { + 'prefix': None, + 'initial': initial, + } + return forms.UpdateShareType(self.request, **kwargs) + + @ddt.data( + ({}, []), + ({'foo': 'bar', 'quuz': 'zaab'}, ["foo=bar\r\n", "quuz=zaab\r\n"]), + ) + @ddt.unpack + def test___init__(self, extra_specs_dict_input, extra_specs_str_output): + form = self._get_form({'extra_specs': extra_specs_dict_input}) + + for expected_extra_spec in extra_specs_str_output: + self.assertIn(expected_extra_spec, form.initial['extra_specs']) + self.assertIn('extra_specs', list(form.fields.keys())) + self.assertTrue( + isinstance(form.fields['extra_specs'], horizon_forms.CharField)) + + @mock.patch('horizon.messages.success') + def test_handle_success_no_changes(self, mock_horizon_messages_success): + initial = {'id': 'fake_id', 'name': 'fake_name', 'extra_specs': {}} + form = self._get_form(initial) + data = {'extra_specs': ''} + + result = form.handle(self.request, data) + + self.assertTrue(result) + mock_horizon_messages_success.assert_called_once_with( + mock.ANY, mock.ANY) + + @mock.patch('horizon.messages.success') + def test_handle_success_only_set(self, mock_horizon_messages_success): + initial = { + 'id': 'fake_id', + 'name': 'fake_name', + 'extra_specs': {'foo': 'bar'} + } + form = self._get_form(initial) + data = {'extra_specs': 'foo=bar\r\n'} + + result = form.handle(self.request, data) + + self.assertTrue(result) + mock_horizon_messages_success.assert_called_once_with( + mock.ANY, mock.ANY) + self.manilaclient.share_types.get.assert_called_once_with( + initial['id']) + self.manilaclient.share_types.get.return_value.set_keys.\ + assert_called_once_with({'foo': 'bar'}) + self.assertFalse( + self.manilaclient.share_types.get.return_value.unset_keys.called) + + @mock.patch('horizon.messages.success') + def test_handle_success_only_unset(self, mock_horizon_messages_success): + initial = { + 'id': 'fake_id', + 'name': 'fake_name', + 'extra_specs': {'foo': 'bar'} + } + form = self._get_form(initial) + data = {'extra_specs': 'foo\r\n'} + share_types_get = self.manilaclient.share_types.get + share_types_get.return_value.get_keys.return_value = { + 'foo': 'bar', 'quuz': 'zaab'} + + result = form.handle(self.request, data) + + self.assertTrue(result) + mock_horizon_messages_success.assert_called_once_with( + mock.ANY, mock.ANY) + self.manilaclient.share_types.get.assert_has_calls([ + mock.call(initial['id'])]) + share_types_get.return_value.get_keys.assert_called_once_with() + self.assertFalse(share_types_get.return_value.set_keys.called) + share_types_get.return_value.unset_keys.assert_called_once_with( + {'foo'}) + + @mock.patch('horizon.messages.success') + def test_handle_success_set_and_unset(self, mock_horizon_messages_success): + initial = { + 'id': 'fake_id', + 'name': 'fake_name', + 'extra_specs': {'foo': 'bar'} + } + form = self._get_form(initial) + data = {'extra_specs': 'foo\r\nquuz=zaab'} + share_types_get = self.manilaclient.share_types.get + share_types_get.return_value.get_keys.return_value = {'foo': 'bar'} + + result = form.handle(self.request, data) + + self.assertTrue(result) + mock_horizon_messages_success.assert_called_once_with( + mock.ANY, mock.ANY) + self.manilaclient.share_types.get.assert_has_calls([ + mock.call(initial['id'])]) + share_types_get.return_value.get_keys.assert_called_once_with() + share_types_get.return_value.set_keys.assert_called_once_with( + {'quuz': 'zaab'}) + share_types_get.return_value.unset_keys.assert_called_once_with( + {'foo'}) + + def test_handle_validation_error(self): + initial = {'id': 'fake_id', 'name': 'fake_name', 'extra_specs': {}} + form = self._get_form(initial) + form.api_error = mock.Mock() + data = {'extra_specs': 'a b'} + + result = form.handle(self.request, data) + + self.assertFalse(result) + form.api_error.assert_called_once_with(mock.ANY) + + @mock.patch('horizon.exceptions.handle') + def test_handle_other_exception(self, mock_horizon_exceptions_handle): + django_forms.ValidationError + initial = {'id': 'fake_id', 'name': 'fake_name', 'extra_specs': {}} + form = self._get_form(initial) + data = {'extra_specs': None} + + result = form.handle(self.request, data) + + self.assertFalse(result) + mock_horizon_exceptions_handle.assert_called_once_with( + self.request, mock.ANY) + + +@ddt.ddt +class ManilaDashboardsAdminSharesCreateShareTypeFormTests(base.APITestCase): + + def setUp(self): + super(self.__class__, self).setUp() + FAKE_ENVIRON = {'REQUEST_METHOD': 'GET', 'wsgi.input': 'fake_input'} + self.request = wsgi.WSGIRequest(FAKE_ENVIRON) + + def _get_form(self, **kwargs): + return forms.CreateShareType(self.request, **kwargs) + + @mock.patch('horizon.messages.success') + def test_create_share_type(self, mock_horizon_messages_success): + form = self._get_form() + data = { + 'extra_specs': '', + 'is_public': False, + 'spec_driver_handles_share_servers': 'True', + 'name': 'share', + } + + result = form.handle(self.request, data) + + self.assertTrue(result) + self.manilaclient.share_types.create.assert_called_once_with( + name=data['name'], + spec_driver_handles_share_servers='true', + spec_snapshot_support=True, + is_public=data["is_public"]) + mock_horizon_messages_success.assert_called_once_with( + self.request, mock.ANY) + + @mock.patch('horizon.messages.success') + def test_create_share_type_with_extra_specs(self, + mock_horizon_messages_success): + form = self._get_form() + data = {'extra_specs': 'a=b \n c=d', + 'is_public': False, + 'spec_driver_handles_share_servers': 'True', + 'name': 'share'} + + result = form.handle(self.request, data) + + self.assertTrue(result) + + set_keys = self.manilaclient.share_types.get.return_value.set_keys + set_keys.assert_called_once_with( + {'a': 'b', 'c': 'd'}) + self.manilaclient.share_types.create.assert_called_once_with( + name=data['name'], + spec_driver_handles_share_servers='true', + spec_snapshot_support=True, + is_public=data["is_public"]) + mock_horizon_messages_success.assert_called_once_with( + self.request, mock.ANY) + + @ddt.data(True, False) + @mock.patch('horizon.messages.success') + def test_public_share_type_creation(self, + enable_public_share_type_creation, + mock_horizon_messages_success): + with self.settings(OPENSTACK_MANILA_FEATURES={ + 'enable_public_share_type_creation': + enable_public_share_type_creation}): + form = self._get_form() + + data = { + 'extra_specs': '', + 'is_public': enable_public_share_type_creation, + 'spec_driver_handles_share_servers': 'True', + 'name': 'share', + } + + result = form.handle(self.request, data) + + self.assertTrue(result) + self.assertEqual( + enable_public_share_type_creation, + form.enable_public_share_type_creation) + if enable_public_share_type_creation: + self.assertIn("is_public", form.fields) + self.assertTrue(form.fields["is_public"]) + else: + self.assertNotIn("is_public", form.fields) + self.manilaclient.share_types.create.assert_called_once_with( + name=data['name'], + spec_driver_handles_share_servers='true', + spec_snapshot_support=True, + is_public=enable_public_share_type_creation) + mock_horizon_messages_success.assert_called_once_with( + self.request, mock.ANY) diff --git a/manila_ui/tests/dashboards/admin/share_types/tests.py b/manila_ui/tests/dashboards/admin/share_types/tests.py new file mode 100644 index 00000000..f995575b --- /dev/null +++ b/manila_ui/tests/dashboards/admin/share_types/tests.py @@ -0,0 +1,88 @@ +# Copyright (c) 2014 NetApp, Inc. +# Copyright (c) 2015 Mirantis, Inc. +# +# 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 django.core.urlresolvers import reverse +import mock +from openstack_dashboard.api import keystone as api_keystone +from openstack_dashboard.api import neutron as api_neutron + +from manila_ui.api import manila as api_manila +from manila_ui.dashboards.admin import utils +from manila_ui.tests.dashboards.project.shares import test_data +from manila_ui.tests import helpers as test +from manila_ui.tests.test_data import keystone_data + +INDEX_URL = reverse('horizon:admin:share_types:index') + + +class ShareTypeTests(test.BaseAdminViewTests): + + def setUp(self): + super(self.__class__, self).setUp() + self.share_type = test_data.share_type + self.url = reverse('horizon:admin:share_types:update_type', + args=[self.share_type.id]) + self.mock_object( + api_manila, "share_type_get", + mock.Mock(return_value=self.share_type)) + self.mock_object( + api_keystone, "tenant_list", + mock.Mock(return_value=(keystone_data.projects, None))) + self.mock_object( + api_neutron, "is_service_enabled", mock.Mock(return_value=[True])) + # Reset taken list of projects to avoid test interference + utils.PROJECTS = {} + + def test_create_share_type(self): + url = reverse('horizon:admin:share_types:create_type') + data = { + 'is_public': True, + 'name': 'my_share_type', + 'spec_driver_handles_share_servers': 'False' + } + form_data = data.copy() + form_data['spec_driver_handles_share_servers'] = 'false' + self.mock_object(api_manila, "share_type_create") + + res = self.client.post(url, data) + + api_manila.share_type_create.assert_called_once_with( + mock.ANY, form_data['name'], + form_data['spec_driver_handles_share_servers'], + is_public=form_data['is_public']) + self.assertRedirectsNoFollow(res, INDEX_URL) + + def test_update_share_type_get(self): + res = self.client.get(self.url) + + api_manila.share_type_get.assert_called_once_with( + mock.ANY, self.share_type.id) + self.assertNoMessages() + self.assertTemplateUsed(res, 'admin/share_types/update.html') + + def test_update_share_type_post(self): + data = { + 'extra_specs': 'driver_handles_share_servers=True' + } + form_data = { + 'extra_specs': {'driver_handles_share_servers': 'True'}, + } + self.mock_object(api_manila, "share_type_set_extra_specs") + + res = self.client.post(self.url, data) + + api_manila.share_type_set_extra_specs.assert_called_once_with( + mock.ANY, self.share_type.id, form_data['extra_specs']) + self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/manila_ui/tests/dashboards/admin/shares/test_forms.py b/manila_ui/tests/dashboards/admin/shares/test_forms.py index 0026089a..248978b8 100644 --- a/manila_ui/tests/dashboards/admin/shares/test_forms.py +++ b/manila_ui/tests/dashboards/admin/shares/test_forms.py @@ -13,10 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. -import ddt from django.core.handlers import wsgi -from django import forms as django_forms -from horizon import forms as horizon_forms import mock from manila_ui.api import manila as api @@ -24,237 +21,6 @@ from manila_ui.dashboards.admin.shares import forms from manila_ui.tests import helpers as base -@ddt.ddt -class ManilaDashboardsAdminSharesUpdateShareTypeFormTests(base.APITestCase): - - def setUp(self): - super(self.__class__, self).setUp() - FAKE_ENVIRON = {'REQUEST_METHOD': 'GET', 'wsgi.input': 'fake_input'} - self.request = wsgi.WSGIRequest(FAKE_ENVIRON) - - def _get_form(self, initial): - kwargs = { - 'prefix': None, - 'initial': initial, - } - return forms.UpdateShareType(self.request, **kwargs) - - @ddt.data( - ({}, []), - ({'foo': 'bar', 'quuz': 'zaab'}, ["foo=bar\r\n", "quuz=zaab\r\n"]), - ) - @ddt.unpack - def test___init__(self, extra_specs_dict_input, extra_specs_str_output): - form = self._get_form({'extra_specs': extra_specs_dict_input}) - - for expected_extra_spec in extra_specs_str_output: - self.assertIn(expected_extra_spec, form.initial['extra_specs']) - self.assertIn('extra_specs', list(form.fields.keys())) - self.assertTrue( - isinstance(form.fields['extra_specs'], horizon_forms.CharField)) - - @mock.patch('horizon.messages.success') - def test_handle_success_no_changes(self, mock_horizon_messages_success): - initial = {'id': 'fake_id', 'name': 'fake_name', 'extra_specs': {}} - form = self._get_form(initial) - data = {'extra_specs': ''} - - result = form.handle(self.request, data) - - self.assertTrue(result) - mock_horizon_messages_success.assert_called_once_with( - mock.ANY, mock.ANY) - - @mock.patch('horizon.messages.success') - def test_handle_success_only_set(self, mock_horizon_messages_success): - initial = { - 'id': 'fake_id', - 'name': 'fake_name', - 'extra_specs': {'foo': 'bar'} - } - form = self._get_form(initial) - data = {'extra_specs': 'foo=bar\r\n'} - - result = form.handle(self.request, data) - - self.assertTrue(result) - mock_horizon_messages_success.assert_called_once_with( - mock.ANY, mock.ANY) - self.manilaclient.share_types.get.assert_called_once_with( - initial['id']) - self.manilaclient.share_types.get.return_value.set_keys.\ - assert_called_once_with({'foo': 'bar'}) - self.assertFalse( - self.manilaclient.share_types.get.return_value.unset_keys.called) - - @mock.patch('horizon.messages.success') - def test_handle_success_only_unset(self, mock_horizon_messages_success): - initial = { - 'id': 'fake_id', - 'name': 'fake_name', - 'extra_specs': {'foo': 'bar'} - } - form = self._get_form(initial) - data = {'extra_specs': 'foo\r\n'} - share_types_get = self.manilaclient.share_types.get - share_types_get.return_value.get_keys.return_value = { - 'foo': 'bar', 'quuz': 'zaab'} - - result = form.handle(self.request, data) - - self.assertTrue(result) - mock_horizon_messages_success.assert_called_once_with( - mock.ANY, mock.ANY) - self.manilaclient.share_types.get.assert_has_calls([ - mock.call(initial['id'])]) - share_types_get.return_value.get_keys.assert_called_once_with() - self.assertFalse(share_types_get.return_value.set_keys.called) - share_types_get.return_value.unset_keys.assert_called_once_with( - {'foo'}) - - @mock.patch('horizon.messages.success') - def test_handle_success_set_and_unset(self, mock_horizon_messages_success): - initial = { - 'id': 'fake_id', - 'name': 'fake_name', - 'extra_specs': {'foo': 'bar'} - } - form = self._get_form(initial) - data = {'extra_specs': 'foo\r\nquuz=zaab'} - share_types_get = self.manilaclient.share_types.get - share_types_get.return_value.get_keys.return_value = {'foo': 'bar'} - - result = form.handle(self.request, data) - - self.assertTrue(result) - mock_horizon_messages_success.assert_called_once_with( - mock.ANY, mock.ANY) - self.manilaclient.share_types.get.assert_has_calls([ - mock.call(initial['id'])]) - share_types_get.return_value.get_keys.assert_called_once_with() - share_types_get.return_value.set_keys.assert_called_once_with( - {'quuz': 'zaab'}) - share_types_get.return_value.unset_keys.assert_called_once_with( - {'foo'}) - - def test_handle_validation_error(self): - initial = {'id': 'fake_id', 'name': 'fake_name', 'extra_specs': {}} - form = self._get_form(initial) - form.api_error = mock.Mock() - data = {'extra_specs': 'a b'} - - result = form.handle(self.request, data) - - self.assertFalse(result) - form.api_error.assert_called_once_with(mock.ANY) - - @mock.patch('horizon.exceptions.handle') - def test_handle_other_exception(self, mock_horizon_exceptions_handle): - django_forms.ValidationError - initial = {'id': 'fake_id', 'name': 'fake_name', 'extra_specs': {}} - form = self._get_form(initial) - data = {'extra_specs': None} - - result = form.handle(self.request, data) - - self.assertFalse(result) - mock_horizon_exceptions_handle.assert_called_once_with( - self.request, mock.ANY) - - -@ddt.ddt -class ManilaDashboardsAdminSharesCreateShareTypeFormTests(base.APITestCase): - - def setUp(self): - super(self.__class__, self).setUp() - FAKE_ENVIRON = {'REQUEST_METHOD': 'GET', 'wsgi.input': 'fake_input'} - self.request = wsgi.WSGIRequest(FAKE_ENVIRON) - - def _get_form(self, **kwargs): - return forms.CreateShareType(self.request, **kwargs) - - @mock.patch('horizon.messages.success') - def test_create_share_type(self, mock_horizon_messages_success): - form = self._get_form() - data = { - 'extra_specs': '', - 'is_public': False, - 'spec_driver_handles_share_servers': 'True', - 'name': 'share', - } - - result = form.handle(self.request, data) - - self.assertTrue(result) - self.manilaclient.share_types.create.assert_called_once_with( - name=data['name'], - spec_driver_handles_share_servers='true', - spec_snapshot_support=True, - is_public=data["is_public"]) - mock_horizon_messages_success.assert_called_once_with( - self.request, mock.ANY) - - @mock.patch('horizon.messages.success') - def test_create_share_type_with_extra_specs(self, - mock_horizon_messages_success): - form = self._get_form() - data = {'extra_specs': 'a=b \n c=d', - 'is_public': False, - 'spec_driver_handles_share_servers': 'True', - 'name': 'share'} - - result = form.handle(self.request, data) - - self.assertTrue(result) - - set_keys = self.manilaclient.share_types.get.return_value.set_keys - set_keys.assert_called_once_with( - {'a': 'b', 'c': 'd'}) - self.manilaclient.share_types.create.assert_called_once_with( - name=data['name'], - spec_driver_handles_share_servers='true', - spec_snapshot_support=True, - is_public=data["is_public"]) - mock_horizon_messages_success.assert_called_once_with( - self.request, mock.ANY) - - @ddt.data(True, False) - @mock.patch('horizon.messages.success') - def test_public_share_type_creation(self, - enable_public_share_type_creation, - mock_horizon_messages_success): - with self.settings(OPENSTACK_MANILA_FEATURES={ - 'enable_public_share_type_creation': - enable_public_share_type_creation}): - form = self._get_form() - - data = { - 'extra_specs': '', - 'is_public': enable_public_share_type_creation, - 'spec_driver_handles_share_servers': 'True', - 'name': 'share', - } - - result = form.handle(self.request, data) - - self.assertTrue(result) - self.assertEqual( - enable_public_share_type_creation, - form.enable_public_share_type_creation) - if enable_public_share_type_creation: - self.assertIn("is_public", form.fields) - self.assertTrue(form.fields["is_public"]) - else: - self.assertNotIn("is_public", form.fields) - self.manilaclient.share_types.create.assert_called_once_with( - name=data['name'], - spec_driver_handles_share_servers='true', - spec_snapshot_support=True, - is_public=enable_public_share_type_creation) - mock_horizon_messages_success.assert_called_once_with( - self.request, mock.ANY) - - class ManilaDashboardsAdminMigrationFormTests(base.APITestCase): def setUp(self): diff --git a/manila_ui/tests/dashboards/admin/shares/tests.py b/manila_ui/tests/dashboards/admin/shares/tests.py index 964b7d3e..79386857 100644 --- a/manila_ui/tests/dashboards/admin/shares/tests.py +++ b/manila_ui/tests/dashboards/admin/shares/tests.py @@ -13,27 +13,19 @@ # License for the specific language governing permissions and limitations # under the License. -""" -This module contains test suites that cover tabs from admin dashboard -by getting generated pages and verifying results. -""" - import ddt from django.core.urlresolvers import reverse -from horizon import exceptions as horizon_exceptions import mock -from neutronclient.client import exceptions - -from manila_ui.api import manila as api_manila -from manila_ui.dashboards.admin.shares import utils -from manila_ui.tests.dashboards.project.shares import test_data -from manila_ui.tests import helpers as test -from manila_ui.tests.test_data import keystone_data - from openstack_dashboard.api import keystone as api_keystone from openstack_dashboard.api import neutron as api_neutron from openstack_dashboard.usage import quotas +from manila_ui.api import manila as api_manila +from manila_ui.dashboards.admin import utils +from manila_ui.tests.dashboards.project.shares import test_data +from manila_ui.tests import helpers as test +from manila_ui.tests.test_data import keystone_data + INDEX_URL = reverse('horizon:admin:shares:index') @@ -49,39 +41,18 @@ class SharesTests(test.BaseAdminViewTests): # Reset taken list of projects to avoid test interference utils.PROJECTS = {} - @ddt.data(True, False) - def test_index_with_all_tabs(self, single_time_slot): + def test_index(self): snaps = [test_data.snapshot] shares = [test_data.share, test_data.nameless_share, test_data.other_share] - share_networks = [test_data.inactive_share_network, - test_data.active_share_network] - security_services = [test_data.sec_service] - if single_time_slot: - utils.timeutils.now.side_effect = [4] + [5 + i for i in range(4)] - else: - utils.timeutils.now.side_effect = [4] + [24 + i for i in range(4)] + utils.timeutils.now.side_effect = [4] + [24 + i for i in range(4)] self.mock_object( api_manila, "share_list", mock.Mock(return_value=shares)) self.mock_object( api_manila, "share_snapshot_list", mock.Mock(return_value=snaps)) - self.mock_object( - api_manila, "share_network_list", - mock.Mock(return_value=share_networks)) - self.mock_object( - api_manila, "share_type_list", mock.Mock(return_value=[])) - self.mock_object( - api_manila, "share_server_list", mock.Mock(return_value=[])) - self.mock_object( - api_manila, "security_service_list", - mock.Mock(return_value=security_services)) self.mock_object( api_neutron, "is_service_enabled", mock.Mock(return_value=[True])) - 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( quotas, "tenant_limit_usages", mock.Mock(return_value=test_data.quota_usage)) @@ -91,24 +62,13 @@ class SharesTests(test.BaseAdminViewTests): res = self.client.get(INDEX_URL) - if single_time_slot: - api_keystone.tenant_list.assert_called_once_with(mock.ANY) - else: - api_keystone.tenant_list.assert_has_calls( - [mock.call(mock.ANY)] * 2) - api_neutron.network_list.assert_called_once_with(mock.ANY) - api_neutron.subnet_list.assert_called_once_with(mock.ANY) - api_manila.share_type_list.assert_called_once_with(mock.ANY) - api_manila.share_server_list.assert_called_once_with(mock.ANY) - api_manila.share_network_list.assert_called_once_with( - mock.ANY, detailed=True, search_opts={'all_tenants': True}) - api_manila.security_service_list.assert_called_once_with( + self.assertTemplateUsed(res, 'admin/shares/index.html') + self.assertEqual(res.status_code, 200) + api_manila.share_list.assert_called_with( mock.ANY, search_opts={'all_tenants': True}) api_manila.share_snapshot_list.assert_called_with( - mock.ANY, search_opts={'all_tenants': True}) - api_manila.share_list.assert_called_with(mock.ANY) - self.assertEqual(res.status_code, 200) - self.assertTemplateUsed(res, 'admin/shares/index.html') + mock.ANY, detailed=True, search_opts={'all_tenants': True}) + api_keystone.tenant_list.assert_called_once_with(mock.ANY) def test_delete_share(self): url = reverse('horizon:admin:shares:index') @@ -295,606 +255,3 @@ class SharesTests(test.BaseAdminViewTests): else: self.assertTemplateUsed( res, 'admin/shares/' + method + '.html') - - -class ShareInstanceTests(test.BaseAdminViewTests): - - def test_list_share_instances(self): - share_instances = [ - test_data.share_instance, - test_data.share_instance_no_ss, - ] - url = reverse('horizon:admin:shares:share_instances_tab') - self.mock_object( - api_manila, "share_instance_list", - mock.Mock(return_value=share_instances)) - self.mock_object( - api_keystone, "tenant_list", mock.Mock(return_value=([], None))) - - res = self.client.get(url) - - self.assertContains(res, "

Shares

") - self.assertContains( - res, - '%s' % ( - share_instances[0].share_server_id, - share_instances[0].share_server_id), - 1, 200) - self.assertContains( - res, - '%s' % ( - share_instances[0].share_network_id, - share_instances[0].share_network_id), - 1, 200) - for si in share_instances: - self.assertContains( - res, '%s' % ( - si.id, si.id)) - self.assertContains(res, si.host) - self.assertContains(res, si.availability_zone) - self.assertContains( - res, - '%s' % ( - si.share_id, si.share_id), - 1, 200) - api_manila.share_instance_list.assert_called_once_with(mock.ANY) - self.assertEqual(5, api_keystone.tenant_list.call_count) - - def test_detail_view_share_instance(self): - share_instance = test_data.share_instance - share_id = share_instance.share_id - ss_id = share_instance.share_server_id - url = reverse('horizon:admin:shares:share_instance_detail', - args=[share_instance.id]) - self.mock_object( - api_manila, "share_instance_get", - mock.Mock(return_value=share_instance)) - self.mock_object( - api_manila, "share_instance_export_location_list", - mock.Mock(return_value=test_data.export_locations)) - - res = self.client.get(url) - - self.assertContains( - res, "

Share Instance Details: %s

" % share_instance.id, - 1, 200) - self.assertContains(res, "
%s
" % share_instance.id, 1, 200) - self.assertContains(res, "
Available
", 1, 200) - self.assertContains(res, "
%s
" % share_instance.host, 1, 200) - self.assertContains( - res, "
%s
" % share_instance.availability_zone, 1, 200) - self.assertContains( - res, - "
%s
" % ( - share_id, share_id), - 1, 200) - self.assertContains( - res, - "
%s
" % ( - share_instance.share_network_id, - share_instance.share_network_id), - 1, 200) - self.assertContains( - res, - "
%s
" % ( - ss_id, ss_id), - 1, 200) - self.assertNoMessages() - api_manila.share_instance_get.assert_called_once_with( - mock.ANY, share_instance.id) - api_manila.share_instance_export_location_list.assert_called_once_with( - mock.ANY, share_instance.id) - - def test_detail_view_share_instance_with_exception(self): - share_instance = test_data.share_instance - url = reverse('horizon:admin:shares:share_instance_detail', - args=[share_instance.id]) - self.mock_object( - api_manila, "share_instance_get", - mock.Mock(side_effect=horizon_exceptions.NotFound(404))) - - res = self.client.get(url) - - self.assertRedirectsNoFollow(res, INDEX_URL) - api_manila.share_instance_get.assert_called_once_with( - mock.ANY, share_instance.id) - - -class ShareServerTests(test.BaseAdminViewTests): - - def test_list_share_servers(self): - share_servers = [ - test_data.share_server, - test_data.share_server_errored, - ] - projects = [ - type('FakeProject', (object, ), - {'id': s.project_id, 'name': '%s_name' % s.project_id}) - for s in share_servers - ] - projects_dict = {p.id: p for p in projects} - url = reverse('horizon:admin:shares:share_servers_tab') - self.mock_object( - api_manila, "share_server_list", - mock.Mock(return_value=share_servers)) - self.mock_object( - api_manila, "share_list", - mock.Mock(side_effect=[ - [], [test_data.share], [test_data.nameless_share]])) - self.mock_object( - api_keystone, "tenant_list", - mock.Mock(return_value=(projects, None))) - - res = self.client.get(url) - - self.assertContains(res, "

Shares

") - for share_server in share_servers: - self.assertContains( - res, - '%s' % ( - share_server.id, share_server.id), - 1, 200) - self.assertContains(res, share_server.host, 1, 200) - self.assertContains( - res, projects_dict[share_server.project_id].name, 1, 200) - self.assertContains( - res, - '%s' % ( - share_server.share_network_id, - share_server.share_network), - 1, 200) - api_manila.share_list.assert_has_calls([ - mock.call( - mock.ANY, search_opts={'share_server_id': share_server.id}) - for share_server in share_servers - ]) - api_manila.share_server_list.assert_called_once_with(mock.ANY) - self.assertEqual(1, api_keystone.tenant_list.call_count) - - def test_detail_view_share_server(self): - share_server = test_data.share_server - shares = [test_data.share, test_data.nameless_share] - url = reverse( - 'horizon:admin:shares:share_server_detail', args=[share_server.id]) - self.mock_object( - api_manila, "share_server_get", - mock.Mock(return_value=share_server)) - self.mock_object( - api_manila, "share_list", mock.Mock(return_value=shares)) - - res = self.client.get(url) - - self.assertContains( - res, "

Share Server Details: %s

" % share_server.id, - 1, 200) - self.assertContains(res, "
%s
" % share_server.id, 1, 200) - self.assertContains(res, "
Active
", 1, 200) - self.assertContains(res, "
%s
" % share_server.host, 1, 200) - self.assertContains( - res, - "
%s
" % ( - share_server.share_network_id, - share_server.share_network_name), - 1, 200) - self.assertContains( - res, - "
%s
" % ( - shares[0].id, shares[0].name), - 1, 200) - self.assertContains( - res, - "
%s
" % ( - shares[1].id, shares[1].id), - 1, 200) - for k, v in share_server.backend_details.items(): - self.assertContains(res, "
%s
" % k) - self.assertContains(res, "
%s
" % v) - self.assertNoMessages() - api_manila.share_server_get.assert_called_once_with( - mock.ANY, share_server.id) - api_manila.share_list.assert_called_once_with( - mock.ANY, search_opts={"share_server_id": share_server.id}) - - def test_detail_view_share_server_with_exception(self): - share_server = test_data.share_server - url = reverse('horizon:admin:shares:share_server_detail', - args=[share_server.id]) - self.mock_object( - api_manila, "share_server_get", - mock.Mock(side_effect=horizon_exceptions.NotFound(404))) - - res = self.client.get(url) - - self.assertRedirectsNoFollow(res, INDEX_URL) - api_manila.share_server_get.assert_called_once_with( - mock.ANY, share_server.id) - - -class SecurityServicesTests(test.BaseAdminViewTests): - - def setUp(self): - super(self.__class__, self).setUp() - self.mock_object( - api_keystone, "tenant_list", - mock.Mock(return_value=(keystone_data.projects, None))) - # Reset taken list of projects to avoid test interference - utils.PROJECTS = {} - - def test_detail_view(self): - sec_service = test_data.sec_service - self.mock_object( - api_manila, "security_service_get", - mock.Mock(return_value=sec_service)) - url = reverse('horizon:admin:shares:security_service_detail', - args=[sec_service.id]) - - res = self.client.get(url) - - self.assertContains(res, "

Security Service Details: %s

" - % sec_service.name, - 1, 200) - self.assertContains(res, "
%s
" % sec_service.name, 1, 200) - self.assertContains(res, "
%s
" % sec_service.id, 1, 200) - self.assertContains(res, "
%s
" % sec_service.user, 1, 200) - self.assertContains(res, "
%s
" % sec_service.server, 1, 200) - self.assertContains(res, "
%s
" % sec_service.dns_ip, 1, 200) - self.assertContains(res, "
%s
" % sec_service.domain, 1, 200) - self.assertNoMessages() - api_manila.security_service_get.assert_called_once_with( - mock.ANY, sec_service.id) - - def test_detail_view_with_exception(self): - url = reverse('horizon:admin:shares:security_service_detail', - args=[test_data.sec_service.id]) - self.mock_object( - api_manila, "security_service_get", - mock.Mock(side_effect=horizon_exceptions.NotFound(404))) - - res = self.client.get(url) - - self.assertRedirectsNoFollow(res, INDEX_URL) - api_manila.security_service_get.assert_called_once_with( - mock.ANY, test_data.sec_service.id) - - def test_delete_security_service(self): - security_service = test_data.sec_service - formData = { - 'action': 'security_services__delete__%s' % security_service.id, - } - self.mock_object(api_manila, "security_service_delete") - self.mock_object( - api_manila, "security_service_list", - mock.Mock(return_value=[test_data.sec_service])) - url = reverse('horizon:admin:shares:index') - - res = self.client.post(url, formData) - - api_keystone.tenant_list.assert_called_once_with(mock.ANY) - api_manila.security_service_delete.assert_called_once_with( - mock.ANY, test_data.sec_service.id) - api_manila.security_service_list.assert_called_once_with( - mock.ANY, search_opts={'all_tenants': True}) - self.assertRedirectsNoFollow(res, INDEX_URL) - - -class ShareNetworksTests(test.BaseAdminViewTests): - - def setUp(self): - super(self.__class__, self).setUp() - self.mock_object( - api_keystone, "tenant_list", - mock.Mock(return_value=(keystone_data.projects, None))) - # Reset taken list of projects to avoid test interference - utils.PROJECTS = {} - - def test_detail_view(self): - share_net = test_data.active_share_network - sec_service = test_data.sec_service - self.mock_object( - api_manila, "share_server_list", mock.Mock(return_value=[])) - self.mock_object( - api_manila, "share_network_get", mock.Mock(return_value=share_net)) - self.mock_object( - api_manila, "share_network_security_service_list", - 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)) - url = reverse('horizon:project:shares:share_network_detail', - args=[share_net.id]) - - res = self.client.get(url) - - 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) - 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 - url = reverse('horizon:project:shares:share_network_detail', - args=[share_net.id]) - self.mock_object( - api_manila, "share_server_list", mock.Mock(return_value=[])) - self.mock_object( - api_manila, "share_network_get", mock.Mock(return_value=share_net)) - self.mock_object( - api_manila, "share_network_security_service_list", - mock.Mock(return_value=[sec_service])) - self.mock_object( - api_neutron, "network_get", mock.Mock( - side_effect=exceptions.NeutronClientException('fake', 500))) - self.mock_object( - api_neutron, "subnet_get", mock.Mock( - side_effect=exceptions.NeutronClientException('fake', 500))) - - res = self.client.get(url) - - 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, "
Unknown
", 2, 200) - self.assertNotContains(res, "
%s
" % share_net.neutron_net_id) - self.assertNotContains(res, - "
%s
" % share_net.neutron_subnet_id) - 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_with_exception(self): - url = reverse('horizon:admin:shares:share_network_detail', - args=[test_data.active_share_network.id]) - self.mock_object( - api_manila, "share_network_get", - mock.Mock(side_effect=horizon_exceptions.NotFound(404))) - - res = self.client.get(url) - - self.assertRedirectsNoFollow(res, INDEX_URL) - api_manila.share_network_get.assert_called_once_with( - mock.ANY, test_data.active_share_network.id) - - 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", - mock.Mock(return_value=[ - test_data.active_share_network, - test_data.inactive_share_network])) - - res = self.client.post(INDEX_URL, formData) - - 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) - 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) - - -class SnapshotsTests(test.BaseAdminViewTests): - - def setUp(self): - super(self.__class__, self).setUp() - self.mock_object( - api_keystone, "tenant_list", - mock.Mock(return_value=(keystone_data.projects, None))) - # Reset taken list of projects to avoid test interference - utils.PROJECTS = {} - - def test_detail_view(self): - snapshot = test_data.snapshot - share = test_data.share - url = reverse('horizon:project:shares:snapshot-detail', - args=[snapshot.id]) - self.mock_object( - api_manila, "share_snapshot_get", mock.Mock(return_value=snapshot)) - self.mock_object( - api_manila, "share_get", mock.Mock(return_value=share)) - - res = self.client.get(url) - - self.assertContains(res, "

Snapshot Details: %s

" - % snapshot.name, - 1, 200) - self.assertContains(res, "
%s
" % snapshot.name, 1, 200) - self.assertContains(res, "
%s
" % snapshot.id, 1, 200) - self.assertContains(res, - "
%s
" % - (snapshot.share_id, share.name), 1, 200) - self.assertContains(res, "
%s GiB
" % snapshot.size, 1, 200) - self.assertNoMessages() - api_manila.share_get.assert_called_once_with(mock.ANY, share.id) - api_manila.share_snapshot_get.assert_called_once_with( - mock.ANY, snapshot.id) - - def test_detail_view_with_mount_support(self): - snapshot = test_data.snapshot_mount_support - rules = [test_data.ip_rule, test_data.user_rule, test_data.cephx_rule] - export_locations = test_data.admin_snapshot_export_locations - share = test_data.share_mount_snapshot - url = reverse('horizon:project:shares:snapshot-detail', - args=[snapshot.id]) - self.mock_object( - api_manila, "share_snapshot_get", mock.Mock(return_value=snapshot)) - self.mock_object( - api_manila, "share_snapshot_rules_list", mock.Mock( - return_value=rules)) - self.mock_object( - api_manila, "share_snap_export_location_list", mock.Mock( - return_value=export_locations)) - self.mock_object( - api_manila, "share_get", mock.Mock(return_value=share)) - - res = self.client.get(url) - - self.assertContains(res, "

Snapshot Details: %s

" - % snapshot.name, - 1, 200) - self.assertContains(res, "
%s
" % snapshot.name, 1, 200) - self.assertContains(res, "
%s
" % snapshot.id, 1, 200) - self.assertContains(res, - "
%s
" % - (snapshot.share_id, share.name), 1, 200) - self.assertContains(res, "
%s GiB
" % snapshot.size, 1, 200) - for el in export_locations: - self.assertContains(res, "value=\"%s\"" % el.path, 1, 200) - self.assertContains( - res, "
Is admin only: %s
" % el.is_admin_only, - 1, 200) - self.assertContains( - res, ("
Snapshot Replica ID: %s
" % - el.share_snapshot_instance_id), 1, 200) - for rule in rules: - self.assertContains(res, "
%s
" % rule.access_type, 1, 200) - self.assertContains( - res, "
Access to: %s
" % rule.access_to, - 1, 200) - self.assertContains( - res, "
Status: active
", len(rules), 200) - self.assertNoMessages() - api_manila.share_get.assert_called_once_with(mock.ANY, share.id) - api_manila.share_snapshot_get.assert_called_once_with( - mock.ANY, snapshot.id) - api_manila.share_snapshot_rules_list.assert_called_once_with( - mock.ANY, snapshot.id) - api_manila.share_snap_export_location_list.assert_called_once_with( - mock.ANY, snapshot) - - def test_detail_view_with_exception(self): - url = reverse('horizon:admin:shares:snapshot-detail', - args=[test_data.snapshot.id]) - self.mock_object( - api_manila, "share_snapshot_get", - mock.Mock(side_effect=horizon_exceptions.NotFound(404))) - - res = self.client.get(url) - - self.assertRedirectsNoFollow(res, INDEX_URL) - api_manila.share_snapshot_get.assert_called_once_with( - mock.ANY, test_data.snapshot.id) - - def test_delete_snapshot(self): - share = test_data.share - snapshot = test_data.snapshot - formData = {'action': 'snapshots__delete__%s' % snapshot.id} - self.mock_object(api_manila, "share_snapshot_delete") - self.mock_object( - api_manila, "share_snapshot_list", - mock.Mock(return_value=[snapshot])) - self.mock_object( - api_manila, "share_list", mock.Mock(return_value=[share])) - url = reverse('horizon:admin:shares:index') - - res = self.client.post(url, formData) - - api_keystone.tenant_list.assert_called_once_with(mock.ANY) - api_manila.share_snapshot_delete.assert_called_once_with( - mock.ANY, test_data.snapshot.id) - api_manila.share_snapshot_list.assert_called_once_with( - mock.ANY, search_opts={'all_tenants': True}) - api_manila.share_list.assert_called_once_with(mock.ANY) - self.assertRedirectsNoFollow(res, INDEX_URL) - - -class ShareTypeTests(test.BaseAdminViewTests): - - def setUp(self): - super(self.__class__, self).setUp() - self.share_type = test_data.share_type - self.url = reverse('horizon:admin:shares:update_type', - args=[self.share_type.id]) - self.mock_object( - api_manila, "share_type_get", - mock.Mock(return_value=self.share_type)) - self.mock_object( - api_keystone, "tenant_list", - mock.Mock(return_value=(keystone_data.projects, None))) - self.mock_object( - api_neutron, "is_service_enabled", mock.Mock(return_value=[True])) - # Reset taken list of projects to avoid test interference - utils.PROJECTS = {} - - def test_create_share_type(self): - url = reverse('horizon:admin:shares:create_type') - data = { - 'is_public': True, - 'name': 'my_share_type', - 'spec_driver_handles_share_servers': 'False' - } - form_data = data.copy() - form_data['spec_driver_handles_share_servers'] = 'false' - self.mock_object(api_manila, "share_type_create") - - res = self.client.post(url, data) - - api_manila.share_type_create.assert_called_once_with( - mock.ANY, form_data['name'], - form_data['spec_driver_handles_share_servers'], - is_public=form_data['is_public']) - self.assertRedirectsNoFollow(res, INDEX_URL) - - def test_update_share_type_get(self): - res = self.client.get(self.url) - - api_manila.share_type_get.assert_called_once_with( - mock.ANY, self.share_type.id) - self.assertNoMessages() - self.assertTemplateUsed(res, 'admin/shares/update_share_type.html') - - def test_update_share_type_post(self): - data = { - 'extra_specs': 'driver_handles_share_servers=True' - } - form_data = { - 'extra_specs': {'driver_handles_share_servers': 'True'}, - } - self.mock_object(api_manila, "share_type_set_extra_specs") - - res = self.client.post(self.url, data) - - api_manila.share_type_set_extra_specs.assert_called_once_with( - mock.ANY, self.share_type.id, form_data['extra_specs']) - self.assertRedirectsNoFollow(res, INDEX_URL) diff --git a/manila_ui/tests/test_data/keystone_data.py b/manila_ui/tests/test_data/keystone_data.py index b616b21a..67d79b0d 100644 --- a/manila_ui/tests/test_data/keystone_data.py +++ b/manila_ui/tests/test_data/keystone_data.py @@ -20,9 +20,9 @@ def data(TEST): "endpoints_links": [], "endpoints": [ {"region": "RegionOne", - "adminURL": "http://admin.manila.example.com:8786/v1.0", - "internalURL": "http://int.manila.example.com:8786/v1.0", - "publicURL": "http://public.manila.example.com:8786/v1.0"}]}, + "adminURL": "http://admin.manila.example.com:8786/v1", + "internalURL": "http://int.manila.example.com:8786/v1", + "publicURL": "http://public.manila.example.com:8786/v1"}]}, ) projects = [ diff --git a/releasenotes/notes/add-share-panel-group-to-admin-dashboard-ef6a02243c0e776c.yaml b/releasenotes/notes/add-share-panel-group-to-admin-dashboard-ef6a02243c0e776c.yaml new file mode 100644 index 00000000..0ad006b0 --- /dev/null +++ b/releasenotes/notes/add-share-panel-group-to-admin-dashboard-ef6a02243c0e776c.yaml @@ -0,0 +1,12 @@ +--- +features: + - Admin dashboard now has manila-specific panel group called 'share'. + All 'tabs' we had before are panels in this group now. Each panel + is loaded in separate page. It allows us to avoid making redundant + API calls that we did loading all tabs at once. +upgrade: + - URLs for resources in admin dashboard were changed. + One part of changes is removal of intermediate "shares" part. Example - + was - "/admin/shares/share_networks/" + became - "/admin/share_networks/" + Other part is rename of resource actions to be more alike.