API v2.45, adds metadata support to access rules

Added access metadata for share access and also introduced the GET /share-access-rules API.
The prior API to retrieve access rules will not work with API version >=2.45

Closes-Bug: #1920687
Change-Id: Iec3a3fad5e2bdf854f04ae974248d899f90bd894
This commit is contained in:
Dina Saparbaeva 2021-03-25 15:43:31 +00:00 committed by Dina Saparbaeva
parent eb52c16c65
commit 8b5b9cb83b
12 changed files with 316 additions and 36 deletions

View File

@ -81,6 +81,21 @@ Deny access
A message indicates whether the action was successful.
Edit share access metadata
--------------------------
#. Log in to the dashboard, choose a project, and click :guilabel:`Shares`.
#. Go to the share that you want to deny access and choose
:guilabel:`Manage Rules` from Actions.
#. Choose the rule you want to edit.
#. Click :guilabel:`Edit Rule Metadata`: To add share access metadata, use key=value.
To unset metadata, use key.
A message indicates whether the action was successful.
Edit share metadata
-------------------

View File

@ -28,7 +28,7 @@ from manilaclient import client as manila_client
LOG = logging.getLogger(__name__)
MANILA_UI_USER_AGENT_REPR = "manila_ui_plugin_for_horizon"
MANILA_VERSION = "2.44"
MANILA_VERSION = "2.45"
MANILA_SERVICE_TYPE = "sharev2"
# API static values
@ -125,7 +125,21 @@ def share_update(request, share_id, name, description, is_public=False):
def share_rules_list(request, share_id):
return manilaclient(request).shares.access_list(share_id)
return manilaclient(request).share_access_rules.access_list(share_id)
def share_rule_get(request, rule_id):
return manilaclient(request).share_access_rules.get(rule_id)
def share_rule_set_metadata(request, rule, metadata):
return manilaclient(request).share_access_rules.set_metadata(
rule, metadata)
def share_rule_unset_metadata(request, rule, keys):
return manilaclient(request).share_access_rules.unset_metadata(
rule, keys)
def share_export_location_list(request, share_id):
@ -137,9 +151,10 @@ def share_instance_export_location_list(request, share_instance_id):
share_instance_id)
def share_allow(request, share_id, access_type, access_to, access_level):
def share_allow(request, share_id, access_type, access_to, access_level,
metadata=None):
return manilaclient(request).shares.allow(
share_id, access_type, access_to, access_level)
share_id, access_type, access_to, access_level, metadata)
def share_deny(request, share_id, rule_id):

View File

