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."""
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
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."""
45 class IllegalPortConfigurationError(vme_errors
.PermanentAppError
):
46 """Raised if the port configuration is illegal."""
50 def CreatePortManager(forwarded_ports
, container_port
):
51 """Construct a PortManager object with port forwarding configured.
54 forwarded_ports: A dictionary containing desired mappings from VM host port
55 to docker container port.
56 container_port: An integer port number for the container port.
59 The PortManager instance.
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.
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
85 InconsistentPortConfigurationError: If a port is configured to do
86 two different conflicting things.
87 IllegalPortConfigurationError: If the port is out of range or
91 A dictionary with forwarding rules as external_port => local_port.
93 if isinstance(ports
, basestring
):
95 ports
= [port
.strip() for port
in ports
.split(',')]
96 port_translations
= {}
100 host_port
, docker_port
= (int(p
.strip()) for p
in port
.split(':'))
101 port_translations
[host_port
] = docker_port
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'
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.'
125 if host_port
in RESERVED_HOST_PORTS
:
126 raise IllegalPortConfigurationError(
127 'Cannot use port %d as it is reserved on the VM.'
129 except ValueError as e
:
130 logging
.exception('Bad port description')
131 raise IllegalPortConfigurationError(
132 'Failed to load %s port configuration: "%s" error: "%s"'
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.
142 A dict of port mappings {host: docker}
144 if not self
._port
_mappings
:
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.
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
)
160 for k
, v
in port_map
.iteritems():
161 result
+= '--publish=%d:%s ' % (k
, v
)
164 def GetReplicaPoolParameters(self
):
165 """Returns the contribution to the replica template."""
166 publish_ports
= self
._BuildDockerPublishArgumentString
()
172 {'key': 'gae_publish_ports', 'value': publish_ports
}