Sheepdog:optimization of connection error handling

This patch optimized the processing method of sheepdog's bug. It
deal with the connection error in __run_dog and __run_qemu_img
functions.

Change-Id: Ia42ee91a720a4347c00146059d48d9b031e7f1a2
Closes-Bug: #1561874
This commit is contained in:
zhangsong 2016-03-25 15:39:01 +08:00
parent b78b032654
commit b4af5221b4
2 changed files with 56 additions and 83 deletions

View File

@ -444,6 +444,22 @@ class SheepdogClientTestCase(test.TestCase):
self.client._run_dog, *args) self.client._run_dog, *args)
self.assertEqual(expected_reason, ex.kwargs['reason']) self.assertEqual(expected_reason, ex.kwargs['reason'])
@mock.patch.object(utils, 'execute')
@mock.patch.object(sheepdog, 'LOG')
def test_run_dog_fail_to_connect_bugcase(self, fake_logger, fake_execute):
# NOTE(zhangsong): Sheepdog's bug case.
# details are written to Sheepdog driver code.
args = ('node', 'list')
stdout = ''
stderr = self.test_data.DOG_COMMAND_ERROR_FAIL_TO_CONNECT
expected_reason = (_('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': SHEEP_ADDR, 'port': SHEEP_PORT})
fake_execute.return_value = (stdout, stderr)
ex = self.assertRaises(exception.SheepdogError,
self.client._run_dog, *args)
self.assertEqual(expected_reason, ex.kwargs['reason'])
@mock.patch.object(utils, 'execute') @mock.patch.object(utils, 'execute')
@mock.patch.object(sheepdog, 'LOG') @mock.patch.object(sheepdog, 'LOG')
def test_run_dog_unknown_error(self, fake_logger, fake_execute): def test_run_dog_unknown_error(self, fake_logger, fake_execute):
@ -498,7 +514,25 @@ class SheepdogClientTestCase(test.TestCase):
@mock.patch.object(utils, 'execute') @mock.patch.object(utils, 'execute')
@mock.patch.object(sheepdog, 'LOG') @mock.patch.object(sheepdog, 'LOG')
def test_run_qemu_img_execution_error(self, fake_logger, fake_execute): def test_run_qemu_img_fail_to_connect(self, fake_logger, fake_execute):
args = ('create', 'dummy')
cmd = ('qemu-img', 'create', 'dummy')
exit_code = 1
stdout = 'stdout dummy'
stderr = self.test_data.QEMU_IMG_FAILED_TO_CONNECT
expected_reason = (_('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': SHEEP_ADDR, 'port': SHEEP_PORT})
fake_execute.side_effect = processutils.ProcessExecutionError(
cmd=cmd, exit_code=exit_code, stdout=stdout, stderr=stderr)
ex = self.assertRaises(exception.SheepdogError,
self.client._run_qemu_img, *args)
self.assertEqual(expected_reason, ex.kwargs['reason'])
@mock.patch.object(utils, 'execute')
@mock.patch.object(sheepdog, 'LOG')
def test_run_qemu_img_unknown_execution_error(self, fake_logger,
fake_execute):
args = ('create', 'dummy') args = ('create', 'dummy')
cmd = ('qemu-img', 'create', 'dummy') cmd = ('qemu-img', 'create', 'dummy')
exit_code = 1 exit_code = 1
@ -653,20 +687,6 @@ class SheepdogClientTestCase(test.TestCase):
self.client.delete(self._vdiname) self.client.delete(self._vdiname)
self.assertTrue(fake_logger.warning.called) self.assertTrue(fake_logger.warning.called)
@mock.patch.object(sheepdog.SheepdogClient, '_run_dog')
def test_delete_fail_to_connect_bugcase(self, fake_execute):
# NOTE(tishizaki): Sheepdog's bug case.
# details are written to Sheepdog driver code.
stdout = ''
stderr = self.test_data.DOG_COMMAND_ERROR_FAIL_TO_CONNECT
expected_reason = (_('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': SHEEP_ADDR, 'port': SHEEP_PORT})
fake_execute.return_value = (stdout, stderr)
ex = self.assertRaises(exception.SheepdogError,
self.client.delete, self._vdiname)
self.assertEqual(expected_reason, ex.kwargs['reason'])
@mock.patch.object(sheepdog.SheepdogClient, '_run_dog') @mock.patch.object(sheepdog.SheepdogClient, '_run_dog')
@mock.patch.object(sheepdog, 'LOG') @mock.patch.object(sheepdog, 'LOG')
def test_delete_unknown_error(self, fake_logger, fake_execute): def test_delete_unknown_error(self, fake_logger, fake_execute):
@ -786,23 +806,6 @@ class SheepdogClientTestCase(test.TestCase):
self.client.delete_snapshot(*args) self.client.delete_snapshot(*args)
self.assertTrue(fake_logger.warning.called) self.assertTrue(fake_logger.warning.called)
@mock.patch.object(sheepdog.SheepdogClient, '_run_dog')
@mock.patch.object(sheepdog, 'LOG')
def test_delete_snapshot_fail_to_connect_bugcase(self, fake_logger,
fake_execute):
# NOTE(tishizaki): Sheepdog's bug case.
# details are written to Sheepdog driver code.
args = (self._src_vdiname, self._snapname)
stdout = ''
stderr = self.test_data.DOG_COMMAND_ERROR_FAIL_TO_CONNECT
expected_reason = (_('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': SHEEP_ADDR, 'port': SHEEP_PORT})
fake_execute.return_value = (stdout, stderr)
ex = self.assertRaises(exception.SheepdogError,
self.client.delete_snapshot, *args)
self.assertEqual(expected_reason, ex.kwargs['reason'])
@mock.patch.object(sheepdog.SheepdogClient, '_run_dog') @mock.patch.object(sheepdog.SheepdogClient, '_run_dog')
@mock.patch.object(sheepdog, 'LOG') @mock.patch.object(sheepdog, 'LOG')
def test_delete_snapshot_unknown_error(self, fake_logger, fake_execute): def test_delete_snapshot_unknown_error(self, fake_logger, fake_execute):
@ -836,27 +839,6 @@ class SheepdogClientTestCase(test.TestCase):
self.client.clone(*args) self.client.clone(*args)
fake_execute.assert_called_once_with(*expected_cmd) fake_execute.assert_called_once_with(*expected_cmd)
@mock.patch.object(sheepdog.SheepdogClient, '_run_qemu_img')
@mock.patch.object(sheepdog, 'LOG')
def test_clone_fail_to_connect(self, fake_logger, fake_execute):
args = (self._src_vdiname, self._snapname,
self._dst_vdiname, self._dst_vdisize)
cmd = self.test_data.cmd_qemuimg_vdi_clone(*args)
exit_code = 2
stdout = 'stdout_dummy'
stderr = self.test_data.QEMU_IMG_FAILED_TO_CONNECT
expected_msg = self.test_data.sheepdog_cmd_error(cmd=cmd,
exit_code=exit_code,
stdout=stdout,
stderr=stderr)
fake_execute.side_effect = exception.SheepdogCmdError(
cmd=cmd, exit_code=exit_code, stdout=stdout.replace('\n', '\\n'),
stderr=stderr.replace('\n', '\\n'))
ex = self.assertRaises(exception.SheepdogCmdError, self.client.clone,
*args)
self.assertTrue(fake_logger.error.called)
self.assertEqual(expected_msg, ex.msg)
@mock.patch.object(sheepdog.SheepdogClient, '_run_qemu_img') @mock.patch.object(sheepdog.SheepdogClient, '_run_qemu_img')
@mock.patch.object(sheepdog, 'LOG') @mock.patch.object(sheepdog, 'LOG')
def test_clone_dst_vdi_already_exists(self, fake_logger, fake_execute): def test_clone_dst_vdi_already_exists(self, fake_logger, fake_execute):

