Browse Source

feat: Add akamai background job admin endpoint

REQUEST:
    POST /admin/provider/akamai/background_job
    {
      "job_type": "akamai_update_papi_property_for_mod_san",
      "domain_name": "testabc.cnamecdn.com",
      "san_cert_name": "secure1.san1.test_cdn.com"
    }
RESPONSE:
    202 ACCEPTED
Note: The admin endpoints are also exposed as scripts:
    akamai-property-udpate-mod-san --domain_name <your_domain_name> --san_cert_name <your_san_cert_name>
    akamai-cert-status-check-and-update --domain_name <your_domain_name> --cert_type "san" --project_id <PROJECT_ID> --flavor_id <FLAVOR_ID>

Change-Id: I9211d9f24cbd46c8818993d3ff8493d5fba4bc28
tags/2015.11.0b3
Sriram Madapusi Vasudevan tonytan4ever 4 years ago
parent
commit
7a2c4c56e4
35 changed files with 1482 additions and 18 deletions
  1. +64
    -0
      poppy/cmd/akamai_check_and_update_cert_status.py
  2. +78
    -0
      poppy/cmd/akamai_update_papi_property_for_mod_san.py
  3. +8
    -3
      poppy/distributed_task/taskflow/task/create_service_tasks.py
  4. +2
    -0
      poppy/distributed_task/utils/memoized_controllers.py
  5. +2
    -0
      poppy/manager/base/__init__.py
  6. +36
    -0
      poppy/manager/base/background_job.py
  7. +95
    -0
      poppy/manager/default/background_job.py
  8. +2
    -0
      poppy/manager/default/controllers.py
  9. +4
    -0
      poppy/manager/default/driver.py
  10. +19
    -3
      poppy/manager/default/services.py
  11. +0
    -0
      poppy/provider/akamai/background_jobs/__init__.py
  12. +0
    -0
      poppy/provider/akamai/background_jobs/check_cert_status_and_update/__init__.py
  13. +52
    -0
      poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_flow.py
  14. +124
    -0
      poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_tasks.py
  15. +0
    -0
      poppy/provider/akamai/background_jobs/update_property/__init__.py
  16. +50
    -0
      poppy/provider/akamai/background_jobs/update_property/update_property_flow.py
  17. +276
    -0
      poppy/provider/akamai/background_jobs/update_property/update_property_tasks.py
  18. +27
    -4
      poppy/provider/akamai/driver.py
  19. +4
    -1
      poppy/storage/cassandra/services.py
  20. +27
    -1
      poppy/storage/mockdb/services.py
  21. +31
    -0
      poppy/transport/pecan/controllers/v1/admin.py
  22. +4
    -1
      poppy/transport/pecan/models/request/ssl_certificate.py
  23. +1
    -0
      poppy/transport/pecan/models/response/service.py
  24. +88
    -0
      poppy/transport/validators/schemas/background_jobs.py
  25. +2
    -0
      setup.cfg
  26. +8
    -1
      tests/etc/default_functional.conf
  27. +14
    -0
      tests/functional/transport/pecan/controllers/data_post_background_jobs.json
  28. +22
    -0
      tests/functional/transport/pecan/controllers/data_post_background_jobs_bad_input.json
  29. +51
    -0
      tests/functional/transport/pecan/controllers/test_background_jobs.py
  30. +1
    -1
      tests/functional/transport/pecan/controllers/test_ssl_certificate.py
  31. +0
    -0
      tests/unit/provider/akamai/background_jobs/__init__.py
  32. +321
    -0
      tests/unit/provider/akamai/background_jobs/akamai_mocks.py
  33. +66
    -0
      tests/unit/provider/akamai/background_jobs/test_flows.py
  34. +0
    -0
      tests/unit/storage/cassandra/data_get_certs_by_domain.json
  35. +3
    -3
      tests/unit/storage/cassandra/test_services.py

+ 64
- 0
poppy/cmd/akamai_check_and_update_cert_status.py View File

@@ -0,0 +1,64 @@
# 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.

from oslo_config import cfg

from poppy.common import cli
from poppy.openstack.common import log
from poppy.provider.akamai.background_jobs.check_cert_status_and_update import \
check_cert_status_and_update_flow

LOG = log.getLogger(__name__)

CLI_OPT = [
cfg.StrOpt('domain_name',
required=True,
help='The domain you want to check cert status on'),
cfg.StrOpt('cert_type',
default='san',
help='Cert type of this cert'),
cfg.StrOpt('project_id',
required=True,
help='project id of this cert'),
cfg.StrOpt('flavor_id',
default='cdn',
help='flavor id of this cert'),
]


@cli.runnable
def run():
# TODO(kgriffs): For now, we have to use the global config
# to pick up common options from openstack.common.log, since
# that module uses the global CONF instance exclusively.
conf = cfg.ConfigOpts()
conf.register_cli_opts(CLI_OPT)
conf(prog='akamai-cert-check')

LOG.info('Starting to check status on domain: %s, for project_id: %s'
'flavor_id: %s, cert_type: %s' %
(
conf.domain_name,
conf.project_id,
conf.flavor_id,
conf.cert_type
))

check_cert_status_and_update_flow.run_check_cert_status_and_update_flow(
conf.domain_name,
conf.cert_type,
conf.flavor_id,
conf.project_id
)

+ 78
- 0
poppy/cmd/akamai_update_papi_property_for_mod_san.py View File

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

from oslo_config import cfg

from poppy.common import cli
from poppy.openstack.common import log
from poppy.provider.akamai.background_jobs.update_property import \
update_property_flow

LOG = log.getLogger(__name__)

CLI_OPT = [
cfg.StrOpt('domain_name',
required=True,
help='The domain you want to add in host name (cnameFrom)'),
cfg.StrOpt('san_cert_name',
required=True,
help='Cert type of this cert'),
cfg.StrOpt('update_type',
default="hostsnames",
help='Update type for this update, available types are:'
'hostsnames, secureEdgeHost, rules'),
cfg.StrOpt('action',
default="add",
help='What kind of action, do you want "add" or "remove" '
'hostnames'),
cfg.StrOpt('property_spec',
default='akamai_https_san_config_numbers',
help='Property spec of the property to be updated'),
cfg.StrOpt('san_cert_domain_suffix',
default='edgekey.net',
help='Property spec of the property to be updated'),
]


@cli.runnable
def run():
# TODO(kgriffs): For now, we have to use the global config
# to pick up common options from openstack.common.log, since
# that module uses the global CONF instance exclusively.
conf = cfg.ConfigOpts()
conf.register_cli_opts(CLI_OPT)
conf(prog='akamai-papi-update')

