From b14d12987bd146657a3ec2509a46c5a9b7ad93e3 Mon Sep 17 00:00:00 2001 From: Ziad Sawalha Date: Fri, 14 Feb 2014 01:25:26 -0600 Subject: [PATCH] Update api docstrings to match guidelines Per http://docs.openstack.org/developer/hacking/ and http://www.python.org/dev/peps/pep-0257/ Change-Id: I33f03d784996e298ebd94aabec93e5083b5df840 --- heat/api/aws/exception.py | 249 +++++++++++++++++--------------- heat/api/aws/utils.py | 29 ++-- heat/api/cloudwatch/versions.py | 8 +- heat/api/cloudwatch/watch.py | 82 ++++------- heat/api/openstack/v1/util.py | 27 ++-- 5 files changed, 188 insertions(+), 207 deletions(-) diff --git a/heat/api/aws/exception.py b/heat/api/aws/exception.py index 41407581fd..7b37b1e8d8 100644 --- a/heat/api/aws/exception.py +++ b/heat/api/aws/exception.py @@ -14,7 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Heat API exception subclasses - maps API response errors to AWS Errors""" +"""Heat API exception subclasses - maps API response errors to AWS Errors.""" import six import webob.exc @@ -24,12 +24,15 @@ from heat.common import serializers class HeatAPIException(webob.exc.HTTPError): - ''' + + """webob HTTPError subclass that creates a serialized body. + Subclass webob HTTPError so we can correctly serialize the wsgi response into the http response body, using the format specified by the request. Note this should not be used directly, instead use the subclasses defined below which map to AWS API errors - ''' + """ + code = 400 title = "HeatAPIException" explanation = _("Generic HeatAPIException, please use specific " @@ -37,22 +40,22 @@ class HeatAPIException(webob.exc.HTTPError): err_type = "Sender" def __init__(self, detail=None): - ''' - Overload HTTPError constructor, so we can create a default serialized - body. This is required because not all error responses are processed - by the wsgi controller (ie auth errors, which are further up the - paste pipeline. We serialize in XML by default (as AWS does) - ''' + """Overload HTTPError constructor to create a default serialized body. + + This is required because not all error responses are processed + by the wsgi controller (such as auth errors), which are further up the + paste pipeline. We serialize in XML by default (as AWS does). + """ webob.exc.HTTPError.__init__(self, detail=detail) serializer = serializers.XMLResponseSerializer() serializer.default(self, self.get_unserialized_body()) def get_unserialized_body(self): - ''' - Return a dict suitable for serialization in the wsgi controller + """Return a dict suitable for serialization in the wsgi controller. + This wraps the exception details in a format which maps to the - expected format for the AWS API - ''' + expected format for the AWS API. + """ # Note the aws response format specifies a "Code" element which is not # the html response code, but the AWS API error code, e.g self.title if self.detail: @@ -66,18 +69,18 @@ class HeatAPIException(webob.exc.HTTPError): # Common Error Subclasses: class HeatIncompleteSignatureError(HeatAPIException): - ''' - The request signature does not conform to AWS standards - ''' + + """The request signature does not conform to AWS standards.""" + code = 400 title = "IncompleteSignature" explanation = _("The request signature does not conform to AWS standards") class HeatInternalFailureError(HeatAPIException): - ''' - The request processing has failed due to some unknown error - ''' + + """The request processing has failed due to some unknown error.""" + code = 500 title = "InternalFailure" explanation = _("The request processing has failed due to an " @@ -86,45 +89,45 @@ class HeatInternalFailureError(HeatAPIException): class HeatInvalidActionError(HeatAPIException): - ''' - The action or operation requested is invalid - ''' + + """The action or operation requested is invalid.""" + code = 400 title = "InvalidAction" explanation = _("The action or operation requested is invalid") class HeatInvalidClientTokenIdError(HeatAPIException): - ''' - The X.509 certificate or AWS Access Key ID provided does not exist - ''' + + """The X.509 certificate or AWS Access Key ID provided does not exist.""" + code = 403 title = "InvalidClientTokenId" explanation = _("The certificate or AWS Key ID provided does not exist") class HeatInvalidParameterCombinationError(HeatAPIException): - ''' - Parameters that must not be used together were used together - ''' + + """Parameters that must not be used together were used together.""" + code = 400 title = "InvalidParameterCombination" explanation = _("Incompatible parameters were used together") class HeatInvalidParameterValueError(HeatAPIException): - ''' - A bad or out-of-range value was supplied for the input parameter - ''' + + """A bad or out-of-range value was supplied for the input parameter.""" + code = 400 title = "InvalidParameterValue" explanation = _("A bad or out-of-range value was supplied") class HeatInvalidQueryParameterError(HeatAPIException): - ''' - AWS query string is malformed, does not adhere to AWS standards - ''' + + """AWS query string is malformed, does not adhere to AWS standards.""" + code = 400 title = "InvalidQueryParameter" explanation = _("AWS query string is malformed, does not adhere to " @@ -132,46 +135,52 @@ class HeatInvalidQueryParameterError(HeatAPIException): class HeatMalformedQueryStringError(HeatAPIException): - ''' - The query string is malformed - ''' + + """The query string is malformed.""" + code = 404 title = "MalformedQueryString" explanation = _("The query string is malformed") class HeatMissingActionError(HeatAPIException): - ''' - The request is missing an action or operation parameter - ''' + + """The request is missing an action or operation parameter.""" + code = 400 title = "MissingAction" explanation = _("The request is missing an action or operation parameter") class HeatMissingAuthenticationTokenError(HeatAPIException): - ''' + + """Does not contain a valid AWS Access Key or certificate. + Request must contain either a valid (registered) AWS Access Key ID - or X.509 certificate - ''' + or X.509 certificate. + """ + code = 403 title = "MissingAuthenticationToken" explanation = _("Does not contain a valid AWS Access Key or certificate") class HeatMissingParameterError(HeatAPIException): - ''' - An input parameter that is mandatory for processing the request is missing - ''' + + """A mandatory input parameter is missing. + + An input parameter that is mandatory for processing the request is missing. + """ + code = 400 title = "MissingParameter" explanation = _("A mandatory input parameter is missing") class HeatOptInRequiredError(HeatAPIException): - ''' - The AWS Access Key ID needs a subscription for the service - ''' + + """The AWS Access Key ID needs a subscription for the service.""" + code = 403 title = "OptInRequired" explanation = _("The AWS Access Key ID needs a subscription for the " @@ -179,19 +188,22 @@ class HeatOptInRequiredError(HeatAPIException): class HeatRequestExpiredError(HeatAPIException): - ''' + + """Request expired or more than 15mins in the future. + Request is past expires date or the request date (either with 15 minute - padding), or the request date occurs more than 15 minutes in the future - ''' + padding), or the request date occurs more than 15 minutes in the future. + """ + code = 400 title = "RequestExpired" explanation = _("Request expired or more than 15mins in the future") class HeatServiceUnavailableError(HeatAPIException): - ''' - The request has failed due to a temporary failure of the server - ''' + + """The request has failed due to a temporary failure of the server.""" + code = 503 title = "ServiceUnavailable" explanation = _("Service temporarily unavailable") @@ -199,18 +211,18 @@ class HeatServiceUnavailableError(HeatAPIException): class HeatThrottlingError(HeatAPIException): - ''' - Request was denied due to request throttling - ''' + + """Request was denied due to request throttling.""" + code = 400 title = "Throttling" explanation = _("Request was denied due to request throttling") class AlreadyExistsError(HeatAPIException): - ''' - Resource with the name requested already exists - ''' + + """Resource with the name requested already exists.""" + code = 400 title = 'AlreadyExists' explanation = _("Resource with the name requested already exists") @@ -218,20 +230,22 @@ class AlreadyExistsError(HeatAPIException): # Not documented in the AWS docs, authentication failure errors class HeatAccessDeniedError(HeatAPIException): - ''' + + """Authentication fails due to user IAM group memberships. + This is the response given when authentication fails due to user - IAM group memberships meaning we deny access - ''' + IAM group memberships meaning we deny access. + """ + code = 403 title = "AccessDenied" explanation = _("User is not authorized to perform action") class HeatSignatureError(HeatAPIException): - ''' - This is the response given when authentication fails due to - a bad signature - ''' + + """Authentication fails due to a bad signature.""" + code = 403 title = "SignatureDoesNotMatch" explanation = _("The request signature we calculated does not match the " @@ -240,9 +254,9 @@ class HeatSignatureError(HeatAPIException): # Heat-specific errors class HeatAPINotImplementedError(HeatAPIException): - ''' - This is the response given when an API action is not yet implemented - ''' + + """API action is not yet implemented.""" + code = 500 title = "APINotImplemented" explanation = _("The requested action is not yet implemented") @@ -250,9 +264,9 @@ class HeatAPINotImplementedError(HeatAPIException): class HeatActionInProgressError(HeatAPIException): - ''' - Cannot perform action on stack in its current state - ''' + + """Cannot perform action on stack in its current state.""" + code = 400 title = 'InvalidAction' explanation = ("Cannot perform action on stack while other actions are " + @@ -260,49 +274,50 @@ class HeatActionInProgressError(HeatAPIException): def map_remote_error(ex): - """ - Map RemoteError exceptions returned by the engine - to HeatAPIException subclasses which can be used to return - properly formatted AWS error responses - """ - inval_param_errors = ( - 'AttributeError', - 'ValueError', - 'InvalidTenant', - 'StackNotFound', - 'ResourceActionNotSupported', - 'ResourceNotFound', - 'ResourceNotAvailable', - 'ResourceTypeNotFound', - 'PhysicalResourceNotFound', - 'WatchRuleNotFound', - 'StackValidationFailed', - 'InvalidSchemaError', - 'InvalidTemplateReference', - 'InvalidTemplateVersion', - 'InvalidTemplateSection', - 'UnknownUserParameter', - 'UserParameterMissing', - 'InvalidTemplateParameter', - 'MissingCredentialError', - ) - denied_errors = ('Forbidden', 'NotAuthorized') - already_exists_errors = ('StackExists') - invalid_action_errors = ('ActionInProgress',) + """Map rpc_common.RemoteError exceptions to HeatAPIException subclasses. - ex_type = ex.__class__.__name__ + Map rpc_common.RemoteError exceptions returned by the engine + to HeatAPIException subclasses which can be used to return + properly formatted AWS error responses. + """ + inval_param_errors = ( + 'AttributeError', + 'ValueError', + 'InvalidTenant', + 'StackNotFound', + 'ResourceActionNotSupported', + 'ResourceNotFound', + 'ResourceNotAvailable', + 'ResourceTypeNotFound', + 'PhysicalResourceNotFound', + 'WatchRuleNotFound', + 'StackValidationFailed', + 'InvalidSchemaError', + 'InvalidTemplateReference', + 'InvalidTemplateVersion', + 'InvalidTemplateSection', + 'UnknownUserParameter', + 'UserParameterMissing', + 'InvalidTemplateParameter', + 'MissingCredentialError', + ) + denied_errors = ('Forbidden', 'NotAuthorized') + already_exists_errors = ('StackExists') + invalid_action_errors = ('ActionInProgress',) - if ex_type.endswith('_Remote'): - ex_type = ex_type[:-len('_Remote')] + ex_type = ex.__class__.__name__ - if ex_type in inval_param_errors: - return HeatInvalidParameterValueError(detail=six.text_type(ex)) - elif ex_type in denied_errors: - return HeatAccessDeniedError(detail=six.text_type(ex)) - elif ex_type in already_exists_errors: - return AlreadyExistsError(detail=six.text_type(ex)) - elif ex_type in invalid_action_errors: - return HeatActionInProgressError(detail=six.text_type(ex)) - else: - # Map everything else to internal server error for now - return HeatInternalFailureError(detail=six.text_type(ex)) + if ex_type.endswith('_Remote'): + ex_type = ex_type[:-len('_Remote')] + + if ex_type in inval_param_errors: + return HeatInvalidParameterValueError(detail=six.text_type(ex)) + elif ex_type in denied_errors: + return HeatAccessDeniedError(detail=six.text_type(ex)) + elif ex_type in already_exists_errors: + return AlreadyExistsError(detail=six.text_type(ex)) + elif ex_type in invalid_action_errors: + return HeatActionInProgressError(detail=six.text_type(ex)) + else: + # Map everything else to internal server error for now + return HeatInternalFailureError(detail=six.text_type(ex)) diff --git a/heat/api/aws/utils.py b/heat/api/aws/utils.py index e77a8ee622..8178449b4d 100644 --- a/heat/api/aws/utils.py +++ b/heat/api/aws/utils.py @@ -11,9 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -''' -Helper utilities related to the AWS API implementations -''' +"""Helper utilities related to the AWS API implementations.""" import itertools import re @@ -26,16 +24,12 @@ LOG = logging.getLogger(__name__) def format_response(action, response): - """ - Format response from engine into API format - """ + """Format response from engine into API format.""" return {'%sResponse' % action: {'%sResult' % action: response}} def extract_param_pairs(params, prefix='', keyname='', valuename=''): - """ - Extract a dictionary of user input parameters, from AWS style - parameter-pair encoded list + """Extract user input params from AWS style parameter-pair encoded list. In the AWS API list items appear as two key-value pairs (passed as query parameters) with keys of the form below: @@ -46,7 +40,7 @@ def extract_param_pairs(params, prefix='', keyname='', valuename=''): Prefix.member.2.keyvalue=somevalue We reformat this into a dict here to match the heat - engine API expected format + engine API expected format. """ plist = extract_param_list(params, prefix) kvs = [(p[keyname], p[valuename]) for p in plist @@ -56,8 +50,7 @@ def extract_param_pairs(params, prefix='', keyname='', valuename=''): def extract_param_list(params, prefix=''): - """ - Extract a list-of-dicts based on parameters containing AWS style list + """Extract a list-of-dicts based on parameters containing AWS style list. MetricData.member.1.MetricName=buffers MetricData.member.1.Unit=Bytes @@ -67,9 +60,8 @@ def extract_param_list(params, prefix=''): MetricData.member.2.Value=12345 This can be extracted by passing prefix=MetricData, resulting in a - list containing two dicts + list containing two dicts. """ - key_re = re.compile(r"%s\.member\.([0-9]+)\.(.*)" % (prefix)) def get_param_data(params): @@ -94,10 +86,11 @@ def extract_param_list(params, prefix=''): def get_param_value(params, key): - """ + """Looks up an expected parameter in a parsed params dict. + Helper function, looks up an expected parameter in a parsed params dict and returns the result. If params does not contain - the requested key we raise an exception of the appropriate type + the requested key we raise an exception of the appropriate type. """ try: return params[key] @@ -107,9 +100,7 @@ def get_param_value(params, key): def reformat_dict_keys(keymap=None, inputdict=None): - ''' - Utility function for mapping one dict format to another - ''' + """Utility function for mapping one dict format to another.""" keymap = keymap or {} inputdict = inputdict or {} return dict([(outk, inputdict[ink]) for ink, outk in keymap.items() diff --git a/heat/api/cloudwatch/versions.py b/heat/api/cloudwatch/versions.py index c92a992b3d..c773573d44 100644 --- a/heat/api/cloudwatch/versions.py +++ b/heat/api/cloudwatch/versions.py @@ -11,9 +11,7 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Controller that returns information on the heat API versions -""" +"""Controller that returns information on the heat API versions.""" import httplib import json @@ -23,9 +21,7 @@ import webob.dec class Controller(object): - """ - A controller that produces information on the heat API versions. - """ + """A controller that produces information on the heat API versions.""" def __init__(self, conf): self.conf = conf diff --git a/heat/api/cloudwatch/watch.py b/heat/api/cloudwatch/watch.py index 4fb2343102..c29579be78 100644 --- a/heat/api/cloudwatch/watch.py +++ b/heat/api/cloudwatch/watch.py @@ -11,9 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. -""" -endpoint for heat AWS-compatible CloudWatch API -""" +"""Endpoint for heat AWS-compatible CloudWatch API.""" + from oslo import messaging import six @@ -34,9 +33,9 @@ LOG = logging.getLogger(__name__) class WatchController(object): - """ - WSGI controller for CloudWatch resource in heat API - Implements the API actions + """WSGI controller for CloudWatch resource in heat API. + + Implements the API actions. """ def __init__(self, options): @@ -61,10 +60,10 @@ class WatchController(object): @staticmethod def _reformat_dimensions(dims): - ''' - Reformat dimensions list into AWS API format - Parameter dims is a list of dicts - ''' + """Reformat dimensions list into AWS API format. + + :param dims: a list of dicts. + """ newdims = [] for count, d in enumerate(dims, 1): for key in d.keys(): @@ -72,29 +71,21 @@ class WatchController(object): return newdims def delete_alarms(self, req): - """ - Implements DeleteAlarms API action - """ + """Implements DeleteAlarms API action.""" self._enforce(req, 'DeleteAlarms') return exception.HeatAPINotImplementedError() def describe_alarm_history(self, req): - """ - Implements DescribeAlarmHistory API action - """ + """Implements DescribeAlarmHistory API action.""" self._enforce(req, 'DescribeAlarmHistory') return exception.HeatAPINotImplementedError() def describe_alarms(self, req): - """ - Implements DescribeAlarms API action - """ + """Implements DescribeAlarms API action.""" self._enforce(req, 'DescribeAlarms') def format_metric_alarm(a): - """ - Reformat engine output into the AWS "MetricAlarm" format - """ + """Reformat engine output into the AWS "MetricAlarm" format.""" keymap = { engine_api.WATCH_ACTIONS_ENABLED: 'ActionsEnabled', engine_api.WATCH_ALARM_ACTIONS: 'AlarmActions', @@ -118,7 +109,8 @@ class WatchController(object): engine_api.WATCH_STATE_VALUE: 'StateValue', engine_api.WATCH_STATISTIC: 'Statistic', engine_api.WATCH_THRESHOLD: 'Threshold', - engine_api.WATCH_UNIT: 'Unit'} + engine_api.WATCH_UNIT: 'Unit', + } # AWS doesn't return StackId in the main MetricAlarm # structure, so we add StackId as a dimension to all responses @@ -151,47 +143,39 @@ class WatchController(object): return result def describe_alarms_for_metric(self, req): - """ - Implements DescribeAlarmsForMetric API action - """ + """Implements DescribeAlarmsForMetric API action.""" self._enforce(req, 'DescribeAlarmsForMetric') return exception.HeatAPINotImplementedError() def disable_alarm_actions(self, req): - """ - Implements DisableAlarmActions API action - """ + """Implements DisableAlarmActions API action.""" self._enforce(req, 'DisableAlarmActions') return exception.HeatAPINotImplementedError() def enable_alarm_actions(self, req): - """ - Implements EnableAlarmActions API action - """ + """Implements EnableAlarmActions API action.""" self._enforce(req, 'EnableAlarmActions') return exception.HeatAPINotImplementedError() def get_metric_statistics(self, req): - """ - Implements GetMetricStatistics API action - """ + """Implements GetMetricStatistics API action.""" self._enforce(req, 'GetMetricStatistics') return exception.HeatAPINotImplementedError() def list_metrics(self, req): - """ - Implements ListMetrics API action + """Implements ListMetrics API action. + Lists metric datapoints associated with a particular alarm, - or all alarms if none specified + or all alarms if none specified. """ self._enforce(req, 'ListMetrics') def format_metric_data(d, fil=None): - """ - Reformat engine output into the AWS "Metric" format + """Reformat engine output into the AWS "Metric" format. + Takes an optional filter dict, which is traversed so a metric dict is only returned if all keys match - the filter dict + the filter dict. """ fil = fil or {} dimensions = [ @@ -247,16 +231,12 @@ class WatchController(object): return result def put_metric_alarm(self, req): - """ - Implements PutMetricAlarm API action - """ + """Implements PutMetricAlarm API action.""" self._enforce(req, 'PutMetricAlarm') return exception.HeatAPINotImplementedError() def put_metric_data(self, req): - """ - Implements PutMetricData API action - """ + """Implements PutMetricData API action.""" self._enforce(req, 'PutMetricData') con = req.context @@ -304,9 +284,7 @@ class WatchController(object): return api_utils.format_response("PutMetricData", result) def set_alarm_state(self, req): - """ - Implements SetAlarmState API action - """ + """Implements SetAlarmState API action.""" self._enforce(req, 'SetAlarmState') # Map from AWS state names to those used in the engine @@ -341,8 +319,6 @@ class WatchController(object): def create_resource(options): - """ - Watch resource factory method. - """ + """Watch resource factory method.""" deserializer = wsgi.JSONRequestDeserializer() return wsgi.Resource(WatchController(options), deserializer) diff --git a/heat/api/openstack/v1/util.py b/heat/api/openstack/v1/util.py index 3660c68c8d..a165541212 100644 --- a/heat/api/openstack/v1/util.py +++ b/heat/api/openstack/v1/util.py @@ -21,10 +21,13 @@ from heat.common import identifier def policy_enforce(handler): - ''' - Decorator for a handler method that checks the path matches the - request context and enforce policy defined in policy.json - ''' + """Decorator that enforces policies. + + Checks the path matches the request context and enforce policy defined in + policy.json. + + This is a handler method decorator. + """ @wraps(handler) def handle_stack_method(controller, req, tenant_id, **kwargs): if req.context.tenant_id != tenant_id: @@ -40,10 +43,10 @@ def policy_enforce(handler): def identified_stack(handler): - ''' - Decorator for a handler method that passes a stack identifier in place of - the various path components. - ''' + """Decorator that passes a stack identifier instead of path components. + + This is a handler method decorator. + """ @policy_enforce @wraps(handler) def handle_stack_method(controller, req, stack_name, stack_id, **kwargs): @@ -56,7 +59,7 @@ def identified_stack(handler): def make_url(req, identity): - '''Return the URL for the supplied identity dictionary.''' + """Return the URL for the supplied identity dictionary.""" try: stack_identity = identifier.HeatIdentifier(**identity) except ValueError: @@ -67,12 +70,12 @@ def make_url(req, identity): def make_link(req, identity, relationship='self'): - '''Return a link structure for the supplied identity dictionary.''' + """Return a link structure for the supplied identity dictionary.""" return {'href': make_url(req, identity), 'rel': relationship} def get_allowed_params(params, whitelist): - '''Extract from ``params`` all entries listed in ``whitelist`` + """Extract from ``params`` all entries listed in ``whitelist``. The returning dict will contain an entry for a key if, and only if, there's an entry in ``whitelist`` for that key and at least one entry in @@ -83,7 +86,7 @@ def get_allowed_params(params, whitelist): :param whitelist: an array of strings to whitelist :returns: a dict with {key: value} pairs - ''' + """ allowed_params = {} for key, get_type in six.iteritems(whitelist):