Skip to content

Commit

Permalink
seems to work
Browse files Browse the repository at this point in the history
  • Loading branch information
Michel Käser committed Aug 9, 2015
1 parent de4e842 commit 6325b3f
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 66 deletions.
112 changes: 82 additions & 30 deletions ipynbsrv/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -609,13 +609,15 @@ def create_snapshot(self, name, description=None):

def get_backend_base_url(self):
"""
Returns the base URL under which proxied services should listen within the container.
Get the base url under which the protected service should listen.
"""
base_url = None
if self.is_image_based() and self.image.protected_port:
base_url = settings.CONTAINER_ACCESS_BASE_URI + self.server.internal_ip + PortMapping
elif self.is_clone() and self.clone_of.is_image_based() and self.clone_of.image.protected_port:
base_url = settings.CONTAINER_ACCESS_BASE_URI + self.server.internal_ip + PortMapping
if self.has_protected_port():
for port_mapping in self.port_mappings.all():
if port_mapping.internal_port == self.image.protected_port:
ip_and_port = port_mapping.server.internal_ip + ':' + str(port_mapping.external_port)
base_url = settings.CONTAINER_ACCESS_BASE_URI + ip_and_port.encode('hex') + '/'
break
return base_url

def get_backend_name(self):
Expand Down Expand Up @@ -643,9 +645,16 @@ def has_clones(self):
return Container.objects.filter(clone_of=self).exists()
has_clones.boolean = True

def has_protected_port(self):
"""
Return `True` if the container is exposing a protected port.
"""
return self.image.protected_port is not None
has_protected_port.boolean = True

def is_clone(self):
"""
Return true if this container is a clone of another one.
Return `True` if this container is a clone of another one.
"""
return self.clone_of is not None
is_clone.boolean = True
Expand Down Expand Up @@ -1062,30 +1071,6 @@ def __unicode__(self):
return self.__str__()


class PortMapping(models.Model):

"""
TODO: write doc.
"""

container = models.ForeignKey(
'container',
related_name='port_mappings',
help_text='The container for which this port mapping is.'
)
external_port = models.PositiveIntegerField(
help_text='The public port for this port mapping (e.g. the one under which it can be accessed).'
)
internal_port = models.PositiveIntegerField(
help_text='The container internal port.'
)

class Meta:
unique_together = (
('container', 'external_port', 'internal_port')
)


class NotificationLog(models.Model):

"""
Expand Down Expand Up @@ -1130,6 +1115,73 @@ def __unicode__(self):
return self.__str__()


class PortMapping(models.Model):

"""
TODO: write doc.
"""

server = models.ForeignKey(
'Server',
related_name='port_mappings',
help_text='The server on which this port mapping is active.'
)
container = models.ForeignKey(
'Container',
related_name='port_mappings',
help_text='The container for which this port mapping is.'
)
external_port = models.PositiveIntegerField(
help_text='The public port for this port mapping (e.g. the one under which it can be accessed).'
)
internal_port = models.PositiveIntegerField(
help_text='The container internal port.'
)

def clean(self):
"""
:inherit.
"""
if not self.server and self.container:
self.server = self.container.server
if not self.external_port:
self.external_port = PortMapping.get_available_server_port(self.server)

@classmethod
def get_available_server_port(cls, server):
"""
Get a free port safe to use as `external_port` on `server`.
:param server: The server to get a free port of.
"""
port = settings.CONTAINER_PORT_MAPPINGS_START_PORT
mappings = cls.objects.filter(server=server)
if mappings.exists():
port = mappings.latest('external_port').external_port + 1
# TODO: if reached settings.CONTAINER_PORT_MAPPINGS_END_PORT
# start over and look while i+1 for free port.
# ServerSelectionAlgorithm must guarantee enough ports are free!
return port

def __str__(self):
"""
:inherit.
"""
return smart_unicode("%s:%i->%s:%i" % (self.server, self.external_port, self.container, self.internal_port))

def __unicode__(self):
"""
:inherit.
"""
return self.__str__()

class Meta:
unique_together = (
('server', 'external_port'),
('container', 'external_port', 'internal_port')
)


class Server(models.Model):

"""
Expand Down
2 changes: 2 additions & 0 deletions ipynbsrv/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
Settings related to containers.
"""
CONTAINER_ACCESS_BASE_URI = '/ct/'
CONTAINER_PORT_MAPPINGS_START_PORT = 49152
CONTAINER_PORT_MAPPINGS_END_PORT = 65534

"""
Settings storing the paths (relative to STORAGE_DIR_BASE) under which (user) directories should be created.
Expand Down
79 changes: 52 additions & 27 deletions ipynbsrv/core/signals/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from ipynbsrv.contract.errors import ContainerBackendError, ContainerNotFoundError
from ipynbsrv.core import settings
from ipynbsrv.core.helpers import get_storage_backend
from ipynbsrv.core.models import Container, ContainerImage
from ipynbsrv.core.models import Container, ContainerImage, PortMapping
from ipynbsrv.core.signals.signals import *
from os import path
import time
Expand All @@ -13,25 +13,69 @@
storage_backend = get_storage_backend()


