| #!/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. |
| # |
| |
| |
| |
| |
| """ |
| A library for working with BackendInfoExternal records, describing backends |
| configured for an application. Supports loading the records from backend.yaml. |
| """ |
| |
| |
| |
| |
| |
| |
| |
| |
| import os |
| import yaml |
| from yaml import representer |
| |
| if os.environ.get('APPENGINE_RUNTIME') == 'python27': |
| from google.appengine.api import validation |
| from google.appengine.api import yaml_builder |
| from google.appengine.api import yaml_listener |
| from google.appengine.api import yaml_object |
| else: |
| from google.appengine.api import validation |
| from google.appengine.api import yaml_builder |
| from google.appengine.api import yaml_listener |
| from google.appengine.api import yaml_object |
| |
| NAME_REGEX = r'(?!-)[a-z\d\-]{1,100}' |
| FILE_REGEX = r'(?!\^).*(?!\$).{1,256}' |
| CLASS_REGEX = r'^[bB](1|2|4|8|4_1G)$' |
| OPTIONS_REGEX = r'^[a-z, ]*$' |
| STATE_REGEX = r'^(START|STOP|DISABLED)$' |
| |
| |
| BACKENDS = 'backends' |
| |
| |
| NAME = 'name' |
| CLASS = 'class' |
| INSTANCES = 'instances' |
| OPTIONS = 'options' |
| PUBLIC = 'public' |
| DYNAMIC = 'dynamic' |
| FAILFAST = 'failfast' |
| MAX_CONCURRENT_REQUESTS = 'max_concurrent_requests' |
| START = 'start' |
| |
| VALID_OPTIONS = frozenset([PUBLIC, DYNAMIC, FAILFAST]) |
| |
| |
| STATE = 'state' |
| |
| class BadConfig(Exception): |
| """An invalid configuration was provided.""" |
| |
| class ListWithoutSort(list): |
| def sort(self): |
| pass |
| |
| class SortedDict(dict): |
| def __init__(self, keys, data): |
| super(SortedDict, self).__init__() |
| self.keys = keys |
| self.update(data) |
| |
| def items(self): |
| result = ListWithoutSort() |
| for key in self.keys: |
| if type(self.get(key)) != type(None): |
| result.append((key, self.get(key))) |
| return result |
| |
| representer.SafeRepresenter.add_representer( |
| SortedDict, representer.SafeRepresenter.represent_dict) |
| |
| class BackendEntry(validation.Validated): |
| """A backend entry describes a single backend.""" |
| ATTRIBUTES = { |
| NAME: NAME_REGEX, |
| CLASS: validation.Optional(CLASS_REGEX), |
| INSTANCES: validation.Optional(validation.TYPE_INT), |
| MAX_CONCURRENT_REQUESTS: validation.Optional(validation.TYPE_INT), |
| |
| |
| OPTIONS: validation.Optional(OPTIONS_REGEX), |
| PUBLIC: validation.Optional(validation.TYPE_BOOL), |
| DYNAMIC: validation.Optional(validation.TYPE_BOOL), |
| FAILFAST: validation.Optional(validation.TYPE_BOOL), |
| START: validation.Optional(FILE_REGEX), |
| |
| |
| STATE: validation.Optional(STATE_REGEX), |
| } |
| |
| def __init__(self, *args, **kwargs): |
| super(BackendEntry, self).__init__(*args, **kwargs) |
| self.Init() |
| |
| def Init(self): |
| if self.public: |
| raise BadConfig("Illegal field: 'public'") |
| if self.dynamic: |
| raise BadConfig("Illegal field: 'dynamic'") |
| if self.failfast: |
| raise BadConfig("Illegal field: 'failfast'") |
| self.ParseOptions() |
| return self |
| |
| def set_class(self, Class): |
| """Setter for 'class', since an attribute reference is an error.""" |
| self.Set(CLASS, Class) |
| |
| def get_class(self): |
| """Accessor for 'class', since an attribute reference is an error.""" |
| return self.Get(CLASS) |
| |
| def ToDict(self): |
| """Returns a sorted dictionary representing the backend entry.""" |
| self.ParseOptions().WriteOptions() |
| result = super(BackendEntry, self).ToDict() |
| return SortedDict([NAME, |
| CLASS, |
| INSTANCES, |
| START, |
| OPTIONS, |
| MAX_CONCURRENT_REQUESTS, |
| STATE], |
| result) |
| |
| def ParseOptions(self): |
| """Parses the 'options' field and sets appropriate fields.""" |
| if self.options: |
| options = [option.strip() for option in self.options.split(',')] |
| else: |
| options = [] |
| |
| for option in options: |
| if option not in VALID_OPTIONS: |
| raise BadConfig('Unrecognized option: %s', option) |
| |
| self.public = PUBLIC in options |
| self.dynamic = DYNAMIC in options |
| self.failfast = FAILFAST in options |
| return self |
| |
| def WriteOptions(self): |
| """Writes the 'options' field based on other settings.""" |
| options = [] |
| if self.public: |
| options.append('public') |
| if self.dynamic: |
| options.append('dynamic') |
| if self.failfast: |
| options.append('failfast') |
| if options: |
| self.options = ', '.join(options) |
| else: |
| self.options = None |
| return self |
| |
| |
| def LoadBackendEntry(backend_entry): |
| """Parses a BackendEntry object from a string. |
| |
| Args: |
| backend_entry: a backend entry, as a string |
| |
| Returns: |
| A BackendEntry object. |
| """ |
| builder = yaml_object.ObjectBuilder(BackendEntry) |
| handler = yaml_builder.BuilderHandler(builder) |
| listener = yaml_listener.EventListener(handler) |
| listener.Parse(backend_entry) |
| |
| entries = handler.GetResults() |
| if len(entries) < 1: |
| raise BadConfig('Empty backend configuration.') |
| if len(entries) > 1: |
| raise BadConfig('Multiple backend entries were found in configuration.') |
| |
| return entries[0].Init() |
| |
| |
| class BackendInfoExternal(validation.Validated): |
| """BackendInfoExternal describes all backend entries for an application.""" |
| ATTRIBUTES = { |
| BACKENDS: validation.Optional(validation.Repeated(BackendEntry)), |
| } |
| |
| |
| def LoadBackendInfo(backend_info, open_fn=None): |
| """Parses a BackendInfoExternal object from a string. |
| |
| Args: |
| backend_info: a backends stanza (list of backends) as a string |
| open_fn: Function for opening files. Unused. |
| |
| Returns: |
| A BackendInfoExternal object. |
| """ |
| builder = yaml_object.ObjectBuilder(BackendInfoExternal) |
| handler = yaml_builder.BuilderHandler(builder) |
| listener = yaml_listener.EventListener(handler) |
| listener.Parse(backend_info) |
| |
| backend_info = handler.GetResults() |
| if len(backend_info) < 1: |
| return BackendInfoExternal(backends=[]) |
| if len(backend_info) > 1: |
| raise BadConfig("Only one 'backends' clause is allowed.") |
| |
| info = backend_info[0] |
| if not info.backends: |
| return BackendInfoExternal(backends=[]) |
| |
| for backend in info.backends: |
| backend.Init() |
| return info |