1.9.30 sync.
[gae.git] / python / google / appengine / client / services / port_manager.py
blob5af9d9212b92c9f355fa2075d6a3bfb04e2fb38b
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """A helper file with a helper class for opening ports."""
19 import logging
21 from google.appengine.client.services import vme_errors
23 # These ports are used by our code or critical system daemons.
24 RESERVED_HOST_PORTS = [22, # SSH
25 5000, # Docker registry
26 8080, # HTTP server
27 10000, # For unlocking?
28 10001, # Nanny stubby proxy endpoint
30 # We allow users to forward traffic to our HTTP server internally.
31 RESERVED_DOCKER_PORTS = [22, # SSH
32 5000, # Docker registry
33 10001, # Nanny stubby proxy endpoint
36 DEFAULT_CONTAINER_PORT = 8080
37 VM_PORT_FOR_CONTAINER = 8080
40 class InconsistentPortConfigurationError(vme_errors.PermanentAppError):
41 """The port is already in use."""
42 pass
45 class IllegalPortConfigurationError(vme_errors.PermanentAppError):
46 """Raised if the port configuration is illegal."""
47 pass
50 def CreatePortManager(forwarded_ports, container_port):
51 """Construct a PortManager object with port forwarding configured.
53 Args:
54 forwarded_ports: A string containing desired mappings from VM host ports
55 to docker container ports.
56 container_port: An integer port number for the container port.
58 Returns:
59 The PortManager instance.
60 """
61 port_manager_obj = PortManager(container_port)
62 ports_list = forwarded_ports if forwarded_ports else []
63 logging.debug('setting forwarded ports %s', ports_list)
64 port_manager_obj.Add(ports_list, 'forwarded')
65 return port_manager_obj
68 class PortManager(object):
69 """A helper class for VmManager to deal with port mappings."""
71 def __init__(self, container_port=DEFAULT_CONTAINER_PORT):
72 self.used_host_ports = {}
73 self._port_mappings = {}
74 self.container_port = container_port
76 def Add(self, ports, kind):
77 """Load port configurations and adds them to an internal dict.
79 Args:
80 ports: A list of strings or a CSV representing port forwarding.
81 kind: what kind of port configuration this is, only used for error
82 reporting.
84 Raises:
85 InconsistentPortConfigurationError: If a port is configured to do
86 two different conflicting things.
87 IllegalPortConfigurationError: If the port is out of range or
88 is not a number.
90 Returns:
91 A dictionary with forwarding rules as external_port => local_port.
92 """
93 if isinstance(ports, basestring):
94 # split a csv
95 ports = [port.strip() for port in ports.split(',')]
96 port_translations = {}
97 for port in ports:
98 try:
99 if ':' in port:
100 host_port, docker_port = (int(p.strip()) for p in port.split(':'))
101 port_translations[host_port] = docker_port
102 else:
103 host_port = int(port)
104 docker_port = host_port
105 port_translations[host_port] = host_port
106 if (host_port in self.used_host_ports and
107 self.used_host_ports[host_port] != docker_port):
108 raise InconsistentPortConfigurationError(
109 'Configuration conflict, port %d configured to forward '
110 'differently.' % host_port)
111 self.used_host_ports[host_port] = docker_port
112 if (host_port < 1 or host_port > 65535 or
113 docker_port < 1 or docker_port > 65535):
114 raise IllegalPortConfigurationError(
115 'Failed to load %s port configuration: invalid port %s'
116 % (kind, port))
117 if docker_port < 1024:
118 raise IllegalPortConfigurationError(
119 'Cannot listen on port %d as it is priviliged, use a forwarding '
120 'port.' % docker_port)
121 if docker_port in RESERVED_DOCKER_PORTS:
122 raise IllegalPortConfigurationError(
123 'Cannot use port %d as it is reserved on the VM.'
124 % docker_port)
125 if host_port in RESERVED_HOST_PORTS:
126 raise IllegalPortConfigurationError(
127 'Cannot use port %d as it is reserved on the VM.'
128 % host_port)
129 except ValueError as e:
130 logging.exception('Bad port description')
131 raise IllegalPortConfigurationError(
132 'Failed to load %s port configuration: "%s" error: "%s"'
133 % (kind, port, e))
134 # At this point we know they are not destructive.
135 self._port_mappings.update(port_translations)
136 return port_translations
138 def GetAllMappedPorts(self):
139 """Returns all mapped ports.
141 Returns:
142 A dict of port mappings {host: docker}
144 if not self._port_mappings:
145 return {}
146 else:
147 return self._port_mappings
149 # TODO: look into moving this into a DockerManager.
150 def _BuildDockerPublishArgumentString(self):
151 """Generates a string of ports to expose to the Docker container.
153 Returns:
154 A string with --publish=host:docker pairs.
156 port_map = self.GetAllMappedPorts()
157 # Map container port to port 8080 on the VM (default to 8080 if not set)
158 port_map[VM_PORT_FOR_CONTAINER] = int(self.container_port)
159 result = ''
160 for k, v in port_map.iteritems():
161 result += '--publish=%d:%s ' % (k, v)
162 return result
164 def GetReplicaPoolParameters(self):
165 """Returns the contribution to the replica template."""
166 publish_ports = self._BuildDockerPublishArgumentString()
167 maps = {
168 'template': {
169 'vmParams': {
170 'metadata': {
171 'items': [
172 {'key': 'gae_publish_ports', 'value': publish_ports}
178 return maps