def create_container_port_mappings(container):
"""
Create the port mappings for the given container.
:param container: The container to create the mappings for.
"""
ports = []
image = None
if container.is_image_based():
image = container.image
elif container.is_clone() and container.clone_of.is_image_based():
image = container.clone_of.image

if image:
protected_port = image.protected_port
public_ports = image.public_ports
if protected_port:
mapping = PortMapping(
server=container.server,
container=container,
external_port=PortMapping.get_available_server_port(container.server),
internal_port=protected_port
)
mapping.save()
ports.append({
ContainerBackend.PORT_MAPPING_KEY_ADDRESS: mapping.server.internal_ip,
ContainerBackend.PORT_MAPPING_KEY_EXTERNAL: mapping.external_port,
ContainerBackend.PORT_MAPPING_KEY_INTERNAL: mapping.internal_port
})
if public_ports:
for port in public_ports.split(','):
mapping = PortMapping(
server=container.server,
container=container,
external_port=PortMapping.get_available_server_port(container.server),
internal_port=port
)
mapping.save()
ports.append({
ContainerBackend.PORT_MAPPING_KEY_ADDRESS: '0.0.0.0',
ContainerBackend.PORT_MAPPING_KEY_EXTERNAL: mapping.external_port,
ContainerBackend.PORT_MAPPING_KEY_INTERNAL: mapping.internal_port
})
return ports


@receiver(container_created)
def create_on_server(sender, container, **kwargs):
"""
Create the newly saved container on the server's container backend.
"""
if container is not None:
ports = []
ports = create_container_port_mappings(container)
clone_of = None
cmd = None
image = None
if container.is_image_based():
ports = get_container_port_mappings(container)
cmd = container.image.command
image = container.image.backend_pk
clone_of = None
if container.is_clone():
elif container.is_clone() and container.clone_of.is_image_based():
clone_of = container.clone_of.backend_pk
if container.clone_of.is_image_based():
ports = get_container_port_mappings(container.clone_of)
cmd = container.clone_of.image.command
cmd = container.clone_of.image.command
image = container.clone_of.image.backend_pk

result = None
try:
Expand Down Expand Up @@ -109,25 +153,6 @@ def delete_on_server(sender, container, **kwargs):
raise ex


def get_container_port_mappings(container):
"""
Return the list of port mappings for the container that can be passed to container backends.
"""
ports = []
if container.image.protected_port is not None:
ports.append({
ContainerBackend.PORT_MAPPING_KEY_ADDRESS: container.server.internal_ip,
ContainerBackend.PORT_MAPPING_KEY_INTERNAL: container.image.protected_port
})
if container.image.public_ports is not None:
for port in container.image.public_ports.split(','):
ports.append({
ContainerBackend.PORT_MAPPING_KEY_ADDRESS: '0.0.0.0',
ContainerBackend.PORT_MAPPING_KEY_INTERNAL: int(port)
})
return ports


@receiver(container_restarted)
def restart_on_server(sender, container, **kwargs):
"""
Expand Down
16 changes: 7 additions & 9 deletions lib/confs/nginx/ipynbsrv.conf
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,15 @@ server {
}

# proxy/workspace location
location ~* /workspace/(\d+)/(.*) {
location ~* /ct/([^\/]+)/(.*) {
# authorization
# ensure only container's owner can access it
satisfy all;
auth_request /auth;
#satisfy all;
#auth_request /auth;

# use the Django error pages
# forbidden = 404 so the user doesn't know there is a container
# 50x grouped to 500
error_page 403 404 /error/404;
error_page 500 502 503 504 /error/500;
# get the IP and port from encoded part
set $decoded_backend '';
set_decode_hex $decoded_backend $1;

# needed for websockets connections
proxy_http_version 1.1;
Expand All @@ -78,6 +76,6 @@ server {
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Real-IP $remote_addr;

proxy_pass http://172.17.42.1:$1;
proxy_pass http://$decoded_backend;
}
}

0 comments on commit 6325b3f

Please sign in to comment.