Skip to content

Commit f08e9b0

Browse files
author
Sivaram Mothiki
committed
Merge pull request #3612 from mboersma/swarm
feat(controller): add swarm scheduler tech preview
2 parents f4c2a24 + 6c4905e commit f08e9b0

23 files changed

Lines changed: 771 additions & 107 deletions

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ builder/image/bin/get-app-config
3131
builder/image/bin/get-app-values
3232
builder/image/bin/publish-release-controller
3333
builder/image/bin/yaml2json-procfile
34-
cache/image/bin
34+
cache/image/bin/
3535
client/dist/
3636
client/makeself/
3737
contrib/azure/azure-user-data
@@ -50,6 +50,7 @@ logspout/Godeps/
5050
publisher/Godeps/
5151
publisher/image/bin/publisher
5252
router/Godeps/
53+
swarm/image/bin/
5354

5455
# coverage reports
5556
.coverage

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ repo_path = github.com/deis/deis
1010
GO_PACKAGES = pkg/time version
1111
GO_PACKAGES_REPO_PATH = $(addprefix $(repo_path)/,$(GO_PACKAGES))
1212

13-
COMPONENTS=builder cache controller database logger logspout publisher registry router store
13+
COMPONENTS=builder cache controller database logger logspout publisher registry router store swarm
1414
START_ORDER=publisher store logger logspout database cache registry controller builder router
1515
CLIENTS=client deisctl
1616

contrib/coreos/user-data.example

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ coreos:
3232
ExecStart=/usr/bin/bash -c 'if fleetd --version | grep -q 0.9.1; then curl -sSL --retry 5 --retry-delay 2 -o /opt/bin/fleetd-0.9.2 https://s3-us-west-2.amazonaws.com/opdemand/fleetd-v0.9.2 && chmod +x /opt/bin/fleetd-0.9.2 && mkdir -p /etc/systemd/system/fleet.service.d/ && ln -sf /opt/conf/fleetd-092-custom-binary.conf /etc/systemd/system/fleet.service.d/99-upgrade-fleet-091.conf; else rm -f /etc/systemd/system/fleet.service.d/99-upgrade-fleet-091.conf; fi'
3333
RemainAfterExit=yes
3434
Type=oneshot
35+
- name: docker-tcp.socket
36+
command: start
37+
enable: true
38+
content: |
39+
[Unit]
40+
Description=Docker Socket for the API
41+
42+
[Socket]
43+
ListenStream=2375
44+
BindIPv6Only=both
45+
Service=docker.service
46+
[Install]
47+
WantedBy=sockets.target
3548
- name: stop-update-engine.service
3649
command: start
3750
content: |

controller/conf.d/confd_settings.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ keys = [
1010
"/deis/registry",
1111
"/deis/domains",
1212
"/deis/platform",
13+
"/deis/scheduler",
1314
]
1415
reload_cmd = "/app/bin/reload"

