Add support of share groups

To both existing dashboards - admin and project.

Change-Id: Ice335de858c234799405f7592b9f08e58e6dec6e
Implements BluePrint share-groups
This commit is contained in:
Valeriy Ponomaryov 2017-05-11 19:43:26 +03:00
parent 3fbc64a6e9
commit 087948ae1a
96 changed files with 5131 additions and 49 deletions

View File

@ -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'],
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,23 @@
{% load i18n sizeformat parse_date %}
<h3>{% trans "Share Group Snapshot Overview" %}</h3>
<div class="detail">
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ share_group_snapshot.name }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ share_group_snapshot.id }}</dd>
{% url 'horizon:admin:share_groups:detail' share_group_snapshot.share_group_id as sg_url %}
<dt>{% trans "Source Share Group" %}</dt>
<dd><a href="{{ sg_url }}">{{ share_group_snapshot.sg_name_or_id }}</a></dd>
{% if share_group_snapshot.description %}
<dt>{% trans "Description" %}</dt>
<dd>{{ share_group_snapshot.description }}</dd>
{% endif %}
<dt>{% trans "Status" %}</dt>
<dd>{{ share_group_snapshot.status|capfirst }}</dd>
<dt>{% trans "Created" %}</dt>
<dd>{{ share_group_snapshot.created_at|parse_date }}</dd>
</dl>
</div>

View File

@ -0,0 +1,5 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<p>"Reset status of <b>'{{ share_group_snapshot_name }}'</b> share group snapshot."</p>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Group Snapshot Details" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Group Snapshots" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ share_group_snapshots_table.render }}
</div>
</div>
{% endblock %}

View File

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

View File

@ -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<share_group_snapshot_id>[^/]+)/$',
views.ShareGroupSnapshotDetailView.as_view(),
name='detail'),
urls.url(
r'^(?P<share_group_snapshot_id>[^/]+)/reset_status$',
views.ResetShareGroupSnapshotStatusView.as_view(),
name='reset_status'),
]

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description" %}:</h3>
<p>{% trans "The share group type defines the characteristics of a share group backend." %}
<br /><br /><strong>Extra specs</strong> field:<br />
{% trans "One line - one action. Empty strings will be ignored." %}<br />
{% trans "To add group-specs use:" %}
<pre>key=value</pre>
</p>
{% endblock %}

View File

@ -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 %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description" %}:</h3>
<p>{% blocktrans %}
Placeholder for description of share group type access managing form.
{% endblocktrans %}</p>
</div>
<table class="table-fixed" id="projectListSortContainer">
<tbody>
<tr>
<td class="actions">
<label id="selected_project_label">{% trans "Selected Projects" %}</label>
<ul id="selected_project" class="projectlist">
</ul>
<label>{% trans "Available projects" %}</label>
<ul id="available_project" class="projectlist">
</ul>
</td>
</tr>
</tbody>
</table>
<table class="table-fixed" id="projectListIdContainer">
<tbody>
<tr>
<td class="actions">
<div id="projectListId">
{% include "horizon/common/_form_fields.html" %}
</div>
</td>
<td class="help_text">
{{ step.get_help_text }}
</td>
</tr>
</tbody>
</table>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Submit" %}" />
<a href="{% url 'horizon:admin:share_group_types:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,14 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description" %}:</h3>
<p>
{% trans "Here can be modified group-specs for share group type." %}<br />
{% trans "One line - one action. Empty strings will be ignored." %}<br />
{% trans "To add group-specs use:" %}
<pre>key=value</pre>
{% trans "To unset group-specs use:" %}
<pre>key</pre>
{% trans "All pairs that are in field for left are set for this share group type." %}
</p>
{% endblock %}

View File

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

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Group Types" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ share_group_types_table.render }}
</div>
</div>
{% endblock %}

View File

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

View File

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

View File

