diff --git a/openstack_dashboard/api/rest/keystone.py b/openstack_dashboard/api/rest/keystone.py index 32df3ad412..b78d411d0f 100644 --- a/openstack_dashboard/api/rest/keystone.py +++ b/openstack_dashboard/api/rest/keystone.py @@ -589,3 +589,69 @@ class Groups(generic.View): request, domain=request.GET.get('domain_id', domain_context))] return {'items': items} + + @rest_utils.ajax(data_required=True) + def post(self, request): + """Create a group. + + This action creates a group using parameters supplied in the POST + application/json object. The "name" (string) parameter is required, + "description" (string) is optional. + + This method returns the new group object on success. + """ + domain_context = request.session.get('domain_context') + new_group = api.keystone.group_create( + request, + request.GET.get('domain_id', domain_context), + request.DATA['name'], + request.DATA.get("description", None)) + + return rest_utils.CreatedResponse( + '/api/keystone/groups/%s' % new_group.id, + new_group.to_dict() + ) + + @rest_utils.ajax(data_required=True) + def delete(self, request): + """Delete multiple groups by id. + + The DELETE data should be an application/json array of group ids to + delete. + + This method returns HTTP 204 (no content) on success. + """ + for group_id in request.DATA: + api.keystone.group_delete(request, group_id) + + +@urls.register +class Group(generic.View): + """API over a single group.""" + url_regex = r'keystone/groups/(?P[0-9a-f]+)$' + + @rest_utils.ajax() + def get(self, request, id): + """Get a specific group by id.""" + return api.keystone.group_get(request, id).to_dict() + + @rest_utils.ajax() + def delete(self, request, id): + """Delete a single group by id. + + This method returns HTTP 204 (no content) on success. + """ + api.keystone.group_delete(request, id) + + @rest_utils.ajax(data_required=True) + def patch(self, request, id): + """Update a single group. + + The PATCH data should be an application/json object with the + "name" and "description" attribute to update. + + This method returns HTTP 204 (no content) on success. + """ + api.keystone.group_update(request, id, + request.DATA['name'], + request.DATA.get("description", None)) diff --git a/openstack_dashboard/static/app/core/openstack-service-api/keystone.service.js b/openstack_dashboard/static/app/core/openstack-service-api/keystone.service.js index 81da53f21f..f445dc998b 100644 --- a/openstack_dashboard/static/app/core/openstack-service-api/keystone.service.js +++ b/openstack_dashboard/static/app/core/openstack-service-api/keystone.service.js @@ -58,7 +58,12 @@ grantRole: grantRole, serviceCatalog: serviceCatalog, getServices: getServices, - getGroups: getGroups + getGroups: getGroups, + createGroup: createGroup, + getGroup: getGroup, + editGroup: editGroup, + deleteGroup: deleteGroup, + deleteGroups: deleteGroups }; return service; @@ -103,6 +108,7 @@ }); } + // Group function getGroups() { return apiService.get('/api/keystone/groups/') .error(function () { @@ -110,6 +116,42 @@ }); } + function createGroup(newGroup) { + return apiService.post('/api/keystone/groups/', newGroup) + .error(function () { + toastService.add('error', gettext('Unable to create the group.')); + }); + } + + function getGroup(groupId) { + return apiService.get('/api/keystone/groups/' + groupId) + .error(function () { + toastService.add('error', gettext('Unable to retrieve the group.')); + }); + } + + function editGroup(updatedGroup) { + var url = '/api/keystone/groups/' + updatedGroup.id; + return apiService.patch(url, updatedGroup) + .error(function () { + toastService.add('error', gettext('Unable to edit the group.')); + }); + } + + function deleteGroup(groupId) { + return apiService.delete('/api/keystone/groups/' + groupId) + .error(function () { + toastService.add('error', gettext('Unable to delete the group.')); + }); + } + + function deleteGroups(groupIds) { + return apiService.delete('/api/keystone/groups/', groupIds) + .error(function () { + toastService.add('error', gettext('Unable to delete the groups.')); + }); + } + /** * @name getCurrentUserSession * @param {Object} config - The configuration for which we want a session diff --git a/openstack_dashboard/static/app/core/openstack-service-api/keystone.service.spec.js b/openstack_dashboard/static/app/core/openstack-service-api/keystone.service.spec.js index b672e75ad8..4062d66164 100644 --- a/openstack_dashboard/static/app/core/openstack-service-api/keystone.service.spec.js +++ b/openstack_dashboard/static/app/core/openstack-service-api/keystone.service.spec.js @@ -390,6 +390,66 @@ "method": "get", "path": "/api/keystone/groups/", "error": "Unable to fetch the groups." + }, + { + "func": "createGroup", + "method": "post", + "path": "/api/keystone/groups/", + "data": "new group", + "error": "Unable to create the group.", + "testInput": [ + "new group" + ] + }, + { + "func": "getGroup", + "method": "get", + "path": "/api/keystone/groups/14", + "error": "Unable to retrieve the group.", + "testInput": [ + 14 + ] + }, + { + "func": "editGroup", + "method": "patch", + "path": "/api/keystone/groups/42", + "data": { + "id": 42 + }, + "error": "Unable to edit the group.", + "testInput": [ + { + "id": 42 + } + ] + }, + { + "func": "deleteGroup", + "method": "delete", + "path": "/api/keystone/groups/14", + "error": "Unable to delete the group.", + "testInput": [ + 14 + ] + }, + { + "func": "deleteGroups", + "method": "delete", + "path": "/api/keystone/groups/", + "data": [ + 1, + 2, + 3 + ], + "error": "Unable to delete the groups.", + "testInput": [ + [ + 1, + 2, + 3 + ] + ] } ]; diff --git a/openstack_dashboard/test/api_tests/keystone_rest_tests.py b/openstack_dashboard/test/api_tests/keystone_rest_tests.py index 0a2f9acfd2..e9cec3e5e1 100644 --- a/openstack_dashboard/test/api_tests/keystone_rest_tests.py +++ b/openstack_dashboard/test/api_tests/keystone_rest_tests.py @@ -692,6 +692,97 @@ class KeystoneRestTestCase(test.TestCase): {"items": [{"name": "uno!"}, {"name": "dos!"}]}) kc.group_list.assert_called_once_with(request, domain='the_domain') + @mock.patch.object(keystone.api, 'keystone') + def test_group_create(self, kc): + request = self.mock_rest_request(**{ + 'session.get': mock.Mock(return_value='the_domain'), + 'GET': {}, + 'body': '{"name": "bug!", "description": "bugaboo!!"}', + }) + kc.group_create.return_value.id = 'group789' + kc.group_create.return_value.to_dict.return_value = { + 'id': 'group789', 'name': 'bug!', 'description': 'bugaboo!!' + } + + response = keystone.Groups().post(request) + self.assertStatusCode(response, 201) + self.assertEqual(response['location'], + '/api/keystone/groups/group789') + self.assertEqual(response.json, + {"id": "group789", + "name": "bug!", + "description": "bugaboo!!"}) + kc.group_create.assert_called_once_with(request, 'the_domain', + 'bug!', 'bugaboo!!') + + @mock.patch.object(keystone.api, 'keystone') + def test_group_create_without_description(self, kc): + request = self.mock_rest_request(**{ + 'session.get': mock.Mock(return_value='the_domain'), + 'GET': {}, + 'body': '{"name": "bug!"}', + }) + kc.group_create.return_value.id = 'group789' + kc.group_create.return_value.to_dict.return_value = { + 'id': 'group789', 'name': 'bug!' + } + + response = keystone.Groups().post(request) + self.assertStatusCode(response, 201) + self.assertEqual(response['location'], + '/api/keystone/groups/group789') + self.assertEqual(response.json, + {"id": "group789", + "name": "bug!"}) + kc.group_create.assert_called_once_with(request, 'the_domain', + 'bug!', None) + + @mock.patch.object(keystone.api, 'keystone') + def test_group_get(self, kc): + request = self.mock_rest_request() + kc.group_get.return_value.to_dict.return_value = { + 'name': 'bug!', 'description': 'bugaboo!!'} + response = keystone.Group().get(request, 'the_id') + self.assertStatusCode(response, 200) + self.assertEqual(response.json, {"name": "bug!", + "description": "bugaboo!!"}) + kc.group_get.assert_called_once_with(request, 'the_id') + + @mock.patch.object(keystone.api, 'keystone') + def test_group_delete(self, kc): + request = self.mock_rest_request() + response = keystone.Group().delete(request, 'the_id') + self.assertStatusCode(response, 204) + self.assertEqual(response.content, b'') + kc.group_delete.assert_called_once_with(request, 'the_id') + + @mock.patch.object(keystone.api, 'keystone') + def test_group_patch(self, kc): + request = self.mock_rest_request( + body='{"name": "spam_i_am", "description": "Sir Spam"}') + response = keystone.Group().patch(request, 'the_id') + self.assertStatusCode(response, 204) + self.assertEqual(response.content, b'') + kc.group_update.assert_called_once_with(request, + 'the_id', + 'spam_i_am', + 'Sir Spam') + + @mock.patch.object(keystone.api, 'keystone') + def test_group_delete_many(self, kc): + request = self.mock_rest_request(body=''' + ["id1", "id2", "id3"] + ''') + + response = keystone.Groups().delete(request) + self.assertStatusCode(response, 204) + self.assertEqual(response.content, b'') + kc.group_delete.assert_has_calls([ + mock.call(request, 'id1'), + mock.call(request, 'id2'), + mock.call(request, 'id3'), + ]) + # # Services #