@ -334,15 +334,29 @@ class AddRule(forms.SelfHandlingForm):
choices=(('rw', 'read-write'), ('ro', 'read-only'),))
access_to = forms.CharField(
label=_("Access To"), max_length="255", required=True)
metadata = forms.CharField(
label=_("Metadata"), required=False,
widget=forms.Textarea(attrs={'rows': 4}))
def handle(self, request, data):
share_id = self.initial['share_id']
metadata = {}
try:
set_dict, unset_list = utils.parse_str_meta(data['metadata'])
if unset_list:
msg = _("Expected only pairs of key=value.")
raise ValidationError(message=msg)
metadata = set_dict
except ValidationError as e:
self.api_error(e.messages[0])
return False
try:
manila.share_allow(
request, share_id,
access_to=data['access_to'],
access_type=data['access_type'],
access_level=data['access_level'])
access_level=data['access_level'],
metadata=metadata)
message = _('Creating rule for "%s"') % data['access_to']
messages.success(request, message)
return True
@ -353,6 +367,39 @@ class AddRule(forms.SelfHandlingForm):
request, _('Unable to add rule.'), redirect=redirect)
class UpdateRuleMetadataForm(forms.SelfHandlingForm):
metadata = forms.CharField(widget=forms.Textarea,
label=_("Metadata"), required=False)
def __init__(self, *args, **kwargs):
super(UpdateRuleMetadataForm, self).__init__(*args, **kwargs)
rule_metadata = utils.metadata_to_str(
self.initial["metadata"]
).replace('<br/>', '\r\n')
self.initial["metadata"] = rule_metadata
def handle(self, request, data):
rule_id = self.initial['rule_id']
try:
rule = manila.share_rule_get(self.request, rule_id)
set_dict, unset_list = utils.parse_str_meta(data['metadata'])
if set_dict:
manila.share_rule_set_metadata(request, rule, set_dict)
if unset_list:
manila.share_rule_unset_metadata(request, rule, unset_list)
message = _('Updating share access rule metadata ')
messages.success(request, message)
return True
except ValidationError as e:
self.api_error(e.messages[0])
return False
except Exception:
redirect = reverse("horizon:project:shares:manage_rules")
exceptions.handle(request,
_('Unable to update rule metadata.'),
redirect=redirect)
class ResizeForm(forms.SelfHandlingForm):
name = forms.CharField(
max_length="255", label=_("Share Name"),

View File

@ -347,16 +347,30 @@ class DeleteRule(tables.DeleteAction):
exceptions.handle(request, msg)
class EditRuleMetadata(tables.LinkAction):
name = "update_rule_metadata"
verbose_name = _("Edit Rule Metadata")
url = "horizon:project:shares:update_rule_metadata"
classes = ("ajax-modal", "btn-create")
policy_rules = (("share_access_metadata", "share_access_metadata:update"),)
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}
def allowed(self, request, rule=None):
return rule.state == "active"
class UpdateRuleRow(tables.Row):
ajax = True
def get_data(self, request, rule_id):
rules = manila.share_rules_list(request, self.table.kwargs['share_id'])
if rules:
for rule in rules:
if rule.id == rule_id:
return rule
raise exceptions.NotFound
rule = manila.share_rule_get(request, rule_id)
rule.metadata = utils.metadata_to_str(rule.metadata)
return rule
class RulesTable(tables.DataTable):
@ -364,6 +378,8 @@ class RulesTable(tables.DataTable):
access_to = tables.Column("access_to", verbose_name=_("Access to"))
access_level = tables.Column(
"access_level", verbose_name=_("Access Level"))
metadata = tables.Column(
"metadata", verbose_name=_("Metadata"))
status = tables.Column("state", verbose_name=_("Status"))
access_key = tables.Column("access_key", verbose_name=_("Access Key"))
created_at = tables.Column("created_at", verbose_name=_("Created At"),
@ -383,7 +399,8 @@ class RulesTable(tables.DataTable):
AddRule,
DeleteRule)
row_actions = (
DeleteRule,)
DeleteRule,
EditRuleMetadata,)
def get_share_network(share):

View File

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

View File

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

View File

@ -36,6 +36,10 @@ urlpatterns = [
r'^(?P<share_id>[^/]+)/rule_add/$',
shares_views.AddRuleView.as_view(),
name='rule_add'),
urls.url(
r'^rules/(?P<rule_id>[^/]+)/update_rule_metadata/$',
shares_views.UpdateRuleMetadataView.as_view(),
name='update_rule_metadata'),
urls.url(
r'^(?P<share_id>[^/]+)/$',
shares_views.DetailView.as_view(),

View File

@ -249,6 +249,53 @@ class AddRuleView(forms.ModalFormView):
args=[self.kwargs['share_id']])
class UpdateRuleMetadataView(forms.ModalFormView):
form_class = share_form.UpdateRuleMetadataForm
form_id = ""
template_name = 'project/shares/update_rule_metadata.html'
modal_header = _("Edit Rule Metadata")
modal_id = "update_rule_metadata_modal"
submit_label = _("Save Changes")
submit_url = "horizon:project:shares:update_rule_metadata"
success_url = reverse_lazy("horizon:project:shares:index")
page_title = _('Edit Rule Metadata')
def get_object(self):
if not hasattr(self, "_object"):
rule_id = self.kwargs['rule_id']
try:
self._object = manila.share_rule_get(self.request, rule_id)
except Exception:
msg = _('Unable to retrieve share access rule.')
url = reverse('horizon:project:shares:manage_rules')
exceptions.handle(self.request, msg, redirect=url)
return self._object
def get_context_data(self, **kwargs):
context = super(UpdateRuleMetadataView, 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):
rule = self.get_object()
return {'rule_id': self.kwargs["rule_id"],
'metadata': rule.metadata}
# To redirect to Rules Table page, after updating the rule
# metadata. Loop is to get the share_id neccessary to display
# Rules Table page.
def get_success_url(self):
shares = manila.share_list(self.request)
for share in shares:
rules = manila.share_rules_list(self.request, share.id)
for rule in rules:
if rule.id == self.kwargs["rule_id"]:
return reverse("horizon:project:shares:manage_rules",
args=[share.id])
class ManageRulesView(tables.DataTableView):
table_class = shares_tables.RulesTable
template_name = 'project/shares/manage_rules.html'
@ -257,7 +304,6 @@ class ManageRulesView(tables.DataTableView):
context = super(ManageRulesView, self).get_context_data(**kwargs)
share = manila.share_get(self.request, self.kwargs['share_id'])
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']}
@ -273,6 +319,11 @@ class ManageRulesView(tables.DataTableView):
exceptions.handle(self.request,
_('Unable to retrieve share rules.'),
redirect=redirect)
return []
for rule in rules:
rule.metadata = ui_utils.metadata_to_str(rule.metadata)
return rules

