Add user group assignment API

Add methods for adding and removing users to/from groups and
checking for membership.

This also updates the task_manager code to look for more types
that we don't want to munchify because one of the new keystone
API methods being called returned a bool value.

This replaces Ida3cff3acdc1406c5e6d61500766a292565191fc

Change-Id: Ib34c116010312ed26b042621fcf2e7b5b774424f
This commit is contained in:
David Shrewsbury 2015-11-18 09:45:38 -05:00
parent 0189dd248a
commit 72190ec191
6 changed files with 233 additions and 14 deletions

View File

@ -42,6 +42,21 @@ class UserGet(task_manager.Task):
return client.keystone_client.users.get(**self.args)
class UserAddToGroup(task_manager.Task):
def main(self, client):
return client.keystone_client.users.add_to_group(**self.args)
class UserCheckInGroup(task_manager.Task):
def main(self, client):
return client.keystone_client.users.check_in_group(**self.args)
class UserRemoveFromGroup(task_manager.Task):
def main(self, client):
return client.keystone_client.users.remove_from_group(**self.args)
class ProjectList(task_manager.Task):
def main(self, client):
return client._project_manager.list()

View File

@ -554,6 +554,86 @@ class OpenStackCloud(object):
self.list_users.invalidate(self)
return True
def _get_user_and_group(self, user_name_or_id, group_name_or_id):
user = self.get_user(user_name_or_id)
if not user:
raise OpenStackCloudException(
'User {user} not found'.format(user=user_name_or_id))
group = self.get_group(group_name_or_id)
if not group:
raise OpenStackCloudException(
'Group {user} not found'.format(user=group_name_or_id))
return (user, group)
def add_user_to_group(self, name_or_id, group_name_or_id):
"""Add a user to a group.
:param string name_or_id: User name or ID
:param string group_name_or_id: Group name or ID
:raises: ``OpenStackCloudException`` if something goes wrong during
the openstack API call
"""
user, group = self._get_user_and_group(name_or_id, group_name_or_id)
with _utils.shade_exceptions(
"Error adding user {user} to group {group}".format(
user=name_or_id, group=group_name_or_id)
):
self.manager.submitTask(
_tasks.UserAddToGroup(user=user['id'], group=group['id'])
)
def is_user_in_group(self, name_or_id, group_name_or_id):
"""Check to see if a user is in a group.
:param string name_or_id: User name or ID
:param string group_name_or_id: Group name or ID
:returns: True if user is in the group, False otherwise
:raises: ``OpenStackCloudException`` if something goes wrong during
the openstack API call
"""
user, group = self._get_user_and_group(name_or_id, group_name_or_id)
try:
return self.manager.submitTask(
_tasks.UserCheckInGroup(user=user['id'], group=group['id'])
)
except keystoneauth1.exceptions.http.NotFound:
# Because the keystone API returns either True or raises an
# exception, which is awesome.
return False
except OpenStackCloudException:
raise
except Exception as e:
raise OpenStackCloudException(
"Error adding user {user} to group {group}: {err}".format(
user=name_or_id, group=group_name_or_id, err=str(e))
)
def remove_user_from_group(self, name_or_id, group_name_or_id):
"""Remove a user from a group.
:param string name_or_id: User name or ID
:param string group_name_or_id: Group name or ID
:raises: ``OpenStackCloudException`` if something goes wrong during
the openstack API call
"""
user, group = self._get_user_and_group(name_or_id, group_name_or_id)
with _utils.shade_exceptions(
"Error removing user {user} from group {group}".format(
user=name_or_id, group=group_name_or_id)
):
self.manager.submitTask(
_tasks.UserRemoveFromGroup(user=user['id'], group=group['id'])
)
@property
def glance_client(self):
if self._glance_client is None:

View File

@ -79,9 +79,11 @@ class Task(object):
if self._exception:
six.reraise(type(self._exception), self._exception,
self._traceback)
if type(self._result) == list:
return meta.obj_list_to_dict(self._result)
elif not isinstance(self._result, types.GeneratorType):
elif type(self._result) not in (bool, int, float, str, set,
types.GeneratorType):
return meta.obj_to_dict(self._result)
else:
return self._result

View File