LOG.info("%s: %s to %s, on property: %s" % (
conf.action,
conf.domain_name,
conf.san_cert_name,
conf.property_spec
))

update_info_list = json.dumps([
(conf.action,
{
"cnameFrom": conf.domain_name,
"cnameTo": '.'.join([conf.san_cert_name,
conf.san_cert_domain_suffix]),
"cnameType": "EDGE_HOSTNAME"
})
])

update_property_flow.run_update_property_flow(
conf.property_spec, conf.update_type, update_info_list)

+ 8
- 3
poppy/distributed_task/taskflow/task/create_service_tasks.py View File

@@ -55,9 +55,14 @@ class CreateProviderServicesTask(task.Task):
for domain in service_obj.domains:
if domain.certificate == 'san':
cert_for_domain = (
self.storage_controller.get_cert_by_domain(
domain.domain, domain.certificate,
service_obj.flavor_id, project_id))
self.storage_controller.get_certs_by_domain(
domain.domain,
project_id=project_id,
flavor_id=service_obj.flavor_id,
cert_type=domain.certificate
))
if cert_for_domain == []:
cert_for_domain = None
domain.cert_info = cert_for_domain
except ValueError:
msg = 'Creating service {0} from Poppy failed. ' \


+ 2
- 0
poppy/distributed_task/utils/memoized_controllers.py View File

@@ -46,6 +46,8 @@ def task_controllers(program, controller=None):
return service_controller, service_controller.storage_controller
if controller == 'dns':
return service_controller, service_controller.dns_controller
if controller == 'providers':
return service_controller, bootstrap_obj.manager.providers
if controller == 'ssl_certificate':
return service_controller, (
bootstrap_obj.manager.ssl_certificate_controller)


+ 2
- 0
poppy/manager/base/__init__.py View File

@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from poppy.manager.base import background_job
from poppy.manager.base import driver
from poppy.manager.base import flavors
from poppy.manager.base import home
@@ -22,6 +23,7 @@ from poppy.manager.base import ssl_certificate

Driver = driver.ManagerDriverBase

BackgroundJobController = background_job.BackgroundJobControllerBase
FlavorsController = flavors.FlavorsControllerBase
ServicesController = services.ServicesControllerBase
HomeController = home.HomeControllerBase


+ 36
- 0
poppy/manager/base/background_job.py View File

@@ -0,0 +1,36 @@
# Copyright (c) 2014 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 abc

import six

from poppy.manager.base import controller


@six.add_metaclass(abc.ABCMeta)
class BackgroundJobControllerBase(controller.ManagerControllerBase):
"""Health controller base class."""

def __init__(self, manager):
super(BackgroundJobControllerBase, self).__init__(manager)

@abc.abstractmethod
def post_job(self, job_type, args):
"""Returns the health of storage and providers

:raises: NotImplementedError
"""
raise NotImplementedError

+ 95
- 0
poppy/manager/default/background_job.py View File

@@ -0,0 +1,95 @@
# Copyright (c) 2014 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 json

from oslo_config import cfg

from poppy.manager import base
from poppy.notification.mailgun import driver as n_driver
from poppy.openstack.common import log
from poppy.provider.akamai.background_jobs.check_cert_status_and_update import \
check_cert_status_and_update_flow
from poppy.provider.akamai.background_jobs.update_property import \
update_property_flow
from poppy.provider.akamai import driver as a_driver

conf = cfg.CONF
conf(project='poppy', prog='poppy', args=[])
LOG = log.getLogger(__name__)


class BackgroundJobController(base.BackgroundJobController):

def __init__(self, manager):
super(BackgroundJobController, self).__init__(manager)
self.distributed_task_controller = (
self._driver.distributed_task.services_controller)
self.akamai_san_cert_suffix = (
conf[a_driver.AKAMAI_GROUP].akamai_https_access_url_suffix)
self.notify_email_list = (
conf[n_driver.MAIL_NOTIFICATION_GROUP].recipients)

def post_job(self, job_type, kwargs):
kwargs = kwargs
if job_type == "akamai_check_and_update_cert_status":
LOG.info('Starting to check status on domain: %s,'
'for project_id: %s'
'flavor_id: %s, cert_type: %s' %
(
kwargs.get("domain_name"),
kwargs.get("project_id"),
kwargs.get("flavor_id"),
kwargs.get("cert_type")
))
self.distributed_task_controller.submit_task(
check_cert_status_and_update_flow.
check_cert_status_and_update_flow,
**kwargs)
elif job_type == "akamai_update_papi_property_for_mod_san":
LOG.info("%s: %s to %s, on property: %s" % (
kwargs.get("action", 'add'),
kwargs.get("domain_name"),
kwargs.get("san_cert_name"),
kwargs.get("property_spec", 'akamai_https_san_config_numbers')
))

t_kwargs = {}

update_info_list = json.dumps([
(kwargs.get("property_spec", 'add'),
{
"cnameFrom": kwargs.get("domain_name"),
"cnameTo": '.'.join([kwargs.get("san_cert_name"),
kwargs.get(
"san_cert_domain_suffix",
self.akamai_san_cert_suffix)]),
"cnameType": "EDGE_HOSTNAME"
})
])

t_kwargs = {
"property_spec": kwargs.get("property_spec",
'akamai_https_san_config_numbers'),
"update_type": kwargs.get("update_type", 'hostnames'),
"update_info_list": update_info_list,
"notify_email_list": self.notify_email_list
}

self.distributed_task_controller.submit_task(
update_property_flow.update_property_flow,
**t_kwargs)
else:
raise NotImplementedError('job type: %s has not been implemented')

+ 2
- 0
poppy/manager/default/controllers.py View File

@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from poppy.manager.default import background_job
from poppy.manager.default import flavors
from poppy.manager.default import health
from poppy.manager.default import home
@@ -20,6 +21,7 @@ from poppy.manager.default import services
from poppy.manager.default import ssl_certificate


BackgroundJob = background_job.BackgroundJobController
Home = home.DefaultHomeController
Flavors = flavors.DefaultFlavorsController
Health = health.DefaultHealthController


+ 4
- 0
poppy/manager/default/driver.py View File

@@ -44,6 +44,10 @@ class DefaultManagerDriver(base.Driver):
def health_controller(self):
return controllers.Health(self)

@decorators.lazy_property(write=False)
def background_job_controller(self):
return controllers.BackgroundJob(self)

@decorators.lazy_property(write=False)
def ssl_certificate_controller(self):
return controllers.SSLCertificate(self)

