Merge "Add mountable snapshots support to manila-ui"

This commit is contained in:
Jenkins 2017-01-26 19:05:09 +00:00 committed by Gerrit Code Review
commit 863e927b3c
20 changed files with 668 additions and 49 deletions

View File

@ -33,7 +33,7 @@ from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
MANILA_UI_USER_AGENT_REPR = "manila_ui_plugin_for_horizon"
MANILA_VERSION = "2.29" # requires manilaclient 1.12.0 or newer
MANILA_VERSION = "2.32" # requires manilaclient 1.13.0 or newer
MANILA_SERVICE_TYPE = "sharev2"
# API static values
@ -208,6 +208,29 @@ def share_snapshot_delete(request, snapshot_id):
return manilaclient(request).share_snapshots.delete(snapshot_id)
def share_snapshot_allow(request, snapshot_id, access_type, access_to):
return manilaclient(request).share_snapshots.allow(
snapshot_id, access_type, access_to)
def share_snapshot_deny(request, snapshot_id, rule_id):
return manilaclient(request).share_snapshots.deny(snapshot_id, rule_id)
def share_snapshot_rules_list(request, snapshot_id):
return manilaclient(request).share_snapshots.access_list(snapshot_id)
def share_snap_export_location_list(request, snapshot):
return manilaclient(request).share_snapshot_export_locations.list(
snapshot=snapshot)
def share_snap_instance_export_location_list(request, snapshot_instance):
return manilaclient(request).share_snapshot_export_locations.list(
snapshot_instance=snapshot_instance)
def share_server_list(request, search_opts=None):
return manilaclient(request).share_servers.list(search_opts=search_opts)

View File

@ -37,6 +37,7 @@ from manila_ui.dashboards.project.shares.share_networks import \
from manila_ui.dashboards.project.shares.shares import views as share_views
from manila_ui.dashboards.project.shares.snapshots import \
views as snapshot_views
from manila_ui.dashboards import utils as ui_utils
from manila_ui.utils import filters
filters = (filters.get_item,)
@ -53,8 +54,8 @@ class DetailView(share_views.DetailView):
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
context["page_title"] = _("Share Details: %(share_name)s") % \
{'share_name': context["share_display_name"]}
context["page_title"] = _("Share Details: %(share_name)s") % {
'share_name': context["share_display_name"]}
return context
@ -349,8 +350,8 @@ class ShareServDetail(tabs.TabView):
share_server_display_name = share_server.id
context["share_server"] = share_server
context["share_server_display_name"] = share_server_display_name
context["page_title"] = _("Share Server Details: %(server_name)s") % \
{'server_name': share_server_display_name}
context["page_title"] = _("Share Server Details: %(server_name)s") % {
'server_name': share_server_display_name}
return context
@memoized.memoized_method
@ -384,13 +385,6 @@ class ShareInstanceDetailView(tabs.TabView):
tab_group_class = project_tabs.ShareInstanceDetailTabs
template_name = 'admin/shares/share_instance_detail.html'
def _calculate_size_of_longest_export_location(self, export_locations):
size = 40
for export_location in export_locations:
if len(export_location["path"]) > size:
size = len(export_location["path"])
return size
def get_context_data(self, **kwargs):
context = super(self.__class__, self).get_context_data(**kwargs)
share_instance = self.get_data()
@ -408,9 +402,11 @@ class ShareInstanceDetailView(tabs.TabView):
share_instance.export_locations = (
manila.share_instance_export_location_list(
self.request, share_instance_id))
share_instance.el_size = (
self._calculate_size_of_longest_export_location(
share_instance.export_locations))
export_locations = [
exp['path'] for exp in share_instance.export_locations
]
share_instance.el_size = ui_utils.calculate_longest_str_size(
export_locations)
return share_instance
except Exception:
redirect = reverse('horizon:admin:shares:index')

View File

