| #!/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. |
| # |
| |
| |
| |
| """Backends API. |
| |
| This API provides utility methods for working with backends. |
| """ |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| import os |
| import re |
| |
| from google.appengine.api import app_identity |
| from google.appengine.api import modules |
| |
| class Error(Exception): |
| """Base class for exceptions in this module.""" |
| |
| class DefaultHostnameError(Error): |
| """Raised if no default hostname is set in the environment.""" |
| |
| class InvalidBackendError(Error): |
| """Raised if an invalid backend was provided.""" |
| pass |
| |
| class InvalidInstanceError(Error): |
| """Raised if an invalid instance was provided.""" |
| pass |
| |
| |
| def get_backend(): |
| """Get the name of the backend handling this request. |
| |
| Returns: |
| string: The current backend, or None if this is not a backend. |
| """ |
| return os.environ.get('BACKEND_ID', None) |
| |
| |
| def get_instance(): |
| """Get the instance number of the backend handling this request. |
| |
| Returns: |
| int: The instance, in [0, instances-1], or None if this is not a backend. |
| """ |
| instance = os.environ.get('INSTANCE_ID', None) |
| if instance is not None: |
| return int(instance) |
| else: |
| return None |
| |
| |
| def get_url(backend=None, instance=None, protocol='http'): |
| """Returns a URL pointing to a backend or backend instance. |
| |
| This method works in both production and development environments. |
| |
| Args: |
| backend: The name of the backend. If None, the current backend will be used. |
| |
| instance: An optoinal instance number. If provided, the URL will target |
| the specific instance. If absent, the URL will point to a load-balancing |
| hostname for the backend. |
| |
| protocol: The protocol to use (default='http'). |
| |
| Raises: |
| InvalidBackendError |
| InvalidInstanceError |
| |
| Returns: |
| The URL of the backend or backend instance. |
| """ |
| if backend is None: |
| backend = get_backend() |
| |
| |
| if _is_dev2_environment(): |
| return _get_dev2_url(backend, instance) |
| elif _is_dev_environment(): |
| return _get_dev_url(backend, instance) |
| |
| |
| hostname = get_hostname(backend, instance) |
| return '%s://%s' % (protocol, hostname) |
| |
| |
| def get_hostname(backend=None, instance=None): |
| """Returns the hostname for a backend or backend instance. |
| |
| Args: |
| backend: The name of the backend. If None, the current backend will be used. |
| |
| instance: An optoinal instance number. If provided, the hostname will |
| represent the specific instance. If absent, the hostname will represent |
| the backend as a whole. |
| |
| Raises: |
| InvalidBackendError |
| InvalidInstanceError |
| |
| Returns: |
| The hostname of the backend or backend instance. |
| """ |
| if backend is None: |
| backend = get_backend() |
| |
| |
| if not isinstance(backend, (str, unicode)): |
| raise InvalidBackendError('Invalid backend: %s' % backend) |
| |
| if not re.match('^[a-zA-Z0-9\-]+$', backend): |
| raise InvalidBackendError('Invalid backend: %s' % backend) |
| |
| |
| if instance is not None: |
| try: |
| instance = int(instance) |
| except ValueError: |
| raise InvalidInstanceError('instance must be an integer.') |
| |
| |
| if _is_dev2_environment(): |
| return _get_dev2_hostname(backend, instance) |
| elif _is_dev_environment(): |
| return _get_dev_hostname(backend, instance) |
| |
| hostname = app_identity.get_default_version_hostname() |
| if hostname is None: |
| raise DefaultHostnameError |
| |
| hostname = '%s.%s' % (backend, hostname) |
| if instance is not None: |
| hostname = '%d.%s' % (instance, hostname) |
| return hostname |
| |
| |
| def _is_dev2_environment(): |
| """Indicates whether this code is being run in devappserver2.""" |
| return os.environ.get('SERVER_SOFTWARE', '') == 'Development/2.0' |
| |
| |
| def _get_dev2_url(backend, instance=None): |
| """Returns the url of a backend [instance] in devappserver2. |
| |
| Args: |
| backend: The name of the backend. |
| instance: The backend instance number, in [0, instances-1]. |
| |
| Returns: |
| The url of the backend. |
| """ |
| return 'http://%s' % _get_dev2_hostname(backend, instance) |
| |
| |
| def _get_dev2_hostname(backend, instance=None): |
| """Returns the hostname of a backend [instance] in devappserver2. |
| |
| Args: |
| backend: The name of the backend. |
| instance: The backend instance number, in [0, instances-1]. |
| |
| Returns: |
| The hostname of the backend. |
| """ |
| try: |
| return modules.get_hostname(module=backend, instance=instance) |
| except modules.InvalidModuleError: |
| raise InvalidBackendError() |
| except modules.InvalidInstancesError: |
| raise InvalidInstanceError() |
| |
| |
| def _is_dev_environment(): |
| """Indicates whether this code is being run in the development environment.""" |
| return os.environ.get('SERVER_SOFTWARE', '').startswith('Development') |
| |
| |
| def _get_dev_url(backend, instance=None): |
| """Returns the url of a backend [instance] in the dev_appserver. |
| |
| Args: |
| backend: The name of the backend. |
| instance: The backend instance number, in [0, instances-1]. |
| |
| Returns: |
| The url of the backend. |
| """ |
| return 'http://%s' % _get_dev_hostname(backend, instance) |
| |
| |
| def _get_dev_hostname(backend, instance=None): |
| """Returns the hostname of a backend [instance] in the dev_appserver. |
| |
| Args: |
| backend: The name of the backend. |
| instance: The backend instance number, in [0, instances-1]. |
| |
| Returns: |
| The hostname of the backend. |
| """ |
| |
| port = _get_dev_port(backend, instance) |
| if not port: |
| error = 'Backend not found: %s' % backend |
| if instance is not None: |
| error = '%s.%d' % (error, instance) |
| raise InvalidBackendError(error) |
| |
| host = os.environ.get('SERVER_NAME', 'localhost') |
| return '%s:%d' % (host, port) |
| |
| |
| def _get_dev_port(backend, instance=None): |
| """Returns the port for a backend [instance] in the dev_appserver. |
| |
| Args: |
| backend: The name of the backend. |
| instance: The backend instance (optional). |
| |
| Returns: |
| int: The backend port. |
| """ |
| port = os.environ.get(_get_dev_port_var(backend, instance), None) |
| if port: |
| return int(port) |
| else: |
| return None |
| |
| |
| def _set_dev_port(port, backend, instance=None, env=os.environ): |
| """Sets the port for a backend [instance] in the dev_appserver. |
| |
| Args: |
| port: The port. |
| backend: The name of the backend. |
| instance: The backend instance (optional). |
| env: The environment in which to set the port. |
| """ |
| env[_get_dev_port_var(backend, instance)] = str(port) |
| |
| |
| def _get_dev_port_var(backend, instance=None): |
| """Return the environment variable for a backend port. |
| |
| Backend ports are stored at GET_PORT_<backend> for backends |
| and GET_PORT_<backend>.<instance> for individual instances. |
| |
| Args: |
| backend: The name of the backend. |
| instance: The backend instance (optional). |
| |
| Returns: |
| string: The environment variable where the backend port is stored. |
| """ |
| port_var = 'BACKEND_PORT.%s' % str(backend).lower() |
| if instance is not None: |
| port_var = '%s.%d' % (port_var, instance) |
| return port_var |