+ 19
- 3
poppy/manager/default/services.py View File

@@ -239,6 +239,18 @@ class DefaultServicesController(base.ServicesController):
existing_shared_domains[customer_domain] = domain.domain
domain.domain = customer_domain

# old domains need to bind as well
elif domain.certificate == 'san':
cert_for_domain = (
self.storage_controller.get_certs_by_domain(
domain.domain,
project_id=project_id,
flavor_id=service_old.flavor_id,
cert_type=domain.certificate))
if cert_for_domain == []:
cert_for_domain = None
domain.cert_info = cert_for_domain

service_old_json = json.loads(json.dumps(service_old.to_dict()))

# remove fields that cannot be part of PATCH
@@ -293,9 +305,13 @@ class DefaultServicesController(base.ServicesController):

elif domain.certificate == 'san':
cert_for_domain = (
self.storage_controller.get_cert_by_domain(
domain.domain, domain.certificate,
service_new.flavor_id, project_id))
self.storage_controller.get_certs_by_domain(
domain.domain,
project_id=project_id,
flavor_id=service_new.flavor_id,
cert_type=domain.certificate))
if cert_for_domain == []:
cert_for_domain = None
domain.cert_info = cert_for_domain

# retrofit the access url info into


+ 0
- 0
poppy/provider/akamai/background_jobs/__init__.py View File


+ 0
- 0
poppy/provider/akamai/background_jobs/check_cert_status_and_update/__init__.py View File


+ 52
- 0
poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_flow.py View File

@@ -0,0 +1,52 @@
# 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.

from oslo_config import cfg
from taskflow import engines
from taskflow.patterns import linear_flow

from poppy.openstack.common import log
from poppy.provider.akamai.background_jobs.check_cert_status_and_update import \
check_cert_status_and_update_tasks


LOG = log.getLogger(__name__)


conf = cfg.CONF
conf(project='poppy', prog='poppy', args=[])


def check_cert_status_and_update_flow():
flow = linear_flow.Flow('Update Akamai Property').add(
check_cert_status_and_update_tasks.GetCertInfoTask(),
check_cert_status_and_update_tasks.CheckCertStatusTask(),
check_cert_status_and_update_tasks.UpdateCertStatusTask()
)
return flow


def run_check_cert_status_and_update_flow(domain_name, cert_type, flavor_id,
project_id):
e = engines.load(
check_cert_status_and_update_flow(),
store={
'domain_name': domain_name,
'cert_type': cert_type,
'flavor_id': flavor_id,
'project_id': project_id
},
engine='serial')
e.run()

+ 124
- 0
poppy/provider/akamai/background_jobs/check_cert_status_and_update/check_cert_status_and_update_tasks.py View File

@@ -0,0 +1,124 @@
# 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 json

from oslo_config import cfg
from taskflow import task

from poppy.distributed_task.utils import memoized_controllers
from poppy.openstack.common import log
from poppy.transport.pecan.models.request import ssl_certificate


LOG = log.getLogger(__name__)
conf = cfg.CONF
conf(project='poppy', prog='poppy', args=[])


class GetCertInfoTask(task.Task):
default_provides = "cert_obj_json"

def execute(self, domain_name, cert_type, flavor_id, project_id):
service_controller, self.storage_controller = \
memoized_controllers.task_controllers('poppy', 'storage')
res = self.storage_controller.get_certs_by_domain(
domain_name, project_id=project_id,
flavor_id=flavor_id, cert_type=cert_type)
if res is None:
return ""
return json.dumps(res.to_dict())


class CheckCertStatusTask(task.Task):
default_provides = "status_change_to"

def __init__(self):
super(CheckCertStatusTask, self).__init__()
service_controller, self.providers = \
memoized_controllers.task_controllers('poppy', 'providers')
self.akamai_driver = self.providers['akamai'].obj

def execute(self, cert_obj_json):
if cert_obj_json != "":
cert_obj = ssl_certificate.load_from_json(json.loads(cert_obj_json)
)
latest_sps_id = cert_obj.cert_details['Akamai']['extra_info'].get(
'akamai_spsId')

if latest_sps_id is None:
return ""

resp = self.akamai_driver.akamai_sps_api_client.get(
self.akamai_driver.akamai_sps_api_base_url.format(
spsId=latest_sps_id
)
)

if resp.status_code != 200:
raise RuntimeError('SPS API Request Failed'
'Exception: %s' % resp.text)

status = json.loads(resp.text)['requestList'][0]['status']

# This SAN Cert is on pending status
if status != 'SPS Request Complete':
LOG.info("SPS Not completed for %s..." %
self.cert)
return ""
else:
LOG.info("SPS completed for %s..." %
cert_obj.get_san_edge_name())
return "deployed"


class UpdateCertStatusTask(task.Task):

def __init__(self):
super(UpdateCertStatusTask, self).__init__()
service_controller, self.storage_controller = \
memoized_controllers.task_controllers('poppy', 'storage')

def execute(self, project_id, cert_obj_json, status_change_to):
if cert_obj_json != "":
cert_obj = ssl_certificate.load_from_json(json.loads(cert_obj_json)
)
cert_details = cert_obj.cert_details

if status_change_to == "deployed":
cert_details['Akamai']['extra_info']['status'] = 'deployed'
cert_details['Akamai'] = json.dumps(cert_details['Akamai'])
self.storage_controller.update_cert_info(cert_obj.domain_name,
cert_obj.cert_type,
cert_obj.flavor_id,
cert_details)

service_obj = (
self.storage_controller.
get_service_details_by_domain_name(cert_obj.domain_name)
)
# Update provider details
if service_obj is not None:
service_obj.provider_details['Akamai'].\
domains_certificate_status.\
set_domain_certificate_status(cert_obj.domain_name,
'deployed')
self.storage_controller.update_provider_details(
project_id,
service_obj.service_id,
service_obj.provider_details
)
else:
pass

+ 0
- 0
poppy/provider/akamai/background_jobs/update_property/__init__.py View File


+ 50
- 0
poppy/provider/akamai/background_jobs/update_property/update_property_flow.py View File

@@ -0,0 +1,50 @@
# 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.

from oslo_config import cfg
from taskflow import engines
from taskflow.patterns import linear_flow

from poppy.openstack.common import log
from poppy.provider.akamai.background_jobs.update_property import (
update_property_tasks)


LOG = log.getLogger(__name__)


conf = cfg.CONF
conf(project='poppy', prog='poppy', args=[])


