From 9829044ff408f90fd1e94baebcb5d4d00211bb68 Mon Sep 17 00:00:00 2001 From: Yuvaraju Meenuga Date: Mon, 12 Nov 2018 18:03:56 +0530 Subject: [PATCH] [CDN-1160] Check latest property for cert status 'deployed' To mark a certificate status with 'deployed', the domain must be present in both the provider certificate and in latest property hostnames. Also, use the Task 'GetCertInfoTask' for retrieving the certificate details direct from Cassandra. --- poppy/manager/default/background_job.py | 5 +- .../check_cert_status_and_update_flow.py | 2 + .../check_cert_status_and_update_tasks.py | 63 ++++++++++++- .../akamai/background_jobs/akamai_mocks.py | 89 +++++++++++++++++++ .../akamai/background_jobs/test_flows.py | 34 +++---- 5 files changed, 172 insertions(+), 21 deletions(-) diff --git a/poppy/manager/default/background_job.py b/poppy/manager/default/background_job.py index 18ed5c04..8b92ce80 100644 --- a/poppy/manager/default/background_job.py +++ b/poppy/manager/default/background_job.py @@ -112,7 +112,10 @@ def post_job(self, job_type, kwargs): ) t_kwargs = { "cert_obj_json": json.dumps(cert_dict), - "project_id": cert_dict.get("project_id") + "project_id": cert_dict.get("project_id"), + "property_spec": kwargs.get( + "property_spec", + 'akamai_https_sni_config_numbers') } if cert_dict.get('property_activated', False) is True: self.distributed_task_controller.submit_task( diff --git a/poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_flow.py b/poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_flow.py index 414c8395..49e44f79 100644 --- a/poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_flow.py +++ b/poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_flow.py @@ -31,6 +31,8 @@ def check_cert_status_and_update_flow(): flow = linear_flow.Flow('Update Akamai Property').add( + check_cert_status_and_update_tasks.GetHostnamesFromProperty(), + check_cert_status_and_update_tasks.GetCertInfoTask(), check_cert_status_and_update_tasks.CheckCertStatusTask(), check_cert_status_and_update_tasks.UpdateCertStatusTask() ) diff --git a/poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_tasks.py b/poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_tasks.py index b656bba4..a65e693b 100644 --- a/poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_tasks.py +++ b/poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_tasks.py @@ -28,6 +28,54 @@ conf(project='poppy', prog='poppy', args=[]) +class GetHostnamesFromProperty(task.Task): + default_provides = 'hosts_in_latest_property' + + def __init__(self): + super(GetHostnamesFromProperty, self).__init__() + service_controller, self.providers = \ + memoized_controllers.task_controllers('poppy', 'providers') + self.akamai_driver = self.providers['akamai'].obj + self.existing_hosts = [] + + def execute(self, property_spec): + """Get the host names from the latest property""" + self.property_id = self.akamai_driver.papi_property_id(property_spec) + + LOG.info('Starting to get latest version for property: %s' + % self.property_id) + resp = self.akamai_driver.akamai_papi_api_client.get( + self.akamai_driver.akamai_papi_api_base_url.format( + middle_part='properties/%s' % self.property_id) + ) + if resp.status_code != 200: + raise RuntimeError('PAPI API request failed.' + 'Exception: %s' % resp.text) + else: + property = json.loads(resp.text)['properties']['items'][0] + latestVersion = property['latestVersion'] or 0 + production_version = property['productionVersion'] or -1 + staging_version = property['stagingVersion'] or -1 + + max_version = max(latestVersion, production_version, + staging_version) + resp = self.akamai_driver.akamai_papi_api_client.get( + self.akamai_driver.akamai_papi_api_base_url.format( + middle_part='properties/%s/versions/%s/hostnames' % + (self.property_id, + str(max_version))) + ) + if resp.status_code != 200: + raise RuntimeError('PAPI API request failed.' + 'Exception: %s' % resp.text) + self.existing_hosts = json.loads(resp.text)['hostnames'][ + 'items'] + self.existing_hosts = [str(host['cnameFrom']) + for host in self.existing_hosts] + + return self.existing_hosts + + class GetCertInfoTask(task.Task): default_provides = "cert_obj_json" @@ -55,7 +103,7 @@ def __init__(self): memoized_controllers.task_controllers('poppy', 'providers') self.akamai_driver = self.providers['akamai'].obj - def execute(self, cert_obj_json): + def execute(self, cert_obj_json, hosts_in_latest_property): if cert_obj_json != "": cert_obj = ssl_certificate.load_from_json( json.loads(cert_obj_json)) @@ -156,7 +204,16 @@ def execute(self, cert_obj_json): if change_url not in pending_changes: if cert_obj.domain_name in dns_names: - return "deployed" + if cert_obj.domain_name in hosts_in_latest_property: + return "deployed" + else: + # The hostname is still not on the latest + # property version. Return the item to the + # queue. Another attempt to update property + # should happen. + self.akamai_driver.san_mapping_queue.enqueue_san_mapping( + json.dumps(cert_obj.to_dict())) + return current_status else: return "failed" else: @@ -214,4 +271,4 @@ def execute(self, project_id, cert_obj_json, status_change_to): service_obj.provider_details ) else: - pass + pass \ No newline at end of file diff --git a/tests/unit/provider/akamai/background_jobs/akamai_mocks.py b/tests/unit/provider/akamai/background_jobs/akamai_mocks.py index 5af20c3c..14e4aace 100644 --- a/tests/unit/provider/akamai/background_jobs/akamai_mocks.py +++ b/tests/unit/provider/akamai/background_jobs/akamai_mocks.py @@ -70,6 +70,7 @@ def get_providers(): } akamai_mock_provider_obj.akamai_sps_api_client = MockSPSAPIClient() akamai_mock_provider_obj.akamai_papi_api_client = MockPapiAPIClient() + akamai_mock_provider_obj.akamai_cps_api_client = MockCPSAPIClient() akamai_mock_provider_obj.akamai_sps_api_base_url = ( 'https://mybaseurl.net/config-secure-provisioning-service/' 'v1/sps-requests/{spsId}?' @@ -77,6 +78,10 @@ def get_providers(): akamai_mock_provider_obj.akamai_papi_api_base_url = ( 'https://mybaseurl.net/papi/v1/{middle_part}/' '?contractId=ctr_None&groupId=grp_None') + akamai_mock_provider_obj.akamai_cps_api_base_url = ( + 'https://mybaseurl.net/' + '/cps/v2/enrollments/{enrollmentId}' + ) akamai_mock_provider_obj.san_mapping_queue.\ traverse_queue.return_value = [] akamai_mock_provider.obj = akamai_mock_provider_obj @@ -108,6 +113,26 @@ def get_certs_by_domain(self, domain_name, project_id=None, flavor_id=None, cert_type=None): + if cert_type == 'sni': + return ssl_certificate.SSLCertificate( + "premium", + "blog.testabcd.com", + "sni", + project_id=project_id, + cert_details={ + 'Akamai': { + u'cert_domain': u'secure2.san1.test_123.com', + u'extra_info': { + u'change_url': '/cps/v2/enrollments/10000/changes/10000', + u'action': u'Waiting for customer domain ' + 'validation for blog.testabc.com', + u'create_at': u'2015-09-29 16:09:12.429147', + u'san cert': u'secure2.san1.test_123.com', + u'status': u'create_in_progress'} + } + } + ) + return ssl_certificate.SSLCertificate( "premium", "blog.testabcd.com", @@ -165,6 +190,11 @@ def get(self, url): "edgeHostnameId": "ehn_1126816", "cnameFrom": "secure.san2.test_789.com", "cnameTo": "secure.test_456.com.edge_host_test.net" + }, { + "cnameType": "EDGE_HOSTNAME", + "edgeHostnameId": "ehn_1231234", + "cnameFrom": "blog.testabcd.com", + "cnameTo": "secure.test_456.com.edge_host_test.net" }] } }) @@ -341,3 +371,62 @@ def post(self, url, data=None, headers=None): def put(self, url, data=None, headers=None): return self.response_200 + +class MockCPSAPIClient(mock.Mock): + def __init__(self): + super(MockCPSAPIClient, self).__init__() + self.response_200 = mock.Mock(status_code=200) + + def get(self, url, headers): + self.response_200.text = json.dumps({ + "location": "/cps/v2/enrollments/10002", + "ra": "third-party", + "validationType": "third-party", + "certificateType": "third-party", + "certificateChainType": "default", + "networkConfiguration": { + "geography": "core", + "secureNetwork": "enhanced-tls", + "mustHaveCiphers": "ak-akamai-default", + "preferredCiphers": "ak-akamai-default-interim", + "disallowedTlsVersions": [], + "sniOnly": True, + "quicEnabled": False, + "sni": { + "cloneDnsNames": False, + "dnsNames": [ + "blog.testabcd.com", + "san1.example.com" + ] + }, + "ocspStapling": "not-set" + }, + "signatureAlgorithm": None, + "changeManagement": True, + "csr": { + "cn": "www.example.com", + "c": "US", + "st": "MA", + "l": "Cambridge", + "o": "Akamai", + "ou": "WebEx", + "sans": [ + "san1.example.com", + "san2.example.com", + "san3.example.com" + ] + }, + + "thirdParty": { + "excludeSans": False + }, + "enableMultiStackedCertificates": False, + "pendingChanges": [ + "/cps/v2/enrollments/10002/changes/10002" + ], + "maxAllowedSanNames": 100, + "maxAllowedWildcardSanNames": 100 + }) + + self.response_200.status_code = 200 + return self.response_200 diff --git a/tests/unit/provider/akamai/background_jobs/test_flows.py b/tests/unit/provider/akamai/background_jobs/test_flows.py index 7792baed..f4df97a0 100644 --- a/tests/unit/provider/akamai/background_jobs/test_flows.py +++ b/tests/unit/provider/akamai/background_jobs/test_flows.py @@ -72,24 +72,12 @@ def task_controllers_side_effect(*args, **kwargs): self.addCleanup(bootstrap_patcher.stop) def test_check_cert_status_and_update_flow(self): - cert_obj_json = json.dumps( - ssl_certificate.SSLCertificate( - 'cdn', - 'website.com', - 'san', - cert_details={ - 'Akamai': { - 'extra_info': { - 'san cert': 'secure1.san1.testcdn.com' - } - } - } - ).to_dict() - ) - kwargs = { - 'cert_obj_json': cert_obj_json, - 'project_id': "000" + 'domain_name': 'website.com', + 'cert_type': 'san', + 'flavor_id': 'cdn', + 'project_id': "000", + "property_spec": "akamai_https_san_config_numbers" } engines.run(check_cert_status_and_update_flow. check_cert_status_and_update_flow(), @@ -115,3 +103,15 @@ def test_update_papi_flow(self): } engines.run(update_property_flow.update_property_flow(), store=kwargs) + + def test_domain_exists_in_latest_property_hostnames(self): + kwargs = { + 'domain_name': 'website.com', + 'cert_type': 'sni', + 'flavor_id': 'flavor1', + 'project_id': "000", + "property_spec": "akamai_https_san_config_numbers" + } + engines.run(check_cert_status_and_update_flow. + check_cert_status_and_update_flow(), + store=kwargs)