From 87f7ade695ab6dfe6b0b590a6647913a9ce50b5b Mon Sep 17 00:00:00 2001 From: Rui Chen Date: Sat, 8 Oct 2016 15:15:59 +0800 Subject: [PATCH] Add instance type CLI Add the CLI of instance type and entry point, update openstack request id key to offical format, modify the related unit tests. Change-Id: Ie736d99e5a9756dbae372793f39ad3c5cca72fe8 --- nimbleclient/common/base.py | 10 +- nimbleclient/common/http.py | 2 +- nimbleclient/osc/v1/instance_type.py | 182 ++++++++++++ nimbleclient/tests/unit/base.py | 12 + nimbleclient/tests/unit/common/test_base.py | 38 +-- nimbleclient/tests/unit/common/test_http.py | 4 +- nimbleclient/tests/unit/fakes.py | 93 ++++++- nimbleclient/tests/unit/osc/v1/__init__.py | 0 .../tests/unit/osc/v1/test_instance_type.py | 263 ++++++++++++++++++ nimbleclient/v1/client.py | 5 + nimbleclient/v1/instance_type.py | 45 +++ setup.cfg | 6 +- 12 files changed, 629 insertions(+), 31 deletions(-) create mode 100644 nimbleclient/osc/v1/instance_type.py create mode 100644 nimbleclient/tests/unit/osc/v1/__init__.py create mode 100644 nimbleclient/tests/unit/osc/v1/test_instance_type.py create mode 100644 nimbleclient/v1/instance_type.py diff --git a/nimbleclient/common/base.py b/nimbleclient/common/base.py index bc5dd13..4047aa7 100644 --- a/nimbleclient/common/base.py +++ b/nimbleclient/common/base.py @@ -35,13 +35,13 @@ except NameError: def getid(obj): - """Get obj's id or object itself if no id + """Get obj's uuid or object itself if no uuid Abstracts the common pattern of allowing both an object or an object's ID (UUID) as a parameter when dealing with relationships. """ try: - return obj.id + return obj.uuid except AttributeError: return obj @@ -168,7 +168,7 @@ class ManagerWithFind(Manager): elif num > 1: raise exceptions.NoUniqueMatch else: - return self.get(matches[0].id) + return self.get(matches[0].uuid) def findall(self, **kwargs): """Find all items with attributes matching ``**kwargs``. @@ -214,7 +214,7 @@ class RequestIdMixin(object): def _append_request_id(self, resp): if isinstance(resp, Response): - # Extract 'x-openstack-request-id' from headers if + # Extract 'X-Openstack-Request-Id' from headers if # response is a Response object. request_id = (resp.headers.get('x-openstack-request-id') or resp.headers.get('x-compute-request-id')) @@ -280,7 +280,7 @@ class Resource(RequestIdMixin): if not hasattr(self.manager, 'get'): return - new = self.manager.get(self.id) + new = self.manager.get(self.uuid) if new: self._add_details(new._info) # The 'request_ids' attribute has been added, diff --git a/nimbleclient/common/http.py b/nimbleclient/common/http.py index fd4b0fe..aa10b4b 100644 --- a/nimbleclient/common/http.py +++ b/nimbleclient/common/http.py @@ -299,7 +299,7 @@ class SessionClient(adapter.LegacyJsonAdapter): kwargs.setdefault('user_agent', USER_AGENT) if 'data' in kwargs: - kwargs['data'] = jsonutils.dumps(kwargs['data']) + kwargs['json'] = kwargs.pop('data') resp, body = super(SessionClient, self).request( url, method, diff --git a/nimbleclient/osc/v1/instance_type.py b/nimbleclient/osc/v1/instance_type.py new file mode 100644 index 0000000..ca6d36e --- /dev/null +++ b/nimbleclient/osc/v1/instance_type.py @@ -0,0 +1,182 @@ +# Copyright 2016 Huawei, Inc. All rights reserved. +# +# 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. +# + + +"""Nimble v1 Type action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils +import six + +from nimbleclient.common.i18n import _ + + +LOG = logging.getLogger(__name__) + + +class CreateType(command.ShowOne): + """Create a new instance type""" + + def get_parser(self, prog_name): + parser = super(CreateType, self).get_parser(prog_name) + parser.add_argument( + "name", + metavar="", + help=_("New type name") + ) + public_group = parser.add_mutually_exclusive_group() + public_group.add_argument( + "--public", + action="store_true", + help=_("Type is available to other projects (default)") + ) + public_group.add_argument( + "--private", + action="store_true", + help=_("Type is not available to other projects") + ) + parser.add_argument( + "--description", + metavar="", + help=_("Type description"), + ) + return parser + + def take_action(self, parsed_args): + bc_client = self.app.client_manager.baremetal_compute + + info = {} + is_public = True + if parsed_args.public: + is_public = True + if parsed_args.private: + is_public = False + + data = bc_client.instance_type.create( + name=parsed_args.name, + is_public=is_public, + description=parsed_args.description, + ) + info.update(data._info) + + return zip(*sorted(six.iteritems(info))) + + +class DeleteType(command.Command): + """Delete existing instance type(s)""" + + def get_parser(self, prog_name): + parser = super(DeleteType, self).get_parser(prog_name) + parser.add_argument( + 'type', + metavar='', + nargs='+', + help=_("Type(s) to delete (name or UUID)") + ) + return parser + + def take_action(self, parsed_args): + bc_client = self.app.client_manager.baremetal_compute + result = 0 + for one_type in parsed_args.type: + try: + data = utils.find_resource( + bc_client.instance_type, one_type) + bc_client.instance_type.delete(data.uuid) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete type with name or UUID " + "'%(type)s': %(e)s") % {'type': one_type, 'e': e}) + + if result > 0: + total = len(parsed_args.type) + msg = (_("%(result)s of %(total)s types failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListType(command.Lister): + """List all types""" + + def get_parser(self, prog_name): + parser = super(ListType, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + default=False, + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + bc_client = self.app.client_manager.baremetal_compute + + data = bc_client.instance_type.list() + + if parsed_args.long: + # This is the easiest way to change column headers + column_headers = ( + "UUID", + "Name", + "Is Public", + "Description", + "Properties", + ) + columns = ( + "UUID", + "Name", + "Is Public", + "Description", + "Extra Specs", + ) + else: + column_headers = columns = ( + "UUID", + "Name", + "Is Public", + ) + + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in data)) + + +class ShowType(command.ShowOne): + """Display instance type details""" + + def get_parser(self, prog_name): + parser = super(ShowType, self).get_parser(prog_name) + parser.add_argument( + 'type', + metavar='', + help=_("Type to display (name or UUID)") + ) + return parser + + def take_action(self, parsed_args): + + bc_client = self.app.client_manager.baremetal_compute + data = utils.find_resource( + bc_client.instance_type, + parsed_args.type, + ) + + info = {} + info.update(data._info) + return zip(*sorted(six.iteritems(info))) diff --git a/nimbleclient/tests/unit/base.py b/nimbleclient/tests/unit/base.py index ac0ca9e..85d0477 100644 --- a/nimbleclient/tests/unit/base.py +++ b/nimbleclient/tests/unit/base.py @@ -16,7 +16,19 @@ from osc_lib.tests import utils +from nimbleclient.tests.unit import fakes + class TestBase(utils.TestCommand): """Test case base class for all unit tests.""" pass + + +class TestBaremetalComputeV1(TestBase): + """Test case base class for the unit tests of Baremetal Compute V1 API.""" + + def setUp(self): + super(TestBaremetalComputeV1, self).setUp() + + fake_client = fakes.FakeBaremetalComputeV1Client() + self.app.client_manager.baremetal_compute = fake_client diff --git a/nimbleclient/tests/unit/common/test_base.py b/nimbleclient/tests/unit/common/test_base.py index 7c3aa6e..3b4fb8c 100644 --- a/nimbleclient/tests/unit/common/test_base.py +++ b/nimbleclient/tests/unit/common/test_base.py @@ -34,7 +34,7 @@ class TestResource(test_base.TestBase): self.assertEqual(4, base.getid(4)) class TmpObject(object): - id = 4 + uuid = 4 self.assertEqual(4, base.getid(TmpObject)) def test_init_with_attribute_info(self): @@ -46,13 +46,13 @@ class TestResource(test_base.TestBase): def test_resource_lazy_getattr(self): fake_manager = mock.Mock() - return_resource = base.Resource(None, dict(id=mock.sentinel.fake_id, + return_resource = base.Resource(None, dict(uuid=mock.sentinel.fake_id, foo='bar', name='fake_name')) fake_manager.get.return_value = return_resource r = base.Resource(fake_manager, - dict(id=mock.sentinel.fake_id, foo='bar')) + dict(uuid=mock.sentinel.fake_id, foo='bar')) self.assertTrue(hasattr(r, 'foo')) self.assertEqual('bar', r.foo) self.assertFalse(r.is_loaded()) @@ -73,7 +73,7 @@ class TestResource(test_base.TestBase): # Two resources of different types: never equal r1 = base.Resource(None, {'id': 1}) - r2 = fakes.FaksResource(None, {'id': 1}) + r2 = fakes.FakeResource(None, {'id': 1}) self.assertNotEqual(r1, r2) # Two resources with no ID: equal if their info is equal @@ -99,8 +99,8 @@ class TestManager(test_base.TestBase): def test_manager_get(self, mock_get): mock_get.return_value = (fakes.create_response_obj_with_header(), mock.MagicMock()) - fake_resource = fakes.FaksResource( - None, dict(id=fakes.FAKE_RESOURCE_ID, + fake_resource = fakes.FakeResource( + None, dict(uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME)) result = self.fake_manager.get(fake_resource) self.assertIsInstance(result, base.Resource) @@ -123,8 +123,8 @@ class TestManager(test_base.TestBase): def test_manager_update(self, mock_patch): mock_patch.return_value = (fakes.create_response_obj_with_header(), mock.MagicMock()) - fake_resource = fakes.FaksResource( - None, dict(id=fakes.FAKE_RESOURCE_ID, + fake_resource = fakes.FakeResource( + None, dict(uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME)) result = self.fake_manager.update(fake_resource) self.assertIsInstance(result, base.Resource) @@ -138,8 +138,8 @@ class TestManager(test_base.TestBase): def test_manager_delete(self, mock_delete): mock_delete.return_value = (fakes.create_response_obj_with_header(), None) - fake_resource = fakes.FaksResource( - None, dict(id=fakes.FAKE_RESOURCE_ID, + fake_resource = fakes.FakeResource( + None, dict(uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME)) result = self.fake_manager.delete(fake_resource) self.assertIsInstance(result, base.TupleWithMeta) @@ -151,8 +151,8 @@ class TestManager(test_base.TestBase): def test_manager_create(self, mock_post): mock_post.return_value = (fakes.create_response_obj_with_header(), mock.MagicMock()) - fake_resource = fakes.FaksResource( - None, dict(id=fakes.FAKE_RESOURCE_ID, + fake_resource = fakes.FakeResource( + None, dict(uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME)) result = self.fake_manager.create(fake_resource) self.assertIsInstance(result, base.Resource) @@ -164,9 +164,9 @@ class TestManager(test_base.TestBase): @mock.patch.object(fakes.FakeHTTPClient, 'get') def test_manager_find(self, mock_get): - fake_json_body_1 = dict(id=fakes.FAKE_RESOURCE_ID, + fake_json_body_1 = dict(uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME) - fake_json_body_2 = dict(id='no_existed_id', + fake_json_body_2 = dict(uuid='no_existed_id', name='no_existed_name') mock_get.side_effect = [ (fakes.create_response_obj_with_header(), @@ -175,10 +175,10 @@ class TestManager(test_base.TestBase): (fakes.create_response_obj_with_header(), fake_json_body_1) ] - result = self.fake_manager.find(id=fakes.FAKE_RESOURCE_ID, + result = self.fake_manager.find(uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME) self.assertIsInstance(result, base.Resource) - self.assertEqual(fakes.FAKE_RESOURCE_ID, result.id) + self.assertEqual(fakes.FAKE_RESOURCE_ID, result.uuid) self.assertEqual(fakes.FAKE_RESOURCE_NAME, result.name) self.assertTrue(result.is_loaded()) expect_collection_url = fakes.FAKE_RESOURCE_COLLECTION_URL @@ -194,14 +194,14 @@ class TestManager(test_base.TestBase): {'resources': []}) self.assertRaises(exceptions.NotFound, self.fake_manager.find, - id=fakes.FAKE_RESOURCE_ID, + uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME) expect_collection_url = fakes.FAKE_RESOURCE_COLLECTION_URL mock_get.assert_called_once_with(expect_collection_url, headers={}) @mock.patch.object(fakes.FakeHTTPClient, 'get') def test_manager_find_more_than_one_result(self, mock_get): - fake_json_body_1 = dict(id=fakes.FAKE_RESOURCE_ID, + fake_json_body_1 = dict(uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME) fake_json_body_2 = copy.deepcopy(fake_json_body_1) mock_get.return_value = (fakes.create_response_obj_with_header(), @@ -209,7 +209,7 @@ class TestManager(test_base.TestBase): fake_json_body_2]}) self.assertRaises(exceptions.NoUniqueMatch, self.fake_manager.find, - id=fakes.FAKE_RESOURCE_ID, + uuid=fakes.FAKE_RESOURCE_ID, name=fakes.FAKE_RESOURCE_NAME) expect_collection_url = fakes.FAKE_RESOURCE_COLLECTION_URL mock_get.assert_called_once_with(expect_collection_url, headers={}) diff --git a/nimbleclient/tests/unit/common/test_http.py b/nimbleclient/tests/unit/common/test_http.py index 1134af4..29d74d6 100644 --- a/nimbleclient/tests/unit/common/test_http.py +++ b/nimbleclient/tests/unit/common/test_http.py @@ -633,7 +633,7 @@ class TestSessionClient(base.TestBase): resp, body = client.request('', 'GET', **kwargs) self.assertEqual({'endpoint_override': 'http://no.where/', - 'data': '"some_data"', + 'json': 'some_data', 'user_agent': 'python-nimbleclient', 'raise_exc': False}, self.request.call_args[1]) self.assertEqual(200, resp.status_code) @@ -655,7 +655,7 @@ class TestSessionClient(base.TestBase): resp, body = client.request('', 'GET', **kwargs) self.assertEqual({'endpoint_override': 'http://no.where/', - 'data': "{'files': test}}", + 'json': {'files': data}, 'user_agent': 'python-nimbleclient', 'raise_exc': False}, self.request.call_args[1]) self.assertEqual(200, resp.status_code) diff --git a/nimbleclient/tests/unit/fakes.py b/nimbleclient/tests/unit/fakes.py index 6a24174..27dfbe9 100644 --- a/nimbleclient/tests/unit/fakes.py +++ b/nimbleclient/tests/unit/fakes.py @@ -13,10 +13,15 @@ # under the License. # +import copy +import uuid + +import mock from oslo_serialization import jsonutils from requests import Response from nimbleclient.common import base +from nimbleclient.v1 import instance_type # fake request id FAKE_REQUEST_ID = 'req-0594c66b-6973-405c-ae2c-43fcfc00f2e3' @@ -47,6 +52,16 @@ def create_resource_manager(): return FakeManager() +class FakeBaremetalComputeV1Client(object): + + def __init__(self, **kwargs): + self.fake_http_client = mock.Mock() + + self.instance_type = instance_type.InstanceTypeManager( + self.fake_http_client) + self.instance = None + + class FakeHTTPClient(object): def get(self): @@ -68,12 +83,12 @@ class FakeHTTPClient(object): pass -class FaksResource(base.Resource): - id = 'N/A' +class FakeResource(base.Resource): + pass class FakeManager(base.ManagerWithFind): - resource_class = FaksResource + resource_class = FakeResource def __init__(self, api=None): if not api: @@ -129,3 +144,75 @@ class FakeHTTPResponse(object): def json(self): return jsonutils.loads(self.content) + + +class FakeInstanceType(object): + """Fake one instance type.""" + + @staticmethod + def create_one_instance_type(attrs=None): + """Create a fake instance type. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with uuid and other attributes + """ + attrs = attrs or {} + + # Set default attribute + instance_type_info = { + "created_at": "2016-09-27T02:37:21.966342+00:00", + "description": "fake_description", + "extra_specs": {}, + "is_public": True, + "name": "instance-type-name-" + uuid.uuid4().hex, + "updated_at": None, + "uuid": "instance-type-id-" + uuid.uuid4().hex, + } + + # Overwrite default attributes. + instance_type_info.update(attrs) + + instance_type = FakeResource( + manager=None, + info=copy.deepcopy(instance_type_info), + loaded=True) + return instance_type + + @staticmethod + def create_instance_types(attrs=None, count=2): + """Create multiple fake instance types. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of instance types to fake + :return: + A list of FakeResource objects faking the instance types + """ + instance_types = [] + for i in range(0, count): + instance_types.append( + FakeInstanceType.create_one_instance_type(attrs)) + + return instance_types + + @staticmethod + def get_instance_types(instance_types=None, count=2): + """Get an iterable Mock object with a list of faked instance types. + + If instance_types list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List instance_types: + A list of FakeResource objects faking instance types + :param int count: + The number of instance types to fake + :return: + An iterable Mock object with side_effect set to a list of faked + instance types + """ + if instance_types is None: + instance_types = FakeInstanceType.create_instance_types(count) + return mock.Mock(side_effect=instance_types) diff --git a/nimbleclient/tests/unit/osc/v1/__init__.py b/nimbleclient/tests/unit/osc/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nimbleclient/tests/unit/osc/v1/test_instance_type.py b/nimbleclient/tests/unit/osc/v1/test_instance_type.py new file mode 100644 index 0000000..05d887f --- /dev/null +++ b/nimbleclient/tests/unit/osc/v1/test_instance_type.py @@ -0,0 +1,263 @@ +# Copyright 2016 Huawei, Inc. All rights reserved. +# +# 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 + +from osc_lib import utils + +from nimbleclient.common import base +from nimbleclient.osc.v1 import instance_type +from nimbleclient.tests.unit import base as test_base +from nimbleclient.tests.unit import fakes +from nimbleclient.v1 import instance_type as instance_type_mgr + + +class TestInstanceType(test_base.TestBaremetalComputeV1): + fake_type = fakes.FakeInstanceType.create_one_instance_type() + + columns = ( + 'created_at', + 'description', + 'extra_specs', + 'is_public', + 'name', + 'updated_at', + 'uuid', + ) + + data = ( + fake_type.created_at, + fake_type.description, + fake_type.extra_specs, + fake_type.is_public, + fake_type.name, + fake_type.updated_at, + fake_type.uuid, + ) + + +@mock.patch.object(instance_type_mgr.InstanceTypeManager, '_create') +class TestInstanceTypeCreate(TestInstanceType): + + def setUp(self): + super(TestInstanceTypeCreate, self).setUp() + self.cmd = instance_type.CreateType(self.app, None) + + def test_type_create(self, mock_create): + arglist = [ + 'type1', + ] + verifylist = [ + ('name', 'type1'), + ] + mock_create.return_value = self.fake_type + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + mock_create.assert_called_once_with('/types', + data={ + 'name': 'type1', + 'is_public': True, + 'description': None, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_type_create_with_public(self, mock_create): + arglist = [ + '--public', + 'type1', + ] + verifylist = [ + ('public', True), + ('name', 'type1'), + ] + mock_create.return_value = self.fake_type + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + mock_create.assert_called_once_with('/types', + data={ + 'name': 'type1', + 'is_public': True, + 'description': None, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_type_create_with_private(self, mock_create): + arglist = [ + '--private', + 'type1', + ] + verifylist = [ + ('private', True), + ('name', 'type1'), + ] + mock_create.return_value = self.fake_type + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + mock_create.assert_called_once_with('/types', + data={ + 'name': 'type1', + 'is_public': False, + 'description': None, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_type_create_with_description(self, mock_create): + arglist = [ + '--description', 'test description.', + 'type1', + ] + verifylist = [ + ('description', 'test description.'), + ('name', 'type1'), + ] + mock_create.return_value = self.fake_type + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + mock_create.assert_called_once_with( + '/types', + data={ + 'name': 'type1', + 'is_public': True, + 'description': 'test description.', + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +@mock.patch.object(utils, 'find_resource') +@mock.patch.object(instance_type_mgr.InstanceTypeManager, '_delete') +class TestInstanceTypeDelete(TestInstanceType): + + def setUp(self): + super(TestInstanceTypeDelete, self).setUp() + self.cmd = instance_type.DeleteType(self.app, None) + + def test_type_delete(self, mock_delete, mock_find): + arglist = [ + 'type1', + ] + verifylist = [ + ('type', ['type1']), + ] + mock_find.return_value = self.fake_type + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + expected_url = '/types/%s' % base.getid(self.fake_type) + mock_delete.assert_called_once_with(expected_url) + self.assertIsNone(result) + + def test_type_multiple_delete(self, mock_delete, mock_find): + arglist = [ + 'type1', + 'type2', + 'type3' + ] + verifylist = [ + ('type', ['type1', 'type2', 'type3']), + ] + mock_find.return_value = self.fake_type + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + expected_url = '/types/%s' % base.getid(self.fake_type) + expected_call = [mock.call(expected_url), mock.call(expected_url), + mock.call(expected_url)] + mock_delete.assert_has_calls(expected_call) + self.assertIsNone(result) + + +@mock.patch.object(instance_type_mgr.InstanceTypeManager, '_list') +class TestInstanceTypeList(TestInstanceType): + + list_columns = ( + "UUID", + "Name", + "Is Public", + ) + + list_columns_long = ( + "UUID", + "Name", + "Is Public", + "Description", + "Properties", + ) + + list_data = (( + TestInstanceType.fake_type.uuid, + TestInstanceType.fake_type.name, + TestInstanceType.fake_type.is_public, + ), ) + + list_data_long = (( + TestInstanceType.fake_type.uuid, + TestInstanceType.fake_type.name, + TestInstanceType.fake_type.is_public, + TestInstanceType.fake_type.description, + TestInstanceType.fake_type.extra_specs, + ), ) + + def setUp(self): + super(TestInstanceTypeList, self).setUp() + self.cmd = instance_type.ListType(self.app, None) + + def test_type_list(self, mock_list): + arglist = [] + verifylist = [] + mock_list.return_value = [self.fake_type] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + mock_list.assert_called_once_with('/types', response_key='types') + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, tuple(data)) + + def test_type_list_with_long(self, mock_list): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + mock_list.return_value = [self.fake_type] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + mock_list.assert_called_once_with('/types', response_key='types') + self.assertEqual(self.list_columns_long, columns) + self.assertEqual(self.list_data_long, tuple(data)) + + +@mock.patch.object(instance_type_mgr.InstanceTypeManager, '_get') +class TestInstanceTypeShow(TestInstanceType): + + def setUp(self): + super(TestInstanceTypeShow, self).setUp() + self.cmd = instance_type.ShowType(self.app, None) + + def test_type_show(self, mock_get): + arglist = [ + 'type1', + ] + verifylist = [ + ('type', 'type1'), + ] + mock_get.return_value = self.fake_type + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + expected_url = '/types/%s' % parsed_args.type + mock_get.assert_called_once_with(expected_url) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/nimbleclient/v1/client.py b/nimbleclient/v1/client.py index 05e61ef..a4b558a 100644 --- a/nimbleclient/v1/client.py +++ b/nimbleclient/v1/client.py @@ -14,6 +14,7 @@ # from nimbleclient.common import http +from nimbleclient.v1 import instance_type class Client(object): @@ -22,3 +23,7 @@ class Client(object): def __init__(self, *args, **kwargs): """Initialize a new client for the Nimble v1 API.""" self.http_client = http._construct_http_client(*args, **kwargs) + + self.instance_type = instance_type.InstanceTypeManager( + self.http_client) + self.instance = None diff --git a/nimbleclient/v1/instance_type.py b/nimbleclient/v1/instance_type.py new file mode 100644 index 0000000..4f4a101 --- /dev/null +++ b/nimbleclient/v1/instance_type.py @@ -0,0 +1,45 @@ +# Copyright 2016 Huawei, Inc. All rights reserved. +# +# 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. +# + +from nimbleclient.common import base + + +class InstanceType(base.Resource): + pass + + +class InstanceTypeManager(base.ManagerWithFind): + resource_class = InstanceType + + def create(self, name, is_public, description=None): + url = '/types' + data = { + 'name': name, + 'is_public': is_public, + 'description': description, + } + return self._create(url, data=data) + + def delete(self, instance_type): + url = '/types/%s' % base.getid(instance_type) + return self._delete(url) + + def get(self, instance_type): + url = '/types/%s' % base.getid(instance_type) + return self._get(url) + + def list(self): + url = '/types' + return self._list(url, response_key='types') diff --git a/setup.cfg b/setup.cfg index 7b2588c..77ea2ee 100644 --- a/setup.cfg +++ b/setup.cfg @@ -28,6 +28,10 @@ openstack.cli.extension = baremetal_compute = nimbleclient.osc.plugin openstack.baremetal_compute.v1 = + baremetal_compute_type_create = nimbleclient.osc.v1.instance_type:CreateType + baremetal_compute_type_delete = nimbleclient.osc.v1.instance_type:DeleteType + baremetal_compute_type_list = nimbleclient.osc.v1.instance_type:ListType + baremetal_compute_type_show = nimbleclient.osc.v1.instance_type:ShowType [build_sphinx] @@ -55,4 +59,4 @@ output_file = nimbleclient/locale/nimbleclient.pot [build_releasenotes] all_files = 1 build-dir = releasenotes/build -source-dir = releasenotes/source \ No newline at end of file +source-dir = releasenotes/source