@ -28,6 +28,7 @@ from manila_ui.dashboards.project.shares.replicas import (
from manila_ui.dashboards.project.shares.replicas import (
tables as replicas_tables)
from manila_ui.dashboards.project.shares.replicas import tabs as replicas_tabs
from manila_ui.dashboards import utils as ui_utils
class ManageReplicasView(tables.DataTableView):
@ -70,14 +71,6 @@ class DetailReplicaView(tabs.TabView):
template_name = 'project/shares/replicas/detail.html'
_redirect_url = 'horizon:project:shares:index'
def _calculate_size_of_longest_export_location(self, export_locations):
size = 40
for export_location in export_locations:
current_size = len(export_location["path"])
if current_size > size:
size = current_size
return size
def get_context_data(self, **kwargs):
context = super(DetailReplicaView, self).get_context_data(**kwargs)
replica = self.get_data()
@ -97,8 +90,11 @@ class DetailReplicaView(tabs.TabView):
replica.export_locations = (
manila.share_instance_export_location_list(
self.request, replica_id))
replica.el_size = self._calculate_size_of_longest_export_location(
replica.export_locations)
export_locations = [
exp['path'] for exp in replica.export_locations
]
replica.el_size = ui_utils.calculate_longest_str_size(
export_locations)
except Exception:
redirect = reverse(self._redirect_url)
exceptions.handle(

View File

@ -29,6 +29,7 @@ from manila_ui.dashboards.project.shares.shares \
import tables as shares_tables
from manila_ui.dashboards.project.shares.shares \
import tabs as shares_tabs
from manila_ui.dashboards import utils as ui_utils
from openstack_dashboard.usage import quotas
@ -52,14 +53,6 @@ class DetailView(tabs.TabView):
tab_group_class = shares_tabs.ShareDetailTabs
template_name = 'project/shares/shares/detail.html'
def _calculate_size_of_longest_export_location(self, export_locations):
size = 40
for export_location in export_locations:
current_size = len(export_location["path"])
if current_size > size:
size = current_size
return size
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
share = self.get_data()
@ -67,8 +60,8 @@ class DetailView(tabs.TabView):
context["share"] = share
context["share_display_name"] = share_display_name
context["page_title"] = _("Share Details: "
"%(share_display_name)s") % \
{'share_display_name': share_display_name}
"%(share_display_name)s") % {
'share_display_name': share_display_name}
return context
@memoized.memoized_method
@ -79,8 +72,10 @@ class DetailView(tabs.TabView):
share.rules = manila.share_rules_list(self.request, share_id)
share.export_locations = manila.share_export_location_list(
self.request, share_id)
share.el_size = self._calculate_size_of_longest_export_location(
share.export_locations)
export_locations = [
exp['path'] for exp in share.export_locations]
share.el_size = ui_utils.calculate_longest_str_size(
export_locations)
except Exception:
redirect = reverse('horizon:project:shares:index')
exceptions.handle(self.request,
@ -230,8 +225,8 @@ class ManageRulesView(tables.DataTableView):
context['share_display_name'] = share.name or share.id
context["share"] = self.get_data()
context["page_title"] = _("Share Rules: "
"%(share_display_name)s") % \
{'share_display_name': context['share_display_name']}
"%(share_display_name)s") % {
'share_display_name': context['share_display_name']}
return context
@memoized.memoized_method

View File

@ -73,3 +73,28 @@ class UpdateForm(forms.SelfHandlingForm):
return True
except Exception:
exceptions.handle(request, _('Unable to update snapshot.'))
class AddRule(forms.SelfHandlingForm):
access_type = forms.ChoiceField(
label=_("Access Type"), required=True,
choices=(('ip', 'ip'), ('user', 'user'), ('cephx', 'cephx'),
('cert', 'cert')))
access_to = forms.CharField(
label=_("Access To"), max_length="255", required=True)
def handle(self, request, data):
snapshot_id = self.initial['snapshot_id']
try:
manila.share_snapshot_allow(
request, snapshot_id,
access_to=data['access_to'],
access_type=data['access_type'])
message = _('Creating snapshot rule for "%s"') % data['access_to']
messages.success(request, message)
return True
except Exception:
redirect = reverse("horizon:project:shares:snapshot_manage_rules",
args=[self.initial['snapshot_id']])
exceptions.handle(
request, _('Unable to add snapshot rule.'), redirect=redirect)

View File

@ -137,6 +137,83 @@ class SnapshotShareNameColumn(tables.Column):
return reverse(self.link, args=(snapshot.share_id,))
class ManageRules(tables.LinkAction):
name = "snapshot_manage_rules"
verbose_name = _("Manage Rules")
url = "horizon:project:shares:snapshot_manage_rules"
classes = ("btn-edit",)
policy_rules = (("share", "share:access_get_all"),)
def allowed(self, request, snapshot=None):
share = manila.share_get(request, snapshot.share_id)
return share.mount_snapshot_support
class AddRule(tables.LinkAction):
name = "snapshot_rule_add"
verbose_name = _("Add rule")
url = 'horizon:project:shares:snapshot_rule_add'
classes = ("ajax-modal", "btn-create")
icon = "plus"
policy_rules = (("share", "share:allow_access"),)
def allowed(self, request, snapshot=None):
snapshot = manila.share_snapshot_get(
request, self.table.kwargs['snapshot_id'])
return snapshot.status in ("available", "in-use")
def get_link_url(self):
return reverse(self.url, args=[self.table.kwargs['snapshot_id']])
class DeleteRule(tables.DeleteAction):
data_type_singular = _("Rule")
data_type_plural = _("Rules")
action_past = _("Scheduled deletion of %(data_type)s")
policy_rules = (("share", "share:deny_access"),)
def delete(self, request, obj_id):
try:
manila.share_snapshot_deny(
request, self.table.kwargs['snapshot_id'], obj_id)
except Exception:
msg = _('Unable to delete snapshot rule "%s".') % obj_id
exceptions.handle(request, msg)
class UpdateRuleRow(tables.Row):
ajax = True
def get_data(self, request, rule_id):
rules = manila.share_snapshot_rules_list(
request, self.table.kwargs['snapshot_id'])
if rules:
for rule in rules:
if rule.id == rule_id:
return rule
raise exceptions.NotFound
class RulesTable(tables.DataTable):
access_type = tables.Column("access_type", verbose_name=_("Access Type"))
access_to = tables.Column("access_to", verbose_name=_("Access to"))
status = tables.Column("state", verbose_name=_("Status"))
def get_object_display(self, obj):
return obj.id
class Meta(object):
name = "rules"
verbose_name = _("Rules")
status_columns = ["status"]
row_class = UpdateRuleRow
table_actions = (
AddRule,
DeleteRule)
row_actions = (
DeleteRule,)
class SnapshotsTable(tables.DataTable):
STATUS_CHOICES = (
("in-use", True),
@ -184,4 +261,5 @@ class SnapshotsTable(tables.DataTable):
row_actions = (
EditSnapshot,
CreateShareFromSnapshot,
ManageRules,
DeleteSnapshot)

View File

@ -18,14 +18,18 @@ 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.shares.snapshots import forms \
as snapshot_forms
from manila_ui.dashboards.project.shares.snapshots\
from manila_ui.dashboards.project.shares.snapshots \
import tables as snapshot_tables
from manila_ui.dashboards.project.shares.snapshots \
import tabs as snapshot_tabs
from manila_ui.dashboards import utils as ui_utils
from openstack_dashboard.usage import quotas
@ -41,8 +45,8 @@ class SnapshotDetailView(tabs.TabView):
context["snapshot"] = snapshot
context["snapshot_display_name"] = snapshot_display_name
context["page_title"] = _("Snapshot Details: "
"%(snapshot_display_name)s") % \
{'snapshot_display_name': snapshot_display_name}
"%(snapshot_display_name)s") % (
{'snapshot_display_name': snapshot_display_name})
return context
@memoized.memoized_method
@ -51,6 +55,18 @@ class SnapshotDetailView(tabs.TabView):
snapshot_id = self.kwargs['snapshot_id']
snapshot = manila.share_snapshot_get(self.request, snapshot_id)
share = manila.share_get(self.request, snapshot.share_id)
if share.mount_snapshot_support:
snapshot.rules = manila.share_snapshot_rules_list(
self.request, snapshot_id)
snapshot.export_locations = (
manila.share_snap_export_location_list(
self.request, snapshot))
export_locations = [
exp['path'] for exp in snapshot.export_locations
]
snapshot.el_size = ui_utils.calculate_longest_str_size(
export_locations)
snapshot.share_name_or_id = share.name or share.id
except Exception:
exceptions.handle(self.request,
@ -122,3 +138,72 @@ class UpdateView(forms.ModalFormView):
return {'snapshot_id': self.kwargs["snapshot_id"],
'name': snapshot.name,
'description': snapshot.description}
class AddRuleView(forms.ModalFormView):
form_class = snapshot_forms.AddRule
form_id = "rule_add_snap"
template_name = 'project/shares/snapshots/rule_add.html'
modal_header = _("Add Rule")
modal_id = "rule_add_snap_modal"
submit_label = _("Add")
submit_url = "horizon:project:shares:snapshot_rule_add"
success_url = reverse_lazy("horizon:project:shares:index")
page_title = _('Add Rule')
def get_object(self):
if not hasattr(self, "_object"):
snapshot_id = self.kwargs['snapshot_id']
try:
self._object = manila.share_snapshot_get(
self.request, snapshot_id)
except Exception:
msg = _('Unable to retrieve snapshot.')
url = reverse('horizon:project:shares:index')
exceptions.handle(self.request, msg, redirect=url)
return self._object
def get_context_data(self, **kwargs):
context = super(AddRuleView, 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 {'snapshot_id': self.kwargs["snapshot_id"],
'name': snapshot.name,
'description': snapshot.description}
def get_success_url(self):
return reverse("horizon:project:shares:snapshot_manage_rules",
args=[self.kwargs['snapshot_id']])
class ManageRulesView(tables.DataTableView):
table_class = snapshot_tables.RulesTable
template_name = 'project/shares/snapshots/manage_rules.html'
def get_context_data(self, **kwargs):
context = super(ManageRulesView, self).get_context_data(**kwargs)
snapshot = manila.share_snapshot_get(
self.request, self.kwargs['snapshot_id'])
context['snapshot_display_name'] = snapshot.name or snapshot.id
context["snapshot"] = self.get_data()
context["page_title"] = _("Snapshot Rules: "
"%(snapshot_display_name)s") % {
'snapshot_display_name': context['snapshot_display_name']}
return context
@memoized.memoized_method
def get_data(self):
try:
snapshot_id = self.kwargs['snapshot_id']
rules = manila.share_snapshot_rules_list(
self.request, snapshot_id)
except Exception:
redirect = reverse('horizon:project:shares:index')
exceptions.handle(self.request,
_('Unable to retrieve snapshot rules.'),
redirect=redirect)
return rules

View File

@ -61,6 +61,8 @@
{% url 'horizon:project:shares:share_network_detail' share.share_network_id as sn_url%}
<dd><a href="{{ sn_url }}">{{ share.share_network_id }}</a></dd>
{% endif %}
<dt>{% trans "Mount snapshot support" %}</dt>
<dd>{{ share.mount_snapshot_support }}</dd>
<dt>{% trans "Created" %}</dt>
<dd>{{ share.created_at|parse_date }}</dd>
<dt>{% trans "Host" %}</dt>

View File

@ -0,0 +1,11 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description" %}:</h3>
<p>{% blocktrans %}
Add policy rule to snapshot, 'ip' rule represents ip address,
'user' rule represents username or usergroup,
'cephx' rule represents ceph auth ID, and 'cert' rule represents
certificate.
{% endblocktrans %}</p>
{% endblock %}

View File

@ -17,9 +17,43 @@
{% endif %}
<dt>{% trans "Status" %}</dt>
<dd>{{ snapshot.status|capfirst }}</dd>
{% if snapshot.export_locations %}
<dt>{% trans "Export locations" %}</dt>
{% for el in snapshot.export_locations %}
<dd><p>
<div><b>Path:</b>
<input type="text" readonly="true"
value="{{ el.path }}" size="{{ snapshot.el_size }}"
onClick="this.setSelectionRange(0, this.value.length)">
</div>
{% if el.is_admin_only != None %}
<div><b>Is admin only:</b> {{ el.is_admin_only }}</div>
{% endif %}
{% if el.share_snapshot_instance_id %}
<div><b>Snapshot Replica ID:</b> {{ el.share_snapshot_instance_id }}</div>
{% endif %}
</p></dd>
{% endfor %}
{% endif %}
</dl>
</div>
{% if snapshot.rules != None %}
<div class="detail">
<h4>{% trans "Access Rules" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
{% for rule in snapshot.rules %}
<dt>{{ rule.access_type }}</dt>
<dd><p>
<div><b>Access to: </b>{{ rule.access_to }}</div>
<div><b>Status: </b>{{ rule.state }}</div>
</p></dd>
{% endfor %}
</dl>
</div>
{% endif %}
<div class="detail">
<h4>{% trans "Specs" %}</h4>
<hr class="header_rule">

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Share Rules" %}{% endblock %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Add Rule" %}{% endblock %}
{% block main %}
{% include 'project/shares/snapshots/_rule_add.html' %}
{% endblock %}

View File

@ -79,6 +79,12 @@ urlpatterns = [
url(r'^(?P<share_id>[^/]+)/extend/$',
shares_views.ExtendView.as_view(),
name='extend'),
url(r'^(?P<snapshot_id>[^/]+)/snapshot_rules/$',
snapshot_views.ManageRulesView.as_view(),
name='snapshot_manage_rules'),
url(r'^(?P<snapshot_id>[^/]+)/snapshot_rule_add/$',
snapshot_views.AddRuleView.as_view(),
name='snapshot_rule_add'),
]
if manila.is_replication_enabled():

View File

@ -103,3 +103,12 @@ def get_nice_security_service_type(security_service):
'kerberos': 'Kerberos',
}
return type_mapping.get(security_service.type, security_service.type)
def calculate_longest_str_size(str_list):
size = 40
for str_val in str_list:
current_size = len(str_val)
if current_size > size:
size = current_size
return size

View File

@ -199,6 +199,43 @@ class ManilaApiTests(base.APITestCase):
mock_reset_state = self.manilaclient.share_replicas.reset_replica_state
mock_reset_state.assert_called_once_with(replica, state)
def test_allow_snapshot(self):
access_type = "fake_type"
access_to = "fake_value"
api.share_snapshot_allow(self.request, self.id, access_type,
access_to)
client = self.manilaclient
client.share_snapshots.allow.assert_called_once_with(
self.id, access_type, access_to)
def test_deny_snapshot(self):
api.share_snapshot_deny(self.request, self.id, self.id)
client = self.manilaclient
client.share_snapshots.deny.assert_called_once_with(self.id, self.id)
def test_list_snapshot_rules(self):
api.share_snapshot_rules_list(self.request, self.id)
client = self.manilaclient
client.share_snapshots.access_list.assert_called_once_with(self.id)
def test_list_snapshot_export_locations(self):
api.share_snap_export_location_list(self.request, self.id)
client = self.manilaclient
client.share_snapshot_export_locations.list.assert_called_once_with(
snapshot=self.id)
def test_list_snapshot_instance_export_locations(self):
api.share_snap_instance_export_location_list(self.request, self.id)
client = self.manilaclient
client.share_snapshot_export_locations.list.assert_called_once_with(
snapshot_instance=self.id)
def test_migration_start(self):
api.migration_start(self.request, 'fake_share', 'fake_host', False,
True, True, True, True, 'fake_net_id',

View File

@ -750,6 +750,59 @@ class SnapshotsTests(test.BaseAdminViewTests):
api_manila.share_snapshot_get.assert_called_once_with(
mock.ANY, snapshot.id)
def test_detail_view_with_mount_support(self):
snapshot = test_data.snapshot_mount_support
rules = [test_data.ip_rule, test_data.user_rule, test_data.cephx_rule]
export_locations = test_data.admin_snapshot_export_locations
share = test_data.share_mount_snapshot
url = reverse('horizon:project:shares:snapshot-detail',
args=[snapshot.id])
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(return_value=snapshot))
self.mock_object(
api_manila, "share_snapshot_rules_list", mock.Mock(
return_value=rules))
self.mock_object(
api_manila, "share_snap_export_location_list", mock.Mock(
return_value=export_locations))
self.mock_object(
api_manila, "share_get", mock.Mock(return_value=share))
res = self.client.get(url)
self.assertContains(res, "<h1>Snapshot Details: %s</h1>"
% snapshot.name,
1, 200)
self.assertContains(res, "<dd>%s</dd>" % snapshot.name, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % snapshot.id, 1, 200)
self.assertContains(res,
"<dd><a href=\"/admin/shares/%s/\">%s</a></dd>" %
(snapshot.share_id, share.name), 1, 200)
self.assertContains(res, "<dd>%s GiB</dd>" % snapshot.size, 1, 200)
for el in export_locations:
self.assertContains(res, "value=\"%s\"" % el.path, 1, 200)
self.assertContains(
res, "<div><b>Is admin only:</b> %s</div>" % el.is_admin_only,
1, 200)
self.assertContains(
res, ("<div><b>Snapshot Replica ID:</b> %s</div>" %
el.share_snapshot_instance_id), 1, 200)
for rule in rules:
self.assertContains(res, "<dt>%s</dt>" % rule.access_type, 1, 200)
self.assertContains(
res, "<div><b>Access to: </b>%s</div>" % rule.access_to,
1, 200)
self.assertContains(
res, "<div><b>Status: </b>active</div>", len(rules), 200)
self.assertNoMessages()
api_manila.share_get.assert_called_once_with(mock.ANY, share.id)
api_manila.share_snapshot_get.assert_called_once_with(
mock.ANY, snapshot.id)
api_manila.share_snapshot_rules_list.assert_called_once_with(
mock.ANY, snapshot.id)
api_manila.share_snap_export_location_list.assert_called_once_with(
mock.ANY, snapshot)
def test_detail_view_with_exception(self):
url = reverse('horizon:admin:shares:snapshot-detail',
args=[test_data.snapshot.id])

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
from django.core.urlresolvers import reverse
import mock
@ -26,6 +27,7 @@ SHARE_INDEX_URL = reverse('horizon:project:shares:index')
SHARE_SNAPSHOTS_TAB_URL = reverse('horizon:project:shares:snapshots_tab')
@ddt.ddt
class SnapshotSnapshotViewTests(test.TestCase):
def test_create_snapshot_get(self):
@ -116,6 +118,53 @@ class SnapshotSnapshotViewTests(test.TestCase):
api_manila.share_snapshot_get.assert_called_once_with(
mock.ANY, snapshot.id)
def test_detail_view_with_mount_support(self):
snapshot = test_data.snapshot_mount_support
share = test_data.share_mount_snapshot
rules = [test_data.ip_rule, test_data.user_rule, test_data.cephx_rule]
export_locations = test_data.user_snapshot_export_locations
url = reverse('horizon:project:shares:snapshot-detail',
args=[snapshot.id])
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(return_value=snapshot))
self.mock_object(
api_manila, "share_snapshot_rules_list", mock.Mock(
return_value=rules))
self.mock_object(
api_manila, "share_snap_export_location_list", mock.Mock(
return_value=export_locations))
self.mock_object(
api_manila, "share_get", mock.Mock(return_value=share))
res = self.client.get(url)
self.assertContains(res, "<h1>Snapshot Details: %s</h1>"
% snapshot.name,
1, 200)
self.assertContains(res, "<dd>%s</dd>" % snapshot.name, 1, 200)
self.assertContains(res, "<dd>%s</dd>" % snapshot.id, 1, 200)
self.assertContains(res,
"<dd><a href=\"/admin/shares/%s/\">%s</a></dd>" %
(snapshot.share_id, share.name), 1, 200)
self.assertContains(res, "<dd>%s GiB</dd>" % snapshot.size, 1, 200)
for el in export_locations:
self.assertContains(res, "value=\"%s\"" % el.path, 1, 200)
for rule in rules:
self.assertContains(res, "<dt>%s</dt>" % rule.access_type, 1, 200)
self.assertContains(
res, "<div><b>Access to: </b>%s</div>" % rule.access_to,
1, 200)
self.assertContains(
res, "<div><b>Status: </b>active</div>", len(rules), 200)
self.assertNoMessages()
api_manila.share_get.assert_called_once_with(mock.ANY, share.id)
api_manila.share_snapshot_get.assert_called_once_with(
mock.ANY, snapshot.id)
api_manila.share_snapshot_rules_list.assert_called_once_with(
mock.ANY, snapshot.id)
api_manila.share_snap_export_location_list.assert_called_once_with(
mock.ANY, snapshot)
def test_update_snapshot_get(self):
snapshot = test_data.snapshot
url = reverse('horizon:project:shares:edit_snapshot',
@ -142,8 +191,8 @@ class SnapshotSnapshotViewTests(test.TestCase):
'description': snapshot.description,
}
self.mock_object(api_manila, "share_snapshot_update")
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(return_value=snapshot))
self.mock_object(api_manila, "share_snapshot_get",
mock.Mock(return_value=snapshot))
res = self.client.post(url, formData)
@ -152,3 +201,131 @@ class SnapshotSnapshotViewTests(test.TestCase):
mock.ANY, snapshot.id)
api_manila.share_snapshot_update.assert_called_once_with(
mock.ANY, snapshot.id, formData['name'], formData['description'])
def test_list_rules(self):
snapshot = test_data.snapshot
rules = [test_data.ip_rule, test_data.user_rule, test_data.cephx_rule]
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(
return_value=snapshot))
self.mock_object(
api_manila, "share_snapshot_rules_list", mock.Mock(
return_value=rules))
url = reverse('horizon:project:shares:snapshot_manage_rules',
args=[snapshot.id])
res = self.client.get(url)
self.assertEqual(res.status_code, 200)
self.assertTemplateUsed(res,
'project/shares/snapshots/manage_rules.html')
api_manila.share_snapshot_rules_list.assert_called_once_with(
mock.ANY, snapshot.id)
def test_list_rules_exception(self):
snapshot = test_data.snapshot
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(
return_value=snapshot))
self.mock_object(
api_manila, "share_snapshot_rules_list",
mock.Mock(side_effect=Exception('fake')))
url = reverse('horizon:project:shares:snapshot_manage_rules',
args=[snapshot.id])
res = self.client.get(url)
self.assertEqual(res.status_code, 302)
self.assertTemplateNotUsed(
res, 'project/shares/snapshots/manage_rules.html')
api_manila.share_snapshot_rules_list.assert_called_once_with(
mock.ANY, snapshot.id)
def test_create_rule_get(self):
snapshot = test_data.snapshot
url = reverse('horizon:project:shares:snapshot_rule_add',
args=[snapshot.id])
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(
return_value=snapshot))
self.mock_object(
neutron, "is_service_enabled", mock.Mock(return_value=[True]))
res = self.client.get(url)
self.assertNoMessages()
self.assertTemplateUsed(res, 'project/shares/snapshots/rule_add.html')
def test_create_rule_get_exception(self):
snapshot = test_data.snapshot
url = reverse('horizon:project:shares:snapshot_rule_add',
args=[snapshot.id])
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(
side_effect=Exception('fake')))
res = self.client.get(url)
self.assertEqual(res.status_code, 302)
self.assertTemplateNotUsed(
res, 'project/shares/snapshots/rule_add.html')
@ddt.data(None, Exception('fake'))
def test_create_rule_post(self, exc):
snapshot = test_data.snapshot
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(
return_value=snapshot))
url = reverse('horizon:project:shares:snapshot_rule_add',
args=[snapshot.id])
self.mock_object(api_manila, "share_snapshot_allow",
mock.Mock(side_effect=exc))
formData = {
'access_type': 'user',
'method': u'CreateForm',
'access_to': 'someuser',
}
res = self.client.post(url, formData)
self.assertEqual(res.status_code, 302)
api_manila.share_snapshot_allow.assert_called_once_with(
mock.ANY, snapshot.id, access_type=formData['access_type'],
access_to=formData['access_to'])
self.assertRedirectsNoFollow(
res,
reverse('horizon:project:shares:snapshot_manage_rules',
args=[snapshot.id])
)
@ddt.data(None, Exception('fake'))
def test_delete_rule(self, exc):
snapshot = test_data.snapshot
rule = test_data.ip_rule
formData = {'action': 'rules__delete__%s' % rule.id}
self.mock_object(
api_manila, "share_snapshot_get", mock.Mock(
return_value=snapshot))
self.mock_object(api_manila, "share_snapshot_deny",
mock.Mock(side_effect=exc))
self.mock_object(
api_manila, "share_snapshot_rules_list", mock.Mock(
return_value=[rule]))
url = reverse(
'horizon:project:shares:snapshot_manage_rules',
args=[snapshot.id])
res = self.client.post(url, formData)
self.assertEqual(res.status_code, 302)
api_manila.share_snapshot_deny.assert_called_with(
mock.ANY, snapshot.id, rule.id)
api_manila.share_snapshot_rules_list.assert_called_with(
mock.ANY, snapshot.id)