@ -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<share_group_type_id>[^/]+)/update$',
views.UpdateShareGroupTypeView.as_view(),
name='update'),
urls.url(
r'^(?P<share_group_type_id>[^/]+)/manage_access$',
views.ManageShareGroupTypeAccessView.as_view(),
name='manage_access'),
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,61 @@
{% load i18n sizeformat parse_date %}
<h3>{% trans "Share Group Overview" %}</h3>
<div class="detail">
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "ID" %}</dt>
<dd>{{ share_group.id }}</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ share_group.name }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ share_group.description }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ share_group.status|capfirst }}</dd>
<dt>{% trans "Created" %}</dt>
<dd>{{ share_group.created_at|parse_date }}</dd>
<dt>{% trans "Host" %}</dt>
<dd>{{ share_group.host}}</dd>
{% if share_group.source_share_group_snapshot_id %}
<dt>{% trans "Source share group snapshot" %}</dt>
<dd>{{ share_group.source_share_group_snapshot_id}}</dd>
{% endif %}
<dt>{% trans "Share Group Type" %}</dt>
<dd>{{ share_group.share_group_type_id}}</dd>
{% if share_group.availability_zone %}
<dt>{% trans "Availability Zone" %}</dt>
<dd>{{ share_group.availability_zone }}</dd>
{% endif %}
{% if share_group.share_network_id %}
<dt>{% trans "Share network" %}</dt>
{% url 'horizon:admin:share_networks:share_network_detail' share_group.share_network_id as sn_url%}
<dd><a href="{{ sn_url }}">{{ share_group.share_network_id }}</a></dd>
{% endif %}
{% if share_group.share_server_id %}
<dt>{% trans "Share server" %}</dt>
{% url 'horizon:admin:share_servers:share_server_detail' share_group.share_server_id as share_server_url%}
<dd><a href="{{ share_server_url }}">{{ share_group.share_server_id }}</a></dd>
{% endif %}
{% if share_group.share_types %}
<dt>{% trans "Share Types" %}</dt>
{% for st in share_group.share_types %}
<dd><p>
<div><b>Share Type Name:</b> {{ st.name }}</div>
<div><b>Share Type Visibility:</b> {% if st.is_public %}Public{% else %}Private{% endif %}</div>
<div><b>Network handling enabled:</b> {{ st.dhss }}</div>
</p></dd>
{% endfor %}
{% endif %}
{% if share_group.members %}
<dt>{% trans "Shares" %}</dt>
{% for m in share_group.members %}
{% url 'horizon:admin:shares:detail' m.id as share_url%}
<dd>
<div>
<a href="{{ share_url }}">{% if m.name %}{{ m.name }}{% else %}{{ m.id }}{% endif %}</a>
</div>
</dd>
{% endfor %}
{% endif %}
</dl>
</div>

View File

@ -0,0 +1,5 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<p>"Reset status of <b>'{{ share_group_name }}'</b> share group."</p>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Group Details" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Groups" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ share_groups_table.render }}
</div>
</div>
{% endblock %}

View File

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

View File

@ -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<share_group_id>[^/]+)/$',
views.ShareGroupDetailView.as_view(),
name='detail'),
urls.url(
r'^(?P<share_group_id>[^/]+)/reset_status$',
views.ResetShareGroupStatusView.as_view(),
name='reset_status'),
]

View File

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

View File

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

View File

@ -57,10 +57,15 @@
</p></dd>
{% endif %}
{% if share.share_network_id %}
<dt>{% trans "Share network" %}</dt>
<dt>{% trans "Share Network" %}</dt>
{% url 'horizon:admin:share_networks:share_network_detail' share.share_network_id as sn_url%}
<dd><a href="{{ sn_url }}">{{ share.share_network_id }}</a></dd>
{% endif %}
{% if share.share_group_id %}
<dt>{% trans "Share Group" %}</dt>
{% url 'horizon:admin:share_groups:detail' share.share_group_id as sg_url%}
<dd><a href="{{ sg_url }}">{{ share.share_group_id }}</a></dd>
{% endif %}
<dt>{% trans "Mount snapshot support" %}</dt>
<dd>{{ share.mount_snapshot_support }}</dd>
<dt>{% trans "Created" %}</dt>

View File

@ -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<share_id>[^/]+)/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<share_id>[^/]+)$',

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,8 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<div>
Please, choose a name for the new share group snapshot,
that will be created from <b>'{{ share_group_name }}'</b> share group.
</div>
{% endblock %}

View File

@ -0,0 +1,23 @@
{% load i18n sizeformat parse_date %}
<h3>{% trans "Share Group Snapshot Overview" %}</h3>
<div class="detail">
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ share_group_snapshot.name }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ share_group_snapshot.id }}</dd>
{% url 'horizon:project:share_groups:detail' share_group_snapshot.share_group_id as sg_url %}
<dt>{% trans "Source Share Group" %}</dt>
<dd><a href="{{ sg_url }}">{{ share_group_snapshot.sg_name_or_id }}</a></dd>
{% if share_group_snapshot.description %}
<dt>{% trans "Description" %}</dt>
<dd>{{ share_group_snapshot.description }}</dd>
{% endif %}
<dt>{% trans "Status" %}</dt>
<dd>{{ share_group_snapshot.status|capfirst }}</dd>
<dt>{% trans "Created" %}</dt>
<dd>{{ share_group_snapshot.created_at|parse_date }}</dd>
</dl>
</div>

View File

@ -0,0 +1,6 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description" %}:</h3>
<p>{% trans "From here you can modify name and description of a share group snapshot." %}</p>
{% endblock %}

