Implement CRUD operations for Mapping objects

Change-Id: I4b8f2e77e741cf74f50aba98ab975af7321b02c6
Implements: bp/add-openstackclient-federation-crud
This commit is contained in:
Marek Denis 2014-04-10 18:43:51 +02:00
parent 02320a5a24
commit 30b0a41ce7
4 changed files with 515 additions and 0 deletions

View File

@ -0,0 +1,209 @@
# Copyright 2014 CERN
#
# 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.
#
"""Identity v3 federation mapping action implementations"""
import json
import logging
from cliff import command
from cliff import lister
from cliff import show
import six
from openstackclient.common import exceptions
from openstackclient.common import utils
class _RulesReader(object):
"""Helper class capable of reading rules from files"""
def _read_rules(self, path):
"""Read and parse rules from path
Expect the file to contain a valid JSON structure.
:param path: path to the file
:return: loaded and valid dictionary with rules
:raises exception.CommandError: In case the file cannot be
accessed or the content is not a valid JSON.
Example of the content of the file:
[
{
"local": [
{
"group": {
"id": "85a868"
}
}
],
"remote": [
{
"type": "orgPersonType",
"any_one_of": [
"Employee"
]
},
{
"type": "sn",
"any_one_of": [
"Young"
]
}
]
}
]
"""
blob = utils.read_blob_file_contents(path)
try:
rules = json.loads(blob)
except ValueError as e:
raise exceptions.CommandError(
'An error occurred when reading '
'rules from file %s: %s' % (path, e))
else:
return rules
class CreateMapping(show.ShowOne, _RulesReader):
"""Create new federation mapping"""
log = logging.getLogger(__name__ + '.CreateMapping')
def get_parser(self, prog_name):
parser = super(CreateMapping, self).get_parser(prog_name)
parser.add_argument(
'mapping',
metavar='<name>',
help='New mapping (must be unique)',
)
parser.add_argument(
'--rules',
metavar='<rules>', required=True,
help='Filename with rules',
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
identity_client = self.app.client_manager.identity
rules = self._read_rules(parsed_args.rules)
mapping = identity_client.federation.mappings.create(
mapping_id=parsed_args.mapping,
rules=rules)
info = {}
info.update(mapping._info)
return zip(*sorted(six.iteritems(info)))
class DeleteMapping(command.Command):
"""Delete federation mapping"""
log = logging.getLogger(__name__ + '.DeleteMapping')
def get_parser(self, prog_name):
parser = super(DeleteMapping, self).get_parser(prog_name)
parser.add_argument(
'mapping',
metavar='<name>',
help='Mapping to delete',
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
identity_client = self.app.client_manager.identity
identity_client.federation.mappings.delete(parsed_args.mapping)
return
class ListMapping(lister.Lister):
"""List federation mappings"""
log = logging.getLogger(__name__ + '.ListMapping')
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
# NOTE(marek-denis): Since rules can be long and tedious I have decided
# to only list ids of the mappings. If somebody wants to check the
# rules, (s)he should show specific ones.
identity_client = self.app.client_manager.identity
data = identity_client.federation.mappings.list()
columns = ('ID',)
items = [utils.get_item_properties(s, columns) for s in data]
return (columns, items)
class SetMapping(show.ShowOne, _RulesReader):
"""Update federation mapping"""
log = logging.getLogger(__name__ + '.SetMapping')
def get_parser(self, prog_name):
parser = super(SetMapping, self).get_parser(prog_name)
parser.add_argument(
'mapping',
metavar='<name>',
help='Mapping to update.',
)
parser.add_argument(
'--rules',
metavar='<rules>', required=True,
help='Filename with rules',
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
identity_client = self.app.client_manager.identity
rules = self._read_rules(parsed_args.rules)
mapping = identity_client.federation.mappings.update(
mapping=parsed_args.mapping,
rules=rules)
info = {}
info.update(mapping._info)
return zip(*sorted(six.iteritems(info)))
class ShowMapping(show.ShowOne):
"""Show federation mapping details"""
log = logging.getLogger(__name__ + '.ShowMapping')
def get_parser(self, prog_name):
parser = super(ShowMapping, self).get_parser(prog_name)
parser.add_argument(
'mapping',
metavar='<name>',
help='Mapping to show',
)
return parser
def take_action(self, parsed_args):
self.log.debug('take_action(%s)' % parsed_args)
identity_client = self.app.client_manager.identity
mapping = identity_client.federation.mappings.get(parsed_args.mapping)
info = {}
info.update(mapping._info)
return zip(*sorted(six.iteritems(info)))

View File

@ -38,6 +38,65 @@ GROUP = {
'name': group_name,
}
mapping_id = 'test_mapping'
mapping_rules_file_path = '/tmp/path/to/file'
# Copied from
# (https://github.com/openstack/keystone/blob\
# master/keystone/tests/mapping_fixtures.py
EMPLOYEE_GROUP_ID = "0cd5e9"
DEVELOPER_GROUP_ID = "xyz"
MAPPING_RULES = [
{
"local": [
{
"group": {
"id": EMPLOYEE_GROUP_ID
}
}
],
"remote": [
{
"type": "orgPersonType",
"not_any_of": [
"Contractor",
"Guest"
]
}
]
}
]
MAPPING_RULES_2 = [
{
"local": [
{
"group": {
"id": DEVELOPER_GROUP_ID
}
}
],
"remote": [
{
"type": "orgPersonType",
"any_one_of": [
"Contractor"
]
}
]
}
]
MAPPING_RESPONSE = {
"id": mapping_id,
"rules": MAPPING_RULES
}
MAPPING_RESPONSE_2 = {
"id": mapping_id,
"rules": MAPPING_RULES_2
}
project_id = '8-9-64'
project_name = 'beatles'
project_description = 'Fab Four'
@ -224,6 +283,8 @@ class FakeFederationManager(object):
def __init__(self, **kwargs):
self.identity_providers = mock.Mock()
self.identity_providers.resource_class = fakes.FakeResource(None, {})
self.mappings = mock.Mock()
self.mappings.resource_class = fakes.FakeResource(None, {})
class FakeFederatedClient(FakeIdentityv3Client):

View File

@ -0,0 +1,239 @@
# Copyright 2014 CERN.
#
# 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 copy
import mock
from openstackclient.common import exceptions
from openstackclient.identity.v3 import mapping
from openstackclient.tests import fakes
from openstackclient.tests.identity.v3 import fakes as identity_fakes
class TestMapping(identity_fakes.TestFederatedIdentity):
def setUp(self):
super(TestMapping, self).setUp()
federation_lib = self.app.client_manager.identity.federation
self.mapping_mock = federation_lib.mappings
self.mapping_mock.reset_mock()
class TestMappingCreate(TestMapping):
def setUp(self):
super(TestMappingCreate, self).setUp()
self.mapping_mock.create.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.MAPPING_RESPONSE),
loaded=True
)
self.cmd = mapping.CreateMapping(self.app, None)
def test_create_mapping(self):
arglist = [
'--rules', identity_fakes.mapping_rules_file_path,
identity_fakes.mapping_id
]
verifylist = [
('mapping', identity_fakes.mapping_id),
('rules', identity_fakes.mapping_rules_file_path)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
mocker = mock.Mock()
mocker.return_value = identity_fakes.MAPPING_RULES
with mock.patch("openstackclient.identity.v3.mapping."
"CreateMapping._read_rules", mocker):
columns, data = self.cmd.take_action(parsed_args)
self.mapping_mock.create.assert_called_with(
mapping_id=identity_fakes.mapping_id,
rules=identity_fakes.MAPPING_RULES)
collist = ('id', 'rules')
self.assertEqual(columns, collist)
datalist = (identity_fakes.mapping_id,
identity_fakes.MAPPING_RULES)
self.assertEqual(datalist, data)
class TestMappingDelete(TestMapping):
def setUp(self):
super(TestMappingDelete, self).setUp()
self.mapping_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.MAPPING_RESPONSE),
loaded=True)
self.mapping_mock.delete.return_value = None
self.cmd = mapping.DeleteMapping(self.app, None)
def test_delete_mapping(self):
arglist = [
identity_fakes.mapping_id
]
verifylist = [
('mapping', identity_fakes.mapping_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.cmd.take_action(parsed_args)
self.mapping_mock.delete.assert_called_with(
identity_fakes.mapping_id)
class TestMappingList(TestMapping):
def setUp(self):
super(TestMappingList, self).setUp()
self.mapping_mock.get.return_value = fakes.FakeResource(
None,
{'id': identity_fakes.mapping_id},
loaded=True)
# Pretend list command returns list of two mappings.
# NOTE(marek-denis): We are returning FakeResources with mapping id
# only as ShowMapping class is implemented in a way where rules will
# not be displayed, only mapping ids.
self.mapping_mock.list.return_value = [
fakes.FakeResource(
None,
{'id': identity_fakes.mapping_id},
loaded=True,
),
fakes.FakeResource(
None,
{'id': 'extra_mapping'},
loaded=True,
),
]
# Get the command object to test
self.cmd = mapping.ListMapping(self.app, None)
def test_mapping_list(self):
arglist = []
verifylist = []
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.mapping_mock.list.assert_called_with()
collist = ('ID',)
self.assertEqual(columns, collist)
datalist = [(identity_fakes.mapping_id,), ('extra_mapping',)]
self.assertEqual(datalist, data)
class TestMappingShow(TestMapping):
def setUp(self):
super(TestMappingShow, self).setUp()
self.mapping_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.MAPPING_RESPONSE),
loaded=True
)
self.cmd = mapping.ShowMapping(self.app, None)
def test_mapping_show(self):
arglist = [
identity_fakes.mapping_id
]
verifylist = [
('mapping', identity_fakes.mapping_id)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
columns, data = self.cmd.take_action(parsed_args)
self.mapping_mock.get.assert_called_with(
identity_fakes.mapping_id)
collist = ('id', 'rules')
self.assertEqual(columns, collist)
datalist = (identity_fakes.mapping_id,
identity_fakes.MAPPING_RULES)
self.assertEqual(datalist, data)
class TestMappingSet(TestMapping):
def setUp(self):
super(TestMappingSet, self).setUp()
self.mapping_mock.get.return_value = fakes.FakeResource(
None,
copy.deepcopy(identity_fakes.MAPPING_RESPONSE),
loaded=True
)
self.mapping_mock.update.return_value = fakes.FakeResource(
None,
identity_fakes.MAPPING_RESPONSE_2,
loaded=True
)
# Get the command object to test
self.cmd = mapping.SetMapping(self.app, None)
def test_set_new_rules(self):
arglist = [
'--rules', identity_fakes.mapping_rules_file_path,
identity_fakes.mapping_id
]
verifylist = [
('mapping', identity_fakes.mapping_id),
('rules', identity_fakes.mapping_rules_file_path)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
mocker = mock.Mock()
mocker.return_value = identity_fakes.MAPPING_RULES_2
with mock.patch("openstackclient.identity.v3.mapping."
"SetMapping._read_rules", mocker):
columns, data = self.cmd.take_action(parsed_args)
self.mapping_mock.update.assert_called_with(
mapping=identity_fakes.mapping_id,
rules=identity_fakes.MAPPING_RULES_2)
collist = ('id', 'rules')
self.assertEqual(columns, collist)
datalist = (identity_fakes.mapping_id,
identity_fakes.MAPPING_RULES_2)
self.assertEqual(datalist, data)
def test_set_rules_wrong_file_path(self):
arglist = [
'--rules', identity_fakes.mapping_rules_file_path,
identity_fakes.mapping_id
]
verifylist = [
('mapping', identity_fakes.mapping_id),
('rules', identity_fakes.mapping_rules_file_path)
]
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
parsed_args)

View File

@ -212,6 +212,12 @@ openstack.identity.v3 =
identity_provider_set = openstackclient.identity.v3.identity_provider:SetIdentityProvider
identity_provider_show = openstackclient.identity.v3.identity_provider:ShowIdentityProvider
mapping_create = openstackclient.identity.v3.mapping:CreateMapping
mapping_delete = openstackclient.identity.v3.mapping:DeleteMapping
mapping_list = openstackclient.identity.v3.mapping:ListMapping
mapping_set = openstackclient.identity.v3.mapping:SetMapping
mapping_show = openstackclient.identity.v3.mapping:ShowMapping
policy_create = openstackclient.identity.v3.policy:CreatePolicy
policy_delete = openstackclient.identity.v3.policy:DeletePolicy
policy_list = openstackclient.identity.v3.policy:ListPolicy