Merge "Add mountable snapshots support to manila-ui"
This commit is contained in:
commit
863e927b3c
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %}
|
|
@ -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">
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Share Rules" %}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
|
@ -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 %}
|
|
@ -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():
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
features:
|
||||
- Added support for the mountable snapshots feature to manila-ui.
|
||||
|
Loading…
Reference in New Issue