View File

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

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Group Snapshot Details" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Group Snapshots" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ share_group_snapshots_table.render }}
</div>
</div>
{% endblock %}

View File

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

View File

@ -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<share_group_id>[^/]+)/snapshot_create/$',
views.CreateShareGroupSnapshotView.as_view(),
name='create'),
urls.url(
r'^(?P<share_group_snapshot_id>[^/]+)/$',
views.ShareGroupSnapshotDetailView.as_view(),
name='detail'),
urls.url(
r'^(?P<share_group_snapshot_id>[^/]+)/update/$',
views.UpdateShareGroupSnapshotView.as_view(),
name='update'),
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,6 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description" %}:</h3>
<p>{% trans "Select parameters of share group you want to create. " %}</p>
{% endblock %}

View File

@ -0,0 +1,55 @@
{% load i18n sizeformat parse_date %}
<h3>{% trans "Share Group Overview" %}</h3>
<div class="detail">
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "ID" %}</dt>
<dd>{{ share_group.id }}</dd>
<dt>{% trans "Name" %}</dt>
<dd>{{ share_group.name }}</dd>
<dt>{% trans "Description" %}</dt>
<dd>{{ share_group.description }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ share_group.status|capfirst }}</dd>
<dt>{% trans "Created" %}</dt>
<dd>{{ share_group.created_at|parse_date }}</dd>
{% if share_group.source_share_group_snapshot_id %}
<dt>{% trans "Source SG Snapshot" %}</dt>
{% url 'horizon:project:share_group_snapshots:detail' share_group.source_share_group_snapshot_id as ssgs_url%}
<dd><a href="{{ ssgs_url }}">{{ share_group.source_share_group_snapshot_id }}</a></dd>
{% endif %}
<dt>{% trans "Share Group Type" %}</dt>
<dd>{{ share_group.share_group_type_id}}</dd>
{% if share_group.availability_zone %}
<dt>{% trans "Availability Zone" %}</dt>
<dd>{{ share_group.availability_zone }}</dd>
{% endif %}
{% if share_group.share_network_id %}
<dt>{% trans "Share Network" %}</dt>
{% url 'horizon:project:share_networks:share_network_detail' share_group.share_network_id as sn_url%}
<dd><a href="{{ sn_url }}">{{ share_group.share_network_id }}</a></dd>
{% endif %}
{% if share_group.share_types %}
<dt>{% trans "Share Types" %}</dt>
{% for st in share_group.share_types %}
<dd><p>
<div><b>Share Type Name:</b> {{ st.name }}</div>
<div><b>Share Type Visibility:</b> {% if st.is_public %}Public{% else %}Private{% endif %}</div>
<div><b>Network handling enabled:</b> {{ st.dhss }}</div>
</p></dd>
{% endfor %}
{% endif %}
{% if share_group.members %}
<dt>{% trans "Shares" %}</dt>
{% for m in share_group.members %}
{% url 'horizon:project:shares:detail' m.id as share_url%}
<dd>
<div>
<a href="{{ share_url }}">{% if m.name %}{{ m.name }}{% else %}{{ m.id }}{% endif %}</a>
</div>
</dd>
{% endfor %}
{% endif %}
</dl>
</div>

View File

@ -0,0 +1,6 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description" %}:</h3>
<p>{% trans "From here you can modify name, description and visibility of a share group." %}</p>
{% endblock %}

View File

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

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Group Details" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Groups" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ share_groups_table.render }}
</div>
</div>
{% endblock %}

View File

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

View File

@ -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<share_group_id>[^/]+)/$',
views.ShareGroupDetailView.as_view(),
name='detail'),
urls.url(
r'^(?P<share_group_id>[^/]+)/update/$',
views.ShareGroupUpdateView.as_view(),
name='update'),
]

View File

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

View File

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

View File

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

View File

@ -57,10 +57,15 @@
</p></dd>
{% endif %}
{% if share.share_network_id %}
<dt>{% trans "Share network" %}</dt>
<dt>{% trans "Share Network" %}</dt>
{% url 'horizon:project:share_networks:share_network_detail' share.share_network_id as sn_url%}
<dd><a href="{{ sn_url }}">{{ share.share_network_id }}</a></dd>
{% endif %}
{% if share.share_group_id %}
<dt>{% trans "Share Group" %}</dt>
{% url 'horizon:project:share_groups:detail' share.share_group_id as sg_url%}
<dd><a href="{{ sg_url }}">{{ share.share_group_id }}</a></dd>
{% endif %}
<dt>{% trans "Mount snapshot support" %}</dt>
<dd>{{ share.mount_snapshot_support }}</dd>
<dt>{% trans "Created" %}</dt>

