Implement container attach

Signed-off-by: Jhon Honce <jhonce@redhat.com>
This commit is contained in:
Jhon Honce
2018-05-25 15:18:43 -07:00
parent a9e9fd4f5b
commit 2cb881fa58
5 changed files with 135 additions and 15 deletions

View File

@ -1,4 +1,5 @@
"""Support files for podman API implementation."""
import collections
import datetime
import functools
@ -12,12 +13,11 @@ __all__ = [
def cached_property(fn):
"""Cache return to save future expense."""
"""Decorate property to cache return value."""
return property(functools.lru_cache(maxsize=8)(fn))
# Cannot subclass collections.UserDict, breaks varlink
class Config(dict):
class Config(collections.UserDict):
"""Silently ignore None values, only take key once."""
def __init__(self, **kwargs):

View File

@ -0,0 +1,111 @@
"""Exported method Container.attach()."""
import fcntl
import os
import select
import signal
import socket
import struct
import sys
import termios
import tty
CONMON_BUFSZ = 8192
class Mixin:
"""Publish attach() for inclusion in Container class."""
def attach(self, eot=4, stdin=None, stdout=None):
"""Attach to container's PID1 stdin and stdout.
stderr is ignored.
"""
if stdin is None:
stdin = sys.stdin.fileno()
if stdout is None:
stdout = sys.stdout.fileno()
with self._client() as podman:
attach = podman.GetAttachSockets(self._id)
# This is the UDS where all the IO goes
io_socket = attach['sockets']['io_socket']
assert len(io_socket) <= 107,\
'Path length for sockets too long. {} > 107'.format(
len(io_socket)
)
# This is the control socket where resizing events are sent to conmon
ctl_socket = attach['sockets']['control_socket']
def resize_handler(signum, frame):
"""Send the new window size to conmon.
The method arguments are not used.
"""
packed = fcntl.ioctl(stdout, termios.TIOCGWINSZ,
struct.pack('HHHH', 0, 0, 0, 0))
rows, cols, _, _ = struct.unpack('HHHH', packed)
with open(ctl_socket, 'w') as skt:
# send conmon window resize message
skt.write('1 {} {}\n'.format(rows, cols))
def log_handler(signum, frame):
"""Send command to reopen log to conmon.
The method arguments are not used.
"""
with open(ctl_socket, 'w') as skt:
# send conmon reopen log message
skt.write('2\n')
try:
# save off the old settings for terminal
original_attr = termios.tcgetattr(stdout)
tty.setraw(stdin)
# initialize containers window size
resize_handler(None, sys._getframe(0))
# catch any resizing events and send the resize info
# to the control fifo "socket"
signal.signal(signal.SIGWINCH, resize_handler)
except termios.error:
original_attr = None
try:
# Prepare socket for communicating with conmon/container
with socket.socket(socket.AF_UNIX, socket.SOCK_SEQPACKET) as skt:
skt.connect(io_socket)
skt.sendall(b'\n')
sources = [skt, stdin]
while sources:
readable, _, _ = select.select(sources, [], [])
for r in readable:
if r is skt:
data = r.recv(CONMON_BUFSZ)
if not data:
sources.remove(skt)
# Remove source marker when writing
os.write(stdout, data[1:])
elif r is stdin:
data = os.read(stdin, CONMON_BUFSZ)
if not data:
sources.remove(stdin)
skt.sendall(data)
if eot in data:
sources.clear()
break
else:
raise ValueError('Unknown source in select')
finally:
if original_attr:
termios.tcsetattr(stdout, termios.TCSADRAIN, original_attr)
signal.signal(signal.SIGWINCH, signal.SIG_DFL)

View File

@ -6,8 +6,10 @@ import json
import signal
import time
from ._containers_attach import Mixin as AttachMixin
class Container(collections.UserDict):
class Container(collections.UserDict, AttachMixin):
"""Model for a container."""
def __init__(self, client, id, data):
@ -46,12 +48,6 @@ class Container(collections.UserDict):
with self._client() as podman:
return self._refresh(podman)
def attach(self, detach_key=None, no_stdin=False, sig_proxy=True):
"""Attach to running container."""
with self._client() as podman:
# TODO: streaming and port magic occur, need arguments
podman.AttachToContainer()
def processes(self):
"""Show processes running in container."""
with self._client() as podman:

View File

@ -62,7 +62,8 @@ class PodmanTestCase(unittest.TestCase):
cmd = ['podman']
cmd.extend(podman_args)
cmd.extend(['run', '-d', 'alpine', 'sleep', '500'])
# cmd.extend(['run', '-d', 'alpine', 'sleep', '500'])
cmd.extend(['run', '-dt', 'alpine', '/bin/sh'])
PodmanTestCase.alpine_process = subprocess.Popen(
cmd,
stdout=PodmanTestCase.alpine_log,

View File

@ -1,11 +1,9 @@
import os
import signal
import time
import unittest
from test.podman_testcase import PodmanTestCase
import podman
from podman import datetime_parse
class TestContainers(PodmanTestCase):
@ -65,8 +63,22 @@ class TestContainers(PodmanTestCase):
self.pclient.containers.get("bozo")
def test_attach(self):
with self.assertRaisesNotImplemented():
self.alpine_ctnr.attach()
# StringIO does not support fileno() so we had to go old school
input = os.path.join(self.tmpdir, 'test_attach.stdin')
output = os.path.join(self.tmpdir, 'test_attach.stdout')
with open(input, 'w+') as mock_in, open(output, 'w+') as mock_out:
# double quote is indeed in the expected place
mock_in.write('echo H"ello, World"; exit\n')
mock_in.seek(0, 0)
self.alpine_ctnr.attach(
stdin=mock_in.fileno(), stdout=mock_out.fileno())
mock_out.flush()
mock_out.seek(0, 0)
output = mock_out.read()
self.assertIn('Hello', output)
def test_processes(self):
actual = list(self.alpine_ctnr.processes())