View File

@ -103,9 +103,42 @@ class ManilaApiTests(base.APITestCase):
result = api.share_rules_list(self.request, self.id)
self.assertEqual(
self.manilaclient.shares.access_list.return_value, result)
self.manilaclient.share_access_rules.
access_list.return_value, result)
self.manilaclient.shares.access_list.assert_called_once_with(self.id)
self.manilaclient.share_access_rules.\
access_list.assert_called_once_with(self.id)
def test_share_rule_get(self):
result = api.share_rule_get(self.request, self.id)
self.assertEqual(
self.manilaclient.share_access_rules.get.
return_value, result)
self.manilaclient.share_access_rules.get.\
assert_called_once_with(self.id)
def test_share_rule_set_metadata(self):
fake_metadata = {
"aim": "testing",
"project": "test",
}
api.share_rule_set_metadata(
self.request, self.id, fake_metadata)
self.manilaclient.share_access_rules.\
set_metadata.assert_called_once_with(self.id, fake_metadata)
def test_share_rule_unset_metadata(self):
fake_key = "test"
api.share_rule_unset_metadata(
self.request, self.id, fake_key)
self.manilaclient.share_access_rules.\
unset_metadata.assert_called_once_with(self.id, fake_key)
def test_list_share_export_locations(self):
api.share_export_location_list(self.request, self.id)
@ -120,19 +153,22 @@ class ManilaApiTests(base.APITestCase):
client.share_instance_export_locations.list.assert_called_once_with(
self.id)
@ddt.data(("ip", "10.0.0.13", "rw"), ("ip", "10.0.0.13", None),
("ip", "10.0.0.13", "ro"),
("user", "demo", "rw"),
("user", "demo", None), ("user", "demo", "ro"),
("cephx", "alice", "rw"),
("cephx", "alice", None), ("cephx", "alice", "ro"))
@ddt.data(("ip", "10.0.0.13", "rw", {'testkey': "testval"}),
("ip", "10.0.0.13", None, None),
("ip", "10.0.0.13", "ro", {'key1': "val1"}),
("user", "demo", "rw", {'key2': "val2"}),
("user", "demo", None, None),
("user", "demo", "ro", None),
("cephx", "alice", "rw", {'test': "some"}),
("cephx", "alice", None, None),
("cephx", "alice", "ro", None))
@ddt.unpack
def test_share_allow(self, access_type, access_to, access_level):
def test_share_allow(self, access_type, access_to, access_level, metadata):
api.share_allow(self.request, self.id, access_type,
access_to, access_level)
access_to, access_level, metadata)
self.manilaclient.shares.allow.assert_called_once_with(
self.id, access_type, access_to, access_level)
self.id, access_type, access_to, access_level, metadata)
def test_share_deny(self):
fake_rule_id = "fake_rule_id"

View File