@ -19,9 +19,6 @@ test_users
Functional tests for `shade` user methods.
"""
import random
import string
from shade import operator_cloud
from shade import OpenStackCloudException
from shade.tests import base
@ -31,8 +28,7 @@ class TestUsers(base.TestCase):
def setUp(self):
super(TestUsers, self).setUp()
self.cloud = operator_cloud(cloud='devstack-admin')
self.user_prefix = 'test_user' + ''.join(
random.choice(string.ascii_lowercase) for _ in range(5))
self.user_prefix = self.getUniqueString('user')
self.addCleanup(self._cleanup_users)
def _cleanup_users(self):
@ -111,3 +107,28 @@ class TestUsers(base.TestCase):
self.assertEqual(user_name + '2', new_user['name'])
self.assertEqual('somebody@nowhere.com', new_user['email'])
self.assertFalse(new_user['enabled'])
def test_users_and_groups(self):
if self.cloud.cloud_config.get_api_version('identity') in ('2', '2.0'):
self.skipTest('Identity service does not support groups')
group_name = self.getUniqueString('group')
self.addCleanup(self.cloud.delete_group, group_name)
# Create a group
group = self.cloud.create_group(group_name, 'test group')
self.assertIsNotNone(group)
# Create a user
user_name = self.user_prefix + '_ug'
user_email = 'nobody@nowhere.com'
user = self._create_user(name=user_name, email=user_email)
self.assertIsNotNone(user)
# Add the user to the group
self.cloud.add_user_to_group(user_name, group_name)
self.assertTrue(self.cloud.is_user_in_group(user_name, group_name))
# Remove them from the group
self.cloud.remove_user_from_group(user_name, group_name)
self.assertFalse(self.cloud.is_user_in_group(user_name, group_name))

View File

@ -13,10 +13,8 @@
# limitations under the License.
import mock
import types
import shade
from shade import task_manager
from shade.tests.unit import base
@ -35,6 +33,31 @@ class TestTaskGenerator(task_manager.Task):
yield 1
class TestTaskInt(task_manager.Task):
def main(self, client):
return int(1)
class TestTaskFloat(task_manager.Task):
def main(self, client):
return float(2.0)
class TestTaskStr(task_manager.Task):
def main(self, client):
return "test"
class TestTaskBool(task_manager.Task):
def main(self, client):
return True
class TestTaskSet(task_manager.Task):
def main(self, client):
return set([1, 2])
class TestTaskManager(base.TestCase):
def setUp(self):
@ -50,10 +73,26 @@ class TestTaskManager(base.TestCase):
"""
self.assertRaises(TestException, self.manager.submitTask, TestTask())
@mock.patch.object(shade.meta, 'obj_to_dict')
@mock.patch.object(shade.meta, 'obj_list_to_dict')
def test_dont_munchify_generators(self, mock_ol2d, mock_o2d):
def test_dont_munchify_generators(self):
ret = self.manager.submitTask(TestTaskGenerator())
self.assertEqual(types.GeneratorType, type(ret))
self.assertFalse(mock_o2d.called)
self.assertFalse(mock_ol2d.called)
self.assertIsInstance(ret, types.GeneratorType)
def test_dont_munchify_int(self):
ret = self.manager.submitTask(TestTaskInt())
self.assertIsInstance(ret, int)
def test_dont_munchify_float(self):
ret = self.manager.submitTask(TestTaskFloat())
self.assertIsInstance(ret, float)
def test_dont_munchify_str(self):
ret = self.manager.submitTask(TestTaskStr())
self.assertIsInstance(ret, str)
def test_dont_munchify_bool(self):
ret = self.manager.submitTask(TestTaskBool())
self.assertIsInstance(ret, bool)
def test_dont_munchify_set(self):
ret = self.manager.submitTask(TestTaskSet())
self.assertIsInstance(ret, set)

View File

@ -0,0 +1,62 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import munch
import shade
from shade.tests.unit import base
class TestUsers(base.TestCase):
def setUp(self):
super(TestUsers, self).setUp()
self.cloud = shade.operator_cloud(validate=False)
@mock.patch.object(shade.OpenStackCloud, 'get_user')
@mock.patch.object(shade.OperatorCloud, 'get_group')
@mock.patch.object(shade.OpenStackCloud, 'keystone_client')
def test_add_user_to_group(self, mock_keystone, mock_group, mock_user):
mock_user.return_value = munch.Munch(dict(id=1))
mock_group.return_value = munch.Munch(dict(id=2))
self.cloud.add_user_to_group("user", "group")
mock_keystone.users.add_to_group.assert_called_once_with(
user=1, group=2
)
@mock.patch.object(shade.OpenStackCloud, 'get_user')
@mock.patch.object(shade.OperatorCloud, 'get_group')
@mock.patch.object(shade.OpenStackCloud, 'keystone_client')
def test_is_user_in_group(self, mock_keystone, mock_group, mock_user):
mock_user.return_value = munch.Munch(dict(id=1))
mock_group.return_value = munch.Munch(dict(id=2))
mock_keystone.users.check_in_group.return_value = True
self.assertTrue(self.cloud.is_user_in_group("user", "group"))
mock_keystone.users.check_in_group.assert_called_once_with(
user=1, group=2
)
@mock.patch.object(shade.OpenStackCloud, 'get_user')
@mock.patch.object(shade.OperatorCloud, 'get_group')
@mock.patch.object(shade.OpenStackCloud, 'keystone_client')
def test_remove_user_from_group(self, mock_keystone, mock_group,
mock_user):
mock_user.return_value = munch.Munch(dict(id=1))
mock_group.return_value = munch.Munch(dict(id=2))
self.cloud.remove_user_from_group("user", "group")
mock_keystone.users.remove_from_group.assert_called_once_with(
user=1, group=2
)