diff --git a/doc/source/poppy.manager.rst b/doc/source/poppy.manager.rst index b504c4ca..b9dfd717 100644 --- a/doc/source/poppy.manager.rst +++ b/doc/source/poppy.manager.rst @@ -65,3 +65,14 @@ :members: :undoc-members: :show-inheritance: + + +.. automodule:: poppy.manager.default.background_job + :members: + :undoc-members: + :show-inheritance: + +.. automodule:: poppy.manager.default.health + :members: + :undoc-members: + :show-inheritance: diff --git a/poppy/manager/base/analytics.py b/poppy/manager/base/analytics.py index 46ae6f1a..725d4590 100644 --- a/poppy/manager/base/analytics.py +++ b/poppy/manager/base/analytics.py @@ -30,22 +30,41 @@ def __init__(self, manager): @property def storage_controller(self): + """Return storage controller. + + :return: Storage controller + :rtype: poppy.storage.cassandra.services.ServicesController + """ return self.manager.storage.services_controller @property def providers(self): + """Return provider module. + + :return: Provider module + :rtype: poppy.provider.akamai + """ return self.manager.providers @property def metrics_controller(self): + """Return metrics controller. + + :return: Metrics controller + :rtype: poppy.metrics.blueflood.services.ServicesController + """ return self.manager.metrics.services_controller @abc.abstractmethod def get_metrics_by_domain(self, project_id, domain_name, **extras): - """get analytics metrics by domain + """Get analytics metrics by domain. + + :param project_id: The project id + :type project_id: int + + :param domain_name: The domain name + :type domain_name: str - :param project_id - :param domain_name :raises: NotImplementedError """ raise NotImplementedError diff --git a/poppy/manager/base/background_job.py b/poppy/manager/base/background_job.py index 8bb27372..e1069853 100644 --- a/poppy/manager/base/background_job.py +++ b/poppy/manager/base/background_job.py @@ -31,6 +31,12 @@ def __init__(self, manager): def post_job(self, job_type, kwargs): """Posts a background job to the distributed task driver. - :raises: NotImplementedError + :param job_type: The job type + :type job_type: str + + :param kwargs: Extra arguments for the task engine + :type kwargs: dict + + :raise: NotImplementedError """ raise NotImplementedError diff --git a/poppy/manager/base/controller.py b/poppy/manager/base/controller.py index cd8fad18..ef613006 100644 --- a/poppy/manager/base/controller.py +++ b/poppy/manager/base/controller.py @@ -32,4 +32,8 @@ def __init__(self, driver): @property def driver(self): + """Return the instance of the driver + + :return: Driver's instance + """ return self._driver diff --git a/poppy/manager/base/driver.py b/poppy/manager/base/driver.py index a1757939..f68ba3c8 100644 --- a/poppy/manager/base/driver.py +++ b/poppy/manager/base/driver.py @@ -33,47 +33,72 @@ def __init__(self, conf, storage, providers, dns, distributed_task, @property def conf(self): - """conf + """Return the configuration for the driver. - :returns conf + :return: Configuration + :rtype: dict """ return self._conf @property def storage(self): - """storage + """Return the storage module for the driver. - :returns storage + :return: Storage module + :rtype: poppy.storage.cassandra """ return self._storage @property def providers(self): - """providers + """Return the provider module for the driver. - :returns providers + :return: Providers module + :rtype: poppy.provider.akamai """ return self._providers @property def dns(self): + """Return the dns module for the driver. + + :return: DNS module + :rtype: poppy.provider.dns + """ return self._dns @property def distributed_task(self): + """Return the distributed_task module for the driver. + + :return: distributed_task module + :rtype: poppy.provider.distributed_task + """ return self._distributed_task @property def notification(self): + """Return the notification module for the driver. + + :return: notification module + :rtype: poppy.provider.notification + """ + return self._notification @property def metrics(self): + """Return the metrics module for the driver. + + :return: metrics module + :rtype: poppy.provider.metrics + """ + return self._metrics @abc.abstractproperty def analytics_controller(self): - """Returns the driver's analytics controller + """Return the driver's analytics controller. :raises NotImplementedError """ @@ -81,7 +106,7 @@ def analytics_controller(self): @abc.abstractproperty def services_controller(self): - """Returns the driver's services controller + """Return the driver's services controller. :raises NotImplementedError """ @@ -89,7 +114,7 @@ def services_controller(self): @abc.abstractproperty def flavors_controller(self): - """Returns the driver's flavors controller + """Return the driver's flavors controller. :raises NotImplementedError """ @@ -97,7 +122,7 @@ def flavors_controller(self): @abc.abstractproperty def health_controller(self): - """Returns the driver's health controller + """Return the driver's health controller. :raises NotImplementedError """ diff --git a/poppy/manager/base/flavors.py b/poppy/manager/base/flavors.py index e2a89bd3..9e88d80f 100644 --- a/poppy/manager/base/flavors.py +++ b/poppy/manager/base/flavors.py @@ -32,52 +32,60 @@ def __init__(self, manager): @property def storage(self): - """storage + """Return StorageController for this FlavorController. - :returns: the storage object + :return: The storage object """ return self._storage @property def providers(self): - """storage + """Get list of providers for this FlavorController. - :returns: the list of provider drivers + :return: The list of provider drivers """ return self._providers @abc.abstractmethod def list(self): - """list + """Get list of the supported flavors. - :raises: NotImplementedError + :raise: NotImplementedError """ raise NotImplementedError @abc.abstractmethod def get(self, flavor_id): - """GET + """Get Flavor details for the given flavor id - :param flavor_id - :raises: NotImplementedError + :param flavor_id: Flavor id to get the flavor details + :type flavor_id: str + + :raise: NotImplementedError """ raise NotImplementedError @abc.abstractmethod def add(self, flavor): - """POST + """Add a new flavor. + + :param flavor: Flavor details + :type flavor: poppy.model.flavor.Flavor - :param flavor - :raises: NotImplementedError + :raise: NotImplementedError """ raise NotImplementedError @abc.abstractmethod def delete(self, flavor_id, provider_id): - """DELETE + """Delete a flavor. + + :param flavor_id: The flavor id to delete + :type flavor_id: str + + :param provider_id: The provider id of the flavor + :type provider_id: str - :param flavor_id - :param provider_id - :raises: NotImplementedError + :raise: NotImplementedError """ raise NotImplementedError diff --git a/poppy/manager/base/health.py b/poppy/manager/base/health.py index 5e9369bc..0d88130c 100644 --- a/poppy/manager/base/health.py +++ b/poppy/manager/base/health.py @@ -34,7 +34,7 @@ def __init__(self, manager): @abc.abstractmethod def health(self): - """Returns the health of storage and providers + """Returns the health of storage and providers. :raises: NotImplementedError """ @@ -42,36 +42,44 @@ def health(self): @abc.abstractmethod def is_provider_alive(self, provider_name): - """Returns the health of provider + """Returns the health of provider. + + :param provider_name: The provider name + :type provider_name: str - :param provider_name :raises: NotImplementedError """ raise NotImplementedError @abc.abstractmethod def is_distributed_task_alive(self, distributed_task_name): - """Returns the health of distributed_task + """Returns the health of distributed_task. + + :param distributed_task_name: The task name + :type distributed_task_name: str - :param distributed_task_name :raises: NotImplementedError """ raise NotImplementedError @abc.abstractmethod def is_dns_alive(self, dns_name): - """Returns the health of DNS Provider + """Returns the health of DNS Provider. + + :param dns_name: The DNS name + :type dns_name: str - :param dns_name :raises: NotImplementedError """ raise NotImplementedError @abc.abstractmethod def is_storage_alive(self, storage_name): - """Returns the health of storage + """Returns the health of storage. + + :param storage_name: The name of the storage + :type storage_name: str - :param storage_name :raises: NotImplementedError """ raise NotImplementedError diff --git a/poppy/manager/base/home.py b/poppy/manager/base/home.py index 5f037090..616420b8 100644 --- a/poppy/manager/base/home.py +++ b/poppy/manager/base/home.py @@ -29,9 +29,8 @@ def __init__(self, manager): @abc.abstractmethod def get(self): - """get + """Get JSON text with all resources info. - :param self :raises: NotImplementedError """ raise NotImplementedError diff --git a/poppy/manager/base/notifications.py b/poppy/manager/base/notifications.py index 64f3121e..e119a24b 100644 --- a/poppy/manager/base/notifications.py +++ b/poppy/manager/base/notifications.py @@ -18,12 +18,19 @@ class NotificationWrapper(object): """"ProviderWrapper class.""" def send(self, ext, subject, mail_content): - """Send notifications with subject and mail_content + """Send notifications with subject and mail_content. - :param ext - :param subject - :param mail_content - :returns: ext.obj.services_controller.send(subject, mail_content) + :param ext: + :type ext: + + :param subject: The subject text + :type subject: str + + :param mail_content: The mail content + :type mail_content: str + + :return: ext.obj.services_controller.send(subject, mail_content) + :rtype: ext.obj.services_controller.send """ return ext.obj.services_controller.send(subject, mail_content) diff --git a/poppy/manager/base/providers.py b/poppy/manager/base/providers.py index e831c7bc..d1049170 100644 --- a/poppy/manager/base/providers.py +++ b/poppy/manager/base/providers.py @@ -20,21 +20,34 @@ class ProviderWrapper(object): """"ProviderWrapper class.""" def create(self, ext, service_obj): - """Create a provider + """Create a provider. - :param ext - :param service_obj - :returns: ext.obj.service_controller.create(service_obj) + :param ext: + :type ext: obj + + :param service_obj: The service details + :type service_obj: dict + + :return: ext.obj.service_controller.create(service_obj) + :rtype: ext.obj.service_controller.create """ return ext.obj.service_controller.create(service_obj) def update(self, ext, provider_details, service_obj): - """Update a provider + """Update a provider. + + :param ext: + :type ext: object - :param ext - :param provider_details - :param service_obj + :param provider_details: The provider details + :type provider_details: dict + + :param service_obj: The service details + :type service_obj: dict + + :return: Updated service details + :rtype: dict """ try: @@ -48,6 +61,17 @@ def update(self, ext, provider_details, service_obj): provider_service_id, service_obj) def delete(self, ext, provider_details, project_id): + """Delete a service. + + :param ext: + :type ext: object + + :param provider_details: The provider details + :type provider_details: dict + + :param project_id: The project id + :type project_id: int + """ try: provider_detail = provider_details[ext.obj.provider_name] except KeyError: @@ -60,6 +84,26 @@ def delete(self, ext, provider_details, project_id): def purge(self, ext, service_obj, provider_details, hard=False, purge_url=None): + """Purge a service. + + :param ext: + :type ext: object + + :param service_obj: The service details to purge + :type service_obj: dict + + :param provider_details: The provider details + :type provider_details: dict + + :param hard: (Default False) Provider will be set to status + 'update_in_progress' if set to True + :type hard: bool + + :param purge_url: The purge url + :type purge_url: str + + :raise: BadProviderDetail if service has not been created + """ try: provider_detail = provider_details[ext.obj.provider_name] except KeyError: @@ -76,11 +120,19 @@ def purge(self, ext, service_obj, provider_details, def create_certificate(self, ext, cert_obj, enqueue, https_upgrade): """Create a certificate. - :param ext - :param cert_obj - :param enqueue - :param https_upgrade - :returns: ext.obj.certificate_controller.create_certificate(cert_obj, + :param ext: + :type ext: object + + :param cert_obj: The certificate details + :type cert_obj: dict + + :param enqueue: The enqueue option + :type enqueue: bool + + :param https_upgrade: The upgrade option to https + :type https_upgrade: bool + + :return: ext.obj.certificate_controller.create_certificate(cert_obj, enqueue, https_upgrade) """ @@ -93,8 +145,12 @@ def create_certificate(self, ext, cert_obj, enqueue, https_upgrade): def delete_certificate(self, ext, cert_obj): """Delete a certificate. - :param ext - :param cert_obj + :param ext: + :type ext: object + + :param cert_obj: The certificate details + :type cert_obj: dict + :returns: ext.obj.service_controller.delete_certificate(cert_obj) """ diff --git a/poppy/manager/base/services.py b/poppy/manager/base/services.py index 12bbb2ab..eb4fb456 100644 --- a/poppy/manager/base/services.py +++ b/poppy/manager/base/services.py @@ -36,10 +36,16 @@ def __init__(self, manager): def get_services(self, project_id, marker=None, limit=None): """Get a list of services. - :param project_id - :param marker - :param limit - :raises: NotImplementedError + :param project_id: The project id + :type project_id: str + + :param marker: The marker indicator + :type marker: int + + :param limit: No of services to fetch + :type limit: int + + :raise: NotImplementedError """ raise NotImplementedError @@ -47,20 +53,30 @@ def get_services(self, project_id, marker=None, limit=None): def get_service(self, project_id, service_id): """Get a service. - :param project_id - :param service_id - :raises: NotImplementedError + :param project_id: The project id + :type project_id: int + + :param service_id: The service id + :type service_id: str + + :raise: NotImplementedError """ raise NotImplementedError @abc.abstractmethod def create_service(self, project_id, auth_token, service_obj): - """Create a service. + """Create a new service. + + :param project_id: The project id + :type project_id: int - :param project_id - :param auth_token - :param service_obj - :raises: NotImplementedError + :param auth_token: Token for the authorization + :type auth_token: str + + :param service_obj: The new service details + :type service_obj: dict + + :raise: NotImplementedError """ raise NotImplementedError @@ -69,32 +85,52 @@ def update_service(self, project_id, service_id, auth_token, service_updates, force_update=False): """Update a service. - :param project_id - :param service_id - :param auth_token - :param service_updates - :param force_update - :raises: NotImplementedError + :param project_id: The project id + :type project_id: int + + :param service_id: The service id + :type service_id: str + + :param auth_token: Token for the authorization + :type auth_token: str + + :param service_updates: To be updated details + :type service_updates: dict + + :param force_update: (Default False) Force update + :type force_update: bool + + :raise: NotImplementedError """ raise NotImplementedError @abc.abstractmethod def services_action(self, project_id, action, domain=None): - """services_action + """Perform an action on services. + + :param project_id: The project id + :type project_id: int - :param project_id - :param action - :param domain - :raises ValueError + :param action: Choose from 'delete', 'enable', 'disable' + :type action: str + + :param domain: The domain name + :type domain: str + + :raise: ValueError """ @abc.abstractmethod def delete_service(self, project_id, service_id): """Delete a service. - :param project_id - :param service_id - :raises: NotImplementedError + :param project_id: The project id + :type project_id: int + + :param service_id: The service id + :type service_id: str + + :raise: NotImplementedError """ raise NotImplementedError @@ -104,9 +140,18 @@ def purge(self, project_id, service_id, hard=False, purge_url=None): If purge_url is none, all content of this service will be purged. - :param project_id - :param service_id - :param hard - :param purge_url + :param project_id: The project id + :type project_id: int + + :param service_id: The service id + :type service_id: str + + :param hard: (Default False) Changes provider's status to + 'update_in_progress' if True + + :param purge_url: The purge URL + :type purge_url: str + + :raise: NotImplementedError """ raise NotImplementedError diff --git a/poppy/manager/base/ssl_certificate.py b/poppy/manager/base/ssl_certificate.py index d773c15a..82eaf8d9 100644 --- a/poppy/manager/base/ssl_certificate.py +++ b/poppy/manager/base/ssl_certificate.py @@ -31,9 +31,13 @@ def __init__(self, manager): def create_ssl_certificate(self, project_id, cert_obj): """Create ssl certificate. - :param project_id - :param cert_obj - :raises: NotImplementedError + :param project_id: The project id + :type int + + :param cert_obj: The certificate details + :type cert_obj: dict + + :raise: NotImplementedError """ raise NotImplementedError @@ -41,10 +45,16 @@ def create_ssl_certificate(self, project_id, cert_obj): def delete_ssl_certificate(self, project_id, domain_name, cert_type): """Delete ssl certificate. - :param project_id - :param domain_name - :param cert_type - :raises: NotImplementedError + :param project_id: The project id + :type project_id: int + + :param domain_name: The domain name + :type domain_name: str + + :param cert_type: Type of the certificate + :type cert_type: str + + :raise: NotImplementedError """ raise NotImplementedError @@ -52,8 +62,12 @@ def delete_ssl_certificate(self, project_id, domain_name, cert_type): def get_certs_info_by_domain(self, domain_name, project_id): """Get ssl certificate by domain. - :param domain_name: - :param project_id: - :raises: NotImplementedError + :param domain_name: The domain name + :type domain_name: str + + :param project_id: The project id + :type project_id: int + + :raise: NotImplementedError """ raise NotImplementedError diff --git a/poppy/manager/default/analytics.py b/poppy/manager/default/analytics.py index 5eb60817..0532b475 100644 --- a/poppy/manager/default/analytics.py +++ b/poppy/manager/default/analytics.py @@ -23,6 +23,30 @@ class AnalyticsController(base.AnalyticsController): def get_metrics_by_domain(self, project_id, domain_name, **extras): + """Gets metrics information for a domain. + + The below keys are expected in ``extras`` along with their values. + - metricType + - startTime + - endTime + - metrics_controller + + Example return: + ``{'provider': '', 'flavor':'', 'domain':'', 'metricType': {}}`` + + :param unicode project_id: The project id + :param unicode domain_name: The domain name + :param dict extras: Additional arguments to supply + + :return: A dictionary containing domain name and its metrics + :rtype: dict + + :raises ServiceNotFound: if domain not present under project id + :raises ServiceProviderDetailsNotFound: if no provider details + exists for the service id and project id combo + :raises ProviderDetailsIncomplete: if provider is not found + for the domain + """ storage_controller = self.storage_controller try: diff --git a/poppy/manager/default/background_job.py b/poppy/manager/default/background_job.py index 760f34cd..c94cdf06 100644 --- a/poppy/manager/default/background_job.py +++ b/poppy/manager/default/background_job.py @@ -50,6 +50,42 @@ def __init__(self, manager): self.service_storage = self._driver.storage.services_controller def post_job(self, job_type, kwargs): + """Submit a job to the Job board. + + Iterate over each certificate in san mapping queue. + For each job type, build ``run_list`` and ``ignore_list`` of certificates + based on some logic while submitting jobs for those certificates + belonging to the run_list. San mapping queue may not be intact depending + on the job type. Return both run_list and ignore_list. + + Valid job_type includes. + - akamai_check_and_update_cert_status + - akamai_update_papi_property_for_mod_san + - akamai_update_papi_property_for_mod_sni + + .. code-block:: python + + ( + [ + { + "domain_name": "one.example.com", + "project_id": "000", + "flavor_id": "cdn", + "cert_type": "sni" + } + ], + [] + ) + + + :param str job_type: Type of the job + :param dict kwargs: Additional arguments + + :return: Tuple of run_list and ignore_list + :rtype: tuple(list, list) + + :raises NotImplementedError: if the job_type is not supported + """ queue_data = [] run_list = [] @@ -445,6 +481,11 @@ def post_job(self, job_type, kwargs): ) def get_san_mapping_list(self): + """Get items from SAN mapping queue. + + :return: List of SAN mapping items + :rtype: list + """ res = [] if 'akamai' in self._driver.providers: akamai_driver = self._driver.providers['akamai'].obj @@ -455,6 +496,13 @@ def get_san_mapping_list(self): return res def put_san_mapping_list(self, san_mapping_list): + """Override san_mapping_queue with new certificate objects. + + :param list san_mapping_list: New certificates + + :return: Tuple of new and deleted certs in the san_mapping_queue + :rtype: tuple(list, list) + """ new_queue_data = [json.dumps(r) for r in san_mapping_list] res, deleted = [], [] if 'akamai' in self._driver.providers: @@ -473,6 +521,20 @@ def put_san_mapping_list(self, san_mapping_list): return res, deleted def delete_http_policy(self): + """Submit task to delete Provider's(Akamai's) obsolete http policies. + + Get all the http_policies available from the provider's policy queue + by consuming the queue (meaning queue will be empty after consume). For + each consumed policy, get certificates by domain. If the certificates are + empty for a domain, add that policy to ignore_list. If the certificate + status is not yet deployed, then add that policy to ignore_list and put it + back to policy queue so that it can be consumed in a later trial. Put rest + of the policies into run_list and submit a task to delete these policies. + Return ignored_list and run_list. + + :return: Tuple of deleted and ignored policies + :rtype: tuple(list, list) + """ http_policies = [] run_list = [] ignore_list = [] diff --git a/poppy/manager/default/driver.py b/poppy/manager/default/driver.py index 4e356bce..c4af7d08 100644 --- a/poppy/manager/default/driver.py +++ b/poppy/manager/default/driver.py @@ -31,28 +31,69 @@ def __init__(self, conf, storage, providers, dns, distributed_task, @decorators.lazy_property(write=True) def analytics_controller(self): + """Return the driver's analytics controller. + + :return: Analytics controller + :rtype: poppy.manager.default.analytics.AnalyticsController + """ return controllers.Analytics(self) @decorators.lazy_property(write=True) def services_controller(self): + """Return the driver's services controller. + + :return: Services controller + :rtype: poppy.manager.default.services.DefaultServiceController + """ + return controllers.Services(self) @decorators.lazy_property(write=False) def home_controller(self): + """Return the driver's home controller. + + :return: Home controller + :rtype: poppy.manager.default.home.DefaultHomeController + """ + return controllers.Home(self) @decorators.lazy_property(write=False) def flavors_controller(self): + """Return the driver's flavors controller. + + :return: Flavors controller + :rtype: poppy.manager.default.flavors.DefaultFlavorsController + """ + return controllers.Flavors(self) @decorators.lazy_property(write=False) def health_controller(self): + """Return the driver's health controller. + + :return: Health controller + :rtype: poppy.manager.default.health.DefaultHealthController + """ + return controllers.Health(self) @decorators.lazy_property(write=False) def background_job_controller(self): + """Return the driver's background controller. + + :return: Background Job controller + :rtype: poppy.manager.default.background_job.BackgroundJobController + """ + return controllers.BackgroundJob(self) @decorators.lazy_property(write=False) def ssl_certificate_controller(self): + """Return the driver's SSL Certificate controller. + + :return: SSL Certificate controller + :rtype: poppy.manager.default.ssl_certificate.DefaultSSLCertificateController + """ + return controllers.SSLCertificate(self) diff --git a/poppy/manager/default/flavors.py b/poppy/manager/default/flavors.py index 2adf6845..26fa997d 100644 --- a/poppy/manager/default/flavors.py +++ b/poppy/manager/default/flavors.py @@ -24,28 +24,35 @@ def __init__(self, manager): super(DefaultFlavorsController, self).__init__(manager) def list(self): - """list. + """Get list of the supported flavors. - :return list + :return: List of the supported flavors + :rtype: list """ return self.storage.list() def get(self, flavor_id): - """get. + """Return flavor details. - ":param flavor_id - :return flavor id + :param unicode flavor_id: The flavor id + + :return: Flavor details + :rtype: poppy.model.flavor.Flavor """ return self.storage.get(flavor_id) def add(self, new_flavor): - """add. + """Add a new flavor. + + :param new_flavor: The new flavor details + :type new_flavor: poppy.model.flavor.Flavor + + :return: The newly added flavor details with + provider details + :rtype: poppy.model.flavor.Flavor - :param new_flavor - :return new flavor - :raise LookupError + :raises ValueError: if the flavor already exists """ - # is this a valid flavor? provider_list = self.driver.conf[bootstrap._DRIVER_GROUP].providers for provider in new_flavor.providers: @@ -58,9 +65,8 @@ def add(self, new_flavor): return self.storage.add(new_flavor) def delete(self, flavor_id): - """delete. + """Delete an existing flavor. - :param flavor_id - :return flavor_id + :param unicode flavor_id: The flavor id to delete """ return self.storage.delete(flavor_id) diff --git a/poppy/manager/default/health.py b/poppy/manager/default/health.py index 9657e58b..a3bd98b7 100644 --- a/poppy/manager/default/health.py +++ b/poppy/manager/default/health.py @@ -23,9 +23,22 @@ def __init__(self, manager): super(DefaultHealthController, self).__init__(manager) def health(self): - """health. + """Returns health status of all the modules. - :returns is_alive, health_map + A health_map dict with health information about dns, providers, + distributed_task and storage modules along with is_alive indicator( + ``True if all the modules are alive,`` + ``False if any one of the module is not alive.``) + + This API may be slow in giving response depending on the provider + as it needs to hit the provider's API to fetch the health info. + + If you only need to check the status of ``distributed_task`` + and/or ``storage``, you can use :meth:`ping_check()`, as it provides + a subset of the ``health_map`` and is generally much faster. + + :return: is_alive and dict of health info + :rtype: tuple(bool, dict) """ health_map, is_alive = self.ping_check() @@ -53,6 +66,13 @@ def health(self): return is_alive, health_map def ping_check(self): + """Get health for storage and distributed_task. + + For more details, refer to :meth:`health` . + + :return: is_alive and health_map dict + :rtype: tuple(bool, dict) + """ health_map = {} is_alive = True @@ -78,12 +98,24 @@ def ping_check(self): return health_map, is_alive def is_provider_alive(self, provider_name): - """Returns the health of provider.""" + """Check if provider is alive. + + :param str provider_name: The name of the provider + :return: ``True`` if alive, otherwise ``False`` + :rtype: bool + """ return self._providers[provider_name].obj.is_alive() def is_distributed_task_alive(self, distributed_task_name): - """Returns the health of distributed_task.""" + """Check if distributed_task is alive. + + :param str distributed_task_name: The name of the dist task + :return: ``True`` if alive, otherwise ``False`` + :rtype: bool + + :raises KeyError: if the distributed_task_name is not same as vendor name + """ if distributed_task_name == self._distributed_task.vendor_name.lower(): return self._distributed_task.is_alive() @@ -91,7 +123,14 @@ def is_distributed_task_alive(self, distributed_task_name): raise KeyError def is_storage_alive(self, storage_name): - """Returns the health of storage.""" + """Check if storage is alive. + + :param str storage_name: The name of the storage + :return: ``True`` if alive, otherwise ``False`` + :rtype: bool + + :raises KeyError: if storage_name is not same as underlying storage + """ if storage_name == self._storage.storage_name.lower(): return self._storage.is_alive() @@ -99,7 +138,14 @@ def is_storage_alive(self, storage_name): raise KeyError def is_dns_alive(self, dns_name): - """Returns the health of DNS Provider.""" + """Check if DNS is alive. + + :param str dns_name: The name of the DNS + :return: ``True`` if alive, otherwise ``False`` + :rtype: bool + + :raise KeyError: if dns_name is not same as underlying DNS + """ if dns_name == self._dns.dns_name.lower(): return self._dns.is_alive() diff --git a/poppy/manager/default/home.py b/poppy/manager/default/home.py index 579248f5..b34bc5eb 100644 --- a/poppy/manager/default/home.py +++ b/poppy/manager/default/home.py @@ -80,8 +80,10 @@ def __init__(self, manager): self.JSON_HOME = JSON_HOME def get(self): - """get. + """Get JSON text with all resources info. - :return json home + :return: The JSON text with services, flavors, + health and ping information + :rtype: dict[str, dict] """ return self.JSON_HOME diff --git a/poppy/manager/default/services.py b/poppy/manager/default/services.py index c3ac0ae4..fedbc257 100644 --- a/poppy/manager/default/services.py +++ b/poppy/manager/default/services.py @@ -63,6 +63,11 @@ def __init__(self, manager): self.provider_conf = self.driver.conf[PROVIDER_GROUP] def determine_sleep_times(self): + """Calculate sleep times used in rebind. + + :return: List of times to sleep using exponential backoff + :rtype: list + """ determined_sleep_time = \ random.randrange(self.dns_conf.min_backoff_range, @@ -74,6 +79,15 @@ def determine_sleep_times(self): return backoff def _get_provider_details(self, project_id, service_id): + """Return Provider details for given project and service ids. + + :param unicode project_id: The project id + :param unicode service_id: The service id + :return: Provider details for the given project and service ids + :rtype: Dict[str, poppy.model.helpers.provider_details.ProviderDetail] + + :raises LookupError: if the service does not exists + """ try: provider_details = self.storage_controller.get_provider_details( project_id, @@ -84,6 +98,15 @@ def _get_provider_details(self, project_id, service_id): return provider_details def get_service_by_domain_name(self, domain_name): + """Return service details for given domain name. + + :param unicode domain_name: The domain name + :return: The service details + :rtype: poppy.model.service.Service + + :raises LookupError: if the domain does not exists + or service details can not be found for the domain + """ try: service_details = self.storage_controller\ .get_service_details_by_domain_name(domain_name) @@ -98,6 +121,19 @@ def get_service_by_domain_name(self, domain_name): return service_details def get_services_by_status(self, status): + """Return the services by status. + + Currently supported statuses include: + - create_in_progress + - deployed + - update_in_progress + - delete_in_progress + - failed + + :param unicode status: The status of the service + :return: List of services + :rtype: list + """ services_project_ids = \ self.storage_controller.get_services_by_status(status) @@ -105,6 +141,12 @@ def get_services_by_status(self, status): return services_project_ids def get_domains_by_provider_url(self, provider_url): + """Return domains by Provider url. + + :param unicode provider_url: The provider URL + :return: List of domains + :rtype: list + """ domains = \ self.storage_controller.get_domains_by_provider_url(provider_url) @@ -112,6 +154,14 @@ def get_domains_by_provider_url(self, provider_url): return domains def _append_defaults(self, service_json, operation='create'): + """Add defaults to the service. + + If the service rules are None or Empty, add default rule. + If the operation is 'create', add default ttl and cache. + + :param dict service_json: Service object represented as a dict + :param str operation: (Default 'create') Name of operation + """ # default origin rule for origin in service_json.get('origins', []): if origin.get('rules') is None: @@ -149,31 +199,40 @@ def _append_defaults(self, service_json, operation='create'): caching_entry['rules'].append(default_rule.to_dict()) def get_services(self, project_id, marker=None, limit=None): - """Get a list of services. + """Get list of services. + + :param unicode project_id: The project id + :param int marker: Starting position of the pagination + :param int limit: Number of services to fetch - :param project_id - :param marker - :param limit - :return list + :return: List of services + :rtype: list(poppy.model.service.Service) """ return self.storage_controller.get_services(project_id, marker, limit) def get_service(self, project_id, service_id): - """get. + """Get a service object. - :param project_id - :param service_id - :return controller + :param unicode project_id: The project id + :param unicode service_id: The service id + + :return: The service object + :rtype: poppy.model.services.Service """ return self.storage_controller.get_service(project_id, service_id) def create_service(self, project_id, auth_token, service_json): - """create. + """Create a new service. + + :param unicode project_id: The project id + :param unicode auth_token: Token for authorization + :param dict service_json: The service details to create - :param project_id - :param auth_token - :param service_json - :raises LookupError, ValueError + :return: The new service object created + :rtype: poppy.model.service.Service + + :raises LookupError: if the flavor does not exists + :raises ValueError: if shard domain retry operation has problem """ try: flavor = self.flavor_controller.get(service_json.get('flavor_id')) @@ -250,14 +309,19 @@ def create_service(self, project_id, auth_token, service_json): def update_service(self, project_id, service_id, auth_token, service_updates, force_update=False): - """update. - - :param project_id - :param service_id - :param auth_token - :param service_updates - :param force_update - :raises LookupError, ValueError + """Update a service. + + :param unicode project_id: The project id + :param unicode service_id: The service id + :param unicode auth_token: Token for authorization + :param list service_updates: The service details + :param bool force_update: (Default False) Forceful update \ + of the service + + :raises ServiceNotFound: if service not found + :raises ServiceStatusDisabled: if service is disabled + :raises ServiceStatusNeitherDeployedNorFailed: if force_update + is False and service is neither deployed nor failed """ # get the current service object try: @@ -476,12 +540,27 @@ def update_service(self, project_id, service_id, return def services_limit(self, project_id, limit): + """Set limit for number of services for a given project. + + :param unicode project_id: The project id + :param int limit: No of services + """ self.storage_controller.set_service_limit( project_id=project_id, project_limit=limit) def set_service_provider_details(self, project_id, service_id, auth_token, status): + """Set provider details for a given service. + + :param unicode project_id: The project id + :param unicode service_id: The service id + :param unicode auth_token: Token for authorization + :param str status: Status of the service + + :return: Http status code 201 or 202 + :rtype: int + """ old_service = self.storage_controller.get_service( project_id, service_id @@ -502,6 +581,13 @@ def set_service_provider_details(self, project_id, service_id, return 201 def get_services_limit(self, project_id): + """Get the currently set limit on services for a given project. + + :param unicode project_id: The project id + + :return: The dict with limit's value + :rtype: dict[str, int] + """ limit = self.storage_controller.get_service_limit( project_id=project_id) @@ -510,6 +596,18 @@ def get_services_limit(self, project_id): } def _action_per_service_obj(self, project_id, action, service_obj): + """Perform an action on the service. + + List of actions include + - delete + - enable + - disable + + :param unicode project_id: The project id + :param str action: Action needs to be done + :param service_obj: The service object + :type service_obj: poppy.model.service.Service + """ kwargs = { 'project_id': project_id, @@ -546,13 +644,20 @@ def _action_per_service_obj(self, project_id, action, service_obj): str(e))) def services_action(self, project_id, action, domain=None): - """perform action on services + """perform action on services present in this domain. + + If domain is None, The action is performed on all the + services for a project. Else, the action will be + performed on those services present in the given domain. - :param project_id - :param action - :param domain + List of actions include + - delete + - enable + - disable - :raises ValueError + :param unicode project_id: The project id + :param str action: Action needs to done + :param unicode domain: The name of the domain """ # list all the services of for this project_id @@ -587,11 +692,13 @@ def services_action(self, project_id, action, domain=None): return def delete_service(self, project_id, service_id): - """delete. + """Submit a task to delete a service. - :param project_id - :param service_id - :raises LookupError + Before deletion, update each of the provider's status in + service with 'delete_in_progress' + + :param unicode project_id: The project id + :param unicode service_id: The service id """ service_obj = self.storage_controller.get_service( project_id, service_id) @@ -625,7 +732,29 @@ def delete_service(self, project_id, service_id): return def purge(self, project_id, service_id, hard=False, purge_url=None): - """If purge_url is none, all content of this service will be purge.""" + """Purge the contents of a service. + + Example purge_urls (from least specific to most specific): + - Whole-site wildcard, such as ``/*`` + - URL path, such as ``/images/*`` + - Detailed URL path, such as ``/images/logos/partnerA/*`` + - File extension, such as ``/*.png`` + + + If purge_url is none, all content of this service will be purged. + + If ``hard`` is False, status of the each provider details + will be set to 'update_in_progress' + + :param unicode project_id: The project id + :param unicode service_id: The service id + :param bool hard: (Default False) Mark 'update_in_progress' \ + if set to True + :param unicode purge_url: The purge url + + :raises ServiceStatusNotDeployed: if service not yet deployed + :raises LookupError: if the service does not exists + """ try: service_obj = self.storage_controller.get_service( project_id, @@ -673,6 +802,16 @@ def purge(self, project_id, service_id, hard=False, purge_url=None): return def _generate_shared_ssl_domain(self, domain_name, store): + """Generate shared ssl domain. + + :param unicode domain_name: The domain name + :param unicode store: uuid + + :return: Shared ssl domain + :rtype: unicode + + :raises SharedShardsExhausted: if domain has already been taken + """ try: if not hasattr(self, store): gen_store = { @@ -700,6 +839,15 @@ def _generate_shared_ssl_domain(self, domain_name, store): 'Domain {0} has already been taken'.format(domain_name)) def _pick_shared_ssl_domain(self, domain, service_id, store): + """Select an available shared ssl domain. + + :param unicode domain: The domain name + :param unicode service_id: The service id + :param unicode store: uuid + + :return: The available domain + :rtype: unicode + """ shared_ssl_domain = self._generate_shared_ssl_domain( domain, store) @@ -712,6 +860,19 @@ def _pick_shared_ssl_domain(self, domain, service_id, store): return shared_ssl_domain def _shard_retry(self, project_id, service_obj, store=None): + """Retry to get available ssl domain and add to service. + + :param unicode project_id: The project id + :param service_obj: The service object + :type service_obj: poppy.model.service.Service + + :param unicode store: The store object + + :return: The service object with shared ssl domain added + :rtype: poppy.model.service.Service + + :raises ValueError: + """ # deal with shared ssl domains try: for domain in service_obj.domains: @@ -735,6 +896,27 @@ def _shard_retry(self, project_id, service_obj, store=None): def migrate_domain(self, project_id, service_id, domain_name, new_cert, cert_status='deployed'): + """Migrate domain. + + Domain migration includes + - Set cert_status to all the provider's domain_certification + - Update the all the certificates with cert_status + - Update the access_url of all the providers with new_cert + + As of now, below are the supported statuses. + - create_in_progress + - deployed + - failed + - cancelled + + :param unicode project_id: The project id + :param unicode service_id: The service id + :param unicode domain_name: The domain name + :param unicode new_cert: The new certificate + :param unicode cert_status: (Default 'deployed')Certificate status + + :raises ServiceNotFound: if the service not found + """ dns_controller = self.dns_controller storage_controller = self.storage_controller @@ -802,6 +984,17 @@ def migrate_domain(self, project_id, service_id, domain_name, new_cert, ) def _detect_upgrade_http_to_https(self, old_domains, new_domain): + """Determine whether upgrade is required from http to https. + + :param old_domains: List of old domains + :type old_domains: list(poppy.model.helpers.domain.Domain) + + :param new_domain: New domain + :type new_domain: poppy.model.helpers.domain.Domain + + :return: Boolean value indicating upgrade is required or not + :rtype: bool + """ is_upgrade = False for old_domain in old_domains: if old_domain.domain == new_domain.domain: @@ -815,6 +1008,22 @@ def _detect_upgrade_http_to_https(self, old_domains, new_domain): def update_access_url_service( self, project_id, service_id, access_url_changes): + """Update access url of a service. + + This is typically called from admin APIs only. + + :param unicode project_id: The project id + :param unicode service_id: The service id + :param dict access_url_changes: The changes to update the access url + + :return: ``True`` if the access urls is successfully updated + :rtype: bool + + :raises ValueError: if the domain could not found on the service, + :raises ServiceNotFound: if service is not found, + :raises InvalidOperation: if the domain is shared ssl, + :raises InvalidResourceName: if the access url or domain is invalid + """ try: service_old = self.storage_controller.get_service( project_id, diff --git a/poppy/manager/default/ssl_certificate.py b/poppy/manager/default/ssl_certificate.py index 9820ac87..6db2d011 100644 --- a/poppy/manager/default/ssl_certificate.py +++ b/poppy/manager/default/ssl_certificate.py @@ -45,6 +45,18 @@ def __init__(self, manager): def create_ssl_certificate( self, project_id, cert_obj, https_upgrade=False): + """Create a new certificate. + + :param unicode project_id: The project id + :param dict cert_obj: The certificate details + :param bool https_upgrade: (Default False) + Whether ot not to be upgraded to https + + :return: kwargs with wrapped arguments + :rtype: dict + + :raises ValueError: if if domain is not a valid non-root domain + """ if (not validators.is_valid_domain_name(cert_obj.domain_name)) or \ (validators.is_root_domain( @@ -86,6 +98,17 @@ def create_ssl_certificate( return kwargs def delete_ssl_certificate(self, project_id, domain_name, cert_type): + """Delete an SSL certificate. + + :param unicode project_id: The project id + :param unicode domain_name: The name of the domain + :param unicode cert_type: Type of the certificate to be deleted + + :return: dict with cert, provider and other original inputs + :rtype: dict + + :raises LookupError: if the flavor is not found + """ cert_obj = self.storage.get_certs_by_domain( domain_name, cert_type=cert_type) @@ -110,6 +133,16 @@ def delete_ssl_certificate(self, project_id, domain_name, cert_type): return kwargs def get_certs_info_by_domain(self, domain_name, project_id): + """Get the certificates information by domain. + + :param unicode domain_name: Name of the domain + :param unicode project_id: The project id + + :return: SSLCertificate object + :rtype: poppy.model.ssl_certificate.SSLCertificate + + :raises ValueError: if cert info is not found + """ try: certs_info = self.storage.get_certs_by_domain( domain_name=domain_name, @@ -124,6 +157,11 @@ def get_certs_info_by_domain(self, domain_name, project_id): raise e def get_san_retry_list(self): + """Get items from san_mapping_queue of provider. + + :return: san_mapping_queue of provider + :rtype: dict + """ if 'akamai' in self._driver.providers: akamai_driver = self._driver.providers['akamai'].obj res = akamai_driver.mod_san_queue.traverse_queue() @@ -142,6 +180,15 @@ def get_san_retry_list(self): ] def update_san_retry_list(self, queue_data_list): + """Update provider's mod_san_queue with new items. + + :param list queue_data_list: The list of new items to update with. + :return: tuple of lists of old and new items in mod_san_queue + :rtype: (list, list) + + :raises LookupError: if domain does not exists + :raises ValueError: if certificate already exists + """ for r in queue_data_list: service_obj = self.service_storage\ .get_service_details_by_domain_name(r['domain_name']) @@ -180,6 +227,19 @@ def update_san_retry_list(self, queue_data_list): return res, deleted def rerun_san_retry_list(self): + """Retry the mod san queue. + + Obtain the items from mod san queue and build run_list and + ignore_list by iterating through this retry queue. For each + certificate in the run_list, submit a task to recreate the + ssl certificate. + + :return: list of certificates that were recreated and list of + certificates that were ignored + :rtype: (list, list) + + :raises LookupError: if the flavor is not found + """ run_list = [] ignore_list = [] if 'akamai' in self._driver.providers: @@ -310,6 +370,15 @@ def rerun_san_retry_list(self): return run_list, ignore_list def get_san_cert_configuration(self, san_cert_name): + """Get SAN certificate configuration. + + :param unicode san_cert_name: Name of the SAN certificate + + :return: Configuration of the certificate + :rtype: dict + + :raises ValueError: if not a valid certificate name + """ if 'akamai' in self._driver.providers: akamai_driver = self._driver.providers['akamai'].obj if san_cert_name not in akamai_driver.san_cert_cnames: @@ -326,6 +395,17 @@ def get_san_cert_configuration(self, san_cert_name): return res def update_san_cert_configuration(self, san_cert_name, new_cert_config): + """Update configuration of SAN certificate. + + :param unicode san_cert_name: Name of the SAN certificate + :param dict new_cert_config: New configuration to update with. + + :return: The updated values of the certificate + :rtype: dict + + :raises ValueError: if not a valid certificate + :raises RuntimeError: if unable to call SPS api + """ if 'akamai' in self._driver.providers: akamai_driver = self._driver.providers['akamai'].obj if san_cert_name not in akamai_driver.san_cert_cnames: @@ -361,6 +441,12 @@ def update_san_cert_configuration(self, san_cert_name, new_cert_config): return res def get_sni_cert_configuration(self, cert_name): + """Get configuration for SNI certificates. + + :param unicode cert_name: The name of the SNI certificate + :return: SNI certificate configuration + :rtype: dict + """ if 'akamai' in self._driver.providers: akamai_driver = self._driver.providers['akamai'].obj self._validate_sni_cert_name(akamai_driver, cert_name) @@ -372,6 +458,14 @@ def get_sni_cert_configuration(self, cert_name): return res def update_sni_cert_configuration(self, cert_name, new_cert_config): + """Update SNI certificate's configuration. + + :param unicode cert_name: SNI certificate name + :param dict new_cert_config: New configuration to update with + + :return: The updated configuration of the certificate + :rtype: dict + """ if 'akamai' in self._driver.providers: akamai_driver = self._driver.providers['akamai'].obj self._validate_sni_cert_name(akamai_driver, cert_name) @@ -386,6 +480,11 @@ def update_sni_cert_configuration(self, cert_name, new_cert_config): return res def get_san_cert_hostname_limit(self): + """Get SAN certificate hostname limit. + + :return: Limit for the SAN cert host names + :rtype: int + """ if 'akamai' in self._driver.providers: akamai_driver = self._driver.providers['akamai'].obj res = akamai_driver.cert_info_storage.get_san_cert_hostname_limit() @@ -398,6 +497,13 @@ def get_san_cert_hostname_limit(self): @staticmethod def _validate_sni_cert_name(provider_driver, cert_name): + """Check whether a valid SNI certificate name. + + :param object provider_driver: The provider driver + :param unicode cert_name: The name of the SNI certificate + + :raises ValueError: if not a valid SNI certificate name + """ if cert_name not in provider_driver.sni_cert_cnames: raise ValueError( "{0} is not a valid sni cert, " @@ -405,6 +511,13 @@ def _validate_sni_cert_name(provider_driver, cert_name): cert_name, provider_driver.sni_cert_cnames)) def set_san_cert_hostname_limit(self, request_json): + """Set hostname limit for SAN certificate. + + :param dict request_json: Dict containing the required limit + + :return: The updated limit value + :rtype: int + """ if 'akamai' in self._driver.providers: try: new_limit = request_json['san_cert_hostname_limit'] @@ -425,11 +538,26 @@ def set_san_cert_hostname_limit(self, request_json): return res def get_certs_by_status(self, status): + """Get certificates by their status + + :param unicode status: Status of the certificate to look for + + :return: Certificate details + :rtype: dict + """ certs_by_status = self.storage.get_certs_by_status(status) return certs_by_status def update_certificate_status(self, domain_name, certificate_updates): + """Update a certificate status. + + :param unicode domain_name: The domain name + :param dict certificate_updates: New status of the cert to update with + + :raises ValueError: if no certificate found + :raises CertificateStatusUpdateError: if unable to update certificate status + """ certificate_old = self.storage.get_certs_by_domain(domain_name) if not certificate_old: raise ValueError( diff --git a/poppy/transport/pecan/controllers/v1/admin.py b/poppy/transport/pecan/controllers/v1/admin.py index cb94cc17..4add603a 100644 --- a/poppy/transport/pecan/controllers/v1/admin.py +++ b/poppy/transport/pecan/controllers/v1/admin.py @@ -50,6 +50,11 @@ def __init__(self, driver): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def post(self): + """ + Update the certificate status for the provider belonging to service and domain + :return: Pecan response with http status 202 or 404 + :rtype: pecan.Response + """ request_json = json.loads(pecan.request.body.decode('utf-8')) project_id = request_json.get('project_id', None) service_id = request_json.get('service_id', None) @@ -97,6 +102,14 @@ def __init__(self, driver): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def patch_one(self, service_id): + """ + + Update the access urls for service + :type service_id: str + :param service_id: + :return: Pecan response with http status code 201 or 202 400 or 404 + :rtype: pecan.Response + """ request_json = json.loads(pecan.request.body.decode('utf-8')) project_id = request_json.get('project_id', None) domain_name = request_json.get('domain_name', None) @@ -109,16 +122,16 @@ def patch_one(self, service_id): changes_made = False try: - changes_made = self._driver.manager.services_controller.\ + changes_made = self._driver.manager.services_controller. \ update_access_url_service( - project_id, - service_id, - access_url_changes={ - 'domain_name': domain_name, - 'operator_url': operator_url, - 'provider_url': provider_url - } - ) + project_id, + service_id, + access_url_changes={ + 'domain_name': domain_name, + 'operator_url': operator_url, + 'provider_url': provider_url + } + ) except errors.ServiceNotFound: pecan.abort(404, detail='Service {0} could not be found'.format( service_id)) @@ -148,6 +161,11 @@ def __init__(self, driver): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def post(self): + """ + Post a new background job + :return: Pecan response with json and http status 202 or 404 + :rtype: pecan.Response + """ request_json = json.loads(pecan.request.body.decode('utf-8')) job_type = request_json.pop('job_type') @@ -155,7 +173,7 @@ def post(self): ignored = [] try: - sent, ignored = self._driver.manager.background_job_controller.\ + sent, ignored = self._driver.manager.background_job_controller. \ post_job(job_type, request_json) except NotImplementedError as e: pecan.abort(400, str(e)) @@ -179,6 +197,12 @@ def __init__(self, driver): @pecan.expose('json') def get_all(self): + """ + + Get the list of SAN mapping + :return: A list of SAN mappings + :rtype: list + """ try: return ( self.manager.background_job_controller.get_san_mapping_list()) @@ -194,7 +218,10 @@ def get_all(self): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def put(self): - """The input of the queue data must be a list of dictionaries: + """ + Put the items in the SAN mapping list for the processing + + The input of the queue data must be a list of dictionaries: (after json loaded) [ @@ -202,14 +229,17 @@ def put(self): "san_cert_name": } ] + + :return Dictionary consisting of the new queue and deleted items + :rtype : dict """ try: san_mapping_list = json.loads(pecan.request.body.decode('utf-8')) res, deleted = ( self.manager.background_job_controller. - put_san_mapping_list(san_mapping_list)) + put_san_mapping_list(san_mapping_list)) # queue is the new queue, and deleted is deleted items - return {"queue": res, "deleted": deleted} + return {"queue": res, "deleted": deleted} except Exception as e: pecan.abort(400, str(e)) @@ -222,11 +252,17 @@ def __init__(self, driver): @pecan.expose('json') def get_all(self): + """ + + Get the mod-san Re-try list + :return: List of items in mod-san retry + :rtype: list + """ retry_list = None try: retry_list = ( self._driver.manager.ssl_certificate_controller. - get_san_retry_list()) + get_san_retry_list()) except Exception as e: pecan.abort(404, str(e)) @@ -234,10 +270,14 @@ def get_all(self): @pecan.expose('json') def post(self): - """Rerun retry-list mod-san requests.""" + """ + Rerun retry-list mod-san requests. + :return: Pecan response with JSON and http status code 202 or 404 + :rtype: pecan.Response + """ sent, ignored = None, None try: - sent, ignored = self._driver.manager.ssl_certificate_controller.\ + sent, ignored = self._driver.manager.ssl_certificate_controller. \ rerun_san_retry_list() except Exception as e: pecan.abort(404, str(e)) @@ -256,7 +296,9 @@ def post(self): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def put(self): - """The input of the queue data must be a list of dictionaries: + """ + Update the mod-san retry list + The input of the queue data must be a list of dictionaries: (after json loaded) [ @@ -265,14 +307,16 @@ def put(self): "flavor_id": } ] + :return: Dictionary consisting of new queue and deleted items + :rtype: dict """ try: queue_data = json.loads(pecan.request.body.decode('utf-8')) res, deleted = ( self._driver.manager.ssl_certificate_controller. - update_san_retry_list(queue_data)) + update_san_retry_list(queue_data)) # queue is the new queue, and deleted is deleted items - return {"queue": res, "deleted": deleted} + return {"queue": res, "deleted": deleted} except Exception as e: pecan.abort(400, str(e)) @@ -286,12 +330,20 @@ class AkamaiSanCertConfigController(base.Controller, hooks.HookController): helpers.is_valid_domain_by_name_or_akamai_setting(), helpers.abort_with_message)) def get_one(self, query): - + """ + Get SAN certificate hostname limit or the configuration depending on the query + :type query: str + :param query: Use value 'san_cert_hostname_limit' to get the hostname limit, + else configuration will be returned + :return: Dictionary with hostname limit or configuration if success, + else Pecan response with http status code 400 + :rtype: dict + """ if query == 'san_cert_hostname_limit': try: return ( self._driver.manager.ssl_certificate_controller. - get_san_cert_hostname_limit() + get_san_cert_hostname_limit() ) except Exception as e: pecan.abort(400, str(e)) @@ -299,7 +351,7 @@ def get_one(self, query): try: return ( self._driver.manager.ssl_certificate_controller. - get_san_cert_configuration(query) + get_san_cert_configuration(query) ) except Exception as e: pecan.abort(400, str(e)) @@ -316,6 +368,13 @@ def get_one(self, query): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def post(self, query): + """ + Set the hostname limit or configuration for the SAN certificate depending on the query + :type query: str + :param query: Use 'san_cert_hostname_limit' to set the hostname limit + :return: Pecan response with http status 202 or 400 + :rtype: pecan.Response + """ request_json = json.loads(pecan.request.body.decode('utf-8')) if query == 'san_cert_hostname_limit': @@ -330,7 +389,7 @@ def post(self, query): try: res = ( self._driver.manager.ssl_certificate_controller. - update_san_cert_configuration(query, request_json)) + update_san_cert_configuration(query, request_json)) return res except Exception as e: pecan.abort(400, str(e)) @@ -344,11 +403,19 @@ class AkamaiSNICertConfigController(base.Controller, hooks.HookController): query=rule.Rule( helpers.is_valid_domain_by_name(), helpers.abort_with_message)) - def get_one(self, query): + def get_one(self, cert_name): + """ + + Get SNI certificate configuration + :type cert_name: str + :param cert_name: The name of the SNI certificate + :return: The SNI certificate details + :rtype: dict + """ try: return ( self._driver.manager.ssl_certificate_controller. - get_sni_cert_configuration(query) + get_sni_cert_configuration(cert_name) ) except Exception as e: pecan.abort(400, str(e)) @@ -364,12 +431,19 @@ def get_one(self, query): "sni_config", "POST")), helpers.abort_with_message, stoplight_helpers.pecan_getter)) - def post(self, query): + def post(self, cert_name): + """ + Update the configuration for SNI certificate + :type cert_name: str + :param cert_name: + :return: Updated configuration + :rtype: dict + """ request_json = json.loads(pecan.request.body.decode('utf-8')) try: res = ( self._driver.manager.ssl_certificate_controller. - update_sni_cert_configuration(query, request_json)) + update_sni_cert_configuration(cert_name, request_json)) return res except Exception as e: pecan.abort(400, str(e)) @@ -400,7 +474,6 @@ def __init__(self, driver): class OperatorServiceActionController(base.Controller, hooks.HookController): - __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] def __init__(self, driver): @@ -415,7 +488,12 @@ def __init__(self, driver): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def post(self): + """ + Perform action on a list of services + :return: Pecan response with http status code 202 or 404 + :rtype: pecan.Response + """ service_state_json = json.loads(pecan.request.body.decode('utf-8')) service_action = service_state_json.get('action', None) project_id = service_state_json.get('project_id', None) @@ -429,15 +507,14 @@ def post(self): except Exception as e: pecan.abort(404, detail=( - 'Services action {0} on tenant: {1} failed, ' - 'Reason: {2}'.format(service_action, - project_id, str(e)))) + 'Services action {0} on tenant: {1} failed, ' + 'Reason: {2}'.format(service_action, + project_id, str(e)))) return pecan.Response(None, 202) class OperatorServiceLimitController(base.Controller, hooks.HookController): - __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] def __init__(self, driver): @@ -456,7 +533,14 @@ def __init__(self, driver): helpers.abort_with_message) ) def put(self, project_id): - + """ + + Set limit on number of services per project + :type project_id: int + :param project_id: Id of the project to set the service limit + :return: Pecan response with http status code 201 or 404 + :rtype: pecan.Response + """ service_state_json = json.loads(pecan.request.body.decode('utf-8')) project_limit = service_state_json.get('limit', None) @@ -467,9 +551,9 @@ def put(self, project_id): project_limit) except Exception as e: pecan.abort(404, detail=( - 'Services limit {0} on tenant: {1} failed, ' - 'Reason: {2}'.format(project_limit, - project_id, str(e)))) + 'Services limit {0} on tenant: {1} failed, ' + 'Reason: {2}'.format(project_limit, + project_id, str(e)))) return pecan.Response(None, 201) @@ -480,6 +564,14 @@ def put(self, project_id): helpers.abort_with_message) ) def get_one(self, project_id): + """ + + Get currently set limit on number of services per this project id + :type project_id: int + :param project_id: Id of the Project to get the service limit + :return: Dictionary with the current value of limits + :rtype: dict + """ services_controller = self._driver.manager.services_controller service_limits = services_controller.get_services_limit( @@ -489,7 +581,6 @@ def get_one(self, project_id): class ServiceStatusController(base.Controller, hooks.HookController): - __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] def __init__(self, driver): @@ -505,7 +596,12 @@ def __init__(self, driver): stoplight_helpers.pecan_getter) ) def post(self): + """ + Update or set status of the service with provider details + :return: Pecan response with http status code 201 or 202 or 404 + :rtype: pecan.Response + """ service_state_json = json.loads(pecan.request.body.decode('utf-8')) project_id = service_state_json['project_id'] service_id = service_state_json['service_id'] @@ -522,18 +618,17 @@ def post(self): ) except Exception as e: pecan.abort(404, detail=( - 'Setting state of service {0} on tenant: {1} ' - 'to {2} has failed, ' - 'Reason: {3}'.format(service_id, - project_id, - status, - str(e)))) + 'Setting state of service {0} on tenant: {1} ' + 'to {2} has failed, ' + 'Reason: {3}'.format(service_id, + project_id, + status, + str(e)))) return pecan.Response(None, status_code) class AdminCertController(base.Controller, hooks.HookController): - __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] def __init__(self, driver): @@ -547,6 +642,12 @@ def __init__(self, driver): stoplight_helpers.pecan_getter) ) def get(self): + """ + + Get ssl certificates by status + :return: Pecan response with http status 200 and json + :rtype: pecan.Response + """ ssl_certificate_controller = ( self._driver.manager.ssl_certificate_controller ) @@ -576,7 +677,14 @@ def get(self): stoplight_helpers.pecan_getter) ) def patch_one(self, domain_name): + """ + Update certificate for a domain + :type domain_name: str + :param domain_name: Name of the domain for which the certificate needs to be updated + :return: Pecan response with http status code 204 or 400 or 404 + :rtype: pecan.Response + """ ssl_certificate_controller = ( self._driver.manager.ssl_certificate_controller ) @@ -600,7 +708,6 @@ def patch_one(self, domain_name): class AdminServiceController(base.Controller, hooks.HookController): - __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] def __init__(self, driver): @@ -619,6 +726,12 @@ def __init__(self, driver): stoplight_helpers.pecan_getter) ) def get(self): + """ + + Get services by status + :return: JSON response with http status 200 + :rtype: pecan.Response + """ services_controller = self._driver.manager.services_controller call_args = getattr(pecan.request.context, @@ -632,7 +745,6 @@ def get(self): class DomainController(base.Controller, hooks.HookController): - __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] def __init__(self, driver): @@ -647,13 +759,21 @@ def __init__(self, driver): helpers.abort_with_message) ) def get_one(self, domain_name): + """ + + Get services by domain name + :type domain_name: str + :param domain_name: Name of the domain + :return: JSON response model + :rtype: collections.OrderedDict + """ services_controller = self._driver.manager.services_controller try: service_obj = services_controller.get_service_by_domain_name( domain_name) except LookupError: pecan.abort(404, detail='Domain %s cannot be found' % - domain_name) + domain_name) # convert a service model into a response service model return resp_service_model.Model(service_obj, self) @@ -665,6 +785,12 @@ def get_one(self, domain_name): stoplight_helpers.pecan_getter) ) def get(self): + """ + + Get domains by provider url + :return: Pecan response with http status 200 and json + :rtype: pecan.Response + """ services_controller = self._driver.manager.services_controller call_args = getattr(pecan.request.context, diff --git a/poppy/transport/pecan/controllers/v1/flavors.py b/poppy/transport/pecan/controllers/v1/flavors.py index 7aefc3c6..8dd8e1d4 100644 --- a/poppy/transport/pecan/controllers/v1/flavors.py +++ b/poppy/transport/pecan/controllers/v1/flavors.py @@ -31,15 +31,17 @@ class FlavorsController(base.Controller, hooks.HookController): - __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] """Flavors Controller.""" @pecan.expose('json') def get_all(self): - """get all flavor list.""" - + """ + Get all the flavors + :return: List of available flavors in the system + :rtype: dict + """ flavors_controller = self.driver.manager.flavors_controller result = flavors_controller.list() @@ -57,10 +59,12 @@ def get_all(self): helpers.abort_with_message) ) def get_one(self, flavor_id): - """get_one - - :param flavor_model - :returns JSON flavor(HTTP 200) or HTTP 404 + """ + Get A flavor details by it's id + :param flavor_id: Flavor id + :type flavor_id: str + :return: Serialized response with the Flavor details wrapped + :rtype: collections.OrderedDict """ flavors_controller = self.driver.manager.flavors_controller try: @@ -78,9 +82,10 @@ def get_one(self, flavor_id): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def post(self): - """POST - - :returns JSON flavor(HTTP 200) or HTTP 400 + """ + Create a new flavor + :return: Newly created flavor + :rtype: collections.OrderedDict """ flavors_controller = self.driver.manager.flavors_controller flavor_json = json.loads(pecan.request.body.decode('utf-8')) @@ -104,10 +109,10 @@ def post(self): @pecan.expose('json') def delete(self, flavor_id): - """DELETE - - :param flavor_model - :returns HTTP 204 + """ + Delete an existing flavor + :param flavor_id: Flavor id to be deleted + :type flavor_id: str """ flavors_controller = self.driver.manager.flavors_controller flavors_controller.delete(flavor_id) diff --git a/poppy/transport/pecan/controllers/v1/health.py b/poppy/transport/pecan/controllers/v1/health.py index b39388a4..38141806 100644 --- a/poppy/transport/pecan/controllers/v1/health.py +++ b/poppy/transport/pecan/controllers/v1/health.py @@ -29,14 +29,14 @@ class DNSHealthController(base.Controller, hooks.HookController): @pecan.expose('json') def get(self, dns_name): - """GET. + """ Returns the health of DNS Provider - - :param dns_name - :returns JSON storage model or HTTP 404 + :type dns_name: str + :param dns_name: Name of the DNS + :return: JSON dns model or HTTP 404 + :rtype: collections.OrderedDict """ - health_controller = self._driver.manager.health_controller try: @@ -54,14 +54,14 @@ class StorageHealthController(base.Controller, hooks.HookController): @pecan.expose('json') def get(self, storage_name): - """GET. + """ Returns the health of storage - - :param storage_name - :returns JSON storage model or HTTP 404 + :type storage_name: str + :param storage_name: Name of the storage + :return: JSON storage model or HTTP 404 + :rtype: collections.OrderedDict """ - health_controller = self._driver.manager.health_controller try: @@ -79,14 +79,14 @@ class DistributedTaskHealthController(base.Controller, hooks.HookController): @pecan.expose('json') def get(self, distributed_task_name): - """GET. + """ Returns the health of distributed task manager - - :param distributed_task_name - :returns JSON storage model or HTTP 404 + :type distributed_task_name: str + :param distributed_task_name: Name of the distributed task + :return: JSON Distributed task model or HTTP 404 + :rtype: collections.OrderedDict """ - health_controller = self._driver.manager.health_controller try: @@ -103,7 +103,15 @@ class ProviderHealthController(base.Controller, hooks.HookController): @pecan.expose('json') def get(self, provider_name): - """Returns the health of provider.""" + """ + + Returns the health of provider + :type provider_name: str + :param provider_name: Name of the provider + :return: JSON provider model or HTTP 404 + :rtype: collections.OrderedDict + """ + health_controller = self._driver.manager.health_controller @@ -120,7 +128,12 @@ class HealthController(base.Controller, hooks.HookController): @pecan.expose('json') def get(self): - """Returns the health of storage and providers.""" + """ + Returns the health of storage and providers + :return: JSON response or HTTP 503 + :rtype: collections.OrderedDict + """ + health_controller = self._driver.manager.health_controller diff --git a/poppy/transport/pecan/controllers/v1/home.py b/poppy/transport/pecan/controllers/v1/home.py index 57c2fb54..f5c0ead5 100644 --- a/poppy/transport/pecan/controllers/v1/home.py +++ b/poppy/transport/pecan/controllers/v1/home.py @@ -26,5 +26,10 @@ class HomeController(base.Controller, hooks.HookController): @pecan.expose('json') def get(self): + """ + + Root path for urls '/home/' + :return: A router to route the urls further + """ home_controller = self._driver.manager.home_controller return home_controller.get() diff --git a/poppy/transport/pecan/controllers/v1/ping.py b/poppy/transport/pecan/controllers/v1/ping.py index abcf2c95..1501ffbb 100644 --- a/poppy/transport/pecan/controllers/v1/ping.py +++ b/poppy/transport/pecan/controllers/v1/ping.py @@ -28,6 +28,10 @@ class PingController(base.Controller, hooks.HookController): @pecan.expose('json') def get(self): + """ + Ping the health controller + :return: Pecan response with http status 204 or 503 + """ health_controller = self._driver.manager.health_controller health_map, is_alive = health_controller.ping_check() if is_alive: diff --git a/poppy/transport/pecan/controllers/v1/services.py b/poppy/transport/pecan/controllers/v1/services.py index 43ab6bb0..d8818115 100644 --- a/poppy/transport/pecan/controllers/v1/services.py +++ b/poppy/transport/pecan/controllers/v1/services.py @@ -13,6 +13,53 @@ # See the License for the specific language governing permissions and # limitations under the License. +"""Pecan router to map service related urls. + +Each class in this module maps to different urls. + + - Purge service [:class:`ServiceAssetsController` ] + - Retrieve analytical metrics for domain [:class:`ServicesAnalyticsController` ] + - GET/Create/Delete/Update service [:class:`ServicesController` ] + +Mappings:- + +Each HTTP method is mapped to Pecan's method as shown below. + +Pecan Method HttpMethod/URL +------------- ------------------ +get_one -> GET /services/ +get_all -> GET /services/ +get -> GET /services/ or GET /services/ +post -> POST /services/ +put -> PUT /services/ +delete -> DELETE /services/ + +Example:- + + - The URL ``{{host}}/v1.0/services/ with HTTP POST`` will be received by + :py:func:`ServicesController.post`` + - The URL ``{{host}}/v1.0/services/ with HTTP POST`` will be received by + :py:func:`ServicesController.get_all`` + +Each class in the module have Enabled Context Hook and Errors Hook. +`Context Hook` checks that `X-Project-ID` and `X-Auth-Token` +are present in the request payload and constructs `base_url`. +`Errors Hook` handles any errors during the request. + +``validate`` decorators are injected into each method of the class +to validate the payload and other dependencies. If any of the +validation fails, operation will be aborted and ``Errors Hook`` +will be responsible for sending error response to the user. + +After doing the base level validations on the request +payload, calls will be delegated to Manager layer to +process the request. The Default Manager layer has +various controllers to handle these requests. + +For more details on how the top level URL mapping is done, refer to + :py:mod:`poppy/poppy/transport/pecan/driver.py` +""" + import ast import json import uuid @@ -45,6 +92,7 @@ class ServiceAssetsController(base.Controller, hooks.HookController): + """Controller to purge the contents of service""" __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] @@ -55,6 +103,29 @@ class ServiceAssetsController(base.Controller, hooks.HookController): helpers.abort_with_message) ) def delete(self, service_id): + """Purge contents of a service. + + Purge the content when it is in the provider + network. Based on the ``purge_url`` in the request, + content will be purged. + + For example: When the ``purge_url`` is set to + ``/images/*``, all the images present in the + the path will be purged. + + Note that if the ``purge_url`` is None, all the + contents of the service will be purged. + + The default manager will invoke ``purge taskflow`` + to do this operation. + + :param unicode service_id: ID of the service + :return: Pecan's 200 response if the ``purge taskflow`` + is successfully invoked. 404 if the service not + found. 400 response if the request payload is + incompatible. + :rtype: pecan.Response + """ purge_url = pecan.request.GET.get('url', '/*') purge_all = pecan.request.GET.get('all', False) hard = pecan.request.GET.get('hard', 'True') @@ -96,6 +167,9 @@ def delete(self, service_id): class ServicesAnalyticsController(base.Controller, hooks.HookController): + """Controller to process and return Metrics for a given + service. + """ __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] @@ -110,8 +184,28 @@ class ServicesAnalyticsController(base.Controller, hooks.HookController): stoplight_helpers.pecan_getter) ) def get(self, service_id): - '''Get Analytics By Domain Data''' + """Get Metrics for a given service ID. + + The below keys are expected in the payload. + - metricType + - startTime + - endTime + - metrics_controller + + Example return: + Pecan will serialize the below dict and sends along + with 200 response. + + ``{'provider': '', 'flavor':'', 'domain':'', 'metricType': {}}`` + + :param unicode service_id: ID of the service + :return: Pecan's 200 response if successfully retrieved + analytics for the service. 404 response if the service + was not found or Provider details not found. 500 response + for general server side exceptions. + :rtype: pecan.Response + """ call_args = getattr(pecan.request.context, "call_args") domain = call_args.pop('domain') @@ -134,6 +228,11 @@ def get(self, service_id): class ServicesController(base.Controller, hooks.HookController): + """Handles typical CRUD operations on Services. + + When Manager layer returns an output/ or any Exception + raised, It serializes the responses and returns to user. + """ __hooks__ = [poppy_hooks.Context(), poppy_hooks.Error()] @@ -153,6 +252,24 @@ def __init__(self, driver): @pecan.expose('json') def get_all(self): + """Get all the services in Poppy. + + Example URL to create service: + -``{{host}}/v1.0/services/`` with HTTP method as `GET` + + There is a limit on number of services that can + be fetched at a time. This limit can be configured + through ``poppy.conf`` by setting an integer value + for ``max_services_per_page``. + + :return: Dictionary containing lists of links and + serialized Service objects + :rtype: dict + :raise ValueError: If the request `limit` value + is more than configured ``max_services_per_page`` + Or If the request `marker` is not + a valid UUID + """ marker = pecan.request.GET.get('marker', None) limit = pecan.request.GET.get('limit', 10) try: @@ -202,6 +319,19 @@ def get_all(self): helpers.abort_with_message) ) def get_one(self, service_id): + """Get a Service details by its ID. + + Example URL to get service: + -``{{host}}/v1.0/services/`` + with HTTP method as `GET` + + :param unicode service_id: Id of the service + + :return: Service object serialized into OrderedDict + :rtype: collections.OrderedDict + :raise ValueError: If there was not any service + for the given ID. + """ services_controller = self._driver.manager.services_controller try: service_obj = services_controller.get_service( @@ -220,6 +350,68 @@ def get_one(self, service_id): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def post(self): + """Create a new service. + + Example URL to create service: + -``{{host}}/v1.0/services/`` with HTTP method as `POST` + + An example payload for this POST request: + + .. code-block:: python + + { + "name": "ServiceName0001", + "domains": + [ + { + "domain": "test.domain.com", + "protocol": "http" + } + ], + "origins": + [ + { + "origin": "www.example.com", + "port": 80, + "ssl": false + } + ], + "caching": [ + { + "name": "default", + "ttl": 3600 + } + ], + "flavor_id": "premium", + "restrictions": [ + ] + } + + The payload must have at least one domain and one origin. + The request to create a new service will be + delegated to Default Manager service controller + that does the below. + + - A service dictionary gets created in Cassandra + - Async tasks to create DNS mappings + - Async tasks to create provider policies + - Based on `Enqueue` flag, request to create SSL + certificate will be enqueued in mod_san_queue or + a certificate will be created for HTTPS domains. + Enqueue flag is set to `True` by default. + + Create service will be aborted if .. + - All the available shards are exhausted + - No flavor is created in Cassandra + - If the service name already exists + - Services count is exceeding the limit + + In all the above cases, Pecan sends a 400 error. + + :return: Pecan's 200 response if the service was + successfully created, 400 response otherwise. + :rtype: pecan.Response + """ services_controller = self._driver.manager.services_controller service_json_dict = json.loads(pecan.request.body.decode('utf-8')) service_id = None @@ -254,6 +446,22 @@ def post(self): helpers.abort_with_message) ) def delete(self, service_id): + """Delete a service for a given service ID. + + Example URL to create service: + -``{{host}}/v1.0/service/`` + with HTTP method as `DELETE` + + Deleting service will trigger the below tasks + - Deleting service dictionary from Cassandra + - Deleting DNS mappings + - Deleting associated certificates for each domain in the service + + :param unicode service_id: Id of the service to delete + :return: Pecan's 202 response if the delete operation was + successful. Else, Pecan's 400 response will be returned. + :rtype: pecan.Response + """ services_controller = self._driver.manager.services_controller try: @@ -276,6 +484,37 @@ def delete(self, service_id): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def patch_one(self, service_id): + """Update service. + + Example URL to create service: + -``{{host}}/v1.0/services/`` + with HTTP method as `PATCH` + + For payload, refer to :meth:`post()`. + + Updating service is two-step process. It filters out + the payload and detects newly added domains, deleted + old domains. For newly added domains, it follows the + :meth:`post()` workflow. For deleted domains it + follows :meth:`delete()` workflow. If the service + update involves anything other than domains (ex: + renaming the service etc..) it updated the service + dictionary in cassandra. + + The update operation will be aborted if .. + - If any validations failed in request Payload + - No flavor detected in cassandra + - No service found + - Conflict names while renaming service + - If the service is in invalid states + - Exhausted shard domains + + :param unicode service_id: ID of the service to update + :return: Pecan's 200 response if the service was updated + successfully. 404 response if the service was not found. + In other cases, 400 response will be returned. + :rtype: pecan.Response + """ service_updates = json.loads(pecan.request.body.decode('utf-8')) services_controller = self._driver.manager.services_controller diff --git a/poppy/transport/pecan/controllers/v1/ssl_certificates.py b/poppy/transport/pecan/controllers/v1/ssl_certificates.py index d45d05d0..35a86bcf 100644 --- a/poppy/transport/pecan/controllers/v1/ssl_certificates.py +++ b/poppy/transport/pecan/controllers/v1/ssl_certificates.py @@ -45,6 +45,12 @@ class SSLCertificateController(base.Controller, hooks.HookController): helpers.abort_with_message, stoplight_helpers.pecan_getter)) def post(self): + """ + + Create a new SSL certificate + :return: Pecan response with http status 202 or 400 + :rtype: pecan.Response + """ ssl_certificate_controller = ( self._driver.manager.ssl_certificate_controller) @@ -72,6 +78,14 @@ def post(self): helpers.abort_with_message) ) def delete(self, domain_name): + """ + + Delete an SSL certificate for the domain + :type domain_name: str + :param domain_name: Name of the domain for which the certificates needs to be deleted + :return: Pecan response with http status code 202 or 400 + :rtype: pecan.Response + """ # For now we only support 'san' cert type cert_type = pecan.request.GET.get('cert_type', 'san') @@ -94,7 +108,13 @@ def delete(self, domain_name): helpers.abort_with_message) ) def get_one(self, domain_name): - + """ + Get certificates for a domain name + :type domain_name: str + :param domain_name: Name of the domain + :return: Pecan response with 404 or list of JSON certificate model + :rtype: collections.OrderedDict + """ certificate_controller = \ self._driver.manager.ssl_certificate_controller total_cert_info = [] diff --git a/poppy/transport/pecan/hooks/context.py b/poppy/transport/pecan/hooks/context.py index a9ac517f..5a821b94 100644 --- a/poppy/transport/pecan/hooks/context.py +++ b/poppy/transport/pecan/hooks/context.py @@ -32,6 +32,12 @@ def __init__(self, **kwargs): class ContextHook(hooks.PecanHook): def before(self, state): + """ + + Pre-request validations for Pecan requests + + :param state: Pecan request state + """ context_kwargs = {} if 'X-Project-ID' in state.request.headers: diff --git a/poppy/transport/pecan/hooks/error.py b/poppy/transport/pecan/hooks/error.py index f58b3188..681b5734 100644 --- a/poppy/transport/pecan/hooks/error.py +++ b/poppy/transport/pecan/hooks/error.py @@ -29,7 +29,9 @@ class ErrorHook(hooks.PecanHook): '''Intercepts all errors during request fulfillment and logs them.''' def on_error(self, state, exception): - '''Fires off when an error happens during a request. + """ + + Fires off when an error happens during a request. :param state: The Pecan state for the current request. :type state: pecan.core.state @@ -37,7 +39,8 @@ def on_error(self, state, exception): :type exception: Exception :returns: webob.Response -- JSON response with the error message. - ''' + :rtype: webob.Response + """ exception_payload = { 'status': 500, } diff --git a/poppy/transport/pecan/models/request/cachingrule.py b/poppy/transport/pecan/models/request/cachingrule.py index 35b5e6d4..c3c17df8 100644 --- a/poppy/transport/pecan/models/request/cachingrule.py +++ b/poppy/transport/pecan/models/request/cachingrule.py @@ -18,6 +18,17 @@ def load_from_json(json_data): + """ + Deserialize CachingRule object from JSON + Example : + from poppy.transport.pecan.models.request import cachingrule + CachingRule_obj = cachingrule.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of CachingRule object related key, values + :return: CachingRule object loaded from json_data + :rtype: CachingRule + """ name = json_data.get('name') ttl = json_data.get('ttl') rules = json_data.get('rules', []) diff --git a/poppy/transport/pecan/models/request/domain.py b/poppy/transport/pecan/models/request/domain.py index 944a99f7..8c51454d 100644 --- a/poppy/transport/pecan/models/request/domain.py +++ b/poppy/transport/pecan/models/request/domain.py @@ -18,6 +18,17 @@ def load_from_json(json_data): + """ + Deserialize Domain object from JSON + Example : + from poppy.transport.pecan.models.request import domain + Domain_obj = domain.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of Domain object related key, values + :return: Domain object loaded from json_data + :rtype: Domain + """ domain_name = json_data.get('domain') protocol = json_data.get('protocol', 'http') certification_option = json_data.get('certificate', None) diff --git a/poppy/transport/pecan/models/request/flavor.py b/poppy/transport/pecan/models/request/flavor.py index 95638a25..4622e607 100644 --- a/poppy/transport/pecan/models/request/flavor.py +++ b/poppy/transport/pecan/models/request/flavor.py @@ -17,6 +17,18 @@ def load_from_json(json_data): + """ + + Deserialize Flavor object from JSON + Example : + from poppy.transport.pecan.models.request import flavor + Flavor_obj = flavor.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of Flavor object related key, values + :return: Flavor object loaded from json_data + :rtype: Flavor + """ flavor_id = json_data['id'] providers = [] diff --git a/poppy/transport/pecan/models/request/log_delivery.py b/poppy/transport/pecan/models/request/log_delivery.py index e51a38d9..eee3d2cd 100644 --- a/poppy/transport/pecan/models/request/log_delivery.py +++ b/poppy/transport/pecan/models/request/log_delivery.py @@ -17,6 +17,18 @@ def load_from_json(json_data): + """ + + Deserialize LogDelivery object from JSON + Example : + from poppy.transport.pecan.models.request import log_delivery + LogDelivery_obj = log_delivery.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of LogDelivery object related key, values + :return: LogDelivery object loaded from json_data + :rtype: LogDelivery + """ enabled = json_data.get("enabled", False) result = ld.LogDelivery(enabled) return result diff --git a/poppy/transport/pecan/models/request/origin.py b/poppy/transport/pecan/models/request/origin.py index a1f2bfe3..678993b6 100644 --- a/poppy/transport/pecan/models/request/origin.py +++ b/poppy/transport/pecan/models/request/origin.py @@ -18,6 +18,17 @@ def load_from_json(json_data): + """ + Deserialize Origin object from JSON + Example : + from poppy.transport.pecan.models.request import origin + Origin_obj = origin.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of Origin object related key, values + :return: Origin object loaded from json_data + :rtype: Origin + """ origin_name = json_data.get("origin") origin_name = origin_name.rstrip("/") hostheadertype = json_data.get("hostheadertype", "domain") diff --git a/poppy/transport/pecan/models/request/provider_details.py b/poppy/transport/pecan/models/request/provider_details.py index 55f21cb8..60b186b2 100644 --- a/poppy/transport/pecan/models/request/provider_details.py +++ b/poppy/transport/pecan/models/request/provider_details.py @@ -17,6 +17,19 @@ def load_from_json(json_data): + """ + + Deserialize ProviderDetail object from JSON + Example : + from poppy.transport.pecan.models.request import provider_details + ProviderDetail_obj = provider_details.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of ProviderDetail object related key, values + :return: ProviderDetail object loaded from json_data + :rtype: ProviderDetail + + """ access_urls = json_data.get("access_urls") error_info = json_data.get("error_info", ) provider_service_id = json_data.get("id") diff --git a/poppy/transport/pecan/models/request/restriction.py b/poppy/transport/pecan/models/request/restriction.py index 423472d1..2cf97f57 100644 --- a/poppy/transport/pecan/models/request/restriction.py +++ b/poppy/transport/pecan/models/request/restriction.py @@ -18,6 +18,18 @@ def load_from_json(json_data): + """ + + Deserialize Restriction object from JSON + Example : + from poppy.transport.pecan.models.request import restriction + Restriction_obj = restriction.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of Restriction object related key, values + :return: Restriction object loaded from json_data + :rtype: Restriction + """ name = json_data.get('name') access = json_data.get('access', 'whitelist') res = restriction.Restriction(name, access) diff --git a/poppy/transport/pecan/models/request/rule.py b/poppy/transport/pecan/models/request/rule.py index 9a7bcc98..49944f4d 100644 --- a/poppy/transport/pecan/models/request/rule.py +++ b/poppy/transport/pecan/models/request/rule.py @@ -17,6 +17,18 @@ def load_from_json(json_data): + """ + + Deserialize Rule object from JSON + Example : + from poppy.transport.pecan.models.request import rule + Rule_obj = rule.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of Rule object related key, values + :return: Rule object loaded from json_data + :rtype: Rule + """ name = json_data.get('name', None) res = rule.Rule(name) res.referrer = json_data.get('referrer', None) diff --git a/poppy/transport/pecan/models/request/service.py b/poppy/transport/pecan/models/request/service.py index 46a930eb..e93ccc99 100644 --- a/poppy/transport/pecan/models/request/service.py +++ b/poppy/transport/pecan/models/request/service.py @@ -24,6 +24,18 @@ def load_from_json(json_data): + """ + + Deserialize Service object from JSON + Example : + from poppy.transport.pecan.models.request import service + Service_obj = service.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of Service object related key, values + :return: Service object loaded from json_data + :rtype: Service + """ service_id = json_data.get('service_id', uuid.uuid4()) name = json_data.get("name") diff --git a/poppy/transport/pecan/models/request/ssl_certificate.py b/poppy/transport/pecan/models/request/ssl_certificate.py index 7a189a40..70d58abe 100644 --- a/poppy/transport/pecan/models/request/ssl_certificate.py +++ b/poppy/transport/pecan/models/request/ssl_certificate.py @@ -17,6 +17,18 @@ def load_from_json(json_data): + """ + + Deserialize SSLCertificate object from JSON + Example : + from poppy.transport.pecan.models.request import ssl_certificate + SSLCertificate_obj = ssl_certificate.load_from_json({}) + + :type json_data: dict + :param json_data: Dictionary consisting of SSLCertificate object related key, values + :return: SSLCertificate object loaded from json_data + :rtype: SSLCertificate + """ flavor_id = json_data.get("flavor_id") domain_name = json_data.get("domain_name") cert_type = json_data.get("cert_type") diff --git a/poppy/transport/pecan/models/response/cachingrules.py b/poppy/transport/pecan/models/response/cachingrules.py index 84327e5d..0461f41e 100644 --- a/poppy/transport/pecan/models/response/cachingrules.py +++ b/poppy/transport/pecan/models/response/cachingrules.py @@ -22,8 +22,17 @@ class Model(collections.OrderedDict): + """ + Serialize a cachingrule object into an OrderedDict. + Can be used to send the service details back to client. - 'response class for CachingRules' + Example : + from poppy.model.helpers import cachingrule + from poppy.transport.pecan.models.response import cachingrule as cachingrule_response + cachingrule_response_obj = cachingrule.CachingRule('name', 'ttl') + return cachingrule_response.Model(cachingrule_response_obj, self) + + """ def __init__(self, caching): super(Model, self).__init__() diff --git a/poppy/transport/pecan/models/response/domain.py b/poppy/transport/pecan/models/response/domain.py index 80a08b7f..03f7cf8f 100644 --- a/poppy/transport/pecan/models/response/domain.py +++ b/poppy/transport/pecan/models/response/domain.py @@ -21,8 +21,19 @@ class Model(collections.OrderedDict): + """ + + Serialize a domain object into an OrderedDict. + Can be used to send the service details back to client. + + Example : + from poppy.model.helpers import domain + from poppy.transport.pecan.models.response import domain as domain_response + domain_response_obj = domain.Domain('domain') + return domain_response.Model(domain_response_obj, self) + + """ - 'response class for Domain.' def __init__(self, domain): super(Model, self).__init__() diff --git a/poppy/transport/pecan/models/response/flavor.py b/poppy/transport/pecan/models/response/flavor.py index 04cbfa44..fd477e42 100644 --- a/poppy/transport/pecan/models/response/flavor.py +++ b/poppy/transport/pecan/models/response/flavor.py @@ -22,7 +22,17 @@ class Model(collections.OrderedDict): + """ + Serialize a flavor object into an OrderedDict. + Can be used to send the flavor details back to client. + Example : + from poppy.model import flavor + from poppy.transport.pecan.models.response import flavor as flavor_response + flavor_obj = flavor.Flavor('Premium', []) + return flavor_response.Model(flavor_obj, self) + + """ def __init__(self, flavor, controller): super(Model, self).__init__() diff --git a/poppy/transport/pecan/models/response/health.py b/poppy/transport/pecan/models/response/health.py index 35ced4eb..46c17adb 100644 --- a/poppy/transport/pecan/models/response/health.py +++ b/poppy/transport/pecan/models/response/health.py @@ -22,6 +22,13 @@ class DNSModel(collections.OrderedDict): + """ + + Serialize DNS Model. + Example: + from poppy.transport.pecan.models.response import health + return health.DNSModel(True) + """ def __init__(self, is_alive): super(DNSModel, self).__init__() @@ -32,6 +39,13 @@ def __init__(self, is_alive): class StorageModel(collections.OrderedDict): + """ + + Serialize Storage Model. + Example: + from poppy.transport.pecan.models.response import health + return health.StorageModel(True) + """ def __init__(self, is_alive): super(StorageModel, self).__init__() @@ -42,6 +56,12 @@ def __init__(self, is_alive): class DistributedTaskModel(collections.OrderedDict): + """ + Serialize Distributed task Model. + Example: + from poppy.transport.pecan.models.response import health + return health.DistributedTaskModel(True) + """ def __init__(self, is_alive): super(DistributedTaskModel, self).__init__() @@ -52,6 +72,13 @@ def __init__(self, is_alive): class ProviderModel(collections.OrderedDict): + """ + + Serialize Provider Model. + Example: + from poppy.transport.pecan.models.response import health + return health.ProviderModel(True) + """ def __init__(self, is_alive): super(ProviderModel, self).__init__() @@ -62,6 +89,13 @@ def __init__(self, is_alive): class HealthModel(collections.OrderedDict): + """ + + Serialize Health Model. + Example: + from poppy.transport.pecan.models.response import health + return health.HealthModel(True) + """ def __init__(self, controller, health_map): super(HealthModel, self).__init__() diff --git a/poppy/transport/pecan/models/response/link.py b/poppy/transport/pecan/models/response/link.py index 5ccbe291..bcf50719 100644 --- a/poppy/transport/pecan/models/response/link.py +++ b/poppy/transport/pecan/models/response/link.py @@ -20,7 +20,14 @@ class Model(collections.OrderedDict): - 'response class for Link.' + """ + + Serialize links. + Example: + from poppy.transport.pecan.models.response import link + link_obj = link.Model('href', 'rel') + return link_obj + """ def __init__(self, href, rel): super(Model, self).__init__() self['href'] = href diff --git a/poppy/transport/pecan/models/response/log_delivery.py b/poppy/transport/pecan/models/response/log_delivery.py index dda782ca..c07bef7e 100644 --- a/poppy/transport/pecan/models/response/log_delivery.py +++ b/poppy/transport/pecan/models/response/log_delivery.py @@ -21,7 +21,17 @@ class Model(collections.OrderedDict): - 'Response class for Log Delivery' + """ + Serialize a log_delivery object into an OrderedDict. + Can be used to send the service details back to client. + + Example : + from poppy.model import log_delivery + from poppy.transport.pecan.models.response import log_delivery as log_delivery_response + log_delivery_obj = log_delivery.LogDelivery() + return log_delivery_response.Model(log_delivery_obj, self) + + """ def __init__(self, log_delivery): super(Model, self).__init__() diff --git a/poppy/transport/pecan/models/response/origin.py b/poppy/transport/pecan/models/response/origin.py index c6d6bae9..e7f8a8cc 100644 --- a/poppy/transport/pecan/models/response/origin.py +++ b/poppy/transport/pecan/models/response/origin.py @@ -22,8 +22,19 @@ class Model(collections.OrderedDict): + """ + + Serialize an Origin object into an OrderedDict. + Can be used to send the service details back to client. + + Example : + from poppy.model.helpers import origin + from poppy.transport.pecan.models.response import origin as origin_response + origin_response_obj = origin.Origin('origin') + return origin_response.Model(origin_response_obj, self) + + """ - 'response class for Origin' def __init__(self, origin): super(Model, self).__init__() diff --git a/poppy/transport/pecan/models/response/restriction.py b/poppy/transport/pecan/models/response/restriction.py index fd06b544..3ca63352 100644 --- a/poppy/transport/pecan/models/response/restriction.py +++ b/poppy/transport/pecan/models/response/restriction.py @@ -23,8 +23,19 @@ class Model(collections.OrderedDict): + """ + + Serialize a Restriction object into an OrderedDict. + Can be used to send the service details back to client. + + Example : + from poppy.model.helpers import restriction + from poppy.transport.pecan.models.response import restriction as restriction_response + restriction_response_obj = restriction.Restriction('name') + return restriction_response.Model(restriction_response_obj, self) + + """ - 'response class for Restriction' def __init__(self, restriction): super(Model, self).__init__() diff --git a/poppy/transport/pecan/models/response/rule.py b/poppy/transport/pecan/models/response/rule.py index 68644a1c..4ab2c46b 100644 --- a/poppy/transport/pecan/models/response/rule.py +++ b/poppy/transport/pecan/models/response/rule.py @@ -22,7 +22,18 @@ class Model(collections.OrderedDict): - """response class for rule.""" + """ + + Serialize a Rule object into an OrderedDict. + Can be used to send the service details back to client. + + Example : + from poppy.model.helpers import rule + from poppy.transport.pecan.models.response import rule as rule_response + rule_response_obj = rule.Rule('name') + return rule_response.Model(rule_response_obj, self) + + """ def __init__(self, rule): super(Model, self).__init__() diff --git a/poppy/transport/pecan/models/response/service.py b/poppy/transport/pecan/models/response/service.py index 7e1ac51b..67ae1aa4 100644 --- a/poppy/transport/pecan/models/response/service.py +++ b/poppy/transport/pecan/models/response/service.py @@ -30,7 +30,17 @@ class Model(collections.OrderedDict): - """Service Response Model.""" + """ + Serialize a service object into an OrderedDict. + Can be used to send the service details back to client. + + Example : + from poppy.model import service + from poppy.transport.pecan.models.response import service as service_response + service_obj = service.Service('Service id', 'name', ...) + return service_response.Model(service_obj, self) + + """ def __init__(self, service_obj, controller): super(Model, self).__init__() diff --git a/poppy/transport/pecan/models/response/ssl_certificate.py b/poppy/transport/pecan/models/response/ssl_certificate.py index e6410c93..b19a06fd 100644 --- a/poppy/transport/pecan/models/response/ssl_certificate.py +++ b/poppy/transport/pecan/models/response/ssl_certificate.py @@ -22,7 +22,17 @@ class Model(collections.OrderedDict): - """response class for SSLCertificate.""" + """ + Serialize an ssl_certificate object into an OrderedDict. + Can be used to send the service details back to client. + + Example : + from poppy.model import ssl_certificate + from poppy.transport.pecan.models.response import ssl_certificate as ssl_certificate_response + ssl_certificate_obj = ssl_certificate.SSLCertificate('flavor id', 'domain', ...) + return ssl_certificate_response.Model(ssl_certificate_obj, self) + + """ def __init__(self, ssl_certificate): super(Model, self).__init__() diff --git a/poppy/transport/validators/helpers.py b/poppy/transport/validators/helpers.py index f3a97bf5..6b0177e8 100644 --- a/poppy/transport/validators/helpers.py +++ b/poppy/transport/validators/helpers.py @@ -37,19 +37,34 @@ def req_accepts_json_pecan(request, desired_content_type='application/json'): - # Assume the transport is pecan for now - # for falcon the syntax should actually be: - # request.accept('application/json') + """Check the request's contentType is accepted or not. + + pecan is the currently implemented web framework. + For Falcon use 'application/json' + + :param request: Web request + :type request: pecan.request + + :param desired_content_type: The contentType of the request + :type desired_content_type: str + + :raise: ValidationFailed if the contentType is not accepted + """ + if not request.accept(desired_content_type): raise exceptions.ValidationFailed('Invalid Accept Header') def custom_abort_pecan(errors_info): - """Error_handler for with_schema + """Error_handler for with_schema. Meant to be used with pecan transport. - param errors: a list of validation exceptions + :param errors_info: a list of validation exceptions + :type errors_info: list + + :return: Pecan abort response with http status code 400 + :rtype: pecan.abort """ # TODO(tonytan4ever): gettext support details = dict(errors=[{'message': str(getattr(error, "message", error))} @@ -66,11 +81,19 @@ def with_schema_pecan(request, schema=None, handler=custom_abort_pecan, """Decorate a Pecan/Flask style controller form validation. For an HTTP POST or PUT (RFC2616 unsafe methods) request, the schema is - used to validate the request body. + used to validate the request body + + :param request: Web request + :type request: pecan.request + + :param schema: JSON schema + :type schema: object - :param request: request object - :param schema: A JSON schema. - :param handler: A Function (Error_handler) + :param handler: Error_handler function + :type handler: function + + :return: Decorator to validate the input JSON schema + :rtype: function """ def decorator(f): @@ -104,12 +127,31 @@ def wrapped(*args, **kwargs): def json_matches_service_schema(input_schema): + """Check whether input JSON schema matches with service schema. + + :param input_schema: Input schema dictionary + :type input_schema: Dict[str, bool] + + :return: New function with partial application of + this function and parameters + :rtype: functools.partial + """ return functools.partial( json_matches_service_schema_inner, schema=input_schema) def json_matches_service_schema_inner(request, schema=None): + """Check whether input JSON schema matches with service schema. + + :param request: Web request + :type request: pecan.request + + :param schema: Schema to verify + :type schema: dict + + :raise: ValidationFailed if not a valid JSON string + """ try: data = json.loads(request.body.decode('utf-8')) except ValueError: @@ -119,12 +161,31 @@ def json_matches_service_schema_inner(request, schema=None): def json_matches_flavor_schema(input_schema): + """Check whether input JSON schema matches with flavor schema. + + :param input_schema: Schema to verify + :type input_schema: dict + + :return: New function with partial application of + this function and parameters + :rtype: functools.partial + """ return functools.partial( json_matches_flavor_schema_inner, schema=input_schema) def json_matches_flavor_schema_inner(request, schema=None): + """Check whether input JSON schema matches with flavor schema. + + :param request: Web request + :type request: pecan.request + + :param schema: Schema to verify + :type schema: dict + + :raise: ValidationFailed if not a valid JSON schema + """ try: data = json.loads(request.body.decode('utf-8')) except ValueError: @@ -134,11 +195,27 @@ def json_matches_flavor_schema_inner(request, schema=None): def is_valid_shared_ssl_domain_name(domain_name): + """Check whether the domain name is a valid ssl shared domain. + + :param domain_name: Name of the domain to check + :type domain_name: str + + :return: True if valid, else False + :rtype: bool + """ shared_ssl_domain_regex = '^[a-z0-9][a-z0-9-]{0,62}[a-z0-9]?$' return re.match(shared_ssl_domain_regex, domain_name) is not None def is_valid_tld(domain_name): + """Check whether the domain name is a valid top level dir. + + :param domain_name: Name of the domain to check + :type domain_name: str + + :return: True if valid tld, else False + :rtype: bool + """ try: status = whois.whois(domain_name)['status'] if status is not None or status != '': @@ -158,6 +235,15 @@ def is_valid_tld(domain_name): def is_valid_domain_name(domain_name): + """Check whether the domain name is a valid domain name. + + :param domain_name: Name of the domain to check + :type domain_name: str + + :return: True if valid name, else False + :rtype: bool + """ + # only allow ascii domain_regex = ('^((?=[a-z0-9-]{1,63}\.)[a-z0-9]+' '(-[a-z0-9]+)*\.)+[a-z]{2,63}$') @@ -168,6 +254,14 @@ def is_valid_domain_name(domain_name): def is_valid_domain(domain): + """Check whether the domain is a valid domain. + + :param domain: Domain to check + :type domain: poppy.model.helpers.domain.Domain + + :return: True if valid domain, else False + :rtype: bool + """ domain_name = domain.get('domain') if (domain.get('protocol') == 'https' and domain['certificate'] == u'shared'): @@ -177,6 +271,14 @@ def is_valid_domain(domain): def is_valid_ip_address(ip_address): + """Check whether ip_address is valid + + :param ip_address: IP address to check + :type ip_address: str + + :return: True if valid IP, else False + :rtype: bool + """ ipv4_regex = ('^(((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]' '|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$') # ipv6 is not used to validate origin since akamai does not support ipv6 @@ -199,12 +301,27 @@ def is_valid_ip_address(ip_address): def is_valid_origin(origin): + """Check whether origin is valid + + :param origin: Origin to check + :type origin: poppy.model.helpers.origin.Origin + + :return: True if valid Origin, else False + :rtype: bool + """ return (is_valid_domain_name(origin.get('origin')) or is_valid_ip_address(origin.get('origin'))) @decorators.validation_function def is_valid_project_id(project_id): + """Check whether Project id is valid + + :param project_id: Project id to check + :type project_id: str + + :raise: ValidationFailed if not a valid Project id + """ project_id_regex = '^[a-zA-Z0-9_\\-\\.]{1,256}$' if not re.match(project_id_regex, project_id): raise exceptions.ValidationFailed('Invalid ' @@ -214,6 +331,13 @@ def is_valid_project_id(project_id): @decorators.validation_function def is_valid_akamai_setting(setting): + """Check whether setting is valid Akamai setting + + :param setting: setting to check + :type setting: str + + :raise: ValidationFailed if not a valid setting + """ if setting not in ['san_cert_hostname_limit']: raise exceptions.ValidationFailed( 'Invalid akamai setting : {0}'.format(setting) @@ -222,6 +346,13 @@ def is_valid_akamai_setting(setting): @decorators.validation_function def is_valid_domain_by_name_or_akamai_setting(query): + """Check whether Valid domain by name ot setting. + + :param query: The query to check + :type query: str + + :raise: ValidationFailed if not valid + """ valid_domain = True domain_exc = None valid_setting = True @@ -244,6 +375,14 @@ def is_valid_domain_by_name_or_akamai_setting(query): def is_root_domain(domain): + """Check whether the domain is root domain. + + :param domain: Domain to check + :type domain: poppy.model.helpers.domain.Domain + + :return: True if root domain else False + :rtype: bool + """ domain_name = domain.get('domain') # if the domain contains four or more segments, it a not a root domain @@ -275,6 +414,16 @@ def is_root_domain(domain): def is_valid_service_configuration(service, schema): + """Check whether the service configuration is valid. + + :param service: The service object + :type service: poppy.model.helpers.domain.Domain + + :param schema: JSON schema + :type schema: dict + + :raise: ValidationFailed if not a valid configuration + """ errors_list = list() if schema is not None: errors_list = list( @@ -492,6 +641,13 @@ def is_valid_service_configuration(service, schema): @decorators.validation_function def is_valid_service_id(service_id): + """Check service id is valid or not. + + :param service_id: Service id to check + :type service_id: str + + :raise: ValidationFailed if not a valid service id + """ try: uuid.UUID(service_id) except ValueError: @@ -500,6 +656,13 @@ def is_valid_service_id(service_id): @decorators.validation_function def is_valid_domain_by_name(domain_name): + """Check domain is valid by name + + :param domain_name: Name of the domain + :type domain_name: str + + :raise: ValidationFailed if not a valid domain + """ domain_regex = ('^((?=[a-z0-9-]{1,63}\.)[a-z0-9]+' '(-[a-z0-9]+)*\.)+[a-z]{2,63}$') # allow Punycode @@ -525,6 +688,13 @@ def is_valid_domain_by_name(domain_name): @decorators.validation_function def is_valid_provider_url(request): + """Check Provider url is valid or not. + + :param request: Web request + :type request: pecan.request + + :raise: ValidationFailed if not a valid provider url + """ provider_url = request.GET.get("provider_url", None) if not provider_url: @@ -548,6 +718,16 @@ def is_valid_provider_url(request): def is_valid_flavor_configuration(flavor, schema): + """Check Flavor configuration is valid or not. + + :param flavor: Flavor details + :type flavor: poppy.model.flavor.Flavor + + :param schema: JSON schema + :type schema: dict + + :raise: ValidationFailed if not a valid flavor configuration + """ if schema is not None: errors_list = list( jsonschema.Draft3Validator(schema).iter_errors(flavor)) @@ -571,6 +751,13 @@ def is_valid_flavor_id(flavor_id): @decorators.validation_function def is_valid_analytics_request(request): + """Check Analytics request is valid or not. + + :param request: Web request + :type request: pecan.request + + :raise: ValidationFailed if not a valid analytics request + """ default_end_time = datetime.datetime.utcnow() default_start_time = (datetime.datetime.utcnow() - datetime.timedelta(days=1)) @@ -630,6 +817,13 @@ def is_valid_analytics_request(request): @decorators.validation_function def is_valid_service_status(request): + """Check service status is valid or not. + + :param request: Web request + :type request: pecan.request + + :raise: ValidationFailed if not a valid service status + """ status = request.GET.get('status', "") # NOTE(TheSriram): The statuses listed below are the currently @@ -655,6 +849,13 @@ def is_valid_service_status(request): @decorators.validation_function def is_valid_certificate_status(request): + """Check certificate status is valid or not. + + :param request: Web request + :type request: pecan.request + + :raise: ValidationFailed if not a valid certificate status + """ status = request.GET.get('status', "") # NOTE(TheSriram): The statuses listed below are the currently @@ -679,6 +880,14 @@ def is_valid_certificate_status(request): def abort_with_message(error_info): + """Pecan's abort web response utility. + + :param error_info: Error information + :type error_info: dict + + :return: Pecan web response with http status code 400 + :rtype: pecan.abort + """ pecan.abort(400, detail=util.help_escape( getattr(error_info, "message", "")), headers={'Content-Type': "application/json"}) diff --git a/poppy/transport/validators/schema_base.py b/poppy/transport/validators/schema_base.py index dd48621b..e30aeaf5 100644 --- a/poppy/transport/validators/schema_base.py +++ b/poppy/transport/validators/schema_base.py @@ -23,22 +23,22 @@ class SchemaBase(object): @classmethod def get_schema(cls, resource_name, operation): - """Returns the schema for an operation + """Returns the schema for an operation. :param resource_name: Operation for which resource need - to be validated. - :type operation: `six.text_type` + to be validated + :type resource_name: str :param operation: Operation for which params need - to be validated. + to be validated. :type operation: `six.text_type` - :returns: Operation's schema + :return: Operation's schema :rtype: dict - :raises: `errors.InvalidResource` if the resource - does not exist and `errors.InvalidOperation` if the operation - does not exist + :raise: + InvalidResource if the resource does not exist, + InvalidOperation if the operation does not exist """ try: resource_schemas = cls.schema[resource_name] diff --git a/poppy/transport/validators/schemas/background_jobs.py b/poppy/transport/validators/schemas/background_jobs.py index 426ae7b0..983370ee 100644 --- a/poppy/transport/validators/schemas/background_jobs.py +++ b/poppy/transport/validators/schemas/background_jobs.py @@ -18,7 +18,7 @@ class BackgroundJobSchema(schema_base.SchemaBase): - """JSON Schema validation for /admin/provider/akamai/background_job""" + """JSON Schema validation for /admin/provider/akamai/background_job.""" schema = { 'background_jobs': { diff --git a/poppy/transport/validators/schemas/domain_migration.py b/poppy/transport/validators/schemas/domain_migration.py index be397fdb..cafe5778 100644 --- a/poppy/transport/validators/schemas/domain_migration.py +++ b/poppy/transport/validators/schemas/domain_migration.py @@ -18,7 +18,7 @@ class DomainMigrationServiceSchema(schema_base.SchemaBase): - '''JSON Schema validation for /admin/provider/akamai/service''' + """JSON Schema validation for /admin/provider/akamai/service.""" schema = { 'domain_migration': { diff --git a/poppy/transport/validators/schemas/service.py b/poppy/transport/validators/schemas/service.py index 237cb904..fb91bad4 100644 --- a/poppy/transport/validators/schemas/service.py +++ b/poppy/transport/validators/schemas/service.py @@ -20,7 +20,7 @@ class ServiceSchema(schema_base.SchemaBase): - '''JSON Schema validation for /service.''' + """JSON Schema validation for /service.""" schema = { 'service': { diff --git a/poppy/transport/validators/schemas/service_limit.py b/poppy/transport/validators/schemas/service_limit.py index aecc7b4c..b9ae7582 100644 --- a/poppy/transport/validators/schemas/service_limit.py +++ b/poppy/transport/validators/schemas/service_limit.py @@ -18,7 +18,7 @@ class ServiceLimitSchema(schema_base.SchemaBase): - '''JSON Schema validation for /admin/limits/{project_id}.''' + """JSON Schema validation for /admin/limits/{project_id}.""" schema = { 'service_limit': { diff --git a/poppy/transport/validators/schemas/ssl_certificate.py b/poppy/transport/validators/schemas/ssl_certificate.py index 533c7cfa..4f9683db 100644 --- a/poppy/transport/validators/schemas/ssl_certificate.py +++ b/poppy/transport/validators/schemas/ssl_certificate.py @@ -20,7 +20,7 @@ class SSLCertificateSchema(schema_base.SchemaBase): - '''JSON Schema validation for /ssl_certificate.''' + """JSON Schema validation for /ssl_certificate.""" schema = { 'ssl_certificate': { diff --git a/poppy/transport/validators/stoplight/helpers.py b/poppy/transport/validators/stoplight/helpers.py index e013d493..6efdf35c 100644 --- a/poppy/transport/validators/stoplight/helpers.py +++ b/poppy/transport/validators/stoplight/helpers.py @@ -19,6 +19,6 @@ def pecan_getter(parm): - """pecan getter.""" + """pecan getter""" pecan_module = __import__('pecan', globals(), locals(), ['request']) return getattr(pecan_module, 'request') diff --git a/poppy/transport/validators/stoplight/rule.py b/poppy/transport/validators/stoplight/rule.py index f9bef8a1..40d71567 100644 --- a/poppy/transport/validators/stoplight/rule.py +++ b/poppy/transport/validators/stoplight/rule.py @@ -28,9 +28,17 @@ def Rule(vfunc, on_error, getter=None): to happen. :param vfunc: The function used to validate this param + :type vfunc: function + :param on_error: The function to call when an error is detected - :param value_src: The source from which the value can be + :type on_error: function + + :param getter: The source from which the value can be This function should take a value as a field name - as a single param. + as a single param + :type getter: function + + :return: A Validation rule object to validate + :rtype: ValidationRule """ return ValidationRule(vfunc=vfunc, errfunc=on_error, getter=getter)