blob: 89acab62c0e6d1e80d3297fd58974564c7cc3fa5 [file] [log] [blame]
Zsolt Haraszti86be6f12016-09-27 09:56:49 -07001#
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -08002# Copyright 2017 the original author or authors.
Zsolt Haraszti86be6f12016-09-27 09:56:49 -07003#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Some docker related convenience functions
19"""
alshabib7941d402016-11-08 00:11:20 +010020from datetime import datetime
alshabib05fb71f2016-12-04 16:08:29 -080021from concurrent.futures import ThreadPoolExecutor
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070022
23import os
alshabib7941d402016-11-08 00:11:20 +010024import socket
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070025from structlog import get_logger
26
alshabib7941d402016-11-08 00:11:20 +010027from docker import Client, errors
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070028
29
alshabibc61999a2016-10-27 16:44:27 -070030docker_socket = os.environ.get('DOCKER_SOCK', 'unix://tmp/docker.sock')
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070031log = get_logger()
32
33
alshabib7941d402016-11-08 00:11:20 +010034def remove_container(id, force=True):
35 try:
36 docker_cli = Client(base_url=docker_socket)
37 containers = docker_cli.remove_container(id, force=force)
38
39 except Exception, e:
40 log.exception('failed', e=e)
41 raise
42
43 return containers
44
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070045def get_my_containers_name():
46 """
47 Return the docker containers name in which this process is running.
48 To look up the container name, we use the container ID extracted from the
49 $HOSTNAME environment variable (which is set by docker conventions).
50 :return: String with the docker container name (or None if any issue is
51 encountered)
52 """
53 my_container_id = os.environ.get('HOSTNAME', None)
54
55 try:
alshabibc61999a2016-10-27 16:44:27 -070056 docker_cli = Client(base_url=docker_socket)
Zsolt Haraszti86be6f12016-09-27 09:56:49 -070057 info = docker_cli.inspect_container(my_container_id)
58
59 except Exception, e:
60 log.exception('failed', my_container_id=my_container_id, e=e)
61 raise
62
63 name = info['Name'].lstrip('/')
64
65 return name
alshabibc67ee3a2016-10-25 23:24:03 -070066
alshabib7941d402016-11-08 00:11:20 +010067def get_all_running_containers():
68 try:
69 docker_cli = Client(base_url=docker_socket)
70 containers = docker_cli.containers()
71
72 except Exception, e:
73 log.exception('failed', e=e)
74 raise
75
76 return containers
77
78def inspect_container(id):
79 try:
80 docker_cli = Client(base_url=docker_socket)
81 info = docker_cli.inspect_container(id)
alshabib7941d402016-11-08 00:11:20 +010082 except Exception, e:
Khen Nursimulu90fc35d2017-01-09 08:42:04 -050083 log.exception('failed-inspect-container', id=id, e=e)
alshabib7941d402016-11-08 00:11:20 +010084 raise
85
86 return info
87
88
alshabibc61999a2016-10-27 16:44:27 -070089def create_host_config(volumes, ports):
90 try:
alshabib7941d402016-11-08 00:11:20 +010091 port_bindings = ports
alshabibc61999a2016-10-27 16:44:27 -070092 binds = ['{0}:{1}'.format(k, v) for k, v in volumes.iteritems()]
93 docker_cli = Client(base_url=docker_socket)
94 host_config = docker_cli.create_host_config(binds=binds,
95 port_bindings=port_bindings)
96 except Exception, e:
Khen Nursimulu90fc35d2017-01-09 08:42:04 -050097 log.exception('failed-host-config-creation', volumes, ports, e=e)
alshabibc61999a2016-10-27 16:44:27 -070098 raise
99
100 return host_config
101
alshabib7941d402016-11-08 00:11:20 +0100102def connect_container_to_network(container, net_id, links):
103 try:
104 docker_cli = Client(base_url=docker_socket)
105 docker_cli.connect_container_to_network(container, net_id, links=links)
Khen Nursimulu90fc35d2017-01-09 08:42:04 -0500106 except Exception, e:
107 log.exception('failed-connection-to-network',
108 container=container, net_id=net_id, e=e)
alshabib7941d402016-11-08 00:11:20 +0100109 raise
alshabibc61999a2016-10-27 16:44:27 -0700110
alshabib7941d402016-11-08 00:11:20 +0100111def create_networking_config(name, links):
alshabibc67ee3a2016-10-25 23:24:03 -0700112 """
113 Creates a container networks based on a set of containers.
114 :param name: the network name
115 :param links: the set of containers to link
116 :return: a network configuration
117 """
118 try:
alshabibc61999a2016-10-27 16:44:27 -0700119 docker_cli = Client(base_url=docker_socket)
alshabibc67ee3a2016-10-25 23:24:03 -0700120 networking_config = docker_cli.create_networking_config({
alshabibc61999a2016-10-27 16:44:27 -0700121 name : docker_cli.create_endpoint_config(links=links)
alshabibc67ee3a2016-10-25 23:24:03 -0700122 })
123 except Exception, e:
Khen Nursimulu90fc35d2017-01-09 08:42:04 -0500124 log.exception('failed-network-creation', name, e=e)
alshabibc67ee3a2016-10-25 23:24:03 -0700125 raise
126
127 return networking_config
128
alshabib7941d402016-11-08 00:11:20 +0100129def stop_container(container, timeout=10):
130 try:
131 docker_cli = Client(base_url=docker_socket)
132 docker_cli.stop(container, timeout=timeout)
133 except Exception, e:
134 log.exception('failed', e=e)
135 raise
alshabibc67ee3a2016-10-25 23:24:03 -0700136
alshabib7941d402016-11-08 00:11:20 +0100137def create_container(args):
138 try:
139 docker_cli = Client(base_url=docker_socket)
140 container = docker_cli.create_container(**args)
141 except Exception, e:
142 log.exception('failed', e=e)
143 raise
144 return container
145
146def start_container(container):
alshabibc67ee3a2016-10-25 23:24:03 -0700147 """
148 Starts a requested container with the appropriate configuration.
149 :param args: contains arguments for container creation
150 (see https://docker-py.readthedocs.io/en/stable/api/#create_container)
151 :return: the containers name
152 """
153 try:
alshabibc61999a2016-10-27 16:44:27 -0700154 docker_cli = Client(base_url=docker_socket)
alshabibc61999a2016-10-27 16:44:27 -0700155 response = docker_cli.start(container=container.get('Id'))
alshabibc67ee3a2016-10-25 23:24:03 -0700156 except Exception, e:
157 log.exception('failed', e=e)
158 raise
alshabibc61999a2016-10-27 16:44:27 -0700159 return response
alshabibc67ee3a2016-10-25 23:24:03 -0700160
alshabib7941d402016-11-08 00:11:20 +0100161class EventProcessor(object):
162 """
163 This class handles the api session and allows for it to
164 be terminated.
165 """
alshabib05fb71f2016-12-04 16:08:29 -0800166 def __init__(self, threads=1):
alshabib7941d402016-11-08 00:11:20 +0100167 self.client = CustomClient(base_url=docker_socket)
168 self.events = self.client.events(decode=True)
Khen Nursimulu90fc35d2017-01-09 08:42:04 -0500169 log.info("starting", threads=threads)
alshabib05fb71f2016-12-04 16:08:29 -0800170 self.exec_service = ThreadPoolExecutor(max_workers=threads)
alshabib7941d402016-11-08 00:11:20 +0100171
172 def stop_listening(self):
173 """
174 Shuts down the socket.
175 :return: None
176 """
177 if self.events is not None:
178 sock = self.client._get_raw_response_socket(self.events.response)
179 sock.shutdown(socket.SHUT_RDWR)
180
181
182 def listen_for_events(self, handlers):
183 """
184 Listens to the docker event stream and applies the functions
185 in the passed handler to each event.
186
187 docker containers can report the following events:
188
189 attach, commit, copy, create, destroy, detach, die,
190 exec_create, exec_detach, exec_start, export,
191 health_status, kill, oom, pause, rename, resize,
192 restart, start, stop, top, unpause, update
193
194 :param handlers: a dict of functions
195 :return: None
196 """
197 if not handlers or len(handlers) == 0:
198 raise ValueError("Handlers cannot be empty")
199
200 for event in self.events:
201 for k in ['time', 'Time']:
202 if k in event:
203 event[k] = datetime.fromtimestamp(event[k])
Khen Nursimulu90fc35d2017-01-09 08:42:04 -0500204 log.debug('docker-event', event=event)
alshabib7941d402016-11-08 00:11:20 +0100205
206 data = {}
207 i = get_id(event)
208 if i is not None:
209 try:
210 if 'from' in event or 'From' in event:
211 data = self.client.inspect_container(i)
212 else:
213 data = self.client.inspect_image(i)
214 data[i] = data
215 except errors.NotFound:
Khen Nursimulu90fc35d2017-01-09 08:42:04 -0500216 log.debug('no-data-for-container', container=i)
alshabib7941d402016-11-08 00:11:20 +0100217
218 status = get_status(event)
219 if status in handlers:
alshabib05fb71f2016-12-04 16:08:29 -0800220 self.exec_service.submit(handlers[get_status(event)], event,
221 data, handlers['podder_config'])
alshabib7941d402016-11-08 00:11:20 +0100222 else:
Khen Nursimulu90fc35d2017-01-09 08:42:04 -0500223 log.debug('no-handler', handler=status)
alshabib7941d402016-11-08 00:11:20 +0100224
225class CustomGenerator(object):
226 """
227 This is a custom ugly class that allows for the generator
228 to be (kind of) cleanly closed.
229 """
230 def __init__(self, stream, response, decode):
231 self.stream = stream
232 self.response = response
233 self.decode = decode
234
235 def __iter__(self):
236 for item in super(CustomClient, self.stream).\
237 _stream_helper(self.response, self.decode):
238 yield item
239
240class CustomClient(Client):
241 def _stream_helper(self, response, decode=False):
242 return CustomGenerator(self, response, decode)
243
244def get_status(event):
245 for k in ['status', 'Status']:
246 if k in event:
247 return event[k]
248
249def get_id(event):
250 for k in ['id', 'ID', 'Id']:
251 if k in event:
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -0800252 return event[k]