From 032af4a026c0d1f52ee8019cb6c1828cee9ec7d4 Mon Sep 17 00:00:00 2001 From: Sriram Madapusi Vasudevan Date: Wed, 7 Oct 2015 14:39:52 -0400 Subject: [PATCH] feat: GET certificate endpoint REQUEST: GET /ssl_certificate/{domain_name} RESPONSE: 200 OK [ { "flavor_id": "myflavor", "domain_name": "www.mydomain.com", "cert_type": "san", "cert_details": { "provider": { "cert_domain": null, "extra_info": { "status": "create_in_progress", "san cert": null, "action": "Waiting for action" } } }, "status": "create_in_progress" } ] Implements: blueprint ssl-certificates Implements: blueprint akamai-ssl-driver Change-Id: I677fc10a627659b01f87a174998435733faec9e2 --- poppy/manager/default/ssl_certificate.py | 15 ++- poppy/model/ssl_certificate.py | 31 ++++- poppy/provider/akamai/services.py | 5 +- poppy/storage/cassandra/services.py | 115 ++++++++++-------- poppy/storage/mockdb/services.py | 21 +++- .../pecan/controllers/v1/ssl_certificates.py | 34 ++++++ .../pecan/models/response/ssl_certificate.py | 2 + .../test_create_ssl_certificate.py | 14 ++- tests/api/utils/client.py | 24 +++- tests/api/utils/models/requests.py | 2 +- .../pecan/controllers/test_ssl_certificate.py | 65 ++++++++++ .../model/helpers/test_ssl_certificate.py | 78 ++++++++++++ .../cassandra/data_get_cert_by_domain.json | 73 +++++++++++ tests/unit/storage/cassandra/test_services.py | 30 +++++ 14 files changed, 444 insertions(+), 65 deletions(-) create mode 100644 tests/unit/model/helpers/test_ssl_certificate.py create mode 100644 tests/unit/storage/cassandra/data_get_cert_by_domain.json diff --git a/poppy/manager/default/ssl_certificate.py b/poppy/manager/default/ssl_certificate.py index 6bf6bcd6..5bfdae5b 100644 --- a/poppy/manager/default/ssl_certificate.py +++ b/poppy/manager/default/ssl_certificate.py @@ -31,7 +31,6 @@ class DefaultSSLCertificateController(base.SSLCertificateController): self.flavor_controller = self._driver.storage.flavors_controller def create_ssl_certificate(self, project_id, cert_obj): - try: flavor = self.flavor_controller.get(cert_obj.flavor_id) # raise a lookup error if the flavor is not found @@ -68,3 +67,17 @@ class DefaultSSLCertificateController(base.SSLCertificateController): delete_ssl_certificate.delete_ssl_certificate, **kwargs) return kwargs + + def get_certs_info_by_domain(self, domain_name, project_id): + try: + certs_info = self.storage_controller.get_certs_by_domain( + domain_name=domain_name, + project_id=project_id) + if not certs_info: + raise ValueError("certificate information" + "not found for {0} ".format(domain_name)) + + return certs_info + + except ValueError as e: + raise e diff --git a/poppy/model/ssl_certificate.py b/poppy/model/ssl_certificate.py index 34465ec1..94d41688 100644 --- a/poppy/model/ssl_certificate.py +++ b/poppy/model/ssl_certificate.py @@ -32,11 +32,13 @@ class SSLCertificate(common.DictSerializableModel): flavor_id, domain_name, cert_type, + project_id=None, cert_details={}): self._flavor_id = flavor_id self._domain_name = domain_name self._cert_type = cert_type self._cert_details = cert_details + self._project_id = project_id @property def flavor_id(self): @@ -47,6 +49,15 @@ class SSLCertificate(common.DictSerializableModel): def flavor_id(self, value): self._flavor_id = value + @property + def project_id(self): + """Get project id.""" + return self._project_id + + @project_id.setter + def project_id(self, value): + self._project_id = value + @property def domain_name(self): """Get domain name""" @@ -89,7 +100,7 @@ class SSLCertificate(common.DictSerializableModel): # provider per flavor (that is akamai), so the first one # value of this dictionary is akamai cert_details first_provider_cert_details = ( - self.cert_details.values()[0].get("extra_info", None)) + list(self.cert_details.values())[0].get("extra_info", None)) if first_provider_cert_details is None: return "deployed" else: @@ -109,10 +120,26 @@ class SSLCertificate(common.DictSerializableModel): if self.cert_details is None or self.cert_details == {}: return None first_provider_cert_details = ( - self.cert_details.values()[0].get("extra_info", None)) + list(self.cert_details.values())[0].get("extra_info", None)) if first_provider_cert_details is None: return None else: return first_provider_cert_details.get('san cert', None) else: return None + + @classmethod + def init_from_dict(cls, input_dict): + flavor_id = input_dict.get('flavor_id', None) + domain_name = input_dict.get('domain_name', None) + cert_type = input_dict.get('cert_type', None) + cert_details = input_dict.get('cert_details', {}) + project_id = input_dict.get('project_id', None) + + ssl_cert = cls(flavor_id=flavor_id, + domain_name=domain_name, + cert_type=cert_type, + cert_details=cert_details, + project_id=project_id) + + return ssl_cert diff --git a/poppy/provider/akamai/services.py b/poppy/provider/akamai/services.py index 32a53945..5204430e 100644 --- a/poppy/provider/akamai/services.py +++ b/poppy/provider/akamai/services.py @@ -614,9 +614,8 @@ class ServiceController(base.ServiceBase): 'status': 'failed', 'san cert': None, 'action': 'Waiting for action... ' - 'Provision san cert failed for %s failed.' - ' Reason: %s' % - (cert_obj.domain_name, str(e)) + 'Provision san cert failed for %s failed.' % + cert_obj.domain_name }) else: return self.responder.ssl_certificate_provisioned(None, { diff --git a/poppy/storage/cassandra/services.py b/poppy/storage/cassandra/services.py index 2c394ae9..d7c988d2 100644 --- a/poppy/storage/cassandra/services.py +++ b/poppy/storage/cassandra/services.py @@ -22,7 +22,12 @@ except ImportError: # pragma: no cover import collections # pragma: no cover from cassandra import query +import six +if six.PY2: + from itertools import ifilterfalse as filterfalse +else: + from itertools import filterfalse from poppy.model.helpers import cachingrule from poppy.model.helpers import domain from poppy.model.helpers import origin @@ -364,36 +369,14 @@ class ServicesController(base.ServicesController): :param cert_type :param comparing_project_id - :raises ValueError :returns Boolean if the cert with same type exists with another user. """ - LOG.info("Check if cert on '{0}' exists".format(domain_name)) - args = { - 'domain_name': domain_name.lower() - } - stmt = query.SimpleStatement( - CQL_VERIFY_CERT, - consistency_level=self._driver.consistency_level) - results = self.session.execute(stmt, args) + cert = self.get_certs_by_domain(domain_name=domain_name, + cert_type=comparing_cert_type, + flavor_id=comparing_flavor_id) - if results: - msg = None - for r in results: - if str(r.get('project_id')) != str(comparing_project_id): - msg = "Domain '{0}' has already been created cert by {1}"\ - .format(domain_name, r.get('project_id')) - LOG.warn(msg) - raise ValueError(msg) - elif (str(r.get('flavor_id')) == str(comparing_flavor_id) - and - str(r.get('cert_type')) == str(comparing_cert_type)): - msg = "{0} have already created cert of type {1} on {2}"\ - .format(str(comparing_project_id), - comparing_cert_type, - domain_name) - LOG.warn(msg) - raise ValueError(msg) - return False + if cert: + return True else: return False @@ -503,13 +486,9 @@ class ServicesController(base.ServicesController): "project_id: {0} set to be {1}".format(project_id, project_limit)) - def get_cert_by_domain(self, domain_name, cert_type, - flavor_id, - project_id): - - LOG.info(("Search for cert on '{0}', type: {1}, flavor_id: {2}, " - "project_id: {3}").format(domain_name, cert_type, flavor_id, - project_id)) + def get_certs_by_domain(self, domain_name, project_id=None, flavor_id=None, + cert_type=None): + LOG.info("Check if cert on '{0}' exists".format(domain_name)) args = { 'domain_name': domain_name.lower() } @@ -517,7 +496,7 @@ class ServicesController(base.ServicesController): CQL_SEARCH_CERT_BY_DOMAIN, consistency_level=self._driver.consistency_level) results = self.session.execute(stmt, args) - + certs = [] if results: for r in results: r_project_id = str(r.get('project_id')) @@ -529,18 +508,50 @@ class ServicesController(base.ServicesController): # And the value of cert_details is a string dict for key in cert_details: r_cert_details[key] = json.loads(cert_details[key]) - if r_project_id == str(project_id) and \ - r_flavor_id == str(flavor_id) and \ - r_cert_type == str(cert_type): - res = ssl_certificate.SSLCertificate(r_flavor_id, - domain_name, - r_cert_type, - r_cert_details) - return res - else: - return None + LOG.info("Certificate for domain: {0} " + "with flavor_id: {1}, " + "cert_details : {2} and " + "cert_type: {3} present " + "on project_id: {4}".format(domain_name, + r_flavor_id, + r_cert_details, + r_cert_type, + r_project_id)) + ssl_cert = ssl_certificate.SSLCertificate( + domain_name=domain_name, + flavor_id=r_flavor_id, + cert_details=r_cert_details, + cert_type=r_cert_type, + project_id=r_project_id) + + certs.append(ssl_cert) + + non_none_attrs_gen = filterfalse( + lambda x: list(x.values())[0] is None, [{'project_id': project_id}, + {'flavor_id': flavor_id}, + {'cert_type': cert_type}]) + non_none_attrs_list = list(non_none_attrs_gen) + non_none_attrs_dict = {} + + if non_none_attrs_list: + for attr in non_none_attrs_list: + non_none_attrs_dict.update(attr) + + def argfilter(certificate): + all_conditions = True + if non_none_attrs_dict: + for k, v in non_none_attrs_dict.items(): + if getattr(certificate, k) != v: + all_conditions = False + + return all_conditions + + total_certs = [cert for cert in certs if argfilter(cert)] + + if len(total_certs) == 1: + return total_certs[0] else: - return None + return total_certs def delete_cert(self, project_id, domain_name, cert_type): """delete_cert @@ -795,12 +806,12 @@ class ServicesController(base.ServicesController): self.session.execute(stmt, delete_args) def create_cert(self, project_id, cert_obj): - - if not self.cert_already_exist(cert_obj.domain_name, - cert_obj.cert_type, - cert_obj.flavor_id, - project_id): - pass + if self.cert_already_exist(domain_name=cert_obj.domain_name, + comparing_cert_type=cert_obj.cert_type, + comparing_flavor_id=cert_obj.flavor_id, + comparing_project_id=project_id): + raise ValueError('Certificate already exists ' + 'for {0} '.format(cert_obj.domain_name)) args = { 'project_id': project_id, diff --git a/poppy/storage/mockdb/services.py b/poppy/storage/mockdb/services.py index c2514558..4ce46c54 100644 --- a/poppy/storage/mockdb/services.py +++ b/poppy/storage/mockdb/services.py @@ -33,6 +33,7 @@ class ServicesController(base.ServicesController): self.projectid_service_limit = {} self.default_max_service_limit = 20 self.service_count_per_project_id = {} + self.certs = {} @property def session(self): @@ -161,10 +162,26 @@ class ServicesController(base.ServicesController): def update_cert_info(self, domain_name, cert_type, flavor_id, cert_details): - pass + key = (flavor_id, domain_name, cert_type) + if key in self.certs: + self.certs[key].cert_details = cert_details def create_cert(self, project_id, cert_obj): - pass + key = (cert_obj.flavor_id, cert_obj.domain_name, cert_obj.cert_type) + if key not in self.certs: + self.certs[key] = cert_obj + else: + raise ValueError + + def get_certs_by_domain(self, domain_name, project_id=None): + certs = [] + for cert in self.certs: + if domain_name in cert: + certs.append(self.certs[cert]) + if project_id: + return [cert for cert in certs if cert.project_id == project_id] + else: + return certs def delete_cert(self, project_id, domain_name, cert_type): if "non_exist" in domain_name: diff --git a/poppy/transport/pecan/controllers/v1/ssl_certificates.py b/poppy/transport/pecan/controllers/v1/ssl_certificates.py index 42a483ee..cc508e7b 100644 --- a/poppy/transport/pecan/controllers/v1/ssl_certificates.py +++ b/poppy/transport/pecan/controllers/v1/ssl_certificates.py @@ -21,6 +21,8 @@ from pecan import hooks from poppy.transport.pecan.controllers import base from poppy.transport.pecan import hooks as poppy_hooks from poppy.transport.pecan.models.request import ssl_certificate +from poppy.transport.pecan.models.response import ssl_certificate \ + as ssl_cert_model from poppy.transport.validators import helpers from poppy.transport.validators.schemas import ssl_certificate\ as ssl_certificate_validation @@ -51,6 +53,7 @@ class SSLCertificateController(base.Controller, hooks.HookController): try: project_id = certificate_info_dict.get('project_id') cert_obj = ssl_certificate.load_from_json(certificate_info_dict) + cert_obj.project_id = project_id ssl_certificate_controller.create_ssl_certificate(project_id, cert_obj) except LookupError as e: @@ -83,3 +86,34 @@ class SSLCertificateController(base.Controller, hooks.HookController): 'Reason: %s' % str(e)) return pecan.Response(None, 202) + + @pecan.expose('json') + @decorators.validate( + domain_name=rule.Rule( + helpers.is_valid_domain_by_name(), + helpers.abort_with_message) + ) + def get_one(self, domain_name): + + certificate_controller = \ + self._driver.manager.ssl_certificate_controller + total_cert_info = [] + + try: + certs_info = certificate_controller.get_certs_info_by_domain( + domain_name=domain_name, + project_id=self.project_id) + except ValueError: + pecan.abort(404, detail='certificate ' + 'could not be found ' + 'for domain : %s' % + domain_name) + else: + # convert a cert model into a response cert model + try: + if iter(certs_info): + for cert in certs_info: + total_cert_info.append(ssl_cert_model.Model(cert)) + return total_cert_info + except TypeError: + return ssl_cert_model.Model(certs_info) diff --git a/poppy/transport/pecan/models/response/ssl_certificate.py b/poppy/transport/pecan/models/response/ssl_certificate.py index 9efeb3bf..59055a34 100644 --- a/poppy/transport/pecan/models/response/ssl_certificate.py +++ b/poppy/transport/pecan/models/response/ssl_certificate.py @@ -29,3 +29,5 @@ class Model(collections.OrderedDict): self["flavor_id"] = ssl_certificate.flavor_id self['domain_name'] = util.help_escape(ssl_certificate.domain_name) self['cert_type'] = ssl_certificate.cert_type + self['cert_details'] = ssl_certificate.cert_details + self['status'] = ssl_certificate.get_cert_status() diff --git a/tests/api/ssl_certificate/test_create_ssl_certificate.py b/tests/api/ssl_certificate/test_create_ssl_certificate.py index 596ea91a..d448f4e2 100644 --- a/tests/api/ssl_certificate/test_create_ssl_certificate.py +++ b/tests/api/ssl_certificate/test_create_ssl_certificate.py @@ -12,6 +12,8 @@ # implied. # See the License for the specific language governing permissions and # limitations under the License. +import json + import ddt from tests.api import base @@ -50,7 +52,7 @@ class TestCreateSSLCertificate(base.TestBase): self.skipTest('Create ssl certificate needs to' ' be run when commanded') - self.cert_type = test_data.get('cert_type') + cert_type = test_data.get('cert_type') rand_string = self.generate_random_string() domain_name = rand_string + test_data.get('domain_name') flavor_id = test_data.get('flavor_id') or self.flavor_id @@ -63,6 +65,16 @@ class TestCreateSSLCertificate(base.TestBase): ) self.assertEqual(resp.status_code, 202) + resp = self.client.get_ssl_certificate( + domain_name=domain_name + ) + self.assertEqual(resp.status_code, 200) + + for cert in json.loads(resp.content): + self.assertEqual(cert['domain_name'], domain_name) + self.assertEqual(cert['flavor_id'], flavor_id) + self.assertEqual(cert['cert_type'], cert_type) + def tearDown(self): self.client.delete_ssl_certificate( cert_type=self.cert_type, diff --git a/tests/api/utils/client.py b/tests/api/utils/client.py index 5f46c59c..3204d895 100644 --- a/tests/api/utils/client.py +++ b/tests/api/utils/client.py @@ -437,8 +437,8 @@ class PoppyClient(client.AutoMarshallingHTTPClient): requestslib_kwargs=None): """Creates SSL Certificate - :return: Response Object containing response code 200 and body with - details of service + :return: Response Object containing response code 202 + POST ssl_certificate """ @@ -460,10 +460,28 @@ class PoppyClient(client.AutoMarshallingHTTPClient): """Deletes SSL Certificate :return: Response Object containing response code 202 - GET + + DELETE ssl_certificate """ + url = '{0}/ssl_certificate/{1}'.format(self.url, domain_name) return self.request('DELETE', url, requestslib_kwargs=requestslib_kwargs) + + def get_ssl_certificate(self, + domain_name, + requestslib_kwargs=None,): + """GET SSL Certificate + + :return: Response Object containing response code 200 and body with + details of certificate request + + GET + ssl_certificate + """ + + url = '{0}/ssl_certificate/{1}'.format(self.url, domain_name) + + return self.request('GET', url, requestslib_kwargs=requestslib_kwargs) diff --git a/tests/api/utils/models/requests.py b/tests/api/utils/models/requests.py index ff2e33d9..dee3b622 100644 --- a/tests/api/utils/models/requests.py +++ b/tests/api/utils/models/requests.py @@ -132,7 +132,7 @@ class CreateFlavor(base.AutoMarshallingModel): class CreateSSLCertificate(base.AutoMarshallingModel): - """Marshalling for Create Flavor requests.""" + """Marshalling for Create SSL Certificate requests.""" def __init__(self, cert_type=None, domain_name=None, flavor_id=None, project_id=None): diff --git a/tests/functional/transport/pecan/controllers/test_ssl_certificate.py b/tests/functional/transport/pecan/controllers/test_ssl_certificate.py index 1f265dcf..d60857fe 100644 --- a/tests/functional/transport/pecan/controllers/test_ssl_certificate.py +++ b/tests/functional/transport/pecan/controllers/test_ssl_certificate.py @@ -69,6 +69,71 @@ class SSLCertificateControllerTest(base.FunctionalTest): 'X-Project-ID': self.project_id}) self.assertEqual(202, response.status_code) + def test_get_ssl_certificate_non_existing_domain(self): + + # get non existing domain + domain = 'www.idontexist.com' + response = self.app.get('/v1.0/ssl_certificate/{0}'.format(domain), + headers={ + 'Content-Type': 'application/json', + 'X-Project-ID': self.project_id}, + expect_errors=True) + self.assertEqual(404, response.status_code) + + def test_get_ssl_certificate_existing_domain(self): + domain = 'www.iexist.com' + ssl_certificate_json = { + "cert_type": "san", + "domain_name": domain, + "flavor_id": self.flavor_id, + "project_id": self.project_id + } + response = self.app.post('/v1.0/ssl_certificate', + params=json.dumps(ssl_certificate_json), + headers={ + 'Content-Type': 'application/json', + 'X-Project-ID': self.project_id}) + self.assertEqual(202, response.status_code) + + # get existing domain with same project_id + + response = self.app.get('/v1.0/ssl_certificate/{0}'.format(domain), + headers={ + 'Content-Type': 'application/json', + 'X-Project-ID': self.project_id}) + response_list = json.loads(response.body.decode("utf-8")) + self.assertEqual(200, response.status_code) + self.assertEqual(ssl_certificate_json["cert_type"], + response_list[0]["cert_type"]) + self.assertEqual(ssl_certificate_json["domain_name"], + response_list[0]["domain_name"]) + self.assertEqual(ssl_certificate_json["flavor_id"], + response_list[0]["flavor_id"]) + + def test_get_ssl_certificate_existing_domain_different_project_id(self): + domain = 'www.iexist.com' + ssl_certificate_json = { + "cert_type": "san", + "domain_name": domain, + "flavor_id": self.flavor_id, + "project_id": self.project_id + } + response = self.app.post('/v1.0/ssl_certificate', + params=json.dumps(ssl_certificate_json), + headers={ + 'Content-Type': 'application/json', + 'X-Project-ID': self.project_id}) + self.assertEqual(202, response.status_code) + + # get existing domain with different project_id + + response = self.app.get('/v1.0/ssl_certificate/{0}'.format(domain), + headers={ + 'Content-Type': 'application/json', + 'X-Project-ID': str(uuid.uuid4())}, + expect_errors=True) + self.assertEqual(404, response.status_code) + def test_create_with_invalid_json(self): # create with errorenous data: invalid json data response = self.app.post('/v1.0/ssl_certificate', diff --git a/tests/unit/model/helpers/test_ssl_certificate.py b/tests/unit/model/helpers/test_ssl_certificate.py new file mode 100644 index 00000000..888d370a --- /dev/null +++ b/tests/unit/model/helpers/test_ssl_certificate.py @@ -0,0 +1,78 @@ +# Copyright (c) 2015 Rackspace, Inc. +# +# 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 ddt + +from poppy.model import ssl_certificate +from tests.unit import base + + +@ddt.ddt +class TestSSLCertificate(base.TestCase): + + def test_ssl_certificate(self): + + project_id = '12345' + cert_details = { + 'mock': { + 'extra_info': 'nope' + } + } + flavor_id = 'myflavor' + domain_name = 'www.mydomain.com' + cert_type = 'san' + + ssl_cert = ssl_certificate.SSLCertificate(project_id=project_id, + flavor_id=flavor_id, + domain_name=domain_name, + cert_type=cert_type, + cert_details=cert_details) + + # test all properties + # project_id + self.assertEqual(ssl_cert.project_id, project_id) + ssl_cert.project_id = '123456' + + # flavor_id + self.assertEqual(ssl_cert.flavor_id, flavor_id) + ssl_cert.flavor_id = 'yourflavor' + + # domain_name + self.assertEqual(ssl_cert.domain_name, domain_name) + ssl_cert.domain_name = 'www.yourdomain.com' + + # cert_type + self.assertEqual(ssl_cert.cert_type, cert_type) + ssl_cert.cert_type = 'custom' + self.assertRaises(ValueError, setattr, ssl_cert, 'cert_type', + 'whatever') + # cert_details + self.assertEqual(ssl_cert.cert_details, cert_details) + cert_details_two = cert_details.copy() + cert_details_two['mock']['extra_info'] = 'maybe' + ssl_cert.cert_details = cert_details_two + + # get cert status + cert_details['mock']['extra_info'] = { + 'status': 'deployed' + } + ssl_cert.cert_details = cert_details + self.assertEqual(ssl_cert.get_cert_status(), 'deployed') + cert_details['mock']['extra_info'] = { + 'status': 'whatever' + } + + self.assertRaises(ValueError, ssl_cert.get_cert_status) diff --git a/tests/unit/storage/cassandra/data_get_cert_by_domain.json b/tests/unit/storage/cassandra/data_get_cert_by_domain.json new file mode 100644 index 00000000..4eb19aba --- /dev/null +++ b/tests/unit/storage/cassandra/data_get_cert_by_domain.json @@ -0,0 +1,73 @@ +[ + [ + [ + { + "project_id": 12345, + "flavor_id": "flavor1", + "cert_type": "san", + "domain_name": "www.mydomain.com", + "cert_details": { + "provider": "{\"cert_domain\": \"abc\", \"extra_info\": { \"status\": \"deployed\", \"san_cert\": \"awesome_san\", \"action\": \"Ready\"}}" + } + }, + { + "project_id": 12345, + "flavor_id": "flavor2", + "cert_type": "custom", + "domain_name": "www.mydomain.com", + "cert_details": { + "provider": "{\"cert_domain\": \"abc\", \"extra_info\": { \"status\": \"deployed\", \"san_cert\": \"awesome_custom\", \"action\": \"Ready\"}}" + } + } + ], + [ + { + "project_id": 12345, + "flavor_id": "flavor1", + "cert_type": "custom", + "domain_name": "www.example.com", + "cert_details": { + "provider": "{\"cert_domain\": \"abc\", \"extra_info\": { \"status\": \"deployed\", \"san_cert\": \"awesome_custom\", \"action\": \"Ready\"}}" + } + }, + { + "project_id": 12345, + "flavor_id": "flavor1", + "cert_type": "san", + "domain_name": "www.example.com", + "cert_details": { + "provider": "{\"cert_domain\": \"abc\", \"extra_info\": { \"status\": \"deployed\", \"san_cert\": \"awesome_san\", \"action\": \"Ready\"}}" + } + }, + { + "project_id": 12346, + "flavor_id": "flavor2", + "cert_type": "san", + "domain_name": "www.mydomain2.com", + "cert_details": { + "provider": "{\"cert_domain\": \"abc\", \"extra_info\": { \"status\": \"deployed\", \"san_cert\": \"awesome_san\", \"action\": \"Ready\"}}" + } + } + ], + [ + { + "project_id": 12345, + "flavor_id": "flavor1", + "cert_type": "san", + "domain_name": "www.mydomain.com", + "cert_details": { + "provider": "{\"cert_domain\": \"abc\", \"extra_info\": { \"status\": \"deployed\", \"san_cert\": \"awesome_san\", \"action\": \"Ready\"}}" + } + }, + { + "project_id": 12346, + "flavor_id": "flavor2", + "cert_type": "san", + "domain_name": "www.mydomain2.com", + "cert_details": { + "provider": "{\"cert_domain\": \"abc\", \"extra_info\": { \"status\": \"deployed\", \"san_cert\": \"awesome_san\", \"action\": \"Ready\"}}" + } + } + ] + ] +] \ No newline at end of file diff --git a/tests/unit/storage/cassandra/test_services.py b/tests/unit/storage/cassandra/test_services.py index cdead39a..302e9a57 100644 --- a/tests/unit/storage/cassandra/test_services.py +++ b/tests/unit/storage/cassandra/test_services.py @@ -26,6 +26,7 @@ import mock from oslo_config import cfg from poppy.model.helpers import provider_details +from poppy.model import ssl_certificate from poppy.storage.cassandra import driver from poppy.storage.cassandra import services from poppy.transport.pecan.models.request import service as req_service @@ -179,6 +180,35 @@ class CassandraStorageServiceTests(base.TestCase): self.assertTrue("CloudFront" in actual_response) self.assertTrue("Fastly" in actual_response) + @ddt.file_data('data_get_cert_by_domain.json') + @mock.patch.object(services.ServicesController, 'session') + @mock.patch.object(cassandra.cluster.Session, 'execute') + def test_get_cert_by_domain(self, cert_details_json, + mock_session, mock_execute): + # mock the response from cassandra + mock_execute.execute.return_value = cert_details_json[0] + actual_response = self.sc.get_certs_by_domain( + domain_name="www.mydomain.com") + self.assertEqual(len(actual_response), 2) + self.assertTrue(all([isinstance(ssl_cert, + ssl_certificate.SSLCertificate) + for ssl_cert in actual_response])) + mock_execute.execute.return_value = cert_details_json[1] + actual_response = self.sc.get_certs_by_domain( + domain_name="www.example.com", + flavor_id="flavor1") + self.assertEqual(len(actual_response), 2) + self.assertTrue(all([isinstance(ssl_cert, + ssl_certificate.SSLCertificate) + for ssl_cert in actual_response])) + mock_execute.execute.return_value = cert_details_json[2] + actual_response = self.sc.get_certs_by_domain( + domain_name="www.mydomain.com", + flavor_id="flavor1", + cert_type="san") + self.assertTrue(isinstance(actual_response, + ssl_certificate.SSLCertificate)) + @ddt.file_data('data_provider_details.json') @mock.patch.object(services.ServicesController, 'session') @mock.patch.object(cassandra.cluster.Session, 'execute')