def update_property_flow():
flow = linear_flow.Flow('Update Akamai Property').add(
update_property_tasks.PropertyGetLatestVersionTask(),
update_property_tasks.PropertyUpdateTask(),
update_property_tasks.PropertyActivateTask()
)
return flow


def run_update_property_flow(property_spec, update_type, update_info_list):
e = engines.load(
update_property_flow(),
store={
"property_spec": property_spec,
"update_type": update_type,
"update_info_list": update_info_list
},
engine='serial')
e.run()

+ 276
- 0
poppy/provider/akamai/background_jobs/update_property/update_property_tasks.py View File

@@ -0,0 +1,276 @@
# 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 json

from oslo_config import cfg
from taskflow import task

from poppy.distributed_task.utils import memoized_controllers
from poppy.openstack.common import log


LOG = log.getLogger(__name__)
conf = cfg.CONF
conf(project='poppy', prog='poppy', args=[])


class PropertyGetLatestVersionTask(task.Task):
default_provides = "new_version_number"

def __init__(self):
super(PropertyGetLatestVersionTask, self).__init__()
service_controller, self.providers = \
memoized_controllers.task_controllers('poppy', 'providers')
self.akamai_driver = self.providers['akamai'].obj
self.sc = self.akamai_driver.service_controller
self.akamai_conf = self.akamai_driver.akamai_conf

def execute(self, property_spec):
"""Get/Create a new Akamai property update version if necessary"""
self.property_id = self.akamai_driver.papi_property_id(property_spec)

LOG.info('Starting to get next 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:
a_property = json.loads(resp.text)['properties']['items'][0]
latestVersion = a_property['latestVersion'] or 0
production_version = a_property['productionVersion'] or -1
staging_version = a_property['stagingVersion'] or -1

max_version = max(latestVersion, production_version,
staging_version)

if production_version == -1 and staging_version == -1:
# if the max version has not been activated yet
# we just reuse the max version
LOG.info("New version for : %s is %s" % (self.property_id,
str(max_version)))
return max_version
else:
# else now we need to create a new version (bump up a 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' % (
self.property_id, str(max_version)))
)
if resp.status_code != 200:
raise RuntimeError('PAPI API request failed.'
'Exception: %s' % resp.text)
etag = json.loads(resp.text)['versions']['items'][0]['etag']
# create a new version
resp = self.akamai_driver.akamai_papi_api_client.post(
self.akamai_driver.akamai_papi_api_base_url.format(
middle_part='properties/%s/versions' % (
self.property_id)),
data=json.dumps({
'createFromVersion': max_version,
'createFromEtag': etag
}),
headers={'Content-type': 'application/json'}
)

if resp.status_code != 201:
raise RuntimeError('PAPI API request failed.'
'Exception: %s' % resp.text)
LOG.info("New version for : %s is %s" % (self.property_id,
str(max_version+1)))
return max_version + 1


class PropertyUpdateTask(task.Task):
default_provides = 'update_detail'

def __init__(self):
super(PropertyUpdateTask, self).__init__()
service_controller, self.providers = \
memoized_controllers.task_controllers('poppy', 'providers')
self.akamai_driver = self.providers['akamai'].obj
self.sc = self.akamai_driver.service_controller
self.akamai_conf = self.akamai_driver.akamai_conf

self.existing_hosts = []
self.existing_edgehostnames = []

def execute(self, property_spec, new_version_number, update_type,
update_info_list):
"""Update an Akamai property"""
self.property_id = self.akamai_driver.papi_property_id(property_spec)

update_info_list = json.loads(update_info_list)
update_detail = ""

# avoid loading hostnames multiple times
if update_type == 'hostnames':
if self.existing_hosts == []:
LOG.info("Getting Hostnames...")
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(new_version_number)))
)
if resp.status_code != 200:
raise RuntimeError('PAPI API request failed.'
'Exception: %s' % resp.text)
self.existing_hosts = json.loads(resp.text)['hostnames'][
'items']
# message should be a list assembled hosts dictionary
for action, host_info in update_info_list:
# add new hosts
if action == 'add':
cnameToEdgeHostname = host_info['cnameTo']
# avoid loading edgehostnames multiple times
if self.existing_edgehostnames == []:
LOG.info("Getting EdgeHostnames...")
resp = self.akamai_driver.akamai_papi_api_client.\
get(
self.akamai_driver.
akamai_papi_api_base_url.format(
middle_part='edgehostnames')
)

if resp.status_code != 200:
raise RuntimeError('PAPI API request failed.'
'Exception: %s' % resp.text)
self.existing_edgehostnames = (
json.loads(resp.text)['edgeHostnames']['items']
)

for edgehostname in self.existing_edgehostnames:
if (edgehostname['domainPrefix'] ==
cnameToEdgeHostname.replace(
edgehostname['domainSuffix'], "")[:-1]):
host_info['edgeHostnameId'] = (
edgehostname['edgeHostnameId'])

self.existing_hosts.append(host_info)
update_detail = "Add cnameFrom: %s to cnameTo %s" % (
host_info['cnameFrom'], host_info['cnameTo'])
# remove a hosts
elif action == 'remove':
for idx, existing_host_info in enumerate(
self.existing_hosts):
if existing_host_info['cnameFrom'] == (
host_info['cnameFrom']):
del self.existing_hosts[idx]
break
update_detail = ("Remove cnameFrom: %s to cnameTo %s"
% (host_info['cnameFrom'],
host_info['cnameTo']))

LOG.info('Start Updating Hostnames: %s' %
str(self.existing_hosts))
resp = self.akamai_driver.akamai_papi_api_client.put(
self.akamai_driver.akamai_papi_api_base_url.format(
middle_part='properties/%s/versions/%s/hostnames' % (
self.property_id,
str(new_version_number))),
data=json.dumps(self.existing_hosts),
headers={'Content-type': 'application/json'}
)

if resp.status_code != 200:
LOG.info("Updating property hostnames response code: %s" %
str(resp.status_code))
LOG.info("Updating property hostnames response text: %s" %
str(resp.text))
else:
LOG.info("Update property hostnames successful...")
# Handle secureEdgeHost addition
elif update_type == 'secureEdgeHost':
# Note(tonytan4ever): This will be used when adding custom cert
pass
elif update_type == 'rules':
pass

return update_detail


class PropertyActivateTask(task.Task):

def __init__(self):
super(PropertyActivateTask, self).__init__()
service_controller, self.providers = \
memoized_controllers.task_controllers('poppy', 'providers')
self.akamai_driver = self.providers['akamai'].obj
self.akamai_conf = self.akamai_driver.akamai_conf
self.sc = self.akamai_driver.service_controller

