blob: bc938d4e4972ff1dfe7981405872743e22c02521 [file] [log] [blame]
#!/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