Add EOF error hints for LXC/LXD; closes #373.

issue260
David Wilson 7 years ago
parent d5a8293c91
commit 71f9e84ab3

@ -48,6 +48,12 @@ class Stream(mitogen.parent.Stream):
container = None container = None
lxc_attach_path = 'lxc-attach' lxc_attach_path = 'lxc-attach'
eof_error_hint = (
'Note: many versions of LXC do not report program execution failure '
'meaningfully. Please check the host logs (/var/log) for more '
'information.'
)
def construct(self, container, lxc_attach_path=None, **kwargs): def construct(self, container, lxc_attach_path=None, **kwargs):
super(Stream, self).construct(**kwargs) super(Stream, self).construct(**kwargs)
self.container = container self.container = container

@ -49,6 +49,12 @@ class Stream(mitogen.parent.Stream):
lxc_path = 'lxc' lxc_path = 'lxc'
python_path = 'python' python_path = 'python'
eof_error_hint = (
'Note: many versions of LXC do not report program execution failure '
'meaningfully. Please check the host logs (/var/log) for more '
'information.'
)
def construct(self, container, lxc_path=None, **kwargs): def construct(self, container, lxc_path=None, **kwargs):
super(Stream, self).construct(**kwargs) super(Stream, self).construct(**kwargs)
self.container = container self.container = container

@ -407,6 +407,8 @@ def write_all(fd, s, deadline=None):
:raises mitogen.core.TimeoutError: :raises mitogen.core.TimeoutError:
Bytestring could not be written entirely before deadline was exceeded. Bytestring could not be written entirely before deadline was exceeded.
:raises mitogen.parent.EofError:
Stream indicated EOF, suggesting the child process has exitted.
:raises mitogen.core.StreamError: :raises mitogen.core.StreamError:
File descriptor was disconnected before write could complete. File descriptor was disconnected before write could complete.
""" """
@ -430,7 +432,7 @@ def write_all(fd, s, deadline=None):
for fd in poller.poll(timeout): for fd in poller.poll(timeout):
n, disconnected = mitogen.core.io_op(os.write, fd, window) n, disconnected = mitogen.core.io_op(os.write, fd, window)
if disconnected: if disconnected:
raise mitogen.core.StreamError('EOF on stream during write') raise EofError('EOF on stream during write')
written += n written += n
finally: finally:
@ -449,6 +451,8 @@ def iter_read(fds, deadline=None):
:raises mitogen.core.TimeoutError: :raises mitogen.core.TimeoutError:
Attempt to read beyond deadline. Attempt to read beyond deadline.
:raises mitogen.parent.EofError:
All streams indicated EOF, suggesting the child process has exitted.
:raises mitogen.core.StreamError: :raises mitogen.core.StreamError:
Attempt to read past end of file. Attempt to read past end of file.
""" """
@ -478,10 +482,9 @@ def iter_read(fds, deadline=None):
poller.close() poller.close()
if not poller.readers: if not poller.readers:
raise mitogen.core.StreamError( raise EofError(u'EOF on stream; last 300 bytes received: %r' %
u'EOF on stream; last 300 bytes received: %r' % (b('').join(bits)[-300:].decode('latin1'),))
(b('').join(bits)[-300:].decode('latin1'),)
)
raise mitogen.core.TimeoutError('read timed out') raise mitogen.core.TimeoutError('read timed out')
@ -501,6 +504,8 @@ def discard_until(fd, s, deadline):
:raises mitogen.core.TimeoutError: :raises mitogen.core.TimeoutError:
Attempt to read beyond deadline. Attempt to read beyond deadline.
:raises mitogen.parent.EofError:
All streams indicated EOF, suggesting the child process has exitted.
:raises mitogen.core.StreamError: :raises mitogen.core.StreamError:
Attempt to read past end of file. Attempt to read past end of file.
""" """
@ -607,6 +612,14 @@ def wstatus_to_str(status):
return 'unknown wait status (%d)' % (status,) return 'unknown wait status (%d)' % (status,)
class EofError(mitogen.core.StreamError):
"""
Raised by :func:`iter_read` and :func:`write_all` when EOF is detected by
the child process.
"""
# inherits from StreamError to maintain compatibility.
class Argv(object): class Argv(object):
""" """
Wrapper to defer argv formatting when debug logging is disabled. Wrapper to defer argv formatting when debug logging is disabled.
@ -1105,6 +1118,16 @@ class Stream(mitogen.core.Stream):
msg = 'Child start failed: %s. Command was: %s' % (e, Argv(args)) msg = 'Child start failed: %s. Command was: %s' % (e, Argv(args))
raise mitogen.core.StreamError(msg) raise mitogen.core.StreamError(msg)
eof_error_hint = None
def _adorn_eof_error(self, e):
"""
Used by subclasses to provide additional information in the case of a
failed connection.
"""
if self.eof_error_hint:
e.args = ('%s\n\n%s' % (e.args[0], self.eof_error_hint),)
def connect(self): def connect(self):
LOG.debug('%r.connect()', self) LOG.debug('%r.connect()', self)
self.pid, fd, extra_fd = self.start_child() self.pid, fd, extra_fd = self.start_child()
@ -1116,6 +1139,10 @@ class Stream(mitogen.core.Stream):
try: try:
self._connect_bootstrap(extra_fd) self._connect_bootstrap(extra_fd)
except EofError:
e = sys.exc_info()[1]
self._adorn_eof_error(e)
raise
except Exception: except Exception:
self._reap_child() self._reap_child()
raise raise

