diff --git a/pankoclient/osc/v2/capabilities.py b/pankoclient/osc/v2/capabilities.py index 1d3f1bc..159cfa8 100644 --- a/pankoclient/osc/v2/capabilities.py +++ b/pankoclient/osc/v2/capabilities.py @@ -22,6 +22,6 @@ class CliCapabilitiesList(show.ShowOne): """List capabilities for event service""" def take_action(self, parsed_args): - ac = self.app.client_manager.alarming + ac = self.app.client_manager.event caps = ac.capabilities.list() return self.dict2columns(caps) diff --git a/pankoclient/osc/v2/events.py b/pankoclient/osc/v2/events.py deleted file mode 100644 index a1faa68..0000000 --- a/pankoclient/osc/v2/events.py +++ /dev/null @@ -1,109 +0,0 @@ -# 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. -# - - -"""Panko v2 event action implementations""" - -import logging - -from osc_lib.cli import parseractions -from osc_lib.command import command -from osc_lib import exceptions -from osc_lib import utils -import six - -from pankoclient.common.i18n import _ - -LOG = logging.getLogger(__name__) - - -class ListEvent(command.Lister): - """List all baremetal servers""" - - def get_parser(self, prog_name): - parser = super(ListEvent, self).get_parser(prog_name) - parser.add_argument( - '--long', - action='store_true', - default=False, - help=_("List additional fields in output") - ) - parser.add_argument( - '--all-projects', - action='store_true', - default=False, - help=_("List the baremetal servers of all projects, " - "only available for admin users.") - ) - return parser - - @staticmethod - def _networks_formatter(network_info): - return_info = [] - for port_uuid in network_info: - port_ips = [] - for fixed_ip in network_info[port_uuid]['fixed_ips']: - port_ips.append(fixed_ip['ip_address']) - return_info.append(', '.join(port_ips)) - return '; '.join(return_info) - - def take_action(self, parsed_args): - bc_client = self.app.client_manager.baremetal_compute - - if parsed_args.long: - data = bc_client.server.list(detailed=True, - all_projects=parsed_args.all_projects) - formatters = {'network_info': self._networks_formatter} - # This is the easiest way to change column headers - column_headers = ( - "UUID", - "Name", - "Flavor", - "Status", - "Power State", - "Image", - "Description", - "Availability Zone", - "Networks" - ) - columns = ( - "uuid", - "name", - "instance_type_uuid", - "status", - "power_state", - "image_uuid", - "description", - "availability_zone", - "network_info" - ) - else: - data = bc_client.server.list(all_projects=parsed_args.all_projects) - formatters = None - column_headers = ( - "UUID", - "Name", - "Status", - ) - columns = ( - "uuid", - "name", - "status", - ) - - return (column_headers, - (utils.get_item_properties( - s, columns, formatters=formatters - ) for s in data)) diff --git a/pankoclient/tests/functional/__init__.py b/pankoclient/tests/functional/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pankoclient/tests/functional/base.py b/pankoclient/tests/functional/base.py new file mode 100644 index 0000000..caf6eb2 --- /dev/null +++ b/pankoclient/tests/functional/base.py @@ -0,0 +1,20 @@ +# 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 osc_lib.tests import utils + + +class TestBase(utils.TestCommand): + """Test case base class for all functional tests.""" + pass diff --git a/pankoclient/tests/unit/__init__.py b/pankoclient/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pankoclient/tests/unit/base.py b/pankoclient/tests/unit/base.py new file mode 100644 index 0000000..37b0c40 --- /dev/null +++ b/pankoclient/tests/unit/base.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +# 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 osc_lib.tests import utils + +from pankoclient.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.event = fake_client diff --git a/pankoclient/tests/unit/common/__init__.py b/pankoclient/tests/unit/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pankoclient/tests/unit/common/test_base.py b/pankoclient/tests/unit/common/test_base.py new file mode 100644 index 0000000..43b28a0 --- /dev/null +++ b/pankoclient/tests/unit/common/test_base.py @@ -0,0 +1,277 @@ +# 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 copy + +import mock +import six + +from pankoclient.common import base +from pankoclient.common import exceptions +from pankoclient.tests.unit import base as test_base +from pankoclient.tests.unit import fakes + + +class TestResource(test_base.TestBase): + + def test_resource_repr(self): + r = base.Resource(None, dict(foo='bar', baz='spam')) + self.assertEqual('', repr(r)) + + def test_getid(self): + self.assertEqual(4, base.getid(4)) + + class TmpObject(object): + uuid = 4 + self.assertEqual(4, base.getid(TmpObject)) + + def test_init_with_attribute_info(self): + r = base.Resource(None, dict(foo='bar', baz='spam')) + self.assertTrue(hasattr(r, 'foo')) + self.assertEqual('bar', r.foo) + self.assertTrue(hasattr(r, 'baz')) + self.assertEqual('spam', r.baz) + + def test_resource_lazy_getattr(self): + fake_manager = mock.Mock() + 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(uuid=mock.sentinel.fake_id, foo='bar')) + self.assertTrue(hasattr(r, 'foo')) + self.assertEqual('bar', r.foo) + self.assertFalse(r.is_loaded()) + + # Trigger load + self.assertEqual('fake_name', r.name) + fake_manager.get.assert_called_once_with(mock.sentinel.fake_id) + self.assertTrue(r.is_loaded()) + + # Missing stuff still fails after a second get + self.assertRaises(AttributeError, getattr, r, 'blahblah') + + def test_eq(self): + # Two resources of the same type with the same id: not equal + r1 = base.Resource(None, {'id': 1, 'name': 'hi'}) + r2 = base.Resource(None, {'id': 1, 'name': 'hello'}) + self.assertNotEqual(r1, r2) + + # Two resources of different types: never equal + r1 = base.Resource(None, {'id': 1}) + r2 = fakes.FakeResource(None, {'id': 1}) + self.assertNotEqual(r1, r2) + + # Two resources with no ID: equal if their info is equal + r1 = base.Resource(None, {'name': 'joe', 'age': 12}) + r2 = base.Resource(None, {'name': 'joe', 'age': 12}) + self.assertEqual(r1, r2) + + def test_resource_object_with_request_ids(self): + resp_obj = fakes.create_response_obj_with_header() + r = base.Resource(None, {'name': '1'}, resp=resp_obj) + self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, r.request_ids) + + def test_resource_object_with_compute_request_ids(self): + resp_obj = fakes.create_response_obj_with_compute_header() + r = base.Resource(None, {'name': '1'}, resp=resp_obj) + self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, r.request_ids) + + +class TestManager(test_base.TestBase): + fake_manager = fakes.create_resource_manager() + + @mock.patch.object(fakes.FakeHTTPClient, 'get') + def test_manager_get(self, mock_get): + mock_get.return_value = (fakes.create_response_obj_with_header(), + mock.MagicMock()) + 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) + self.assertIsInstance(result._info, mock.MagicMock) + self.assertTrue(result.is_loaded()) + expect_url = (fakes.FAKE_RESOURCE_ITEM_URL % fakes.FAKE_RESOURCE_ID) + mock_get.assert_called_once_with(expect_url, headers={}) + + @mock.patch.object(fakes.FakeHTTPClient, 'get') + def test_manager_list(self, mock_get): + mock_get.return_value = (fakes.create_response_obj_with_header(), + mock.MagicMock()) + result = self.fake_manager.list() + self.assertIsInstance(result, base.ListWithMeta) + self.assertEqual([], result) + expect_url = fakes.FAKE_RESOURCE_COLLECTION_URL + mock_get.assert_called_once_with(expect_url, headers={}) + + @mock.patch.object(fakes.FakeHTTPClient, 'patch') + def test_manager_update(self, mock_patch): + mock_patch.return_value = (fakes.create_response_obj_with_header(), + mock.MagicMock()) + 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) + self.assertIsInstance(result._info, mock.MagicMock) + self.assertFalse(result.is_loaded()) + expect_url = (fakes.FAKE_RESOURCE_ITEM_URL % fakes.FAKE_RESOURCE_ID) + mock_patch.assert_called_once_with(expect_url, data=fake_resource, + headers={}) + + @mock.patch.object(fakes.FakeHTTPClient, 'delete') + def test_manager_delete(self, mock_delete): + mock_delete.return_value = (fakes.create_response_obj_with_header(), + None) + 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) + self.assertEqual(tuple(), result) + expect_url = (fakes.FAKE_RESOURCE_ITEM_URL % fakes.FAKE_RESOURCE_ID) + mock_delete.assert_called_once_with(expect_url, headers={}) + + @mock.patch.object(fakes.FakeHTTPClient, 'post') + def test_manager_create(self, mock_post): + mock_post.return_value = (fakes.create_response_obj_with_header(), + mock.MagicMock()) + 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) + self.assertIsInstance(result._info, mock.MagicMock) + self.assertFalse(result.is_loaded()) + expect_url = fakes.FAKE_RESOURCE_COLLECTION_URL + mock_post.assert_called_once_with(expect_url, data=fake_resource, + headers={}) + + @mock.patch.object(fakes.FakeHTTPClient, 'get') + def test_manager_find(self, mock_get): + fake_json_body_1 = dict(uuid=fakes.FAKE_RESOURCE_ID, + name=fakes.FAKE_RESOURCE_NAME) + fake_json_body_2 = dict(uuid='no_existed_id', + name='no_existed_name') + mock_get.side_effect = [ + (fakes.create_response_obj_with_header(), + {'resources': [fake_json_body_1, + fake_json_body_2]}), + (fakes.create_response_obj_with_header(), + fake_json_body_1) + ] + 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.uuid) + self.assertEqual(fakes.FAKE_RESOURCE_NAME, result.name) + self.assertTrue(result.is_loaded()) + expect_collection_url = fakes.FAKE_RESOURCE_COLLECTION_URL + expect_item_url = (fakes.FAKE_RESOURCE_ITEM_URL % + fakes.FAKE_RESOURCE_ID) + mock_get.assert_has_calls( + [mock.call(expect_collection_url, headers={}), + mock.call(expect_item_url, headers={})]) + + @mock.patch.object(fakes.FakeHTTPClient, 'get') + def test_manager_find_no_result(self, mock_get): + mock_get.return_value = (fakes.create_response_obj_with_header(), + {'resources': []}) + self.assertRaises(exceptions.NotFound, + self.fake_manager.find, + 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(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(), + {'resources': [fake_json_body_1, + fake_json_body_2]}) + self.assertRaises(exceptions.NoUniqueMatch, + self.fake_manager.find, + 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={}) + + +class ListWithMetaTest(test_base.TestBase): + def test_list_with_meta(self): + resp = fakes.create_response_obj_with_header() + obj = base.ListWithMeta([], resp) + self.assertEqual([], obj) + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) + + +class DictWithMetaTest(test_base.TestBase): + def test_dict_with_meta(self): + resp = fakes.create_response_obj_with_header() + obj = base.DictWithMeta({}, resp) + self.assertEqual({}, obj) + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) + + +class TupleWithMetaTest(test_base.TestBase): + def test_tuple_with_meta(self): + resp = fakes.create_response_obj_with_header() + expected_tuple = (1, 2) + obj = base.TupleWithMeta(expected_tuple, resp) + self.assertEqual(expected_tuple, obj) + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) + + +class StrWithMetaTest(test_base.TestBase): + def test_str_with_meta(self): + resp = fakes.create_response_obj_with_header() + obj = base.StrWithMeta('test-str', resp) + self.assertEqual('test-str', obj) + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) + + +class BytesWithMetaTest(test_base.TestBase): + def test_bytes_with_meta(self): + resp = fakes.create_response_obj_with_header() + obj = base.BytesWithMeta(b'test-bytes', resp) + self.assertEqual(b'test-bytes', obj) + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) + + +if six.PY2: + class UnicodeWithMetaTest(test_base.TestBase): + def test_unicode_with_meta(self): + resp = fakes.create_response_obj_with_header() + obj = base.UnicodeWithMeta(u'test-unicode', resp) + self.assertEqual(u'test-unicode', obj) + # Check request_ids attribute is added to obj + self.assertTrue(hasattr(obj, 'request_ids')) + self.assertEqual(fakes.FAKE_REQUEST_ID_LIST, obj.request_ids) diff --git a/pankoclient/tests/unit/common/test_exceptions.py b/pankoclient/tests/unit/common/test_exceptions.py new file mode 100644 index 0000000..de28db5 --- /dev/null +++ b/pankoclient/tests/unit/common/test_exceptions.py @@ -0,0 +1,99 @@ +# 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 pankoclient.common import exceptions as exc +from pankoclient.tests.unit import base + + +class TestHTTPExceptions(base.TestBase): + + def test_from_response(self): + mock_resp = mock.Mock() + mock_resp.status_code = 413 + mock_resp.json.return_value = { + 'entityTooLarge': { + 'code': 413, + 'message': 'Request Entity Too Large', + 'details': 'Error Details...', + } + } + mock_resp.headers = { + 'Content-Type': 'application/json', + 'x-openstack-request-id': mock.sentinel.fake_request_id, + 'retry-after': 10, + } + err = exc.from_response(mock_resp, 'POST', 'fake_url') + + self.assertIsInstance(err, exc.RequestEntityTooLarge) + self.assertEqual(413, err.status_code) + self.assertEqual('POST', err.method) + self.assertEqual('fake_url', err.url) + self.assertEqual('Request Entity Too Large (HTTP 413) (Request-ID: ' + 'sentinel.fake_request_id)', err.message) + self.assertEqual('Error Details...', err.details) + self.assertEqual(10, err.retry_after) + self.assertEqual(mock.sentinel.fake_request_id, err.request_id) + + def test_from_response_webob_new_format(self): + mock_resp = mock.Mock() + mock_resp.status_code = 413 + mock_resp.json.return_value = { + 'code': 413, + 'message': 'Request Entity Too Large', + 'details': 'Error Details...', + } + mock_resp.headers = { + 'Content-Type': 'application/json', + 'x-openstack-request-id': mock.sentinel.fake_request_id, + 'retry-after': 10, + } + err = exc.from_response(mock_resp, 'POST', 'fake_url') + + self.assertIsInstance(err, exc.RequestEntityTooLarge) + self.assertEqual(413, err.status_code) + self.assertEqual('POST', err.method) + self.assertEqual('fake_url', err.url) + self.assertEqual('Request Entity Too Large (HTTP 413) (Request-ID: ' + 'sentinel.fake_request_id)', err.message) + self.assertEqual('Error Details...', err.details) + self.assertEqual(10, err.retry_after) + self.assertEqual(mock.sentinel.fake_request_id, err.request_id) + + def test_from_response_pecan_response_format(self): + mock_resp = mock.Mock() + mock_resp.status_code = 400 + mock_resp.json.return_value = { + u'error_message': u'{"debuginfo": null, ' + u'"faultcode": "Client", ' + u'"faultstring": "Error Details..."}' + } + mock_resp.headers = { + 'Content-Type': 'application/json', + 'Openstack-Request-Id': 'fake_request_id', + 'Content-Length': '216', + 'Connection': 'keep-alive', + 'Date': 'Mon, 26 Dec 2016 06:59:04 GMT' + } + err = exc.from_response(mock_resp, 'POST', 'fake_url') + + self.assertEqual(400, err.status_code) + self.assertEqual('POST', err.method) + self.assertEqual('fake_url', err.url) + self.assertEqual( + 'Error Details... (HTTP 400) (Request-ID: fake_request_id)', + err.message) + self.assertEqual('fake_request_id', err.request_id) diff --git a/pankoclient/tests/unit/common/test_http.py b/pankoclient/tests/unit/common/test_http.py new file mode 100644 index 0000000..569303d --- /dev/null +++ b/pankoclient/tests/unit/common/test_http.py @@ -0,0 +1,679 @@ +# 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 socket + +from keystoneauth1 import adapter +import mock +from osc_lib.tests import fakes as osc_fakes +from oslo_serialization import jsonutils +import six + +from pankoclient.common import exceptions as exc +from pankoclient.common import http +from pankoclient.common import utils +from pankoclient.tests.unit import base +from pankoclient.tests.unit import fakes + + +@mock.patch('pankoclient.common.http.requests.request') +class TestHttpClient(base.TestBase): + + def setUp(self): + super(TestHttpClient, self).setUp() + + def test_http_raw_request(self, mock_request): + headers = {'User-Agent': 'python-pankoclient', + 'Content-Type': 'application/octet-stream'} + mock_request.return_value = fakes.FakeHTTPResponse(200, 'OK', {}, '') + client = http.HTTPClient('http://example.com:6688') + resp, body = client.raw_request('GET', '/prefix') + self.assertEqual(200, resp.status_code) + self.assertEqual('', ''.join([x for x in resp.content])) + mock_request.assert_called_once_with('GET', + 'http://example.com:6688/prefix', + allow_redirects=False, + headers=headers) + + def test_token_or_credentials(self, mock_request): + # Record a 200 + fake200 = fakes.FakeHTTPResponse(200, 'OK', {}, '') + mock_request.side_effect = [fake200, fake200, fake200] + + # Replay, create client, assert + client = http.HTTPClient('http://example.com:6688') + resp, body = client.raw_request('GET', '') + self.assertEqual(200, resp.status_code) + + client.username = osc_fakes.USERNAME + client.password = osc_fakes.PASSWORD + resp, body = client.raw_request('GET', '') + self.assertEqual(200, resp.status_code) + + client.auth_token = osc_fakes.AUTH_TOKEN + resp, body = client.raw_request('GET', '') + self.assertEqual(200, resp.status_code) + + # no token or credentials + mock_request.assert_has_calls([ + mock.call('GET', 'http://example.com:6688', + allow_redirects=False, + headers={'User-Agent': 'python-pankoclient', + 'Content-Type': 'application/octet-stream'}), + mock.call('GET', 'http://example.com:6688', + allow_redirects=False, + headers={'User-Agent': 'python-pankoclient', + 'X-Auth-Key': osc_fakes.PASSWORD, + 'X-Auth-User': osc_fakes.USERNAME, + 'Content-Type': 'application/octet-stream'}), + mock.call('GET', 'http://example.com:6688', + allow_redirects=False, + headers={'User-Agent': 'python-pankoclient', + 'X-Auth-Token': osc_fakes.AUTH_TOKEN, + 'Content-Type': 'application/octet-stream'}) + ]) + + def test_region_name(self, mock_request): + # Record a 200 + fake200 = fakes.FakeHTTPResponse(200, 'OK', {}, '') + mock_request.return_value = fake200 + + client = http.HTTPClient('http://example.com:6688') + client.region_name = osc_fakes.REGION_NAME + resp, body = client.raw_request('GET', '') + self.assertEqual(200, resp.status_code) + + mock_request.assert_called_once_with( + 'GET', 'http://example.com:6688', + allow_redirects=False, + headers={'X-Region-Name': osc_fakes.REGION_NAME, + 'User-Agent': 'python-pankoclient', + 'Content-Type': 'application/octet-stream'}) + + def test_http_json_request(self, mock_request): + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, '{}') + + client = http.HTTPClient('http://example.com:6688') + resp, body = client.json_request('GET', '') + self.assertEqual(200, resp.status_code) + self.assertEqual({}, body) + + mock_request.assert_called_once_with( + 'GET', 'http://example.com:6688', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + + def test_http_json_request_argument_passed_to_requests(self, mock_request): + """Check that we have sent the proper arguments to requests.""" + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, '{}') + + client = http.HTTPClient('http://example.com:6688') + client.verify_cert = True + client.cert_file = 'RANDOM_CERT_FILE' + client.key_file = 'RANDOM_KEY_FILE' + client.auth_url = osc_fakes.AUTH_URL + resp, body = client.json_request('POST', '', data='text') + self.assertEqual(200, resp.status_code) + self.assertEqual({}, body) + + mock_request.assert_called_once_with( + 'POST', 'http://example.com:6688', + allow_redirects=False, + cert=('RANDOM_CERT_FILE', 'RANDOM_KEY_FILE'), + verify=True, + data='"text"', + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'X-Auth-Url': osc_fakes.AUTH_URL, + 'User-Agent': 'python-pankoclient'}) + + def test_http_json_request_w_req_body(self, mock_request): + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, '{}') + + client = http.HTTPClient('http://example.com:6688') + resp, body = client.json_request('POST', '', data='test-body') + self.assertEqual(200, resp.status_code) + self.assertEqual({}, body) + mock_request.assert_called_once_with( + 'POST', 'http://example.com:6688', + data='"test-body"', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + + def test_http_json_request_non_json_resp_cont_type(self, mock_request): + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'not/json'}, '{}') + + client = http.HTTPClient('http://example.com:6688') + resp, body = client.json_request('POST', '', data='test-data') + self.assertEqual(200, resp.status_code) + self.assertIsNone(body) + mock_request.assert_called_once_with( + 'POST', 'http://example.com:6688', data='"test-data"', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + + def test_http_json_request_invalid_json(self, mock_request): + # Record a 200 + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, 'invalid-json') + + client = http.HTTPClient('http://example.com:6688') + resp, body = client.json_request('GET', '') + self.assertEqual(200, resp.status_code) + self.assertEqual('invalid-json', body) + mock_request.assert_called_once_with( + 'GET', 'http://example.com:6688', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + + def test_http_json_request_redirect_delete(self, mock_request): + mock_request.side_effect = [ + fakes.FakeHTTPResponse( + 302, 'Found', + {'location': 'http://example.com:6688/foo/bar'}, + ''), + fakes.FakeHTTPResponse( + 200, 'OK', + {'Content-Type': 'application/json'}, + '{}')] + + client = http.HTTPClient('http://example.com:6688/foo') + resp, body = client.json_request('DELETE', '') + + self.assertEqual(200, resp.status_code) + mock_request.assert_has_calls([ + mock.call('DELETE', 'http://example.com:6688/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}), + mock.call('DELETE', 'http://example.com:6688/foo/bar', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + ]) + + def test_http_json_request_redirect_post(self, mock_request): + mock_request.side_effect = [ + fakes.FakeHTTPResponse( + 302, 'Found', + {'location': 'http://example.com:6688/foo/bar'}, + ''), + fakes.FakeHTTPResponse( + 200, 'OK', + {'Content-Type': 'application/json'}, + '{}')] + + client = http.HTTPClient('http://example.com:6688/foo') + resp, body = client.json_request('POST', '') + + self.assertEqual(200, resp.status_code) + mock_request.assert_has_calls([ + mock.call('POST', 'http://example.com:6688/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}), + mock.call('POST', 'http://example.com:6688/foo/bar', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + ]) + + def test_http_json_request_redirect_put(self, mock_request): + mock_request.side_effect = [ + fakes.FakeHTTPResponse( + 302, 'Found', + {'location': 'http://example.com:6688/foo/bar'}, + ''), + fakes.FakeHTTPResponse( + 200, 'OK', + {'Content-Type': 'application/json'}, + '{}')] + + client = http.HTTPClient('http://example.com:6688/foo') + resp, body = client.json_request('PUT', '') + + self.assertEqual(200, resp.status_code) + mock_request.assert_has_calls([ + mock.call('PUT', 'http://example.com:6688/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}), + mock.call('PUT', 'http://example.com:6688/foo/bar', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + ]) + + def test_http_json_request_redirect_diff_location(self, mock_request): + mock_request.side_effect = [ + fakes.FakeHTTPResponse( + 302, 'Found', + {'location': 'http://example.com:6688/diff_lcation'}, + ''), + fakes.FakeHTTPResponse( + 200, 'OK', + {'Content-Type': 'application/json'}, + '{}')] + + client = http.HTTPClient('http://example.com:6688/foo') + resp, body = client.json_request('PUT', '') + + self.assertEqual(200, resp.status_code) + mock_request.assert_has_calls([ + mock.call('PUT', 'http://example.com:6688/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}), + mock.call('PUT', 'http://example.com:6688/diff_lcation', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + ]) + + def test_http_json_request_redirect_error_without_location(self, + mock_request): + mock_request.return_value = fakes.FakeHTTPResponse( + 302, 'Found', {}, '') + client = http.HTTPClient('http://example.com:6688/foo') + self.assertRaises(exc.EndpointException, + client.json_request, 'DELETE', '') + mock_request.assert_called_once_with( + 'DELETE', 'http://example.com:6688/foo', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + + def test_http_json_request_redirect_get(self, mock_request): + # Record the 302 + mock_request.side_effect = [ + fakes.FakeHTTPResponse( + 302, 'Found', + {'location': 'http://example.com:6688'}, + ''), + fakes.FakeHTTPResponse( + 200, 'OK', + {'Content-Type': 'application/json'}, + '{}')] + + client = http.HTTPClient('http://example.com:6688') + resp, body = client.json_request('GET', '') + self.assertEqual(200, resp.status_code) + self.assertEqual({}, body) + + mock_request.assert_has_calls([ + mock.call('GET', 'http://example.com:6688', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}), + mock.call('GET', 'http://example.com:6688', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + ]) + + def test_http_404_json_request(self, mock_request): + mock_request.return_value = fakes.FakeHTTPResponse( + 404, 'Not Found', {'Content-Type': 'application/json'}, '') + + client = http.HTTPClient('http://example.com:6688') + e = self.assertRaises(exc.NotFound, client.json_request, 'GET', '') + # Assert that the raised exception can be converted to string + self.assertIsNotNone(str(e)) + # Record a 404 + mock_request.assert_called_once_with( + 'GET', 'http://example.com:6688', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + + def test_http_300_json_request(self, mock_request): + mock_request.return_value = fakes.FakeHTTPResponse( + 300, 'OK', {'Content-Type': 'application/json'}, '') + client = http.HTTPClient('http://example.com:6688') + e = self.assertRaises( + exc.MultipleChoices, client.json_request, 'GET', '') + # Assert that the raised exception can be converted to string + self.assertIsNotNone(str(e)) + + # Record a 300 + mock_request.assert_called_once_with( + 'GET', 'http://example.com:6688', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}) + + def test_fake_json_request(self, mock_request): + headers = {'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'} + mock_request.side_effect = [socket.gaierror] + + client = http.HTTPClient('fake://example.com:6688') + self.assertRaises(exc.EndpointNotFound, + client.json_request, "GET", "/") + mock_request.assert_called_once_with('GET', 'fake://example.com:6688/', + allow_redirects=False, + headers=headers) + + def test_http_request_socket_error(self, mock_request): + headers = {'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'} + mock_request.side_effect = [socket.error] + + client = http.HTTPClient('http://example.com:6688') + self.assertRaises(exc.ConnectionError, + client.json_request, "GET", "/") + mock_request.assert_called_once_with('GET', 'http://example.com:6688/', + allow_redirects=False, + headers=headers) + + def test_http_request_socket_timeout(self, mock_request): + headers = {'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'} + mock_request.side_effect = [socket.timeout] + + client = http.HTTPClient('http://example.com:6688') + self.assertRaises(exc.ConnectionError, + client.json_request, "GET", "/") + mock_request.assert_called_once_with('GET', 'http://example.com:6688/', + allow_redirects=False, + headers=headers) + + def test_http_request_specify_timeout(self, mock_request): + mock_request.return_value = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, '{}') + + client = http.HTTPClient('http://example.com:6688', timeout='123') + resp, body = client.json_request('GET', '') + self.assertEqual(200, resp.status_code) + self.assertEqual({}, body) + mock_request.assert_called_once_with( + 'GET', 'http://example.com:6688', + allow_redirects=False, + headers={'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-pankoclient'}, + timeout=float(123)) + + def test_get_system_ca_file(self, mock_request): + chosen = '/etc/ssl/certs/ca-certificates.crt' + with mock.patch('os.path.exists') as mock_os: + mock_os.return_value = chosen + + ca = http.get_system_ca_file() + self.assertEqual(chosen, ca) + + mock_os.assert_called_once_with(chosen) + + def test_insecure_verify_cert_none(self, mock_request): + client = http.HTTPClient('https://foo', insecure=True) + self.assertFalse(client.verify_cert) + + def test_passed_cert_to_verify_cert(self, mock_request): + client = http.HTTPClient('https://foo', ca_file="NOWHERE") + self.assertEqual("NOWHERE", client.verify_cert) + + with mock.patch('pankoclient.common.http.get_system_ca_file') as gsf: + gsf.return_value = "SOMEWHERE" + client = http.HTTPClient('https://foo') + self.assertEqual("SOMEWHERE", client.verify_cert) + + def test_methods(self, mock_request): + fake = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, '{}') + mock_request.return_value = fake + + client = http.HTTPClient('http://example.com:6688') + methods = [client.get, client.put, client.post, client.patch, + client.delete, client.head] + for method in methods: + resp, body = method('') + self.assertEqual(200, resp.status_code) + + +class TestSessionClient(base.TestBase): + + def setUp(self): + super(TestSessionClient, self).setUp() + self.request = mock.patch.object(adapter.LegacyJsonAdapter, + 'request').start() + + def test_session_simple_request(self): + resp = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/octet-stream'}, '{}') + self.request.return_value = (resp, {}) + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY) + resp, body = client.request(method='GET', url='') + self.assertEqual(200, resp.status_code) + self.assertEqual('{}', ''.join([x for x in resp.content])) + self.assertEqual({}, body) + + def test_session_json_request(self): + fake = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, + jsonutils.dumps({'some': 'body'})) + self.request.return_value = (fake, {'some': 'body'}) + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY) + + resp, body = client.request('', 'GET') + self.assertEqual(200, resp.status_code) + self.assertEqual({'some': 'body'}, resp.json()) + self.assertEqual({'some': 'body'}, body) + + def test_404_error_response(self): + fake = fakes.FakeHTTPResponse( + 404, 'Not Found', {'Content-Type': 'application/json'}, '') + self.request.return_value = (fake, '') + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY) + e = self.assertRaises(exc.NotFound, + client.request, '', 'GET') + # Assert that the raised exception can be converted to string + self.assertIsNotNone(six.text_type(e)) + + def test_redirect_302_location(self): + fake1 = fakes.FakeHTTPResponse( + 302, 'OK', {'location': 'http://no.where/ishere'}, '') + fake2 = fakes.FakeHTTPResponse(200, 'OK', + {'Content-Type': 'application/json'}, + jsonutils.dumps({'Mount': 'Fuji'})) + self.request.side_effect = [ + (fake1, None), (fake2, {'Mount': 'Fuji'})] + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY, + endpoint_override='http://no.where/') + resp, body = client.request('', 'GET', redirect=True) + + self.assertEqual(200, resp.status_code) + self.assertEqual({'Mount': 'Fuji'}, utils.get_response_body(resp)) + self.assertEqual({'Mount': 'Fuji'}, body) + + self.assertEqual(('', 'GET'), self.request.call_args_list[0][0]) + self.assertEqual(('ishere', 'GET'), self.request.call_args_list[1][0]) + for call in self.request.call_args_list: + self.assertEqual({'user_agent': 'python-pankoclient', + 'raise_exc': False, + 'redirect': True}, call[1]) + + def test_302_location_not_override(self): + fake1 = fakes.FakeHTTPResponse( + 302, 'OK', {'location': 'http://no.where/ishere'}, '') + fake2 = fakes.FakeHTTPResponse(200, 'OK', + {'Content-Type': 'application/json'}, + jsonutils.dumps({'Mount': 'Fuji'})) + self.request.side_effect = [ + (fake1, None), (fake2, {'Mount': 'Fuji'})] + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY, + endpoint_override='http://endpoint/') + resp, body = client.request('', 'GET', redirect=True) + + self.assertEqual(200, resp.status_code) + self.assertEqual({'Mount': 'Fuji'}, utils.get_response_body(resp)) + self.assertEqual({'Mount': 'Fuji'}, body) + + self.assertEqual(('', 'GET'), self.request.call_args_list[0][0]) + self.assertEqual(('http://no.where/ishere', + 'GET'), self.request.call_args_list[1][0]) + for call in self.request.call_args_list: + self.assertEqual({'user_agent': 'python-pankoclient', + 'raise_exc': False, + 'redirect': True}, call[1]) + + def test_redirect_302_no_location(self): + fake = fakes.FakeHTTPResponse( + 302, 'OK', {}, '') + self.request.side_effect = [(fake, '')] + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY) + e = self.assertRaises(exc.EndpointException, + client.request, '', 'GET', redirect=True) + self.assertEqual("Location not returned with redirect", + six.text_type(e)) + + def test_no_redirect_302_no_location(self): + fake = fakes.FakeHTTPResponse(302, 'OK', + {'location': 'http://no.where/ishere'}, + '') + self.request.side_effect = [(fake, '')] + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY) + resp, body = client.request('', 'GET') + + self.assertEqual(fake, resp) + + def test_300_error_response(self): + fake = fakes.FakeHTTPResponse( + 300, 'FAIL', {'Content-Type': 'application/octet-stream'}, '') + self.request.return_value = (fake, '') + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY) + e = self.assertRaises(exc.MultipleChoices, + client.request, '', 'GET') + # Assert that the raised exception can be converted to string + self.assertIsNotNone(six.text_type(e)) + + def test_506_error_response(self): + # for 506 we don't have specific exception type + fake = fakes.FakeHTTPResponse( + 506, 'FAIL', {'Content-Type': 'application/octet-stream'}, '') + self.request.return_value = (fake, '') + + client = http.SessionClient(session=mock.ANY, + auth=mock.ANY) + e = self.assertRaises(exc.HttpServerError, + client.request, '', 'GET') + + self.assertEqual(506, e.status_code) + + def test_kwargs(self): + fake = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, '{}') + kwargs = dict(endpoint_override='http://no.where/', + data='some_data') + + client = http.SessionClient(mock.ANY) + + self.request.return_value = (fake, {}) + + resp, body = client.request('', 'GET', **kwargs) + + self.assertEqual({'endpoint_override': 'http://no.where/', + 'json': 'some_data', + 'user_agent': 'python-pankoclient', + 'raise_exc': False}, self.request.call_args[1]) + self.assertEqual(200, resp.status_code) + self.assertEqual({}, body) + self.assertEqual({}, utils.get_response_body(resp)) + + @mock.patch.object(jsonutils, 'dumps') + def test_kwargs_with_files(self, mock_dumps): + fake = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, '{}') + mock_dumps.return_value = "{'files': test}}" + data = six.BytesIO(b'test') + kwargs = {'endpoint_override': 'http://no.where/', + 'data': {'files': data}} + client = http.SessionClient(mock.ANY) + + self.request.return_value = (fake, {}) + + resp, body = client.request('', 'GET', **kwargs) + + self.assertEqual({'endpoint_override': 'http://no.where/', + 'json': {'files': data}, + 'user_agent': 'python-pankoclient', + 'raise_exc': False}, self.request.call_args[1]) + self.assertEqual(200, resp.status_code) + self.assertEqual({}, body) + self.assertEqual({}, utils.get_response_body(resp)) + + def test_methods(self): + fake = fakes.FakeHTTPResponse( + 200, 'OK', {'Content-Type': 'application/json'}, '{}') + self.request.return_value = (fake, {}) + + client = http.SessionClient(mock.ANY) + methods = [client.get, client.put, client.post, client.patch, + client.delete, client.head] + for method in methods: + resp, body = method('') + self.assertEqual(200, resp.status_code) + + def test_credentials_headers(self): + client = http.SessionClient(mock.ANY) + self.assertEqual({}, client.credentials_headers()) diff --git a/pankoclient/tests/unit/common/test_utils.py b/pankoclient/tests/unit/common/test_utils.py new file mode 100644 index 0000000..931d5a9 --- /dev/null +++ b/pankoclient/tests/unit/common/test_utils.py @@ -0,0 +1,50 @@ +# 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 pankoclient.common import utils +from pankoclient.tests.unit import base + + +class TestUtils(base.TestBase): + + def test_get_response_body_json(self): + resp = mock.Mock() + resp.headers = {'Content-Type': 'application/json'} + resp.json.return_value = mock.sentinel.fake_body + body = utils.get_response_body(resp) + self.assertEqual(mock.sentinel.fake_body, body) + + def test_get_response_body_json_value_error(self): + resp = mock.Mock() + resp.content = mock.sentinel.fake_content + resp.headers = {'Content-Type': 'application/json'} + resp.json.side_effect = ValueError('json format error.') + body = utils.get_response_body(resp) + self.assertEqual(mock.sentinel.fake_content, body) + + def test_get_response_body_raw(self): + resp = mock.Mock() + resp.headers = {'Content-Type': 'application/octet-stream'} + resp.body.return_value = mock.sentinel.fake_body + body = utils.get_response_body(resp) + self.assertEqual(mock.sentinel.fake_body, body) + + def test_get_response_body_unknown_type(self): + resp = mock.Mock() + resp.headers = {'Content-Type': 'application/unknown'} + body = utils.get_response_body(resp) + self.assertIsNone(body) diff --git a/pankoclient/tests/unit/fakes.py b/pankoclient/tests/unit/fakes.py new file mode 100644 index 0000000..8c2ea31 --- /dev/null +++ b/pankoclient/tests/unit/fakes.py @@ -0,0 +1,143 @@ +# 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 oslo_serialization import jsonutils +from requests import Response + +from pankoclient.common import base +from pankoclient.v2 import capabilities + + +# fake request id +FAKE_REQUEST_ID = 'req-0594c66b-6973-405c-ae2c-43fcfc00f2e3' +FAKE_REQUEST_ID_LIST = [FAKE_REQUEST_ID] + +# fake resource id +FAKE_RESOURCE_ID = '0594c66b-6973-405c-ae2c-43fcfc00f2e3' +FAKE_RESOURCE_NAME = 'name-0594c66b-6973-405c-ae2c-43fcfc00f2e3' + +# fake resource response key +FAKE_RESOURCE_ITEM_URL = '/resources/%s' +FAKE_RESOURCE_COLLECTION_URL = '/resources' + + +def create_response_obj_with_header(): + resp = Response() + resp.headers['x-openstack-request-id'] = FAKE_REQUEST_ID + return resp + + +def create_response_obj_with_compute_header(): + resp = Response() + resp.headers['x-compute-request-id'] = FAKE_REQUEST_ID + return resp + + +def create_resource_manager(): + return FakeManager() + + +class FakeBaremetalComputeV1Client(object): + + def __init__(self, **kwargs): + self.fake_http_client = mock.Mock() + self.capabilities = capabilities.CapabilitiesManager( + self.fake_http_client) + + +class FakeHTTPClient(object): + + def get(self): + pass + + def head(self): + pass + + def post(self): + pass + + def put(self): + pass + + def delete(self): + pass + + def patch(self): + pass + + +class FakeResource(base.Resource): + pass + + +class FakeManager(base.ManagerWithFind): + resource_class = FakeResource + + def __init__(self, api=None): + if not api: + api = FakeHTTPClient() + super(FakeManager, self).__init__(api) + + def get(self, resource): + return self._get(FAKE_RESOURCE_ITEM_URL % base.getid(resource)) + + def list(self): + return self._list(FAKE_RESOURCE_COLLECTION_URL, + response_key='resources') + + def update(self, resource): + return self._update(FAKE_RESOURCE_ITEM_URL % base.getid(resource), + resource) + + def create(self, resource): + return self._create(FAKE_RESOURCE_COLLECTION_URL, resource) + + def delete(self, resource): + return self._delete(FAKE_RESOURCE_ITEM_URL % base.getid(resource)) + + +class FakeRaw(object): + version = 110 + + +class FakeHTTPResponse(object): + + version = 1.1 + + def __init__(self, status_code, reason, headers, content): + self.headers = headers + self.content = content + self.status_code = status_code + self.reason = reason + self.raw = FakeRaw() + + def getheader(self, name, default=None): + return self.headers.get(name, default) + + def getheaders(self): + return self.headers.items() + + def read(self, amt=None): + b = self.content + self.content = None + return b + + def iter_content(self, chunksize): + return self.content + + def json(self): + return jsonutils.loads(self.content) + diff --git a/pankoclient/tests/unit/osc/__init__.py b/pankoclient/tests/unit/osc/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pankoclient/tests/unit/osc/test_plugin.py b/pankoclient/tests/unit/osc/test_plugin.py new file mode 100644 index 0000000..ada7523 --- /dev/null +++ b/pankoclient/tests/unit/osc/test_plugin.py @@ -0,0 +1,79 @@ +# 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.tests import fakes + +from pankoclient.osc import plugin +from pankoclient.tests.unit import base + + +class TestBaremetalComputePlugin(base.TestBase): + + @mock.patch('pankoclient.v1.client.Client') + def test_make_client_with_session(self, panko_client): + instance = mock.Mock() + instance._api_version = { + plugin.API_NAME: plugin.DEFAULT_EVENT_API_VERSION} + instance.get_endpoint_for_service_type.return_value = mock.sentinel.ep + instance.region_name = fakes.REGION_NAME + instance.interface = fakes.INTERFACE + instance.auth.auth_url = fakes.AUTH_URL + instance.auth_ref.username = fakes.USERNAME + instance.session = 'fake_session' + + plugin.make_client(instance) + + instance.get_endpoint_for_service_type.assert_called_once_with( + plugin.API_NAME, + region_name=fakes.REGION_NAME, + interface=fakes.INTERFACE, + ) + panko_client.assert_called_once_with( + endpoint=mock.sentinel.ep, + auth_url=fakes.AUTH_URL, + region_name=fakes.REGION_NAME, + username=fakes.USERNAME, + session='fake_session', + ) + + @mock.patch('pankoclient.v1.client.Client') + def test_make_client_no_session(self, panko_client): + instance = mock.Mock() + instance._api_version = { + plugin.API_NAME: plugin.DEFAULT_EVENT_API_VERSION} + instance.get_endpoint_for_service_type.return_value = mock.sentinel.ep + instance.region_name = fakes.REGION_NAME + instance.interface = fakes.INTERFACE + instance.auth.auth_url = fakes.AUTH_URL + instance.auth_ref.username = fakes.USERNAME + instance.auth_ref.auth_token = fakes.AUTH_TOKEN + instance.session = None + + plugin.make_client(instance) + + instance.get_endpoint_for_service_type.assert_called_once_with( + plugin.API_NAME, + region_name=fakes.REGION_NAME, + interface=fakes.INTERFACE, + ) + panko_client.assert_called_once_with( + endpoint=mock.sentinel.ep, + auth_url=fakes.AUTH_URL, + region_name=fakes.REGION_NAME, + username=fakes.USERNAME, + token=fakes.AUTH_TOKEN, + ) diff --git a/pankoclient/tests/unit/osc/v1/__init__.py b/pankoclient/tests/unit/osc/v1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pankoclient/v2/client.py b/pankoclient/v2/client.py index c35bcbf..34fb0b1 100644 --- a/pankoclient/v2/client.py +++ b/pankoclient/v2/client.py @@ -13,8 +13,8 @@ # under the License. # +from pankoclient.v2 import capabilities from pankoclient.common import http -from pankoclient.v2 import events class Client(object): @@ -23,5 +23,4 @@ class Client(object): def __init__(self, *args, **kwargs): """Initialize a new client for the Panko v1 API.""" self.http_client = http._construct_http_client(*args, **kwargs) - self.event = events.EventManager( - self.http_client) + self.capabilities = capabilities.CapabilitiesManager(self.http_client) diff --git a/pankoclient/v2/events.py b/pankoclient/v2/events.py deleted file mode 100644 index bb17cd1..0000000 --- a/pankoclient/v2/events.py +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright 2017 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 pankoclient.common import base -from pankoclient.common import utils - -class Event(base.Resource): - pass - - -class EventManager(base.ManagerWithFind): - resource_class = Event - - def list(self, query=None, limit=None, marker=None, sorts=None): - """List Events - :param query: Filter arguments for which Events to return - :type query: list - :param limit: maximum number of resources to return - :type limit: int - :param marker: the last item of the previous page; we return the next - results after this value. - :type marker: str - :param sorts: list of resource attributes to order by. - :type sorts: list of str - """ - pagination = utils.get_pagination_options(limit, marker, sorts) - #simple_query_string = EventManager.build_simple_query_string(query) - - url = self.url - options = [] - if pagination: - options.append(pagination) - #if simple_query_string: - # options.append(simple_query_string) - if options: - url += "?" + "&".join(options) - return self._get(url).json() diff --git a/setup.cfg b/setup.cfg index 795bc1a..a41d431 100644 --- a/setup.cfg +++ b/setup.cfg @@ -30,7 +30,7 @@ openstack.cli.extension = event = pankoclient.osc.plugin openstack.event.v2 = - alarming capabilities list = pankoclient.v2.capabilities_cli:CliCapabilitiesList + event capabilities list = pankoclient.osc.v2.capabilities:CliCapabilitiesList [build_sphinx] source-dir = doc/source