From 3cc9879347aee436a97a13fa04ca7c0f1e7c6490 Mon Sep 17 00:00:00 2001 From: Dougal Matthews Date: Mon, 27 Jan 2014 09:58:31 +0000 Subject: [PATCH] Add bindings for overcloud API entrypoints Add the entrypoint for the new overcloud type provided in the Tuskar API after the domain model refactor. Change-Id: I49b63905b5d657e63471bc3d02cc7cc2edd90caf Implements: blueprint update-to-match-api-changes --- tuskarclient/common/base.py | 14 +++- tuskarclient/tests/v1/test_client.py | 4 +- tuskarclient/tests/v1/test_overclouds.py | 78 +++++++++++++++++++ tuskarclient/v1/client.py | 2 + tuskarclient/v1/overclouds.py | 95 ++++++++++++++++++++++++ 5 files changed, 189 insertions(+), 4 deletions(-) create mode 100644 tuskarclient/tests/v1/test_overclouds.py create mode 100644 tuskarclient/v1/overclouds.py diff --git a/tuskarclient/common/base.py b/tuskarclient/common/base.py index d4f4914..8f508b2 100644 --- a/tuskarclient/common/base.py +++ b/tuskarclient/common/base.py @@ -56,16 +56,26 @@ class Manager(object): path is returned. Otherwise the collection path is returned. :param id: id of the resource (optional) + :type id: string + + :return: A string representing the API endpoint + :rtype: string """ raise NotImplementedError("_path method not implemented.") def _single_path(self, id): - """This is like the _path method, but it asserts that the rack_id + """This is like the _path method, but it asserts that the id parameter is not None. This is useful e.g. when you want to make sure that you can't issue a DELETE request on a collection URL. + + :param id: id of the resource (not optional) + :type id: string + + :return: A string representing the API endpoint + :rtype: string """ if not id: - raise ValueError("{0} id for deletion must not be null." + raise ValueError("{0} id is required." .format(self.resource_class)) return self._path(id) diff --git a/tuskarclient/tests/v1/test_client.py b/tuskarclient/tests/v1/test_client.py index c8c0368..2c0d0a0 100644 --- a/tuskarclient/tests/v1/test_client.py +++ b/tuskarclient/tests/v1/test_client.py @@ -55,5 +55,5 @@ class ClientTest(tutils.TestCase): self.client = client.Client(self.endpoint) def test_managers_present(self): - #TODO(dmatthew): re-add tests for the managers with the new versions - pass + self.assertThat(self.client, HasManager('OvercloudManager', + 'overclouds')) diff --git a/tuskarclient/tests/v1/test_overclouds.py b/tuskarclient/tests/v1/test_overclouds.py new file mode 100644 index 0000000..31ca73d --- /dev/null +++ b/tuskarclient/tests/v1/test_overclouds.py @@ -0,0 +1,78 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +import tuskarclient.tests.utils as tutils +from tuskarclient.v1 import overclouds + + +class OvercloudManagerTest(tutils.TestCase): + + def setUp(self): + """Create a mock API object and bind to the OvercloudManager manager. + """ + super(OvercloudManagerTest, self).setUp() + self.api = mock.Mock() + self.om = overclouds.OvercloudManager(self.api) + + def test_get(self): + """Test a standard GET operation to read/retrieve the overcloud.""" + self.om._get = mock.Mock(return_value='fake_overcloud') + + self.assertEqual(self.om.get('fake_overcloud'), 'fake_overcloud') + self.om._get.assert_called_with('/v1/overclouds/fake_overcloud') + + def test_get_404(self): + """Test a 404 response to a standard GET.""" + self.om._get = mock.Mock(return_value=None) + + self.assertEqual(self.om.get('fake_overcloud'), None) + self.om._get.assert_called_with('/v1/overclouds/fake_overcloud') + + def test_list(self): + """Test retrieving a list of overclouds via GET.""" + self.om._list = mock.Mock(return_value=['fake_overcloud']) + + self.assertEqual(self.om.list(), ['fake_overcloud']) + self.om._list.assert_called_with('/v1/overclouds') + + def test_create(self): + """Test creating a new overcloud via POST.""" + self.om._create = mock.Mock(return_value=['fake_overcloud']) + + self.assertEqual( + self.om.create(dummy='dummy overcloud data'), + ['fake_overcloud']) + + self.om._create.assert_called_with( + '/v1/overclouds', + {'dummy': 'dummy overcloud data'}) + + def test_update(self): + """Test updating an overcloud via POST.""" + self.om._update = mock.Mock(return_value=['fake_overcloud']) + + self.assertEqual( + self.om.update(42, dummy='dummy overcloud data'), + ['fake_overcloud']) + + self.om._update.assert_called_with( + '/v1/overclouds/42', + {'dummy': 'dummy overcloud data'}) + + def test_delete(self): + """Test deleting/removing an overcloud via DELETE.""" + self.om._delete = mock.Mock(return_value=None) + + self.assertEqual(self.om.delete(42), None) + self.om._delete.assert_called_with('/v1/overclouds/42') diff --git a/tuskarclient/v1/client.py b/tuskarclient/v1/client.py index ccd46c7..5e8ad56 100644 --- a/tuskarclient/v1/client.py +++ b/tuskarclient/v1/client.py @@ -11,6 +11,7 @@ # under the License. from tuskarclient.common import http +from tuskarclient.v1 import overclouds class Client(object): @@ -23,3 +24,4 @@ class Client(object): def __init__(self, *args, **kwargs): self.http_client = http.HTTPClient(*args, **kwargs) + self.overclouds = overclouds.OvercloudManager(self.http_client) diff --git a/tuskarclient/v1/overclouds.py b/tuskarclient/v1/overclouds.py new file mode 100644 index 0000000..6de84b7 --- /dev/null +++ b/tuskarclient/v1/overclouds.py @@ -0,0 +1,95 @@ +# 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 tuskarclient.common import base + + +class Overcloud(base.Resource): + """Represents an instance of a Overcloud in the Tuskar API. + + :param manager: Manager object + :param info: dictionary representing resource attributes + :param loaded: prevent lazy-loading if set to True + """ + + +class OvercloudManager(base.Manager): + """OvercloudManager interacts with the Tuskar API and provides CRUD + operations for the Overcloud type. + + """ + + #: The class used to represent an Overcloud instance + resource_class = Overcloud + + @staticmethod + def _path(overcloud_id=None): + + if overcloud_id: + return '/v1/overclouds/%s' % overcloud_id + + return '/v1/overclouds' + + def list(self): + """Get a list of the existing Overclouds + + :return: A list of overclounds or an empty list if none are found. + :rtype: [tuskarclient.v1.overclouds.Overcloud] or [] + """ + return self._list(self._path()) + + def get(self, overcloud_id): + """Get the Overcloud by its ID. + + :param overcloud_id: id of the Overcloud. + :type overcloud_id: string + + :return: A Overcloud instance or None if its not found. + :rtype: tuskarclient.v1.overclouds.Overcloud or None + """ + return self._get(self._single_path(overcloud_id)) + + def create(self, **fields): + """Create a new Overcloud. + + :param fields: A set of key/value pairs representing a Overcloud. + :type fields: string + + :return: A Overcloud instance or None if its not found. + :rtype: tuskarclient.v1.overclouds.Overcloud + """ + return self._create(self._path(), fields) + + def update(self, overcloud_id, **fields): + """Update an existing Overcloud. + + :param overcloud_id: id of the Overcloud. + :type overcloud_id: string + + :param fields: A set of key/value pairs representing the Overcloud. + :type fields: string + + :return: A Overcloud instance or None if its not found. + :rtype: tuskarclient.v1.overclouds.Overcloud or None + """ + return self._update(self._single_path(overcloud_id), fields) + + def delete(self, overcloud_id): + """Delete an Overcloud. + + :param overcloud_id: id of the Overcloud. + :type overcloud_id: string + + :return: None + :rtype: None + """ + return self._delete(self._single_path(overcloud_id))