Refactor secret functional tests using models and behaviors

Updated the functional tests to use models and behaviors for secrets.
* Adding simple HATEOS-compatible rest client
* Fixing model de/serialization
* Modifying all tests to conform to the new client
* Adding option to load tempest config from local etc
* Incorporating review feedback

Change-Id: I497b4f7bf10a9f47ca399b569d964762505466c9
This commit is contained in:
Steve Heyman 2014-09-12 19:19:07 -05:00 committed by John Vrbanac
parent c13df4f2be
commit 018404b82e
16 changed files with 569 additions and 187 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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'))

View File

@ -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)

View File

View File

@ -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)

View File

@ -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