diff --git a/etc/poppy.conf b/etc/poppy.conf index 28427876..8e6e8c8e 100644 --- a/etc/poppy.conf +++ b/etc/poppy.conf @@ -71,13 +71,16 @@ replication_strategy = class:SimpleStrategy, replication_factor:1 [drivers:storage:mockdb] database = poppy + [drivers:dns:rackspace] project_id = "" api_key = "" use_shards = True -num_shards = 500 -shard_prefix = "cdn_" +num_shards = 499 +shard_prefix = "cdn" url = "poppycdn.net" +# You email associated with DNS, for notifications +email = "your@email.com" [drivers:provider:fastly] apikey = "MYAPIKEY" diff --git a/poppy/dns/base/__init__.py b/poppy/dns/base/__init__.py index 0d4436b7..fe2d4db2 100644 --- a/poppy/dns/base/__init__.py +++ b/poppy/dns/base/__init__.py @@ -18,4 +18,4 @@ from poppy.dns.base import services Driver = driver.DNSDriverBase -ServiceBase = services.ServicesControllerBase +ServicesBase = services.ServicesControllerBase diff --git a/poppy/dns/base/driver.py b/poppy/dns/base/driver.py index 899b3ade..b683b0fe 100644 --- a/poppy/dns/base/driver.py +++ b/poppy/dns/base/driver.py @@ -43,14 +43,29 @@ class DNSDriverBase(object): :raises NotImplementedError """ + raise NotImplementedError @abc.abstractproperty def dns_name(self): + """Name of this provider. + + :raises NotImplementedError + """ + + raise NotImplementedError + + @property + def client(self): + """Client for this provider. + + :raises NotImplementedError + """ + raise NotImplementedError @abc.abstractproperty - def service_controller(self): + def services_controller(self): """Returns the driver's hostname controller. :raises NotImplementedError diff --git a/poppy/dns/base/responder.py b/poppy/dns/base/responder.py new file mode 100644 index 00000000..8dcbd024 --- /dev/null +++ b/poppy/dns/base/responder.py @@ -0,0 +1,62 @@ +# 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 traceback + + +class Responder(object): + """Responder Class.""" + + def __init__(self, dns_name): + self.dns = dns_name + + def failed(self, msg): + """failed. + + :param msg + :returns {error, error details} + """ + + return { + 'error': msg, + 'error_detail': traceback.format_exc() + } + + def created(self, dns_details): + """created. + + :param dns_details + :returns dns_details + """ + + return dns_details + + def updated(self, dns_details): + """updated. + + :param dns_details + :returns dns_details + """ + + return dns_details + + def deleted(self, dns_details): + """deleted. + + :param dns_details + :returns dns_details + """ + + return dns_details diff --git a/poppy/dns/base/services.py b/poppy/dns/base/services.py index 76a7fdf0..7109f2a0 100644 --- a/poppy/dns/base/services.py +++ b/poppy/dns/base/services.py @@ -18,6 +18,7 @@ import abc import six from poppy.dns.base import controller +from poppy.dns.base import responder @six.add_metaclass(abc.ABCMeta) @@ -27,3 +28,34 @@ class ServicesControllerBase(controller.DNSControllerBase): def __init__(self, driver): super(ServicesControllerBase, self).__init__(driver) + + self.responder = responder.Responder(driver.dns_name) + + def update(self, service_old, service_updates, responders): + """update. + + :param service_old: previous service state + :param service_updates: updates to service state + :param responders: responders from providers + :raises NotImplementedError + """ + + raise NotImplementedError + + def delete(self, provider_details): + """delete. + + :param provider_details + :raises NotImplementedError + """ + + raise NotImplementedError + + def create(self, responders): + """create. + + :param responders + :raises NotImplementedError + """ + + raise NotImplementedError diff --git a/poppy/dns/default/controllers.py b/poppy/dns/default/controllers.py index 4e7d9454..49b10332 100644 --- a/poppy/dns/default/controllers.py +++ b/poppy/dns/default/controllers.py @@ -24,4 +24,4 @@ Field Mappings: from poppy.dns.default import services -ServiceController = services.ServiceController +ServicesController = services.ServicesController diff --git a/poppy/dns/default/driver.py b/poppy/dns/default/driver.py index 6934b27e..a9717337 100644 --- a/poppy/dns/default/driver.py +++ b/poppy/dns/default/driver.py @@ -23,17 +23,42 @@ LOG = logging.getLogger(__name__) class DNSProvider(base.Driver): + """Default DNS Provider.""" def __init__(self, conf): super(DNSProvider, self).__init__(conf) def is_alive(self): - return False + """is_alive. + + :return boolean + """ + + return True @property def dns_name(self): - return "Default" + """DNS provider name. + + :return 'Default' + """ + + return 'Default' @property - def service_controller(self): - return controllers.ServiceController(self) + def client(self): + """Client to this provider. + + :return None + """ + + return None + + @property + def services_controller(self): + """Hook for service controller. + + :return service_controller + """ + + return controllers.ServicesController(self) diff --git a/poppy/dns/default/services.py b/poppy/dns/default/services.py index 4d8e2cfe..89c657ce 100644 --- a/poppy/dns/default/services.py +++ b/poppy/dns/default/services.py @@ -16,9 +16,64 @@ from poppy.dns import base -class ServiceController(base.ServiceBase): +class ServicesController(base.ServicesBase): def __init__(self, driver): - super(ServiceController, self).__init__(driver) + super(ServicesController, self).__init__(driver) self.driver = driver + + def update(self, service_old, service_updates, responders): + """Default DNS update. + + :param service_old: previous service state + :param service_updates: updates to service state + :param responders: responders from providers + """ + + dns_details = {} + for responder in responders: + for provider_name in responder: + if 'error' in responder[provider_name]: + continue + access_urls = [] + for link in responder[provider_name]['links']: + access_url = { + 'domain': link['domain'], + 'provider_url': link['href'], + 'operator_url': link['href']} + access_urls.append(access_url) + dns_details[provider_name] = {'access_urls': access_urls} + return self.responder.created(dns_details) + + def delete(self, provider_details): + """Default DNS delete. + + :param provider_details + """ + + dns_details = {} + for provider_name in provider_details: + dns_details[provider_name] = self.responder.deleted({}) + return dns_details + + def create(self, responders): + """Default DNS create. + + :param responders: responders from providers + """ + + dns_details = {} + for responder in responders: + for provider_name in responder: + if 'error' in responder[provider_name]: + continue + access_urls = [] + for link in responder[provider_name]['links']: + access_url = { + 'domain': link['domain'], + 'provider_url': link['href'], + 'operator_url': link['href']} + access_urls.append(access_url) + dns_details[provider_name] = {'access_urls': access_urls} + return self.responder.created(dns_details) diff --git a/poppy/dns/designate/controllers.py b/poppy/dns/designate/controllers.py index 768ac450..27bec027 100644 --- a/poppy/dns/designate/controllers.py +++ b/poppy/dns/designate/controllers.py @@ -24,4 +24,4 @@ Field Mappings: from poppy.dns.designate import services -ServiceController = services.ServiceController +ServicesController = services.ServicesController diff --git a/poppy/dns/designate/services.py b/poppy/dns/designate/services.py index 4d8e2cfe..e1b22e15 100644 --- a/poppy/dns/designate/services.py +++ b/poppy/dns/designate/services.py @@ -16,9 +16,9 @@ from poppy.dns import base -class ServiceController(base.ServiceBase): +class ServicesController(base.ServicesBase): def __init__(self, driver): - super(ServiceController, self).__init__(driver) + super(ServicesController, self).__init__(driver) self.driver = driver diff --git a/poppy/dns/rackspace/controllers.py b/poppy/dns/rackspace/controllers.py index 46752d3e..a6763dd0 100644 --- a/poppy/dns/rackspace/controllers.py +++ b/poppy/dns/rackspace/controllers.py @@ -24,4 +24,4 @@ Field Mappings: from poppy.dns.rackspace import services -ServiceController = services.ServiceController +ServicesController = services.ServicesController diff --git a/poppy/dns/rackspace/driver.py b/poppy/dns/rackspace/driver.py index bad49330..e8ceeb2a 100644 --- a/poppy/dns/rackspace/driver.py +++ b/poppy/dns/rackspace/driver.py @@ -16,6 +16,7 @@ """DNS Provider implementation.""" from oslo.config import cfg +import pyrax from poppy.dns import base from poppy.dns.rackspace import controllers @@ -34,6 +35,8 @@ RACKSPACE_OPTIONS = [ help='The shard prefix to use'), cfg.StrOpt('url', default='', help='The url for customers to CNAME to'), + cfg.StrOpt('email', help='The email to be provided to Rackspace DNS for' + 'creating subdomains'), ] RACKSPACE_GROUP = 'drivers:dns:rackspace' @@ -42,17 +45,51 @@ LOG = logging.getLogger(__name__) class DNSProvider(base.Driver): + """Rackspace DNS Provider.""" def __init__(self, conf): super(DNSProvider, self).__init__(conf) + self._conf.register_opts(RACKSPACE_OPTIONS, group=RACKSPACE_GROUP) + self.rackdns_conf = self._conf[RACKSPACE_GROUP] + pyrax.set_setting("identity_type", "rackspace") + pyrax.set_credentials(self.rackdns_conf.project_id, + self.rackdns_conf.api_key) + self.rackdns_client = pyrax.cloud_dns + def is_alive(self): + """is_alive. + + :return boolean + """ + + # TODO(obulpathi): Implement health check + # and add DNS to health endpoint return True @property def dns_name(self): - return "Rackspace Cloud DNS" + """DNS provider name. + + :return 'Rackspace Cloud DNS' + """ + + return 'Rackspace Cloud DNS' @property - def service_controller(self): - return controllers.ServiceController(self) + def client(self): + """Client to this provider. + + :return client + """ + + return self.rackdns_client + + @property + def services_controller(self): + """Hook for service controller. + + :return service_controller + """ + + return controllers.ServicesController(self) diff --git a/poppy/dns/rackspace/services.py b/poppy/dns/rackspace/services.py index 4d8e2cfe..3f0257c8 100644 --- a/poppy/dns/rackspace/services.py +++ b/poppy/dns/rackspace/services.py @@ -13,12 +13,334 @@ # See the License for the specific language governing permissions and # limitations under the License. +import random +import sets + +import pyrax.exceptions as exc + from poppy.dns import base +from poppy.openstack.common import log + +LOG = log.getLogger(__name__) -class ServiceController(base.ServiceBase): +class ServicesController(base.ServicesBase): def __init__(self, driver): - super(ServiceController, self).__init__(driver) + super(ServicesController, self).__init__(driver) - self.driver = driver + self.client = driver.client + + def _get_subdomain(self, subdomain_name): + """Returns a subdomain, if it does not exist, create it + + :param subdomain_name + :return subdomain + """ + + try: + subdomain = self.client.find(name=subdomain_name) + except exc.NotFound: + subdomain = self.client.create( + name=subdomain_name, + emailAddress=self._driver.rackdns_conf.email, + ttl=900) + return subdomain + + def _create_cname_records(self, links): + """Creates a subdomain + + :param links: Access URLS from providers + :return dns_links: Map from provider access URL to DNS access URL + """ + + cdn_domain_name = self._driver.rackdns_conf.url + shard_prefix = self._driver.rackdns_conf.shard_prefix + num_shards = self._driver.rackdns_conf.num_shards + + # randomly select a shard + shard_id = random.randint(0, num_shards - 1) + subdomain_name = '{0}{1}.{2}'.format(shard_prefix, shard_id, + cdn_domain_name) + subdomain = self._get_subdomain(subdomain_name) + # create CNAME record for adding + cname_records = [] + dns_links = {} + for link in links: + name = '{0}.{1}'.format(link, subdomain_name) + cname_record = {'type': 'CNAME', + 'name': name, + 'data': links[link], + 'ttl': 300} + dns_links[links[link]] = name + cname_records.append(cname_record) + # add the cname records + subdomain.add_records(cname_records) + return dns_links + + def _delete_cname_record(self, access_url): + """Delete a CNAME record + + :param access_url: DNS Access URL + :return error_msg: returns error message, if any + """ + + # extract shard name + shard_name = access_url.split('.')[-3] + subdomain_name = '.'.join([shard_name, self._driver.rackdns_conf.url]) + # get subdomain + subdomain = self.client.find(name=subdomain_name) + # search and find the CNAME record + name = access_url + record_type = 'CNAME' + records = self.client.search_records(subdomain, record_type, name) + # delete the record + # we should get one record, + # or none if it has been deleted already + if not records: + LOG.info('DNS record already deleted: {0}'.format(access_url)) + elif len(records) == 1: + LOG.info('Deleting DNS records for : {0}'.format(access_url)) + records[0].delete() + elif len(records) > 1: + error_msg = 'Multiple DNS records found: {0}'.format(access_url) + return error_msg + return + + def create(self, responders): + """Create CNAME record for a service. + + :param responders: responders from providers + :return dns_links: Map from provider urls to DNS urls + """ + # gather the provider urls and cname them + links = {} + for responder in responders: + for provider_name in responder: + if 'error' in responder[provider_name]: + continue + for link in responder[provider_name]['links']: + if link['rel'] == 'access_url': + links[link['domain']] = link['href'] + + # create CNAME records + try: + dns_links = self._create_cname_records(links) + except Exception as e: + error_msg = 'Rackspace DNS Exception: {0}'.format(e) + LOG.error(error_msg) + return self.responder.failed(error_msg) + + # gather the CNAMED links + dns_details = {} + for responder in responders: + for provider_name in responder: + if 'error' in responder[provider_name]: + continue + access_urls = [] + for link in responder[provider_name]['links']: + if link['rel'] == 'access_url': + access_url = { + 'domain': link['domain'], + 'provider_url': link['href'], + 'operator_url': dns_links[link['href']]} + access_urls.append(access_url) + dns_details[provider_name] = {'access_urls': access_urls} + return self.responder.created(dns_details) + + def delete(self, provider_details): + """Delete CNAME records for a service. + + :param provider_details + :return dns_details: Map from provider_name to delete errors + """ + + dns_details = {} + for provider_name in provider_details: + error_msg = '' + access_urls = provider_details[provider_name].access_urls + for access_url in access_urls: + try: + msg = self._delete_cname_record(access_url['operator_url']) + if msg: + error_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 to CDN ' + 'subdomain {0}'.format(e)) + error_msg = (error_msg + 'Can not access subdomain . ' + 'Exception: {0}'.format(e)) + except Exception as e: + LOG.error('Exception: {0}'.format(e)) + error_msg = error_msg + 'Exception: {0}'.format(e) + # format the error or success message for this provider + if error_msg: + dns_details[provider_name] = self.responder.failed(error_msg) + else: + dns_details[provider_name] = self.responder.deleted({}) + return dns_details + + def _update_added_domains(self, responders, added_domains): + """Update added domains.""" + + # if no domains are added, return + dns_details = {} + if not added_domains: + for responder in responders: + for provider_name in responder: + dns_details[provider_name] = {'access_urls': {}} + return dns_details + + # gather the provider links for the added domains + links = {} + for responder in responders: + for provider_name in responder: + if 'error' in responder[provider_name]: + continue + for link in responder[provider_name]['links']: + domain_added = (link['rel'] == 'access_url' and + link['domain'] in added_domains) + if domain_added: + links[link['domain']] = link['href'] + + # create CNAME records for added domains + try: + dns_links = self._create_cname_records(links) + except Exception as e: + error_msg = 'Rackspace DNS Exception: {0}'.format(e) + LOG.error(error_msg) + return self.responder.failed(error_msg) + + # gather the CNAMED links for added domains + for responder in responders: + for provider_name in responder: + if 'error' in responder[provider_name]: + continue + access_urls = {} + for link in responder[provider_name]['links']: + if link['domain'] in added_domains: + access_urls[link['href']] = dns_links[link['href']] + dns_details[provider_name] = {'access_urls': access_urls} + return dns_details + + def _update_removed_domains(self, provider_details, removed_domains): + """Update removed domains.""" + + # if no domains are removed, return + dns_details = {} + if not removed_domains: + for provider_name in provider_details: + dns_details[provider_name] = {'access_urls': {}} + return dns_details + + # delete the records for deleted domains + for provider_name in provider_details: + error_msg = '' + provider_detail = provider_details[provider_name] + for access_url in provider_detail.access_urls: + if access_url['domain'] not in removed_domains: + continue + try: + msg = self._delete_cname_record(access_url['operator_url']) + if msg: + error_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 to CDN ' + 'subdomain {0}'.format(e)) + error_msg = (error_msg + 'Can not access subdomain. ' + 'Exception: {0}'.format(e)) + except Exception as e: + LOG.error('Exception: {0}'.format(e)) + error_msg = error_msg + 'Exception: {0}'.format(e) + # format the error or success message for this provider + if error_msg: + dns_details[provider_name] = self.responder.failed(error_msg) + else: + dns_details[provider_name] = self.responder.deleted({}) + return dns_details + + def update(self, service_old, service_updates, responders): + """Update CNAME records for a service. + + :param service_old: previous service state + :param service_updates: updates to service state + :param responders: responders from providers + + :return dns_details: Map from provider_name to update errors + """ + + # get old domains + old_domains = sets.Set() + old_access_urls_map = {} + provider_details = service_old.provider_details + for provider_name in provider_details: + provider_detail = provider_details[provider_name] + access_urls = provider_detail.access_urls + old_access_urls_map[provider_name] = {'access_urls': access_urls} + for access_url in access_urls: + old_domains.add(access_url['domain']) + + # get new_domains + new_domains = sets.Set() + for responder in responders: + for provider_name in responder: + links = responder[provider_name]['links'] + for link in links: + new_domains.add(link['domain']) + + # 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 + if old_domains == new_domains: + return old_access_urls_map + + # get the list of added, removed and common domains + added_domains = new_domains.difference(old_domains) + removed_domains = old_domains.difference(new_domains) + common_domains = new_domains.intersection(old_domains) + + # add new domains + dns_links = self._update_added_domains(responders, added_domains) + + # remove CNAME records for deleted domains + provider_details = service_old.provider_details + self._update_removed_domains(provider_details, removed_domains) + + # gather the CNAMED links and remove stale links + dns_details = {} + for responder in responders: + for provider_name in responder: + if 'error' in responder[provider_name]: + continue + provider_detail = service_old.provider_details[provider_name] + old_access_urls = provider_detail.access_urls + operator_urls = dns_links[provider_name]['access_urls'] + access_urls = [] + for link in responder[provider_name]['links']: + if link['domain'] in removed_domains: + continue + elif link['domain'] in added_domains: + operator_url = operator_urls[link['href']] + access_url = { + 'domain': link['domain'], + 'provider_url': link['href'], + 'operator_url': operator_url} + access_urls.append(access_url) + elif link['domain'] in common_domains: + # iterate through old access urls and get access url + operator_url = None + for old_access_url in old_access_urls: + if old_access_url['domain'] == link['domain']: + operator_url = old_access_url['operator_url'] + break + access_url = { + 'domain': link['domain'], + 'provider_url': link['href'], + 'operator_url': operator_url} + access_urls.append(access_url) + dns_details[provider_name] = {'access_urls': access_urls} + + return self.responder.updated(dns_details) diff --git a/poppy/manager/default/service_async_workers/create_service_worker.py b/poppy/manager/default/service_async_workers/create_service_worker.py index 249a8992..f9e82e16 100644 --- a/poppy/manager/default/service_async_workers/create_service_worker.py +++ b/poppy/manager/default/service_async_workers/create_service_worker.py @@ -29,29 +29,34 @@ def service_create_worker(providers_list, service_controller, service_obj) responders.append(responder) + # create dns mapping + dns = service_controller.dns_controller + dns_responder = dns.create(responders) + provider_details_dict = {} for responder in responders: for provider_name in responder: - if 'error' not in responder[provider_name]: + if 'error' in responder[provider_name]: + error_info = responder[provider_name]['error_detail'] + provider_details_dict[provider_name] = ( + provider_details.ProviderDetail(error_info=error_info)) + provider_details_dict[provider_name].status = 'failed' + elif 'error' in dns_responder[provider_name]: + error_info = dns_responder[provider_name]['error_detail'] + provider_details_dict[provider_name] = ( + provider_details.ProviderDetail(error_info=error_info)) + provider_details_dict[provider_name].status = 'failed' + else: + access_urls = dns_responder[provider_name]['access_urls'] provider_details_dict[provider_name] = ( provider_details.ProviderDetail( provider_service_id=responder[provider_name]['id'], - access_urls=[link['href'] for link in - responder[provider_name]['links']]) - ) + access_urls=access_urls)) if 'status' in responder[provider_name]: provider_details_dict[provider_name].status = ( responder[provider_name]['status']) else: - provider_details_dict[provider_name].status = ( - 'deployed') - else: - provider_details_dict[provider_name] = ( - provider_details.ProviderDetail( - error_info=responder[provider_name]['error_detail'] - ) - ) - provider_details_dict[provider_name].status = 'failed' + provider_details_dict[provider_name].status = 'deployed' service_controller.storage_controller.update_provider_details( project_id, diff --git a/poppy/manager/default/service_async_workers/delete_service_worker.py b/poppy/manager/default/service_async_workers/delete_service_worker.py index 409b0806..867c61d8 100644 --- a/poppy/manager/default/service_async_workers/delete_service_worker.py +++ b/poppy/manager/default/service_async_workers/delete_service_worker.py @@ -31,6 +31,9 @@ def service_delete_worker(provider_details, service_controller, responders.append(responder) LOG.info('Deleting service from %s complete...' % provider) + # delete associated cname records from DNS + dns_responder = service_controller.dns_controller.delete(provider_details) + for responder in responders: # this is the item of responder, if there's "error" # key in it, it means the deletion for this provider failed. @@ -43,13 +46,19 @@ def service_delete_worker(provider_details, service_controller, (provider_name, service_name)) # stores the error info for debugging purposes. provider_details[provider_name].error_info = ( - responder[provider_name].get('error_info') - ) + responder[provider_name].get('error_info')) + elif 'error' in dns_responder[provider_name]: + LOG.info('Delete service from DNS failed') + LOG.info('Updating provider detail status of %s for %s'.foramt( + (provider_name, service_name))) + # stores the error info for debugging purposes. + provider_details[provider_name].error_info = ( + dns_responder[provider_name].get('error_info')) else: # delete service successful, remove this provider detail record del provider_details[provider_name] - service_controller.storage_controller._driver.connect() + service_controller.storage_controller._driver.connect() if provider_details == {}: # Only if all provider successfully deleted we can delete # the poppy service. @@ -65,4 +74,4 @@ def service_delete_worker(provider_details, service_controller, service_controller.storage_controller.update_provider_details( project_id, service_name, - provider_details) \ No newline at end of file + provider_details) diff --git a/poppy/manager/default/service_async_workers/update_service_worker.py b/poppy/manager/default/service_async_workers/update_service_worker.py index b441fd95..bd731675 100644 --- a/poppy/manager/default/service_async_workers/update_service_worker.py +++ b/poppy/manager/default/service_async_workers/update_service_worker.py @@ -32,17 +32,20 @@ def update_worker(service_controller, project_id, service_name, responders.append(responder) LOG.info(u'Updating service from {0} complete'.format(provider)) + # create dns mapping + dns = service_controller.dns_controller + dns_responder = dns.update(service_old, service_updates, responders) + # gather links and status for service from providers provider_details_dict = {} for responder in responders: for provider_name in responder: if 'error' not in responder[provider_name]: + access_urls = dns_responder[provider_name]['access_urls'] provider_details_dict[provider_name] = ( provider_details.ProviderDetail( provider_service_id=responder[provider_name]['id'], - access_urls=[link['href'] for link in - responder[provider_name]['links']]) - ) + access_urls=access_urls)) if 'status' in responder[provider_name]: provider_details_dict[provider_name].status = ( responder[provider_name]['status']) diff --git a/poppy/manager/default/services.py b/poppy/manager/default/services.py index aa6c7fc0..39883cae 100644 --- a/poppy/manager/default/services.py +++ b/poppy/manager/default/services.py @@ -32,6 +32,7 @@ class DefaultServicesController(base.ServicesController): self.storage_controller = self._driver.storage.services_controller self.flavor_controller = self._driver.storage.flavors_controller + self.dns_controller = self._driver.dns.services_controller def _get_provider_details(self, project_id, service_name): try: @@ -86,6 +87,7 @@ class DefaultServicesController(base.ServicesController): raise e self.storage_controller._driver.close_connection() + p = multiprocessing.Process( name='Process: create poppy service %s for' ' project id: %s' % @@ -141,6 +143,7 @@ class DefaultServicesController(base.ServicesController): provider_details) self.storage_controller._driver.close_connection() + p = multiprocessing.Process( name=('Process: update poppy service {0} for project id: {1}' .format(service_name, project_id)), @@ -172,6 +175,7 @@ class DefaultServicesController(base.ServicesController): provider_details) self.storage_controller._driver.close_connection() + p = multiprocessing.Process( name='Process: delete poppy service %s for' ' project id: %s' % @@ -184,6 +188,7 @@ class DefaultServicesController(base.ServicesController): project_id, service_name)) p.start() + return def purge(self, project_id, service_name, purge_url=None): diff --git a/poppy/model/helpers/provider_details.py b/poppy/model/helpers/provider_details.py index 9039eb45..653272d4 100644 --- a/poppy/model/helpers/provider_details.py +++ b/poppy/model/helpers/provider_details.py @@ -26,7 +26,7 @@ class ProviderDetail(object): """ProviderDetail object for each provider.""" - def __init__(self, provider_service_id=None, access_urls=[], + def __init__(self, provider_service_id=None, access_urls={}, status=u"deploy_in_progress", name=None, error_info=None): self._provider_service_id = provider_service_id self._id = provider_service_id @@ -44,14 +44,14 @@ class ProviderDetail(object): def provider_service_id(self, value): self._provider_service_id = value - @property - def access_urls(self): - return self._access_urls - @property def name(self): return self._name + @property + def access_urls(self): + return self._access_urls + @access_urls.setter def access_urls(self, value): self._access_urls = value diff --git a/poppy/provider/akamai/services.py b/poppy/provider/akamai/services.py index 82ab3934..7ce63b1a 100644 --- a/poppy/provider/akamai/services.py +++ b/poppy/provider/akamai/services.py @@ -98,7 +98,8 @@ class ServiceController(base.ServiceBase): LOG.info('Creating policy %s on domain %s complete' % (dp, ','.join(classified_domain))) links.append({'href': self.driver.akamai_access_url_link, - "rel": 'access_url' + 'rel': 'access_url', + 'domain': service_obj.name }) except Exception: return self.responder.failed("failed to create service") @@ -279,7 +280,8 @@ class ServiceController(base.ServiceBase): LOG.info('Creating/Updateing policy %s on domain %s ' 'complete' % (dp, ','.join(classified_domain))) links.append({'href': self.driver.akamai_access_url_link, - 'rel': 'access_url' + 'rel': 'access_url', + 'domain': service_obj.name }) except Exception: return self.responder.failed("failed to update service") @@ -348,7 +350,9 @@ class ServiceController(base.ServiceBase): return self.responder.failed("failed to update service") ids = policies links.append({'href': self.driver.akamai_access_url_link, - 'rel': 'access_url'}) + 'rel': 'access_url', + 'domain': service_obj.name + }) return self.responder.updated(json.dumps(ids), links) def delete(self, provider_service_id): diff --git a/poppy/provider/fastly/services.py b/poppy/provider/fastly/services.py index 16d1058f..54278153 100644 --- a/poppy/provider/fastly/services.py +++ b/poppy/provider/fastly/services.py @@ -50,7 +50,8 @@ class ServiceController(base.ServiceBase): service_version.number) links = [{"href": '.'.join([domain_check.domain.name, "global.prod.fastly.net"]), - "rel": 'access_url'} + "rel": 'access_url', + "domain": domain_check.domain.name} for domain_check in domain_checks] for origin in service_obj.origins: diff --git a/poppy/provider/mock/services.py b/poppy/provider/mock/services.py index 9a57143a..217b02f4 100644 --- a/poppy/provider/mock/services.py +++ b/poppy/provider/mock/services.py @@ -37,6 +37,7 @@ class ServiceController(base.ServiceBase): service_id = uuid.uuid1() return self.responder.created(str(service_id), [{ "href": "www.mysite.com", + "domain": "www.mydomain.com", 'rel': "access_url"}]) def delete(self, provider_service_id): diff --git a/poppy/storage/cassandra/services.py b/poppy/storage/cassandra/services.py index 5fcbed4c..5b52b1f1 100644 --- a/poppy/storage/cassandra/services.py +++ b/poppy/storage/cassandra/services.py @@ -293,7 +293,7 @@ class ServicesController(base.ServicesController): provider_detail_dict = json.loads( provider_details_result[provider_name]) provider_service_id = provider_detail_dict.get('id', None) - access_urls = provider_detail_dict.get("access_urls", None) + access_urls = provider_detail_dict.get("access_urls", []) status = provider_detail_dict.get("status", u'creating') error_info = provider_detail_dict.get("error_info", None) provider_detail_obj = provider_details.ProviderDetail( diff --git a/poppy/storage/mockdb/services.py b/poppy/storage/mockdb/services.py index 82fffe06..f43f19ba 100644 --- a/poppy/storage/mockdb/services.py +++ b/poppy/storage/mockdb/services.py @@ -37,16 +37,17 @@ class ServicesController(base.ServicesController): provider_details = { 'MaxCDN': json.dumps( {'id': 11942, - 'access_urls': ['mypullzone.netdata.com']}), + 'access_urls': [{'operator_url': 'mypullzone.netdata.com'}]}), 'Mock': json.dumps( {'id': 73242, - 'access_urls': ['mycdn.mock.com']}), + 'access_urls': [{'operator_url': 'mycdn.mock.com'}]}), 'CloudFront': json.dumps( {'id': '5ABC892', - 'access_urls': ['cf123.cloudcf.com']}), + 'access_urls': [{'operator_url': 'cf123.cloudcf.com'}]}), 'Fastly': json.dumps( {'id': 3488, - 'access_urls': ['mockcf123.fastly.prod.com']})} + 'access_urls': + [{'operator_url': 'mockcf123.fastly.prod.com'}]})} services = [{'name': 'mockdb1_service_name', 'domains': [json.dumps({'domain': 'www.mywebsite.com'})], @@ -88,16 +89,17 @@ class ServicesController(base.ServicesController): provider_details = { 'MaxCDN': json.dumps( {'id': 11942, - 'access_urls': ['mypullzone.netdata.com']}), + 'access_urls': [{'operator_url': 'mypullzone.netdata.com'}]}), 'Mock': json.dumps( {'id': 73242, - 'access_urls': ['mycdn.mock.com']}), + 'access_urls': [{'operator_url': 'mycdn.mock.com'}]}), 'CloudFront': json.dumps( {'id': '5ABC892', - 'access_urls': ['cf123.cloudcf.com']}), + 'access_urls': [{'operator_url': 'cf123.cloudcf.com'}]}), 'Fastly': json.dumps( {'id': 3488, - 'access_urls': ['mockcf123.fastly.prod.com']})} + 'access_urls': + [{'operator_url': 'mockcf123.fastly.prod.com'}]})} service_dict = {'name': service_name, 'domains': [domain_json], diff --git a/poppy/transport/pecan/models/response/service.py b/poppy/transport/pecan/models/response/service.py index 4c72e37e..f36b2c0b 100644 --- a/poppy/transport/pecan/models/response/service.py +++ b/poppy/transport/pecan/models/response/service.py @@ -49,10 +49,10 @@ class Model(collections.OrderedDict): request.host_url, self['name']))), 'self')] - for provider_name in service_obj.provider_details: - for access_url in ( - service_obj.provider_details[provider_name].access_urls): + provider_detail = service_obj.provider_details[provider_name] + access_urls = provider_detail.access_urls + for access_url in access_urls: self["links"].append(link.Model( - access_url, + access_url['operator_url'], 'access_url')) diff --git a/requirements/dns/rackspace.txt b/requirements/dns/rackspace.txt new file mode 100644 index 00000000..34a66203 --- /dev/null +++ b/requirements/dns/rackspace.txt @@ -0,0 +1 @@ +pyrax diff --git a/tests/etc/default_functional.conf b/tests/etc/default_functional.conf index 50d1f29c..4cb0b901 100644 --- a/tests/etc/default_functional.conf +++ b/tests/etc/default_functional.conf @@ -1,8 +1,9 @@ [drivers] -providers = mock,cloudfront,fastly +providers = mock,maxcdn,cloudfront,fastly transport = pecan manager = default storage = mockdb +dns = default [drivers:storage:cassandra] cluster = "192.168.59.103"