@ -300,7 +300,7 @@ class ShareViewTests(test.APITestCase):
[mock.call(mock.ANY, self.share.id) for i in (1, 2)])
def test_list_rules(self):
rules = [test_data.ip_rule, test_data.user_rule, test_data.cephx_rule]
rules = test_data.share_access_list
self.mock_object(
api_manila, "share_rules_list", mock.Mock(return_value=rules))
url = reverse(
@ -331,6 +331,7 @@ class ShareViewTests(test.APITestCase):
'method': 'CreateForm',
'access_to': 'someuser',
'access_level': 'rw',
'metadata': {},
}
res = self.client.post(url, formData)
@ -338,7 +339,8 @@ class ShareViewTests(test.APITestCase):
api_manila.share_allow.assert_called_once_with(
mock.ANY, self.share.id, access_type=formData['access_type'],
access_to=formData['access_to'],
access_level=formData['access_level'])
access_level=formData['access_level'],
metadata=formData['metadata'])
self.assertRedirectsNoFollow(
res,
reverse('horizon:project:shares:manage_rules',
@ -360,6 +362,43 @@ class ShareViewTests(test.APITestCase):
mock.ANY, self.share.id, rule.id)
api_manila.share_rules_list.assert_called_with(mock.ANY, self.share.id)
def test_update_share_rule_metadata_get(self):
rule = test_data.user_rule
url = reverse(
'horizon:project:shares:update_rule_metadata', args=[rule.id])
self.mock_object(
api_manila, "share_rule_get", mock.Mock(return_value=rule))
res = self.client.get(url)
api_manila.share_rule_get.assert_called_once_with(mock.ANY, rule.id)
self.assertNoMessages()
self.assertTemplateUsed(
res, 'project/shares/update_rule_metadata.html')
def test_update_share_rule_metadata_post(self):
rule = test_data.user_rule
data = {
'metadata': 'aaa=ccc',
}
form_data = {
'metadata': {'aaa': 'ccc'},
}
url = reverse(
'horizon:project:shares:update_rule_metadata', args=[rule.id])
self.mock_object(
api_manila, "share_list", mock.Mock(
return_value=test_data.shares_list))
self.mock_object(
api_manila, "share_rules_list", mock.Mock(
return_value=test_data.share_access_list))
self.mock_object(
api_manila, "share_rule_get", mock.Mock(return_value=rule))
self.mock_object(api_manila, "share_rule_set_metadata")
self.client.post(url, data)
api_manila.share_rule_set_metadata.assert_called_once_with(
mock.ANY, rule, form_data['metadata'])
def test_resize_share_get(self):
share = test_data.share
url = reverse('horizon:project:shares:resize', args=[share.id])

View File

@ -11,10 +11,10 @@
# 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 collections
from manilaclient.v2 import messages
from manilaclient.v2 import security_services
from manilaclient.v2 import share_access_rules
from manilaclient.v2 import share_export_locations
from manilaclient.v2 import share_group_snapshots
from manilaclient.v2 import share_group_types
@ -102,6 +102,8 @@ other_share = shares.Share(
'replication_type': 'readable',
'mount_snapshot_support': False})
shares_list = [share, nameless_share, other_share]
share_replica = share_replicas.ShareReplica(
share_replicas.ShareReplicaManager(FakeAPIClient),
{'id': '11023e92-8008-4c8b-8059-replica00001',
@ -207,17 +209,46 @@ user_snapshot_export_locations = [
'path': '1.1.1.2:/not/too/long/path/to/user/share_snapshot'}
)
]
ip_rule = share_access_rules.ShareAccessRule(
share_access_rules.ShareAccessRuleManager(FakeAPIClient),
{'id': 'ca8b755c-fe13-497f-81d0-fd2f13a30a78',
'access_level': 'rw',
'access_to': '1.1.1.1',
'access_type': 'ip',
'state': 'active',
'access_key': '',
'created_at': '2021-03-03T14:29:41.000000',
'updated_at': '',
"metadata": {},
})
rule = collections.namedtuple('Access', ['access_type', 'access_to', 'state',
'id', 'access_level', 'access_key'])
user_rule = share_access_rules.ShareAccessRule(
share_access_rules.ShareAccessRuleManager(FakeAPIClient),
{'id': '0837072-c49e-11e3-bd64-60a44c371189',
'access_level': 'rw',
'access_to': 'someuser',
'access_type': 'user',
'state': 'active',
'access_key': '',
'created_at': '2021-03-03T14:29:41.000000',
'updated_at': '',
'metadata': {'abc': 'ddd'},
})
user_rule = rule('user', 'someuser', 'active',
'10837072-c49e-11e3-bd64-60a44c371189', 'rw', '')
ip_rule = rule('ip', '1.1.1.1', 'active',
'2cc8e2f8-c49e-11e3-bd64-60a44c371189', 'rw', '')
cephx_rule = rule('cephx', 'alice', 'active',
'235481bc-1a84-11e6-9666-68f728a0492e', 'rw',
'AQAdFCNYDCapMRAANuK/CiEZbog2911a+t5dcQ==')
cephx_rule = share_access_rules.ShareAccessRule(
share_access_rules.ShareAccessRuleManager(FakeAPIClient),
{'id': '235481bc-1a84-11e6-9666-68f728a0492e',
'access_level': 'rw',
'access_to': 'alice',
'access_type': 'cephx',
'state': 'active',
'access_key': 'AQAdFCNYDCapMRAANuK/CiEZbog2911a+t5dcQ==',
'created_at': '2021-03-03T14:29:41.000000',
'updated_at': '',
'metadata': {'test': 'true'},
})
share_access_list = [user_rule, cephx_rule, ip_rule]
snapshot = share_snapshots.ShareSnapshot(
share_snapshots.ShareSnapshotManager(FakeAPIClient),

View File

@ -0,0 +1,5 @@
---
features:
- |
Added access metadata for share access and
also introduced the GET /share-access-rules API.