diff --git a/README.rst b/README.rst
index 905532b2..9613c506 100644
--- a/README.rst
+++ b/README.rst
@@ -93,9 +93,11 @@ It is possible to enable or disable some Manila UI features. To do so,
look for files located in "manila_ui/local/local_settings.d/" directory,
where you can redefine the values of the OPENSTACK_MANILA_FEATURES dict::
+ * enable_share_groups
* enable_replication
* enable_migration
* enable_public_share_type_creation
+ * enable_public_share_group_type_creation
* enable_public_shares
* enabled_share_protocols
@@ -108,9 +110,11 @@ expected to redefine enabled_share_protocols as follows:
.. code-block:: python
OPENSTACK_MANILA_FEATURES = {
+ 'enable_share_groups': True,
'enable_replication': True,
'enable_migration': True,
'enable_public_share_type_creation': True,
+ 'enable_public_share_group_type_creation': True,
'enable_public_shares': True,
'enabled_share_protocols': ['NFS'],
}
diff --git a/manila_ui/api/manila.py b/manila_ui/api/manila.py
index 1b3def35..0d9a533a 100644
--- a/manila_ui/api/manila.py
+++ b/manila_ui/api/manila.py
@@ -19,20 +19,19 @@
# under the License.
from __future__ import absolute_import
-import logging
-
from django.conf import settings
-from manilaclient import client as manila_client
+from horizon import exceptions
+import logging
+from openstack_dashboard.api import base
import six
-from horizon import exceptions
-from horizon.utils.memoized import memoized # noqa
-from openstack_dashboard.api import base
-
+from manilaclient import client as manila_client
LOG = logging.getLogger(__name__)
MANILA_UI_USER_AGENT_REPR = "manila_ui_plugin_for_horizon"
+# NOTE(vponomaryov): update version to 2.34 when manilaclient is released with
+# its support. It will allow to show 'availability zones' for share groups.
MANILA_VERSION = "2.32" # requires manilaclient 1.13.0 or newer
MANILA_SERVICE_TYPE = "sharev2"
@@ -80,16 +79,21 @@ def share_get(request, share_id):
def share_create(request, size, name, description, proto, snapshot_id=None,
metadata=None, share_network=None, share_type=None,
- is_public=None, availability_zone=None):
+ is_public=None, availability_zone=None, share_group_id=None):
return manilaclient(request).shares.create(
proto, size, name=name, description=description,
share_network=share_network, snapshot_id=snapshot_id,
metadata=metadata, share_type=share_type, is_public=is_public,
- availability_zone=availability_zone)
+ availability_zone=availability_zone,
+ share_group_id=share_group_id,
+ )
-def share_delete(request, share_id):
- return manilaclient(request).shares.delete(share_id)
+def share_delete(request, share_id, share_group_id=None):
+ return manilaclient(request).shares.delete(
+ share_id,
+ share_group_id=share_group_id,
+ )
def share_update(request, share_id, name, description, is_public=''):
@@ -481,13 +485,144 @@ def pool_list(request, detailed=False):
return manilaclient(request).pools.list(detailed=detailed)
-@memoized
-def is_replication_enabled():
- manila_config = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {})
- return manila_config.get('enable_replication', True)
+# ####### Share Groups # #######
+
+def share_group_create(request, name, description=None,
+ share_group_type=None,
+ share_types=None,
+ share_network=None,
+ source_share_group_snapshot=None,
+ availability_zone=None):
+ return manilaclient(request).share_groups.create(
+ name=name,
+ description=description,
+ share_group_type=share_group_type,
+ share_types=share_types,
+ share_network=share_network,
+ source_share_group_snapshot=source_share_group_snapshot,
+ availability_zone=availability_zone,
+ )
-@memoized
-def is_migration_enabled():
- manila_config = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {})
- return manila_config.get('enable_migration', True)
+def share_group_get(request, share_group):
+ return manilaclient(request).share_groups.get(share_group)
+
+
+def share_group_update(request, share_group, name, description):
+ return manilaclient(request).share_groups.update(
+ share_group,
+ name=name,
+ description=description,
+ )
+
+
+def share_group_delete(request, share_group, force=False):
+ return manilaclient(request).share_groups.delete(share_group, force=force)
+
+
+def share_group_reset_state(request, share_group, state):
+ return manilaclient(request).share_groups.reset_state(share_group, state)
+
+
+def share_group_list(request, detailed=True, search_opts=None, sort_key=None,
+ sort_dir=None):
+ return manilaclient(request).share_groups.list(
+ detailed=detailed,
+ search_opts=search_opts,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ )
+
+
+# ####### Share Group Snapshots # #######
+
+def share_group_snapshot_create(request, share_group, name, description=None):
+ return manilaclient(request).share_group_snapshots.create(
+ share_group=share_group,
+ name=name,
+ description=description,
+ )
+
+
+def share_group_snapshot_get(request, share_group_snapshot):
+ return manilaclient(request).share_group_snapshots.get(
+ share_group_snapshot)
+
+
+def share_group_snapshot_update(request, share_group_snapshot, name,
+ description):
+ return manilaclient(request).share_group_snapshots.update(
+ share_group_snapshot,
+ name=name,
+ description=description,
+ )
+
+
+def share_group_snapshot_delete(request, share_group_snapshot, force=False):
+ return manilaclient(request).share_group_snapshots.delete(
+ share_group_snapshot, force=force)
+
+
+def share_group_snapshot_reset_state(request, share_group_snapshot, state):
+ return manilaclient(request).share_group_snapshots.reset_state(
+ share_group_snapshot, state)
+
+
+def share_group_snapshot_list(request, detailed=True, search_opts=None,
+ sort_key=None, sort_dir=None):
+ return manilaclient(request).share_group_snapshots.list(
+ detailed=detailed,
+ search_opts=search_opts,
+ sort_key=sort_key,
+ sort_dir=sort_dir,
+ )
+
+
+# ####### Share Group Types # ########
+
+def share_group_type_create(request, name, share_types, is_public=False,
+ group_specs=None):
+ return manilaclient(request).share_group_types.create(
+ name=name, share_types=share_types, is_public=is_public,
+ group_specs=group_specs)
+
+
+def share_group_type_get(request, share_group_type):
+ return manilaclient(request).share_group_types.get(share_group_type)
+
+
+def share_group_type_list(request, show_all=True):
+ return manilaclient(request).share_group_types.list(show_all=show_all)
+
+
+def share_group_type_delete(request, share_group_type):
+ return manilaclient(request).share_group_types.delete(share_group_type)
+
+
+def share_group_type_access_list(request, share_group_type):
+ return manilaclient(request).share_group_type_access.list(share_group_type)
+
+
+def share_group_type_access_add(request, share_group_type, project):
+ return manilaclient(request).share_group_type_access.add_project_access(
+ share_group_type, project)
+
+
+def share_group_type_access_remove(request, share_group_type, project):
+ return manilaclient(request).share_group_type_access.remove_project_access(
+ share_group_type, project)
+
+
+def share_group_type_set_specs(request, share_group_type, group_specs):
+ return manilaclient(request).share_group_types.get(
+ share_group_type).set_keys(group_specs)
+
+
+def share_group_type_unset_specs(request, share_group_type, keys):
+ return manilaclient(request).share_group_types.get(
+ share_group_type).unset_keys(keys)
+
+
+def share_group_type_get_specs(request, share_group_type):
+ return manilaclient(request).share_group_types.get(
+ share_group_type).get_keys()
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/__init__.py b/manila_ui/dashboards/admin/share_group_snapshots/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/forms.py b/manila_ui/dashboards/admin/share_group_snapshots/forms.py
new file mode 100644
index 00000000..a39dab83
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/forms.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+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
+
+
+class ResetShareGroupSnapshotStatusForm(forms.SelfHandlingForm):
+ status = forms.ChoiceField(
+ label=_("Status"),
+ required=True,
+ choices=(
+ ('available', 'available'),
+ ('error', 'error'),
+ )
+ )
+
+ def handle(self, request, data):
+ s_id = self.initial['share_group_snapshot_id']
+ try:
+ manila.share_group_snapshot_reset_state(
+ request, s_id, data["status"])
+ message = _(
+ "Reseting share group snapshot ('%(id)s') status "
+ "from '%(from)s' to '%(to)s'.") % {
+ "id": self.initial['share_group_snapshot_name'] or s_id,
+ "from": self.initial['share_group_snapshot_status'],
+ "to": data["status"]}
+ messages.success(request, message)
+ return True
+ except Exception:
+ redirect = reverse("horizon:admin:share_group_snapshots:index")
+ exceptions.handle(
+ request,
+ _("Unable to reset status of share group snapshot "
+ "'%s'.") % s_id,
+ redirect=redirect)
+ return False
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/panel.py b/manila_ui/dashboards/admin/share_group_snapshots/panel.py
new file mode 100644
index 00000000..5455b5f0
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/panel.py
@@ -0,0 +1,29 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+import horizon
+from openstack_dashboard.dashboards.admin import dashboard
+
+
+class ShareGroupSnapshots(horizon.Panel):
+ name = _("Share Group Snapshots")
+ slug = 'share_group_snapshots'
+ permissions = (
+ 'openstack.services.share',
+ )
+
+
+dashboard.Admin.register(ShareGroupSnapshots)
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/tables.py b/manila_ui/dashboards/admin/share_group_snapshots/tables.py
new file mode 100644
index 00000000..8e630513
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/tables.py
@@ -0,0 +1,142 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from 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 horizon.utils import filters
+
+from manila_ui.api import manila
+
+
+class ShareGroupSnapshotShareGroupNameColumn(tables.Column):
+ def get_link_url(self, snapshot):
+ return reverse(self.link, args=(snapshot.share_group_id,))
+
+
+class DeleteShareGroupSnapshot(tables.DeleteAction):
+
+ @staticmethod
+ def action_present(count):
+ return ungettext_lazy(
+ u"Delete Share Group Snapshot",
+ u"Delete Share Group Snapshots",
+ count
+ )
+
+ @staticmethod
+ def action_past(count):
+ return ungettext_lazy(
+ u"Deleted Share Group Snapshot",
+ u"Deleted Share Group Snapshots",
+ count
+ )
+
+ 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_group_snapshot_delete(request, obj_id)
+ except Exception:
+ msg = _('Unable to delete share group snapshot "%s". '
+ 'One or more share groups depend on it.')
+ exceptions.check_message(["snapshots", "dependent"], msg % name)
+ raise
+
+ def allowed(self, request, snapshot=None):
+ if snapshot:
+ return snapshot.status.lower() in ('available', 'error')
+ return True
+
+
+class ResetShareGroupSnapshotStatus(tables.LinkAction):
+ name = "reset_share_group_snapshot_status"
+ verbose_name = _("Reset status")
+ url = "horizon:admin:share_group_snapshots:reset_status"
+ classes = ("ajax-modal", "btn-create")
+
+ def allowed(self, request, share_group=None):
+ return True
+
+
+class UpdateShareGroupSnapshotRow(tables.Row):
+ ajax = True
+
+ def get_data(self, request, share_group_snapshot_id):
+ snapshot = manila.share_group_snapshot_get(
+ request, share_group_snapshot_id)
+ if not snapshot.name:
+ snapshot.name = share_group_snapshot_id
+ return snapshot
+
+
+class ShareGroupSnapshotsTable(tables.DataTable):
+ STATUS_CHOICES = (
+ ("available", True),
+ ("creating", None),
+ ("error", False),
+ )
+ STATUS_DISPLAY_CHOICES = (
+ ("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_group_snapshots:detail")
+ description = tables.Column(
+ "description",
+ verbose_name=_("Description"),
+ truncate=40)
+ created_at = tables.Column(
+ "created_at",
+ verbose_name=_("Created at"),
+ filters=(
+ filters.parse_isotime,
+ ))
+ status = tables.Column(
+ "status",
+ filters=(title,),
+ verbose_name=_("Status"),
+ status=True,
+ status_choices=STATUS_CHOICES,
+ display_choices=STATUS_DISPLAY_CHOICES)
+ source = ShareGroupSnapshotShareGroupNameColumn(
+ "share_group",
+ verbose_name=_("Source"),
+ link="horizon:admin:share_groups:detail")
+
+ def get_object_display(self, obj):
+ return obj.name
+
+ class Meta(object):
+ name = "share_group_snapshots"
+ verbose_name = _("Share Group Snapshots")
+ status_columns = ["status"]
+ row_class = UpdateShareGroupSnapshotRow
+ table_actions = (
+ tables.NameFilterAction,
+ DeleteShareGroupSnapshot,
+ )
+ row_actions = (
+ ResetShareGroupSnapshotStatus,
+ DeleteShareGroupSnapshot,
+ )
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/tabs.py b/manila_ui/dashboards/admin/share_group_snapshots/tabs.py
new file mode 100644
index 00000000..428de6d4
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/tabs.py
@@ -0,0 +1,34 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+from horizon import tabs
+
+
+class ShareGroupSnapshotOverviewTab(tabs.Tab):
+ name = _("Share Group Snapshot Overview")
+ slug = "share_group_snapshot_overview_tab"
+ template_name = "admin/share_group_snapshots/_detail.html"
+
+ def get_context_data(self, request):
+ return {"share_group_snapshot": self.tab_group.kwargs[
+ "share_group_snapshot"]}
+
+
+class ShareGroupSnapshotDetailTabs(tabs.TabGroup):
+ slug = "share_group_snapshot_details"
+ tabs = (
+ ShareGroupSnapshotOverviewTab,
+ )
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/_detail.html b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/_detail.html
new file mode 100644
index 00000000..a3d82982
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/_detail.html
@@ -0,0 +1,23 @@
+{% load i18n sizeformat parse_date %}
+
+
{% trans "Share Group Snapshot Overview" %}
+
+
+
+ {% trans "Name" %}
+ {{ share_group_snapshot.name }}
+ {% trans "ID" %}
+ {{ share_group_snapshot.id }}
+ {% url 'horizon:admin:share_groups:detail' share_group_snapshot.share_group_id as sg_url %}
+ {% trans "Source Share Group" %}
+ {{ share_group_snapshot.sg_name_or_id }}
+ {% if share_group_snapshot.description %}
+ {% trans "Description" %}
+ {{ share_group_snapshot.description }}
+ {% endif %}
+ {% trans "Status" %}
+ {{ share_group_snapshot.status|capfirst }}
+ {% trans "Created" %}
+ {{ share_group_snapshot.created_at|parse_date }}
+
+
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/_reset_status.html b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/_reset_status.html
new file mode 100644
index 00000000..d7bfbcb6
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/_reset_status.html
@@ -0,0 +1,5 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-body-right %}
+ "Reset status of '{{ share_group_snapshot_name }}' share group snapshot."
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/detail.html b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/detail.html
new file mode 100644
index 00000000..577d367c
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/detail.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Group Snapshot Details" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ tab_group.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/index.html b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/index.html
new file mode 100644
index 00000000..840225b0
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Group Snapshots" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ share_group_snapshots_table.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/reset_status.html b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/reset_status.html
new file mode 100644
index 00000000..b7e8f850
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/templates/share_group_snapshots/reset_status.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Reset Share Group Snapshot Status" %}{% endblock %}
+
+{% block main %}
+ {% include 'admin/share_group_snapshots/_reset_status.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/urls.py b/manila_ui/dashboards/admin/share_group_snapshots/urls.py
new file mode 100644
index 00000000..6eadb9f3
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/urls.py
@@ -0,0 +1,36 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf import urls
+
+from manila_ui.dashboards.admin.share_group_snapshots import views
+from manila_ui import features
+
+
+if features.is_share_groups_enabled():
+ urlpatterns = [
+ urls.url(
+ r'^$',
+ views.ShareGroupSnapshotsView.as_view(),
+ name='index'),
+ urls.url(
+ r'^(?P[^/]+)/$',
+ views.ShareGroupSnapshotDetailView.as_view(),
+ name='detail'),
+ urls.url(
+ r'^(?P[^/]+)/reset_status$',
+ views.ResetShareGroupSnapshotStatusView.as_view(),
+ name='reset_status'),
+ ]
diff --git a/manila_ui/dashboards/admin/share_group_snapshots/views.py b/manila_ui/dashboards/admin/share_group_snapshots/views.py
new file mode 100644
index 00000000..11132e87
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_snapshots/views.py
@@ -0,0 +1,137 @@
+# Copyright 2017 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.
+
+"""
+Admin views for managing share group snapshots.
+"""
+
+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 import tabs
+from horizon.utils import memoized
+
+from manila_ui.api import manila
+import manila_ui.dashboards.admin.share_group_snapshots.forms as sgs_forms
+import manila_ui.dashboards.admin.share_group_snapshots.tables as sgs_tables
+import manila_ui.dashboards.admin.share_group_snapshots.tabs as sgs_tabs
+
+
+class ShareGroupSnapshotsView(tables.MultiTableView):
+ table_classes = (
+ sgs_tables.ShareGroupSnapshotsTable,
+ )
+ template_name = "admin/share_group_snapshots/index.html"
+ page_title = _("Share Group Snapshots")
+
+ @memoized.memoized_method
+ def get_share_group_snapshots_data(self):
+ share_group_snapshots = []
+ try:
+ share_group_snapshots = manila.share_group_snapshot_list(
+ self.request, search_opts={'all_tenants': True})
+ share_groups = manila.share_group_list(self.request)
+ sg_names = dict([(sg.id, sg.name or sg.id) for sg in share_groups])
+ for snapshot in share_group_snapshots:
+ snapshot.share_group = sg_names.get(snapshot.share_group_id)
+ except Exception:
+ msg = _("Unable to retrieve share group snapshot list.")
+ exceptions.handle(self.request, msg)
+ return share_group_snapshots
+
+
+class ShareGroupSnapshotDetailView(tabs.TabView):
+ tab_group_class = sgs_tabs.ShareGroupSnapshotDetailTabs
+ template_name = "admin/share_group_snapshots/detail.html"
+ redirect_url = reverse_lazy("horizon:admin:share_group_snapshots:index")
+
+ def get_context_data(self, **kwargs):
+ context = super(ShareGroupSnapshotDetailView, self).get_context_data(
+ **kwargs)
+ snapshot = self.get_data()
+ snapshot_display_name = snapshot.name or snapshot.id
+ context["snapshot"] = snapshot
+ context["snapshot_display_name"] = snapshot_display_name
+ context["page_title"] = _(
+ "Share Group Snapshot Details: %(sgs_display_name)s") % (
+ {'sgs_display_name': snapshot_display_name})
+ return context
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ share_group_snapshot = manila.share_group_snapshot_get(
+ self.request, self.kwargs['share_group_snapshot_id'])
+ sg = manila.share_group_get(
+ self.request, share_group_snapshot.share_group_id)
+ share_group_snapshot.sg_name_or_id = sg.name or sg.id
+ except Exception:
+ exceptions.handle(
+ self.request,
+ _('Unable to retrieve share group snapshot details.'),
+ redirect=self.redirect_url)
+ return share_group_snapshot
+
+ def get_tabs(self, request, *args, **kwargs):
+ return self.tab_group_class(
+ request, share_group_snapshot=self.get_data(), **kwargs)
+
+
+class ResetShareGroupSnapshotStatusView(forms.ModalFormView):
+ form_class = sgs_forms.ResetShareGroupSnapshotStatusForm
+ form_id = "reset_share_group_snapshot_status"
+ template_name = 'admin/share_group_snapshots/reset_status.html'
+ modal_header = _("Reset Status")
+ modal_id = "reset_share_group_snapshot_status_modal"
+ submit_label = _("Reset status")
+ submit_url = "horizon:admin:share_group_snapshots:reset_status"
+ success_url = reverse_lazy("horizon:admin:share_group_snapshots:index")
+ page_title = _("Reset Share Group Snapshot Status")
+
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ s_id = self.kwargs["share_group_snapshot_id"]
+ try:
+ self._object = manila.share_group_snapshot_get(
+ self.request, s_id)
+ except Exception:
+ msg = _("Unable to retrieve share group snapshot '%s'.") % s_id
+ url = reverse('horizon:admin:share_group_snapshots:index')
+ exceptions.handle(self.request, msg, redirect=url)
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(self.__class__, self).get_context_data(**kwargs)
+ sgs = self.get_object()
+ context['share_group_snapshot_id'] = self.kwargs[
+ 'share_group_snapshot_id']
+ context['share_group_snapshot_name'] = sgs.name or sgs.id
+ context['share_group_snapshot_status'] = sgs.status
+ context['share_group_id'] = sgs.share_group_id
+ context['submit_url'] = reverse(
+ self.submit_url, args=(context['share_group_snapshot_id'], ))
+ return context
+
+ def get_initial(self):
+ sgs = self.get_object()
+ return {
+ "share_group_snapshot_id": self.kwargs["share_group_snapshot_id"],
+ "share_group_snapshot_name": sgs.name or sgs.id,
+ "share_group_snapshot_status": sgs.status,
+ "share_group_id": sgs.share_group_id,
+ }
diff --git a/manila_ui/dashboards/admin/share_group_types/__init__.py b/manila_ui/dashboards/admin/share_group_types/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/dashboards/admin/share_group_types/forms.py b/manila_ui/dashboards/admin/share_group_types/forms.py
new file mode 100644
index 00000000..96ff9ff7
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/forms.py
@@ -0,0 +1,141 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from 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
+
+
+SGT_GROUP_SPECS_FORM_ATTRS = {
+ "rows": 5,
+ "cols": 40,
+ "style": "height: 135px; width: 100%;", # in case 'rows' not picked up
+}
+
+
+class CreateShareGroupTypeForm(forms.SelfHandlingForm):
+ name = forms.CharField(max_length="255", label=_("Name"), required=True)
+ group_specs = forms.CharField(
+ required=False, label=_("Group specs"),
+ widget=forms.widgets.Textarea(attrs=SGT_GROUP_SPECS_FORM_ATTRS))
+ share_types = forms.MultipleChoiceField(
+ label=_("Share Types"),
+ required=True,
+ widget=forms.SelectMultiple(attrs={
+ "style": "height: 155px;",
+ }),
+ error_messages={
+ 'required': _("At least one share type must be specified.")
+ })
+ is_public = forms.BooleanField(
+ label=_("Public"), required=False, initial=True,
+ help_text=("Defines whether this share group type is available for "
+ "all or not. List of allowed tenants should be set "
+ "separately."))
+
+ def __init__(self, request, *args, **kwargs):
+ super(self.__class__, self).__init__(request, *args, **kwargs)
+ manila_features = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {})
+ self.enable_public_share_group_type_creation = manila_features.get(
+ 'enable_public_share_group_type_creation', True)
+ if not self.enable_public_share_group_type_creation:
+ self.fields.pop('is_public')
+ share_type_choices = manila.share_type_list(request)
+ self.fields['share_types'].choices = [
+ (choice.id, choice.name) for choice in share_type_choices]
+
+ def clean(self):
+ cleaned_data = super(CreateShareGroupTypeForm, self).clean()
+ return cleaned_data
+
+ def handle(self, request, data):
+ try:
+ set_dict, unset_list = utils.parse_str_meta(data['group_specs'])
+ if unset_list:
+ msg = _("Expected only pairs of key=value.")
+ raise ValidationError(message=msg)
+
+ is_public = (
+ self.enable_public_share_group_type_creation and
+ data["is_public"])
+ share_group_type = manila.share_group_type_create(
+ request, data["name"], share_types=data['share_types'],
+ is_public=is_public)
+ if set_dict:
+ manila.share_group_type_set_specs(
+ request, share_group_type.id, set_dict)
+
+ msg = _("Successfully created share group type: "
+ "%s") % share_group_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 group type.'))
+ return False
+
+
+class UpdateShareGroupTypeForm(forms.SelfHandlingForm):
+
+ def __init__(self, *args, **kwargs):
+ super(self.__class__, self).__init__(*args, **kwargs)
+ # NOTE(vponomaryov): parse existing group specs
+ # to str view for textarea html element
+ es_str = ""
+ for k, v in self.initial["group_specs"].items():
+ es_str += "%s=%s\r\n" % (k, v)
+ self.initial["group_specs"] = es_str
+
+ group_specs = forms.CharField(
+ required=False, label=_("Group specs"),
+ widget=forms.widgets.Textarea(attrs=SGT_GROUP_SPECS_FORM_ATTRS))
+
+ def handle(self, request, data):
+ try:
+ set_dict, unset_list = utils.parse_str_meta(data['group_specs'])
+ if set_dict:
+ manila.share_group_type_set_specs(
+ request, self.initial["id"], set_dict)
+ if unset_list:
+ get = manila.share_group_type_get_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_group_type_unset_specs(
+ request, self.initial["id"], to_unset)
+ msg = _(
+ "Successfully updated group specs for share group 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 group_specs for share group type.")
+ exceptions.handle(request, msg)
+ return False
diff --git a/manila_ui/dashboards/admin/share_group_types/panel.py b/manila_ui/dashboards/admin/share_group_types/panel.py
new file mode 100644
index 00000000..2c4957d7
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/panel.py
@@ -0,0 +1,29 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+import horizon
+from openstack_dashboard.dashboards.admin import dashboard
+
+
+class ShareGroupTypes(horizon.Panel):
+ name = _("Share Group Types")
+ slug = 'share_group_types'
+ permissions = (
+ 'openstack.services.share',
+ )
+
+
+dashboard.Admin.register(ShareGroupTypes)
diff --git a/manila_ui/dashboards/admin/share_group_types/tables.py b/manila_ui/dashboards/admin/share_group_types/tables.py
new file mode 100644
index 00000000..d608f335
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/tables.py
@@ -0,0 +1,111 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from 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 CreateShareGroupType(tables.LinkAction):
+ name = "create"
+ verbose_name = _("Create Share Group Type")
+ url = "horizon:admin:share_group_types:create"
+ classes = ("ajax-modal", "btn-create")
+ icon = "plus"
+
+
+class DeleteShareGroupType(tables.DeleteAction):
+ @staticmethod
+ def action_present(count):
+ return ungettext_lazy(
+ u"Delete Share Group Type",
+ u"Delete Share Group Types",
+ count
+ )
+
+ @staticmethod
+ def action_past(count):
+ return ungettext_lazy(
+ u"Deleted Share Group Type",
+ u"Deleted Share Group Types",
+ count
+ )
+
+ def delete(self, request, obj_id):
+ manila.share_group_type_delete(request, obj_id)
+
+
+class ManageShareGroupTypeAccess(tables.LinkAction):
+ name = "manage"
+ verbose_name = _("Manage Share Group Type Access")
+ url = "horizon:admin:share_group_types:manage_access"
+ classes = ("ajax-modal", "btn-create")
+
+ def allowed(self, request, obj_id):
+ sgt = manila.share_group_type_get(request, obj_id)
+ # Enable it only for private share group types
+ return not sgt.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 UpdateShareGroupType(tables.LinkAction):
+ name = "update share group type"
+ verbose_name = _("Update Share group Type")
+ url = "horizon:admin:share_group_types:update"
+ 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 ShareGroupTypesTable(tables.DataTable):
+ name = tables.WrappingColumn("name", verbose_name=_("Name"))
+ group_specs = tables.Column("group_specs", verbose_name=_("Group specs"))
+ share_types = tables.Column("share_types", verbose_name=_("Share types"))
+ visibility = tables.Column(
+ "is_public", verbose_name=_("Visibility"),
+ filters=(lambda d: 'public' if d is True else 'private', ),
+ )
+
+ def get_object_display(self, share_group_type):
+ return share_group_type.name
+
+ def get_object_id(self, share_group_type):
+ return six.text_type(share_group_type.id)
+
+ class Meta(object):
+ name = "share_group_types"
+ verbose_name = _("Share Group Types")
+ table_actions = (
+ tables.NameFilterAction,
+ CreateShareGroupType,
+ DeleteShareGroupType,
+ )
+ row_actions = (
+ UpdateShareGroupType,
+ ManageShareGroupTypeAccess,
+ DeleteShareGroupType,
+ )
diff --git a/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_create.html b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_create.html
new file mode 100644
index 00000000..1a793859
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_create.html
@@ -0,0 +1,11 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-body-right %}
+ {% trans "Description" %}:
+ {% trans "The share group type defines the characteristics of a share group backend." %}
+ Extra specs field:
+ {% trans "One line - one action. Empty strings will be ignored." %}
+ {% trans "To add group-specs use:" %}
+
key=value
+
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_manage_access.html b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_manage_access.html
new file mode 100644
index 00000000..8d2734fa
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_manage_access.html
@@ -0,0 +1,59 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+
+{% block form_id %}{% endblock %}
+{% block form_action %}{% url 'horizon:admin:share_group_types:manage_access' share_group_type.id %}{% endblock %}
+
+{% block modal_id %}manage_share_group_type_access_modal{% endblock %}
+{% block modal-header %}{% trans "Manage Share Group Type Access" %}{% endblock %}
+
+
+{% block modal-body %}
+
+
+ {% include "horizon/common/_form_fields.html" %}
+
+
+
+
{% trans "Description" %}:
+
{% blocktrans %}
+ Placeholder for description of share group type access managing form.
+ {% endblocktrans %}
+
+
+
+
+
+ {% trans "Selected Projects" %}
+
+ {% trans "Available projects" %}
+
+
+
+
+
+
+
+
+
+
+
+ {% include "horizon/common/_form_fields.html" %}
+
+
+
+ {{ step.get_help_text }}
+
+
+
+
+
+
+{% endblock %}
+
+{% block modal-footer %}
+
+ {% trans "Cancel" %}
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_update.html b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_update.html
new file mode 100644
index 00000000..08326d4f
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/_update.html
@@ -0,0 +1,14 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-body-right %}
+ {% trans "Description" %}:
+
+ {% trans "Here can be modified group-specs for share group type." %}
+ {% trans "One line - one action. Empty strings will be ignored." %}
+ {% trans "To add group-specs use:" %}
+
key=value
+ {% trans "To unset group-specs use:" %}
+ key
+ {% trans "All pairs that are in field for left are set for this share group type." %}
+
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/create.html b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/create.html
new file mode 100644
index 00000000..0ca18056
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/create.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Create Group Share Type" %}{% endblock %}
+
+{% block main %}
+ {% include 'admin/share_group_types/_create.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/index.html b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/index.html
new file mode 100644
index 00000000..f5d6d877
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Group Types" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ share_group_types_table.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/manage_access.html b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/manage_access.html
new file mode 100644
index 00000000..7c98472f
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/manage_access.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Manage Share Group Type Access" %}{% endblock %}
+
+{% block main %}
+ {% include 'admin/share_group_types/_manage_access.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/update.html b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/update.html
new file mode 100644
index 00000000..a8ea56b1
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/templates/share_group_types/update.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Update Share Group Type" %}{% endblock %}
+
+{% block main %}
+ {% include 'admin/share_group_types/_update.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_group_types/urls.py b/manila_ui/dashboards/admin/share_group_types/urls.py
new file mode 100644
index 00000000..563a2c6b
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/urls.py
@@ -0,0 +1,40 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf import urls
+
+from manila_ui.dashboards.admin.share_group_types import views
+from manila_ui import features
+
+
+if features.is_share_groups_enabled():
+ urlpatterns = [
+ urls.url(
+ r'^$',
+ views.ShareGroupTypesView.as_view(),
+ name='index'),
+ urls.url(
+ r'^create$',
+ views.CreateShareGroupTypeView.as_view(),
+ name='create'),
+ urls.url(
+ r'^(?P[^/]+)/update$',
+ views.UpdateShareGroupTypeView.as_view(),
+ name='update'),
+ urls.url(
+ r'^(?P[^/]+)/manage_access$',
+ views.ManageShareGroupTypeAccessView.as_view(),
+ name='manage_access'),
+ ]
diff --git a/manila_ui/dashboards/admin/share_group_types/views.py b/manila_ui/dashboards/admin/share_group_types/views.py
new file mode 100644
index 00000000..d12ac2f8
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/views.py
@@ -0,0 +1,130 @@
+# Copyright 2017 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.
+
+"""
+Admin views for managing share group 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_group_types import forms as sgt_forms
+from manila_ui.dashboards.admin.share_group_types import tables as sgt_tables
+import manila_ui.dashboards.admin.share_group_types.workflows as sgt_workflows
+from manila_ui.dashboards import utils as common_utils
+
+
+class ShareGroupTypesView(tables.MultiTableView):
+ table_classes = (
+ sgt_tables.ShareGroupTypesTable,
+ )
+ template_name = "admin/share_group_types/index.html"
+ page_title = _("Share Group Types")
+
+ @memoized.memoized_method
+ def get_share_group_types_data(self):
+ try:
+ share_group_types = manila.share_group_type_list(self.request)
+ except Exception:
+ exceptions.handle(
+ self.request, _('Unable to retrieve share group types.'))
+ return []
+
+ share_types = manila.share_type_list(self.request)
+ st_mapping = {}
+ for st in share_types:
+ st_mapping[st.id] = st.name
+ for sgt in share_group_types:
+ sgt.group_specs = common_utils.metadata_to_str(
+ sgt.group_specs, 8, 45)
+ sgt.share_types = ", ".join(
+ [st_mapping[st] for st in sgt.share_types])
+ return share_group_types
+
+
+class CreateShareGroupTypeView(forms.ModalFormView):
+ form_class = sgt_forms.CreateShareGroupTypeForm
+ form_id = "create_share_group_type"
+ template_name = 'admin/share_group_types/create.html'
+ modal_header = _("Create Share Group Type")
+ modal_id = "create_share_group_type_modal"
+ submit_label = _("Create")
+ submit_url = reverse_lazy("horizon:admin:share_group_types:create")
+ success_url = reverse_lazy("horizon:admin:share_group_types:index")
+ page_title = _("Create Share Group Type")
+
+
+class ManageShareGroupTypeAccessView(workflows.WorkflowView):
+ workflow_class = sgt_workflows.ManageShareGroupTypeAccessWorkflow
+ template_name = "admin/share_group_types/manage_access.html"
+ success_url = 'horizon:admin:share_group_types:index'
+ page_title = _("Manage Share Group Type Access")
+
+ def get_initial(self):
+ return {'id': self.kwargs["share_group_type_id"]}
+
+ def get_context_data(self, **kwargs):
+ context = super(ManageShareGroupTypeAccessView, self).get_context_data(
+ **kwargs)
+ context['id'] = self.kwargs['share_group_type_id']
+ return context
+
+
+class UpdateShareGroupTypeView(forms.ModalFormView):
+ form_class = sgt_forms.UpdateShareGroupTypeForm
+ form_id = "update_share_group_type"
+ template_name = "admin/share_group_types/update.html"
+ modal_header = _("Update Share Group Type")
+ modal_id = "update_share_group_type_modal"
+ submit_label = _("Update")
+ submit_url = "horizon:admin:share_group_types:update"
+ success_url = reverse_lazy("horizon:admin:share_group_types:index")
+ page_title = _("Update Share Group Type")
+
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ sgt_id = self.kwargs["share_group_type_id"]
+ try:
+ self._object = manila.share_group_type_get(
+ self.request, sgt_id)
+ except Exception:
+ msg = _("Unable to retrieve share_gruop_type.")
+ url = reverse("horizon:admin:share_group_types:index")
+ exceptions.handle(self.request, msg, redirect=url)
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(UpdateShareGroupTypeView, 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_group_type = self.get_object()
+ return {
+ "id": self.kwargs["share_group_type_id"],
+ "name": share_group_type.name,
+ "group_specs": share_group_type.group_specs,
+ }
diff --git a/manila_ui/dashboards/admin/share_group_types/workflows.py b/manila_ui/dashboards/admin/share_group_types/workflows.py
new file mode 100644
index 00000000..68bd1fa6
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_group_types/workflows.py
@@ -0,0 +1,115 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+from horizon import exceptions
+from horizon import forms
+from horizon import workflows
+from openstack_dashboard.api import keystone
+
+from manila_ui.api import manila
+
+
+class AddProjectAction(workflows.MembershipAction):
+
+ def __init__(self, request, *args, **kwargs):
+ super(AddProjectAction, self).__init__(request, *args, **kwargs)
+ default_role_field_name = self.get_default_role_field_name()
+ self.fields[default_role_field_name] = forms.CharField(required=False)
+ self.fields[default_role_field_name].initial = 'member'
+
+ field_name = self.get_member_field_name('member')
+ self.fields[field_name] = forms.MultipleChoiceField(required=False)
+ share_group_type_id = self.initial['id']
+
+ # Get list of existing projects
+ try:
+ projects, __ = keystone.tenant_list(request)
+ except Exception:
+ err_msg = _('Unable to get list of projects.')
+ exceptions.handle(request, err_msg)
+
+ # Get list of projects with access to this Share Group Type
+ try:
+ share_group_type = manila.share_group_type_get(
+ request, share_group_type_id)
+ self.share_group_type_name = share_group_type.name
+ projects_initial = manila.share_group_type_access_list(
+ request, share_group_type)
+ except Exception:
+ err_msg = _(
+ 'Unable to get information about share group type access.')
+ exceptions.handle(request, err_msg)
+
+ self.fields[field_name].choices = [
+ (project.id, project.name or project.id) for project in projects]
+ self.fields[field_name].initial = [
+ pr.project_id for pr in projects_initial]
+ self.projects_initial = set(self.fields[field_name].initial)
+
+ class Meta(object):
+ name = _("Projects with access to share group type")
+ slug = "update_members"
+
+ def handle(self, request, context):
+ context.update({
+ 'name': self.share_group_type_name,
+ 'projects_add': self.projects_allow - self.projects_initial,
+ 'projects_remove': self.projects_initial - self.projects_allow,
+ })
+ return context
+
+ def clean(self):
+ cleaned_data = super(AddProjectAction, self).clean()
+ self.projects_allow = set(
+ cleaned_data[self.get_member_field_name('member')])
+ return cleaned_data
+
+
+class AddProjectStep(workflows.UpdateMembersStep):
+ action_class = AddProjectAction
+ available_list_title = _("Available projects")
+ help_text = _("Allow project access to share group type.")
+ members_list_title = _("Selected projects")
+ no_available_text = _("No projects found.")
+ no_members_text = _("No projects selected.")
+ depends_on = ("id", )
+ show_roles = False
+
+
+class ManageShareGroupTypeAccessWorkflow(workflows.Workflow):
+ slug = "manage_share_group_type_access"
+ name = _("Manage Share Group Type Access")
+ finalize_button_name = _("Manage Share Group Type Access")
+ success_message = _('Updated access for share group type "%s".')
+ failure_message = _('Unable to update access for share group type "%s".')
+ success_url = 'horizon:admin:share_group_types:index'
+ default_steps = (AddProjectStep, )
+
+ def format_status_message(self, message):
+ return message % self.context['name']
+
+ def handle(self, request, context):
+ try:
+ for project in self.context['projects_remove']:
+ manila.share_group_type_access_remove(
+ request, self.context['id'], project)
+ for project in self.context['projects_add']:
+ manila.share_group_type_access_add(
+ request, self.context['id'], project)
+ return True
+ except Exception:
+ exceptions.handle(request, _('Unable to update share group type.'))
+ return False
diff --git a/manila_ui/dashboards/admin/share_groups/__init__.py b/manila_ui/dashboards/admin/share_groups/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/dashboards/admin/share_groups/forms.py b/manila_ui/dashboards/admin/share_groups/forms.py
new file mode 100644
index 00000000..78b2b6b9
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/forms.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+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
+
+
+class ResetShareGroupStatusForm(forms.SelfHandlingForm):
+ status = forms.ChoiceField(
+ label=_("Status"),
+ required=True,
+ choices=(
+ ('available', 'available'),
+ ('error', 'error'),
+ )
+ )
+
+ def handle(self, request, data):
+ sg_id = self.initial['share_group_id']
+ try:
+ manila.share_group_reset_state(request, sg_id, data["status"])
+ message = _(
+ "Reseting share group ('%(id)s') status from '%(from)s' "
+ "to '%(to)s'.") % {
+ "id": (self.initial['share_group_name'] or
+ self.initial['share_group_id']),
+ "from": self.initial['share_group_status'],
+ "to": data["status"]}
+ messages.success(request, message)
+ return True
+ except Exception:
+ redirect = reverse("horizon:admin:share_groups:index")
+ exceptions.handle(
+ request,
+ _("Unable to reset status of share group '%s'.") % sg_id,
+ redirect=redirect)
+ return False
diff --git a/manila_ui/dashboards/admin/share_groups/panel.py b/manila_ui/dashboards/admin/share_groups/panel.py
new file mode 100644
index 00000000..cb515b2b
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/panel.py
@@ -0,0 +1,29 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+import horizon
+from openstack_dashboard.dashboards.admin import dashboard
+
+
+class ShareGroups(horizon.Panel):
+ name = _("Share Groups")
+ slug = 'share_groups'
+ permissions = (
+ 'openstack.services.share',
+ )
+
+
+dashboard.Admin.register(ShareGroups)
diff --git a/manila_ui/dashboards/admin/share_groups/tables.py b/manila_ui/dashboards/admin/share_groups/tables.py
new file mode 100644
index 00000000..9374bf71
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/tables.py
@@ -0,0 +1,127 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+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 DeleteShareGroup(tables.DeleteAction):
+
+ @staticmethod
+ def action_present(count):
+ return ungettext_lazy(
+ u"Delete Share Group",
+ u"Delete Share Groups",
+ count
+ )
+
+ @staticmethod
+ def action_past(count):
+ return ungettext_lazy(
+ u"Deleted Share Group",
+ u"Deleted Share Groups",
+ count
+ )
+
+ def delete(self, request, obj_id):
+ manila.share_group_delete(request, obj_id)
+
+
+class ResetShareGroupStatus(tables.LinkAction):
+ name = "reset_share_group_status"
+ verbose_name = _("Reset status")
+ url = "horizon:admin:share_groups:reset_status"
+ classes = ("ajax-modal", "btn-create")
+
+ def allowed(self, request, share_group=None):
+ return True
+
+
+class ShareGroupsTable(tables.DataTable):
+ def get_share_network_link(share_group):
+ if getattr(share_group, 'share_network_id', None):
+ return reverse("horizon:admin:share_networks:share_network_detail",
+ args=(share_group.share_network_id,))
+ else:
+ return None
+
+ def get_share_server_link(share_group):
+ if getattr(share_group, 'share_server_id', None):
+ return reverse("horizon:admin:share_servers:share_server_detail",
+ args=(share_group.share_server_id,))
+ else:
+ return None
+
+ 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"),
+ )
+ name = tables.Column(
+ "name",
+ verbose_name=_("Name"),
+ link="horizon:admin:share_groups: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"))
+ share_network_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"),
+ link=get_share_server_link)
+
+ def get_object_display(self, share_group):
+ return six.text_type(share_group.id)
+
+ def get_object_id(self, share_group):
+ return six.text_type(share_group.id)
+
+ class Meta(object):
+ name = "share_groups"
+ verbose_name = _("Share Groups")
+ status_columns = ("status", )
+ table_actions = (
+ tables.NameFilterAction,
+ DeleteShareGroup,
+ )
+ row_actions = (
+ ResetShareGroupStatus,
+ DeleteShareGroup,
+ )
diff --git a/manila_ui/dashboards/admin/share_groups/tabs.py b/manila_ui/dashboards/admin/share_groups/tabs.py
new file mode 100644
index 00000000..c584db88
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/tabs.py
@@ -0,0 +1,33 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+from horizon import tabs
+
+
+class ShareGroupOverviewTab(tabs.Tab):
+ name = _("Share Group Overview")
+ slug = "share_group_overview_tab"
+ template_name = "admin/share_groups/_detail.html"
+
+ def get_context_data(self, request):
+ return {"share_group": self.tab_group.kwargs["share_group"]}
+
+
+class ShareGroupDetailTabs(tabs.TabGroup):
+ slug = "share_group_details"
+ tabs = (
+ ShareGroupOverviewTab,
+ )
diff --git a/manila_ui/dashboards/admin/share_groups/templates/share_groups/_detail.html b/manila_ui/dashboards/admin/share_groups/templates/share_groups/_detail.html
new file mode 100644
index 00000000..008dbafe
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/templates/share_groups/_detail.html
@@ -0,0 +1,61 @@
+{% load i18n sizeformat parse_date %}
+
+{% trans "Share Group Overview" %}
+
+
+
+ {% trans "ID" %}
+ {{ share_group.id }}
+ {% trans "Name" %}
+ {{ share_group.name }}
+ {% trans "Description" %}
+ {{ share_group.description }}
+ {% trans "Status" %}
+ {{ share_group.status|capfirst }}
+ {% trans "Created" %}
+ {{ share_group.created_at|parse_date }}
+ {% trans "Host" %}
+ {{ share_group.host}}
+ {% if share_group.source_share_group_snapshot_id %}
+ {% trans "Source share group snapshot" %}
+ {{ share_group.source_share_group_snapshot_id}}
+ {% endif %}
+ {% trans "Share Group Type" %}
+ {{ share_group.share_group_type_id}}
+ {% if share_group.availability_zone %}
+ {% trans "Availability Zone" %}
+ {{ share_group.availability_zone }}
+ {% endif %}
+ {% if share_group.share_network_id %}
+ {% trans "Share network" %}
+ {% url 'horizon:admin:share_networks:share_network_detail' share_group.share_network_id as sn_url%}
+ {{ share_group.share_network_id }}
+ {% endif %}
+ {% if share_group.share_server_id %}
+ {% trans "Share server" %}
+ {% url 'horizon:admin:share_servers:share_server_detail' share_group.share_server_id as share_server_url%}
+ {{ share_group.share_server_id }}
+ {% endif %}
+ {% if share_group.share_types %}
+ {% trans "Share Types" %}
+ {% for st in share_group.share_types %}
+
+
Share Type Name: {{ st.name }}
+ Share Type Visibility: {% if st.is_public %}Public{% else %}Private{% endif %}
+ Network handling enabled: {{ st.dhss }}
+
+ {% endfor %}
+ {% endif %}
+ {% if share_group.members %}
+ {% trans "Shares" %}
+ {% for m in share_group.members %}
+ {% url 'horizon:admin:shares:detail' m.id as share_url%}
+
+
+
+ {% endfor %}
+ {% endif %}
+
+
diff --git a/manila_ui/dashboards/admin/share_groups/templates/share_groups/_reset_status.html b/manila_ui/dashboards/admin/share_groups/templates/share_groups/_reset_status.html
new file mode 100644
index 00000000..ad186146
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/templates/share_groups/_reset_status.html
@@ -0,0 +1,5 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-body-right %}
+ "Reset status of '{{ share_group_name }}' share group."
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_groups/templates/share_groups/detail.html b/manila_ui/dashboards/admin/share_groups/templates/share_groups/detail.html
new file mode 100644
index 00000000..95114d91
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/templates/share_groups/detail.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Group Details" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ tab_group.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_groups/templates/share_groups/index.html b/manila_ui/dashboards/admin/share_groups/templates/share_groups/index.html
new file mode 100644
index 00000000..93a16df5
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/templates/share_groups/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Groups" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ share_groups_table.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_groups/templates/share_groups/reset_status.html b/manila_ui/dashboards/admin/share_groups/templates/share_groups/reset_status.html
new file mode 100644
index 00000000..f3529e7a
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/templates/share_groups/reset_status.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Reset Share Group Status" %}{% endblock %}
+
+{% block main %}
+ {% include 'admin/share_groups/_reset_status.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/admin/share_groups/urls.py b/manila_ui/dashboards/admin/share_groups/urls.py
new file mode 100644
index 00000000..ddc23897
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/urls.py
@@ -0,0 +1,36 @@
+# Copyright 2017 Mirantis Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf import urls
+
+from manila_ui.dashboards.admin.share_groups import views
+from manila_ui import features
+
+
+if features.is_share_groups_enabled():
+ urlpatterns = [
+ urls.url(
+ r'^$',
+ views.ShareGroupsView.as_view(),
+ name='index'),
+ urls.url(
+ r'^(?P[^/]+)/$',
+ views.ShareGroupDetailView.as_view(),
+ name='detail'),
+ urls.url(
+ r'^(?P[^/]+)/reset_status$',
+ views.ResetShareGroupStatusView.as_view(),
+ name='reset_status'),
+ ]
diff --git a/manila_ui/dashboards/admin/share_groups/views.py b/manila_ui/dashboards/admin/share_groups/views.py
new file mode 100644
index 00000000..b0ef705f
--- /dev/null
+++ b/manila_ui/dashboards/admin/share_groups/views.py
@@ -0,0 +1,133 @@
+# Copyright 2017 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.
+
+"""
+Admin views for managing share groups.
+"""
+
+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 import tabs
+from horizon.utils import memoized
+
+from manila_ui.api import manila
+from manila_ui.dashboards.admin.share_groups import forms as sg_forms
+from manila_ui.dashboards.admin.share_groups import tables as sg_tables
+from manila_ui.dashboards.admin.share_groups import tabs as sg_tabs
+
+
+class ShareGroupsView(tables.MultiTableView):
+ table_classes = (
+ sg_tables.ShareGroupsTable,
+ )
+ template_name = "admin/share_groups/index.html"
+ page_title = _("Share Groups")
+
+ @memoized.memoized_method
+ def get_share_groups_data(self):
+ try:
+ share_groups = manila.share_group_list(
+ self.request, detailed=True)
+ except Exception:
+ share_groups = []
+ exceptions.handle(
+ self.request, _("Unable to retrieve share groups."))
+ return share_groups
+
+
+class ShareGroupDetailView(tabs.TabView):
+ tab_group_class = sg_tabs.ShareGroupDetailTabs
+ template_name = 'admin/share_groups/detail.html'
+
+ def get_context_data(self, **kwargs):
+ context = super(self.__class__, self).get_context_data(**kwargs)
+ share_group = self.get_data()
+ context["share_group"] = share_group
+ context["page_title"] = (
+ _("Share Group Details: %s") % share_group.id)
+ return context
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ share_group_id = self.kwargs['share_group_id']
+ share_group = manila.share_group_get(self.request, share_group_id)
+ members = manila.share_list(
+ self.request, search_opts={"share_group_id": share_group_id})
+ share_group.members = members
+ share_types = manila.share_type_list(self.request)
+ share_group.share_types = [
+ {"id": st.id,
+ "name": st.name,
+ "is_public": getattr(st, 'share_type_access:is_public'),
+ "dhss": st.extra_specs.get('driver_handles_share_servers')}
+ for st in share_types if st.id in share_group.share_types
+ ]
+ return share_group
+ except Exception:
+ redirect = reverse('horizon:admin:share_groups:index')
+ exceptions.handle(
+ self.request,
+ _('Unable to retrieve share group details.'),
+ redirect=redirect)
+
+ def get_tabs(self, request, *args, **kwargs):
+ share_group = self.get_data()
+ return self.tab_group_class(request, share_group=share_group, **kwargs)
+
+
+class ResetShareGroupStatusView(forms.ModalFormView):
+ form_class = sg_forms.ResetShareGroupStatusForm
+ form_id = "reset_share_group_status"
+ template_name = 'admin/share_groups/reset_status.html'
+ modal_header = _("Reset Status")
+ modal_id = "reset_share_group_status_modal"
+ submit_label = _("Reset status")
+ submit_url = "horizon:admin:share_groups:reset_status"
+ success_url = reverse_lazy("horizon:admin:share_groups:index")
+ page_title = _("Reset Share Group Status")
+
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ sg_id = self.kwargs["share_group_id"]
+ try:
+ self._object = manila.share_group_get(self.request, sg_id)
+ except Exception:
+ msg = _("Unable to retrieve share group '%s'.") % sg_id
+ url = reverse('horizon:admin:share_groups:index')
+ exceptions.handle(self.request, msg, redirect=url)
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(self.__class__, self).get_context_data(**kwargs)
+ sg = self.get_object()
+ context['share_group_id'] = self.kwargs['share_group_id']
+ context['share_group_name'] = sg.name or sg.id
+ context['share_group_status'] = sg.status
+ context['submit_url'] = reverse(
+ self.submit_url, args=(context['share_group_id'], ))
+ return context
+
+ def get_initial(self):
+ sg = self.get_object()
+ return {
+ "share_group_id": self.kwargs["share_group_id"],
+ "share_group_name": sg.name or sg.id,
+ "share_group_status": sg.status,
+ }
diff --git a/manila_ui/dashboards/admin/shares/tables.py b/manila_ui/dashboards/admin/shares/tables.py
index 32a66570..c40bc32e 100644
--- a/manila_ui/dashboards/admin/shares/tables.py
+++ b/manila_ui/dashboards/admin/shares/tables.py
@@ -14,8 +14,8 @@ from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import tables
-from manila_ui.api import manila
from manila_ui.dashboards.project.shares import tables as shares_tables
+from manila_ui import features
class MigrationStartAction(tables.LinkAction):
@@ -30,7 +30,7 @@ class MigrationStartAction(tables.LinkAction):
if share:
return (share.status.upper() == "AVAILABLE" and
not getattr(share, 'has_snapshot', False) and
- manila.is_migration_enabled())
+ features.is_migration_enabled())
return False
@@ -44,7 +44,7 @@ class MigrationCompleteAction(tables.LinkAction):
def allowed(self, request, share=None):
if (share and share.status.upper() == "MIGRATING" and
- manila.is_migration_enabled()):
+ features.is_migration_enabled()):
return True
return False
@@ -59,7 +59,7 @@ class MigrationCancelAction(tables.LinkAction):
def allowed(self, request, share=None):
if (share and share.status.upper() == "MIGRATING" and
- manila.is_migration_enabled()):
+ features.is_migration_enabled()):
return True
return False
@@ -74,7 +74,7 @@ class MigrationGetProgressAction(tables.LinkAction):
def allowed(self, request, share=None):
if (share and share.status.upper() == "MIGRATING" and
- manila.is_migration_enabled()):
+ features.is_migration_enabled()):
return True
return False
@@ -115,7 +115,7 @@ class ManageReplicas(tables.LinkAction):
def allowed(self, request, share):
share_replication_enabled = share.replication_type is not None
- return manila.is_replication_enabled() and share_replication_enabled
+ return features.is_replication_enabled() and share_replication_enabled
class SharesTable(shares_tables.SharesTable):
@@ -156,7 +156,9 @@ class SharesTable(shares_tables.SharesTable):
UnmanageShareAction,
shares_tables.DeleteShare,
)
- columns = (
+ columns = [
'tenant', 'host', 'name', 'size', 'status', 'visibility',
'share_type', 'protocol', 'share_server',
- )
+ ]
+ if features.is_share_groups_enabled():
+ columns.append('share_group_id')
diff --git a/manila_ui/dashboards/admin/shares/templates/shares/_detail.html b/manila_ui/dashboards/admin/shares/templates/shares/_detail.html
index a31ec270..a318a135 100644
--- a/manila_ui/dashboards/admin/shares/templates/shares/_detail.html
+++ b/manila_ui/dashboards/admin/shares/templates/shares/_detail.html
@@ -57,10 +57,15 @@
{% endif %}
{% if share.share_network_id %}
- {% trans "Share network" %}
+ {% trans "Share Network" %}
{% url 'horizon:admin:share_networks:share_network_detail' share.share_network_id as sn_url%}
{{ share.share_network_id }}
{% endif %}
+ {% if share.share_group_id %}
+ {% trans "Share Group" %}
+ {% url 'horizon:admin:share_groups:detail' share.share_group_id as sg_url%}
+ {{ share.share_group_id }}
+ {% endif %}
{% trans "Mount snapshot support" %}
{{ share.mount_snapshot_support }}
{% trans "Created" %}
diff --git a/manila_ui/dashboards/admin/shares/urls.py b/manila_ui/dashboards/admin/shares/urls.py
index 15f3355f..aedb0977 100644
--- a/manila_ui/dashboards/admin/shares/urls.py
+++ b/manila_ui/dashboards/admin/shares/urls.py
@@ -12,9 +12,9 @@
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
+from manila_ui import features
urlpatterns = [
@@ -36,7 +36,7 @@ urlpatterns = [
name='unmanage'),
]
-if manila.is_replication_enabled():
+if features.is_replication_enabled():
urlpatterns.extend([
urls.url(
r'^(?P[^/]+)/replicas/$',
@@ -60,7 +60,7 @@ if manila.is_replication_enabled():
name='reset_replica_state'),
])
-if manila.is_migration_enabled():
+if features.is_migration_enabled():
urlpatterns.extend([
urls.url(
r'^migration_start/(?P[^/]+)$',
diff --git a/manila_ui/dashboards/project/share_group_snapshots/__init__.py b/manila_ui/dashboards/project/share_group_snapshots/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/dashboards/project/share_group_snapshots/forms.py b/manila_ui/dashboards/project/share_group_snapshots/forms.py
new file mode 100644
index 00000000..23a45849
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/forms.py
@@ -0,0 +1,75 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+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
+
+
+class CreateShareGroupSnapshotForm(forms.SelfHandlingForm):
+ name = forms.CharField(max_length="255", label=_("Name"), required=True)
+ description = forms.CharField(
+ widget=forms.Textarea,
+ label=_("Description"),
+ required=False)
+
+ def __init__(self, request, *args, **kwargs):
+ super(self.__class__, self).__init__(request, *args, **kwargs)
+ # populate share_group_id
+ sg_id = kwargs.get('initial', {}).get('share_group_id', [])
+ self.fields['share_group_id'] = forms.CharField(
+ widget=forms.HiddenInput(), initial=sg_id)
+
+ def handle(self, request, data):
+ try:
+ snapshot = manila.share_group_snapshot_create(
+ request,
+ data['share_group_id'], data['name'], data['description'])
+ message = _('Creating share group snapshot "%s".') % data['name']
+ messages.success(request, message)
+ return snapshot
+ except Exception:
+ redirect = reverse("horizon:project:share_group_snapshots:index")
+ exceptions.handle(
+ request,
+ _('Unable to create share group snapshot.'),
+ redirect=redirect)
+ return False
+
+
+class UpdateShareGroupSnapshotForm(forms.SelfHandlingForm):
+ name = forms.CharField(
+ max_length="255", label=_("Name"))
+ description = forms.CharField(
+ widget=forms.Textarea, label=_("Description"), required=False)
+
+ def handle(self, request, data):
+ sgs_id = self.initial['share_group_snapshot_id']
+ try:
+ manila.share_group_snapshot_update(
+ request, sgs_id, data['name'], data['description'])
+ message = _('Updating share group snapshot "%s"') % data['name']
+ messages.success(request, message)
+ return True
+ except Exception:
+ redirect = reverse("horizon:project:share_group_snapshots:index")
+ exceptions.handle(
+ request, _('Unable to update share group snapshot.'),
+ redirect=redirect)
+ return False
diff --git a/manila_ui/dashboards/project/share_group_snapshots/panel.py b/manila_ui/dashboards/project/share_group_snapshots/panel.py
new file mode 100644
index 00000000..8be79791
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/panel.py
@@ -0,0 +1,29 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+import horizon
+from openstack_dashboard.dashboards.project import dashboard
+
+
+class ShareGroupSnapshots(horizon.Panel):
+ name = _("Share Group Snapshots")
+ slug = 'share_group_snapshots'
+ permissions = (
+ 'openstack.services.share',
+ )
+
+
+dashboard.Project.register(ShareGroupSnapshots)
diff --git a/manila_ui/dashboards/project/share_group_snapshots/tables.py b/manila_ui/dashboards/project/share_group_snapshots/tables.py
new file mode 100644
index 00000000..4cab8344
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/tables.py
@@ -0,0 +1,173 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+from django.core.urlresolvers import reverse
+from django.template.defaultfilters import title # noqa
+from django.utils.http import urlencode # 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 horizon.utils import filters
+
+from manila_ui.api import manila
+
+
+class UpdateShareGroupSnapshot(tables.LinkAction):
+ name = "update_share_group_snapshot"
+ verbose_name = _("Update")
+ url = "horizon:project:share_group_snapshots:update"
+ classes = ("ajax-modal", "btn-camera")
+
+ def allowed(self, request, share_group_snapshot=None):
+ return share_group_snapshot.status in ("available", "error")
+
+
+class CreateShareGroupSnapshot(tables.LinkAction):
+ name = "create_share_group_snapshot"
+ verbose_name = _("Create Share Group Snapshot")
+ url = "horizon:project:share_group_snapshots:create"
+ classes = ("ajax-modal", "btn-camera")
+
+ def allowed(self, request, share_group=None):
+ self.verbose_name = _("Create Share Group Snapshot")
+ classes = [c for c in self.classes if c != "disabled"]
+ self.classes = classes
+ return share_group.status == "available"
+
+
+class CreateShareGroupFromSnapshot(tables.LinkAction):
+ name = "create_share_group_from_snapshot"
+ verbose_name = _("Create Share Group")
+ url = "horizon:project:share_groups:create"
+ classes = ("ajax-modal", "btn-camera")
+
+ def get_link_url(self, datum):
+ base_url = reverse(self.url)
+ params = urlencode({
+ "snapshot_id": self.table.get_object_id(datum)})
+ return "?".join([base_url, params])
+
+ def allowed(self, request, share_group=None):
+ return share_group.status == "available"
+
+
+class ShareGroupSnapshotShareGroupNameColumn(tables.Column):
+ def get_link_url(self, snapshot):
+ return reverse(self.link, args=(snapshot.share_group_id,))
+
+
+class DeleteShareGroupSnapshot(tables.DeleteAction):
+
+ @staticmethod
+ def action_present(count):
+ return ungettext_lazy(
+ u"Delete Share Group Snapshot",
+ u"Delete Share Group Snapshots",
+ count
+ )
+
+ @staticmethod
+ def action_past(count):
+ return ungettext_lazy(
+ u"Deleted Share Group Snapshot",
+ u"Deleted Share Group Snapshots",
+ count
+ )
+
+ 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_group_snapshot_delete(request, obj_id)
+ except Exception:
+ msg = _('Unable to delete share group snapshot "%s". '
+ 'One or more share groups depend on it.')
+ exceptions.check_message(["snapshots", "dependent"], msg % name)
+ raise
+
+ def allowed(self, request, snapshot=None):
+ if snapshot:
+ return snapshot.status.lower() in ('available', 'error')
+ return True
+
+
+class UpdateShareGroupSnapshotRow(tables.Row):
+ ajax = True
+
+ def get_data(self, request, share_group_snapshot_id):
+ snapshot = manila.share_group_snapshot_get(
+ request, share_group_snapshot_id)
+ if not snapshot.name:
+ snapshot.name = share_group_snapshot_id
+ return snapshot
+
+
+class ShareGroupSnapshotsTable(tables.DataTable):
+ STATUS_CHOICES = (
+ ("available", True),
+ ("creating", None),
+ ("error", False),
+ )
+ STATUS_DISPLAY_CHOICES = (
+ ("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:project:share_group_snapshots:detail")
+ description = tables.Column(
+ "description",
+ verbose_name=_("Description"),
+ truncate=40)
+ created_at = tables.Column(
+ "created_at",
+ verbose_name=_("Created at"),
+ filters=(
+ filters.parse_isotime,
+ ))
+ status = tables.Column(
+ "status",
+ filters=(title,),
+ verbose_name=_("Status"),
+ status=True,
+ status_choices=STATUS_CHOICES,
+ display_choices=STATUS_DISPLAY_CHOICES)
+ source = ShareGroupSnapshotShareGroupNameColumn(
+ "share_group",
+ verbose_name=_("Source"),
+ link="horizon:project:share_groups:detail")
+
+ def get_object_display(self, obj):
+ return obj.name
+
+ class Meta(object):
+ name = "share_group_snapshots"
+ verbose_name = _("Share Group Snapshots")
+ status_columns = ["status"]
+ row_class = UpdateShareGroupSnapshotRow
+ table_actions = (
+ tables.NameFilterAction,
+ DeleteShareGroupSnapshot,
+ )
+ row_actions = (
+ CreateShareGroupFromSnapshot,
+ UpdateShareGroupSnapshot,
+ DeleteShareGroupSnapshot,
+ )
diff --git a/manila_ui/dashboards/project/share_group_snapshots/tabs.py b/manila_ui/dashboards/project/share_group_snapshots/tabs.py
new file mode 100644
index 00000000..cc674465
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/tabs.py
@@ -0,0 +1,34 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+from horizon import tabs
+
+
+class ShareGroupSnapshotOverviewTab(tabs.Tab):
+ name = _("Share Group Snapshot Overview")
+ slug = "share_group_snapshot_overview_tab"
+ template_name = "project/share_group_snapshots/_detail.html"
+
+ def get_context_data(self, request):
+ return {"share_group_snapshot": self.tab_group.kwargs[
+ "share_group_snapshot"]}
+
+
+class ShareGroupSnapshotDetailTabs(tabs.TabGroup):
+ slug = "share_group_snapshot_details"
+ tabs = (
+ ShareGroupSnapshotOverviewTab,
+ )
diff --git a/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_create.html b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_create.html
new file mode 100644
index 00000000..0c9d6415
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_create.html
@@ -0,0 +1,8 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-body-right %}
+
+ Please, choose a name for the new share group snapshot,
+ that will be created from '{{ share_group_name }}' share group.
+
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_detail.html b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_detail.html
new file mode 100644
index 00000000..91dbc9e7
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_detail.html
@@ -0,0 +1,23 @@
+{% load i18n sizeformat parse_date %}
+
+{% trans "Share Group Snapshot Overview" %}
+
+
+
+ {% trans "Name" %}
+ {{ share_group_snapshot.name }}
+ {% trans "ID" %}
+ {{ share_group_snapshot.id }}
+ {% url 'horizon:project:share_groups:detail' share_group_snapshot.share_group_id as sg_url %}
+ {% trans "Source Share Group" %}
+ {{ share_group_snapshot.sg_name_or_id }}
+ {% if share_group_snapshot.description %}
+ {% trans "Description" %}
+ {{ share_group_snapshot.description }}
+ {% endif %}
+ {% trans "Status" %}
+ {{ share_group_snapshot.status|capfirst }}
+ {% trans "Created" %}
+ {{ share_group_snapshot.created_at|parse_date }}
+
+
diff --git a/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_update.html b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_update.html
new file mode 100644
index 00000000..dab70eab
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/_update.html
@@ -0,0 +1,6 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-body-right %}
+ {% trans "Description" %}:
+ {% trans "From here you can modify name and description of a share group snapshot." %}
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/create.html b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/create.html
new file mode 100644
index 00000000..1a514f01
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/create.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Create Share Group Snapshot" %}{% endblock %}
+
+{% block main %}
+ {% include 'project/share_group_snapshots/_create.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/detail.html b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/detail.html
new file mode 100644
index 00000000..577d367c
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/detail.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Group Snapshot Details" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ tab_group.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/index.html b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/index.html
new file mode 100644
index 00000000..840225b0
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Group Snapshots" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ share_group_snapshots_table.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/update.html b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/update.html
new file mode 100644
index 00000000..61fdceb8
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/templates/share_group_snapshots/update.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Update Share Group Snapshot" %}{% endblock %}
+
+{% block main %}
+ {% include 'project/share_group_snapshots/_update.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_group_snapshots/urls.py b/manila_ui/dashboards/project/share_group_snapshots/urls.py
new file mode 100644
index 00000000..d0ff0376
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/urls.py
@@ -0,0 +1,40 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf import urls
+
+from manila_ui.dashboards.project.share_group_snapshots import views
+from manila_ui import features
+
+
+if features.is_share_groups_enabled():
+ urlpatterns = [
+ urls.url(
+ r'^$',
+ views.ShareGroupSnapshotsView.as_view(),
+ name='index'),
+ urls.url(
+ r'^(?P[^/]+)/snapshot_create/$',
+ views.CreateShareGroupSnapshotView.as_view(),
+ name='create'),
+ urls.url(
+ r'^(?P[^/]+)/$',
+ views.ShareGroupSnapshotDetailView.as_view(),
+ name='detail'),
+ urls.url(
+ r'^(?P[^/]+)/update/$',
+ views.UpdateShareGroupSnapshotView.as_view(),
+ name='update'),
+ ]
diff --git a/manila_ui/dashboards/project/share_group_snapshots/views.py b/manila_ui/dashboards/project/share_group_snapshots/views.py
new file mode 100644
index 00000000..c11729a4
--- /dev/null
+++ b/manila_ui/dashboards/project/share_group_snapshots/views.py
@@ -0,0 +1,164 @@
+# Copyright 2017 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.
+
+"""
+Project views for managing share group snapshots.
+"""
+
+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 import tabs
+from horizon.utils import memoized
+
+from manila_ui.api import manila
+import manila_ui.dashboards.project.share_group_snapshots.forms as sgs_forms
+import manila_ui.dashboards.project.share_group_snapshots.tables as sgs_tables
+import manila_ui.dashboards.project.share_group_snapshots.tabs as sgs_tabs
+
+
+class ShareGroupSnapshotsView(tables.MultiTableView):
+ table_classes = (
+ sgs_tables.ShareGroupSnapshotsTable,
+ )
+ template_name = "project/share_group_snapshots/index.html"
+ page_title = _("Share Group Snapshots")
+
+ @memoized.memoized_method
+ def get_share_group_snapshots_data(self):
+ share_group_snapshots = []
+ try:
+ share_group_snapshots = manila.share_group_snapshot_list(
+ self.request, search_opts={'all_tenants': True})
+ share_groups = manila.share_group_list(self.request)
+ sg_names = dict([(sg.id, sg.name or sg.id) for sg in share_groups])
+ for snapshot in share_group_snapshots:
+ snapshot.share_group = sg_names.get(snapshot.share_group_id)
+ except Exception:
+ msg = _("Unable to retrieve share group snapshot list.")
+ exceptions.handle(self.request, msg)
+
+ return share_group_snapshots
+
+
+class ShareGroupSnapshotDetailView(tabs.TabView):
+ tab_group_class = sgs_tabs.ShareGroupSnapshotDetailTabs
+ template_name = "project/share_group_snapshots/detail.html"
+ redirect_url = reverse_lazy("horizon:project:share_group_snapshots:index")
+
+ def get_context_data(self, **kwargs):
+ context = super(ShareGroupSnapshotDetailView, self).get_context_data(
+ **kwargs)
+ snapshot = self.get_data()
+ snapshot_display_name = snapshot.name or snapshot.id
+ context["snapshot"] = snapshot
+ context["snapshot_display_name"] = snapshot_display_name
+ context["page_title"] = _(
+ "Share Group Snapshot Details: %(sgs_display_name)s") % (
+ {'sgs_display_name': snapshot_display_name})
+ return context
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ share_group_snapshot = manila.share_group_snapshot_get(
+ self.request, self.kwargs['share_group_snapshot_id'])
+ sg = manila.share_group_get(
+ self.request, share_group_snapshot.share_group_id)
+ share_group_snapshot.sg_name_or_id = sg.name or sg.id
+ except Exception:
+ exceptions.handle(
+ self.request,
+ _('Unable to retrieve share group snapshot details.'),
+ redirect=self.redirect_url)
+ return share_group_snapshot
+
+ def get_tabs(self, request, *args, **kwargs):
+ return self.tab_group_class(
+ request, share_group_snapshot=self.get_data(), **kwargs)
+
+
+class CreateShareGroupSnapshotView(forms.ModalFormView):
+ form_class = sgs_forms.CreateShareGroupSnapshotForm
+ form_id = "create_share_group_snapshot"
+ template_name = 'project/share_group_snapshots/create.html'
+ modal_header = _("Create Share Group Snapshot")
+ modal_id = "create_share_group_snapshot_modal"
+ submit_label = _("Create Share Group Snapshot")
+ submit_url = "horizon:project:share_group_snapshots:create"
+ success_url = reverse_lazy('horizon:project:share_group_snapshots:index')
+ page_title = _('Create Share Group Snapshot')
+
+ def get_context_data(self, **kwargs):
+ context = super(self.__class__, self).get_context_data(**kwargs)
+ sg_id = self.kwargs['share_group_id']
+ try:
+ sg = manila.share_group_get(self.request, sg_id)
+ context['share_group_name'] = sg.name or sg_id
+ except Exception:
+ exceptions.handle(
+ self.request,
+ _("Unable to get the specified share group '%s' for "
+ "snapshot creation.") % sg_id)
+ context['share_group_name'] = sg_id
+ context['share_group_id'] = sg_id
+ # TODO(vponomaryov): add support of quotas when it is implemented
+ # for share group snapshots on server side.
+ return context
+
+ def get_initial(self):
+ self.submit_url = reverse(self.submit_url, kwargs=self.kwargs)
+ return {'share_group_id': self.kwargs["share_group_id"]}
+
+
+class UpdateShareGroupSnapshotView(forms.ModalFormView):
+ form_class = sgs_forms.UpdateShareGroupSnapshotForm
+ form_id = "update_share_group_snapshot"
+ template_name = 'project/share_group_snapshots/update.html'
+ modal_header = _("Update Share Group Snapshot")
+ modal_id = "update_share_group_snapshot_modal"
+ submit_label = _("Update")
+ submit_url = "horizon:project:share_group_snapshots:update"
+ success_url = reverse_lazy('horizon:project:share_group_snapshots:index')
+ page_title = _('Update Share Group')
+
+ @memoized.memoized_method
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ try:
+ self._object = manila.share_group_snapshot_get(
+ self.request, self.kwargs['share_group_snapshot_id'])
+ except Exception:
+ msg = _('Unable to retrieve share group snapshot.')
+ url = reverse('horizon:project:share_group_snapshots:index')
+ exceptions.handle(self.request, msg, redirect=url)
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(self.__class__, 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):
+ snapshot = self.get_object()
+ return {
+ 'share_group_snapshot_id': self.kwargs["share_group_snapshot_id"],
+ 'name': snapshot.name,
+ 'description': snapshot.description,
+ }
diff --git a/manila_ui/dashboards/project/share_groups/__init__.py b/manila_ui/dashboards/project/share_groups/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/dashboards/project/share_groups/forms.py b/manila_ui/dashboards/project/share_groups/forms.py
new file mode 100644
index 00000000..833507db
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/forms.py
@@ -0,0 +1,265 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+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 horizon.utils import memoized
+import six
+
+from manila_ui.api import manila
+
+
+class CreateShareGroupForm(forms.SelfHandlingForm):
+ name = forms.CharField(label=_("Name"), max_length="255", required=True)
+ description = forms.CharField(
+ label=_("Description"),
+ max_length="255",
+ widget=forms.Textarea(attrs={"rows": 3}),
+ required=False)
+
+ def __init__(self, request, *args, **kwargs):
+ super(CreateShareGroupForm, self).__init__(request, *args, **kwargs)
+ self.st_field_name_prefix = "share-type-choices-"
+ self.fields["source_type"] = forms.ChoiceField(
+ label=_("Source Type"),
+ widget=forms.Select(attrs={
+ "class": "switchable",
+ "data-slug": "source",
+ }),
+ required=False)
+
+ self.fields["snapshot"] = forms.ChoiceField(
+ label=_("Use share group snapshot as a source"),
+ widget=forms.SelectWidget(attrs={
+ "class": "switched",
+ "data-switch-on": "source",
+ "data-source-snapshot": _("Share Group Snapshot"),
+ }),
+ required=True)
+
+ if ("snapshot_id" in request.GET or
+ kwargs.get("data", {}).get("snapshot")):
+ try:
+ snapshot = self.get_share_group_snapshot(
+ request,
+ request.GET.get(
+ "snapshot_id",
+ kwargs.get("data", {}).get("snapshot")))
+ self.fields["name"].initial = snapshot.name
+ self.fields["snapshot"].choices = (
+ (snapshot.id, snapshot.name or snapshot.id),
+ )
+ try:
+ # Set the share group type from the original share group
+ orig_sg = manila.share_group_get(
+ request, snapshot.share_group_id)
+ self.fields["sgt"].initial = orig_sg.share_group_type_id
+ except Exception:
+ pass
+ del self.fields["source_type"]
+ except Exception:
+ exceptions.handle(
+ request,
+ _("Unable to load the specified share group snapshot."))
+ else:
+ source_type_choices = []
+ try:
+ snapshot_list = manila.share_group_snapshot_list(request)
+ snapshots = [s for s in snapshot_list
+ if s.status == "available"]
+ if snapshots:
+ source_type_choices.append(("snapshot", _("Snapshot")))
+ self.fields["snapshot"].choices = (
+ [("", _("Choose a snapshot"))] +
+ [(s.id, s.name or s.id) for s in snapshots]
+ )
+ else:
+ del self.fields["snapshot"]
+ except Exception:
+ exceptions.handle(
+ request,
+ _("Unable to retrieve share group snapshots."))
+
+ if source_type_choices:
+ choices = ([('none', _("No source, empty share group"))] +
+ source_type_choices)
+ self.fields["source_type"].choices = choices
+ else:
+ del self.fields["source_type"]
+
+ self.fields["az"] = forms.ChoiceField(
+ label=_("Availability Zone"),
+ widget=forms.SelectWidget(attrs={
+ "class": "switched",
+ "data-switch-on": "source",
+ "data-source-none": _("Availability Zone"),
+ }),
+ required=False)
+
+ availability_zones = manila.availability_zone_list(request)
+ self.fields["az"].choices = (
+ [("", "")] + [(az.name, az.name) for az in availability_zones])
+
+ share_group_types = manila.share_group_type_list(request)
+ self.fields["sgt"] = forms.ChoiceField(
+ label=_("Share Group Type"),
+ widget=forms.fields.SelectWidget(attrs={
+ "class": "switched switchable",
+ "data-switch-on": "source",
+ "data-source-none": _("Share Group Type"),
+ "data-slug": "sgt",
+ }),
+ required=True)
+ self.fields["sgt"].choices = (
+ [("", "")] + [(sgt.id, sgt.name) for sgt in share_group_types])
+
+ # NOTE(vponomaryov): create separate set of available share types
+ # for each of share group types.
+ share_types = manila.share_type_list(request)
+ for sgt in share_group_types:
+ st_choices = (
+ [(st.id, st.name)
+ for st in share_types if st.id in sgt.share_types])
+ amount_of_choices = len(st_choices)
+ st_field_name = self.st_field_name_prefix + sgt.id
+ if amount_of_choices < 2:
+ st_field = forms.ChoiceField(
+ label=_("Share Types"),
+ choices=st_choices,
+ widget=forms.fields.SelectWidget(attrs={
+ "class": "switched",
+ "data-switch-on": "sgt",
+ "data-sgt-%s" % sgt.id: _(
+ "Share Types (one available)"),
+ }),
+ required=True)
+ else:
+ height = min(30 * amount_of_choices, 155)
+ st_field = forms.MultipleChoiceField(
+ label=_("Share Types"),
+ choices=st_choices,
+ widget=forms.fields.widgets.SelectMultiple(attrs={
+ "style": "max-height: %spx;" % height,
+ "class": "switched",
+ "data-switch-on": "sgt",
+ "data-sgt-%s" % sgt.id: _(
+ "Share Types (multiple available)"),
+ }),
+ required=False)
+ st_field.initial = st_choices[0]
+ self.fields[st_field_name] = st_field
+
+ self.fields["share_network"] = forms.ChoiceField(
+ label=_("Share Network"),
+ widget=forms.fields.SelectWidget(attrs={
+ "class": "switched",
+ "data-switch-on": "source",
+ "data-source-none": _("Share Network"),
+ }),
+ required=False)
+ share_networks = manila.share_network_list(request)
+ self.fields["share_network"].choices = (
+ [("", "")] +
+ [(sn.id, sn.name or sn.id) for sn in share_networks])
+
+ def clean(self):
+ cleaned_data = super(CreateShareGroupForm, self).clean()
+ errors = [k for k in self.errors.viewkeys()]
+
+ for k in errors:
+ sgt_name = k.split(self.st_field_name_prefix)[-1]
+ chosen_sgt = cleaned_data.get("sgt")
+ if (k.startswith(self.st_field_name_prefix) and
+ sgt_name != chosen_sgt):
+ cleaned_data[k] = "Not set"
+ self.errors.pop(k, None)
+
+ source_type = cleaned_data.get("source_type")
+ if source_type != "snapshot":
+ self.errors.pop("snapshot", None)
+ share_group_type = cleaned_data.get("sgt")
+ if share_group_type:
+ cleaned_data["share_types"] = cleaned_data.get(
+ self.st_field_name_prefix + share_group_type)
+ if isinstance(cleaned_data["share_types"], six.string_types):
+ cleaned_data["share_types"] = [cleaned_data["share_types"]]
+ else:
+ self.errors.pop("sgt", None)
+
+ return cleaned_data
+
+ def handle(self, request, data):
+ try:
+ source_type = data.get('source_type')
+ if (data.get("snapshot") and source_type in (None, 'snapshot')):
+ snapshot = self.get_share_group_snapshot(
+ request, data["snapshot"])
+ snapshot_id = snapshot.id
+ source_sg = manila.share_group_get(
+ request, snapshot.share_group_id)
+ data['sgt'] = source_sg.share_group_type_id
+ else:
+ snapshot_id = None
+
+ share_group = manila.share_group_create(
+ request,
+ name=data['name'],
+ description=data['description'],
+ share_group_type=data['sgt'],
+ share_types=None if snapshot_id else data.get('share_types'),
+ share_network=(
+ None if snapshot_id else data.get('share_network')),
+ source_share_group_snapshot=snapshot_id,
+ availability_zone=None if snapshot_id else data['az'])
+
+ message = _('Creating share group "%s"') % data['name']
+ messages.success(request, message)
+ return share_group
+ except ValidationError as e:
+ self.api_error(e.messages[0])
+ return False
+ except Exception:
+ exceptions.handle(request, ignore=True)
+ self.api_error(_("Unable to create share group."))
+ return False
+
+ @memoized.memoized
+ def get_share_group_snapshot(self, request, sg_snapshot_id):
+ return manila.share_group_snapshot_get(request, sg_snapshot_id)
+
+
+class UpdateShareGroupForm(forms.SelfHandlingForm):
+ name = forms.CharField(
+ max_length="255", label=_("Share Group Name"))
+ description = forms.CharField(
+ widget=forms.Textarea, label=_("Description"), required=False)
+
+ def handle(self, request, data):
+ sg_id = self.initial['share_group_id']
+ try:
+ manila.share_group_update(
+ request, sg_id, data['name'], data['description'])
+ message = _('Updating share group "%s"') % data['name']
+ messages.success(request, message)
+ return True
+ except Exception:
+ redirect = reverse("horizon:project:share_groups:index")
+ exceptions.handle(
+ request, _('Unable to update share group.'), redirect=redirect)
+ return False
diff --git a/manila_ui/dashboards/project/share_groups/panel.py b/manila_ui/dashboards/project/share_groups/panel.py
new file mode 100644
index 00000000..23f0667d
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/panel.py
@@ -0,0 +1,29 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+import horizon
+from openstack_dashboard.dashboards.project import dashboard
+
+
+class ShareGroups(horizon.Panel):
+ name = _("Share Groups")
+ slug = 'share_groups'
+ permissions = (
+ 'openstack.services.share',
+ )
+
+
+dashboard.Project.register(ShareGroups)
diff --git a/manila_ui/dashboards/project/share_groups/tables.py b/manila_ui/dashboards/project/share_groups/tables.py
new file mode 100644
index 00000000..dac174ed
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/tables.py
@@ -0,0 +1,161 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.core.urlresolvers import reverse
+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
+import manila_ui.dashboards.project.share_group_snapshots.tables as sgs_tables
+
+
+class UpdateShareGroup(tables.LinkAction):
+ name = "update"
+ verbose_name = _("Update")
+ url = "horizon:project:share_groups:update"
+ classes = ("ajax-modal", "btn-create")
+
+ def allowed(self, request, share_group=None):
+ return share_group.status in ("available", "error")
+
+
+class DeleteShareGroup(tables.DeleteAction):
+
+ @staticmethod
+ def action_present(count):
+ return ungettext_lazy(
+ u"Delete Share Group",
+ u"Delete Share Groups",
+ count
+ )
+
+ @staticmethod
+ def action_past(count):
+ return ungettext_lazy(
+ u"Deleted Share Group",
+ u"Deleted Share Groups",
+ count
+ )
+
+ def delete(self, request, obj_id):
+ manila.share_group_delete(request, obj_id)
+
+ def allowed(self, request, share_group=None):
+ if share_group:
+ # Row button
+ return (share_group.status.lower() in ('available', 'error'))
+ # Table button. Always return 'True'
+ return True
+
+
+class CreateShareGroup(tables.LinkAction):
+ name = "create"
+ verbose_name = _("Create Share Group")
+ url = "horizon:project:share_groups:create"
+ classes = ("ajax-modal", "btn-create")
+ icon = "plus"
+ policy_rules = (("share", "share_group:create"),)
+
+ def allowed(self, request, share=None):
+ # TODO(vponomaryov): implement quota restriction when quota support
+ # is implemented for share groups.
+ return True
+
+
+class UpdateShareGroupRow(tables.Row):
+ ajax = True
+
+ def get_data(self, request, sg_id):
+ sg = manila.share_group_get(request, sg_id)
+ return sg
+
+
+class ShareGroupsTable(tables.DataTable):
+ def get_share_network_link(share_group):
+ if getattr(share_group, 'share_network_id', None):
+ return reverse(
+ "horizon:project:share_networks:share_network_detail",
+ args=(share_group.share_network_id,))
+ else:
+ return None
+
+ def get_source_share_group_snapshot_link(share_group):
+ if getattr(share_group, 'source_share_group_snapshot_id', None):
+ return reverse(
+ "horizon:project:share_group_snapshots:detail",
+ args=(share_group.source_share_group_snapshot_id,))
+ else:
+ return None
+
+ 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"),
+ )
+ name = tables.Column(
+ "name",
+ verbose_name=_("Name"),
+ link="horizon:project:share_groups:detail")
+ 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"))
+ share_network_id = tables.Column(
+ "share_network_id",
+ verbose_name=_("Share Network"),
+ link=get_share_network_link)
+ source_share_group_snapshot_id = tables.Column(
+ "source_share_group_snapshot_id",
+ verbose_name=_("Source Share Group Snapshot"),
+ link=get_source_share_group_snapshot_link)
+
+ def get_object_display(self, share_group):
+ return six.text_type(share_group.id)
+
+ def get_object_id(self, share_group):
+ return six.text_type(share_group.id)
+
+ class Meta(object):
+ name = "share_groups"
+ verbose_name = _("Share Groups")
+ status_columns = ("status", )
+ row_class = UpdateShareGroupRow
+ table_actions = (
+ CreateShareGroup,
+ tables.NameFilterAction,
+ DeleteShareGroup,
+ )
+ row_actions = (
+ sgs_tables.CreateShareGroupSnapshot,
+ UpdateShareGroup,
+ DeleteShareGroup,
+ )
diff --git a/manila_ui/dashboards/project/share_groups/tabs.py b/manila_ui/dashboards/project/share_groups/tabs.py
new file mode 100644
index 00000000..bc5b1eda
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/tabs.py
@@ -0,0 +1,33 @@
+# Copyright 2017 Mirantis, Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.utils.translation import ugettext_lazy as _
+from horizon import tabs
+
+
+class ShareGroupOverviewTab(tabs.Tab):
+ name = _("Share Group Overview")
+ slug = "share_group_overview_tab"
+ template_name = "project/share_groups/_detail.html"
+
+ def get_context_data(self, request):
+ return {"share_group": self.tab_group.kwargs["share_group"]}
+
+
+class ShareGroupDetailTabs(tabs.TabGroup):
+ slug = "share_group_details"
+ tabs = (
+ ShareGroupOverviewTab,
+ )
diff --git a/manila_ui/dashboards/project/share_groups/templates/share_groups/_create.html b/manila_ui/dashboards/project/share_groups/templates/share_groups/_create.html
new file mode 100644
index 00000000..9e2d3241
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/templates/share_groups/_create.html
@@ -0,0 +1,6 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-body-right %}
+{% trans "Description" %}:
+{% trans "Select parameters of share group you want to create. " %}
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_groups/templates/share_groups/_detail.html b/manila_ui/dashboards/project/share_groups/templates/share_groups/_detail.html
new file mode 100644
index 00000000..69df06c4
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/templates/share_groups/_detail.html
@@ -0,0 +1,55 @@
+{% load i18n sizeformat parse_date %}
+
+{% trans "Share Group Overview" %}
+
+
+
+ {% trans "ID" %}
+ {{ share_group.id }}
+ {% trans "Name" %}
+ {{ share_group.name }}
+ {% trans "Description" %}
+ {{ share_group.description }}
+ {% trans "Status" %}
+ {{ share_group.status|capfirst }}
+ {% trans "Created" %}
+ {{ share_group.created_at|parse_date }}
+ {% if share_group.source_share_group_snapshot_id %}
+ {% trans "Source SG Snapshot" %}
+ {% url 'horizon:project:share_group_snapshots:detail' share_group.source_share_group_snapshot_id as ssgs_url%}
+ {{ share_group.source_share_group_snapshot_id }}
+ {% endif %}
+ {% trans "Share Group Type" %}
+ {{ share_group.share_group_type_id}}
+ {% if share_group.availability_zone %}
+ {% trans "Availability Zone" %}
+ {{ share_group.availability_zone }}
+ {% endif %}
+ {% if share_group.share_network_id %}
+ {% trans "Share Network" %}
+ {% url 'horizon:project:share_networks:share_network_detail' share_group.share_network_id as sn_url%}
+ {{ share_group.share_network_id }}
+ {% endif %}
+ {% if share_group.share_types %}
+ {% trans "Share Types" %}
+ {% for st in share_group.share_types %}
+
+
Share Type Name: {{ st.name }}
+ Share Type Visibility: {% if st.is_public %}Public{% else %}Private{% endif %}
+ Network handling enabled: {{ st.dhss }}
+
+ {% endfor %}
+ {% endif %}
+ {% if share_group.members %}
+ {% trans "Shares" %}
+ {% for m in share_group.members %}
+ {% url 'horizon:project:shares:detail' m.id as share_url%}
+
+
+
+ {% endfor %}
+ {% endif %}
+
+
diff --git a/manila_ui/dashboards/project/share_groups/templates/share_groups/_update.html b/manila_ui/dashboards/project/share_groups/templates/share_groups/_update.html
new file mode 100644
index 00000000..c8826477
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/templates/share_groups/_update.html
@@ -0,0 +1,6 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% block modal-body-right %}
+ {% trans "Description" %}:
+ {% trans "From here you can modify name, description and visibility of a share group." %}
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_groups/templates/share_groups/create.html b/manila_ui/dashboards/project/share_groups/templates/share_groups/create.html
new file mode 100644
index 00000000..d8e54768
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/templates/share_groups/create.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Create Share Group" %}{% endblock %}
+
+{% block main %}
+ {% include 'project/share_groups/_create.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_groups/templates/share_groups/detail.html b/manila_ui/dashboards/project/share_groups/templates/share_groups/detail.html
new file mode 100644
index 00000000..95114d91
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/templates/share_groups/detail.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Group Details" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ tab_group.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_groups/templates/share_groups/index.html b/manila_ui/dashboards/project/share_groups/templates/share_groups/index.html
new file mode 100644
index 00000000..93a16df5
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/templates/share_groups/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Share Groups" %}{% endblock %}
+
+{% block main %}
+
+
+ {{ share_groups_table.render }}
+
+
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_groups/templates/share_groups/update.html b/manila_ui/dashboards/project/share_groups/templates/share_groups/update.html
new file mode 100644
index 00000000..6977288c
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/templates/share_groups/update.html
@@ -0,0 +1,7 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Update Share Group" %}{% endblock %}
+
+{% block main %}
+ {% include 'project/share_groups/_update.html' %}
+{% endblock %}
diff --git a/manila_ui/dashboards/project/share_groups/urls.py b/manila_ui/dashboards/project/share_groups/urls.py
new file mode 100644
index 00000000..41e7e742
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/urls.py
@@ -0,0 +1,40 @@
+# Copyright 2017 Mirantis Inc.
+# All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from django.conf import urls
+
+from manila_ui.dashboards.project.share_groups import views
+from manila_ui import features
+
+
+if features.is_share_groups_enabled():
+ urlpatterns = [
+ urls.url(
+ r'^$',
+ views.ShareGroupsView.as_view(),
+ name='index'),
+ urls.url(
+ r'^create/$',
+ views.ShareGroupCreateView.as_view(),
+ name='create'),
+ urls.url(
+ r'^(?P[^/]+)/$',
+ views.ShareGroupDetailView.as_view(),
+ name='detail'),
+ urls.url(
+ r'^(?P[^/]+)/update/$',
+ views.ShareGroupUpdateView.as_view(),
+ name='update'),
+ ]
diff --git a/manila_ui/dashboards/project/share_groups/views.py b/manila_ui/dashboards/project/share_groups/views.py
new file mode 100644
index 00000000..3f179fbd
--- /dev/null
+++ b/manila_ui/dashboards/project/share_groups/views.py
@@ -0,0 +1,150 @@
+# Copyright 2017 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.
+
+"""
+Project views for managing share groups.
+"""
+
+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 import tabs
+from horizon.utils import memoized
+
+from manila_ui.api import manila
+from manila_ui.dashboards.project.share_groups import forms as sg_forms
+from manila_ui.dashboards.project.share_groups import tables as sg_tables
+from manila_ui.dashboards.project.share_groups import tabs as sg_tabs
+
+
+class ShareGroupsView(tables.MultiTableView):
+ table_classes = (
+ sg_tables.ShareGroupsTable,
+ )
+ template_name = "project/share_groups/index.html"
+ page_title = _("Share Groups")
+
+ @memoized.memoized_method
+ def get_share_groups_data(self):
+ try:
+ share_groups = manila.share_group_list(
+ self.request, detailed=True)
+ except Exception:
+ share_groups = []
+ exceptions.handle(
+ self.request, _("Unable to retrieve share groups."))
+ return share_groups
+
+
+class ShareGroupDetailView(tabs.TabView):
+ tab_group_class = sg_tabs.ShareGroupDetailTabs
+ template_name = 'project/share_groups/detail.html'
+
+ def get_context_data(self, **kwargs):
+ context = super(self.__class__, self).get_context_data(**kwargs)
+ share_group = self.get_data()
+ context["share_group"] = share_group
+ context["page_title"] = (
+ _("Share Group Details: %s") % share_group.id)
+ return context
+
+ @memoized.memoized_method
+ def get_data(self):
+ try:
+ share_group_id = self.kwargs['share_group_id']
+ share_group = manila.share_group_get(self.request, share_group_id)
+ members = manila.share_list(
+ self.request, search_opts={"share_group_id": share_group_id})
+ share_group.members = members
+ share_types = manila.share_type_list(self.request)
+ share_group.share_types = [
+ {"id": st.id,
+ "name": st.name,
+ "is_public": getattr(st, 'share_type_access:is_public'),
+ "dhss": st.extra_specs.get('driver_handles_share_servers')}
+ for st in share_types if st.id in share_group.share_types
+ ]
+ return share_group
+ except Exception:
+ redirect = reverse('horizon:project:share_groups:index')
+ exceptions.handle(
+ self.request,
+ _('Unable to retrieve share group details.'),
+ redirect=redirect)
+
+ def get_tabs(self, request, *args, **kwargs):
+ share_group = self.get_data()
+ return self.tab_group_class(request, share_group=share_group, **kwargs)
+
+
+class ShareGroupCreateView(forms.ModalFormView):
+ form_class = sg_forms.CreateShareGroupForm
+ form_id = "create_share_group"
+ template_name = 'project/share_groups/create.html'
+ modal_header = _("Create Share Group")
+ modal_id = "create_share_group_modal"
+ submit_label = _("Create")
+ submit_url = reverse_lazy("horizon:project:share_groups:create")
+ success_url = reverse_lazy("horizon:project:share_groups:index")
+ page_title = _('Create a Share')
+
+ def get_context_data(self, **kwargs):
+ context = super(ShareGroupCreateView, self).get_context_data(**kwargs)
+ try:
+ # TODO(vponomaryov): add quota logic here when quotas are
+ # implemented for share groups.
+ pass
+ except Exception:
+ exceptions.handle(self.request)
+ return context
+
+
+class ShareGroupUpdateView(forms.ModalFormView):
+ form_class = sg_forms.UpdateShareGroupForm
+ form_id = "update_share_group"
+ template_name = 'project/share_groups/update.html'
+ modal_header = _("Update")
+ modal_id = "update_share_group_modal"
+ submit_label = _("Update")
+ submit_url = "horizon:project:share_groups:update"
+ success_url = reverse_lazy("horizon:project:share_groups:index")
+ page_title = _("Update Share Group")
+
+ def get_object(self):
+ if not hasattr(self, "_object"):
+ sg_id = self.kwargs['share_group_id']
+ try:
+ self._object = manila.share_group_get(self.request, sg_id)
+ except Exception:
+ msg = _('Unable to retrieve share group.')
+ url = reverse('horizon:project:share_groups:index')
+ exceptions.handle(self.request, msg, redirect=url)
+ return self._object
+
+ def get_context_data(self, **kwargs):
+ context = super(ShareGroupUpdateView, self).get_context_data(**kwargs)
+ return context
+
+ def get_initial(self):
+ self.submit_url = reverse(self.submit_url, kwargs=self.kwargs)
+ sg = self.get_object()
+ return {
+ 'share_group_id': self.kwargs["share_group_id"],
+ 'name': sg.name,
+ 'description': sg.description,
+ }
diff --git a/manila_ui/dashboards/project/shares/forms.py b/manila_ui/dashboards/project/shares/forms.py
index 6d0074a1..99f22144 100644
--- a/manila_ui/dashboards/project/shares/forms.py
+++ b/manila_ui/dashboards/project/shares/forms.py
@@ -18,16 +18,17 @@ from django.core.urlresolvers import reverse
from django.forms import ValidationError # noqa
from django.template.defaultfilters import filesizeformat # noqa
from django.utils.translation import ugettext_lazy as _
-import six
-
from horizon import exceptions
from horizon import forms
from horizon import messages
from horizon.utils.memoized import memoized # noqa
+from openstack_dashboard.usage import quotas
+import six
from manila_ui.api import manila
from manila_ui.dashboards import utils
-from openstack_dashboard.usage import quotas
+from manila_ui import features
+from manilaclient.common.apiclient import exceptions as m_exceptions
class CreateForm(forms.SelfHandlingForm):
@@ -65,6 +66,14 @@ class CreateForm(forms.SelfHandlingForm):
self.fields['availability_zone'].choices = (
[("", "")] + [(az.name, az.name) for az in availability_zones])
+ if features.is_share_groups_enabled():
+ share_groups = manila.share_group_list(request)
+ self.fields['sg'] = forms.ChoiceField(
+ label=_("Share Group"),
+ choices=[("", "")] + [(sg.id, sg.name or sg.id)
+ for sg in share_groups],
+ required=False)
+
self.sn_field_name_prefix = 'share-network-choices-'
for st in share_types:
extra_specs = st.get_keys()
@@ -226,17 +235,20 @@ class CreateForm(forms.SelfHandlingForm):
share_type=data['share_type'],
is_public=is_public,
metadata=metadata,
- availability_zone=data['availability_zone'])
+ availability_zone=data['availability_zone'],
+ share_group_id=data.get('sg') or None,
+ )
message = _('Creating share "%s"') % data['name']
messages.success(request, message)
return share
except ValidationError as e:
self.api_error(e.messages[0])
- return False
+ except m_exceptions.BadRequest as e:
+ self.api_error(_("Unable to create share. %s") % e.message)
except Exception:
exceptions.handle(request, ignore=True)
self.api_error(_("Unable to create share."))
- return False
+ return False
@memoized
def get_snapshot(self, request, id):
diff --git a/manila_ui/dashboards/project/shares/tables.py b/manila_ui/dashboards/project/shares/tables.py
index 946564c3..b3593375 100644
--- a/manila_ui/dashboards/project/shares/tables.py
+++ b/manila_ui/dashboards/project/shares/tables.py
@@ -22,10 +22,12 @@ from django.utils.translation import ungettext_lazy
from horizon import exceptions
from horizon import messages
from horizon import tables
+from openstack_dashboard.usage import quotas
+
from manila_ui.api import manila
from manila_ui.dashboards.project.share_snapshots import tables as ss_tables
from manila_ui.dashboards import utils
-from openstack_dashboard.usage import quotas
+from manila_ui import features
DELETABLE_STATES = (
@@ -60,8 +62,10 @@ class DeleteShare(tables.DeleteAction):
return {"project_id": project_id}
def delete(self, request, obj_id):
+ share = manila.share_get(request, obj_id)
try:
- manila.share_delete(request, obj_id)
+ manila.share_delete(
+ request, share.id, share_group_id=share.share_group_id)
except Exception:
msg = _('Unable to delete share "%s". ') % obj_id
messages.error(request, msg)
@@ -250,6 +254,21 @@ class SharesTableBase(tables.DataTable):
def get_object_display(self, obj):
return obj.name or obj.id
+ def get_share_group_link(share):
+ if features.is_share_groups_enabled() and share.share_group_id:
+ return reverse(
+ "horizon:project:share_groups:detail",
+ args=(share.share_group_id,))
+ else:
+ return None
+
+ share_group_id = tables.Column(
+ "share_group_id",
+ verbose_name=_("Share Group"),
+ empty_value="-",
+ link=get_share_group_link,
+ )
+
class ManageRules(tables.LinkAction):
name = "manage_rules"
@@ -268,7 +287,7 @@ class ManageReplicas(tables.LinkAction):
def allowed(self, request, share):
share_replication_enabled = share.replication_type is not None
- return manila.is_replication_enabled() and share_replication_enabled
+ return features.is_replication_enabled() and share_replication_enabled
class AddRule(tables.LinkAction):
@@ -387,3 +406,10 @@ class SharesTable(SharesTableBase):
ManageReplicas,
EditShareMetadata,
DeleteShare)
+
+ columns = [
+ 'name', 'description', 'metadata', 'size', 'status',
+ 'proto', 'visibility', 'share_network',
+ ]
+ if features.is_share_groups_enabled():
+ columns.append('share_group_id')
diff --git a/manila_ui/dashboards/project/shares/templates/shares/_detail.html b/manila_ui/dashboards/project/shares/templates/shares/_detail.html
index 32f3018f..fa41ce32 100644
--- a/manila_ui/dashboards/project/shares/templates/shares/_detail.html
+++ b/manila_ui/dashboards/project/shares/templates/shares/_detail.html
@@ -57,10 +57,15 @@
{% endif %}
{% if share.share_network_id %}
- {% trans "Share network" %}
+ {% trans "Share Network" %}
{% url 'horizon:project:share_networks:share_network_detail' share.share_network_id as sn_url%}
{{ share.share_network_id }}
{% endif %}
+ {% if share.share_group_id %}
+ {% trans "Share Group" %}
+ {% url 'horizon:project:share_groups:detail' share.share_group_id as sg_url%}
+ {{ share.share_group_id }}
+ {% endif %}
{% trans "Mount snapshot support" %}
{{ share.mount_snapshot_support }}
{% trans "Created" %}
diff --git a/manila_ui/dashboards/project/shares/urls.py b/manila_ui/dashboards/project/shares/urls.py
index 37675922..4508e5a3 100644
--- a/manila_ui/dashboards/project/shares/urls.py
+++ b/manila_ui/dashboards/project/shares/urls.py
@@ -14,9 +14,9 @@
from django.conf import urls
-from manila_ui.api import manila
from manila_ui.dashboards.project.shares.replicas import views as replica_views
from manila_ui.dashboards.project.shares import views as shares_views
+from manila_ui import features
urlpatterns = [
@@ -58,7 +58,7 @@ urlpatterns = [
name='revert'),
]
-if manila.is_replication_enabled():
+if features.is_replication_enabled():
urlpatterns.extend([
urls.url(
r'^(?P[^/]+)/create_replica/$',
diff --git a/manila_ui/features.py b/manila_ui/features.py
new file mode 100644
index 00000000..b72de5c5
--- /dev/null
+++ b/manila_ui/features.py
@@ -0,0 +1,50 @@
+# 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.
+
+"""
+This module stores functions that return boolean values and say
+which manila features are enabled and which are disabled among those
+that are optional for manila UI.
+These functions are required mostly for complex features, which consist
+of more than one logical part in manila UI and requires appropriate logic
+change in more than 1 place. For example, to disable share groups feature
+we need to do following:
+- Remove 'share_groups' panel from 'share groups' panel group in each
+ dashboard.
+- Disable or do not register URLs for disabled features, so, no one
+ will be able to request disabled features knowing direct URL.
+- Add/remove buttons for other (not disabled) features that are related
+ to it somehow.
+"""
+
+from django.conf import settings
+from horizon.utils import memoized
+
+
+@memoized.memoized
+def is_share_groups_enabled():
+ manila_config = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {})
+ return manila_config.get('enable_share_groups', True)
+
+
+@memoized.memoized
+def is_replication_enabled():
+ manila_config = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {})
+ return manila_config.get('enable_replication', True)
+
+
+@memoized.memoized
+def is_migration_enabled():
+ manila_config = getattr(settings, 'OPENSTACK_MANILA_FEATURES', {})
+ return manila_config.get('enable_migration', True)
diff --git a/manila_ui/local/enabled/_9080_manila_admin_add_share_groups_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9080_manila_admin_add_share_groups_panel_to_share_panel_group.py
new file mode 100644
index 00000000..c0cefb12
--- /dev/null
+++ b/manila_ui/local/enabled/_9080_manila_admin_add_share_groups_panel_to_share_panel_group.py
@@ -0,0 +1,25 @@
+# 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 manila_ui import features
+
+
+PANEL_DASHBOARD = 'admin'
+PANEL_GROUP = 'share'
+PANEL = 'share_groups'
+
+if features.is_share_groups_enabled():
+ ADD_PANEL = 'manila_ui.dashboards.admin.share_groups.panel.ShareGroups'
+else:
+ REMOVE_PANEL = not features.is_share_groups_enabled()
diff --git a/manila_ui/local/enabled/_9080_manila_project_add_share_groups_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9080_manila_project_add_share_groups_panel_to_share_panel_group.py
new file mode 100644
index 00000000..22979deb
--- /dev/null
+++ b/manila_ui/local/enabled/_9080_manila_project_add_share_groups_panel_to_share_panel_group.py
@@ -0,0 +1,25 @@
+# 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 manila_ui import features
+
+
+PANEL_DASHBOARD = 'project'
+PANEL_GROUP = 'share'
+PANEL = 'share_groups'
+
+if features.is_share_groups_enabled():
+ ADD_PANEL = 'manila_ui.dashboards.project.share_groups.panel.ShareGroups'
+else:
+ REMOVE_PANEL = not features.is_share_groups_enabled()
diff --git a/manila_ui/local/enabled/_9085_manila_admin_add_share_group_snapshots_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9085_manila_admin_add_share_group_snapshots_panel_to_share_panel_group.py
new file mode 100644
index 00000000..9967ba51
--- /dev/null
+++ b/manila_ui/local/enabled/_9085_manila_admin_add_share_group_snapshots_panel_to_share_panel_group.py
@@ -0,0 +1,26 @@
+# 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 manila_ui import features
+
+
+PANEL_DASHBOARD = 'admin'
+PANEL_GROUP = 'share'
+PANEL = 'share_group_snapshots'
+
+if features.is_share_groups_enabled():
+ ADD_PANEL = ('manila_ui.dashboards.admin.share_group_snapshots.panel.'
+ 'ShareGroupSnapshots')
+else:
+ REMOVE_PANEL = not features.is_share_groups_enabled()
diff --git a/manila_ui/local/enabled/_9085_manila_project_add_share_group_snapshots_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9085_manila_project_add_share_group_snapshots_panel_to_share_panel_group.py
new file mode 100644
index 00000000..152e8ae8
--- /dev/null
+++ b/manila_ui/local/enabled/_9085_manila_project_add_share_group_snapshots_panel_to_share_panel_group.py
@@ -0,0 +1,26 @@
+# 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 manila_ui import features
+
+
+PANEL_DASHBOARD = 'project'
+PANEL_GROUP = 'share'
+PANEL = 'share_group_snapshots'
+
+if features.is_share_groups_enabled():
+ ADD_PANEL = ('manila_ui.dashboards.project.share_group_snapshots.panel.'
+ 'ShareGroupSnapshots')
+else:
+ REMOVE_PANEL = not features.is_share_groups_enabled()
diff --git a/manila_ui/local/enabled/_9090_manila_admin_add_share_group_types_panel_to_share_panel_group.py b/manila_ui/local/enabled/_9090_manila_admin_add_share_group_types_panel_to_share_panel_group.py
new file mode 100644
index 00000000..2de37e55
--- /dev/null
+++ b/manila_ui/local/enabled/_9090_manila_admin_add_share_group_types_panel_to_share_panel_group.py
@@ -0,0 +1,26 @@
+# 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 manila_ui import features
+
+
+PANEL_DASHBOARD = 'admin'
+PANEL_GROUP = 'share'
+PANEL = 'share_group_types'
+
+if features.is_share_groups_enabled():
+ ADD_PANEL = (
+ 'manila_ui.dashboards.admin.share_group_types.panel.ShareGroupTypes')
+else:
+ REMOVE_PANEL = not features.is_share_groups_enabled()
diff --git a/manila_ui/local/local_settings.d/_90_manila_shares.py b/manila_ui/local/local_settings.d/_90_manila_shares.py
index 8e187f0a..95a6a45a 100644
--- a/manila_ui/local/local_settings.d/_90_manila_shares.py
+++ b/manila_ui/local/local_settings.d/_90_manila_shares.py
@@ -15,9 +15,11 @@
# The OPENSTACK_MANILA_FEATURES settings can be used to enable or disable
# the UI for the various services provided by Manila.
OPENSTACK_MANILA_FEATURES = {
+ 'enable_share_groups': True,
'enable_replication': True,
'enable_migration': True,
'enable_public_share_type_creation': True,
+ 'enable_public_share_group_type_creation': True,
'enable_public_shares': True,
'enabled_share_protocols': ['NFS', 'CIFS', 'GlusterFS', 'HDFS', 'CephFS',
'MapRFS'],
diff --git a/manila_ui/tests/api/test_manila.py b/manila_ui/tests/api/test_manila.py
index dbe94337..a5cef1d0 100644
--- a/manila_ui/tests/api/test_manila.py
+++ b/manila_ui/tests/api/test_manila.py
@@ -26,6 +26,37 @@ class ManilaApiTests(base.APITestCase):
super(self.__class__, self).setUp()
self.id = "fake_id"
+ @ddt.data((None, True), ("some_fake_sg_id", False))
+ @ddt.unpack
+ def test_share_create(self, sg_id, is_public):
+ kwargs = {
+ "share_network": "fake_sn",
+ "snapshot_id": "fake_snapshot_id",
+ "metadata": {"k1": "v1", "k2": "v2"},
+ "share_type": "fake_st",
+ "is_public": is_public,
+ "availability_zone": "fake_az",
+ "share_group_id": sg_id,
+ }
+ size = 5
+ name = "fake_name"
+ desc = "fake_description"
+ proto = "fake_share_protocol"
+
+ api.share_create(self.request, size, name, desc, proto, **kwargs)
+
+ self.manilaclient.shares.create.assert_called_once_with(
+ proto, size, name=name, description=desc, **kwargs)
+
+ @ddt.data(None, "some_fake_sg_id")
+ def test_share_delete(self, sg_id):
+ s_id = "fake_share_id"
+
+ api.share_delete(self.request, s_id, sg_id)
+
+ self.manilaclient.shares.delete.assert_called_once_with(
+ s_id, share_group_id=sg_id)
+
def test_list_share_export_locations(self):
api.share_export_location_list(self.request, self.id)
@@ -346,3 +377,294 @@ class ManilaApiTests(base.APITestCase):
mock_sn_create = self.manilaclient.share_networks.create
mock_sn_create.assert_called_once_with(**expected_kwargs)
+
+ # Share groups tests
+
+ def test_share_group_create(self):
+ name = "fake_sg_name"
+ kwargs = {
+ "description": "fake_desc",
+ "share_group_type": "fake_sg_type",
+ "share_types": ["fake", "list", "of", "fake", "share", "types"],
+ "share_network": "fake_sn",
+ "source_share_group_snapshot": "fake_source_share_group_snapshot",
+ "availability_zone": "fake_az",
+ }
+
+ result = api.share_group_create(self.request, name, **kwargs)
+
+ self.assertEqual(
+ self.manilaclient.share_groups.create.return_value, result)
+ self.manilaclient.share_groups.create.assert_called_once_with(
+ name=name, **kwargs)
+
+ def test_share_group_get(self):
+ sg = "fake_share_group"
+
+ result = api.share_group_get(self.request, sg)
+
+ self.assertEqual(
+ self.manilaclient.share_groups.get.return_value, result)
+ self.manilaclient.share_groups.get.assert_called_once_with(sg)
+
+ def test_share_group_update(self):
+ sg = "fake_share_group"
+ name = "fake_name"
+ desc = "fake_desc"
+
+ result = api.share_group_update(self.request, sg, name, desc)
+
+ self.assertEqual(
+ self.manilaclient.share_groups.update.return_value, result)
+ self.manilaclient.share_groups.update.assert_called_once_with(
+ sg, name=name, description=desc)
+
+ @ddt.data({}, {"force": True}, {"force": False})
+ def test_share_group_delete(self, kwargs):
+ sg = 'fake_share_group'
+
+ api.share_group_delete(self.request, sg, **kwargs)
+
+ self.manilaclient.share_groups.delete.assert_called_once_with(
+ sg, force=kwargs.get("force", False))
+
+ def test_share_group_reset_state(self):
+ sg = 'fake_share_group'
+ state = 'fake_state'
+
+ result = api.share_group_reset_state(self.request, sg, state)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_groups.reset_state.return_value,
+ result)
+ self.manilaclient.share_groups.reset_state.assert_called_once_with(
+ sg, state)
+
+ @ddt.data(
+ {},
+ {"detailed": True},
+ {"detailed": False},
+ {"search_opts": {"foo": "bar"}},
+ {"sort_key": "id", "sort_dir": "asc"},
+ )
+ def test_share_group_list(self, kwargs):
+ result = api.share_group_list(self.request, **kwargs)
+
+ self.assertEqual(
+ self.manilaclient.share_groups.list.return_value, result)
+ self.manilaclient.share_groups.list.assert_called_once_with(
+ detailed=kwargs.get("detailed", True),
+ search_opts=kwargs.get("search_opts"),
+ sort_key=kwargs.get("sort_key"),
+ sort_dir=kwargs.get("sort_dir"),
+ )
+
+ # Share Group Snapshots tests
+
+ def test_share_group_snapshot_create(self):
+ sg = 'fake_share_group'
+ name = 'fake_name'
+ desc = 'fake_description'
+
+ result = api.share_group_snapshot_create(self.request, sg, name, desc)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_snapshots.create.return_value,
+ result)
+ self.manilaclient.share_group_snapshots.create.assert_called_once_with(
+ share_group=sg, name=name, description=desc)
+
+ def test_share_group_snapshot_get(self):
+ sgs = 'fake_share_group_snapshot'
+
+ result = api.share_group_snapshot_get(self.request, sgs)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_snapshots.get.return_value, result)
+ self.manilaclient.share_group_snapshots.get.assert_called_once_with(
+ sgs)
+
+ def test_share_group_snapshot_update(self):
+ sgs = 'fake_share_group_snapshot'
+ name = 'fake_name'
+ desc = 'fake_description'
+
+ result = api.share_group_snapshot_update(self.request, sgs, name, desc)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_snapshots.update.return_value,
+ result)
+ self.manilaclient.share_group_snapshots.update.assert_called_once_with(
+ sgs, name=name, description=desc)
+
+ @ddt.data(True, False)
+ def test_share_group_snapshot_delete(self, force):
+ sgs = 'fake_share_group_snapshot'
+
+ result = api.share_group_snapshot_delete(self.request, sgs, force)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_snapshots.delete.return_value,
+ result)
+ self.manilaclient.share_group_snapshots.delete.assert_called_once_with(
+ sgs, force=force)
+
+ def test_share_group_snapshot_reset_state(self):
+ sgs = 'fake_share_group_snapshot'
+ state = 'fake_state'
+
+ result = api.share_group_snapshot_reset_state(self.request, sgs, state)
+
+ rs_method = self.manilaclient.share_group_snapshots.reset_state
+ self.assertIsNotNone(result)
+ self.assertEqual(rs_method.return_value, result)
+ rs_method.assert_called_once_with(sgs, state)
+
+ @ddt.data(
+ {},
+ {'detailed': False},
+ {'detailed': True, 'search_opts': 'foo',
+ 'sort_key': 'k', 'sort_dir': 'v'},
+ )
+ def test_share_group_snapshot_list(self, kwargs):
+ result = api.share_group_snapshot_list(self.request, **kwargs)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_snapshots.list.return_value,
+ result)
+ self.manilaclient.share_group_snapshots.list.assert_called_once_with(
+ detailed=kwargs.get('detailed', True),
+ search_opts=kwargs.get('search_opts'),
+ sort_key=kwargs.get('sort_key'),
+ sort_dir=kwargs.get('sort_dir'))
+
+ # Share Group Types tests
+
+ @ddt.data(
+ {'is_public': True},
+ {'is_public': False, 'group_specs': {'foo': 'bar'}},
+ {'group_specs': {}},
+ )
+ def test_share_group_type_create(self, kwargs):
+ name = 'fake_sgt_name'
+ sts = ['fake', 'list', 'of', 'share', 'types']
+
+ result = api.share_group_type_create(self.request, name, sts, **kwargs)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_types.create.return_value,
+ result)
+ self.manilaclient.share_group_types.create.assert_called_once_with(
+ name=name, share_types=sts,
+ is_public=kwargs.get('is_public', False),
+ group_specs=kwargs.get('group_specs'))
+
+ def test_share_group_type_get(self):
+ sgt = "fake_sgt"
+
+ result = api.share_group_type_get(self.request, sgt)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_types.get.return_value, result)
+ self.manilaclient.share_group_types.get.assert_called_once_with(sgt)
+
+ @ddt.data(True, False)
+ def test_share_group_type_list(self, show_all):
+ result = api.share_group_type_list(self.request, show_all)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_types.list.return_value, result)
+ self.manilaclient.share_group_types.list.assert_called_once_with(
+ show_all=show_all)
+
+ def test_share_group_type_delete(self):
+ sgt = 'fake_share_group_type'
+
+ result = api.share_group_type_delete(self.request, sgt)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_types.delete.return_value, result)
+ self.manilaclient.share_group_types.delete.assert_called_once_with(sgt)
+
+ def test_share_group_type_access_list(self):
+ sgt = 'fake_share_group_type'
+
+ result = api.share_group_type_access_list(self.request, sgt)
+
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ self.manilaclient.share_group_type_access.list.return_value,
+ result)
+ self.manilaclient.share_group_type_access.list.assert_called_once_with(
+ sgt)
+
+ def test_share_group_type_access_add(self):
+ sgt = 'fake_share_group_type'
+ project = 'fake_project'
+
+ result = api.share_group_type_access_add(self.request, sgt, project)
+
+ sgt_access = self.manilaclient.share_group_type_access
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ sgt_access.add_project_access.return_value, result)
+ sgt_access.add_project_access.assert_called_once_with(sgt, project)
+
+ def test_share_group_type_access_remove(self):
+ sgt = 'fake_share_group_type'
+ project = 'fake_project'
+
+ result = api.share_group_type_access_remove(self.request, sgt, project)
+
+ sgt_access = self.manilaclient.share_group_type_access
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ sgt_access.remove_project_access.return_value, result)
+ sgt_access.remove_project_access.assert_called_once_with(sgt, project)
+
+ def test_share_group_type_set_specs(self):
+ sgt = 'fake_share_group_type'
+ group_specs = 'fake_specs'
+
+ result = api.share_group_type_set_specs(self.request, sgt, group_specs)
+
+ get_method = self.manilaclient.share_group_types.get
+ self.assertIsNotNone(result)
+ self.assertEqual(get_method.return_value.set_keys.return_value, result)
+ get_method.assert_called_once_with(sgt)
+ get_method.return_value.set_keys.assert_called_once_with(group_specs)
+
+ def test_share_group_type_unset_specs(self):
+ sgt = 'fake_share_group_type'
+ keys = ['fake', 'list', 'of', 'keys', 'for', 'deletion']
+
+ result = api.share_group_type_unset_specs(self.request, sgt, keys)
+
+ get_method = self.manilaclient.share_group_types.get
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ get_method.return_value.unset_keys.return_value, result)
+ get_method.assert_called_once_with(sgt)
+ get_method.return_value.unset_keys.assert_called_once_with(keys)
+
+ def test_share_group_type_get_specs(self):
+ sgt = 'fake_share_group_type'
+
+ result = api.share_group_type_get_specs(self.request, sgt)
+
+ get_method = self.manilaclient.share_group_types.get
+ self.assertIsNotNone(result)
+ self.assertEqual(
+ get_method.return_value.get_keys.return_value, result)
+ get_method.assert_called_once_with(sgt)
+ get_method.return_value.get_keys.assert_called_once_with()
diff --git a/manila_ui/tests/dashboards/admin/share_group_snapshots/__init__.py b/manila_ui/tests/dashboards/admin/share_group_snapshots/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/tests/dashboards/admin/share_group_snapshots/tests.py b/manila_ui/tests/dashboards/admin/share_group_snapshots/tests.py
new file mode 100644
index 00000000..fe5f5f1a
--- /dev/null
+++ b/manila_ui/tests/dashboards/admin/share_group_snapshots/tests.py
@@ -0,0 +1,200 @@
+# Copyright (c) 2017 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.urlresolvers import reverse
+import mock
+
+from manila_ui.api import manila as api_manila
+from manila_ui.tests.dashboards.project import test_data
+from manila_ui.tests import helpers as test
+
+INDEX_URL = reverse('horizon:admin:share_group_snapshots:index')
+
+
+@ddt.ddt
+class ShareGroupSnapshotTests(test.BaseAdminViewTests):
+
+ def setUp(self):
+ super(self.__class__, self).setUp()
+ self.sg = test_data.share_group
+ self.sg_nl = test_data.share_group_nameless
+ self.sg_dhss_true = test_data.share_group_dhss_true
+ self.sgs = test_data.share_group_snapshot
+ self.sgs_nl = test_data.share_group_snapshot_nameless
+
+ def test_share_group_snapshots_list_get(self):
+ share_groups = [self.sg, self.sg_nl, self.sg_dhss_true]
+ sgss = [self.sgs, self.sgs_nl]
+ self.mock_object(
+ api_manila, 'share_group_snapshot_list',
+ mock.Mock(return_value=sgss))
+ self.mock_object(
+ api_manila, 'share_group_list',
+ mock.Mock(return_value=share_groups))
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertStatusCode(res, 200)
+ self.assertMessageCount(error=0)
+ self.assertNoMessages()
+ api_manila.share_group_snapshot_list.assert_called_once_with(
+ mock.ANY, search_opts={'all_tenants': True})
+ api_manila.share_group_list.assert_called_once_with(mock.ANY)
+ self.assertTemplateUsed(res, 'admin/share_group_snapshots/index.html')
+ self.assertContains(res, "Share Group Snapshots ")
+ self.assertContains(
+ res, 'Delete Share Group Snapshot', len(sgss))
+ self.assertContains(res, 'Delete Share Group Snapshots', 1)
+ for sgs in sgss:
+ self.assertContains(
+ res,
+ 'href="/admin/share_group_snapshots/%s/reset_status"> '
+ 'Reset status' % sgs.id, 1)
+ self.assertContains(
+ res, 'value="share_group_snapshots__delete__%s' % sgs.id, 1)
+ self.assertContains(
+ res,
+ '%(name)s ' % {
+ 'id': self.sgs.id, 'name': self.sgs.name}, 1)
+ self.assertContains(
+ res,
+ '- ' % self.sgs_nl.id, 1)
+
+ def test_share_group_snapshots_list_error_get(self):
+ self.mock_object(
+ api_manila, 'share_group_snapshot_list',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertStatusCode(res, 200)
+ self.assertTemplateUsed(res, 'admin/share_group_snapshots/index.html')
+ self.assertContains(res, "Share Group Snapshots ")
+ self.assertContains(res, 'Delete Share Group Snapshot', 0)
+ self.assertContains(res, 'Delete Share Group Snapshots', 0)
+ self.assertNoMessages()
+
+ @ddt.data(
+ test_data.share_group_snapshot,
+ test_data.share_group_snapshot_nameless,
+ )
+ def test_share_group_snapshot_detailed_page_get(self, sgs):
+ sg = test_data.share_group_nameless
+ url = reverse(
+ 'horizon:admin:share_group_snapshots:detail', args=[sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(return_value=sgs))
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=sg))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(res, 'admin/share_group_snapshots/detail.html')
+ self.assertStatusCode(res, 200)
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, sgs.id)
+ api_manila.share_group_get.assert_called_once_with(
+ mock.ANY, sgs.share_group_id)
+
+ def test_share_group_snapshot_detailed_page_error_get(self):
+ url = reverse(
+ 'horizon:admin:share_group_snapshots:detail', args=[self.sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(url)
+
+ self.assertTemplateNotUsed(
+ res, 'admin/share_group_snapshots/detail.html')
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(error=1)
+ self.assertMessageCount(success=0)
+
+ def test_share_group_snapshot_reset_status_get(self):
+ url = reverse(
+ 'horizon:admin:share_group_snapshots:reset_status',
+ args=[self.sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(return_value=self.sgs))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(
+ res, 'admin/share_group_snapshots/reset_status.html')
+ self.assertStatusCode(res, 200)
+ self.assertMessageCount(error=0)
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+
+ def test_share_group_snapshot_reset_status_error_get(self):
+ url = reverse(
+ 'horizon:admin:share_group_snapshots:reset_status',
+ args=[self.sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(url)
+
+ self.assertTemplateNotUsed(
+ res, 'admin/share_group_snapshots/reset_status.html')
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(error=1)
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+
+ def test_share_group_snapshot_reset_status_post(self):
+ url = reverse(
+ 'horizon:admin:share_group_snapshots:reset_status',
+ args=[self.sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(return_value=self.sgs))
+ self.mock_object(api_manila, 'share_group_snapshot_reset_state')
+ form_data = {'status': 'error'}
+
+ res = self.client.post(url, form_data)
+
+ self.assertTemplateNotUsed(
+ res, 'admin/share_group_snapshots/reset_status.html')
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(error=0)
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+ api_manila.share_group_snapshot_reset_state.assert_called_once_with(
+ mock.ANY, self.sgs.id, form_data['status'])
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ def test_share_group_snapshot_delete_post(self):
+ data = {'action': 'share_group_snapshots__delete__%s' % self.sgs.id}
+ self.mock_object(api_manila, "share_group_snapshot_delete")
+ self.mock_object(
+ api_manila, "share_group_snapshot_list",
+ mock.Mock(return_value=[self.sgs]))
+
+ res = self.client.post(INDEX_URL, data)
+
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(success=1)
+ api_manila.share_group_snapshot_delete.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+ api_manila.share_group_snapshot_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_group_types/__init__.py b/manila_ui/tests/dashboards/admin/share_group_types/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/tests/dashboards/admin/share_group_types/tests.py b/manila_ui/tests/dashboards/admin/share_group_types/tests.py
new file mode 100644
index 00000000..927bd21d
--- /dev/null
+++ b/manila_ui/tests/dashboards/admin/share_group_types/tests.py
@@ -0,0 +1,142 @@
+# Copyright (c) 2017 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 copy
+
+from django.core.urlresolvers import reverse
+import mock
+
+from manila_ui.api import manila as api_manila
+from manila_ui.tests.dashboards.project import test_data
+from manila_ui.tests import helpers as test
+
+INDEX_URL = reverse('horizon:admin:share_group_types:index')
+
+
+class ShareGroupTypeTests(test.BaseAdminViewTests):
+
+ def setUp(self):
+ super(self.__class__, self).setUp()
+ self.sgt = copy.copy(test_data.share_group_type)
+ self.sgt_p = copy.copy(test_data.share_group_type_private)
+ self.url = reverse(
+ 'horizon:admin:share_group_types:update', args=[self.sgt.id])
+ self.mock_object(
+ api_manila, "share_group_type_get",
+ mock.Mock(return_value=self.sgt))
+
+ def test_list_share_group_types_get(self):
+ sgts = [self.sgt, self.sgt_p]
+ self.mock_object(
+ api_manila, "share_group_type_list", mock.Mock(return_value=sgts))
+ self.mock_object(
+ api_manila, "share_type_list",
+ mock.Mock(return_value=[
+ test_data.share_type, test_data.share_type_private]))
+ self.mock_object(
+ api_manila, "share_group_type_get",
+ mock.Mock(side_effect=lambda req, sgt_id: (
+ self.sgt
+ if sgt_id in (self.sgt, self.sgt.id, self.sgt.name) else
+ self.sgt_p)))
+
+ res = self.client.get(INDEX_URL)
+
+ api_manila.share_group_type_list.assert_called_once_with(mock.ANY)
+ api_manila.share_type_list.assert_called_once_with(mock.ANY)
+ self.assertEqual(len(sgts), api_manila.share_group_type_get.call_count)
+ self.assertContains(res, "Share Group Types ")
+ self.assertContains(res, 'href="/admin/share_group_types/create"')
+ self.assertContains(res, 'Delete Share Group Type', len(sgts))
+ self.assertContains(res, 'Delete Share Group Types', 1)
+ for sgt in (self.sgt, self.sgt_p):
+ for s in (
+ 'href="/admin/share_group_types/%s/update">' % sgt.id,
+ 'value="share_group_types__delete__%s"' % sgt.id):
+ self.assertContains(res, s, 1, 200)
+ self.assertContains(
+ res,
+ 'href="/admin/share_group_types/%s/manage_access"' % sgt.id,
+ 0 if sgt.is_public else 1)
+
+ def test_create_share_group_type_post(self):
+ url = reverse('horizon:admin:share_group_types:create')
+ data = {
+ 'method': u'CreateShareGroupTypeForm',
+ 'is_public': True,
+ 'name': 'my_share_group_type',
+ 'share_types': ['foo'],
+ 'group_specs': 'key=value',
+ }
+ self.mock_object(
+ api_manila, "share_group_type_create",
+ mock.Mock(return_value=type(
+ 'SGT', (object, ), {'id': 'sgt_id', 'name': 'sgt_name'})))
+ self.mock_object(api_manila, "share_group_type_set_specs")
+ self.mock_object(
+ api_manila, "share_type_list",
+ mock.Mock(return_value=[
+ type('ST', (object, ), {'id': s_id, 'name': s_id + '_name'})
+ for s_id in ('foo', 'bar')
+ ]))
+
+ res = self.client.post(url, data)
+
+ api_manila.share_group_type_create.assert_called_once_with(
+ mock.ANY,
+ data['name'],
+ is_public=data['is_public'],
+ share_types=['foo'])
+ api_manila.share_type_list.assert_called_once_with(mock.ANY)
+ api_manila.share_group_type_set_specs.assert_called_once_with(
+ mock.ANY, 'sgt_id', {'key': 'value'})
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ def test_update_share_group_type_get(self):
+ res = self.client.get(self.url)
+
+ api_manila.share_group_type_get.assert_called_once_with(
+ mock.ANY, self.sgt.id)
+ self.assertNoMessages()
+ self.assertTemplateUsed(res, 'admin/share_group_types/update.html')
+
+ def test_update_share_group_type_post(self):
+ form_data = {'group_specs': 'foo=bar'}
+ data = {'group_specs': {'foo': 'bar'}}
+ self.mock_object(api_manila, "share_group_type_set_specs")
+
+ res = self.client.post(self.url, form_data)
+
+ api_manila.share_group_type_set_specs.assert_called_once_with(
+ mock.ANY, self.sgt.id, data['group_specs'])
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ def test_delete_share_group_type(self):
+ data = {'action': 'share_group_types__delete__%s' % self.sgt.id}
+ self.mock_object(api_manila, "share_group_type_delete")
+ self.mock_object(
+ api_manila, "share_group_type_list",
+ mock.Mock(return_value=[self.sgt]))
+ self.mock_object(
+ api_manila, "share_type_list",
+ mock.Mock(return_value=[test_data.share_type]))
+
+ res = self.client.post(INDEX_URL, data)
+
+ api_manila.share_group_type_delete.assert_called_once_with(
+ mock.ANY, self.sgt.id)
+ api_manila.share_group_type_list.assert_called_once_with(
+ mock.ANY)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
diff --git a/manila_ui/tests/dashboards/admin/share_groups/__init__.py b/manila_ui/tests/dashboards/admin/share_groups/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/tests/dashboards/admin/share_groups/tests.py b/manila_ui/tests/dashboards/admin/share_groups/tests.py
new file mode 100644
index 00000000..af0417a7
--- /dev/null
+++ b/manila_ui/tests/dashboards/admin/share_groups/tests.py
@@ -0,0 +1,186 @@
+# Copyright (c) 2017 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.urlresolvers import reverse
+import mock
+
+from manila_ui.api import manila as api_manila
+from manila_ui.tests.dashboards.project import test_data
+from manila_ui.tests import helpers as test
+
+INDEX_URL = reverse('horizon:admin:share_groups:index')
+
+
+@ddt.ddt
+class ShareGroupTests(test.BaseAdminViewTests):
+
+ def setUp(self):
+ super(self.__class__, self).setUp()
+ self.sg = test_data.share_group
+ self.sg_nl = test_data.share_group_nameless
+ self.sg_dhss_true = test_data.share_group_dhss_true
+
+ def test_share_groups_list_get(self):
+ sgs = [self.sg, self.sg_nl, self.sg_dhss_true]
+ self.mock_object(
+ api_manila, 'share_group_list', mock.Mock(return_value=sgs))
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertStatusCode(res, 200)
+ api_manila.share_group_list.assert_called_once_with(
+ mock.ANY, detailed=True)
+ self.assertTemplateUsed(res, 'admin/share_groups/index.html')
+ self.assertContains(res, "Share Groups ")
+ self.assertContains(res, 'Delete Share Group', len(sgs))
+ self.assertContains(res, 'Delete Share Groups', 1)
+ for sg in sgs:
+ self.assertContains(
+ res,
+ 'href="/admin/share_groups/%s/reset_status"> '
+ 'Reset status' % sg.id, 1)
+ self.assertContains(
+ res, 'value="share_groups__delete__%s' % sg.id, 1)
+ self.assertContains(
+ res,
+ '%(name)s ' % {
+ 'id': sg.id, 'name': sg.name}, 1)
+ self.assertContains(
+ res,
+ '- ' % self.sg_nl.id, 1)
+ self.assertNoMessages()
+
+ def test_share_group_list_error_get(self):
+ self.mock_object(
+ api_manila, 'share_group_list',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertStatusCode(res, 200)
+ self.assertTemplateUsed(res, 'admin/share_groups/index.html')
+ self.assertContains(res, "Share Groups ")
+ self.assertContains(res, 'Delete Share Group', 0)
+ self.assertContains(res, 'Delete Share Groups', 0)
+ self.assertNoMessages()
+
+ @ddt.data(
+ test_data.share_group_nameless,
+ test_data.share_group_dhss_true,
+ )
+ def test_share_group_detailed_page_get(self, sg):
+ url = reverse('horizon:admin:share_groups:detail', args=[sg.id])
+ shares = [test_data.share, test_data.nameless_share]
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=sg))
+ self.mock_object(
+ api_manila, 'share_list', mock.Mock(return_value=shares))
+ self.mock_object(
+ api_manila, 'share_type_list', mock.Mock(return_value=[
+ test_data.share_type, test_data.share_type_dhss_true]))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(res, 'admin/share_groups/detail.html')
+ self.assertStatusCode(res, 200)
+ for share in shares:
+ data = {'id': share.id, 'name': share.name or share.id}
+ self.assertContains(
+ res, '%(name)s ' % data)
+
+ def test_share_group_detailed_page_error_get(self):
+ sg = test_data.share_group_dhss_true
+ url = reverse('horizon:admin:share_groups:detail', args=[sg.id])
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(side_effect=type(
+ 'CustomExc', (Exception, ), {})))
+
+ res = self.client.get(url)
+
+ self.assertTemplateNotUsed(res, 'admin/share_groups/detail.html')
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(error=1)
+ self.assertMessageCount(success=0)
+
+ def test_share_group_reset_status_get(self):
+ sg = test_data.share_group_dhss_true
+ url = reverse('horizon:admin:share_groups:reset_status', args=[sg.id])
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=sg))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(res, 'admin/share_groups/reset_status.html')
+ self.assertStatusCode(res, 200)
+ self.assertMessageCount(error=0)
+ api_manila.share_group_get.assert_called_once_with(mock.ANY, sg.id)
+
+ def test_share_group_reset_status_error_get(self):
+ sg = test_data.share_group_dhss_true
+ url = reverse('horizon:admin:share_groups:reset_status', args=[sg.id])
+ self.mock_object(
+ api_manila, 'share_group_get',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(url)
+
+ self.assertTemplateNotUsed(res, 'admin/share_groups/reset_status.html')
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(error=1)
+ api_manila.share_group_get.assert_called_once_with(mock.ANY, sg.id)
+
+ def test_share_group_reset_status_post(self):
+ sg = test_data.share_group_dhss_true
+ url = reverse('horizon:admin:share_groups:reset_status', args=[sg.id])
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=sg))
+ self.mock_object(api_manila, 'share_group_reset_state')
+ form_data = {'status': 'error'}
+
+ res = self.client.post(url, form_data)
+
+ self.assertTemplateNotUsed(res, 'admin/share_groups/reset_status.html')
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(error=0)
+ api_manila.share_group_get.assert_called_once_with(mock.ANY, sg.id)
+ api_manila.share_group_reset_state.assert_called_once_with(
+ mock.ANY, sg.id, form_data['status'])
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ def test_share_group_delete_post(self):
+ sg = test_data.share_group_dhss_true
+ data = {'action': 'share_groups__delete__%s' % sg.id}
+ self.mock_object(api_manila, "share_group_delete")
+ self.mock_object(
+ api_manila, "share_group_list", mock.Mock(return_value=[sg]))
+
+ res = self.client.post(INDEX_URL, data)
+
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(success=1)
+ api_manila.share_group_delete.assert_called_once_with(mock.ANY, sg.id)
+ api_manila.share_group_list.assert_called_once_with(
+ mock.ANY, detailed=True)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
diff --git a/manila_ui/tests/dashboards/admin/shares/tests.py b/manila_ui/tests/dashboards/admin/shares/tests.py
index 8a7dbcfe..487cf129 100644
--- a/manila_ui/tests/dashboards/admin/shares/tests.py
+++ b/manila_ui/tests/dashboards/admin/shares/tests.py
@@ -85,7 +85,8 @@ class SharesTests(test.BaseAdminViewTests):
res = self.client.post(url, formData)
api_keystone.tenant_list.assert_called_once_with(mock.ANY)
- api_manila.share_delete.assert_called_once_with(mock.ANY, share.id)
+ api_manila.share_delete.assert_called_once_with(
+ mock.ANY, share.id, share_group_id=share.share_group_id)
api_manila.share_list.assert_called_once_with(
mock.ANY, search_opts={'all_tenants': True})
api_manila.share_snapshot_list.assert_called_once_with(
diff --git a/manila_ui/tests/dashboards/project/share_group_snapshots/__init__.py b/manila_ui/tests/dashboards/project/share_group_snapshots/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/tests/dashboards/project/share_group_snapshots/tests.py b/manila_ui/tests/dashboards/project/share_group_snapshots/tests.py
new file mode 100644
index 00000000..a012cb71
--- /dev/null
+++ b/manila_ui/tests/dashboards/project/share_group_snapshots/tests.py
@@ -0,0 +1,295 @@
+# Copyright (c) 2017 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.urlresolvers import reverse
+import mock
+
+from manila_ui.api import manila as api_manila
+from manila_ui.tests.dashboards.project import test_data
+from manila_ui.tests import helpers as test
+
+INDEX_URL = reverse('horizon:project:share_group_snapshots:index')
+
+
+@ddt.ddt
+class ShareGroupSnapshotTests(test.TestCase):
+
+ def setUp(self):
+ super(self.__class__, self).setUp()
+ self.sg = test_data.share_group
+ self.sg_nl = test_data.share_group_nameless
+ self.sg_dhss_true = test_data.share_group_dhss_true
+ self.sgs = test_data.share_group_snapshot
+ self.sgs_nl = test_data.share_group_snapshot_nameless
+
+ def test_share_group_snapshots_list_get(self):
+ share_groups = [self.sg, self.sg_nl, self.sg_dhss_true]
+ sgss = [self.sgs, self.sgs_nl]
+ self.mock_object(
+ api_manila, 'share_group_snapshot_list',
+ mock.Mock(return_value=sgss))
+ self.mock_object(
+ api_manila, 'share_group_list',
+ mock.Mock(return_value=share_groups))
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertStatusCode(res, 200)
+ self.assertMessageCount(error=0)
+ self.assertNoMessages()
+ api_manila.share_group_snapshot_list.assert_called_once_with(
+ mock.ANY, search_opts={'all_tenants': True})
+ api_manila.share_group_list.assert_called_once_with(mock.ANY)
+ self.assertTemplateUsed(
+ res, 'project/share_group_snapshots/index.html')
+ self.assertContains(res, "Share Group Snapshots ")
+ self.assertContains(
+ res, 'Delete Share Group Snapshot', len(sgss))
+ self.assertContains(res, 'Delete Share Group Snapshots', 1)
+ for sgs in sgss:
+ self.assertContains(
+ res, 'value="share_group_snapshots__delete__%s' % sgs.id, 1)
+ self.assertContains(
+ res,
+ '%(name)s -' % self.sgs_nl.id, 1)
+
+ def test_share_group_snapshots_list_error_get(self):
+ self.mock_object(
+ api_manila, 'share_group_snapshot_list',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertStatusCode(res, 200)
+ self.assertTemplateUsed(
+ res, 'project/share_group_snapshots/index.html')
+ self.assertContains(res, "Share Group Snapshots ")
+ self.assertContains(res, 'Delete Share Group Snapshot', 0)
+ self.assertContains(res, 'Delete Share Group Snapshots', 0)
+ self.assertNoMessages()
+
+ @ddt.data(
+ test_data.share_group_snapshot,
+ test_data.share_group_snapshot_nameless,
+ )
+ def test_share_group_snapshot_detailed_page_get(self, sgs):
+ sg = test_data.share_group_nameless
+ url = reverse(
+ 'horizon:project:share_group_snapshots:detail', args=[sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(return_value=sgs))
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=sg))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(
+ res, 'project/share_group_snapshots/detail.html')
+ self.assertStatusCode(res, 200)
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, sgs.id)
+ api_manila.share_group_get.assert_called_once_with(
+ mock.ANY, sgs.share_group_id)
+
+ def test_share_group_snapshot_detailed_page_error_get(self):
+ url = reverse(
+ 'horizon:project:share_group_snapshots:detail', args=[self.sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(url)
+
+ self.assertTemplateNotUsed(
+ res, 'admin/share_group_snapshots/detail.html')
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(error=1)
+ self.assertMessageCount(success=0)
+
+ @ddt.data(
+ lambda *args, **kwargs: test_data.share_group,
+ type('CustomExc', (Exception, ), {}),
+ )
+ def test_share_group_snapshot_create_get(self, sg_get_side_effect):
+ url = reverse(
+ 'horizon:project:share_group_snapshots:create', args=[self.sg.id])
+ self.mock_object(
+ api_manila, 'share_group_get',
+ mock.Mock(side_effect=sg_get_side_effect))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(
+ res, 'project/share_group_snapshots/create.html')
+ self.assertStatusCode(res, 200)
+ self.assertNoMessages()
+ api_manila.share_group_get.assert_called_once_with(
+ mock.ANY, self.sg.id)
+ self.assertMessageCount(error=0)
+
+ def test_share_group_snapshot_create_post(self):
+ url = reverse(
+ 'horizon:project:share_group_snapshots:create', args=[self.sg.id])
+ form_data = {
+ 'method': 'CreateShareGroupSnapshotForm',
+ 'name': 'fake_SG_snapshot_name',
+ 'description': 'fake SG snapshot description',
+ 'share_group_id': self.sg.id,
+ }
+ self.mock_object(
+ api_manila, "share_group_snapshot_create",
+ mock.Mock(return_value=self.sgs))
+
+ res = self.client.post(url, form_data)
+
+ self.assertTemplateNotUsed(
+ res, 'project/share_group_snapshots/create.html')
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ self.assertMessageCount(error=0)
+ self.assertMessageCount(success=1)
+ api_manila.share_group_snapshot_create.assert_called_once_with(
+ mock.ANY, self.sg.id, form_data['name'], form_data['description'])
+
+ def test_share_group_snapshot_create_error_post(self):
+ url = reverse(
+ 'horizon:project:share_group_snapshots:create', args=[self.sg.id])
+ form_data = {
+ 'method': 'CreateShareGroupSnapshotForm',
+ 'name': 'fake_SG_snapshot_name',
+ 'description': 'fake SG snapshot description',
+ 'share_group_id': self.sg.id,
+ }
+ self.mock_object(
+ api_manila, "share_group_snapshot_create",
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.post(url, form_data)
+
+ self.assertTemplateNotUsed(
+ res, 'project/share_group_snapshots/create.html')
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ self.assertMessageCount(error=1)
+ self.assertMessageCount(success=0)
+ api_manila.share_group_snapshot_create.assert_called_once_with(
+ mock.ANY, self.sg.id, form_data['name'], form_data['description'])
+
+ def test_share_group_snapshot_update_get(self):
+ url = reverse(
+ 'horizon:project:share_group_snapshots:update', args=[self.sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(return_value=self.sgs))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(
+ res, 'project/share_group_snapshots/update.html')
+ self.assertStatusCode(res, 200)
+ self.assertNoMessages()
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+
+ def test_share_group_snapshot_update_error_get(self):
+ url = reverse(
+ 'horizon:project:share_group_snapshots:update', args=[self.sgs.id])
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(url)
+
+ self.assertTemplateNotUsed(
+ res, 'project/share_group_snapshots/update.html')
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ self.assertMessageCount(error=1)
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+
+ def test_share_group_snapshot_update_post(self):
+ self.mock_object(api_manila, "share_group_snapshot_update")
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(return_value=self.sgs))
+ form_data = {
+ 'method': 'UpdateShareGroupSnapshotForm',
+ 'name': self.sgs.name,
+ 'description': self.sgs.description,
+ }
+ url = reverse(
+ 'horizon:project:share_group_snapshots:update', args=[self.sgs.id])
+
+ res = self.client.post(url, form_data)
+
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ api_manila.share_group_snapshot_update.assert_called_once_with(
+ mock.ANY, self.sgs.id, form_data['name'], form_data['description'])
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+ self.assertMessageCount(error=0)
+ self.assertMessageCount(success=1)
+
+ def test_share_group_snapshot_update_error_post(self):
+ self.mock_object(
+ api_manila, "share_group_snapshot_update",
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+ self.mock_object(
+ api_manila, 'share_group_snapshot_get',
+ mock.Mock(return_value=self.sgs))
+ form_data = {
+ 'method': 'UpdateShareGroupSnapshotForm',
+ 'name': self.sgs.name,
+ 'description': self.sgs.description,
+ }
+ url = reverse(
+ 'horizon:project:share_group_snapshots:update', args=[self.sgs.id])
+
+ res = self.client.post(url, form_data)
+
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ api_manila.share_group_snapshot_update.assert_called_once_with(
+ mock.ANY, self.sgs.id, form_data['name'], form_data['description'])
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+ self.assertMessageCount(error=1)
+ self.assertMessageCount(success=0)
+
+ def test_share_group_snapshot_delete_post(self):
+ data = {'action': 'share_group_snapshots__delete__%s' % self.sgs.id}
+ self.mock_object(api_manila, "share_group_snapshot_delete")
+ self.mock_object(
+ api_manila, "share_group_snapshot_list",
+ mock.Mock(return_value=[self.sgs]))
+
+ res = self.client.post(INDEX_URL, data)
+
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(success=1)
+ api_manila.share_group_snapshot_delete.assert_called_once_with(
+ mock.ANY, self.sgs.id)
+ api_manila.share_group_snapshot_list.assert_called_once_with(
+ mock.ANY, search_opts={'all_tenants': True})
+ self.assertRedirectsNoFollow(res, INDEX_URL)
diff --git a/manila_ui/tests/dashboards/project/share_groups/__init__.py b/manila_ui/tests/dashboards/project/share_groups/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/manila_ui/tests/dashboards/project/share_groups/tests.py b/manila_ui/tests/dashboards/project/share_groups/tests.py
new file mode 100644
index 00000000..d1e9b4f6
--- /dev/null
+++ b/manila_ui/tests/dashboards/project/share_groups/tests.py
@@ -0,0 +1,383 @@
+# Copyright (c) 2017 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.urlresolvers import reverse
+import mock
+
+from manila_ui.api import manila as api_manila
+from manila_ui.tests.dashboards.project import test_data
+from manila_ui.tests import helpers as test
+
+INDEX_URL = reverse('horizon:project:share_groups:index')
+
+
+@ddt.ddt
+class ShareGroupTests(test.TestCase):
+
+ def setUp(self):
+ super(self.__class__, self).setUp()
+ self.sg = test_data.share_group
+ self.sg_nl = test_data.share_group_nameless
+ self.sg_dhss_true = test_data.share_group_dhss_true
+
+ def test_share_groups_list_get(self):
+ sgs = [self.sg, self.sg_nl, self.sg_dhss_true]
+ self.mock_object(
+ api_manila, 'share_group_list', mock.Mock(return_value=sgs))
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertStatusCode(res, 200)
+ api_manila.share_group_list.assert_called_once_with(
+ mock.ANY, detailed=True)
+ self.assertTemplateUsed(res, 'project/share_groups/index.html')
+ self.assertContains(res, "Share Groups ")
+ self.assertContains(res, 'Delete Share Group', len(sgs))
+ self.assertContains(res, 'Delete Share Groups', 1)
+ for sg in sgs:
+ self.assertContains(
+ res, 'value="share_groups__delete__%s' % sg.id, 1)
+ self.assertContains(
+ res,
+ '%(name)s ' % {
+ 'id': sg.id, 'name': sg.name}, 1)
+ self.assertContains(
+ res,
+ '- ' % self.sg_nl.id, 1)
+ self.assertNoMessages()
+
+ def test_share_group_list_error_get(self):
+ self.mock_object(
+ api_manila, 'share_group_list',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(INDEX_URL)
+
+ self.assertStatusCode(res, 200)
+ self.assertTemplateUsed(res, 'project/share_groups/index.html')
+ self.assertContains(res, "Share Groups ")
+ self.assertContains(res, 'Delete Share Group', 0)
+ self.assertContains(res, 'Delete Share Groups', 0)
+ self.assertNoMessages()
+
+ @ddt.data(
+ test_data.share_group_nameless,
+ test_data.share_group_dhss_true,
+ )
+ def test_share_group_detailed_page_get(self, sg):
+ url = reverse('horizon:project:share_groups:detail', args=[sg.id])
+ shares = [test_data.share, test_data.nameless_share]
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=sg))
+ self.mock_object(
+ api_manila, 'share_list', mock.Mock(return_value=shares))
+ self.mock_object(
+ api_manila, 'share_type_list', mock.Mock(return_value=[
+ test_data.share_type, test_data.share_type_dhss_true]))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(res, 'project/share_groups/detail.html')
+ self.assertStatusCode(res, 200)
+ for share in shares:
+ data = {'id': share.id, 'name': share.name or share.id}
+ self.assertContains(
+ res, '%(name)s ' % data)
+
+ def test_share_group_detailed_page_error_get(self):
+ url = reverse('horizon:project:share_groups:detail', args=[self.sg.id])
+ self.mock_object(
+ api_manila, 'share_group_get',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(url)
+
+ self.assertTemplateNotUsed(res, 'project/share_groups/detail.html')
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(error=1)
+ self.assertMessageCount(success=0)
+
+ def test_share_group_update_get(self):
+ url = reverse('horizon:project:share_groups:update', args=[self.sg.id])
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=self.sg))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(res, 'project/share_groups/update.html')
+ self.assertStatusCode(res, 200)
+ self.assertNoMessages()
+ api_manila.share_group_get.assert_called_once_with(
+ mock.ANY, self.sg.id)
+
+ def test_share_group_update_error_get(self):
+ url = reverse('horizon:project:share_groups:update', args=[self.sg.id])
+ self.mock_object(
+ api_manila, 'share_group_get',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+
+ res = self.client.get(url)
+
+ self.assertTemplateNotUsed(res, 'project/share_groups/update.html')
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ self.assertMessageCount(error=1)
+ api_manila.share_group_get.assert_called_once_with(
+ mock.ANY, self.sg.id)
+
+ def test_share_group_update_post(self):
+ self.mock_object(api_manila, "share_group_update")
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=self.sg))
+ form_data = {
+ 'method': 'UpdateShareGroupForm',
+ 'name': self.sg.name,
+ 'description': self.sg.description,
+ }
+ url = reverse('horizon:project:share_groups:update', args=[self.sg.id])
+
+ res = self.client.post(url, form_data)
+
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ api_manila.share_group_update.assert_called_once_with(
+ mock.ANY, self.sg.id, form_data['name'], form_data['description'])
+ api_manila.share_group_get.assert_called_once_with(
+ mock.ANY, self.sg.id)
+ self.assertMessageCount(error=0)
+ self.assertMessageCount(success=1)
+
+ def test_share_group_update_error_post(self):
+ self.mock_object(
+ api_manila, 'share_group_update',
+ mock.Mock(side_effect=type('CustomExc', (Exception, ), {})))
+ self.mock_object(
+ api_manila, 'share_group_get', mock.Mock(return_value=self.sg))
+ form_data = {
+ 'method': 'UpdateShareGroupForm',
+ 'name': self.sg.name,
+ 'description': self.sg.description,
+ }
+ url = reverse('horizon:project:share_groups:update', args=[self.sg.id])
+
+ res = self.client.post(url, form_data)
+
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ api_manila.share_group_update.assert_called_once_with(
+ mock.ANY, self.sg.id, form_data['name'], form_data['description'])
+ api_manila.share_group_get.assert_called_once_with(
+ mock.ANY, self.sg.id)
+ self.assertMessageCount(error=1)
+ self.assertMessageCount(success=0)
+
+ def test_share_group_delete_post(self):
+ sg = test_data.share_group_dhss_true
+ data = {'action': 'share_groups__delete__%s' % sg.id}
+ self.mock_object(api_manila, "share_group_delete")
+ self.mock_object(
+ api_manila, "share_group_list", mock.Mock(return_value=[sg]))
+
+ res = self.client.post(INDEX_URL, data)
+
+ self.assertStatusCode(res, 302)
+ self.assertMessageCount(success=1)
+ api_manila.share_group_delete.assert_called_once_with(mock.ANY, sg.id)
+ api_manila.share_group_list.assert_called_once_with(
+ mock.ANY, detailed=True)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+
+ def test_share_group_create_get(self):
+ url = reverse('horizon:project:share_groups:create')
+ self.mock_object(
+ api_manila, 'share_group_type_list',
+ mock.Mock(return_value=[
+ test_data.share_group_type,
+ test_data.share_group_type_private,
+ test_data.share_group_type_dhss_true,
+ ]))
+ self.mock_object(
+ api_manila, 'share_group_snapshot_list',
+ mock.Mock(return_value=[
+ test_data.share_group_snapshot,
+ test_data.share_group_snapshot_nameless,
+ ]))
+ self.mock_object(
+ api_manila, 'share_network_list',
+ mock.Mock(return_value=[
+ test_data.inactive_share_network,
+ test_data.active_share_network,
+ ]))
+ self.mock_object(
+ api_manila, 'share_type_list',
+ mock.Mock(return_value=[
+ test_data.share_type,
+ test_data.share_type_private,
+ test_data.share_type_dhss_true,
+ ]))
+ self.mock_object(
+ api_manila, 'availability_zone_list',
+ mock.Mock(return_value=[]))
+
+ res = self.client.get(url)
+
+ self.assertTemplateUsed(res, 'project/share_groups/create.html')
+ self.assertStatusCode(res, 200)
+ self.assertNoMessages()
+ self.assertMessageCount(error=0)
+ api_manila.share_group_type_list.assert_called_once_with(mock.ANY)
+ api_manila.share_group_snapshot_list.assert_called_once_with(mock.ANY)
+ api_manila.share_network_list.assert_called_once_with(mock.ANY)
+ api_manila.share_type_list.assert_called_once_with(mock.ANY)
+ api_manila.availability_zone_list.assert_called_once_with(mock.ANY)
+
+ def test_share_group_create_post(self):
+ url = reverse('horizon:project:share_groups:create')
+ form_data = {
+ 'method': 'CreateShareGroupForm',
+ 'name': 'fake_sg_name',
+ 'description': 'fake SG description',
+ 'sgt': test_data.share_group_type.id,
+ 'share-type-choices-%s' % test_data.share_group_type.id: (
+ test_data.share_group_type.share_types[0]),
+ 'availability_zone': '',
+ 'share_network': test_data.inactive_share_network.id,
+ 'snapshot': '',
+ }
+ self.mock_object(
+ api_manila, "share_group_create", mock.Mock(return_value=self.sg))
+ self.mock_object(
+ api_manila, 'share_group_type_list',
+ mock.Mock(return_value=[
+ test_data.share_group_type,
+ test_data.share_group_type_private,
+ test_data.share_group_type_dhss_true,
+ ]))
+ self.mock_object(
+ api_manila, 'share_group_snapshot_list',
+ mock.Mock(return_value=[
+ test_data.share_group_snapshot,
+ test_data.share_group_snapshot_nameless,
+ ]))
+ self.mock_object(
+ api_manila, 'share_network_list',
+ mock.Mock(return_value=[
+ test_data.inactive_share_network,
+ test_data.active_share_network,
+ ]))
+ self.mock_object(
+ api_manila, 'share_type_list',
+ mock.Mock(return_value=[
+ test_data.share_type,
+ test_data.share_type_private,
+ test_data.share_type_dhss_true,
+ ]))
+ self.mock_object(
+ api_manila, 'availability_zone_list',
+ mock.Mock(return_value=[]))
+
+ res = self.client.post(url, form_data)
+
+ self.assertTemplateNotUsed(res, 'project/share_groups/create.html')
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ api_manila.share_group_type_list.assert_called_once_with(mock.ANY)
+ api_manila.share_group_snapshot_list.assert_called_once_with(mock.ANY)
+ api_manila.share_network_list.assert_called_once_with(mock.ANY)
+ api_manila.share_type_list.assert_called_once_with(mock.ANY)
+ api_manila.availability_zone_list.assert_called_once_with(mock.ANY)
+ api_manila.share_group_create.assert_called_once_with(
+ mock.ANY,
+ name=form_data['name'],
+ description=form_data['description'],
+ share_group_type=test_data.share_group_type.id,
+ share_types=[test_data.share_type.id],
+ share_network=form_data['share_network'],
+ source_share_group_snapshot=None,
+ availability_zone=form_data['availability_zone'])
+
+ def test_share_group_create_from_snapshot_post(self):
+ url = reverse('horizon:project:share_groups:create')
+ form_data = {
+ 'method': 'CreateShareGroupForm',
+ 'name': 'fake_sg_name',
+ 'description': 'fake SG description',
+ 'sgt': '',
+ 'snapshot': test_data.share_group_snapshot.id,
+ }
+ self.mock_object(
+ api_manila, "share_group_snapshot_get",
+ mock.Mock(return_value=test_data.share_group_snapshot))
+ self.mock_object(
+ api_manila, "share_group_get", mock.Mock(return_value=self.sg_nl))
+ self.mock_object(
+ api_manila, "share_group_create", mock.Mock(return_value=self.sg))
+ self.mock_object(
+ api_manila, 'share_group_type_list',
+ mock.Mock(return_value=[
+ test_data.share_group_type,
+ test_data.share_group_type_private,
+ test_data.share_group_type_dhss_true,
+ ]))
+ self.mock_object(
+ api_manila, 'share_group_snapshot_list',
+ mock.Mock(return_value=[
+ test_data.share_group_snapshot,
+ test_data.share_group_snapshot_nameless,
+ ]))
+ self.mock_object(
+ api_manila, 'share_network_list',
+ mock.Mock(return_value=[
+ test_data.inactive_share_network,
+ test_data.active_share_network,
+ ]))
+ self.mock_object(
+ api_manila, 'share_type_list',
+ mock.Mock(return_value=[
+ test_data.share_type,
+ test_data.share_type_private,
+ test_data.share_type_dhss_true,
+ ]))
+ self.mock_object(
+ api_manila, 'availability_zone_list',
+ mock.Mock(return_value=[]))
+
+ res = self.client.post(url, form_data)
+
+ self.assertTemplateNotUsed(res, 'project/share_groups/create.html')
+ self.assertStatusCode(res, 302)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
+ api_manila.share_group_snapshot_list.assert_not_called()
+ api_manila.share_group_type_list.assert_not_called()
+ api_manila.share_network_list.assert_not_called()
+ api_manila.share_type_list.assert_not_called()
+ api_manila.availability_zone_list.assert_not_called()
+ api_manila.share_group_snapshot_get.assert_called_once_with(
+ mock.ANY, test_data.share_group_snapshot.id)
+ api_manila.share_group_create.assert_called_once_with(
+ mock.ANY,
+ name=form_data['name'],
+ description=form_data['description'],
+ share_group_type=test_data.share_group_type.id,
+ share_types=None,
+ share_network=None,
+ source_share_group_snapshot=test_data.share_group_snapshot.id,
+ availability_zone=None)
diff --git a/manila_ui/tests/dashboards/project/shares/tests.py b/manila_ui/tests/dashboards/project/shares/tests.py
index 99bf3648..4ba728c5 100644
--- a/manila_ui/tests/dashboards/project/shares/tests.py
+++ b/manila_ui/tests/dashboards/project/shares/tests.py
@@ -45,6 +45,9 @@ class ShareViewTests(test.APITestCase):
self.fake_share_type.get_keys = mock.Mock(
return_value={'driver_handles_share_servers': 'True'})
self.share = test_data.share
+ self.mock_object(
+ api_manila, "share_group_list",
+ mock.Mock(return_value=[]))
self.mock_object(
api_manila, "share_get", mock.Mock(return_value=self.share))
self.mock_object(
@@ -114,12 +117,14 @@ class ShareViewTests(test.APITestCase):
api_manila, "share_type_list",
mock.Mock(return_value=[self.fake_share_type, ]))
- self.client.post(url, formData)
+ res = self.client.post(url, formData)
+ self.assertRedirectsNoFollow(res, INDEX_URL)
api_manila.share_create.assert_called_once_with(
mock.ANY, size=formData['size'], name=formData['name'],
description=formData['description'], proto=formData['share_proto'],
- snapshot_id=None, is_public=False, share_network=share_net.id,
+ snapshot_id=None, is_public=False,
+ share_group_id=None, share_network=share_net.id,
metadata={}, share_type=formData['share_type'],
availability_zone=formData['availability_zone'])
api_manila.share_snapshot_list.assert_called_once_with(mock.ANY)
@@ -173,7 +178,7 @@ class ShareViewTests(test.APITestCase):
mock.ANY, size=formData['size'], name=formData['name'],
description=formData['description'], proto=formData['share_proto'],
snapshot_id=snapshot.id, is_public=False,
- share_network=share_net.id, metadata={},
+ share_group_id=None, share_network=share_net.id, metadata={},
share_type=formData['share_type'],
availability_zone=formData['availability_zone'])
self.assertRedirectsNoFollow(res, INDEX_URL)
@@ -185,6 +190,8 @@ class ShareViewTests(test.APITestCase):
self.mock_object(
api_manila, "share_network_list", mock.Mock(return_value=[]))
self.mock_object(api_manila, "share_delete")
+ self.mock_object(
+ api_manila, "share_get", mock.Mock(return_value=self.share))
self.mock_object(
api_manila, "share_list", mock.Mock(return_value=[self.share]))
@@ -194,8 +201,9 @@ class ShareViewTests(test.APITestCase):
api_manila.share_snapshot_list.assert_called_once_with(
mock.ANY, detailed=True)
api_manila.share_list.assert_called_with(mock.ANY)
+ api_manila.share_get.assert_called_with(mock.ANY, self.share.id)
api_manila.share_delete.assert_called_with(
- mock.ANY, self.share.id)
+ mock.ANY, self.share.id, share_group_id=self.share.share_group_id)
self.assertRedirectsNoFollow(res, INDEX_URL)
def test_detail_view(self):
@@ -669,6 +677,7 @@ class ShareViewTests(test.APITestCase):
metadata=utils.parse_str_meta(data['metadata'])[0],
name=data['name'],
proto=data['share_proto'],
+ share_group_id=None,
share_network=test_data.active_share_network.id,
share_type=data['share_type'],
size=data['size'],
diff --git a/manila_ui/tests/dashboards/project/test_data.py b/manila_ui/tests/dashboards/project/test_data.py
index 1476545a..50a103b0 100644
--- a/manila_ui/tests/dashboards/project/test_data.py
+++ b/manila_ui/tests/dashboards/project/test_data.py
@@ -32,6 +32,9 @@ except ImportError:
from manilaclient.v1 import shares
from manilaclient.v2 import share_export_locations
+from manilaclient.v2 import share_group_snapshots
+from manilaclient.v2 import share_group_types
+from manilaclient.v2 import share_groups
from manilaclient.v2 import share_instances
from manilaclient.v2 import share_replicas
from manilaclient.v2 import share_servers
@@ -59,6 +62,7 @@ share = shares.Share(
'share_network_id': '7f3d1c33-8d00-4511-29df-a2def31f3b5d',
'availability_zone': 'Test AZ',
'replication_type': 'readable',
+ 'share_group_id': 'fake_share_group_id',
'mount_snapshot_support': False})
nameless_share = shares.Share(
@@ -332,13 +336,136 @@ share_server_errored = share_servers.ShareServer(
share_type = share_types.ShareType(
share_types.ShareTypeManager(FakeAPIClient),
{'id': 'share-type-id1',
- 'name': 'test-share-type',
+ 'name': 'test-share-type1',
+ 'share_type_access:is_public': True,
'extra_specs': {
'snapshot_support': True,
'driver_handles_share_servers': False}
}
)
+share_type_private = share_types.ShareType(
+ share_types.ShareTypeManager(FakeAPIClient),
+ {'id': 'share-type-id2',
+ 'name': 'test-share-type2',
+ 'share_type_access:is_public': False,
+ 'extra_specs': {'driver_handles_share_servers': False}}
+)
+
+share_type_dhss_true = share_types.ShareType(
+ share_types.ShareTypeManager(FakeAPIClient),
+ {'id': 'share-type-id3',
+ 'name': 'test-share-type3',
+ 'share_type_access:is_public': True,
+ 'extra_specs': {'driver_handles_share_servers': True}}
+)
+
+share_group_type = share_group_types.ShareGroupType(
+ share_group_types.ShareGroupTypeManager(FakeAPIClient),
+ {'id': 'fake_share_group_type_id1',
+ 'name': 'fake_share_group_type_name',
+ 'share_types': [share_type.id],
+ 'group_specs': {'k1': 'v1', 'k2': 'v2'},
+ 'is_public': True}
+)
+
+share_group_type_private = share_group_types.ShareGroupType(
+ share_group_types.ShareGroupTypeManager(FakeAPIClient),
+ {'id': 'fake_private_share_group_type_id2',
+ 'name': 'fake_private_share_group_type_name',
+ 'share_types': [share_type.id, share_type_private.id],
+ 'group_specs': {'k1': 'v1', 'k2': 'v2'},
+ 'is_public': False}
+)
+
+share_group_type_dhss_true = share_group_types.ShareGroupType(
+ share_group_types.ShareGroupTypeManager(FakeAPIClient),
+ {'id': 'fake_share_group_type_id3',
+ 'name': 'fake_share_group_type_name',
+ 'share_types': [share_type_dhss_true.id],
+ 'group_specs': {'k3': 'v3', 'k4': 'v4'},
+ 'is_public': True}
+)
+
+share_group = share_groups.ShareGroup(
+ share_groups.ShareGroupManager(FakeAPIClient),
+ {'id': 'fake_share_group_id',
+ 'name': 'fake_share_group_name',
+ 'description': 'fake sg description',
+ 'status': 'available',
+ 'share_types': [share_type.id],
+ 'share_group_type_id': share_group_type.id,
+ 'source_share_group_snapshot_id': None,
+ 'share_network_id': None,
+ 'share_server_id': None,
+ 'availability_zone': None,
+ 'host': 'fake_host_987654321',
+ 'consistent_snapshot_support': None,
+ 'created_at': '2017-05-31T13:36:15.000000',
+ 'project_id': 'fake_project_id_987654321'}
+)
+
+share_group_nameless = share_groups.ShareGroup(
+ share_groups.ShareGroupManager(FakeAPIClient),
+ {'id': 'fake_nameless_share_group_id',
+ 'name': None,
+ 'status': 'available',
+ 'share_types': [share_type.id],
+ 'share_group_type_id': share_group_type.id,
+ 'source_share_group_snapshot_id': None,
+ 'share_network_id': None,
+ 'share_server_id': None,
+ 'availability_zone': None,
+ 'host': 'fake_host_987654321',
+ 'consistent_snapshot_support': None,
+ 'created_at': '2017-05-31T13:36:15.000000',
+ 'project_id': 'fake_project_id_987654321'}
+)
+
+share_group_dhss_true = share_groups.ShareGroup(
+ share_groups.ShareGroupManager(FakeAPIClient),
+ {'id': 'fake_dhss_true_share_group_id',
+ 'name': 'fake_dhss_true_share_group_name',
+ 'status': 'available',
+ 'share_types': [share_type_dhss_true.id],
+ 'share_group_type_id': share_group_type_dhss_true.id,
+ 'source_share_group_snapshot_id': None,
+ 'share_network_id': 'fake_share_network_id',
+ 'share_server_id': 'fake_share_server_id',
+ 'availability_zone': None,
+ 'host': 'fake_host_987654321',
+ 'consistent_snapshot_support': None,
+ 'created_at': '2017-05-31T23:59:59.000000',
+ 'project_id': 'fake_project_id_987654321'}
+)
+
+share_group_snapshot = share_group_snapshots.ShareGroupSnapshot(
+ share_group_snapshots.ShareGroupSnapshotManager(FakeAPIClient),
+ {'id': 'fake_share_group_snapshot_id_1',
+ 'name': 'fake_share_group_snapshot_name',
+ 'status': 'available',
+ 'share_group_id': share_group.id,
+ 'description': 'fake sgs description',
+ 'created_at': '2017-06-01T13:13:13.000000',
+ 'project_id': 'fake_project_id_987654321',
+ 'members': [
+ {'share_id': 'fake_share_id_1', 'id': 'fake_ssi_id_1', 'size': 1},
+ {'share_id': 'fake_share_id_2', 'id': 'fake_ssi_id_2', 'size': 2},
+ ]}
+)
+
+share_group_snapshot_nameless = share_group_snapshots.ShareGroupSnapshot(
+ share_group_snapshots.ShareGroupSnapshotManager(FakeAPIClient),
+ {'id': 'fake_share_group_snapshot_id_2_nameless',
+ 'name': None,
+ 'status': 'available',
+ 'share_group_id': share_group_nameless.id,
+ 'description': 'fake nameless sgs description',
+ 'created_at': '2017-06-02T14:14:14.000000',
+ 'project_id': 'fake_project_id_987654321',
+ 'members': []}
+)
+
# Quota Sets
quota_data = dict(shares='1',
share_snapshots='1',