@ -1,6 +1,7 @@
import os import os
import mitogen import mitogen
import mitogen.lxc
import unittest2 import unittest2
@ -11,19 +12,29 @@ def has_subseq(seq, subseq):
return any(seq[x:x+len(subseq)] == subseq for x in range(0, len(seq))) return any(seq[x:x+len(subseq)] == subseq for x in range(0, len(seq)))
class ConstructorTest(testlib.RouterMixin, unittest2.TestCase): class ConstructorTest(testlib.RouterMixin, testlib.TestCase):
lxc_attach_path = testlib.data_path('stubs/lxc-attach.py')
def test_okay(self): def test_okay(self):
lxc_attach_path = testlib.data_path('stubs/lxc-attach.py')
context = self.router.lxc( context = self.router.lxc(
container='container_name', container='container_name',
lxc_attach_path=lxc_attach_path, lxc_attach_path=self.lxc_attach_path,
) )
argv = eval(context.call(os.getenv, 'ORIGINAL_ARGV')) argv = eval(context.call(os.getenv, 'ORIGINAL_ARGV'))
self.assertEquals(argv[0], lxc_attach_path) self.assertEquals(argv[0], self.lxc_attach_path)
self.assertTrue('--clear-env' in argv) self.assertTrue('--clear-env' in argv)
self.assertTrue(has_subseq(argv, ['--name', 'container_name'])) self.assertTrue(has_subseq(argv, ['--name', 'container_name']))
def test_eof(self):
e = self.assertRaises(mitogen.parent.EofError,
lambda: self.router.lxc(
container='container_name',
lxc_attach_path='true',
)
)
self.assertTrue(str(e).endswith(mitogen.lxc.Stream.eof_error_hint))
if __name__ == '__main__': if __name__ == '__main__':
unittest2.main() unittest2.main()

@ -1,13 +1,15 @@
import os import os
import mitogen import mitogen
import mitogen.lxd
import mitogen.parent
import unittest2 import unittest2
import testlib import testlib
class ConstructorTest(testlib.RouterMixin, unittest2.TestCase): class ConstructorTest(testlib.RouterMixin, testlib.TestCase):
def test_okay(self): def test_okay(self):
lxc_path = testlib.data_path('stubs/lxc.py') lxc_path = testlib.data_path('stubs/lxc.py')
context = self.router.lxd( context = self.router.lxd(
@ -21,6 +23,15 @@ class ConstructorTest(testlib.RouterMixin, unittest2.TestCase):
self.assertEquals(argv[2], '--mode=noninteractive') self.assertEquals(argv[2], '--mode=noninteractive')
self.assertEquals(argv[3], 'container_name') self.assertEquals(argv[3], 'container_name')
def test_eof(self):
e = self.assertRaises(mitogen.parent.EofError,
lambda: self.router.lxd(
container='container_name',
lxc_path='true',
)
)
self.assertTrue(str(e).endswith(mitogen.lxd.Stream.eof_error_hint))
if __name__ == '__main__': if __name__ == '__main__':
unittest2.main() unittest2.main()

Loading…
Cancel
Save