Add magnum certificate api tests

This patch will add cert API test for signing and showing
certificates.

The tests for certificates are integrated into test_bay
tests in order to reuse an already existing bay for
cert testing.  As a side effect, this patch also
combines update bay test with create, list, and delete
to minimize the time spent waiting on bay create.

Implements: blueprint magnum-tempest

Change-Id: Ifbb4c779376fa401ca2538aba5097f7af8b4973e
This commit is contained in:
dimtruck 2015-12-22 23:41:31 -06:00
parent ad21348330
commit 6ccda1ad10
11 changed files with 271 additions and 26 deletions

View File

@ -59,6 +59,7 @@ keypair_id = default
flavor_id = m1.magnum2
master_flavor_id = m1.magnum
copy_logs = true
csr_location = $MAGNUM_DIR/default.csr
EOF
# Note(eliqiao): Let's keep this only for debugging on gate.
@ -70,6 +71,40 @@ EOF
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa
nova keypair-add --pub-key ~/.ssh/id_rsa.pub default
# create a valid sample csr
export CSR_FILE=$MAGNUM_DIR/default.csr
cat <<EOF > $CSR_FILE
-----BEGIN CERTIFICATE REQUEST-----
MIIByjCCATMCAQAwgYkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMR8w
HQYDVQQLExZJbmZvcm1hdGlvbiBUZWNobm9sb2d5MRcwFQYDVQQDEw53d3cuZ29v
Z2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApZtYJCHJ4VpVXHfV
IlstQTlO4qC03hjX+ZkPyvdYd1Q4+qbAeTwXmCUKYHThVRd5aXSqlPzyIBwieMZr
WFlRQddZ1IzXAlVRDWwAo60KecqeAXnnUK+5fXoTI/UgWshre8tJ+x/TMHaQKR/J
cIWPhqaQhsJuzZbvAdGA80BLxdMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIhl
4PvFq+e7ipARgI5ZM+GZx6mpCz44DTo0JkwfRDf+BtrsaC0q68eTf2XhYOsq4fkH
Q0uA0aVog3f5iJxCa3Hp5gxbJQ6zV6kJ0TEsuaaOhEko9sdpCoPOnRBm2i/XRD2D
6iNh8f8z0ShGsFqjDgFHyF3o+lUyj+UC6H1QW7bn
-----END CERTIFICATE REQUEST-----
EOF
# create an ivalid sample csr
export INVALID_CSR_FILE=$MAGNUM_DIR/invalid.csr
cat <<EOF > $INVALID_CSR_FILE
-----BEGIN CERTIFICATE REQUEST-----
FAKERFAKERyjCCATMCAQAwgYkxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlh
MRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRMwEQYDVQQKEwpHb29nbGUgSW5jMR8w
HQYDVQQLExZJbmZvcm1hdGlvbiBUZWNobm9sb2d5MRcwFQYDVQQDEw53d3cuZ29v
Z2xlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEApZtYJCHJ4VpVXHfV
IlstQTlO4qC03hjX+ZkPyvdYd1Q4+qbAeTwXmCUKYHThVRd5aXSqlPzyIBwieMZr
WFlRQddZ1IzXAlVRDWwAo60KecqeAXnnUK+5fXoTI/UgWshre8tJ+x/TMHaQKR/J
cIWPhqaQhsJuzZbvAdGA80BLxdMCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4GBAIhl
4PvFq+e7ipARgI5ZM+GZx6mpCz44DTo0JkwfRDf+BtrsaC0q68eTf2XhYOsq4fkH
Q0uA0aVog3f5iJxCa3Hp5gxbJQ6zV6kJ0TEsuaaOhEko9sdpCoPOnRBm2i/XRD2D
6iNh8f8z0ShGsFqjDgFHyF3o+lUyj+UC6H1QW7bn
-----END CERTIFICATE REQUEST-----
EOF
}
function add_flavor {
@ -149,6 +184,7 @@ if [[ "api" == "$coe" ]]; then
iniset $BASE/new/tempest/etc/tempest.conf magnum keypair_id default
iniset $BASE/new/tempest/etc/tempest.conf magnum flavor_id m1.magnum2
iniset $BASE/new/tempest/etc/tempest.conf magnum master_flavor_id m1.magnum
iniset $BASE/new/tempest/etc/tempest.conf magnum csr_location $CSR_FILE
# show tempest config with magnum
cat etc/tempest.conf

View File

@ -111,16 +111,32 @@ class BayClient(client.MagnumClient):
utils.wait_for_condition(
lambda: self.does_bay_not_exist(bay_id), 10, 3600)
def wait_for_created_bay(self, bay_id):
def wait_for_created_bay(self, bay_id, delete_on_error=True):
try:
utils.wait_for_condition(
lambda: self.does_bay_exist(bay_id), 10, 3600)
except Exception:
# In error state. Clean up the bay id
self.delete_bay(bay_id)
self.wait_for_bay_to_delete(bay_id)
# In error state. Clean up the bay id if desired
if delete_on_error:
self.delete_bay(bay_id)
self.wait_for_bay_to_delete(bay_id)
raise
def wait_for_final_state(self, bay_id):
utils.wait_for_condition(
lambda: self.is_bay_in_final_state(bay_id), 10, 3600)
def is_bay_in_final_state(self, bay_id):
try:
resp, model = self.get_bay(bay_id)
if model.status in ['CREATED', 'CREATE_COMPLETE',
'ERROR', 'CREATE_FAILED']:
return True
else:
return False
except exceptions.NotFound:
return False
def does_bay_exist(self, bay_id):
try:
resp, model = self.get_bay(bay_id)

View File

@ -0,0 +1,56 @@
# 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 magnum.tests.functional.api.v1.models import cert_model
from magnum.tests.functional.common import client
class CertClient(client.MagnumClient):
"""Encapsulates REST calls and maps JSON to/from models"""
url = "/certificates"
@classmethod
def cert_uri(cls, bay_id):
"""Construct bay uri
:param bay_id: bay uuid or name
:returns: url string
"""
return "{0}/{1}".format(cls.url, bay_id)
def get_cert(self, bay_id, **kwargs):
"""Makes GET /certificates/bay_id request and returns CertEntity
Abstracts REST call to return a single cert based on uuid or name
:param bay_id: bay uuid or name
:returns: response object and BayCollection object
"""
resp, body = self.get(self.cert_uri(bay_id))
return self.deserialize(resp, body, cert_model.CertEntity)
def post_cert(self, model, **kwargs):
"""Makes POST /certificates request and returns CertEntity
Abstracts REST call to sign new certificate
:param model: CertEntity
:returns: response object and CertEntity object
"""
resp, body = self.post(
CertClient.url,
body=model.to_json(), **kwargs)
return self.deserialize(resp, body, cert_model.CertEntity)

View File

@ -0,0 +1,24 @@
# 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 magnum.tests.functional.common import models
class CertData(models.BaseModel):
"""Data that encapsulates cert attributes"""
pass
class CertEntity(models.EntityModel):
"""Entity Model that represents a single instance of CertData"""
ENTITY_NAME = 'certificate'
MODEL_TYPE = CertData

View File

@ -35,6 +35,7 @@ class BayTest(base.BaseMagnumTest):
self.baymodel_client = None
self.keypairs_client = None
self.bay_client = None
self.cert_client = None
def setUp(self):
try:
@ -49,6 +50,10 @@ class BayTest(base.BaseMagnumTest):
creds=self.credentials,
type_of_creds='default',
request_type='bay')
(self.cert_client, _) = self.get_clients_with_existing_creds(
creds=self.credentials,
type_of_creds='default',
request_type='cert')
model = datagen.valid_swarm_baymodel()
_, self.baymodel = self._create_baymodel(model)
@ -108,12 +113,25 @@ class BayTest(base.BaseMagnumTest):
def test_create_list_and_delete_bays(self):
gen_model = datagen.valid_bay_data(
baymodel_id=self.baymodel.uuid, node_count=1)
# test bay create
_, temp_model = self._create_bay(gen_model)
# test bay list
resp, model = self.bay_client.list_bays()
self.assertEqual(resp.status, 200)
self.assertGreater(len(model.bays), 0)
self.assertIn(
temp_model.uuid, list([x['uuid'] for x in model.bays]))
# test invalid bay update
patch_model = datagen.bay_name_patch_data()
self.assertRaises(
exceptions.BadRequest,
self.bay_client.patch_bay,
temp_model.uuid, patch_model)
# test bay delete
self._delete_bay(temp_model.uuid)
self.bays.remove(temp_model.uuid)
@ -153,3 +171,39 @@ class BayTest(base.BaseMagnumTest):
self.assertRaises(
exceptions.NotFound,
self.bay_client.delete_bay, data_utils.rand_uuid())
@testtools.testcase.attr('positive')
def test_certificate_sign_and_show(self):
first_model = datagen.valid_bay_data(baymodel_id=self.baymodel.uuid,
name='test')
_, bay_model = self._create_bay(first_model)
# test ca show
resp, model = self.cert_client.get_cert(
bay_model.uuid)
self.LOG.info("cert resp: %s" % resp)
self.LOG.info("cert model: %s" % model)
self.assertEqual(resp.status, 200)
self.assertEqual(model.bay_uuid, bay_model.uuid)
self.assertIsNotNone(model.pem)
self.assertIn('-----BEGIN CERTIFICATE-----', model.pem)
self.assertIn('-----END CERTIFICATE-----', model.pem)
# test ca sign
model = datagen.cert_data(bay_uuid=bay_model.uuid)
resp, model = self.cert_client.post_cert(model)
self.LOG.info("cert resp: %s" % resp)
self.LOG.info("cert model: %s" % model)
self.assertEqual(resp.status, 201)
self.assertEqual(model.bay_uuid, bay_model.uuid)
self.assertIsNotNone(model.pem)
self.assertIn('-----BEGIN CERTIFICATE-----', model.pem)
self.assertIn('-----END CERTIFICATE-----', model.pem)
# test ca sign invalid
model = datagen.cert_data(bay_uuid=bay_model.uuid,
csr_data="invalid_path")
self.assertRaises(
exceptions.ServerFault,
self.cert_client.post_cert,
model)

