| #!/usr/bin/env python |
| # |
| # Copyright 2007 Google Inc. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| """Performs translation of queue.xml to queue.yaml.""" |
| |
| from xml.etree import ElementTree |
| |
| from google.appengine.tools import xml_parser_utils |
| from google.appengine.tools.app_engine_config_exception import AppEngineConfigException |
| |
| PUSH_QUEUE_TAGS = ( |
| 'rate', 'bucket-size', 'max-concurrent-requests', 'target') |
| |
| PUSH_QUEUE_RETRY_PARAMS = ( |
| 'task-age-limit', |
| 'min-backoff-seconds', |
| 'max-backoff-seconds', |
| 'max-doublings') |
| |
| RETRY_PARAMETER_TAGS = ('task-retry-limit',) + PUSH_QUEUE_RETRY_PARAMS |
| |
| BAD_MODE_ERROR_MESSAGE = ( |
| 'Mode, if specified, must be either push or pull' |
| ' (defaults to push) for queue entries. Bad value' |
| " '%s' in <queue> entry with name '%s'") |
| |
| PULL_QUEUE_ERROR_MESSAGE = ( |
| 'The element <%s> is not defined for pull ' |
| "queues; bad <queue> entry with name '%s'") |
| RETRY_PARAM_ERROR_MESSAGE = ( |
| 'The element <%s> in <retry-parameters> is not ' |
| "defined for pull queue with name '%s'") |
| |
| |
| def GetQueueYaml(unused_application, queue_xml_str): |
| queue_xml = QueueXmlParser().ProcessXml(queue_xml_str) |
| return queue_xml.ToYaml() |
| |
| |
| class QueueXmlParser(object): |
| """Provides logic for walking down XML tree and pulling data.""" |
| |
| def ProcessXml(self, xml_str): |
| """Parses XML string and returns object representation of relevant info. |
| |
| Args: |
| xml_str: The XML string. |
| Returns: |
| A QueueXml object containing information about task queue |
| specifications from the XML. |
| Raises: |
| AppEngineConfigException: In case of malformed XML or illegal inputs. |
| """ |
| |
| try: |
| self.errors = [] |
| xml_root = ElementTree.fromstring(xml_str) |
| |
| if xml_parser_utils.GetTag(xml_root) != 'queue-entries': |
| raise AppEngineConfigException('Root tag must be <queue-entries>') |
| |
| self.queue_xml = QueueXml() |
| self.queue_xml.queues = [] |
| self.queue_xml.total_storage_limit = xml_parser_utils.GetChildNodeText( |
| xml_root, 'total-storage-limit') |
| for child in xml_parser_utils.GetNodes(xml_root, 'queue'): |
| self.ProcessQueueNode(child) |
| |
| if self.errors: |
| raise AppEngineConfigException('\n'.join(self.errors)) |
| |
| return self.queue_xml |
| |
| except ElementTree.ParseError: |
| raise AppEngineConfigException('Bad input -- not valid XML') |
| |
| def ProcessQueueNode(self, node): |
| """Processes XML <queue> nodes into Queue objects. |
| |
| The following information is parsed out: |
| name |
| mode: can be either push or pull |
| retry-parameters: |
| task-retry-limit |
| ---- push queues only ---- |
| task-age-limit |
| min-backoff-seconds |
| max-back-off-seconds |
| max-doubling |
| bucket-size |
| max-concurrent-requests |
| rate: how often tasks are processed on this queue. |
| target: version of application on which tasks on this queue will be |
| invoked. |
| ---- pull queues only ---- |
| acl: access control list - lists user and writer email addresses. |
| |
| Args: |
| node: Current <queue> XML node being processed. |
| """ |
| |
| name = xml_parser_utils.GetChildNodeText(node, 'name') |
| if not name: |
| self.errors.append('Must specify a name for each <queue> entry') |
| return |
| |
| |
| mode = xml_parser_utils.GetChildNodeText(node, 'mode', 'push') |
| |
| if mode not in ('push', 'pull'): |
| self.errors.append(BAD_MODE_ERROR_MESSAGE % (mode, name)) |
| return |
| if mode == 'pull': |
| queue = PullQueue() |
| queue.name = name |
| self._ProcessPullQueueNode(node, queue) |
| else: |
| queue = PushQueue() |
| queue.name = name |
| self._ProcessPushQueueNode(node, queue) |
| |
| self.queue_xml.queues.append(queue) |
| |
| def _ProcessPushQueueNode(self, node, queue): |
| if xml_parser_utils.GetChild(node, 'acl') is not None: |
| self.errors.append( |
| 'The element <acl> is not defined for push ' |
| "queues; bad <queue> entry with name '%s'" % queue.name) |
| for tag in PUSH_QUEUE_TAGS: |
| field_name = tag.replace('-', '_') |
| setattr(queue, field_name, xml_parser_utils.GetChildNodeText(node, tag)) |
| self._ProcessRetryParametersNode(node, queue) |
| |
| def _ProcessPullQueueNode(self, node, queue): |
| """Populates PullQueue-specific fields from parsed XML.""" |
| for tag in PUSH_QUEUE_TAGS: |
| if xml_parser_utils.GetChild(node, tag) is not None: |
| self.errors.append(PULL_QUEUE_ERROR_MESSAGE % (tag, queue.name)) |
| |
| acl_node = xml_parser_utils.GetChild(node, 'acl') |
| |
| if acl_node is not None: |
| queue.acl = Acl() |
| queue.acl.user_emails = [ |
| sub_node.text for sub_node in |
| xml_parser_utils.GetNodes(acl_node, 'user-email')] |
| queue.acl.writer_emails = [ |
| sub_node.text for sub_node in |
| xml_parser_utils.GetNodes(acl_node, 'writer-email')] |
| else: |
| queue.acl = None |
| |
| self._ProcessRetryParametersNode(node, queue) |
| |
| def _ProcessRetryParametersNode(self, node, queue): |
| """Pulls information out of <retry-parameters> node.""" |
| retry_parameters_node = xml_parser_utils.GetChild( |
| node, 'retry-parameters') |
| if retry_parameters_node is None: |
| queue.retry_parameters = None |
| return |
| retry_parameters = RetryParameters() |
| queue.retry_parameters = retry_parameters |
| retry_parameters.task_retry_limit = xml_parser_utils.GetChildNodeText( |
| retry_parameters_node, 'task-retry-limit') |
| |
| for tag in PUSH_QUEUE_RETRY_PARAMS: |
| |
| if xml_parser_utils.GetChild(retry_parameters_node, tag) is not None: |
| if isinstance(queue, PullQueue): |
| self.errors.append(RETRY_PARAM_ERROR_MESSAGE % (tag, queue.name)) |
| else: |
| setattr( |
| retry_parameters, |
| tag.replace('-', '_'), |
| xml_parser_utils.GetChildNodeText(retry_parameters_node, tag)) |
| |
| |
| class QueueXml(object): |
| |
| def __init__(self): |
| self.queues = [] |
| self.total_storage_limit = None |
| |
| def ToYaml(self): |
| statements = [] |
| if self.total_storage_limit: |
| statements.append('total_storage_limit: %s\n' % self.total_storage_limit) |
| statements.append('queue:') |
| for queue in self.queues: |
| statements += queue.GetYamlStatementsList() |
| |
| return '\n'.join(statements) + '\n' |
| |
| |
| class Queue(object): |
| |
| def GetYamlStatementsList(self): |
| statements = ['- name: %s' % self.name] |
| statements += self.GetAdditionalYamlStatementsList() |
| |
| if self.retry_parameters: |
| statements += self.retry_parameters.GetYamlStatementsList() |
| |
| return statements |
| |
| |
| class PushQueue(Queue): |
| |
| def GetAdditionalYamlStatementsList(self): |
| statements = [' mode: push'] |
| fields = (tag.replace('-', '_') for tag in PUSH_QUEUE_TAGS) |
| for field in fields: |
| field_value = getattr(self, field) |
| if field_value: |
| statements.append(' %s: %s' % (field, field_value)) |
| |
| return statements |
| |
| |
| class PullQueue(Queue): |
| |
| def GetAdditionalYamlStatementsList(self): |
| statements = [' mode: pull'] |
| if self.acl: |
| statements += self.acl.GetYamlStatementsList() |
| return statements |
| |
| |
| class Acl(object): |
| |
| def GetYamlStatementsList(self): |
| statements = [' acl:'] |
| statements += [' - user_email: %s' % user_email for user_email in |
| self.user_emails] |
| statements += [' - writer_email: %s' % writer_email for writer_email in |
| self.writer_emails] |
| return statements |
| |
| |
| class RetryParameters(object): |
| |
| def GetYamlStatementsList(self): |
| statements = [' retry_parameters:'] |
| field_names = (tag.replace('-', '_') for tag in RETRY_PARAMETER_TAGS) |
| for field in field_names: |
| field_value = getattr(self, field, None) |
| if field_value: |
| statements.append(' %s: %s' % (field, field_value)) |
| return statements |