blob: ffd3601a03ed715c2d4cf11b7976ac0dc1c1009a [file] [log] [blame]
Zsolt Haraszti86be6f12016-09-27 09:56:49 -07001#
2# Copyright 2016 the original author or authors.
3#
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:
alshabib05fb71f2016-12-04 16:08:29 -080083 log.debug('failed: {}'.format(e.message))
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:
97 log.exception('failed host config creation', volumes, ports, e=e)
98 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)
106 except:
107 log.exception('Failed to connect container {} to network {}'.format(container, net_id))
108 raise
alshabibc61999a2016-10-27 16:44:27 -0700109
alshabib7941d402016-11-08 00:11:20 +0100110def create_networking_config(name, links):
alshabibc67ee3a2016-10-25 23:24:03 -0700111 """
112 Creates a container networks based on a set of containers.
113 :param name: the network name
114 :param links: the set of containers to link
115 :return: a network configuration
116 """
117 try:
alshabibc61999a2016-10-27 16:44:27 -0700118 docker_cli = Client(base_url=docker_socket)
alshabibc67ee3a2016-10-25 23:24:03 -0700119 networking_config = docker_cli.create_networking_config({
alshabibc61999a2016-10-27 16:44:27 -0700120 name : docker_cli.create_endpoint_config(links=links)
alshabibc67ee3a2016-10-25 23:24:03 -0700121 })
122 except Exception, e:
123 log.exception('failed network creation', name, e=e)
124 raise
125
126 return networking_config
127
alshabib7941d402016-11-08 00:11:20 +0100128def stop_container(container, timeout=10):
129 try:
130 docker_cli = Client(base_url=docker_socket)
131 docker_cli.stop(container, timeout=timeout)
132 except Exception, e:
133 log.exception('failed', e=e)
134 raise
alshabibc67ee3a2016-10-25 23:24:03 -0700135
alshabib7941d402016-11-08 00:11:20 +0100136def create_container(args):
137 try:
138 docker_cli = Client(base_url=docker_socket)
139 container = docker_cli.create_container(**args)
140 except Exception, e:
141 log.exception('failed', e=e)
142 raise
143 return container
144
145def start_container(container):
alshabibc67ee3a2016-10-25 23:24:03 -0700146 """
147 Starts a requested container with the appropriate configuration.
148 :param args: contains arguments for container creation
149 (see https://docker-py.readthedocs.io/en/stable/api/#create_container)
150 :return: the containers name
151 """
152 try:
alshabibc61999a2016-10-27 16:44:27 -0700153 docker_cli = Client(base_url=docker_socket)
alshabibc61999a2016-10-27 16:44:27 -0700154 response = docker_cli.start(container=container.get('Id'))
alshabibc67ee3a2016-10-25 23:24:03 -0700155 except Exception, e:
156 log.exception('failed', e=e)
157 raise
alshabibc61999a2016-10-27 16:44:27 -0700158 return response
alshabibc67ee3a2016-10-25 23:24:03 -0700159
alshabib7941d402016-11-08 00:11:20 +0100160class EventProcessor(object):
161 """
162 This class handles the api session and allows for it to
163 be terminated.
164 """
alshabib05fb71f2016-12-04 16:08:29 -0800165 def __init__(self, threads=1):
alshabib7941d402016-11-08 00:11:20 +0100166 self.client = CustomClient(base_url=docker_socket)
167 self.events = self.client.events(decode=True)
alshabib05fb71f2016-12-04 16:08:29 -0800168 log.info("Starting event processor with {} threads".format(threads))
169 self.exec_service = ThreadPoolExecutor(max_workers=threads)
alshabib7941d402016-11-08 00:11:20 +0100170
171 def stop_listening(self):
172 """
173 Shuts down the socket.
174 :return: None
175 """
176 if self.events is not None:
177 sock = self.client._get_raw_response_socket(self.events.response)
178 sock.shutdown(socket.SHUT_RDWR)
179
180
181 def listen_for_events(self, handlers):
182 """
183 Listens to the docker event stream and applies the functions
184 in the passed handler to each event.
185
186 docker containers can report the following events:
187
188 attach, commit, copy, create, destroy, detach, die,
189 exec_create, exec_detach, exec_start, export,
190 health_status, kill, oom, pause, rename, resize,
191 restart, start, stop, top, unpause, update
192
193 :param handlers: a dict of functions
194 :return: None
195 """
196 if not handlers or len(handlers) == 0:
197 raise ValueError("Handlers cannot be empty")
198
199 for event in self.events:
200 for k in ['time', 'Time']:
201 if k in event:
202 event[k] = datetime.fromtimestamp(event[k])
203 log.debug('docker event: {}'.format(event))
204
205 data = {}
206 i = get_id(event)
207 if i is not None:
208 try:
209 if 'from' in event or 'From' in event:
210 data = self.client.inspect_container(i)
211 else:
212 data = self.client.inspect_image(i)
213 data[i] = data
214 except errors.NotFound:
alshabib9d222022016-11-10 16:11:09 -0800215 log.debug('No data for container {}'.format(i))
alshabib7941d402016-11-08 00:11:20 +0100216
217 status = get_status(event)
218 if status in handlers:
alshabib05fb71f2016-12-04 16:08:29 -0800219 self.exec_service.submit(handlers[get_status(event)], event,
220 data, handlers['podder_config'])
alshabib7941d402016-11-08 00:11:20 +0100221 else:
222 log.debug("No handler for {}; skipping...".format(status))
223
224class CustomGenerator(object):
225 """
226 This is a custom ugly class that allows for the generator
227 to be (kind of) cleanly closed.
228 """
229 def __init__(self, stream, response, decode):
230 self.stream = stream
231 self.response = response
232 self.decode = decode
233
234 def __iter__(self):
235 for item in super(CustomClient, self.stream).\
236 _stream_helper(self.response, self.decode):
237 yield item
238
239class CustomClient(Client):
240 def _stream_helper(self, response, decode=False):
241 return CustomGenerator(self, response, decode)
242
243def get_status(event):
244 for k in ['status', 'Status']:
245 if k in event:
246 return event[k]
247
248def get_id(event):
249 for k in ['id', 'ID', 'Id']:
250 if k in event:
251 return event[k]