def execute(self, property_spec, new_version_number, update_detail,
notify_email_list=[]):
"""Update an Akamai property"""
self.property_id = self.akamai_driver.papi_property_id(property_spec)

# This request needs json
LOG.info('Starting activating version: %s for property: %s' %
(new_version_number,
self.property_id))
data = {
'propertyVersion': new_version_number,
'network': 'PRODUCTION',
'note': 'Updating configuration for property %s: %s' % (
self.property_id, update_detail),
'notifyEmails': notify_email_list,
}
resp = self.akamai_driver.akamai_papi_api_client.post(
self.akamai_driver.akamai_papi_api_base_url.format(
middle_part='properties/%s/activations' %
self.property_id),
data=json.dumps(data),
headers={'Content-type': 'application/json'}
)
# Here activation API call will return a 400,
# with all the messageIds need to handle that not as
# an exception
if resp.status_code != 201 and resp.status_code != 400:
raise RuntimeError('PAPI API request failed.'
'Exception: %s' % resp.text)
# else extract out all the warnings
# acknowledgementWarning
if resp.status_code == 400:
LOG.info("response text: %s" % resp.text)
warnings = [warning['messageId'] for warning in
json.loads(resp.text)['warnings']]
data['acknowledgeWarnings'] = warnings
resp = self.akamai_driver.akamai_papi_api_client.post(
self.akamai_driver.akamai_papi_api_base_url.format(
middle_part='properties/%s/activations/' %
self.property_id),
data=json.dumps(data),
headers={'Content-type': 'application/json'}
)
if resp.status_code != 201:
raise RuntimeError('PAPI API request failed.'
'Exception: %s' % resp.text)

# first get the activation id
# activation id is inside of activation link itself
activation_link = json.loads(resp.text)['activationLink']
LOG.info("Activation link: %s" % activation_link)

return {
"activation_link": activation_link
}

def revert(self, property_spec, new_version_number, **kwargs):
LOG.info('retrying task: %s ...' % self.name)

+ 27
- 4
poppy/provider/akamai/driver.py View File

@@ -102,14 +102,18 @@ AKAMAI_OPTIONS = [
cfg.StrOpt(
'group_id',
help='Operator groupID'),
cfg.StrOpt(
'property_id',
help='Operator propertyID')
]

AKAMAI_GROUP = 'drivers:provider:akamai'


VALID_PROPERTY_SPEC = [
"akamai_http_config_number",
"akamai_https_shared_config_number",
"akamai_https_san_config_numbers",
"akamai_https_custom_config_numbers"]


class CDNProvider(base.Driver):

def __init__(self, conf):
@@ -164,11 +168,20 @@ class CDNProvider(base.Driver):
)
])

self.akamai_sps_api_client = self.akamai_policy_api_client
self.akamai_papi_api_base_url = ''.join([
str(self.akamai_conf.policy_api_base_url),
'papi/v0/{middle_part}/'
'?contractId=ctr_%s&groupId=grp_%s' % (
self.akamai_conf.contract_id,
self.akamai_conf.group_id)
])

self.san_cert_cnames = self.akamai_conf.san_cert_cnames
self.san_cert_hostname_limit = self.akamai_conf.san_cert_hostname_limit

self.akamai_sps_api_client = self.akamai_policy_api_client
self.akamai_papi_api_client = self.akamai_policy_api_client

self.mod_san_queue = (
zookeeper_queue.ZookeeperModSanQueue(self._conf))

@@ -229,6 +242,16 @@ class CDNProvider(base.Driver):
def papi_api_client(self):
return self.akamai_papi_api_client

def papi_property_id(self, property_spec):
if property_spec not in VALID_PROPERTY_SPEC:
raise ValueError('No a valid property spec: %s'
', valid property specs are: %s'
% (property_spec, VALID_PROPERTY_SPEC))
prp_number = self.akamai_conf[property_spec]
if isinstance(prp_number, list):
prp_number = prp_number[0]
return 'prp_%s' % self.akamai_conf[property_spec][0]

@property
def service_controller(self):
"""Returns the driver's hostname controller."""


+ 4
- 1
poppy/storage/cassandra/services.py View File

@@ -892,10 +892,13 @@ class ServicesController(base.ServicesController):
CQL_SEARCH_BY_DOMAIN,
consistency_level=self._driver.consistency_level)
results = self.session.execute(stmt, args)
# If there is not service with this domain
# return None
details = None
for r in results:
proj_id = r.get('project_id')
service = r.get('service_id')
details = self.get(proj_id, service)
details = self.get(proj_id, service)
return details