View File

@ -39,7 +39,8 @@ class MagnumServiceTest(base.BaseMagnumTest):
# get json object
(self.service_client, _) = self.get_clients_with_new_creds(
type_of_creds='admin',
request_type='service')
request_type='service',
class_cleanup=False)
resp, msvcs = self.service_client.magnum_service_list()
self.assertEqual(200, resp.status)
# Note(suro-patz): Following code assumes that we have only

View File

@ -22,9 +22,11 @@ from magnum.tests.functional.common import manager
class BaseMagnumTest(base.BaseTestCase):
"""Sets up configuration required for functional tests"""
ic_class_list = []
ic_method_list = []
def __init__(self, *args, **kwargs):
super(BaseMagnumTest, self).__init__(*args, **kwargs)
self.ic = None
@classmethod
def setUpClass(cls):
@ -34,13 +36,27 @@ class BaseMagnumTest(base.BaseTestCase):
@classmethod
def tearDownClass(cls):
super(BaseMagnumTest, cls).tearDownClass()
cls.clear_credentials(clear_class_creds=True)
def tearDown(self):
if self.ic is not None:
self.ic.clear_creds()
super(BaseMagnumTest, self).tearDown()
self.clear_credentials(clear_method_creds=True)
def get_credentials(self, name=None, type_of_creds="default"):
@classmethod
def clear_credentials(cls,
clear_class_creds=False,
clear_method_creds=False):
if clear_class_creds:
for ic in cls.ic_class_list:
ic.clear_creds()
if clear_method_creds:
for ic in cls.ic_method_list:
ic.clear_creds()
@classmethod
def get_credentials(cls, name=None,
type_of_creds="default",
class_cleanup=False):
if name is None:
# Get name of test method
name = inspect.stack()[1][3]
@ -48,22 +64,27 @@ class BaseMagnumTest(base.BaseTestCase):
name = name[0:32]
# Choose type of isolated creds
self.ic = common_creds.get_credentials_provider(
ic = common_creds.get_credentials_provider(
name,
identity_version=config.Config.auth_version
)
if class_cleanup:
cls.ic_class_list.append(ic)
else:
cls.ic_method_list.append(ic)
creds = None
if "admin" == type_of_creds:
creds = self.ic.get_admin_creds()
creds = ic.get_admin_creds()
elif "alt" == type_of_creds:
creds = self.ic.get_alt_creds()
creds = ic.get_alt_creds()
elif "default" == type_of_creds:
creds = self.ic.get_primary_creds()
creds = ic.get_primary_creds()
else:
creds = self.ic.self.get_credentials(type_of_creds)
creds = ic.self.get_credentials(type_of_creds)
_, keypairs_client = self.get_clients(
_, keypairs_client = cls.get_clients(
creds, type_of_creds, 'keypair_setup')
try:
keypairs_client.show_keypair(config.Config.keypair_id)
@ -72,7 +93,8 @@ class BaseMagnumTest(base.BaseTestCase):
return creds
def get_clients(self, creds, type_of_creds, request_type):
@classmethod
def get_clients(cls, creds, type_of_creds, request_type):
if "admin" == type_of_creds:
manager_inst = manager.AdminManager(credentials=creds,
request_type=request_type)
@ -89,22 +111,27 @@ class BaseMagnumTest(base.BaseTestCase):
# create client with isolated creds
return (manager_inst.client, manager_inst.keypairs_client)
def get_clients_with_existing_creds(self,
@classmethod
def get_clients_with_existing_creds(cls,
name=None,
creds=None,
type_of_creds="default",
request_type=None):
request_type=None,
class_cleanup=False):
if creds is None:
return self.get_clients_with_isolated_creds(name,
type_of_creds,
request_type)
return cls.get_clients_with_new_creds(name,
type_of_creds,
request_type,
class_cleanup)
else:
return self.get_clients(creds, type_of_creds, request_type)
return cls.get_clients(creds, type_of_creds, request_type)
def get_clients_with_new_creds(self,
@classmethod
def get_clients_with_new_creds(cls,
name=None,
type_of_creds="default",
request_type=None):
request_type=None,
class_cleanup=False):
"""Creates isolated creds.
:param name: name, will be used for dynamic creds
@ -113,5 +140,5 @@ class BaseMagnumTest(base.BaseTestCase):
:returns: MagnumClient -- client with isolated creds.
:returns: KeypairClient -- allows for creating of keypairs
"""
creds = self.get_credentials(name, type_of_creds)
return self.get_clients(creds, type_of_creds, request_type)
creds = cls.get_credentials(name, type_of_creds, class_cleanup)
return cls.get_clients(creds, type_of_creds, request_type)

View File

@ -97,6 +97,12 @@ class Config(object):
raise Exception('config missing master_flavor_id key')
cls.master_flavor_id = CONF.magnum.master_flavor_id
@classmethod
def set_csr_location(cls, config):
if 'csr_location' not in CONF.magnum:
raise Exception('config missing csr_location key')
cls.csr_location = CONF.magnum.csr_location
@classmethod
def setUp(cls):
cls.set_admin_creds(config)
@ -112,3 +118,4 @@ class Config(object):
cls.set_flavor_id(config)
cls.set_magnum_url(config)
cls.set_master_flavor_id(config)
cls.set_csr_location(config)

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import random
import socket
import string
@ -21,6 +22,7 @@ from magnum.tests.functional.api.v1.models import bay_model
from magnum.tests.functional.api.v1.models import baymodel_model
from magnum.tests.functional.api.v1.models import baymodelpatch_model
from magnum.tests.functional.api.v1.models import baypatch_model
from magnum.tests.functional.api.v1.models import cert_model
from magnum.tests.functional.common import config
@ -282,3 +284,18 @@ def bay_node_count_patch_data(node_count=2):
"op": "replace"
}]
return baypatch_model.BayPatchCollection.from_dict(data)
def cert_data(bay_uuid, csr_data=None):
if csr_data is None:
csr_data = config.Config.csr_location
data = {
"bay_uuid": bay_uuid
}
if csr_data is not None and os.path.isfile(csr_data):
with open(csr_data, 'r') as f:
data['csr'] = f.read()
model = cert_model.CertEntity.from_dict(data)
return model

View File

@ -15,6 +15,7 @@ from tempest.common import credentials_factory as common_creds
from magnum.tests.functional.api.v1.clients import bay_client
from magnum.tests.functional.api.v1.clients import baymodel_client
from magnum.tests.functional.api.v1.clients import cert_client
from magnum.tests.functional.api.v1.clients import magnum_service_client
from magnum.tests.functional.common import client
from magnum.tests.functional.common import config
@ -33,6 +34,8 @@ class Manager(clients.Manager):
self.client = baymodel_client.BayModelClient(self.auth_provider)
elif request_type == 'bay':
self.client = bay_client.BayClient(self.auth_provider)
elif request_type == 'cert':
self.client = cert_client.CertClient(self.auth_provider)
elif request_type == 'service':
self.client = magnum_service_client.MagnumServiceClient(
self.auth_provider)

View File

@ -51,4 +51,8 @@ MagnumGroup = [
cfg.StrOpt("master_flavor_id",
default="m1.magnum",
help="Master flavor id to use for baymodels."),
cfg.StrOpt("csr_location",
default="/opt/stack/new/magnum/default.csr",
help="CSR location for certificates."),
]