OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 Google Inc. All Rights Reserved. |
| 2 # |
| 3 # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 # you may not use this file except in compliance with the License. |
| 5 # You may obtain a copy of the License at |
| 6 # |
| 7 # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 # |
| 9 # Unless required by applicable law or agreed to in writing, software |
| 10 # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 # See the License for the specific language governing permissions and |
| 13 # limitations under the License. |
| 14 |
| 15 """API serving config collection service implementation. |
| 16 |
| 17 Contains the implementation for BackendService as defined in api_backend.py. |
| 18 """ |
| 19 |
| 20 # pylint: disable=g-statement-before-imports,g-import-not-at-top |
| 21 try: |
| 22 import json |
| 23 except ImportError: |
| 24 import simplejson as json |
| 25 |
| 26 import logging |
| 27 |
| 28 import api_backend |
| 29 import api_exceptions |
| 30 |
| 31 from protorpc import message_types |
| 32 |
| 33 __all__ = [ |
| 34 'ApiConfigRegistry', |
| 35 'BackendServiceImpl', |
| 36 ] |
| 37 |
| 38 |
| 39 class ApiConfigRegistry(object): |
| 40 """Registry of active APIs to be registered with Google API Server.""" |
| 41 |
| 42 def __init__(self): |
| 43 # Set of API classes that have been registered. |
| 44 self.__registered_classes = set() |
| 45 # Set of API config contents served by this App Engine AppId/version |
| 46 self.__api_configs = set() |
| 47 # Map of API method name to ProtoRPC method name. |
| 48 self.__api_methods = {} |
| 49 |
| 50 # pylint: disable=g-bad-name |
| 51 def register_backend(self, config_contents): |
| 52 """Register a single API and its config contents. |
| 53 |
| 54 Args: |
| 55 config_contents: String containing API configuration. |
| 56 """ |
| 57 if config_contents is None: |
| 58 return |
| 59 parsed_config = json.loads(config_contents) |
| 60 self.__register_class(parsed_config) |
| 61 self.__api_configs.add(config_contents) |
| 62 self.__register_methods(parsed_config) |
| 63 |
| 64 def __register_class(self, parsed_config): |
| 65 """Register the class implementing this config, so we only add it once. |
| 66 |
| 67 Args: |
| 68 parsed_config: The JSON object with the API configuration being added. |
| 69 |
| 70 Raises: |
| 71 ApiConfigurationError: If the class has already been registered. |
| 72 """ |
| 73 methods = parsed_config.get('methods') |
| 74 if not methods: |
| 75 return |
| 76 |
| 77 # Determine the name of the class that implements this configuration. |
| 78 service_classes = set() |
| 79 for method in methods.itervalues(): |
| 80 rosy_method = method.get('rosyMethod') |
| 81 if rosy_method and '.' in rosy_method: |
| 82 method_class = rosy_method.split('.', 1)[0] |
| 83 service_classes.add(method_class) |
| 84 |
| 85 for service_class in service_classes: |
| 86 if service_class in self.__registered_classes: |
| 87 raise api_exceptions.ApiConfigurationError( |
| 88 'API class %s has already been registered.' % service_class) |
| 89 self.__registered_classes.add(service_class) |
| 90 |
| 91 def __register_methods(self, parsed_config): |
| 92 """Register all methods from the given api config file. |
| 93 |
| 94 Methods are stored in a map from method_name to rosyMethod, |
| 95 the name of the ProtoRPC method to be called on the backend. |
| 96 If no rosyMethod was specified the value will be None. |
| 97 |
| 98 Args: |
| 99 parsed_config: The JSON object with the API configuration being added. |
| 100 """ |
| 101 methods = parsed_config.get('methods') |
| 102 if not methods: |
| 103 return |
| 104 |
| 105 for method_name, method in methods.iteritems(): |
| 106 self.__api_methods[method_name] = method.get('rosyMethod') |
| 107 |
| 108 def lookup_api_method(self, api_method_name): |
| 109 """Looks an API method up by name to find the backend method to call. |
| 110 |
| 111 Args: |
| 112 api_method_name: Name of the method in the API that was called. |
| 113 |
| 114 Returns: |
| 115 Name of the ProtoRPC method called on the backend, or None if not found. |
| 116 """ |
| 117 return self.__api_methods.get(api_method_name) |
| 118 |
| 119 def all_api_configs(self): |
| 120 """Return a list of all API configration specs as registered above.""" |
| 121 return list(self.__api_configs) |
| 122 |
| 123 |
| 124 class BackendServiceImpl(api_backend.BackendService): |
| 125 """Implementation of BackendService.""" |
| 126 |
| 127 def __init__(self, api_config_registry, app_revision): |
| 128 """Create a new BackendService implementation. |
| 129 |
| 130 Args: |
| 131 api_config_registry: ApiConfigRegistry to register and look up configs. |
| 132 app_revision: string containing the current app revision. |
| 133 """ |
| 134 self.__api_config_registry = api_config_registry |
| 135 self.__app_revision = app_revision |
| 136 |
| 137 # pylint: disable=g-bad-name |
| 138 # pylint: disable=g-doc-return-or-yield |
| 139 # pylint: disable=g-doc-args |
| 140 @staticmethod |
| 141 def definition_name(): |
| 142 """Override definition_name so that it is not BackendServiceImpl.""" |
| 143 return api_backend.BackendService.definition_name() |
| 144 |
| 145 def getApiConfigs(self, request=None): |
| 146 """Return a list of active APIs and their configuration files. |
| 147 |
| 148 Args: |
| 149 request: A request which may contain an app revision |
| 150 |
| 151 Returns: |
| 152 ApiConfigList: A list of API config strings |
| 153 """ |
| 154 if (request and request.appRevision and |
| 155 request.appRevision != self.__app_revision): |
| 156 raise api_exceptions.BadRequestException( |
| 157 message='API backend app revision %s not the same as expected %s' % ( |
| 158 self.__app_revision, request.appRevision)) |
| 159 |
| 160 configs = self.__api_config_registry.all_api_configs() |
| 161 return api_backend.ApiConfigList(items=configs) |
| 162 |
| 163 def logMessages(self, request): |
| 164 """Write a log message from the Swarm FE to the log. |
| 165 |
| 166 Args: |
| 167 request: A log message request. |
| 168 |
| 169 Returns: |
| 170 Void message. |
| 171 """ |
| 172 Level = api_backend.LogMessagesRequest.LogMessage.Level |
| 173 log = logging.getLogger(__name__) |
| 174 for message in request.messages: |
| 175 level = message.level if message.level is not None else Level.info |
| 176 # Create a log record and override the pathname and lineno. These |
| 177 # messages come from the front end, so it's misleading to say that they |
| 178 # come from api_backend_service. |
| 179 record = logging.LogRecord(name=__name__, level=level.number, pathname='', |
| 180 lineno='', msg=message.message, args=None, |
| 181 exc_info=None) |
| 182 log.handle(record) |
| 183 |
| 184 return message_types.VoidMessage() |
OLD | NEW |