DNS updates for http to https+san upgrade

Change-Id: Ic90934e6e27f2f28449a2df4566aaab400a36186
This commit is contained in:
Isaac Mungai 2016-08-12 15:31:38 -04:00
parent 0a67d6f7cc
commit 23fcb0d5f1
16 changed files with 668 additions and 160 deletions

View File

@ -31,7 +31,8 @@ conf(project='poppy', prog='poppy', args=[])
class CreateProviderSSLCertificateTask(task.Task):
default_provides = "responders"
def execute(self, providers_list_json, cert_obj_json, enqueue=True):
def execute(self, providers_list_json, cert_obj_json, enqueue=True,
https_upgrade=False):
service_controller = memoized_controllers.task_controllers('poppy')
# call provider create_ssl_certificate function
@ -46,7 +47,8 @@ class CreateProviderSSLCertificateTask(task.Task):
responder = service_controller.provider_wrapper.create_certificate(
service_controller._driver.providers[provider],
cert_obj,
enqueue
enqueue,
https_upgrade
)
responders.append(responder)
@ -55,7 +57,7 @@ class CreateProviderSSLCertificateTask(task.Task):
class SendNotificationTask(task.Task):
def execute(self, project_id, responders):
def execute(self, project_id, responders, upgrade=False):
service_controller = memoized_controllers.task_controllers('poppy')
notification_content = ""
@ -65,6 +67,13 @@ class SendNotificationTask(task.Task):
"Project ID: %s, Provider: %s, Detail: %s" %
(project_id, provider, str(responder[provider])))
if upgrade is True:
notification_content += (
" The domain was upgraded from HTTP to HTTPS SAN. "
"If applicable, take note of the domain name and "
"delete the old HTTP policy in the provider."
)
for n_driver in service_controller._driver.notification:
service_controller.notification_wrapper.send(
n_driver,

View File

@ -14,6 +14,7 @@
# limitations under the License.
import random
import re
try:
set
except NameError: # noqa pragma: no cover
@ -64,6 +65,7 @@ class ServicesController(base.ServicesBase):
# randomly select a shard
shard_id = random.randint(1, num_shards)
# ex. cdnXXX.altcdn.com
subdomain_name = '{0}{1}.{2}'.format(shard_prefix, shard_id,
cdn_domain_name)
subdomain = self._get_subdomain(subdomain_name)
@ -75,14 +77,51 @@ class ServicesController(base.ServicesBase):
shared_ssl_subdomain_name = None
for link in links:
# pick out shared ssl domains here
domain_name, certificate = link
domain_name, certificate, old_operator_url = link
if certificate == "shared":
shared_ssl_subdomain_name = (
'.'.join(domain_name.split('.')[1:]))
# perform shared ssl cert logic
name = domain_name
else:
name = '{0}.{1}'.format(domain_name, subdomain_name)
if old_operator_url is not None:
# verify sub-domain exists
regex_match = re.match(
r'^.*(' + shard_prefix + '[0-9]+\.' +
re.escape(cdn_domain_name) + ')$',
old_operator_url
)
my_sub_domain_name = regex_match.groups(-1)[0]
if my_sub_domain_name is None:
raise ValueError('Unable to parse old provider url')
# add to cname record
my_sub_domain = self._get_subdomain(my_sub_domain_name)
LOG.info(
"Updating DNS Record for HTTPS upgrade "
"domain {0}. CNAME update from {1} to {2}".format(
my_sub_domain_name,
old_operator_url,
links[link]
)
)
old_dns_record = my_sub_domain.find_record(
'CNAME',
old_operator_url
)
my_sub_domain.update_record(
old_dns_record,
data=links[link]
)
dns_links[link] = {
'provider_url': links[link],
'operator_url': old_operator_url
}
continue
else:
name = '{0}.{1}'.format(domain_name, subdomain_name)
cname_record = {'type': 'CNAME',
'name': name,
@ -245,8 +284,11 @@ class ServicesController(base.ServicesBase):
# We need to distinguish shared ssl domains in
# which case the we will use different shard prefix and
# and shard number
links[(link['domain'], link.get('certificate',
None))] = link['href']
links[(
link['domain'],
link.get('certificate', None),
None # new link no pref operator url
)] = link['href']
# create CNAME records
try:
@ -269,14 +311,16 @@ class ServicesController(base.ServicesBase):
if link['rel'] == 'access_url':
access_url = {
'domain': link['domain'],
'provider_url':
dns_links[(link['domain'],
link.get('certificate', None)
)]['provider_url'],
'operator_url':
dns_links[(link['domain'],
link.get('certificate', None)
)]['operator_url']}
'provider_url': dns_links[(
link['domain'],
link.get('certificate', None),
None
)]['provider_url'],
'operator_url': dns_links[(
link['domain'],
link.get('certificate', None),
None
)]['operator_url']}
# Need to indicate if this access_url is a shared ssl
# access url, since its has different shard_prefix and
# num_shard
@ -310,7 +354,7 @@ class ServicesController(base.ServicesBase):
access_url['operator_url'],
access_url.get('shared_ssl_flag', False))
if msg:
error_msg = error_msg + msg
error_msg += msg
except exc.NotFound as e:
LOG.error('Can not access the subdomain. Please make '
'sure it exists and you have permissions '
@ -320,8 +364,8 @@ class ServicesController(base.ServicesBase):
error_class = e.__class__
except Exception as e:
LOG.error('Rackspace DNS Exception: {0}'.format(e))
error_msg = error_msg + 'Rackspace DNS ' \
'Exception: {0}'.format(e)
error_msg += 'Rackspace DNS ' \
'Exception: {0}'.format(e)
error_class = e.__class__
# format the error message for this provider
if not error_msg:
@ -361,8 +405,11 @@ class ServicesController(base.ServicesBase):
domain_added = (link['rel'] == 'access_url' and
link['domain'] in added_domains)
if domain_added:
links[(link['domain'], link.get('certificate',
None))] = link['href']
links[(
link['domain'],
link.get('certificate', None),
link.get('old_operator_url', None)
)] = link['href']
# create CNAME records for added domains
try:
@ -387,11 +434,13 @@ class ServicesController(base.ServicesBase):
'domain': link['domain'],
'provider_url':
dns_links[(link['domain'],
link.get('certificate', None)
link.get('certificate', None),
link.get('old_operator_url', None)
)]['provider_url'],
'operator_url':
dns_links[(link['domain'],
link.get('certificate', None)
link.get('certificate', None),
link.get('old_operator_url', None)
)]['operator_url']}
# Need to indicate if this access_url is a shared ssl
# access url, since its has different shard_prefix and
@ -497,11 +546,43 @@ class ServicesController(base.ServicesBase):
for link in links:
new_domains.add(link['domain'])
# find http -> https+san upgrade domains
upgraded_domains = set()
for domain in service_updates.domains:
for old_domain in service_old.domains:
if old_domain.domain == domain.domain:
if (
old_domain.protocol == 'http' and
domain.protocol == 'https' and
domain.certificate == 'san'
):
upgraded_domains.add(domain.domain)
break
# if domains have not been updated, return
if not service_updates.domains:
return old_access_urls_map
# if the old set of domains is the same as new set of domains, return
# force dns update when we encounter an upgraded domain
common_domains = new_domains.intersection(old_domains)
for domain_name in common_domains:
upgrade = False
for responder in responders:
for provider_name in responder:
links = responder[provider_name]['links']
for link in links:
if (
link['domain'] == domain_name and
link.get('certificate', None) == 'san' and
link['href'] is not None and
link['old_operator_url'] is not None
):
upgrade = True
if upgrade is True:
old_domains.remove(domain_name)
# if the old set of domains is the same as new set of domains return
if old_domains == new_domains:
return old_access_urls_map
@ -510,6 +591,10 @@ class ServicesController(base.ServicesBase):
removed_domains = old_domains.difference(new_domains)
common_domains = new_domains.intersection(old_domains)
# prevent dns records for upgrade domains from being deleted
retain_domains = removed_domains.intersection(upgraded_domains)
removed_domains = removed_domains.difference(retain_domains)
LOG.info("Added Domains : {0} on service_id : {1} "
"for project_id: {2}".format(added_domains,
service_id,
@ -589,6 +674,37 @@ class ServicesController(base.ServicesBase):
if old_access_url.get('shared_ssl_flag', False):
access_url['shared_ssl_flag'] = True
access_urls.append(access_url)
# find upgraded domains and create placeholders for them
for domain in service_updates.domains:
is_upgrade = False
for old_domain in service_old.domains:
if old_domain.domain == domain.domain:
if (
old_domain.protocol == 'http' and
domain.protocol == 'https' and
domain.certificate == 'san'
):
is_upgrade = True
break
if is_upgrade is True:
old_access_url_for_domain = (
service_old.provider_details.values()[0].
get_domain_access_url(domain.domain))
# add placeholder access url for upgraded domain
# the access_url dict here should be missing an entry
# for http san domain since provider url is
# determined only after an ssl cert is provisioned
access_urls.append({
'domain': domain.domain,
'provider_url': None,
'operator_url': None,
'old_operator_url': old_access_url_for_domain[
'operator_url'
]
})
dns_details[provider_name] = {'access_urls': access_urls}
return self.responder.updated(dns_details)

View File

@ -73,7 +73,7 @@ class ProviderWrapper(object):
hard,
purge_url)
def create_certificate(self, ext, cert_obj, enqueue):
def create_certificate(self, ext, cert_obj, enqueue, https_upgrade):
"""Create a certificate
:param ext
@ -84,5 +84,6 @@ class ProviderWrapper(object):
return ext.obj.certificate_controller.create_certificate(
cert_obj,
enqueue
enqueue,
https_upgrade
)

View File

@ -53,6 +53,7 @@ class DefaultServicesController(base.ServicesController):
self.ssl_certificate_storage = (
self._driver.storage.certificates_controller
)
self.ssl_cert_manager = self._driver.ssl_certificate_controller
self.flavor_controller = self._driver.storage.flavors_controller
self.dns_controller = self._driver.dns.services_controller
self.distributed_task_controller = (
@ -371,7 +372,7 @@ class DefaultServicesController(base.ServicesController):
# only one provider per flavor, that's
# why we use values()[0]
access_url_for_domain = (
service_new.provider_details.values()[0].
list(service_new.provider_details.values())[0].
get_domain_access_url(domain.domain))
if access_url_for_domain is not None:
providers = (
@ -380,21 +381,26 @@ class DefaultServicesController(base.ServicesController):
)
san_cert_url = access_url_for_domain.get(
'provider_url')
# Note(tonytan4ever): stored san_cert_url
# for two times, that's intentional
# a little extra info does not hurt
new_cert_detail = {
providers[0].provider_id.title():
json.dumps(dict(
cert_domain=san_cert_url,
extra_info={
'status': 'deployed',
'san cert': san_cert_url,
'created_at': str(
datetime.datetime.now())
}
))
}
https_upgrade = self._detect_upgrade_http_to_https(
service_old.domains, domain)
if https_upgrade is True:
new_cert_detail = None
else:
# Note(tonytan4ever): stored san_cert_url
# for two times, that's intentional
# a little extra info does not hurt
new_cert_detail = {
providers[0].provider_id.title():
json.dumps(dict(
cert_domain=san_cert_url,
extra_info={
'status': 'deployed',
'san cert': san_cert_url,
'created_at': str(
datetime.datetime.now())
}
))
}
new_cert_obj = ssl_certificate.SSLCertificate(
service_new.flavor_id,
domain.domain,
@ -402,16 +408,32 @@ class DefaultServicesController(base.ServicesController):
project_id,
new_cert_detail
)
self.ssl_certificate_storage.create_certificate(
project_id,
new_cert_obj
)
if https_upgrade is True:
# request a new ssl cert the same way
# ssl_cert creation is done using taskflow
LOG.debug('Sending request to create ssl cert')
self.ssl_cert_manager.create_ssl_certificate(
project_id,
new_cert_obj,
https_upgrade=True
)
else:
self.ssl_certificate_storage.\
create_certificate(
project_id,
new_cert_obj
)
# deserialize cert_details dict
new_cert_obj.cert_details[
providers[0].provider_id.title()] = json.loads(
try:
new_cert_obj.cert_details[
providers[0].provider_id.title()]
)
providers[0].provider_id.title()
] = json.loads(
new_cert_obj.cert_details[
providers[0].provider_id.title()]
)
except Exception:
new_cert_obj.cert_details[
providers[0].provider_id.title()] = {}
domain.cert_info = new_cert_obj
if hasattr(self, store):
@ -773,3 +795,15 @@ class DefaultServicesController(base.ServicesController):
service_id,
provider_details
)
def _detect_upgrade_http_to_https(self, old_domains, new_domain):
is_upgrade = False
for old_domain in old_domains:
if old_domain.domain == new_domain.domain:
if (
old_domain.protocol == 'http' and
new_domain.protocol == 'https'
):
is_upgrade = True
break
return is_upgrade

View File

@ -43,7 +43,8 @@ class DefaultSSLCertificateController(base.SSLCertificateController):
self.service_storage = self._driver.storage.services_controller
self.flavor_controller = self._driver.storage.flavors_controller
def create_ssl_certificate(self, project_id, cert_obj):
def create_ssl_certificate(
self, project_id, cert_obj, https_upgrade=False):
if (not validators.is_valid_domain_name(cert_obj.domain_name)) or \
(validators.is_root_domain(
@ -75,6 +76,9 @@ class DefaultSSLCertificateController(base.SSLCertificateController):
'cert_obj_json': json.dumps(cert_obj.to_dict()),
'context_dict': context_utils.get_current().to_dict()
}
if https_upgrade is True:
kwargs['https_upgrade'] = True
self.distributed_task_controller.submit_task(
create_ssl_certificate.create_ssl_certificate,
**kwargs)

View File

@ -152,10 +152,13 @@ class ProviderDetail(common.DictSerializableModel):
self._error_class = value
def get_domain_access_url(self, domain):
'''Find an access url of a domain.
"""Return an access url object for a domain.
:param domain
'''
:param domain: domain to use as search key
:type domain: poppy.model.helpers.domain.Domain
:returns: access_url -- dict containing matching domain
"""
for access_url in self.access_urls:
if access_url.get('domain') == domain:
return access_url

View File

@ -53,7 +53,7 @@ class CertificateController(base.CertificateBase):
self.driver = driver
self.sps_api_base_url = self.driver.akamai_sps_api_base_url
def create_certificate(self, cert_obj, enqueue=True):
def create_certificate(self, cert_obj, enqueue=True, https_upgrade=False):
if cert_obj.cert_type == 'san':
try:
found, found_cert = (
@ -77,7 +77,7 @@ class CertificateController(base.CertificateBase):
if enqueue:
self.mod_san_queue.enqueue_mod_san_request(
json.dumps(cert_obj.to_dict()))
return self.responder.ssl_certificate_provisioned(None, {
extras = {
'status': 'create_in_progress',
'san cert': None,
# Add logging so it is easier for testing
@ -86,7 +86,18 @@ class CertificateController(base.CertificateBase):
'San cert request for {0} has been '
'enqueued.'.format(cert_obj.domain_name)
)
})
}
if https_upgrade is True:
extras['https upgrade notes'] = (
"This domain was upgraded from HTTP to HTTPS SAN."
"Take note of the domain name. Where applicable, "
"delete the old HTTP policy after the upgrade is "
"complete or the old policy is no longer in use."
)
return self.responder.ssl_certificate_provisioned(
None,
extras
)
san_cert_hostname_limit = (
self.cert_info_storage.get_san_cert_hostname_limit()

View File

@ -397,6 +397,7 @@ class ServiceController(base.ServiceBase):
LOG.info('Creating/Updating policy %s on domain %s '
'complete' % (dp, classified_domain.domain))
edge_host_name = None
old_operator_url = None
if classified_domain.certificate == 'san':
cert_info = getattr(classified_domain, 'cert_info',
None)
@ -409,6 +410,13 @@ class ServiceController(base.ServiceBase):
edge_host_name = (
classified_domain.cert_info.
get_san_edge_name())
domain_access_url = service_obj.provider_details[
self.driver.provider_name
].get_domain_access_url(classified_domain.domain)
old_operator_url = (
None if domain_access_url is None else
domain_access_url.get('old_operator_url', None)
)
domains_certificate_status[
classified_domain.domain] = (
classified_domain.cert_info.get_cert_status())
@ -416,19 +424,41 @@ class ServiceController(base.ServiceBase):
continue
provider_access_url = self._get_provider_access_url(
classified_domain, dp, edge_host_name)
links.append({'href': provider_access_url,
'rel': 'access_url',
'domain': dp,
'certificate':
classified_domain.certificate
})
links.append({
'href': provider_access_url,
'rel': 'access_url',
'domain': dp,
'certificate': classified_domain.certificate,
'old_operator_url': old_operator_url
})
except Exception:
LOG.exception("Failed to Update Service - {0}".
format(provider_service_id))
return self.responder.failed("failed to update service")
# check to see if a domain was upgraded from http -> https+san
# and keep the policy if it was an upgrade
try:
for policy in policies:
is_upgrade = False
for link_id in ids:
if (
link_id['policy_name'] == policy['policy_name'] and
link_id['protocol'] == 'https' and
policy['protocol'] == 'http'
):
is_upgrade = True
# skip policy delete if a http -> https+san
# upgrade is detected
if is_upgrade is True:
LOG.info(
"{0} was upgraded from http to https san. "
"Skipping old policy delete.".format(
policy['policy_name']))
continue
configuration_number = self._get_configuration_number(
util.dict2obj(policy))

View File

@ -116,16 +116,20 @@ class CertificatesController(base.CertificatesController):
consistency_level=self._driver.consistency_level)
self.session.execute(stmt, args)
cert_status = None
try:
provider_status = json.loads(
list(cert_obj.cert_details.values())[0]
)
cert_status = provider_status['extra_info']['status']
except (IndexError, IndexError, ValueError) as e:
LOG.error("Certificate details in inconsistent "
"state: {0}".format(cert_obj.cert_details))
LOG.error(e)
else:
except (IndexError, KeyError, ValueError) as e:
LOG.warning(
"Create certificate missing extra info "
"status {0}: Error {1}. "
"Using 'create_in_progress' instead. ".format(
cert_obj.cert_details, e))
cert_status = 'create_in_progress'
finally:
# insert/update for cassandra
self.insert_cert_status(cert_obj.domain_name, cert_status)
@ -178,13 +182,14 @@ class CertificatesController(base.CertificatesController):
try:
provider_status = json.loads(list(cert_details.values())[0])
cert_status = provider_status['extra_info']['status']
except (IndexError, IndexError, ValueError) as e:
LOG.error("Certificate details in inconsistent "
"state: {0}".format(cert_details))
LOG.error(e)
else:
# insert/update for cassandra
self.insert_cert_status(domain_name, cert_status)
except (IndexError, KeyError, ValueError) as e:
# certs already existing in DB should have all
# the necessary fields
LOG.error(
"Unable to update cert_status because certificate "
"details are in an inconsistent "
"state: {0}: {1}".format(cert_details, e))
def insert_cert_status(self, domain_name, cert_status):
cert_args = {

View File

@ -95,7 +95,10 @@ class Model(collections.OrderedDict):
# add the access urls
access_urls = provider_detail.access_urls
for access_url in access_urls:
if 'operator_url' in access_url:
if (
'operator_url' in access_url and
access_url['operator_url'] is not None
):
self['links'].append(link.Model(
access_url['operator_url'],
'access_url'))

View File

@ -170,7 +170,7 @@ def is_valid_ip_address(ip_address):
# "{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1"
# "}[0-9]))$"
# Note(tonytan4ever): make it more clear because re.mtach will return
# Note(tonytan4ever): make it more clear because re.match will return
# a match object is there is a match, None if there is no match.
return re.match(ipv4_regex, ip_address) is not None

View File

@ -155,7 +155,7 @@ class TestServicesState(base.FunctionalTest):
@given(strategies.text(min_size=257))
def test_services_state_invalid_project_id(self, project_id):
# NOTE(TheSriram): the min size is assigned to 257, since
# project_id regex allows upto 256 chars
# project_id regex allows up to 256 chars
# invalid project_id field
self.req_body['project_id'] = project_id
self.req_body['status'] = 'deployed'

View File

@ -878,7 +878,9 @@ class TestServicesUpdate(base.TestCase):
{
'domain': u'pictures.domain.com',
'href': u'pictures.domain.com.global.prod.fastly.net',
'rel': 'access_url'
'rel': 'access_url',
'certificate': 'san',
'old_operator_url': 'old.operator.url.cdn99.mycdn.com'
}
]}
}]
@ -900,6 +902,63 @@ class TestServicesUpdate(base.TestCase):
self.assertIsNotNone(
access_urls_map[provider_name][domain_new.domain])
def test_update_add_domains_http_to_https_upgrade(self):
subdomain = mock.Mock()
subdomain.add_records = mock.Mock()
self.client.find = mock.Mock(return_value=subdomain)
domains_new = [
domain.Domain('test.domain.com'),
domain.Domain('blog.domain.com')
]
self.service_old.domains = domains_new
service_new = service.Service(
service_id=self.service_old.service_id,
name='myservice',
domains=domains_new,
origins=[],
flavor_id='standard')
responders = [{
'Fastly': {
'id': str(uuid.uuid4()),
'links': [
{
'domain': u'test.domain.com',
'href': u'test.domain.com.global.prod.fastly.net',
'rel': 'access_url'
},
{
'domain': u'blog.domain.com',
'href': u'blog.domain.com.global.prod.fastly.net',
'rel': 'access_url',
'certificate': 'san',
'old_operator_url': 'old.operator.url.cdn99.mycdn.com'
}
]}
}]
dns_details = self.controller.update(
self.service_old,
service_new,
responders
)
access_urls_map = {}
for provider_name in dns_details:
access_urls_map[provider_name] = {}
access_urls_list = dns_details[provider_name]['access_urls']
for access_urls in access_urls_list:
access_urls_map[provider_name][access_urls['domain']] = (
access_urls['operator_url'])
for responder in responders:
for provider_name in responder:
for domain_new in domains_new:
self.assertIsNotNone(
access_urls_map[provider_name][domain_new.domain])
def test_gather_cname_links_positive(self):
cname_links = self.controller.gather_cname_links(self.service_old)
# TODO(isaacm): Add assertions on the returned object

View File

@ -669,8 +669,7 @@ class DefaultManagerServiceTests(base.TestCase):
return_value=Response(False)):
self.mock_update_service(provider_details_json)
@ddt.file_data('service_update.json')
def test_update(self, update_json):
def test_update(self):
provider_details_dict = {
"MaxCDN": {"id": 11942, "access_urls": ["mypullzone.netdata.com"]},
"Mock": {"id": 73242, "access_urls": ["mycdn.mock.com"]},
@ -713,6 +712,177 @@ class DefaultManagerServiceTests(base.TestCase):
# ensure the manager calls the storage driver with the appropriate data
self.sc.storage_controller.update_service.assert_called_once()
def test_upgrade_http_to_https_san(self):
provider_details_dict = {
"MaxCDN": {
"id": 11942,
"access_urls": [{
"domain": "www.mywebsite.com",
"access_url": "mypullzone.netdata.com",
"provider_url": 'maxcdn.provider.com'
}]
},
"Mock": {
"id": 73242,
"access_urls": [{
"domain": "www.mywebsite.com",
"access_url": "mycdn.mock.com",
"provider_url": 'mock.provider.com'
}]
},
"CloudFront": {
"id": "5ABC892",
"access_urls": [{
"access_url": "cf123.cloudcf.com",
"domain": "www.mywebsite.com",
"provider_url": 'cf.provider.com'
}]
},
"Fastly": {
"id": 3488,
"access_urls": [{
"access_url": "mockcf123.fastly.prod.com",
"domain": "www.mywebsite.com",
"provider_url": 'fastly.provider.com'
}]
}
}
providers_details_dict = {}
for name in provider_details_dict:
details = provider_details_dict[name]
provider_detail_obj = provider_details.ProviderDetail(
provider_service_id=details['id'],
access_urls=details['access_urls'],
status=details.get('status', u'unknown'))
providers_details_dict[name] = provider_detail_obj
self.sc.storage_controller.get_provider_details.return_value = (
providers_details_dict
)
service_obj = service.load_from_json(self.service_json)
service_obj.provider_details = providers_details_dict
service_obj.status = u'deployed'
self.sc.storage_controller.get_service.return_value = service_obj
self.sc.ssl_certificate_storage.get_certs_by_domain.return_value = []
self.sc.flavor_controller.get.return_value = flavor.Flavor(
'standard',
providers=[
flavor.Provider('MaxCDN', 'http://maxcdn.com'),
flavor.Provider('Mock', 'http://www.mock.com'),
flavor.Provider('CloudFront', 'http://www.cloudfront.com'),
flavor.Provider('Fastly', 'http://www.fastly.com')
]
)
service_updates = json.dumps([
{
"op": "replace",
"path": "/domains/0",
"value": {
"domain": "www.mywebsite.com",
"protocol": "https",
"certificate": "san"
}
}
])
self.sc.update_service(
self.project_id,
self.service_id,
self.auth_token,
service_updates
)
# ensure the manager calls the storage driver with the appropriate data
self.sc.storage_controller.update_service.assert_called_once()
def test_update_service_operator_status_disabled_error(self):
provider_details_dict = {
"MaxCDN": {"id": 11942, "access_urls": ["mypullzone.netdata.com"]},
"Mock": {"id": 73242, "access_urls": ["mycdn.mock.com"]},
"CloudFront": {
"id": "5ABC892", "access_urls": ["cf123.cloudcf.com"]},
"Fastly": {
"id": 3488, "access_urls": ["mockcf123.fastly.prod.com"]}
}
providers_details = {}
for name in provider_details_dict:
details = provider_details_dict[name]
provider_detail_obj = provider_details.ProviderDetail(
provider_service_id=details['id'],
access_urls=details['access_urls'],
status=details.get('status', u'unknown'))
providers_details[name] = provider_detail_obj
self.sc.storage_controller.get_provider_details.return_value = (
providers_details
)
service_obj = service.load_from_json(self.service_json)
service_obj.status = u'deployed'
service_obj.operator_status = 'disabled'
self.sc.storage_controller.get_service.return_value = service_obj
service_updates = json.dumps([
{
"op": "replace",
"path": "/domains/0",
"value": {"domain": "added.mocksite4.com"}
}
])
with testtools.ExpectedException(errors.ServiceStatusDisabled):
self.sc.update_service(
self.project_id,
self.service_id,
self.auth_token,
service_updates
)
def test_update_service_status_not_failed_or_deployed_error(self):
provider_details_dict = {
"MaxCDN": {"id": 11942, "access_urls": ["mypullzone.netdata.com"]},
"Mock": {"id": 73242, "access_urls": ["mycdn.mock.com"]},
"CloudFront": {
"id": "5ABC892", "access_urls": ["cf123.cloudcf.com"]},
"Fastly": {
"id": 3488, "access_urls": ["mockcf123.fastly.prod.com"]}
}
providers_details = {}
for name in provider_details_dict:
details = provider_details_dict[name]
provider_detail_obj = provider_details.ProviderDetail(
provider_service_id=details['id'],
access_urls=details['access_urls'],
status=details.get('status', u'unknown'))
providers_details[name] = provider_detail_obj
self.sc.storage_controller.get_provider_details.return_value = (
providers_details
)
service_obj = service.load_from_json(self.service_json)
service_obj.status = u'create_in_progress'
self.sc.storage_controller.get_service.return_value = service_obj
service_updates = json.dumps([
{
"op": "replace",
"path": "/domains/0",
"value": {"domain": "added.mocksite4.com"}
}
])
with testtools.ExpectedException(
errors.ServiceStatusNeitherDeployedNorFailed
):
self.sc.update_service(
self.project_id,
self.service_id,
self.auth_token,
service_updates
)
@ddt.file_data('data_provider_details.json')
def test_delete(self, provider_details_json):
self.provider_details = {}

View File

@ -1,73 +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": "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": "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": "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.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\"}}"
}
}
},
{
"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\"}}"
}
}
]
]
]
]

View File

@ -15,7 +15,6 @@
import uuid
import cassandra
import ddt
import mock
from oslo_config import cfg
@ -27,10 +26,10 @@ from tests.unit import base
@ddt.ddt
class CassandraStorageServiceTests(base.TestCase):
class CassandraStorageCertificateTests(base.TestCase):
def setUp(self):
super(CassandraStorageServiceTests, self).setUp()
super(CassandraStorageCertificateTests, self).setUp()
# mock arguments to use
self.project_id = '123456'
@ -54,16 +53,18 @@ class CassandraStorageServiceTests(base.TestCase):
migrations_patcher.start()
self.addCleanup(migrations_patcher.stop)
cluster_patcher = mock.patch('cassandra.cluster.Cluster')
self.mock_cluster = cluster_patcher.start()
self.mock_session = self.mock_cluster().connect()
self.addCleanup(cluster_patcher.stop)
# stubbed cassandra driver
self.cc = certificates.CertificatesController(cassandra_driver)
@ddt.file_data('data_get_certs_by_domain.json')
@mock.patch.object(certificates.CertificatesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_get_certs_by_domain(self, cert_details_json,
mock_session, mock_execute):
def test_get_certs_by_domain(self, cert_details_json):
# mock the response from cassandra
mock_execute.execute.return_value = cert_details_json[0]
self.mock_session.execute.return_value = cert_details_json[0]
actual_response = self.cc.get_certs_by_domain(
domain_name="www.mydomain.com"
)
@ -71,7 +72,7 @@ class CassandraStorageServiceTests(base.TestCase):
self.assertTrue(all([isinstance(ssl_cert,
ssl_certificate.SSLCertificate)
for ssl_cert in actual_response]))
mock_execute.execute.return_value = cert_details_json[1]
self.mock_session.execute.return_value = cert_details_json[1]
actual_response = self.cc.get_certs_by_domain(
domain_name="www.example.com",
flavor_id="flavor1")
@ -79,7 +80,7 @@ class CassandraStorageServiceTests(base.TestCase):
self.assertTrue(all([isinstance(ssl_cert,
ssl_certificate.SSLCertificate)
for ssl_cert in actual_response]))
mock_execute.execute.return_value = cert_details_json[2]
self.mock_session.execute.return_value = cert_details_json[2]
actual_response = self.cc.get_certs_by_domain(
domain_name="www.mydomain.com",
flavor_id="flavor1",
@ -87,20 +88,82 @@ class CassandraStorageServiceTests(base.TestCase):
self.assertTrue(isinstance(actual_response,
ssl_certificate.SSLCertificate))
@mock.patch.object(certificates.CertificatesController, 'session')
@mock.patch.object(cassandra.cluster.Session, 'execute')
def test_get_certs_by_status(self, mock_session, mock_execute):
def test_get_certs_by_status(self):
# mock the response from cassandra
mock_execute.execute.return_value = \
self.mock_session.execute.return_value = \
[{"domain_name": "www.example.com"}]
actual_response = self.cc.get_certs_by_status(
status="deployed")
self.assertEqual(actual_response,
[{"domain_name": "www.example.com"}])
mock_execute.execute.return_value = \
self.mock_session.execute.return_value = \
[{"domain_name": "www.example1.com"}]
actual_response = self.cc.get_certs_by_status(
status="failed")
self.assertEqual(actual_response,
[{"domain_name": "www.example1.com"}])
@ddt.file_data('data_get_certs_by_domain.json')
def test_create_cert_already_exists(self, cert_details_json):
# mock the response from cassandra
self.mock_session.execute.return_value = cert_details_json[0]
ssl_cert_obj = ssl_certificate.SSLCertificate(
'flavor1',
'www.mydomain.com',
'san'
)
self.assertRaises(
ValueError,
self.cc.create_certificate, '12345', ssl_cert_obj
)
@ddt.file_data('data_get_certs_by_domain.json')
def test_delete_cert(self, cert_details_json):
# mock the response from cassandra
self.mock_session.execute.return_value = cert_details_json[0]
try:
self.cc.delete_certificate('12345', 'www.mydomain.com', 'san')
except Exception as e:
self.fail(e)
def test_create_certificate_with_cert_status_in_details(self):
ssl_cert_obj = ssl_certificate.SSLCertificate(
'flavor1',
'www.mydomain.com',
'san',
project_id='12345',
cert_details={
"provider": "{\"cert_domain\": \"abc\", \"extra_info\": "
"{ \"status\": \"deployed\", \"san_cert\": \""
"awesome_san\", \"action\": \"Ready\"}}"
}
)
try:
self.cc.create_certificate('12345', ssl_cert_obj)
except Exception as e:
self.fail(e)
def test_update_certificate_with_cert_status_in_details(self):
ssl_cert_obj = ssl_certificate.SSLCertificate(
'flavor1',
'www.mydomain.com',
'san',
project_id='12345',
cert_details={
"provider": "{\"cert_domain\": \"abc\", \"extra_info\": "
"{ \"status\": \"deployed\", \"san_cert\": \""
"awesome_san\", \"action\": \"Ready\"}}"
}
)
try:
self.cc.update_certificate(
'www.mydomain.com', 'san', 'flavor1',
ssl_cert_obj.cert_details
)
except Exception as e:
self.fail(e)