View File

@ -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<share_id>[^/]+)/create_replica/$',

50
manila_ui/features.py Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'],

View File

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

View File

@ -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, "<h1>Share Group Snapshots</h1>")
self.assertContains(
res, 'Delete Share Group Snapshot</button>', len(sgss))
self.assertContains(res, 'Delete Share Group Snapshots</button>', 1)
for sgs in sgss:
self.assertContains(
res,
'href="/admin/share_group_snapshots/%s/reset_status"> '
'Reset status</a>' % sgs.id, 1)
self.assertContains(
res, 'value="share_group_snapshots__delete__%s' % sgs.id, 1)
self.assertContains(
res,
'<a href="/admin/share_group_snapshots/%(id)s/" >%(name)s</a>' % {
'id': self.sgs.id, 'name': self.sgs.name}, 1)
self.assertContains(
res,
'<a href="/admin/share_group_snapshots/%s/" '
'>-</a>' % 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, "<h1>Share Group Snapshots</h1>")
self.assertContains(res, 'Delete Share Group Snapshot</button>', 0)
self.assertContains(res, 'Delete Share Group Snapshots</button>', 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)

View File

@ -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, "<h1>Share Group Types</h1>")
self.assertContains(res, 'href="/admin/share_group_types/create"')
self.assertContains(res, 'Delete Share Group Type</button>', len(sgts))
self.assertContains(res, 'Delete Share Group Types</button>', 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)

View File

@ -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, "<h1>Share Groups</h1>")
self.assertContains(res, 'Delete Share Group</button>', len(sgs))
self.assertContains(res, 'Delete Share Groups</button>', 1)
for sg in sgs:
self.assertContains(
res,
'href="/admin/share_groups/%s/reset_status"> '
'Reset status</a>' % sg.id, 1)
self.assertContains(
res, 'value="share_groups__delete__%s' % sg.id, 1)
self.assertContains(
res,
'<a href="/admin/share_networks/%s"' % (
self.sg_dhss_true.share_network_id), 1)
self.assertContains(
res,
'<a href="/admin/share_servers/%s"' % (
self.sg_dhss_true.share_server_id), 1)
for sg in (self.sg, self.sg_dhss_true):
self.assertContains(
res,
'<a href="/admin/share_groups/%(id)s/" >%(name)s</a>' % {
'id': sg.id, 'name': sg.name}, 1)
self.assertContains(
res,
'<a href="/admin/share_groups/%s/" >-</a>' % 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, "<h1>Share Groups</h1>")
self.assertContains(res, 'Delete Share Group</button>', 0)
self.assertContains(res, 'Delete Share Groups</button>', 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, '<a href="/admin/shares/%(id)s/">%(name)s</a>' % 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)

View File

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

View File

@ -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, "<h1>Share Group Snapshots</h1>")
self.assertContains(
res, 'Delete Share Group Snapshot</button>', len(sgss))
self.assertContains(res, 'Delete Share Group Snapshots</button>', 1)
for sgs in sgss:
self.assertContains(
res, 'value="share_group_snapshots__delete__%s' % sgs.id, 1)
self.assertContains(
res,
'<a href="/project/share_group_snapshots/%(id)s/" >%(name)s</a' % {
'id': self.sgs.id, 'name': self.sgs.name}, 1)
self.assertContains(
res,
'<a href="/project/share_group_snapshots/%s/" '
'>-</a>' % 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, "<h1>Share Group Snapshots</h1>")
self.assertContains(res, 'Delete Share Group Snapshot</button>', 0)
self.assertContains(res, 'Delete Share Group Snapshots</button>', 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)

View File

@ -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, "<h1>Share Groups</h1>")
self.assertContains(res, 'Delete Share Group</button>', len(sgs))
self.assertContains(res, 'Delete Share Groups</button>', 1)
for sg in sgs:
self.assertContains(
res, 'value="share_groups__delete__%s' % sg.id, 1)
self.assertContains(
res,
'<a href="/project/share_networks/%s"' % (
self.sg_dhss_true.share_network_id), 1)
for sg in (self.sg, self.sg_dhss_true):
self.assertContains(
res,
'<a href="/project/share_groups/%(id)s/" >%(name)s</a>' % {
'id': sg.id, 'name': sg.name}, 1)
self.assertContains(
res,
'<a href="/project/share_groups/%s/" >-</a>' % 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, "<h1>Share Groups</h1>")
self.assertContains(res, 'Delete Share Group</button>', 0)
self.assertContains(res, 'Delete Share Groups</button>', 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, '<a href="/project/shares/%(id)s/">%(name)s</a>' % 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)

View File

@ -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'],

View File

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