From 34a28c302c6fbde5cf5307e16bb0f746d3ee03c3 Mon Sep 17 00:00:00 2001 From: Teruaki Ishizaki Date: Mon, 6 Jul 2015 21:34:37 +0900 Subject: [PATCH] Sheepdog: Improve snapshot and clone operation This patch changes SheepdogDriver Class methods to use SheepdogClient Class methods for detailed Error logs. We change the following SheepdogDriver methods. - create_snapshot - delete_snapshot - create_volume_from_snapshot - create_cloned_volume - extend_volume SheepdogClient Class methods are implemented to enable fine grained Error handling. So, the administrators are enable to do error recovery more easily using those logs. Change-Id: Ib126088e4100739db013b21970051af1f29790d5 --- cinder/tests/unit/test_sheepdog.py | 815 ++++++++++++++++++++++++----- cinder/volume/drivers/sheepdog.py | 239 +++++++-- 2 files changed, 880 insertions(+), 174 deletions(-) diff --git a/cinder/tests/unit/test_sheepdog.py b/cinder/tests/unit/test_sheepdog.py index 74f0c474c..dbbd53bb4 100644 --- a/cinder/tests/unit/test_sheepdog.py +++ b/cinder/tests/unit/test_sheepdog.py @@ -23,7 +23,6 @@ import mock from oslo_concurrency import processutils from oslo_utils import importutils from oslo_utils import units -import six from cinder.backup import driver as backup_driver from cinder import context @@ -32,6 +31,8 @@ from cinder import exception from cinder.i18n import _ from cinder.image import image_utils from cinder import test +from cinder.tests.unit import fake_backup +from cinder.tests.unit import fake_snapshot from cinder.tests.unit import fake_volume from cinder import utils from cinder.volume import configuration as conf @@ -44,6 +45,12 @@ SHEEP_PORT = 7000 class SheepdogDriverTestDataGenerator(object): def __init__(self): self.TEST_VOLUME = self._make_fake_volume(self.TEST_VOL_DATA) + self.TEST_CLONED_VOLUME = self._make_fake_volume( + self.TEST_CLONED_VOL_DATA) + self.TEST_SNAPSHOT = self._make_fake_snapshot( + self.TEST_SNAPSHOT_DATA, self.TEST_VOLUME) + self.TEST_BACKUP_VOLUME = self._make_fake_backup_volume( + self.TEST_BACKUP_VOL_DATA) def sheepdog_cmd_error(self, cmd, exit_code, stdout, stderr): return (('(Command: %(cmd)s) ' @@ -59,16 +66,48 @@ class SheepdogDriverTestDataGenerator(object): return fake_volume.fake_volume_obj(context.get_admin_context(), **volume_data) + def _make_fake_snapshot(self, snapshot_data, src_volume): + snapshot_obj = fake_snapshot.fake_snapshot_obj( + context.get_admin_context(), **snapshot_data) + snapshot_obj.volume = src_volume + return snapshot_obj + + def _make_fake_backup_volume(self, backup_data): + return fake_backup.fake_backup_obj(context.get_admin_context(), + **backup_data) + def cmd_dog_vdi_create(self, name, size): return ('env', 'LC_ALL=C', 'LANG=C', 'dog', 'vdi', 'create', name, - '%sG' % size, '-a', SHEEP_ADDR, '-p', str(SHEEP_PORT)) + '%sG' % size, '-a', SHEEP_ADDR, '-p', SHEEP_PORT) def cmd_dog_vdi_delete(self, name): return ('env', 'LC_ALL=C', 'LANG=C', 'dog', 'vdi', 'delete', name, - '-a', SHEEP_ADDR, '-p', str(SHEEP_PORT)) + '-a', SHEEP_ADDR, '-p', SHEEP_PORT) + + def cmd_dog_vdi_create_snapshot(self, vdiname, snapname): + return ('env', 'LC_ALL=C', 'LANG=C', 'dog', 'vdi', 'snapshot', '-s', + snapname, '-a', SHEEP_ADDR, '-p', SHEEP_PORT, vdiname) + + def cmd_dog_vdi_delete_snapshot(self, vdiname, snapname): + return ('env', 'LC_ALL=C', 'LANG=C', 'dog', 'vdi', 'delete', '-s', + snapname, '-a', SHEEP_ADDR, '-p', SHEEP_PORT, vdiname) + + def cmd_qemuimg_vdi_clone(self, src_vdiname, src_snapname, dst_vdiname, + size): + return ('env', 'LC_ALL=C', 'LANG=C', 'qemu-img', 'create', '-b', + 'sheepdog:%(addr)s:%(port)s:%(src_vdiname)s:%(src_snapname)s' % + {'addr': SHEEP_ADDR, 'port': SHEEP_PORT, + 'src_vdiname': src_vdiname, 'src_snapname': src_snapname}, + 'sheepdog:%(addr)s:%(port)s:%(dst_vdiname)s' % + {'addr': SHEEP_ADDR, 'port': SHEEP_PORT, + 'dst_vdiname': dst_vdiname}, '%sG' % size) + + def cmd_dog_vdi_resize(self, name, size): + return ('env', 'LC_ALL=C', 'LANG=C', 'dog', 'vdi', 'resize', name, + size, '-a', SHEEP_ADDR, '-p', SHEEP_PORT) CMD_DOG_CLUSTER_INFO = ('env', 'LC_ALL=C', 'LANG=C', 'dog', 'cluster', - 'info', '-a', SHEEP_ADDR, '-p', str(SHEEP_PORT)) + 'info', '-a', SHEEP_ADDR, '-p', SHEEP_PORT) TEST_VOL_DATA = { 'size': 1, @@ -83,6 +122,27 @@ class SheepdogDriverTestDataGenerator(object): 'consistencygroup_id': None, } + TEST_CLONED_VOL_DATA = { + 'size': 2, + 'id': '00000000-0000-0000-0000-000000000003', + 'provider_auth': None, + 'host': 'host@backendsec#unit_test_pool', + 'project_id': 'project', + 'provider_location': 'location', + 'display_name': 'vol3', + 'display_description': 'unit test cloned volume', + 'volume_type_id': None, + 'consistencygroup_id': None, + } + + TEST_SNAPSHOT_DATA = { + 'id': '00000000-0000-0000-0000-000000000002', + } + + TEST_BACKUP_VOL_DATA = { + 'volume_id': '00000000-0000-0000-0000-000000000001', + } + COLLIE_NODE_INFO = """ 0 107287605248 3623897354 3% Total 107287605248 3623897354 3% 54760833024 @@ -137,7 +197,38 @@ Cluster status: System is shutting down Failed to create VDI %(vdiname)s: VDI exists already """ - DOG_VDI_DELETE_VDI_NOT_EXISTS = """\ + DOG_VDI_SNAPSHOT_VDI_NOT_FOUND = """\ +Failed to create snapshot for volume-00000000-0000-0000-0000-000000000001: \ +No VDI found +""" + + DOG_VDI_SNAPSHOT_ALREADY_EXISTED = """\ +Failed to create snapshot for volume-00000000-0000-0000-0000-000000000001, \ +maybe snapshot id (0) or tag (snapshot-00000000-0000-0000-0000-000000000002) \ +is existed +""" + + DOG_VDI_SNAPSHOT_TAG_NOT_FOUND = """\ +Failed to open VDI volume-00000000-0000-0000-0000-000000000001 \ +(snapshot id: 0 snapshot tag: snapshot-00000000-0000-0000-0000-000000000002): \ +Failed to find requested tag +""" + + DOG_VDI_SNAPSHOT_VOLUME_NOT_FOUND = """\ +Failed to open VDI volume-00000000-0000-0000-0000-000000000001 \ +(snapshot id: 0 snapshot tag: snapshot-00000000-0000-0000-0000-000000000002): \ +No VDI found +""" + + DOG_VDI_RESIZE_SIZE_SHRINK = """\ +Shrinking VDIs is not implemented +""" + + DOG_VDI_RESIZE_TOO_LARGE = """\ +New VDI size is too large. This volume's max size is 4398046511104 +""" + + DOG_COMMAND_ERROR_VDI_NOT_EXISTS = """\ Failed to open VDI %(vdiname)s (snapshot id: 0 snapshot tag: ): No VDI found """ @@ -147,6 +238,35 @@ failed to connect to 127.0.0.1:7000: Connection refused Failed to get node list """ + QEMU_IMG_VDI_ALREADY_EXISTS = """\ +qemu-img: sheepdog:volume-00000000-0000-0000-0000-000000000001: \ +VDI exists already, +""" + + QEMU_IMG_VDI_NOT_FOUND = """\ +qemu-img: sheepdog:volume-00000000-0000-0000-0000-000000000003: \ +cannot get vdi info, No vdi found, \ +volume-00000000-0000-0000-0000-000000000001 \ +snapshot-00000000-0000-0000-0000-000000000002 +""" + + QEMU_IMG_SNAPSHOT_NOT_FOUND = """\ +qemu-img: sheepdog:volume-00000000-0000-0000-0000-000000000003: \ +cannot get vdi info, Failed to find the requested tag, \ +volume-00000000-0000-0000-0000-000000000001 \ +snapshot-00000000-0000-0000-0000-000000000002 +""" + + QEMU_IMG_SIZE_TOO_LARGE = """\ +qemu-img: sheepdog:volume-00000000-0000-0000-0000-000000000001: \ +An image is too large. The maximum image size is 4096GB +""" + + QEMU_IMG_FAILED_TO_CONNECT = """\ +qemu-img: sheepdog::volume-00000000-0000-0000-0000-000000000001: \ +Failed to connect socket: Connection refused +""" + class FakeImageService(object): def download(self, context, image_id, path): @@ -267,6 +387,10 @@ class SheepdogClientTestCase(test.TestCase): self.client = self.driver.client self._vdiname = self.test_data.TEST_VOLUME.name self._vdisize = self.test_data.TEST_VOLUME.size + self._src_vdiname = self.test_data.TEST_SNAPSHOT.volume_name + self._snapname = self.test_data.TEST_SNAPSHOT.name + self._dst_vdiname = self.test_data.TEST_CLONED_VOLUME.name + self._dst_vdisize = self.test_data.TEST_CLONED_VOLUME.size @mock.patch.object(utils, 'execute') def test_run_dog_success(self, fake_execute): @@ -301,6 +425,7 @@ class SheepdogClientTestCase(test.TestCase): def test_run_dog_unknown_error(self, fake_logger, fake_execute): args = ('cluster', 'info') cmd = self.test_data.CMD_DOG_CLUSTER_INFO + cmd = self.test_data.CMD_DOG_CLUSTER_INFO exit_code = 1 stdout = 'stdout dummy' stderr = 'stderr dummy' @@ -312,6 +437,57 @@ class SheepdogClientTestCase(test.TestCase): self.client._run_dog, *args) self.assertEqual(expected_msg, ex.msg) + @mock.patch.object(utils, 'execute') + def test_run_qemu_img_success(self, fake_execute): + # multiple part of args match the prefix and + # volume name is matched the prefix unfortunately + expected_cmd = ('env', 'LC_ALL=C', 'LANG=C', + 'qemu-img', 'create', '-b', + 'sheepdog:%(addr)s:%(port)s:sheepdog:snap' % + {'addr': SHEEP_ADDR, 'port': SHEEP_PORT}, + 'sheepdog:%(addr)s:%(port)s:clone' % + {'addr': SHEEP_ADDR, 'port': SHEEP_PORT}, '10G') + fake_execute.return_value = ('', '') + self.client._run_qemu_img('create', '-b', 'sheepdog:sheepdog:snap', + 'sheepdog:clone', '10G') + fake_execute.assert_called_once_with(*expected_cmd) + + @mock.patch.object(utils, 'execute') + @mock.patch.object(sheepdog, 'LOG') + def test_run_qemu_img_command_not_found(self, fake_logger, fake_execute): + args = ('create', 'dummy') + expected_msg = 'No such file or directory' + expected_errno = errno.ENOENT + fake_execute.side_effect = OSError(expected_errno, expected_msg) + self.assertRaises(OSError, self.client._run_qemu_img, *args) + self.assertTrue(fake_logger.error.called) + + @mock.patch.object(utils, 'execute') + @mock.patch.object(sheepdog, 'LOG') + def test_run_qemu_img_unknown_os_error(self, fake_logger, fake_execute): + args = ('create', 'dummy') + expected_msg = 'unknown' + expected_errno = errno.EPERM + fake_execute.side_effect = OSError(expected_errno, expected_msg) + self.assertRaises(OSError, self.client._run_qemu_img, *args) + self.assertTrue(fake_logger.error.called) + + @mock.patch.object(utils, 'execute') + @mock.patch.object(sheepdog, 'LOG') + def test_run_qemu_img_execution_error(self, fake_logger, fake_execute): + args = ('create', 'dummy') + cmd = ('qemu-img', 'create', 'dummy') + exit_code = 1 + stdout = 'stdout dummy' + stderr = 'stderr dummy' + expected_msg = self.test_data.sheepdog_cmd_error( + cmd=cmd, exit_code=exit_code, stdout=stdout, stderr=stderr) + fake_execute.side_effect = processutils.ProcessExecutionError( + cmd=cmd, exit_code=exit_code, stdout=stdout, stderr=stderr) + ex = self.assertRaises(exception.SheepdogCmdError, + self.client._run_qemu_img, *args) + self.assertEqual(expected_msg, ex.msg) + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') @mock.patch.object(sheepdog, 'LOG') def test_check_cluster_status_success(self, fake_logger, fake_execute): @@ -484,7 +660,7 @@ class SheepdogClientTestCase(test.TestCase): @mock.patch.object(sheepdog, 'LOG') def test_delete_vdi_not_found(self, fake_logger, fake_execute): stdout = '' - stderr = (self.test_data.DOG_VDI_DELETE_VDI_NOT_EXISTS % + stderr = (self.test_data.DOG_COMMAND_ERROR_VDI_NOT_EXISTS % {'vdiname': self._vdiname}) fake_execute.return_value = (stdout, stderr) self.client.delete(self._vdiname) @@ -542,6 +718,425 @@ class SheepdogClientTestCase(test.TestCase): self.assertTrue(fake_logger.error.called) self.assertEqual(expected_msg, ex.msg) + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + def test_create_snapshot_success(self, fake_execute): + args = (self._src_vdiname, self._snapname) + expected_cmd = ('vdi', 'snapshot', '-s', self._snapname, + self._src_vdiname) + fake_execute.return_value = ('', '') + self.client.create_snapshot(*args) + fake_execute.assert_called_once_with(*expected_cmd) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_create_snapshot_fail_to_connect(self, fake_logger, fake_execute): + args = (self._src_vdiname, self._snapname) + cmd = self.test_data.cmd_dog_vdi_create_snapshot(*args) + exit_code = 2 + stdout = '' + stderr = self.test_data.DOG_COMMAND_ERROR_FAIL_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.create_snapshot, *args) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_create_snapshot_vdi_not_found(self, fake_logger, fake_execute): + args = (self._src_vdiname, self._snapname) + cmd = self.test_data.cmd_dog_vdi_create_snapshot(*args) + exit_code = 1 + stdout = '' + stderr = self.test_data.DOG_VDI_SNAPSHOT_VDI_NOT_FOUND + 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.create_snapshot, *args) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_create_snapshot_snap_name_already_used(self, fake_logger, + fake_execute): + args = (self._src_vdiname, self._snapname) + cmd = self.test_data.cmd_dog_vdi_create_snapshot(*args) + exit_code = 1 + stdout = 'stdout_dummy' + stderr = self.test_data.DOG_VDI_SNAPSHOT_ALREADY_EXISTED + 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.create_snapshot, *args) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_create_snapshot_unknown_error(self, fake_logger, fake_execute): + args = (self._src_vdiname, self._snapname) + cmd = self.test_data.cmd_dog_vdi_create_snapshot(*args) + exit_code = 1 + stdout = 'stdout_dummy' + stderr = 'unknown_error' + 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.create_snapshot, *args) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_delete_snapshot_success(self, fake_logger, fake_execute): + args = (self._src_vdiname, self._snapname) + expected_cmd = ('vdi', 'delete', '-s', self._snapname, + self._src_vdiname) + fake_execute.return_value = ('', '') + self.client.delete_snapshot(*args) + fake_execute.assert_called_once_with(*expected_cmd) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_delete_snapshot_not_found(self, fake_logger, fake_execute): + args = (self._src_vdiname, self._snapname) + stdout = '' + stderr = self.test_data.DOG_VDI_SNAPSHOT_TAG_NOT_FOUND + fake_execute.return_value = (stdout, stderr) + self.client.delete_snapshot(*args) + self.assertTrue(fake_logger.warning.called) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_delete_snapshot_vdi_not_found(self, fake_logger, fake_execute): + args = (self._src_vdiname, self._snapname) + stdout = '' + stderr = self.test_data.DOG_VDI_SNAPSHOT_VOLUME_NOT_FOUND + fake_execute.return_value = (stdout, stderr) + self.client.delete_snapshot(*args) + 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, 'LOG') + def test_delete_snapshot_fail_to_connect(self, fake_logger, fake_execute): + args = (self._src_vdiname, self._snapname) + cmd = self.test_data.cmd_dog_vdi_delete_snapshot(*args) + exit_code = 2 + stdout = '' + stderr = self.test_data.DOG_COMMAND_ERROR_FAIL_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.delete_snapshot, *args) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_delete_snapshot_unknown_error(self, fake_logger, fake_execute): + args = (self._src_vdiname, self._snapname) + cmd = self.test_data.cmd_dog_vdi_delete_snapshot(*args) + exit_code = 2 + stdout = 'stdout_dummy' + stderr = 'unknown_error' + 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.delete_snapshot, *args) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_qemu_img') + def test_clone_success(self, fake_execute): + args = (self._src_vdiname, self._snapname, + self._dst_vdiname, self._dst_vdisize) + src_volume = 'sheepdog:%(src_vdiname)s:%(snapname)s' % { + 'src_vdiname': self._src_vdiname, 'snapname': self._snapname} + dst_volume = 'sheepdog:%s' % self._dst_vdiname + expected_cmd = ('create', '-b', src_volume, dst_volume, + '%sG' % self._dst_vdisize) + fake_execute.return_code = ("", "") + self.client.clone(*args) + 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, 'LOG') + def test_clone_dst_vdi_already_exists(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_VDI_ALREADY_EXISTS + 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, 'LOG') + def test_clone_src_vdi_not_found(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_VDI_NOT_FOUND + 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, 'LOG') + def test_clone_src_snapshot_not_found(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_SNAPSHOT_NOT_FOUND + 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, 'LOG') + def test_clone_too_large_volume_size(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_SIZE_TOO_LARGE + 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, 'LOG') + def test_clone_unknown_error(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 = 'stderr_dummy' + 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_dog') + def test_resize_success(self, fake_execute): + expected_cmd = ('vdi', 'resize', self._vdiname, 10 * 1024 ** 3) + fake_execute.return_value = ('', '') + self.client.resize(self._vdiname, 10) + fake_execute.assert_called_once_with(*expected_cmd) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_resize_fail_to_connect(self, fake_logger, fake_execute): + cmd = self.test_data.cmd_dog_vdi_resize(self._vdiname, 10 * 1024 ** 3) + exit_code = 2 + stdout = 'stdout_dummy' + stderr = self.test_data.DOG_COMMAND_ERROR_FAIL_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.resize, self._vdiname, 10) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_resize_vdi_not_found(self, fake_logger, fake_execute): + cmd = self.test_data.cmd_dog_vdi_resize(self._vdiname, 10 * 1024 ** 3) + exit_code = 1 + stdout = 'stdout_dummy' + stderr = (self.test_data.DOG_COMMAND_ERROR_VDI_NOT_EXISTS % + {'vdiname': self._vdiname}) + 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.resize, self._vdiname, 1) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_resize_shrinking_not_supported(self, fake_logger, fake_execute): + cmd = self.test_data.cmd_dog_vdi_resize(self._vdiname, 1 * 1024 ** 3) + exit_code = 1 + stdout = 'stdout_dummy' + stderr = self.test_data.DOG_VDI_RESIZE_SIZE_SHRINK + 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.resize, self._vdiname, 1) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_resize_too_large_size(self, fake_logger, fake_execute): + cmd = self.test_data.cmd_dog_vdi_resize(self._vdiname, 5 * 1024 ** 4) + exit_code = 64 + stdout = 'stdout_dummy' + stderr = self.test_data.DOG_VDI_RESIZE_TOO_LARGE + 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.resize, self._vdiname, 5120) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + + @mock.patch.object(sheepdog.SheepdogClient, '_run_dog') + @mock.patch.object(sheepdog, 'LOG') + def test_resize_unknown_error(self, fake_logger, fake_execute): + cmd = self.test_data.cmd_dog_vdi_resize(self._vdiname, 10 * 1024 ** 3) + exit_code = 2 + stdout = 'stdout_dummy' + stderr = 'stderr_dummy' + 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.resize, self._vdiname, 10) + self.assertTrue(fake_logger.error.called) + self.assertEqual(expected_msg, ex.msg) + class SheepdogDriverTestCase(test.TestCase): def setUp(self): @@ -558,6 +1153,10 @@ class SheepdogDriverTestCase(test.TestCase): self.client = self.driver.client self._vdiname = self.test_data.TEST_VOLUME.name self._vdisize = self.test_data.TEST_VOLUME.size + self._src_vdiname = self.test_data.TEST_SNAPSHOT.volume_name + self._snapname = self.test_data.TEST_SNAPSHOT.name + self._dst_vdiname = self.test_data.TEST_CLONED_VOLUME.name + self._dst_vdisize = self.test_data.TEST_CLONED_VOLUME.size @mock.patch.object(sheepdog.SheepdogClient, 'check_cluster_status') def test_check_for_setup_error(self, fake_execute): @@ -673,65 +1272,59 @@ class SheepdogDriverTestCase(test.TestCase): self.driver.copy_volume_to_image, *args) - def test_create_cloned_volume(self): - src_vol = { - 'project_id': 'testprjid', - 'name': six.text_type('volume-00000001'), - 'size': '20', - 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66', - } - target_vol = { - 'project_id': 'testprjid', - 'name': six.text_type('volume-00000002'), - 'size': '20', - 'id': '582a1efa-be6a-11e4-a73b-0aa186c60fe0', - } - - with mock.patch.object(self.driver, - '_try_execute') as mock_exe: - self.driver.create_cloned_volume(target_vol, src_vol) - - snapshot_name = src_vol['name'] + '-temp-snapshot' - qemu_src_volume_name = "sheepdog:%s" % src_vol['name'] - qemu_snapshot_name = '%s:%s' % (qemu_src_volume_name, - snapshot_name) - qemu_target_volume_name = "sheepdog:%s" % target_vol['name'] - calls = [ - mock.call('qemu-img', 'snapshot', '-c', - snapshot_name, qemu_src_volume_name), - mock.call('qemu-img', 'create', '-b', - qemu_snapshot_name, - qemu_target_volume_name, - '%sG' % target_vol['size']), - ] - mock_exe.assert_has_calls(calls) - - def test_create_cloned_volume_failure(self): - fake_name = six.text_type('volume-00000001') - fake_size = '20' - fake_vol = {'project_id': 'testprjid', 'name': fake_name, - 'size': fake_size, - 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'} - src_vol = fake_vol + @mock.patch.object(sheepdog.SheepdogClient, 'create_snapshot') + @mock.patch.object(sheepdog.SheepdogClient, 'clone') + @mock.patch.object(sheepdog.SheepdogClient, 'delete_snapshot') + def test_create_cloned_volume(self, fake_delete_snapshot, + fake_clone, fake_create_snapshot): + src_vol = self.test_data.TEST_VOLUME + cloned_vol = self.test_data.TEST_CLONED_VOLUME + + self.driver.create_cloned_volume(cloned_vol, src_vol) + snapshot_name = src_vol.name + '-temp-snapshot' + fake_create_snapshot.assert_called_once_with(src_vol.name, + snapshot_name) + fake_clone.assert_called_once_with(src_vol.name, snapshot_name, + cloned_vol.name, cloned_vol.size) + fake_delete_snapshot.assert_called_once_with(src_vol.name, + snapshot_name) + + @mock.patch.object(sheepdog.SheepdogClient, 'create_snapshot') + @mock.patch.object(sheepdog.SheepdogClient, 'clone') + @mock.patch.object(sheepdog.SheepdogClient, 'delete_snapshot') + @mock.patch.object(sheepdog, 'LOG') + def test_create_cloned_volume_failure(self, fake_logger, + fake_delete_snapshot, + fake_clone, fake_create_snapshot): + src_vol = self.test_data.TEST_VOLUME + cloned_vol = self.test_data.TEST_CLONED_VOLUME + snapshot_name = src_vol.name + '-temp-snapshot' + + fake_clone.side_effect = exception.SheepdogCmdError( + cmd='dummy', exit_code=1, stdout='dummy', stderr='dummy') + self.assertRaises(exception.SheepdogCmdError, + self.driver.create_cloned_volume, + cloned_vol, src_vol) + fake_delete_snapshot.assert_called_once_with(src_vol.name, + snapshot_name) + self.assertTrue(fake_logger.error.called) + + @mock.patch.object(sheepdog.SheepdogClient, 'create_snapshot') + def test_create_snapshot(self, fake_create_snapshot): + snapshot = self.test_data.TEST_SNAPSHOT + self.driver.create_snapshot(snapshot) + fake_create_snapshot.assert_called_once_with(snapshot.volume_name, + snapshot.name) - patch = mock.patch.object - with patch(self.driver, '_try_execute', - side_effect=processutils.ProcessExecutionError): - with patch(self.driver, 'create_snapshot'): - with patch(self.driver, 'delete_snapshot'): - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_cloned_volume, - fake_vol, - src_vol) + @mock.patch.object(sheepdog.SheepdogClient, 'delete_snapshot') + def test_delete_snapshot(self, fake_delete_snapshot): + snapshot = self.test_data.TEST_SNAPSHOT + self.driver.delete_snapshot(snapshot) + fake_delete_snapshot.assert_called_once_with(snapshot.volume_name, + snapshot.name) def test_clone_image_success(self): context = {} - fake_name = six.text_type('volume-00000001') - fake_size = '2' - fake_vol = {'project_id': 'testprjid', 'name': fake_name, - 'size': fake_size, - 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'} - image_location = ('sheepdog:192.168.1.111:7000:Alice', None) image_id = "caa4ffd0-fake-fake-fake-f8631a807f5a" image_meta = {'id': image_id, 'size': 1, 'disk_format': 'raw'} @@ -740,13 +1333,14 @@ class SheepdogDriverTestCase(test.TestCase): patch = mock.patch.object with patch(self.driver, '_try_execute', return_value=True): with patch(self.driver, 'create_cloned_volume'): - with patch(self.driver, '_resize'): + with patch(self.client, 'resize'): model_updated, cloned = self.driver.clone_image( - context, fake_vol, image_location, - image_meta, image_service) + context, self.test_data.TEST_CLONED_VOLUME, + image_location, image_meta, image_service) self.assertTrue(cloned) - self.assertEqual("sheepdog:%s" % fake_name, + self.assertEqual("sheepdog:%s" % + self.test_data.TEST_CLONED_VOLUME.name, model_updated['provider_location']) def test_clone_image_failure(self): @@ -791,57 +1385,35 @@ class SheepdogDriverTestCase(test.TestCase): self.driver._is_cloneable(location, image_meta)) fail_try_execute.assert_called_once_with(*expected_cmd) - def test_extend_volume(self): - fake_name = u'volume-00000001' - fake_size = '20' - fake_vol = {'project_id': 'testprjid', 'name': fake_name, - 'size': fake_size, - 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'} - - self.mox.StubOutWithMock(self.driver, '_resize') - size = int(fake_size) * units.Gi - self.driver._resize(fake_vol, size=size) - - self.mox.ReplayAll() - self.driver.extend_volume(fake_vol, fake_size) - - self.mox.VerifyAll() - def test_create_volume_from_snapshot(self): - fake_name = u'volume-00000001' - fake_size = '10' - fake_vol = {'project_id': 'testprjid', 'name': fake_name, - 'size': fake_size, - 'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66'} - - ss_uuid = '00000000-0000-0000-0000-c3aa7ee01536' - fake_snapshot = {'volume_name': fake_name, - 'name': 'volume-%s' % ss_uuid, - 'id': ss_uuid, - 'size': fake_size} - - with mock.patch.object(sheepdog.SheepdogDriver, - '_try_execute') as mock_exe: - self.driver.create_volume_from_snapshot(fake_vol, fake_snapshot) - args = ['qemu-img', 'create', '-b', - "sheepdog:%s:%s" % (fake_snapshot['volume_name'], - fake_snapshot['name']), - "sheepdog:%s" % fake_vol['name'], - "%sG" % fake_vol['size']] - mock_exe.assert_called_once_with(*args) + dst_volume = self.test_data.TEST_CLONED_VOLUME + snapshot = self.test_data.TEST_SNAPSHOT + with mock.patch.object(self.client, 'clone') as fake_execute: + self.driver.create_volume_from_snapshot(dst_volume, snapshot) + fake_execute.assert_called_once_with(self._src_vdiname, + self._snapname, + self._dst_vdiname, + self._dst_vdisize) + + @mock.patch.object(sheepdog.SheepdogClient, 'resize') + @mock.patch.object(sheepdog, 'LOG') + def test_extend_volume(self, fake_logger, fake_execute): + self.driver.extend_volume(self.test_data.TEST_VOLUME, 10) + fake_execute.assert_called_once_with(self._vdiname, 10) + self.assertTrue(fake_logger.debug.called) @mock.patch.object(db, 'volume_get') @mock.patch.object(sheepdog.SheepdogDriver, '_try_execute') - @mock.patch.object(sheepdog.SheepdogDriver, 'create_snapshot') + @mock.patch.object(sheepdog.SheepdogClient, 'create_snapshot') @mock.patch.object(backup_driver, 'BackupDriver') - @mock.patch.object(sheepdog.SheepdogDriver, 'delete_snapshot') + @mock.patch.object(sheepdog.SheepdogClient, 'delete_snapshot') def test_backup_volume_success(self, fake_delete_snapshot, fake_backup_service, fake_create_snapshot, fake_execute, fake_volume_get): fake_context = {} - fake_backup = {'volume_id': '2926efe0-24ab-45b7-95e1-ff66e0646a33'} - fake_volume = {'id': '2926efe0-24ab-45b7-95e1-ff66e0646a33', - 'name': 'volume-2926efe0-24ab-45b7-95e1-ff66e0646a33'} + fake_volume = self.test_data.TEST_VOLUME + fake_backup = self.test_data.TEST_BACKUP_VOLUME + fake_backup_service = mock.Mock() fake_volume_get.return_value = fake_volume self.driver.backup_volume(fake_context, fake_backup, @@ -859,22 +1431,21 @@ class SheepdogDriverTestCase(test.TestCase): @mock.patch.object(db, 'volume_get') @mock.patch.object(sheepdog.SheepdogDriver, '_try_execute') - @mock.patch.object(sheepdog.SheepdogDriver, 'create_snapshot') + @mock.patch.object(sheepdog.SheepdogClient, 'create_snapshot') @mock.patch.object(backup_driver, 'BackupDriver') - @mock.patch.object(sheepdog.SheepdogDriver, 'delete_snapshot') + @mock.patch.object(sheepdog.SheepdogClient, 'delete_snapshot') def test_backup_volume_fail_to_create_snap(self, fake_delete_snapshot, fake_backup_service, fake_create_snapshot, fake_execute, fake_volume_get): fake_context = {} - fake_backup = {'volume_id': '2926efe0-24ab-45b7-95e1-ff66e0646a33'} - fake_volume = {'id': '2926efe0-24ab-45b7-95e1-ff66e0646a33', - 'name': 'volume-2926efe0-24ab-45b7-95e1-ff66e0646a33'} + fake_volume = self.test_data.TEST_VOLUME + fake_backup = self.test_data.TEST_BACKUP_VOLUME fake_volume_get.return_value = fake_volume - fake_create_snapshot.side_effect = processutils.ProcessExecutionError( + fake_create_snapshot.side_effect = exception.SheepdogCmdError( cmd='dummy', exit_code=1, stdout='dummy', stderr='dummy') - self.assertRaises(exception.VolumeBackendAPIException, + self.assertRaises(exception.SheepdogError, self.driver.backup_volume, fake_context, fake_backup, @@ -886,17 +1457,16 @@ class SheepdogDriverTestCase(test.TestCase): @mock.patch.object(db, 'volume_get') @mock.patch.object(sheepdog.SheepdogDriver, '_try_execute') - @mock.patch.object(sheepdog.SheepdogDriver, 'create_snapshot') + @mock.patch.object(sheepdog.SheepdogClient, 'create_snapshot') @mock.patch.object(backup_driver, 'BackupDriver') - @mock.patch.object(sheepdog.SheepdogDriver, 'delete_snapshot') + @mock.patch.object(sheepdog.SheepdogClient, 'delete_snapshot') def test_backup_volume_fail_to_backup_vol(self, fake_delete_snapshot, fake_backup_service, fake_create_snapshot, fake_execute, fake_volume_get): fake_context = {} - fake_backup = {'volume_id': '2926efe0-24ab-45b7-95e1-ff66e0646a33'} - fake_volume = {'id': '2926efe0-24ab-45b7-95e1-ff66e0646a33', - 'name': 'volume-2926efe0-24ab-45b7-95e1-ff66e0646a33'} + fake_volume = self.test_data.TEST_VOLUME + fake_backup = self.test_data.TEST_BACKUP_VOLUME fake_volume_get.return_value = fake_volume class BackupError(Exception): @@ -917,9 +1487,8 @@ class SheepdogDriverTestCase(test.TestCase): @mock.patch.object(backup_driver, 'BackupDriver') def test_restore_backup(self, fake_backup_service): fake_context = {} - fake_backup = {} - fake_volume = {'id': '2926efe0-24ab-45b7-95e1-ff66e0646a33', - 'name': 'volume-2926efe0-24ab-45b7-95e1-ff66e0646a33'} + fake_backup = self.test_data.TEST_BACKUP_VOLUME + fake_volume = self.test_data.TEST_VOLUME self.driver.restore_backup( fake_context, fake_backup, fake_volume, fake_backup_service) @@ -927,5 +1496,5 @@ class SheepdogDriverTestCase(test.TestCase): call_args, call_kwargs = fake_backup_service.restore.call_args call_backup, call_volume_id, call_sheepdog_fd = call_args self.assertEqual(fake_backup, call_backup) - self.assertEqual(fake_volume['id'], call_volume_id) + self.assertEqual(fake_volume.id, call_volume_id) self.assertIsInstance(call_sheepdog_fd, sheepdog.SheepdogIOWrapper) diff --git a/cinder/volume/drivers/sheepdog.py b/cinder/volume/drivers/sheepdog.py index 66443a306..217890664 100644 --- a/cinder/volume/drivers/sheepdog.py +++ b/cinder/volume/drivers/sheepdog.py @@ -56,6 +56,7 @@ CONF.register_opts(sheepdog_opts) class SheepdogClient(object): """Sheepdog command executor.""" + QEMU_SHEEPDOG_PREFIX = 'sheepdog:' DOG_RESP_CONNECTION_ERROR = 'failed to connect to' DOG_RESP_CLUSTER_RUNNING = 'Cluster status: running' DOG_RESP_CLUSTER_NOT_FORMATTED = ('Cluster status: ' @@ -64,6 +65,17 @@ class SheepdogClient(object): 'Waiting for other nodes to join cluster') DOG_RESP_VDI_ALREADY_EXISTS = ': VDI exists already' DOG_RESP_VDI_NOT_FOUND = ': No VDI found' + DOG_RESP_VDI_SHRINK_NOT_SUPPORT = 'Shrinking VDIs is not implemented' + DOG_RESP_VDI_SIZE_TOO_LARGE = 'New VDI size is too large' + DOG_RESP_SNAPSHOT_VDI_NOT_FOUND = ': No VDI found' + DOG_RESP_SNAPSHOT_NOT_FOUND = ': Failed to find requested tag' + DOG_RESP_SNAPSHOT_EXISTED = 'tag (%(snapname)s) is existed' + QEMU_IMG_RESP_CONNECTION_ERROR = ('Failed to connect socket: ' + 'Connection refused') + QEMU_IMG_RESP_ALREADY_EXISTS = ': VDI exists already' + QEMU_IMG_RESP_SNAPSHOT_NOT_FOUND = 'Failed to find the requested tag' + QEMU_IMG_RESP_VDI_NOT_FOUND = 'No vdi found' + QEMU_IMG_RESP_SIZE_TOO_LARGE = 'An image is too large.' def __init__(self, addr, port): self.addr = addr @@ -71,7 +83,7 @@ class SheepdogClient(object): def _run_dog(self, command, subcommand, *params): cmd = ('env', 'LC_ALL=C', 'LANG=C', 'dog', command, subcommand, - '-a', self.addr, '-p', str(self.port)) + params + '-a', self.addr, '-p', self.port) + params try: return utils.execute(*cmd) except OSError as e: @@ -89,6 +101,36 @@ class SheepdogClient(object): stdout=e.stdout.replace('\n', '\\n'), stderr=e.stderr.replace('\n', '\\n')) + def _run_qemu_img(self, command, *params): + """Executes qemu-img command wrapper""" + cmd = ['env', 'LC_ALL=C', 'LANG=C', 'qemu-img', command] + for param in params: + if param.startswith(self.QEMU_SHEEPDOG_PREFIX): + # replace 'sheepdog:vdiname[:snapshotname]' to + # 'sheepdog:addr:port:vdiname[:snapshotname]' + param = param.replace(self.QEMU_SHEEPDOG_PREFIX, + '%(prefix)s%(addr)s:%(port)s:' % + {'prefix': self.QEMU_SHEEPDOG_PREFIX, + 'addr': self.addr, 'port': self.port}, + 1) + cmd.append(param) + try: + return utils.execute(*cmd) + except OSError as e: + with excutils.save_and_reraise_exception(): + if e.errno == errno.ENOENT: + msg = _LE('Qemu-img is not installed. ' + 'OSError: command is %(cmd)s.') + else: + msg = _LE('OSError: command is %(cmd)s.') + LOG.error(msg, {'cmd': tuple(cmd)}) + except processutils.ProcessExecutionError as e: + raise exception.SheepdogCmdError( + cmd=e.cmd, + exit_code=e.exit_code, + stdout=e.stdout.replace('\n', '\\n'), + stderr=e.stderr.replace('\n', '\\n')) + def check_cluster_status(self): try: (_stdout, _stderr) = self._run_dog('cluster', 'info') @@ -160,6 +202,124 @@ class SheepdogClient(object): else: LOG.error(_LE('Failed to delete volume. %s'), vdiname) + def create_snapshot(self, vdiname, snapname): + try: + self._run_dog('vdi', 'snapshot', '-s', snapname, vdiname) + except exception.SheepdogCmdError as e: + cmd = e.kwargs['cmd'] + _stderr = e.kwargs['stderr'] + with excutils.save_and_reraise_exception(): + if _stderr.startswith(self.DOG_RESP_CONNECTION_ERROR): + LOG.error(_LE('Failed to connect to sheep daemon. ' + 'addr: %(addr)s, port: %(port)s'), + {'addr': self.addr, 'port': self.port}) + elif _stderr.rstrip('\\n').endswith( + self.DOG_RESP_SNAPSHOT_VDI_NOT_FOUND): + LOG.error(_LE('Volume "%s" not found. Please check the ' + 'results of "dog vdi list".'), + vdiname) + elif _stderr.rstrip('\\n').endswith( + self.DOG_RESP_SNAPSHOT_EXISTED % + {'snapname': snapname}): + LOG.error(_LE('Snapshot "%s" already exists.'), snapname) + else: + LOG.error(_LE('Failed to create snapshot. (command: %s)'), + cmd) + + def delete_snapshot(self, vdiname, snapname): + try: + (_stdout, _stderr) = self._run_dog('vdi', 'delete', '-s', + snapname, vdiname) + if _stderr.rstrip().endswith(self.DOG_RESP_SNAPSHOT_NOT_FOUND): + LOG.warning(_LW('Snapshot "%s" not found.'), snapname) + elif _stderr.rstrip().endswith(self.DOG_RESP_VDI_NOT_FOUND): + 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: + cmd = e.kwargs['cmd'] + _stderr = e.kwargs['stderr'] + with excutils.save_and_reraise_exception(): + if _stderr.startswith(self.DOG_RESP_CONNECTION_ERROR): + msg = _LE('Failed to connect to sheep daemon. ' + 'addr: %(addr)s, port: %(port)s') + LOG.error(msg, {'addr': self.addr, 'port': self.port}) + else: + LOG.error(_LE('Failed to delete snapshot. (command: %s)'), + cmd) + + def clone(self, src_vdiname, src_snapname, dst_vdiname, size): + try: + self._run_qemu_img('create', '-b', + 'sheepdog:%(src_vdiname)s:%(src_snapname)s' % + {'src_vdiname': src_vdiname, + 'src_snapname': src_snapname}, + 'sheepdog:%s' % dst_vdiname, '%sG' % size) + except exception.SheepdogCmdError as e: + cmd = e.kwargs['cmd'] + _stderr = e.kwargs['stderr'] + with excutils.save_and_reraise_exception(): + if self.QEMU_IMG_RESP_CONNECTION_ERROR 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. ' + 'Please check the results of "dog vdi list".'), + dst_vdiname) + elif self.QEMU_IMG_RESP_VDI_NOT_FOUND in _stderr: + LOG.error(_LE('Src Volume "%s" not found. ' + 'Please check the results of "dog vdi list".'), + src_vdiname) + elif self.QEMU_IMG_RESP_SNAPSHOT_NOT_FOUND in _stderr: + LOG.error(_LE('Snapshot "%s" not found. ' + 'Please check the results of "dog vdi list".'), + src_snapname) + elif self.QEMU_IMG_RESP_SIZE_TOO_LARGE in _stderr: + LOG.error(_LE('Volume size "%sG" is too large.'), size) + else: + LOG.error(_LE('Failed to clone volume.(command: %s)'), cmd) + + def resize(self, vdiname, size): + size = int(size) * units.Gi + try: + (_stdout, _stderr) = self._run_dog('vdi', 'resize', vdiname, size) + except exception.SheepdogCmdError as e: + _stderr = e.kwargs['stderr'] + with excutils.save_and_reraise_exception(): + if _stderr.startswith(self.DOG_RESP_CONNECTION_ERROR): + LOG.error(_LE('Failed to connect to sheep daemon. ' + 'addr: %(addr)s, port: %(port)s'), + {'addr': self.addr, 'port': self.port}) + elif _stderr.rstrip('\\n').endswith( + self.DOG_RESP_VDI_NOT_FOUND): + LOG.error(_LE('Failed to resize vdi. vdi not found. %s'), + vdiname) + elif _stderr.startswith(self.DOG_RESP_VDI_SHRINK_NOT_SUPPORT): + LOG.error(_LE('Failed to resize vdi. ' + 'Shrinking vdi not supported. ' + 'vdi: %(vdiname)s new size: %(size)s'), + {'vdiname': vdiname, 'size': size}) + elif _stderr.startswith(self.DOG_RESP_VDI_SIZE_TOO_LARGE): + LOG.error(_LE('Failed to resize vdi. ' + 'Too large volume size. ' + 'vdi: %(vdiname)s new size: %(size)s'), + {'vdiname': vdiname, 'size': size}) + else: + LOG.error(_LE('Failed to resize vdi. ' + 'vdi: %(vdiname)s new size: %(size)s'), + {'vdiname': vdiname, 'size': size}) + class SheepdogIOWrapper(io.RawIOBase): """File-like object with Sheepdog backend.""" @@ -318,7 +478,7 @@ class SheepdogDriver(driver.VolumeDriver): (label, ip, port, name) = image_location.split(":", 3) volume_ref = {'name': name, 'size': image_meta['size']} self.create_cloned_volume(volume, volume_ref) - self._resize(volume) + self.client.resize(volume.name, volume.size) vol_path = self.local_path(volume) return {'provider_location': vol_path}, True @@ -333,18 +493,18 @@ class SheepdogDriver(driver.VolumeDriver): 'volume_size': src_vref['size'], } - self.create_snapshot(snapshot) + self.client.create_snapshot(snapshot['volume_name'], snapshot_name) try: - # Create volume - self.create_volume_from_snapshot(volume, snapshot) - except processutils.ProcessExecutionError: - msg = _('Failed to create cloned volume %s.') % volume['id'] - LOG.error(msg) - raise exception.VolumeBackendAPIException(msg) + self.client.clone(snapshot['volume_name'], snapshot_name, + volume.name, volume.size) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error(_LE('Failed to create cloned volume %s.'), + volume.name) finally: # Delete temp Snapshot - self.delete_snapshot(snapshot) + self.client.delete_snapshot(snapshot['volume_name'], snapshot_name) def create_volume(self, volume): """Create a sheepdog volume.""" @@ -352,23 +512,13 @@ class SheepdogDriver(driver.VolumeDriver): def create_volume_from_snapshot(self, volume, snapshot): """Create a sheepdog volume from a snapshot.""" - self._try_execute('qemu-img', 'create', '-b', - "sheepdog:%s:%s" % (snapshot['volume_name'], - snapshot['name']), - "sheepdog:%s" % volume['name'], - '%sG' % volume['size']) + self.client.clone(snapshot.volume_name, snapshot.name, + volume.name, volume.size) def delete_volume(self, volume): """Delete a logical volume.""" self.client.delete(volume.name) - def _resize(self, volume, size=None): - if not size: - size = int(volume['size']) * units.Gi - - self._try_execute('collie', 'vdi', 'resize', - volume['name'], size) - def copy_image_to_volume(self, context, volume, image_service, image_id): with image_utils.temporary_file() as tmp: # (wenhao): we don't need to convert to raw for sheepdog. @@ -381,7 +531,7 @@ class SheepdogDriver(driver.VolumeDriver): # convert and store into sheepdog image_utils.convert_image(tmp, 'sheepdog:%s' % volume['name'], 'raw') - self._resize(volume) + self.client.resize(volume.name, volume.size) def copy_volume_to_image(self, context, volume, image_service, image_meta): """Copy the volume to the specified image.""" @@ -404,13 +554,11 @@ class SheepdogDriver(driver.VolumeDriver): def create_snapshot(self, snapshot): """Create a sheepdog snapshot.""" - self._try_execute('qemu-img', 'snapshot', '-c', snapshot['name'], - "sheepdog:%s" % snapshot['volume_name']) + self.client.create_snapshot(snapshot.volume_name, snapshot.name) def delete_snapshot(self, snapshot): """Delete a sheepdog snapshot.""" - self._try_execute('collie', 'vdi', 'delete', snapshot['volume_name'], - '-s', snapshot['name']) + self.client.delete_snapshot(snapshot.volume_name, snapshot.name) def local_path(self, volume): return "sheepdog:%s" % volume['name'] @@ -472,25 +620,14 @@ class SheepdogDriver(driver.VolumeDriver): def extend_volume(self, volume, new_size): """Extend an Existing Volume.""" - old_size = volume['size'] - - try: - size = int(new_size) * units.Gi - self._resize(volume, size=size) - except Exception: - msg = _('Failed to Extend Volume ' - '%(volname)s') % {'volname': volume['name']} - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - LOG.debug("Extend volume from %(old_size)s GB to %(new_size)s GB.", - {'old_size': old_size, 'new_size': new_size}) + self.client.resize(volume.name, new_size) + LOG.debug('Extend volume from %(old_size)s GB to %(new_size)s GB.', + {'old_size': volume.size, 'new_size': new_size}) def backup_volume(self, context, backup, backup_service): """Create a new backup from an existing volume.""" - volume = self.db.volume_get(context, backup['volume_id']) - temp_snapshot = {'volume_name': volume['name'], - 'name': 'tmp-snap-%s' % volume['name']} + src_volume = self.db.volume_get(context, backup.volume_id) + temp_snapshot_name = 'tmp-snap-%s' % src_volume.name # NOTE(tishizaki): If previous backup_volume operation has failed, # a temporary snapshot for previous operation may exist. @@ -501,23 +638,23 @@ class SheepdogDriver(driver.VolumeDriver): # is failed, and raise ProcessExecutionError when target snapshot # does not exist. try: - self.delete_snapshot(temp_snapshot) - except (processutils.ProcessExecutionError): + self.client.delete_snapshot(src_volume.name, temp_snapshot_name) + except (exception.SheepdogCmdError): pass try: - self.create_snapshot(temp_snapshot) - except (processutils.ProcessExecutionError, OSError): + self.client.create_snapshot(src_volume.name, temp_snapshot_name) + except (exception.SheepdogCmdError, OSError): msg = (_('Failed to create a temporary snapshot for volume %s.') - % volume['id']) + % src_volume.id) LOG.exception(msg) - raise exception.VolumeBackendAPIException(data=msg) + raise exception.SheepdogError(reason=msg) try: - sheepdog_fd = SheepdogIOWrapper(volume, temp_snapshot['name']) + sheepdog_fd = SheepdogIOWrapper(src_volume, temp_snapshot_name) backup_service.backup(backup, sheepdog_fd) finally: - self.delete_snapshot(temp_snapshot) + self.client.delete_snapshot(src_volume.name, temp_snapshot_name) def restore_backup(self, context, backup, volume, backup_service): """Restore an existing backup to a new or existing volume.""" -- 2.45.2