controller/scheduler/swarm.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import re
2+
import time
3+
4+
from django.conf import settings
5+
from docker import Client
6+
7+
from .states import JobState
8+
9+
10+
MATCH = re.compile(
11+
r'(?P<app>[a-z0-9-]+)_?(?P<version>v[0-9]+)?\.?(?P<c_type>[a-z-_]+)?.(?P<c_num>[0-9]+)')
12+
13+
14+
class SwarmClient(object):
15+
16+
def __init__(self, target, auth, options, pkey):
17+
self.target = settings.SWARM_HOST
18+
# single global connection
19+
self.registry = settings.REGISTRY_HOST + ':' + settings.REGISTRY_PORT
20+
self.docker_cli = Client("tcp://{}:2395".format(self.target),
21+
timeout=1200, version='1.17')
22+
23+
def create(self, name, image, command='', template=None, **kwargs):
24+
"""Create a container"""
25+
cimage = self.registry + '/' + image
26+
affinity = "affinity:container!=~/{}*/".format(re.split(r'_v\d.', name)[0])
27+
l = locals().copy()
28+
l.update(re.match(MATCH, name).groupdict())
29+
mem = kwargs.get('memory', {}).get(l['c_type'])
30+
if mem:
31+
mem = mem.lower()
32+
if mem[-2:-1].isalpha() and mem[-1].isalpha():
33+
mem = mem[:-1]
34+
cpu = kwargs.get('cpu', {}).get(l['c_type'])
35+
self.docker_cli.create_container(image=cimage, name=name,
36+
command=command.encode('utf-8'), mem_limit=mem,
37+
cpu_shares=cpu,
38+
environment=[affinity],
39+
host_config={'PublishAllPorts': True})
40+
41+
def start(self, name):
42+
"""
43+
Start a container
44+
"""
45+
self.docker_cli.start(name)
46+
47+
def stop(self, name):
48+
"""
49+
Stop a container
50+
"""
51+
self.docker_cli.stop(name)
52+
53+
def destroy(self, name):
54+
"""
55+
Destroy a container
56+
"""
57+
self.stop(name)
58+
self.docker_cli.remove_container(name)
59+
60+
def run(self, name, image, entrypoint, command):
61+
"""
62+
Run a one-off command
63+
"""
64+
cimage = self.registry + '/' + image
65+
# use affinity for nodes that already have the image
66+
affinity = "affinity:image==~{}".format(cimage)
67+
self.docker_cli.create_container(image=cimage, name=name,
68+
command=command.encode('utf-8'),
69+
environment=[affinity],
70+
entrypoint=[entrypoint])
71+
time.sleep(2)
72+
self.start(name)
73+
rc = 0
74+
while (True):
75+
if self._get_container_state(name) == JobState.created:
76+
break
77+
time.sleep(1)
78+
try:
79+
output = self.docker_cli.logs(name)
80+
return rc, output
81+
except:
82+
rc = 1
83+
return rc, output
84+
85+
def _get_container_state(self, name):
86+
try:
87+
if self.docker_cli.inspect_container(name)['State']['Running']:
88+
return JobState.up
89+
else:
90+
return JobState.created
91+
except:
92+
return JobState.destroyed
93+
94+
def state(self, name):
95+
try:
96+
for _ in range(30):
97+
return self._get_container_state(name)
98+
time.sleep(1)
99+
# FIXME (smothiki): should be able to send JobState.crashed
100+
except KeyError:
101+
return JobState.error
102+
except RuntimeError:
103+
return JobState.destroyed
104+
105+
def attach(self, name):
106+
"""
107+
Attach to a job's stdin, stdout and stderr
108+
"""
109+
raise NotImplementedError
110+
111+
def _get_hostname(self, application_name):
112+
hostname = settings.UNIT_HOSTNAME
113+
if hostname == 'default':
114+
return ''
115+
elif hostname == 'application':
116+
# replace underscore with dots, since underscore is not valid in DNS hostnames
117+
dns_name = application_name.replace('_', '.')
118+
return dns_name
119+
elif hostname == 'server':
120+
raise NotImplementedError
121+
else:
122+
raise RuntimeError('Unsupported hostname: ' + hostname)
123+
124+
def _get_portbindings(self, image):
125+
dictports = self.docker_cli.inspect_image(image)['ContainerConfig']['ExposedPorts']
126+
for port, mapping in dictports.items():
127+
dictports[port] = None
128+
return dictports
129+
130+
def _get_ports(self, image):
131+
dictports = self.docker_cli.inspect_image(image)['ContainerConfig']['ExposedPorts']
132+
return [int(port.split('/')[0]) for port in dictports.iterkeys()]
133+
134+
SchedulerClient = SwarmClient

controller/templates/confd_settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
except:
1111
SCHEDULER_OPTIONS = {}
1212

13+
# scheduler swarm manager host
14+
SWARM_HOST = '{{ if exists "/deis/scheduler/swarm/host" }}{{ getv "/deis/scheduler/swarm/host" }}{{ else }}127.0.0.1{{ end }}'
15+
1316
# base64-encoded SSH private key to facilitate current version of "deis run"
1417
SSH_PRIVATE_KEY = """{{ if exists "/deis/platform/sshPrivateKey" }}{{ getv "/deis/platform/sshPrivateKey" }}{{ else }}""{{end}}"""
1518

controller/tests/controller_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ func TestController(t *testing.T) {
2424
"/deis/database",
2525
"/deis/registry",
2626
"/deis/domains",
27+
"/deis/scheduler",
2728
}
2829
tag, etcdPort := utils.BuildTag(), utils.RandomPort()
2930
imageName := utils.ImagePrefix() + "controller" + ":" + tag

0 commit comments

Comments
 (0)