diff --git a/functionaltests/api/base.py b/functionaltests/api/base.py index 9e9120fce..13068b8eb 100644 --- a/functionaltests/api/base.py +++ b/functionaltests/api/base.py @@ -13,29 +13,21 @@ 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 oslotest.base as oslotest +import os +import oslotest.base as oslotest from tempest import auth -from tempest import clients -from tempest.common import rest_client +from tempest import clients as tempest_clients from tempest import config +from functionaltests.common import client CONF = config.CONF - -class BarbicanClient(rest_client.RestClient): - - def __init__(self, auth_provider): - super(BarbicanClient, self).__init__(auth_provider) - - # get the project id (aka tenant id) which we need in the API tests - # to build the correct URI. - credentials = auth_provider.fill_credentials() - self.project_id = credentials.tenant_id - - self.service = 'keystore' - self.endpoint_url = 'publicURL' +# Use local tempest conf if one is available. +# This usually means we're running tests outside of devstack +if os.path.exists('./etc/dev_tempest.conf'): + CONF.set_config_path('./etc/dev_tempest.conf') class TestCase(oslotest.BaseTestCase): @@ -45,9 +37,9 @@ class TestCase(oslotest.BaseTestCase): credentials = BarbicanCredentials() - mgr = clients.Manager(credentials=credentials) + mgr = tempest_clients.Manager(credentials=credentials) auth_provider = mgr.get_auth_provider(credentials) - self.client = BarbicanClient(auth_provider) + self.client = client.BarbicanClient(auth_provider) class BarbicanCredentials(auth.KeystoneV2Credentials): diff --git a/functionaltests/api/test_versions.py b/functionaltests/api/test_versions.py index 044c78844..b4e1f3237 100644 --- a/functionaltests/api/test_versions.py +++ b/functionaltests/api/test_versions.py @@ -12,21 +12,17 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. -import json - -import testtools - from functionaltests.api import base class VersionDiscoveryTestCase(base.TestCase): - @testtools.skipIf(True, 'Skip until blueprint fix-version-api is complete') def test_get_root_discovers_v1(self): """Covers retrieving version data for Barbican.""" - resp, body = self.client.get(' ') - body = json.loads(body) + url_without_version = self.client.get_base_url(include_version=False) + resp = self.client.get(url_without_version) + body = resp.json() - self.assertEqual(resp.status, 200) + self.assertEqual(resp.status_code, 200) self.assertEqual(body.get('v1'), 'current') self.assertGreater(len(body.get('build')), 1) diff --git a/functionaltests/api/v1/behaviors/__init__.py b/functionaltests/api/v1/behaviors/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/functionaltests/api/v1/behaviors/base_behaviors.py b/functionaltests/api/v1/behaviors/base_behaviors.py new file mode 100644 index 000000000..f6461a93a --- /dev/null +++ b/functionaltests/api/v1/behaviors/base_behaviors.py @@ -0,0 +1,37 @@ +""" +Copyright 2014 Rackspace + +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 os + + +class BaseBehaviors(object): + + def __init__(self, client): + self.client = client + self.created_entities = [] + + def get_id_from_href(self, href): + """Returns the id from reference. + + The id must be the last item in the href. + + :param href: The href containing the id. + :returns the id portion of the href + """ + + item_id = None + if href and len(href) > 0: + base, item_id = os.path.split(href) + return item_id diff --git a/functionaltests/api/v1/behaviors/secret_behaviors.py b/functionaltests/api/v1/behaviors/secret_behaviors.py new file mode 100644 index 000000000..cd1fc7cba --- /dev/null +++ b/functionaltests/api/v1/behaviors/secret_behaviors.py @@ -0,0 +1,68 @@ +""" +Copyright 2014 Rackspace + +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 functionaltests.api.v1.behaviors.base_behaviors import BaseBehaviors +from functionaltests.api.v1.models import secret_models + + +class SecretBehaviors(BaseBehaviors): + + def create_secret(self, model): + """Create a secret from the data in the model. + + :param model: The metadata used to create the secret + :return: A tuple containing the response from the create + and the href to the newly created secret + """ + resp = self.client.post('secrets', request_model=model) + + returned_data = resp.json() + secret_ref = returned_data.get('secret_ref') + if secret_ref: + self.created_entities.append(secret_ref) + return resp, secret_ref + + def update_secret_payload(self, secret_ref, payload, content_type): + """Updates a secret's payload data + + :param secret_ref: HATEOS ref of the secret to be deleted + :return: A request response object + """ + headers = {'Content-Type': content_type} + return self.client.put(secret_ref, data=payload, extra_headers=headers) + + def get_secret_metadata(self, secret_ref): + """Retrieves a secret's metadata + + :param secret_ref: HATEOS ref of the secret to be deleted + :return: A request response object + """ + return self.client.get( + secret_ref, response_model_type=secret_models.SecretModel) + + def delete_secret(self, secret_ref): + """Delete a secret. + + :param secret_ref: HATEOS ref of the secret to be deleted + :return: A request response object + """ + resp = self.client.delete(secret_ref) + self.created_entities.remove(secret_ref) + return resp + + def delete_all_created_secrets(self): + """Delete all of the secrets that we have created.""" + for secret_ref in self.created_entities: + self.delete_secret(secret_ref) diff --git a/functionaltests/api/v1/models/__init__.py b/functionaltests/api/v1/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/functionaltests/api/v1/models/base_models.py b/functionaltests/api/v1/models/base_models.py new file mode 100644 index 000000000..7b4c4dc25 --- /dev/null +++ b/functionaltests/api/v1/models/base_models.py @@ -0,0 +1,84 @@ +""" +Copyright 2014 Rackspace + +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 json + +from tempest.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +class BaseModel(object): + """Base class for models. + + To allow simple (de)serialization we will use __dict__ to create + """ + + def obj_to_json(self): + """Convert this object to a JSON string. + + :return A string of JSON containing the fields in this object + """ + + return json.dumps(self.obj_to_dict()) + + def obj_to_dict(self): + """Create a dict of the values for this model object. + + If there are fields that are not set in this object then those + will NOT have entries in the returned dict. + + :return A dict representing this model + """ + + the_dict = self.__dict__ + retval = self._remove_empty_fields_from_dict(the_dict) + return retval + + def _remove_empty_fields_from_dict(self, dictionary): + """Remove k,v pairs with empty values from a dictionary. + + :param dictionary: a dictionary of stuff + :return: the same dictionary where all k,v pairs with empty values + have been removed. + """ + + # Dumping the keys to a list as we'll be changing the dict size + empty_keys = [k for k, v in dictionary.iteritems() if not v] + for k in empty_keys: + del dictionary[k] + return dictionary + + @classmethod + def json_to_obj(cls, serialized_str): + """Create a model from a JSON string. + + :param serialized_str: the JSON string + :return a secret object + """ + try: + json_dict = json.loads(serialized_str) + return cls.dict_to_obj(json_dict) + except TypeError as e: + LOG.error('Couldn\'t deserialize input: %s', e) + + @classmethod + def dict_to_obj(cls, input_dict): + """Create an object from a dict. + + :param input_dict: A dict of fields. + :return a model object build from the passed in dict. + """ + return cls(**input_dict) diff --git a/functionaltests/api/v1/models/order_models.py b/functionaltests/api/v1/models/order_models.py new file mode 100644 index 000000000..f21230381 --- /dev/null +++ b/functionaltests/api/v1/models/order_models.py @@ -0,0 +1,38 @@ +""" +Copyright 2014 Rackspace + +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 functionaltests.api.v1.models.base_models import BaseModel +from functionaltests.api.v1.models.secret_models import SecretModel + + +class OrderModel(BaseModel): + + def __init__(self, status=None, secret_ref=None, updated=None, + created=None, secret=None, order_ref=None): + super(OrderModel, self).__init__() + self.status = status + self.secret_ref = secret_ref + self.updated = updated + self.created = created + self.secret = secret + self.order_ref = order_ref + + @classmethod + def dict_to_obj(cls, input_dict): + secret_metadata = input_dict.get('secret') + if secret_metadata: + input_dict['secret'] = SecretModel.dict_to_obj(secret_metadata) + + return super(OrderModel, cls).dict_to_obj(input_dict) diff --git a/functionaltests/api/v1/models/secret_models.py b/functionaltests/api/v1/models/secret_models.py new file mode 100644 index 000000000..adaa2b8e0 --- /dev/null +++ b/functionaltests/api/v1/models/secret_models.py @@ -0,0 +1,40 @@ +""" +Copyright 2014 Rackspace + +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 functionaltests.api.v1.models.base_models import BaseModel + + +class SecretModel(BaseModel): + + def __init__(self, name=None, expiration=None, algorithm=None, + secret_ref=None, bit_length=None, mode=None, + payload_content_type=None, payload=None, content_types=None, + payload_content_encoding=None, status=None, updated=None, + created=None): + super(SecretModel, self).__init__() + + self.name = name + self.expiration = expiration + self.algorithm = algorithm + self.bit_length = bit_length + self.mode = mode + self.payload_content_type = payload_content_type + self.payload = payload + self.content_types = content_types + self.payload_content_encoding = payload_content_encoding + self.secret_ref = secret_ref + self.status = status + self.updated = updated + self.created = created diff --git a/functionaltests/api/v1/test_consumers.py b/functionaltests/api/v1/test_consumers.py index cf5fb5159..93d569ca5 100644 --- a/functionaltests/api/v1/test_consumers.py +++ b/functionaltests/api/v1/test_consumers.py @@ -15,6 +15,8 @@ import json from functionaltests.api import base +from functionaltests.api.v1.behaviors import secret_behaviors +from functionaltests.api.v1.models import secret_models create_secret_data = { "name": "AES key", @@ -63,45 +65,39 @@ create_container_data = { class ConsumersTestCase(base.TestCase): + def _create_a_secret(self): + secret_model = secret_models.SecretModel(**create_secret_data) + resp, secret_ref = self.secret_behaviors.create_secret(secret_model) + self.assertEqual(resp.status_code, 201) + self.assertIsNotNone(secret_ref) + + return secret_ref + def setUp(self): super(ConsumersTestCase, self).setUp() + self.secret_behaviors = secret_behaviors.SecretBehaviors(self.client) + # Set up two secrets - secret_json_data = json.dumps(create_secret_data) - resp, body = self.client.post( - '/secrets', - secret_json_data, - headers={'content-type': 'application/json'} - ) - self.assertEqual(resp.status, 201) - - returned_data = json.loads(body) - secret_ref_1 = returned_data['secret_ref'] - self.assertIsNotNone(secret_ref_1) - resp, body = self.client.post( - '/secrets', - secret_json_data, - headers={'content-type': 'application/json'} - ) - self.assertEqual(resp.status, 201) - - returned_data = json.loads(body) - secret_ref_2 = returned_data['secret_ref'] - self.assertIsNotNone(secret_ref_2) + secret_ref_1 = self._create_a_secret() + secret_ref_2 = self._create_a_secret() # Create a container with our secrets create_container_data['secret_refs'][0]['secret_ref'] = secret_ref_1 create_container_data['secret_refs'][1]['secret_ref'] = secret_ref_2 container_json_data = json.dumps(create_container_data) - resp, body = self.client.post( - '/containers', container_json_data, - headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 201) + resp = self.client.post( + 'containers', container_json_data) + self.assertEqual(resp.status_code, 201) - returned_data = json.loads(body) + returned_data = resp.json() container_ref = returned_data['container_ref'] self.assertIsNotNone(container_ref) self.container_id = container_ref.split('/')[-1] + def tearDown(self): + self.secret_behaviors.delete_all_created_secrets() + super(ConsumersTestCase, self).tearDown() + def test_create_consumer(self): """Covers consumer creation. @@ -109,12 +105,12 @@ class ConsumersTestCase(base.TestCase): single POST. """ json_data = json.dumps(create_consumer_data) - resp, body = self.client.post( - '/containers/{0}/consumers'.format(self.container_id), - json_data, headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) + resp = self.client.post( + 'containers/{0}/consumers'.format(self.container_id), + json_data) + self.assertEqual(resp.status_code, 200) - returned_data = json.loads(body) + returned_data = resp.json() consumer_data = returned_data['consumers'] self.assertIsNotNone(consumer_data) self.assertIn(create_consumer_data, consumer_data) @@ -128,23 +124,23 @@ class ConsumersTestCase(base.TestCase): json_data = json.dumps(create_consumer_data_for_delete) # Register the consumer once - resp, body = self.client.post( - '/containers/{0}/consumers'.format(self.container_id), - json_data, headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) + resp = self.client.post( + 'containers/{0}/consumers'.format(self.container_id), + json_data) + self.assertEqual(resp.status_code, 200) - returned_data = json.loads(body) + returned_data = resp.json() consumer_data = returned_data['consumers'] self.assertIsNotNone(consumer_data) self.assertIn(create_consumer_data_for_delete, consumer_data) # Delete the consumer - resp, body = self.client.delete( - '/containers/{0}/consumers'.format(self.container_id), - body=json_data, headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) + resp = self.client.delete( + 'containers/{0}/consumers'.format(self.container_id), + json_data) + self.assertEqual(resp.status_code, 200) - returned_data = json.loads(body) + returned_data = resp.json() consumer_data = returned_data['consumers'] self.assertIsNotNone(consumer_data) self.assertNotIn(create_consumer_data_for_delete, consumer_data) @@ -154,34 +150,34 @@ class ConsumersTestCase(base.TestCase): json_data = json.dumps(create_consumer_data_for_recreate) # Register the consumer once - resp, body = self.client.post( - '/containers/{0}/consumers'.format(self.container_id), - json_data, headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) + resp = self.client.post( + 'containers/{0}/consumers'.format(self.container_id), + json_data) + self.assertEqual(resp.status_code, 200) - returned_data = json.loads(body) + returned_data = resp.json() consumer_data = returned_data['consumers'] self.assertIsNotNone(consumer_data) self.assertIn(create_consumer_data_for_recreate, consumer_data) # Delete the consumer - resp, body = self.client.delete( - '/containers/{0}/consumers'.format(self.container_id), - body=json_data, headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) + resp = self.client.delete( + 'containers/{0}/consumers'.format(self.container_id), + json_data) + self.assertEqual(resp.status_code, 200) - returned_data = json.loads(body) + returned_data = resp.json() consumer_data = returned_data['consumers'] self.assertIsNotNone(consumer_data) self.assertNotIn(create_consumer_data_for_recreate, consumer_data) # Register the consumer again - resp, body = self.client.post( - '/containers/{0}/consumers'.format(self.container_id), - json_data, headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) + resp = self.client.post( + 'containers/{0}/consumers'.format(self.container_id), + json_data) + self.assertEqual(resp.status_code, 200) - returned_data = json.loads(body) + returned_data = resp.json() consumer_data = returned_data['consumers'] self.assertIsNotNone(consumer_data) self.assertIn(create_consumer_data_for_recreate, consumer_data) @@ -191,23 +187,23 @@ class ConsumersTestCase(base.TestCase): json_data = json.dumps(create_consumer_data_for_idempotency) # Register the consumer once - resp, body = self.client.post( - '/containers/{0}/consumers'.format(self.container_id), - json_data, headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) + resp = self.client.post( + 'containers/{0}/consumers'.format(self.container_id), + json_data) + self.assertEqual(resp.status_code, 200) - returned_data = json.loads(body) + returned_data = resp.json() consumer_data = returned_data['consumers'] self.assertIsNotNone(consumer_data) self.assertIn(create_consumer_data_for_idempotency, consumer_data) # Register the consumer again, without deleting it first - resp, body = self.client.post( - '/containers/{0}/consumers'.format(self.container_id), - json_data, headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) + resp = self.client.post( + 'containers/{0}/consumers'.format(self.container_id), + json_data) + self.assertEqual(resp.status_code, 200) - returned_data = json.loads(body) + returned_data = resp.json() consumer_data = returned_data['consumers'] self.assertIsNotNone(consumer_data) self.assertIn(create_consumer_data_for_idempotency, consumer_data) diff --git a/functionaltests/api/v1/test_containers.py b/functionaltests/api/v1/test_containers.py index 71a0458dc..6059d351c 100644 --- a/functionaltests/api/v1/test_containers.py +++ b/functionaltests/api/v1/test_containers.py @@ -15,9 +15,9 @@ import json import re -from tempest import exceptions - from functionaltests.api import base +from functionaltests.api.v1.behaviors import secret_behaviors +from functionaltests.api.v1.models import secret_models create_secret_data = { "name": "AES key", @@ -47,64 +47,47 @@ create_container_data = { class ContainersTestCase(base.TestCase): def _create_a_secret(self): - secret_json_data = json.dumps(create_secret_data) - resp, body = self.client.post( - '/secrets', - secret_json_data, - headers={'content-type': 'application/json'} - ) - self.assertEqual(resp.status, 201) - - returned_data = json.loads(body) - secret_ref = returned_data.get('secret_ref') + secret_model = secret_models.SecretModel(**create_secret_data) + resp, secret_ref = self.secret_behaviors.create_secret(secret_model) + self.assertEqual(resp.status_code, 201) self.assertIsNotNone(secret_ref) + return secret_ref def _get_a_secret(self, secret_id): - resp, body = self.client.get( - '/secrets/{0}'.format(secret_id), - headers={'content-type': 'application/json'} - ) - self.assertEqual(resp.status, 200) - return json.loads(body) + resp = self.client.get('secrets/{0}'.format(secret_id)) + self.assertEqual(resp.status_code, 200) + return resp.json() def _create_a_container(self): json_data = json.dumps(create_container_data) - resp, body = self.client.post( - '/containers', json_data, - headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 201) + resp = self.client.post('containers/', json_data) + self.assertEqual(resp.status_code, 201) - returned_data = json.loads(body) - container_ref = returned_data['container_ref'] + body = resp.json() + container_ref = body.get('container_ref') self.assertIsNotNone(container_ref) return container_ref def _get_a_container(self, container_id): - resp, body = self.client.get( - '/containers/{0}'.format(container_id), - headers={'content-type': 'application/json'} - ) - self.assertEqual(resp.status, 200) - self.assertIsNotNone(body) - return json.loads(body) + resp = self.client.get('containers/{0}'.format(container_id)) + self.assertEqual(resp.status_code, 200) + self.assertIsNotNone(resp.content) + return resp.json() def _get_container_list(self): - resp, body = self.client.get( - '/containers/', - headers={'content-type': 'application/json'} - ) - self.assertEqual(resp.status, 200) - return json.loads(body) + resp = self.client.get('containers/') + self.assertEqual(resp.status_code, 200) + return resp.json() def _delete_a_container(self, container_id): - resp, body = self.client.delete( - '/containers/{0}'.format(container_id), headers={} - ) - self.assertEqual(resp.status, 204) + resp = self.client.delete('containers/{0}'.format(container_id)) + self.assertEqual(resp.status_code, 204) def setUp(self): super(ContainersTestCase, self).setUp() + self.secret_behaviors = secret_behaviors.SecretBehaviors(self.client) + # Set up two secrets secret_ref_1 = self._create_a_secret() secret_ref_2 = self._create_a_secret() @@ -115,6 +98,10 @@ class ContainersTestCase(base.TestCase): self.secret_id_1 = secret_ref_1.split('/')[-1] self.secret_id_2 = secret_ref_2.split('/')[-1] + def tearDown(self): + self.secret_behaviors.delete_all_created_secrets() + super(ContainersTestCase, self).tearDown() + def test_create_container(self): """Covers container creation. @@ -138,11 +125,8 @@ class ContainersTestCase(base.TestCase): self._delete_a_container(container_id) # Verify container is gone - self.assertRaises( - exceptions.NotFound, self.client.get, - '/containers/{0}'.format(container_id), - headers={'content-type': 'application/json'} - ) + resp = self.client.get(container_ref) + self.assertEqual(resp.status_code, 404) # Verify the Secrets from the container still exist self._get_a_secret(self.secret_id_1) diff --git a/functionaltests/api/v1/test_orders.py b/functionaltests/api/v1/test_orders.py index a82e720db..3486c7493 100644 --- a/functionaltests/api/v1/test_orders.py +++ b/functionaltests/api/v1/test_orders.py @@ -12,9 +12,8 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. -import json - from functionaltests.api import base +from functionaltests.api.v1.models import order_models create_order_data = { @@ -36,12 +35,9 @@ class OrdersTestCase(base.TestCase): All of the data needed to create the order is provided in a single POST. """ - json_data = json.dumps(create_order_data) - resp, body = self.client.post( - '/orders', json_data, headers={ - 'content-type': 'application/json'}) - self.assertEqual(resp.status, 202) + model = order_models.OrderModel(**create_order_data) + resp = self.client.post('orders/', request_model=model) + self.assertEqual(resp.status_code, 202) - returned_data = json.loads(body) - order_ref = returned_data['order_ref'] - self.assertIsNotNone(order_ref) + body = resp.json() + self.assertIsNotNone(body.get('order_ref')) diff --git a/functionaltests/api/v1/test_secrets.py b/functionaltests/api/v1/test_secrets.py index cc1cab7ad..e0db6c58c 100644 --- a/functionaltests/api/v1/test_secrets.py +++ b/functionaltests/api/v1/test_secrets.py @@ -12,10 +12,11 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. -import json -import os +import testtools from functionaltests.api import base +from functionaltests.api.v1.behaviors import secret_behaviors +from functionaltests.api.v1.models import secret_models one_phase_create_data = { @@ -35,61 +36,71 @@ two_phase_create_data = { "algorithm": "aes", "bit_length": 256, "mode": "cbc", -} - -two_phase_payload_data = { - "payload": "gF6+lLoF3ohA9aPRpt+6bQ==", - "payload_content_type": "application/octet-stream", "payload_content_encoding": "base64", } class SecretsTestCase(base.TestCase): + def setUp(self): + super(SecretsTestCase, self).setUp() + self.behaviors = secret_behaviors.SecretBehaviors(self.client) + self.one_phase_secret_model = secret_models.SecretModel( + **one_phase_create_data) + self.two_phase_secret_model = secret_models.SecretModel( + **two_phase_create_data) + + def tearDown(self): + self.behaviors.delete_all_created_secrets() + super(SecretsTestCase, self).tearDown() + def test_create_secret_single_phase(self): """Covers single phase secret creation. - All of the data needed to create the secret, including payload, - is provided in a single POST. + Verify that a secret gets created with the correct http + response code and a secret reference. """ - json_data = json.dumps(one_phase_create_data) - resp, body = self.client.post( - '/secrets', json_data, headers={ - 'content-type': 'application/json'}) - self.assertEqual(resp.status, 201) + resp, secret_ref = self.behaviors.create_secret( + self.one_phase_secret_model) - returned_data = json.loads(body) - secret_ref = returned_data['secret_ref'] + self.assertEqual(resp.status_code, 201) self.assertIsNotNone(secret_ref) + def test_get_created_secret_metadata(self): + """Covers retrieval of a created secret's metadata.""" + + create_model = self.one_phase_secret_model + resp, secret_ref = self.behaviors.create_secret(create_model) + + self.assertEqual(resp.status_code, 201) + self.assertIsNotNone(secret_ref) + + get_resp = self.behaviors.get_secret_metadata(secret_ref) + get_model = get_resp.model + + self.assertEqual(get_resp.status_code, 200) + self.assertEqual(get_model.name, create_model.name) + self.assertEqual(get_model.algorithm, create_model.algorithm) + self.assertEqual(get_model.bit_length, create_model.bit_length) + self.assertEqual(get_model.mode, create_model.mode) + + @testtools.skip('Skip until we can fix two-step creation') def test_create_secret_two_phase(self): """Covers two phase secret creation. - The first call, a POST, provides the metadata about the - secret - everything except the payload. A subsequent call (PUT) - provides the payload. + Verify that a secret gets created with the correct http + response code and a secret reference. """ - # phase 1 - POST secret without payload - json_data = json.dumps(two_phase_create_data) - resp, body = self.client.post( - '/secrets', json_data, headers={ - 'content-type': 'application/json'}) - self.assertEqual(resp.status, 201) - returned_data = json.loads(body) - secret_ref = returned_data['secret_ref'] + # Phase 1 + resp, secret_ref = self.behaviors.create_secret( + self.two_phase_secret_model) + + self.assertEqual(resp.status_code, 201) self.assertIsNotNone(secret_ref) - secret_id = os.path.split(secret_ref)[1] - self.assertIsNotNone(secret_id) + # Phase 2 + update_resp = self.behaviors.update_secret_payload( + secret_ref, 'YmFt', 'text/plain') - # phase 2 - provide (PUT) the secret payload - json_data = json.dumps(two_phase_payload_data) - resp, body = self.client.post( - '/secrets/{0}'.format(secret_id), json_data, - headers={'content-type': 'application/json'}) - self.assertEqual(resp.status, 200) - - returned_data = json.loads(body) - secret_ref = returned_data['secret_ref'] - self.assertIsNotNone(secret_ref) + self.assertEqual(update_resp.status_code, 204) diff --git a/functionaltests/common/__init__.py b/functionaltests/common/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/functionaltests/common/client.py b/functionaltests/common/client.py new file mode 100644 index 000000000..e6e41b460 --- /dev/null +++ b/functionaltests/common/client.py @@ -0,0 +1,139 @@ +""" +Copyright 2014 Rackspace + +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 os + +import requests +from requests import auth +from tempest.common.utils import misc as misc_utils +from tempest.openstack.common import log as logging + +LOG = logging.getLogger(__name__) + + +class BarbicanClientAuth(auth.AuthBase): + """Implementation of Requests Auth for Barbican http calls.""" + + def __init__(self, auth_provider): + credentials = auth_provider.fill_credentials() + + self.username = credentials.username + self.password = credentials.password + self.project_id = credentials.tenant_id + self.project_name = credentials.tenant_name + self.token = auth_provider.get_token() + + def __call__(self, r): + r.headers['X-Project-Id'] = self.project_id + r.headers['X-Auth-Token'] = self.token + return r + + +class BarbicanClient(object): + + def __init__(self, auth_provider, api_version='v1'): + self._auth = BarbicanClientAuth(auth_provider) + self._auth_provider = auth_provider + self.timeout = 10 + self.api_version = api_version + self.default_headers = { + 'Content-Type': 'application/json' + } + + def stringify_request(self, request_kwargs, response): + format_kwargs = { + 'code': response.status_code, + 'method': request_kwargs.get('method'), + 'url': request_kwargs.get('url'), + 'headers': response.request.headers, + 'body': request_kwargs.get('data'), + 'response_body': response.content + } + return ('{code} {method} {url}\n' + 'Request Headers: {headers}\n' + 'Request Body: {body}\n' + 'Response: {response_body}\n').format(**format_kwargs) + + def log_request(self, request_kwargs, response): + test_name = misc_utils.find_test_caller() + str_request = self.stringify_request(request_kwargs, response) + LOG.info('Request (%s)\n %s', test_name, str_request) + + def attempt_to_deserialize(self, response, model_type): + if model_type and hasattr(model_type, 'json_to_obj'): + return model_type.json_to_obj(response.content) + + def attempt_to_serialize(self, model): + if model and hasattr(model, 'obj_to_json'): + return model.obj_to_json() + + def get_base_url(self, include_version=True): + filters = { + 'service': 'keystore', + 'api_version': self.api_version if include_version else '' + } + + return self._auth_provider.base_url(filters) + + def request(self, method, url, data=None, extra_headers=None, + use_auth=True, response_model_type=None, request_model=None): + """Prepares and sends http request through Requests.""" + if 'http' not in url: + url = os.path.join(self.get_base_url(), url) + + # Duplicate Base headers and add extras (if needed) + headers = {} + headers.update(self.default_headers) + if extra_headers: + headers.update(extra_headers) + + # Attempt to serialize model if required + if request_model: + data = self.attempt_to_serialize(request_model) + + # Prepare call arguments + call_kwargs = { + 'method': method, + 'url': url, + 'headers': headers, + 'data': data, + 'timeout': self.timeout + } + if use_auth: + call_kwargs['auth'] = self._auth + + response = requests.request(**call_kwargs) + + # Attempt to deserialize the response + response.model = self.attempt_to_deserialize(response, + response_model_type) + self.log_request(call_kwargs, response) + return response + + def get(self, *args, **kwargs): + """Proxies the request method specifically for http GET methods.""" + return self.request('GET', *args, **kwargs) + + def post(self, *args, **kwargs): + """Proxies the request method specifically for http POST methods.""" + return self.request('POST', *args, **kwargs) + + def put(self, *args, **kwargs): + """Proxies the request method specifically for http PUT methods.""" + return self.request('PUT', *args, **kwargs) + + def delete(self, *args, **kwargs): + """Proxies the request method specifically for http DELETE methods.""" + return self.request('DELETE', *args, **kwargs) diff --git a/test-requirements.txt b/test-requirements.txt index 25d52c0e3..6333fa313 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -6,6 +6,7 @@ oslotest>=1.1.0.0a1 testrepository>=0.0.18 testtools>=0.9.34 fixtures>=0.3.14 +requests>=1.2.1,!=2.4.0 # Documentation build requirements sphinx>=1.1.2,<1.2