View File

@ -35,6 +35,7 @@ from manilaclient.v2 import share_export_locations
from manilaclient.v2 import share_instances
from manilaclient.v2 import share_replicas
from manilaclient.v2 import share_servers
from manilaclient.v2 import share_snapshot_export_locations
from openstack_dashboard import api
from openstack_dashboard.usage import quotas as usage_quotas
@ -57,7 +58,8 @@ share = shares.Share(
'share_server_id': '1',
'share_network_id': '7f3d1c33-8d00-4511-29df-a2def31f3b5d',
'availability_zone': 'Test AZ',
'replication_type': 'readable'})
'replication_type': 'readable',
'mount_snapshot_support': False})
nameless_share = shares.Share(
shares.ShareManager(FakeAPIClient),
@ -73,7 +75,8 @@ nameless_share = shares.Share(
'share_type': 'vol_type_1',
'share_server_id': '1',
'share_network_id': '7f3d1c33-8d00-4511-29df-a2def31f3b5d',
'availability_zone': 'Test AZ'})
'availability_zone': 'Test AZ',
'mount_snapshot_support': False})
share_with_metadata = shares.Share(
shares.ShareManager(FakeAPIClient),
@ -87,7 +90,8 @@ share_with_metadata = shares.Share(
'created_at': '2016-06-31 00:00:00',
'share_server_id': '1',
'share_network_id': '7f3d1c33-8d00-4511-29df-a2def31f3b5d',
'availability_zone': 'Test AZ'})
'availability_zone': 'Test AZ',
'mount_snapshot_support': False})
other_share = shares.Share(
shares.ShareManager(FakeAPIClient),
@ -102,7 +106,8 @@ other_share = shares.Share(
'share_type': None,
'share_server_id': '1',
'share_network_id': '7f3d1c33-8d00-4511-29df-a2def31f3b5d',
'availability_zone': 'Test AZ'})
'availability_zone': 'Test AZ',
'mount_snapshot_support': False})
share_replica = share_replicas.ShareReplica(
share_replicas.ShareReplicaManager(FakeAPIClient),
@ -140,6 +145,22 @@ share_replica3 = share_replicas.ShareReplica(
'updated_at': '2016-07-19 21:47:14'}
)
share_mount_snapshot = shares.Share(
shares.ShareManager(FakeAPIClient),
{'id': "11023e92-8008-4c8b-8059-7f2293ff3888",
'status': 'available',
'size': 40,
'name': 'Share name',
'description': 'Share description',
'share_proto': 'NFS',
'metadata': {},
'created_at': '2014-01-27 10:30:00',
'share_server_id': '1',
'share_network_id': '7f3d1c33-8d00-4511-29df-a2def31f3b5d',
'availability_zone': 'Test AZ',
'replication_type': 'readable',
'mount_snapshot_support': True})
admin_export_location = share_export_locations.ShareExportLocation(
share_export_locations.ShareExportLocationManager(FakeAPIClient),
{'id': '6921e862-88bc-49a5-a2df-efeed9acd583',
@ -160,6 +181,40 @@ user_export_location = share_export_locations.ShareExportLocation(
export_locations = [admin_export_location, user_export_location]
admin_snapshot_export_locations = [
share_snapshot_export_locations.ShareSnapshotExportLocation(
share_snapshot_export_locations.ShareSnapshotExportLocationManager(
FakeAPIClient),
{'id': '6921e862-88bc-49a5-a2df-efeed9acd584',
'path': '1.1.1.1:/path/to/admin/share',
'is_admin_only': True,
'share_snapshot_instance_id': 'e1c2d35e-fe67-4028-ad7a-45f668732b1e'}
),
share_snapshot_export_locations.ShareSnapshotExportLocation(
share_snapshot_export_locations.ShareSnapshotExportLocationManager(
FakeAPIClient),
{'id': '6921e862-88bc-49a5-a2df-efeed9acd585',
'path': '1.1.1.2:/path/to/admin/share',
'is_admin_only': False,
'share_snapshot_instance_id': 'e1c2d35e-fe67-4028-ad7a-45f668732b1f'}
)
]
user_snapshot_export_locations = [
share_snapshot_export_locations.ShareSnapshotExportLocation(
share_snapshot_export_locations.ShareSnapshotExportLocationManager(
FakeAPIClient),
{'id': 'b6bd76ce-12a2-42a9-a30a-8a43b503867e',
'path': '1.1.1.1:/path/to/user/share_snapshot'}
),
share_snapshot_export_locations.ShareSnapshotExportLocation(
share_snapshot_export_locations.ShareSnapshotExportLocationManager(
FakeAPIClient),
{'id': 'b6bd76ce-12a2-42a9-a30a-8a43b503867f',
'path': '1.1.1.2:/not/too/long/path/to/user/share_snapshot'}
)
]
rule = collections.namedtuple('Access', ['access_type', 'access_to', 'state',
'id', 'access_level', 'access_key'])
@ -180,6 +235,15 @@ snapshot = share_snapshots.ShareSnapshot(
'status': 'available',
'share_id': '11023e92-8008-4c8b-8059-7f2293ff3887'})
snapshot_mount_support = share_snapshots.ShareSnapshot(
share_snapshots.ShareSnapshotManager(FakeAPIClient),
{'id': '5f3d1c33-7d00-4511-99df-a2def31f3b5e',
'name': 'test snapshot',
'description': 'share snapshot',
'size': 40,
'status': 'available',
'share_id': '11023e92-8008-4c8b-8059-7f2293ff3888'})
inactive_share_network = share_networks.ShareNetwork(
share_networks.ShareNetworkManager(FakeAPIClient),
{'id': '6f3d1c33-8d00-4511-29df-a2def31f3b5d',

View File

@ -30,16 +30,22 @@ INDEX_URL = reverse('horizon:project:shares:index')
class SharesTests(test.TestCase):
def test_index_with_all_tabs(self):
snaps = [test_data.snapshot]
snaps = [test_data.snapshot, test_data.snapshot_mount_support]
shares = [test_data.share, test_data.nameless_share,
test_data.other_share]
share_networks = [test_data.inactive_share_network,
test_data.active_share_network]
security_services = [test_data.sec_service]
snap_shares = [test_data.share, test_data.share_mount_snapshot]
self.mock_object(
api_manila, "share_list", mock.Mock(return_value=shares))
self.mock_object(
api_manila, "share_snapshot_list", mock.Mock(return_value=snaps))
self.mock_object(
api_manila, "share_get", mock.Mock(return_value=snap_shares[0]))
self.mock_object(
api_manila, "share_get", mock.Mock(return_value=snap_shares[1]))
self.mock_object(
api_manila, "share_network_list",
mock.Mock(return_value=share_networks))
@ -67,6 +73,10 @@ class SharesTests(test.TestCase):
api_neutron.subnet_list.assert_called_once_with(mock.ANY)
api_manila.security_service_list.assert_called_once_with(mock.ANY)
api_manila.share_snapshot_list.assert_called_with(mock.ANY)
api_manila.share_get.assert_has_calls([
mock.call(mock.ANY, snaps[0].share_id),
mock.call(mock.ANY, snaps[1].share_id)
])
api_manila.share_list.assert_called_with(mock.ANY)
api_manila.share_network_list.assert_has_calls([
mock.call(mock.ANY),

View File

@ -0,0 +1,4 @@
---
features:
- Added support for the mountable snapshots feature to manila-ui.