class DockerEvents: def __init__(self, docker): self.running = False self.docker = docker self.channel = Channel() def listen(self): return self.channel.listen() def saferun(self): if self.running: return self.running = True asyncio.async(self.run()) @asyncio.coroutine def run(self): self.running = True containers = self.docker.containers response = yield from aiohttp.request( 'GET', self.docker._endpoint('events'), connector=self.docker.connector, ) while True: chunk = yield from response.content.readany() # XXX: WTF. WTF WTF WTF. # WHY AM I NOT GETTING A RETURN ON .READLINE()?! WHY NO NEWLINE # https://github.com/dotcloud/docker/pull/4276 ADDED THEM if chunk == b'': break data = json.loads(chunk.decode('utf-8')) if 'time' in data: data['time'] = dt.datetime.fromtimestamp(data['time']) if 'id' in data and data['status'] in [ "start", "create", ]: data['container'] = yield from containers.get(data['id']) asyncio.async(self.channel.put(data)) response.close() self.running = False
class DockerLog: def __init__(self, docker, container): self.docker = docker self.channel = Channel() self.container = container self.running = False def listen(self): return self.channel.listen() def saferun(self): if self.running: return self.running = True asyncio.async(self.run()) @asyncio.coroutine def run(self): self.running = True containers = self.docker.containers url = self.docker._endpoint( 'containers/{id}/logs'.format(id=self.container._id), follow=True, stdout=True, stderr=True, ) response = yield from aiohttp.request( 'GET', url, connector=self.docker.connector) while True: msg = yield from response.content.readline() asyncio.async(self.channel.put(msg)) if msg == b'': break self.running = False
async def initialize(self): self.logs = Channel() coro = self.events_reader(self.dc, self.logs) await scheduler.spawn(coro)
class DockerManager(): image_navigator: ImageNavigator reserved_ports: Set container_params: pdict def __init__(self, images, container_params, image_params, image_navigator, start_port=8900, end_port=8999, **kwargs): # instance of low-level async docker client self.dc = aiodocker.Docker() # containers images navigator self.image_navigator = image_navigator # pool start port self.start_port = start_port # pool end port self.end_port = end_port # ports reservation self.reserved_ports = set() # common container params self.container_params = pdict.from_dict(container_params) self.image_params = pdict.from_dict(image_params) async def initialize(self): self.logs = Channel() coro = self.events_reader(self.dc, self.logs) await scheduler.spawn(coro) async def logs_reader(self, docker, container: DockerContainer, channel: Channel, name, cid): log_reader = container.logs subscriber = log_reader.subscribe() unixts = int(time()) await scheduler.spawn(log_reader.run(since=int(unixts / 1000))) while True: log_record = await subscriber.get() ts, id = idgen.take() if log_record is None: logger.info('closing docker logs reader') break mv = memoryview(log_record) if len(log_record) <= 8: logger.warn('small shit', len=len(log_record), b64val=b64encode(log_record).decode()) continue message = bytes(mv[8:]).decode('utf-8', 'replace') source = logs_sources.get(str(mv[0]), '') size = struct.unpack('>L', mv[4:8])[0] msg = LogRecord(id, ts, cid, name, source, size, message) await channel.publish(msg) async def events_reader(self, docker, logs): subscriber = docker.events.subscribe() for bc in await self.containers(inband=False, status='running'): container = bc.container await scheduler.spawn( self.logs_reader(docker, container, logs, bc.name, bc.id)) logger.debug(f'creating logger for {bc.name}') while True: event = await subscriber.get() if event is None: break if event['Action'] == 'start' and event['Type'] == 'container': cid = event['Actor']['ID'] container = await docker.containers.get(cid) name = event['Actor']['Attributes']['name'] await scheduler.spawn( self.logs_reader(docker, container, logs, name, cid)) def get_log_reader(self): return self.logs.subscribe() async def containers(self, as_dict=False, status=None, fullinfo=False, inband=True): filters = pdict() if inband: filters.label = ['inband'] if status: filters.status = [status] containers = await self.dc.containers.list( all=True, filters=ujson.dumps(filters)) lst = [] for c in containers: bc = await BandContainer.create_with_info(c) lst.append(bc) return lst if not as_dict else {c.name: c for c in lst} async def conts_list(self): cs = await self.containers() return [c.short_info for c in cs] async def get(self, name): try: container = await self.dc.containers.get(name) if container: return BandContainer(container) except DockerError as e: logger.warn("Fetched exception", status=e.status, message=e.message) # return (await self.containers()).get(name, None) async def available_ports(self): available_ports = set(range(self.start_port, self.end_port)) conts = await self.containers(fullinfo=True) used_ports = set() for cont in conts: cports = cont.ports logger.info('container ports', cname=cont.name, cports=cports) if not cports: logger.warn('no ports', cname=cont.name, cports=cports) for p in cports: used_ports.add(p) logger.info(f"ports used summary", used_ports=used_ports) return available_ports - used_ports - self.reserved_ports def hold_ports(self, ports): for port in ports: self.reserved_ports.add(port) def free_ports(self, ports): for port in ports: self.reserved_ports.add(port) async def remove_container(self, name): # removing if running try: container = await self.get(name) if container: await container.fill() if container.state == 'running': container_autoremove = container.auto_removable() logger.info("Stopping container") await container.stop() if not container_autoremove: await container.delete() else: await container.delete() await asyncio.sleep(0.5) # try: # await container.wait(condition="removed") # except DockerError as e: # logger.debug('Docker 404 received on wait request') # if e.status != 404: # raise e except DockerError: logger.exception('container remove exc') return True async def stop_container(self, name): conts = await self.containers(as_dict=True) if name in conts.keys(): c = conts[name] logger.info(f"stopping container {c.name}") await c.stop() return True async def start_container(self, name): conts = await self.containers(as_dict=True) if name in conts.keys(): c = conts[name] logger.info(f"starting container {c.name}") await c.start() return True async def restart_container(self, name): conts = await self.containers(as_dict=True) if name in conts.keys(): c = conts[name] logger.info(f"restarting container {c.name}") await c.restart() return True async def create_image(self, img, img_options): logger.debug("Building image", n=img.name, io=img_options, path=img.path) async with img.create(img_options) as builder: progress = pdict() struct = builder.struct() last = time() async for chunk in await self.dc.images.build(**struct): if isinstance(chunk, dict): chunk = pdict.from_dict(chunk) if chunk.aux: struct.id = chunk.aux.ID logger.debug('chunk', chunk=chunk) elif chunk.status and chunk.id: progress[chunk.id] = chunk if time() - last > 1: logger.info("\nDocker build progress", progress=progress) last = time() elif chunk.stream: # logger.debug('chunk', chunk=chunk) step = re.search(r'Step\s(\d+)\/(\d+)', chunk.stream) if step: logger.debug('Docker build step ', groups=step.groups()) else: logger.debug('unknown chunk', chunk=chunk) else: logger.debug('unknown chunk type', type=type(chunk), chunk=chunk) if not struct.id: raise Exception('Build process not completed') logger.info('Docker image created', struct_id=struct.id) return img.set_data(await self.dc.images.get(img.name)) async def run_container(self, name, env={}, nocache=None, auto_remove=None, **kwargs): image_options = dict(nocache=def_val(nocache, False), **self.image_params) container_options = dict(auto_remove=def_val(auto_remove, False)) logger.info('called run container (kwargs will not used)', env=env, func_args=dict(auto_remove=auto_remove, nocache=nocache, kwargs=kwargs), image_options=image_options, container_options=container_options) # building image service_img = self.image_navigator[name] await self.create_image(service_img, image_options) await self.remove_container(name) await asyncio.sleep(0.1) # preparing to run available_ports = await self.available_ports() allocated_ports = list(available_ports.pop() for p in service_img.ports) self.hold_ports(allocated_ports) try: params = pdict.from_dict({ **dict(host_ports=allocated_ports), **self.container_params }) params.env.update(env) builder = BandContainerBuilder(service_img) config = builder.run_struct(name, **container_options, **params) # running service logger.info(f"starting container {name}.") dc = await self.dc.containers.run(config=config, name=name) c = BandContainer(dc) await c.ensure_filled() logger.info(f'started container {c.name} [{c.short_id}] {c.ports}') return c.short_info except Exception as exc: raise exc finally: self.free_ports(allocated_ports) async def close(self): await self.dc.close()
def __init__(self, docker, container): self.docker = docker self.channel = Channel() self.container = container self.running = False
def __init__(self, docker): self.running = False self.docker = docker self.channel = Channel()