def update_provider_details(self, project_id, service_id,


+ 27
- 1
poppy/storage/mockdb/services.py View File

@@ -14,11 +14,13 @@
# limitations under the License.

import json
import random

from poppy.model.helpers import domain
from poppy.model.helpers import origin
from poppy.model.helpers import provider_details
from poppy.model import service
from poppy.model import ssl_certificate
from poppy.storage import base


@@ -166,6 +168,9 @@ class ServicesController(base.ServicesController):
if key in self.certs:
self.certs[key].cert_details = cert_details

def get_service_details_by_domain_name(self, domain_name):
pass

def create_cert(self, project_id, cert_obj):
key = (cert_obj.flavor_id, cert_obj.domain_name, cert_obj.cert_type)
if key not in self.certs:
@@ -173,12 +178,33 @@ class ServicesController(base.ServicesController):
else:
raise ValueError

def get_certs_by_domain(self, domain_name, project_id=None):
def get_certs_by_domain(self, domain_name, project_id=None, flavor_id=None,
cert_type=None):
certs = []
for cert in self.certs:
if domain_name in cert:
certs.append(self.certs[cert])
if project_id:
if flavor_id is not None and cert_type is not None:
return ssl_certificate.SSLCertificate(
"premium",
"blog.testabcd.com",
"san",
project_id=project_id,
cert_details={
'Akamai': {
u'cert_domain': u'secure2.san1.test_123.com',
u'extra_info': {
u'action': u'Waiting for customer domain '
'validation for blog.testabc.com',
u'akamai_spsId': str(random.randint(1, 100000)
),
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 [cert for cert in certs if cert.project_id == project_id]
else:
return certs


+ 31
- 0
poppy/transport/pecan/controllers/v1/admin.py View File

@@ -22,6 +22,7 @@ from poppy.transport.pecan.controllers import base
from poppy.transport.pecan import hooks as poppy_hooks
from poppy.transport.pecan.models.response import service as resp_service_model
from poppy.transport.validators import helpers
from poppy.transport.validators.schemas import background_jobs
from poppy.transport.validators.schemas import domain_migration
from poppy.transport.validators.schemas import service_action
from poppy.transport.validators.schemas import service_limit
@@ -73,10 +74,40 @@ class DomainMigrationController(base.Controller, hooks.HookController):
return pecan.Response(None, 202)


class BackgroundJobController(base.Controller, hooks.HookController):
__hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()]

def __init__(self, driver):
super(BackgroundJobController, self).__init__(driver)

@pecan.expose('json')
@decorators.validate(
request=rule.Rule(
helpers.json_matches_service_schema(
background_jobs.BackgroundJobSchema.get_schema(
"background_jobs", "POST")),
helpers.abort_with_message,
stoplight_helpers.pecan_getter))
def post(self):
request_json = json.loads(pecan.request.body.decode('utf-8'))
job_type = request_json.pop('job_type')

try:
self._driver.manager.background_job_controller.post_job(
job_type,
request_json
)
except NotImplementedError as e:
pecan.abort(404, str(e))

return pecan.Response(None, 202)


class AkamaiController(base.Controller, hooks.HookController):
def __init__(self, driver):
super(AkamaiController, self).__init__(driver)
self.__class__.service = DomainMigrationController(driver)
self.__class__.background_job = BackgroundJobController(driver)


class ProviderController(base.Controller, hooks.HookController):


+ 4
- 1
poppy/transport/pecan/models/request/ssl_certificate.py View File

@@ -20,5 +20,8 @@ def load_from_json(json_data):
flavor_id = json_data.get("flavor_id")
domain_name = json_data.get("domain_name")
cert_type = json_data.get("cert_type")
project_id = json_data.get("project_id")
cert_details = json_data.get("cert_details", {})

return ssl_certificate.SSLCertificate(flavor_id, domain_name, cert_type)
return ssl_certificate.SSLCertificate(flavor_id, domain_name,
cert_type, project_id, cert_details)

+ 1
- 0
poppy/transport/pecan/models/response/service.py View File

@@ -108,6 +108,7 @@ class Model(collections.OrderedDict):
continue
except StopIteration:
pass

if 'operator_url' in access_url:
self['links'].append(link.Model(
access_url['operator_url'],


+ 88
- 0
poppy/transport/validators/schemas/background_jobs.py View File

@@ -0,0 +1,88 @@
# 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.

from poppy.transport.validators import schema_base


class BackgroundJobSchema(schema_base.SchemaBase):

'''JSON Schmema validation for /admin/provider/akamai/background_jobs'''

schema = {
'background_jobs': {
'POST': {
'type': [{
'additionalProperties': False,
'properties': {
'job_type': {
'type': 'string',
'required': True,
'enum': ['akamai_check_and_update_cert_status']
},
'domain_name': {
'type': 'string',
'required': True
},
'project_id': {
'type': 'string',
'required': True
},
'cert_type': {
'type': 'string',
'required': True,
'enum': ['san']
},
'flavor_id': {
'type': 'string',
'required': True
}
}
},
{
'additionalProperties': False,
'properties': {
'job_type': {
'type': 'string',
'required': True,
'enum': ['akamai_update_papi_property_for_mod_san']
},
'domain_name': {
'type': 'string',
'required': True
},
'san_cert_name': {
'type': 'string',
'required': True
},
'update_type': {
'type': 'string',
'enum': ['hostsnames']
},
'action': {
'type': 'string',
'enum': ['add', 'remove']
},
'property_spec': {
'type': 'string',
'enum': ['akamai_https_san_config_numbers']
},
'san_cert_domain_suffix': {
'type': 'string'
}
}
}]
}
}
}

+ 2
- 0
setup.cfg View File

@@ -34,6 +34,8 @@ upload-dir = doc/build/html
console_scripts =
poppy-server = poppy.cmd.server:run
poppy-worker = poppy.cmd.task_flow_worker:run
akamai-cert-status-check-and-update = poppy.cmd.akamai_check_and_update_cert_status:run
akamai-property-udpate-mod-san = poppy.cmd.akamai_update_papi_property_for_mod_san:run

poppy.transport =
pecan = poppy.transport.pecan:Driver


+ 8
- 1
tests/etc/default_functional.conf View File

@@ -1,9 +1,10 @@
[drivers]
providers = mock,maxcdn,cloudfront,fastly
providers = mock,maxcdn,akamai,fastly
transport = pecan
manager = default
storage = mockdb
dns = default
notifications = mailgun

[drivers:storage:cassandra]
cluster = "192.168.59.103"
@@ -18,9 +19,15 @@ alias = "MYALIAS"
consumer_secret = "MYCONSUMER_SECRET"
consumer_key = "MYCONSUMERKEY"

[drivers:provider:akamai]
akamai_https_access_url_suffix = "my_https_url_suffix"

[drivers:provider:cloudfront]
aws_access_key_id = "MY_AWS_ACCESS_KEY_ID"
aws_secret_access_key = "MY_AWS_SECRET_ACCESS_KEY"

[drivers:notification:mailgun]
recipients = "myrecipients@abc.com"

[drivers:transport:limits]
max_services_per_page = 20

+ 14
- 0
tests/functional/transport/pecan/controllers/data_post_background_jobs.json View File

@@ -0,0 +1,14 @@
{
"akamai_check_and_update_cert_status": {
"job_type": "akamai_check_and_update_cert_status",
"domain_name": "www.abc.com",
"flavor_id": "mock",
"cert_type": "san",
"project_id": "000"
},
"akamai_update_papi_property_for_mod_san": {
"job_type": "akamai_update_papi_property_for_mod_san",
"domain_name": "www.abc.com",
"san_cert_name": "secure1.test_san.com"
}
}

+ 22
- 0
tests/functional/transport/pecan/controllers/data_post_background_jobs_bad_input.json View File

@@ -0,0 +1,22 @@
{
"invalid_job_type_name": {
"job_type": "nonsense",
"flavor_id": "mock"
},
"missing_cert_type": {
"job_type": "akamai_check_and_update_cert_status",
"domain_name": "www.abc.com",
"flavor_id": "mock",
"project_id": "000"
},
"missing_domain_name": {
"job_type": "akamai_check_and_update_cert_status",
"cert_type": "san",
"flavor_id": "mock",
"project_id": "000"
},
"missing_san_cert_name": {
"job_type": "akamai_check_and_update_cert_status",
"domain_name": "www.abc.com"
}
}

+ 51
- 0
tests/functional/transport/pecan/controllers/test_background_jobs.py View File

@@ -0,0 +1,51 @@
# 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 json
import uuid

import ddt

from tests.functional.transport.pecan import base


@ddt.ddt
class BackgroundJobControllerTest(base.FunctionalTest):

def setUp(self):
super(BackgroundJobControllerTest, self).setUp()

self.project_id = str(uuid.uuid1())
self.service_name = str(uuid.uuid1())
self.flavor_id = str(uuid.uuid1())

@ddt.file_data("data_post_background_jobs_bad_input.json")
def test_post_background_job_negative(self, background_job_json):
response = self.app.post('/v1.0/admin/provider/akamai/background_job',
headers={'Content-Type': 'application/json',
'X-Project-ID': self.project_id},
params=json.dumps(background_job_json),
expect_errors=True)

self.assertEqual(400, response.status_code)

@ddt.file_data("data_post_background_jobs.json")
def test_post_background_job_positive(self, background_job_json):
response = self.app.post('/v1.0/admin/provider/akamai/background_job',
headers={'Content-Type': 'application/json',
'X-Project-ID': self.project_id},
params=json.dumps(background_job_json))

self.assertEqual(202, response.status_code)

+ 1
- 1
tests/functional/transport/pecan/controllers/test_ssl_certificate.py View File

@@ -1,4 +1,4 @@
# Copyright (c) 2014 Rackspace, Inc.
# 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.


+ 0
- 0
tests/unit/provider/akamai/background_jobs/__init__.py View File


+ 321
- 0
tests/unit/provider/akamai/background_jobs/akamai_mocks.py View File

@@ -0,0 +1,321 @@
# 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 random
import uuid

import json
import mock

from poppy.model.helpers import domain
from poppy.model.helpers import origin
from poppy.model import service
from poppy.model import ssl_certificate


class MockBootStrap(mock.Mock):

def __init__(self, conf):
super(MockBootStrap, self).__init__()

@property
def manager(self):
return MockManager()


class MockProviderDetails(mock.Mock):

def __init__(self, service_id):
super(MockProviderDetails, self).__init__()
self.service_id = service_id

def __getitem__(self, provider_name):
akamai_provider_detail_mock = mock.Mock()
akamai_provider_detail_mock.provider_service_id = ''.join([
'[{"protocol":',
' "https", "certificate": ',
'"san", "policy_name": "blog.testabc.com"}]'])
akamai_provider_detail_mock.status = 'deployed'
return akamai_provider_detail_mock


class MockManager(mock.Mock):
def __init__(self):
super(MockManager, self).__init__()

@property
def providers(self):
akamai_mock_provider = mock.Mock()
akamai_mock_provider_obj = mock.Mock()
akamai_mock_provider_obj.service_controller = mock.Mock()
akamai_mock_provider_obj.akamai_conf = {
'property_id': 'prp_12345',
'contract_id': "B-ABCDE",
'group_id': 12345
}
akamai_mock_provider_obj.akamai_sps_api_client = MockSPSAPIClient()
akamai_mock_provider_obj.akamai_papi_api_client = MockPapiAPIClient()
akamai_mock_provider_obj.akamai_sps_api_base_url = (
'https://mybaseurl.net/config-secure-provisioning-service/'
'v1/sps-requests/{spsId}?'
'contractId=None&groupId=None')
akamai_mock_provider_obj.akamai_papi_api_base_url = (
'https://mybaseurl.net/papi/v0/{middle_part}/'
'?contractId=ctr_None&groupId=grp_None')
akamai_mock_provider.obj = akamai_mock_provider_obj
providers = {
'akamai': akamai_mock_provider,
}
return providers

@property
def services_controller(self):
sc = mock.Mock()
sc.storage_controller = MockStorageController()
return sc


class MockStorageController(mock.Mock):

def get_certs_by_domain(self, domain_name, project_id=None,
flavor_id=None,
cert_type=None):

return ssl_certificate.SSLCertificate(
"premium",
"blog.testabcd.com",
"san",
project_id=project_id,
cert_details={
'Akamai': {
u'cert_domain': u'secure2.san1.test_123.com',
u'extra_info': {
u'action': u'Waiting for customer domain '
'validation for blog.testabc.com',
u'akamai_spsId': str(random.randint(1, 100000)),
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'}
}
}
)

def get_service_details_by_domain_name(self, domain_name):
r = service.Service(
str(uuid.uuid4()),
str(uuid.uuid4()),
[domain.Domain('wiki.cc', 'https', 'shared')],
[origin.Origin('mysite.com')],
"strawberry")
r.provider_details = MockProviderDetails(r.service_id)
return r


class MockPapiAPIClient(mock.Mock):
def __init__(self):
super(MockPapiAPIClient, self).__init__()
self.response_200 = mock.Mock(status_code=200)

def get(self, url):
if 'hostnames' in url:
self.response_200.text = json.dumps({
"accountId": "act_1-ABCDE",
"contractId": "B-ABCDE",
"groupId": "grp_12345",
"propertyId": "prp_12345",
"propertyName": "ssl.san.test_123.com_pm",
"propertyVersion": 2,
"etag": str(uuid.uuid4()),
"hostnames": {
"items": [{
"cnameType": "EDGE_HOSTNAME",
"edgeHostnameId": "ehn_1052022",
"cnameFrom": "www.testxxx.com",
"cnameTo": "ssl.test_123.com.edge_host_test.net"
}, {
"cnameType": "EDGE_HOSTNAME",
"edgeHostnameId": "ehn_1126816",
"cnameFrom": "secure.san2.test_789.com",
"cnameTo": "secure.test_456.com.edge_host_test.net"
}]
}
})
if 'edgehostnames' in url:
self.response_200.text = json.dumps({
"accountId": "act_1-ABCDE",
"contractId": "B-ABCDE",
"groupId": "grp_12345",
"edgeHostnames": {
"items": [{
"cnameType": "EDGE_HOSTNAME",
"edgeHostnameId": "ehn_1052022",
"domainPrefix": "secure1.san1.test_123.com",
"domainSuffix": "edge_host_test.net",
"ipVersionBehavior": "IPV4",
"secure": True,
"edgeHostnameDomain": "secure1.san1.test_123.com"
".edge_host_test.net"
}, {
"cnameType": "EDGE_HOSTNAME",
"edgeHostnameId": "ehn_1159587",
"domainPrefix": "secure2.san1.test_123.com",
"domainSuffix": "edge_host_test.net",
"ipVersionBehavior": "IPV4",
"secure": True,
"edgeHostnameDomain": "secure2.san1.test_123.com"
".edge_host_test.net"
}]
}
})
if 'activations' in url:
self.response_200.text = json.dumps({
"activationId": "atv_2511473",
"status": "SUCCESS"
})
if 'versions' in url:
self.response_200.text = json.dumps({
"propertyId": "prp_12345",
"propertyName": "secure.test_123.com_pm",
"accountId": "act_1-ABCDE",
"contractId": "B-ABCDE",
"groupId": "grp_12345",
"versions": {
"items": [{
"propertyVersion": 1,
"etag": str(uuid.uuid4())
}]
}
})
else:
self.response_200.text = json.dumps({
"properties": {
"items": [{
"accountId": "act_1-ABCDE",
"contractId": "B-ABCDE",
"groupId": "grp_12345",
"propertyId": "prp_12345",
"propertyName": "secure.test_123.com_pm",
"latestVersion": 2,
"stagingVersion": 2,
"productionVersion": 1
}]
}
})
self.response_200.status_code = 200
return self.response_200

def post(self, url, data=None, headers=None):
if 'activations' in url:
self.response_200.status_code = 201
self.response_200.text = json.dumps({
"activationLink": "/papi/v0/properties/prp_227429/"
"activations/atv_2511473?contractId"
"=ctr_C-2M6JYA&groupId=grp_12345",
'warnings': []
})
if 'versions' in url:
self.response_200.status_code = 201
return self.response_200

def put(self, url, data=None, headers=None):
if 'hostnames' in url:
self.response_200.text = json.dumps({
"accountId": "act_1-ABCDE",
"contractId": "B-ABCDE",
"groupId": "grp_12345",
"propertyId": "prp_12345",
"propertyName": "ssl.san.test_123.com_pm",
"propertyVersion": 2,
"etag": str(uuid.uuid4()),
"hostnames": {
"items": [{
"cnameType": "EDGE_HOSTNAME",
"edgeHostnameId": "ehn_1052022",
"cnameFrom": "secure.san1.test_789.com",
"cnameTo": "ssl.test_123.com.edge_host_test.net"
}, {
"cnameType": "EDGE_HOSTNAME",
"edgeHostnameId": "ehn_1126816",
"cnameFrom": "secure.san2.test_789.com",
"cnameTo": "secure.test_456.com.edge_host_test.net"
}, {
'cnameTo': 'secure.test_7891.com.edge_host_test.net',
'cnameFrom': 'www.blogyyy.com',
'edgeHostnameId': 'ehn_1126816',
'cnameType': u'EDGE_HOSTNAME'
}, {
'cnameTo': u'secure.test_7891.com.edge_host_test.net',
'cnameFrom': u'www.testxxx.com',
'edgeHostnameId': u'ehn_1126816',
'cnameType': u'EDGE_HOSTNAME'
}]
}
})
return self.response_200


class MockSPSAPIClient(mock.Mock):
def __init__(self):
super(MockSPSAPIClient, self).__init__()
self.response_200 = mock.Mock(status_code=200)

def get(self, url):
self.response_200.text = json.dumps({
"requestList":
[{"resourceUrl": "/config-secure-provisioning-service/"
"v1/sps-requests/1849",
"parameters": [{
"name": "cnameHostname",
"value": "secure.san3.test_123.com"
}, {"name": "createType", "value": "san"},
{"name": "csr.cn",
"value": "secure.san3.test_123.com"},
{"name": "csr.c", "value": "US"},
{"name": "csr.st", "value": "TX"},
{"name": "csr.l", "value": "San Antonio"},
{"name": "csr.o", "value": "Rackspace US Inc."},
{"name": "csr.ou", "value": "IT"},
{"name": "csr.sans",
"value": "secure.san3.test_123.com"},
{"name": "organization-information.organization-name",
"value": "Rackspace US Inc."},
{"name": "organization-information.address-line-one",
"value": "1 Fanatical Place"},
{"name": "organization-information.city",
"value": "San Antonio"}],
"lastStatusChange": "2015-03-19T21:47:10Z",
"spsId": random.randint(1, 10000),
"status": "SPS Request Complete",
"jobId": random.randint(1, 100000)}]})
self.response_200.status_code = 200
return self.response_200

def post(self, url, data=None, headers=None):
self.response_200.status_code = 202
self.response_200.text = json.dumps({
"spsId": 1789,
"resourceLocation":
"/config-secure-provisioning-service/v1/sps-requests/1856",
"Results": {
"size": 1,
"data": [{
"text": None,
"results": {
"type": "SUCCESS",
"jobID": 44434}
}]}})
return self.response_200

def put(self, url, data=None, headers=None):
return self.response_200

+ 66
- 0
tests/unit/provider/akamai/background_jobs/test_flows.py View File

@@ -0,0 +1,66 @@
# 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 json

import mock
from taskflow import engines

from poppy.provider.akamai.background_jobs.check_cert_status_and_update \
import check_cert_status_and_update_flow
from poppy.provider.akamai.background_jobs.update_property import (
update_property_flow)
from tests.unit import base
from tests.unit.provider.akamai.background_jobs import akamai_mocks


class TestAkamaiBJFlowRuns(base.TestCase):

def setUp(self):
super(TestAkamaiBJFlowRuns, self).setUp()

bootstrap_patcher = mock.patch(
'poppy.bootstrap.Bootstrap',
new=akamai_mocks.MockBootStrap
)
bootstrap_patcher.start()
self.addCleanup(bootstrap_patcher.stop)

def test_check_cert_status_and_update_flow(self):
kwargs = {
'domain_name': "blog.testabc.com",
'cert_type': "san",
'flavor_id': "premium",
'project_id': "000"
}
engines.run(check_cert_status_and_update_flow.
check_cert_status_and_update_flow(),
store=kwargs)

def test_update_papi_flow(self):
kwargs = {
"property_spec": "akamai_https_san_config_numbers",
"update_type": "hostnames",
"update_info_list": json.dumps([
(
"add",
{
"cnameFrome": "blog.testabc.com",
"cnameTo": 'secure2.san1.test_cdn.com',
"cnameType": "EDGE_HOSTNAME"
}
)])
}
engines.run(update_property_flow.update_property_flow(),
store=kwargs)

tests/unit/storage/cassandra/data_get_cert_by_domain.json → tests/unit/storage/cassandra/data_get_certs_by_domain.json View File


+ 3
- 3
tests/unit/storage/cassandra/test_services.py View File

@@ -180,11 +180,11 @@ 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')
@ddt.file_data('data_get_certs_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):
def test_get_certs_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(


Loading…
Cancel
Save