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

issue260
David Wilson 6 years ago
parent d5a8293c91
commit 71f9e84ab3

@ -48,6 +48,12 @@ class Stream(mitogen.parent.Stream):
container = None
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):
super(Stream, self).construct(**kwargs)
self.container = container

@ -49,6 +49,12 @@ class Stream(mitogen.parent.Stream):
lxc_path = 'lxc'
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):
super(Stream, self).construct(**kwargs)
self.container = container

@ -407,6 +407,8 @@ def write_all(fd, s, deadline=None):
:raises mitogen.core.TimeoutError:
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:
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):
n, disconnected = mitogen.core.io_op(os.write, fd, window)
if disconnected:
raise mitogen.core.StreamError('EOF on stream during write')
raise EofError('EOF on stream during write')
written += n
finally:
@ -449,6 +451,8 @@ def iter_read(fds, deadline=None):
:raises mitogen.core.TimeoutError:
Attempt to read beyond deadline.
:raises mitogen.parent.EofError:
All streams indicated EOF, suggesting the child process has exitted.
:raises mitogen.core.StreamError:
Attempt to read past end of file.
"""
@ -478,10 +482,9 @@ def iter_read(fds, deadline=None):
poller.close()
if not poller.readers:
raise mitogen.core.StreamError(
u'EOF on stream; last 300 bytes received: %r' %
(b('').join(bits)[-300:].decode('latin1'),)
)
raise EofError(u'EOF on stream; last 300 bytes received: %r' %
(b('').join(bits)[-300:].decode('latin1'),))
raise mitogen.core.TimeoutError('read timed out')
@ -501,6 +504,8 @@ def discard_until(fd, s, deadline):
:raises mitogen.core.TimeoutError:
Attempt to read beyond deadline.
:raises mitogen.parent.EofError:
All streams indicated EOF, suggesting the child process has exitted.
:raises mitogen.core.StreamError:
Attempt to read past end of file.
"""
@ -607,6 +612,14 @@ def wstatus_to_str(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):
"""
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))
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):
LOG.debug('%r.connect()', self)
self.pid, fd, extra_fd = self.start_child()
@ -1116,6 +1139,10 @@ class Stream(mitogen.core.Stream):
try:
self._connect_bootstrap(extra_fd)
except EofError:
e = sys.exc_info()[1]
self._adorn_eof_error(e)
raise
except Exception:
self._reap_child()
raise

@ -1,6 +1,7 @@
import os
import mitogen
import mitogen.lxc
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)))
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):
lxc_attach_path = testlib.data_path('stubs/lxc-attach.py')
context = self.router.lxc(
container='container_name',
lxc_attach_path=lxc_attach_path,
lxc_attach_path=self.lxc_attach_path,
)
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(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__':
unittest2.main()

@ -1,13 +1,15 @@
import os
import mitogen
import mitogen.lxd
import mitogen.parent
import unittest2
import testlib
class ConstructorTest(testlib.RouterMixin, unittest2.TestCase):
class ConstructorTest(testlib.RouterMixin, testlib.TestCase):
def test_okay(self):
lxc_path = testlib.data_path('stubs/lxc.py')
context = self.router.lxd(
@ -21,6 +23,15 @@ class ConstructorTest(testlib.RouterMixin, unittest2.TestCase):
self.assertEquals(argv[2], '--mode=noninteractive')
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__':
unittest2.main()

Loading…
Cancel
Save