View File

@ -84,7 +84,20 @@ class SheepdogClient(object):
cmd = ('env', 'LC_ALL=C', 'LANG=C', 'dog', command, subcommand, cmd = ('env', 'LC_ALL=C', 'LANG=C', 'dog', command, subcommand,
'-a', self.addr, '-p', self.port) + params '-a', self.addr, '-p', self.port) + params
try: try:
return utils.execute(*cmd) (_stdout, _stderr) = utils.execute(*cmd)
if _stderr.startswith(self.DOG_RESP_CONNECTION_ERROR):
# NOTE(tishizaki)
# Dog command does not return error_code although
# dog command cannot connect to sheep process.
# That is a Sheepdog's bug.
# To avoid a Sheepdog's bug, now we need to check stderr.
# If Sheepdog has been fixed, this check logic is needed
# by old Sheepdog users.
reason = (_('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': self.addr, 'port': self.port})
raise exception.SheepdogError(reason=reason)
return (_stdout, _stderr)
except OSError as e: except OSError as e:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
if e.errno == errno.ENOENT: if e.errno == errno.ENOENT:
@ -130,6 +143,12 @@ class SheepdogClient(object):
msg = _LE('OSError: command is %(cmd)s.') msg = _LE('OSError: command is %(cmd)s.')
LOG.error(msg, {'cmd': tuple(cmd)}) LOG.error(msg, {'cmd': tuple(cmd)})
except processutils.ProcessExecutionError as e: except processutils.ProcessExecutionError as e:
_stderr = e.stderr
if self.QEMU_IMG_RESP_CONNECTION_ERROR in _stderr:
reason = (_('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': self.addr, 'port': self.port})
raise exception.SheepdogError(reason=reason)
raise exception.SheepdogCmdError( raise exception.SheepdogCmdError(
cmd=e.cmd, cmd=e.cmd,
exit_code=e.exit_code, exit_code=e.exit_code,
@ -175,18 +194,6 @@ class SheepdogClient(object):
(_stdout, _stderr) = self._run_dog('vdi', 'delete', vdiname) (_stdout, _stderr) = self._run_dog('vdi', 'delete', vdiname)
if _stderr.rstrip().endswith(self.DOG_RESP_VDI_NOT_FOUND): if _stderr.rstrip().endswith(self.DOG_RESP_VDI_NOT_FOUND):
LOG.warning(_LW('Volume not found. %s'), vdiname) LOG.warning(_LW('Volume not found. %s'), vdiname)
elif _stderr.startswith(self.DOG_RESP_CONNECTION_ERROR):
# NOTE(tishizaki)
# Dog command does not return error_code although
# dog command cannot connect to sheep process.
# That is a Sheepdog's bug.
# To avoid a Sheepdog's bug, now we need to check stderr.
# If Sheepdog has been fixed, this check logic is needed
# by old Sheepdog users.
reason = (_('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': self.addr, 'port': self.port})
raise exception.SheepdogError(reason=reason)
except exception.SheepdogCmdError as e: except exception.SheepdogCmdError as e:
_stderr = e.kwargs['stderr'] _stderr = e.kwargs['stderr']
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
@ -220,18 +227,6 @@ class SheepdogClient(object):
LOG.warning(_LW('Snapshot "%s" not found.'), snapname) LOG.warning(_LW('Snapshot "%s" not found.'), snapname)
elif _stderr.rstrip().endswith(self.DOG_RESP_VDI_NOT_FOUND): elif _stderr.rstrip().endswith(self.DOG_RESP_VDI_NOT_FOUND):
LOG.warning(_LW('Volume "%s" not found.'), vdiname) LOG.warning(_LW('Volume "%s" not found.'), vdiname)
elif _stderr.startswith(self.DOG_RESP_CONNECTION_ERROR):
# NOTE(tishizaki)
# Dog command does not return error_code although
# dog command cannot connect to sheep process.
# That is a Sheepdog's bug.
# To avoid a Sheepdog's bug, now we need to check stderr.
# If Sheepdog has been fixed, this check logic is needed
# by old Sheepdog users.
reason = (_('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': self.addr, 'port': self.port})
raise exception.SheepdogError(reason=reason)
except exception.SheepdogCmdError as e: except exception.SheepdogCmdError as e:
cmd = e.kwargs['cmd'] cmd = e.kwargs['cmd']
_stderr = e.kwargs['stderr'] _stderr = e.kwargs['stderr']
@ -250,11 +245,7 @@ class SheepdogClient(object):
cmd = e.kwargs['cmd'] cmd = e.kwargs['cmd']
_stderr = e.kwargs['stderr'] _stderr = e.kwargs['stderr']
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
if self.QEMU_IMG_RESP_CONNECTION_ERROR in _stderr: if self.QEMU_IMG_RESP_ALREADY_EXISTS in _stderr:
LOG.error(_LE('Failed to connect to sheep daemon. '
'addr: %(addr)s, port: %(port)s'),
{'addr': self.addr, 'port': self.port})
elif self.QEMU_IMG_RESP_ALREADY_EXISTS in _stderr:
LOG.error(_LE('Clone volume "%s" already exists. ' LOG.error(_LE('Clone volume "%s" already exists. '
'Please check the results of "dog vdi list".'), 'Please check the results of "dog vdi list".'),
dst_vdiname) dst_vdiname)