--- /dev/null
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import collections
+
+import mock
+from oslo.utils import importutils
+from oslo.utils import timeutils
+
+from cinder import context
+from cinder.openstack.common import log as logging
+from cinder import test
+from cinder.volume import configuration as conf
+
+
+class mock_dbus():
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def Array(defaults, signature=None):
+ return defaults
+
+
+class mock_dm_utils():
+
+ @staticmethod
+ def dict_to_aux_props(x):
+ return x
+
+
+class mock_dm_const():
+
+ TQ_GET_PATH = "get_path"
+
+
+class mock_dm_exc():
+
+ DM_SUCCESS = 0
+ DM_EEXIST = 1
+ DM_ENOENT = 2
+ DM_ERROR = 1000
+
+ pass
+
+
+import sys
+sys.modules['dbus'] = mock_dbus
+sys.modules['drbdmanage'] = collections.namedtuple(
+ 'module', ['consts', 'exceptions', 'utils'])
+sys.modules['drbdmanage.utils'] = collections.namedtuple(
+ 'module', ['dict_to_aux_props'])
+sys.modules['drbdmanage.consts'] = collections.namedtuple(
+ 'module', [])
+sys.modules['drbdmanage.exceptions'] = collections.namedtuple(
+ 'module', ['DM_EEXIST'])
+
+
+from cinder.volume.drivers.drbdmanagedrv import DrbdManageDriver
+
+
+LOG = logging.getLogger(__name__)
+
+
+def create_configuration():
+ configuration = mock.MockObject(conf.Configuration)
+ configuration.san_is_local = False
+ configuration.append_config_values(mock.IgnoreArg())
+ return configuration
+
+
+class DrbdManageFakeDriver():
+
+ resources = {}
+
+ def __init__(self):
+ self.calls = []
+
+ def list_resources(self, res, serial, prop, req):
+ self.calls.append(["list_resources", res, prop, req])
+ if 'cinder-id' in prop and prop['cinder-id'].startswith("deadbeef"):
+ return ([mock_dm_exc.DM_ENOENT, "none", []],
+ [])
+ else:
+ return ([[mock_dm_exc.DM_SUCCESS, "ACK", []]],
+ [("res", dict(prop))])
+
+ def create_resource(self, res, props):
+ self.calls.append(["create_resource", res, props])
+ return [[mock_dm_exc.DM_SUCCESS, "ack", []]]
+
+ def create_volume(self, res, size, props):
+ self.calls.append(["create_volume", res, size, props])
+ return [[mock_dm_exc.DM_SUCCESS, "ack", []]]
+
+ def auto_deploy(self, res, red, delta, site_clients):
+ self.calls.append(["auto_deploy", res, red, delta, site_clients])
+ return [[mock_dm_exc.DM_SUCCESS, "ack", []] * red]
+
+ def list_volumes(self, res, ser, prop, req):
+ self.calls.append(["list_volumes", res, ser, prop, req])
+ if 'cinder-id' in prop and prop['cinder-id'].startswith("deadbeef"):
+ return ([mock_dm_exc.DM_ENOENT, "none", []],
+ [])
+ else:
+ return ([[mock_dm_exc.DM_SUCCESS, "ACK", []]],
+ [("res", dict(), [(2, dict(prop))])
+ ])
+
+ def remove_volume(self, res, nr, force):
+ self.calls.append(["remove_volume", res, nr, force])
+ return [[mock_dm_exc.DM_SUCCESS, "ack", []]]
+
+ def text_query(self, cmd):
+ self.calls.append(["text_query", cmd])
+ if cmd[0] == mock_dm_const.TQ_GET_PATH:
+ return ([(mock_dm_exc.DM_SUCCESS, "ack", [])], ['/dev/drbd0'])
+ return ([(mock_dm_exc.DM_ERROR, 'unknown command', [])], [])
+
+ def list_assignments(self, nodes, res, ser, prop, req):
+ self.calls.append(["list_assignments", nodes, res, ser, prop, req])
+ if 'cinder-id' in prop and prop['cinder-id'].startswith("deadbeef"):
+ return ([mock_dm_exc.DM_ENOENT, "none", []],
+ [])
+ else:
+ return ([[mock_dm_exc.DM_SUCCESS, "ACK", []]],
+ [("node", "res", dict(), [(2, dict(prop))])
+ ])
+
+ def create_snapshot(self, res, snap, nodes, props):
+ self.calls.append(["create_snapshot", res, snap, nodes, props])
+ return [[mock_dm_exc.DM_SUCCESS, "ack", []]]
+
+ def list_snapshots(self, res, sn, prop, req):
+ self.calls.append(["list_snapshots", res, sn, prop, req])
+ if 'cinder-id' in prop and prop['cinder-id'].startswith("deadbeef"):
+ return ([mock_dm_exc.DM_ENOENT, "none", []],
+ [])
+ else:
+ return ([[mock_dm_exc.DM_SUCCESS, "ACK", []]],
+ [("res", [("snap", dict(prop))])
+ ])
+
+ def remove_snapshot(self, res, snap, force):
+ self.calls.append(["remove_snapshot", res, snap, force])
+ return [[mock_dm_exc.DM_SUCCESS, "ack", []]]
+
+ def resize_volume(self, res, vol, ser, size, delta):
+ self.calls.append(["resize_volume", res, vol, ser, size, delta])
+ return [[mock_dm_exc.DM_SUCCESS, "ack", []]]
+
+ def restore_snapshot(self, res, snap, new, rprop, vprops):
+ self.calls.append(["restore_snapshot", res, snap, new, rprop, vprops])
+ return [[mock_dm_exc.DM_SUCCESS, "ack", []]]
+
+
+class DrbdManageTestCase(test.TestCase):
+
+ def setUp(self):
+ self.ctxt = context.get_admin_context()
+ self._mock = mock.Mock()
+ self.configuration = mock.Mock(conf.Configuration)
+ self.configuration.san_is_local = True
+ self.configuration.reserved_percentage = 1
+
+ super(DrbdManageTestCase, self).setUp()
+
+ self.stubs.Set(importutils, 'import_object',
+ self.fake_import_object)
+ self.stubs.Set(DrbdManageDriver, 'call_or_reconnect',
+ self.fake_issue_dbus_call)
+ self.stubs.Set(DrbdManageDriver, 'dbus_connect',
+ self.fake_issue_dbus_connect)
+
+ sys.modules['cinder.volume.drivers.drbdmanagedrv'].dm_const \
+ = mock_dm_const
+ sys.modules['cinder.volume.drivers.drbdmanagedrv'].dm_utils \
+ = mock_dm_utils
+ sys.modules['cinder.volume.drivers.drbdmanagedrv'].dm_exc \
+ = mock_dm_exc
+
+ self.configuration.safe_get = lambda x: 'fake'
+
+ # Infrastructure
+ def fake_import_object(self, what, configuration, db, executor):
+ return None
+
+ def fake_issue_dbus_call(self, fn, *args):
+ return apply(fn, args)
+
+ def fake_issue_dbus_connect(self):
+ self.odm = DrbdManageFakeDriver()
+
+ def call_or_reconnect(self, method, *params):
+ return apply(method, params)
+
+ # Tests per se
+
+ def test_create_volume(self):
+ testvol = {'project_id': 'testprjid',
+ 'name': 'testvol',
+ 'size': 1,
+ 'id': 'ba253fd0-8068-11e4-98c0-5254008ea111',
+ 'volume_type_id': 'drbdmanage',
+ 'created_at': timeutils.utcnow()}
+
+ dmd = DrbdManageDriver(configuration=self.configuration)
+ dmd.odm = DrbdManageFakeDriver()
+ dmd.create_volume(testvol)
+ self.assertEqual(dmd.odm.calls[0][0], "create_resource")
+ self.assertEqual(dmd.odm.calls[1][0], "create_volume")
+ self.assertEqual(dmd.odm.calls[1][2], 1048576)
+ self.assertEqual(dmd.odm.calls[2][0], "auto_deploy")
+
+ def test_delete_volume(self):
+ testvol = {'project_id': 'testprjid',
+ 'name': 'testvol',
+ 'size': 1,
+ 'id': 'ba253fd0-8068-11e4-98c0-5254008ea111',
+ 'volume_type_id': 'drbdmanage',
+ 'created_at': timeutils.utcnow()}
+
+ dmd = DrbdManageDriver(configuration=self.configuration)
+ dmd.odm = DrbdManageFakeDriver()
+ dmd.delete_volume(testvol)
+ self.assertEqual(dmd.odm.calls[0][0], "list_volumes")
+ self.assertEqual(dmd.odm.calls[0][3]["cinder-id"], testvol['id'])
+ self.assertEqual(dmd.odm.calls[1][0], "remove_volume")
+
+ def test_local_path(self):
+ testvol = {'project_id': 'testprjid',
+ 'name': 'testvol',
+ 'size': 1,
+ 'id': 'ba253fd0-8068-11e4-98c0-5254008ea111',
+ 'volume_type_id': 'drbdmanage',
+ 'created_at': timeutils.utcnow()}
+
+ dmd = DrbdManageDriver(configuration=self.configuration)
+ dmd.odm = DrbdManageFakeDriver()
+ data = dmd.local_path(testvol)
+ self.assertTrue(data.startswith("/dev/drbd"))
+
+ def test_create_snapshot(self):
+ testsnap = {'id': 'ca253fd0-8068-11e4-98c0-5254008ea111',
+ 'volume_id': 'ba253fd0-8068-11e4-98c0-5254008ea111'}
+
+ dmd = DrbdManageDriver(configuration=self.configuration)
+ dmd.odm = DrbdManageFakeDriver()
+ dmd.create_snapshot(testsnap)
+ self.assertEqual(dmd.odm.calls[0][0], "list_volumes")
+ self.assertEqual(dmd.odm.calls[1][0], "list_assignments")
+ self.assertEqual(dmd.odm.calls[2][0], "create_snapshot")
+ self.assertTrue('node' in dmd.odm.calls[2][3])
+
+ def test_delete_snapshot(self):
+ testsnap = {'id': 'ca253fd0-8068-11e4-98c0-5254008ea111'}
+
+ dmd = DrbdManageDriver(configuration=self.configuration)
+ dmd.odm = DrbdManageFakeDriver()
+ dmd.delete_snapshot(testsnap)
+ self.assertEqual(dmd.odm.calls[0][0], "list_snapshots")
+ self.assertEqual(dmd.odm.calls[1][0], "remove_snapshot")
+
+ def test_extend_volume(self):
+ testvol = {'project_id': 'testprjid',
+ 'name': 'testvol',
+ 'size': 1,
+ 'id': 'ba253fd0-8068-11e4-98c0-5254008ea111',
+ 'volume_type_id': 'drbdmanage',
+ 'created_at': timeutils.utcnow()}
+
+ dmd = DrbdManageDriver(configuration=self.configuration)
+ dmd.odm = DrbdManageFakeDriver()
+ dmd.extend_volume(testvol, 5)
+ self.assertEqual(dmd.odm.calls[0][0], "list_volumes")
+ self.assertEqual(dmd.odm.calls[0][3]["cinder-id"], testvol['id'])
+ self.assertEqual(dmd.odm.calls[1][0], "resize_volume")
+ self.assertEqual(dmd.odm.calls[1][1], "res")
+ self.assertEqual(dmd.odm.calls[1][2], 2)
+ self.assertEqual(dmd.odm.calls[1][3], -1)
+ self.assertEqual(dmd.odm.calls[1][4]['size'], 5242880)
+
+ def test_create_cloned_volume(self):
+ srcvol = {'project_id': 'testprjid',
+ 'name': 'testvol',
+ 'size': 1,
+ 'id': 'ba253fd0-8068-11e4-98c0-5254008ea111',
+ 'volume_type_id': 'drbdmanage',
+ 'created_at': timeutils.utcnow()}
+
+ newvol = {'id': 'ca253fd0-8068-11e4-98c0-5254008ea111'}
+
+ dmd = DrbdManageDriver(configuration=self.configuration)
+ dmd.odm = DrbdManageFakeDriver()
+ dmd.create_cloned_volume(newvol, srcvol)
+ self.assertEqual(dmd.odm.calls[0][0], "list_volumes")
+ self.assertEqual(dmd.odm.calls[1][0], "list_assignments")
+ self.assertEqual(dmd.odm.calls[2][0], "create_snapshot")
+ self.assertEqual(dmd.odm.calls[3][0], "list_snapshots")
+ self.assertEqual(dmd.odm.calls[4][0], "restore_snapshot")
+ self.assertEqual(dmd.odm.calls[5][0], "list_snapshots")
+ self.assertEqual(dmd.odm.calls[6][0], "remove_snapshot")
+ self.assertEqual(dmd.odm.calls[6][0], "remove_snapshot")
--- /dev/null
+# Copyright (c) 2014 LINBIT HA Solutions GmbH
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+"""
+
+This driver connects Cinder to an installed DRBDmanage instance, see
+ http://oss.linbit.com/drbdmanage/
+ http://git.linbit.com/drbdmanage.git/
+for more details.
+
+"""
+
+import uuid
+
+import dbus
+import drbdmanage.consts as dm_const
+import drbdmanage.exceptions as dm_exc
+import drbdmanage.utils as dm_utils
+from oslo.config import cfg
+from oslo.utils import importutils
+from oslo.utils import units
+import six
+
+
+from cinder import exception
+from cinder.i18n import _, _LW
+from cinder.openstack.common import log as logging
+from cinder.volume import driver
+
+
+LOG = logging.getLogger(__name__)
+EMPTY_LIST = dbus.Array([], signature="a(ss)")
+
+drbd_opts = [
+ cfg.StrOpt('drbdmanage_redundancy',
+ default='1',
+ help='Number of nodes that should replicate the data.'),
+ # TODO(PM): offsite_redundancy?
+ # TODO(PM): choose DRBDmanage storage pool?
+]
+
+
+CONF = cfg.CONF
+CONF.register_opts(drbd_opts)
+
+
+CINDER_AUX_PROP_id = "cinder-id"
+DM_VN_PREFIX = 'CV_' # sadly 2CV isn't allowed by DRBDmanage
+
+
+class DrbdManageDriver(driver.VolumeDriver):
+ """Cinder driver that uses DRBDmanage as data store.
+ """
+
+ VERSION = '1.0.0'
+ drbdmanage_dbus_name = 'org.drbd.drbdmanaged'
+ drbdmanage_dbus_interface = '/interface'
+
+ def __init__(self, execute=None, *args, **kwargs):
+ super(DrbdManageDriver, self).__init__(*args, **kwargs)
+ if self.configuration:
+ self.configuration.append_config_values(drbd_opts)
+ if not self.drbdmanage_dbus_name:
+ self.drbdmanage_dbus_name = 'org.drbd.drbdmanaged'
+ if not self.drbdmanage_dbus_interface:
+ self.drbdmanage_dbus_interface = '/interface'
+ self.drbdmanage_redundancy = int(getattr(self.configuration,
+ 'drbdmanage_redundancy', 1))
+ self.dm_control_vol = ".drbdctrl"
+
+ # Copied from the LVM driver, see
+ # I43190d1dac33748fe55fa00f260f32ab209be656
+ target_driver = \
+ self.target_mapping[self.configuration.safe_get('iscsi_helper')]
+
+ LOG.debug('Attempting to initialize DRBD driver with the '
+ 'following target_driver: %s',
+ target_driver)
+
+ self.target_driver = importutils.import_object(
+ target_driver,
+ configuration=self.configuration,
+ db=self.db,
+ executor=self._execute)
+
+ def dbus_connect(self):
+ self.odm = dbus.SystemBus().get_object(self.drbdmanage_dbus_name,
+ self.drbdmanage_dbus_interface)
+ self.odm.ping()
+
+ def call_or_reconnect(self, fn, *args):
+ """Call DBUS function; if it got disconnected,
+ try once to reconnect.
+ """
+ try:
+ return apply(fn, args)
+ except dbus.DBusException as e:
+ LOG.warn(_LW("got disconnected; trying to reconnect. (%s)") %
+ six.text_type(e))
+ self.dbus_connect()
+ return apply(fn, args)
+
+ def do_setup(self, context):
+ """Any initialization the volume driver does while starting."""
+ super(DrbdManageDriver, self).do_setup(context)
+ self.dbus_connect()
+
+ def check_for_setup_error(self):
+ """Verify that requirements are in place to use DRBDmanage driver."""
+ if self.odm.ping() != 0:
+ message = _('Cannot ping DRBDmanage backend')
+ raise exception.VolumeBackendAPIException(data=message)
+
+ def _clean_uuid(self):
+ """Returns a UUID string, WITHOUT braces."""
+ # Some uuid library versions put braces around the result!?
+ # We don't want them, just a plain [0-9a-f-]+ string.
+ id = str(uuid.uuid4())
+ return id.translate(None, "{}")
+
+ def _check_result(self, res, ignore=None, ret=0):
+ seen_success = False
+ seen_error = False
+ result = ret
+ for (code, fmt, arg_l) in res:
+ # convert from DBUS to Python
+ arg = dict(arg_l)
+ if ignore and code in ignore:
+ if not result:
+ result = code
+ continue
+ if code == dm_exc.DM_SUCCESS:
+ seen_success = True
+ continue
+ seen_error = _("Received error string: %s") % (fmt % arg)
+
+ if seen_error:
+ raise exception.VolumeBackendAPIException(data=seen_error)
+ if seen_success:
+ return ret
+ # by default okay - or the ignored error code.
+ return ret
+
+ # DRBDmanage works in kiB units; Cinder uses float GiB.
+ def _vol_size_to_dm(self, size):
+ return int(size * units.Gi / units.Ki)
+
+ def _vol_size_to_cinder(self, size):
+ return int(size * units.Ki / units.Gi)
+
+ def is_clean_volume_name(self, name):
+ try:
+ if name.startswith(CONF.volume_name_template % "") and \
+ uuid.UUID(name[7:]) is not None:
+ return DM_VN_PREFIX + name[7:]
+ except ValueError:
+ return None
+
+ try:
+ if uuid.UUID(name) is not None:
+ return DM_VN_PREFIX + name
+ except ValueError:
+ return None
+
+ def _priv_hash_from_volume(self, volume):
+ return dm_utils.dict_to_aux_props({
+ CINDER_AUX_PROP_id: volume['id'],
+ })
+
+ def snapshot_name_from_cinder_snapshot(self, snapshot):
+ sn_name = self.is_clean_volume_name(snapshot['id'])
+ return sn_name
+
+ def _res_and_vl_data_for_volume(self, volume, empty_ok=False):
+ """A DRBD resource might consist of several "volumes"
+ (think consistency groups).
+ So we have to find the number of the volume within one resource.
+ Returns resource name, volume number, and resource
+ and volume properties.
+ """
+
+ # If we get a string, use it as-is.
+ # Else it's a dictionary; then get the ID.
+ if type(volume) is str or type(volume) is unicode:
+ v_uuid = volume
+ else:
+ v_uuid = volume['id']
+
+ res, rl = self.call_or_reconnect(self.odm.list_volumes,
+ EMPTY_LIST,
+ 0,
+ dm_utils.dict_to_aux_props(
+ {CINDER_AUX_PROP_id: v_uuid}),
+ EMPTY_LIST)
+ self._check_result(res)
+
+ if (not rl) or (len(rl) == 0):
+ if empty_ok:
+ LOG.debug("No volume %s found." % v_uuid)
+ return None, None, None, None
+ raise exception.VolumeBackendAPIException(
+ data=_("volume %s not found in drbdmanage") % v_uuid)
+ if len(rl) > 1:
+ raise exception.VolumeBackendAPIException(
+ data=_("multiple resources with name %s found by drbdmanage") %
+ v_uuid)
+
+ (r_name, r_props, vols) = rl[0]
+ if len(vols) != 1:
+ raise exception.VolumeBackendAPIException(
+ data=_("not exactly one volume with id %s") %
+ v_uuid)
+
+ (v_nr, v_props) = vols[0]
+
+ LOG.debug("volume %s is %s/%d; %s, %s" %
+ (v_uuid, r_name, v_nr, r_props, v_props))
+
+ return r_name, v_nr, r_props, v_props
+
+ def _resource_and_snap_data_from_snapshot(self, snapshot, empty_ok=False):
+ """Find the DRBD Resource and the snapshot name
+ from the snapshot ID.
+ """
+ s_uuid = snapshot['id']
+ res, rs = self.call_or_reconnect(self.odm.list_snapshots,
+ EMPTY_LIST,
+ EMPTY_LIST,
+ dm_utils.dict_to_aux_props(
+ {CINDER_AUX_PROP_id: s_uuid}),
+ EMPTY_LIST)
+ self._check_result(res)
+
+ if (not rs) or (len(rs) == 0):
+ if empty_ok:
+ return None
+ else:
+ raise exception.VolumeBackendAPIException(
+ data=_("no snapshot with id %s found in drbdmanage") %
+ s_uuid)
+ if len(rs) > 1:
+ raise exception.VolumeBackendAPIException(
+ data=_("multiple resources with snapshot ID %s found") %
+ s_uuid)
+
+ (r_name, snaps) = rs[0]
+ if len(snaps) != 1:
+ raise exception.VolumeBackendAPIException(
+ data=_("not exactly one snapshot with id %s") % s_uuid)
+
+ (s_name, s_props) = snaps[0]
+
+ LOG.debug("snapshot %s is %s/%s" % (s_uuid, r_name, s_name))
+
+ return r_name, s_name, s_props
+
+ def _resource_name_volnr_for_volume(self, volume, empty_ok=False):
+ res, vol, _, _ = self._res_and_vl_data_for_volume(volume, empty_ok)
+ return res, vol
+
+ def local_path(self, volume):
+ dres, dvol = self._resource_name_volnr_for_volume(volume)
+
+ res, data = self.call_or_reconnect(self.odm.text_query,
+ [dm_const.TQ_GET_PATH,
+ dres,
+ str(dvol)])
+ self._check_result(res)
+ if len(data) == 1:
+ return data[0]
+ message = _('Got bad path information from DRBDmanage! (%s)') % data
+ raise exception.VolumeBackendAPIException(data=message)
+
+ def create_volume(self, volume):
+ """Creates a DRBD resource.
+ We address it later on via the ID that gets stored
+ as a private property.
+ """
+
+ # TODO(PM): consistency groups
+ dres = self.is_clean_volume_name(volume['id'])
+
+ LOG.debug("create vol: make %s" % dres)
+ res = self.call_or_reconnect(self.odm.create_resource,
+ dres,
+ EMPTY_LIST)
+ exist = self._check_result(res, ignore=[dm_exc.DM_EEXIST], ret=None)
+ if exist == dm_exc.DM_EEXIST:
+ # Volume already exists, eg. because deploy gave an error
+ # on a previous try (like ENOSPC)
+ pass
+ else:
+
+ props = self._priv_hash_from_volume(volume)
+ # TODO(PM): properties - redundancy, etc
+ res = self.call_or_reconnect(self.odm.create_volume,
+ dres,
+ self._vol_size_to_dm(volume['size']),
+ props)
+ self._check_result(res)
+
+ res = self.call_or_reconnect(self.odm.auto_deploy,
+ dres, self.drbdmanage_redundancy,
+ 0, True)
+ self._check_result(res)
+
+ return 0
+
+ def delete_volume(self, volume):
+ """Deletes a resource."""
+ dres, dvol = self._resource_name_volnr_for_volume(
+ volume,
+ empty_ok=True)
+
+ if not dres:
+ # OK, already gone.
+ return 0
+
+ # TODO(PM): check if in use? Ask whether Primary, or just check result?
+ res = self.call_or_reconnect(self.odm.remove_volume, dres, dvol, False)
+ return self._check_result(res, ignore=[dm_exc.DM_ENOENT])
+ # TODO(PM): delete resource if empty?
+
+ def create_volume_from_snapshot(self, volume, snapshot):
+ """Creates a volume from a snapshot."""
+
+ LOG.debug("create vol from snap: from %s make %s" %
+ (snapshot['id'], volume['id']))
+ # TODO(PM): Consistency groups.
+ dres, sname, sprop = self._resource_and_snap_data_from_snapshot(
+ snapshot)
+
+ new_res = self.is_clean_volume_name(volume['id'])
+
+ r_props = EMPTY_LIST
+ v_props = self._priv_hash_from_volume(volume)
+
+ res = self.call_or_reconnect(self.odm.restore_snapshot,
+ new_res,
+ dres,
+ sname,
+ r_props,
+ v_props)
+ return self._check_result(res, ignore=[dm_exc.DM_ENOENT])
+
+ def create_cloned_volume(self, volume, src_vref):
+ temp_id = self._clean_uuid()
+ snapshot = {'id': temp_id}
+
+ self.create_snapshot(dict(snapshot.items() +
+ [('volume_id', src_vref['id'])]))
+
+ self.create_volume_from_snapshot(volume, snapshot)
+
+ self.delete_snapshot(snapshot)
+
+ def _update_volume_stats(self):
+ LOG.debug("Updating volume stats")
+
+ data = {}
+
+ data["vendor_name"] = 'LINBIT'
+ data["vendor_name"] = 'Open Source'
+ data["driver_version"] = self.VERSION
+ data["storage_protocol"] = "iSCSI"
+ # This has to match the name set in the cinder volume driver spec,
+ # so keep it lowercase
+ data["volume_backend_name"] = "drbdmanage"
+ data["pools"] = []
+
+ res, free, total = self.call_or_reconnect(self.odm.cluster_free_query,
+ self.drbdmanage_redundancy)
+ self._check_result(res)
+
+ location_info =\
+ ('DrbdManageDriver:%(cvol)s:%(dbus)s' %
+ {'cvol': self.dm_control_vol,
+ 'dbus': self.drbdmanage_dbus_name})
+
+ # TODO(PM): multiple DRBDmanage instances and/or multiple pools
+ single_pool = {}
+ single_pool.update(dict(
+ pool_name=data["volume_backend_name"],
+ free_capacity_gb=self._vol_size_to_cinder(free),
+ total_capacity_gb=self._vol_size_to_cinder(total),
+ reserved_percentage=self.configuration.reserved_percentage,
+ location_info=location_info,
+ QoS_support=False))
+
+ data["pools"].append(single_pool)
+
+ self._stats = data
+
+ def get_volume_stats(self, refresh=False):
+ """Get volume status.
+
+ If 'refresh' is True, run update the stats first.
+ """
+
+ if refresh:
+ self._update_volume_stats()
+ return self._stats
+
+ def extend_volume(self, volume, new_size):
+ dres, dvol = self._resource_name_volnr_for_volume(volume)
+
+ res = self.call_or_reconnect(self.odm.resize_volume,
+ dres, dvol, -1,
+ {"size": self._vol_size_to_dm(new_size)},
+ 0)
+ self._check_result(res)
+ return 0
+
+ def create_snapshot(self, snapshot):
+ """Creates a snapshot."""
+ sn_name = self.snapshot_name_from_cinder_snapshot(snapshot)
+
+ LOG.debug("create snapshot: from %s make %s" %
+ (snapshot['volume_id'], snapshot['id']))
+ dres, dvol = self._resource_name_volnr_for_volume(
+ snapshot["volume_id"])
+
+ res, data = self.call_or_reconnect(self.odm.list_assignments,
+ EMPTY_LIST,
+ [dres],
+ 0,
+ EMPTY_LIST,
+ EMPTY_LIST)
+ self._check_result(res)
+
+ nodes = map(lambda d: d[0], data)
+ if len(nodes) < 1:
+ raise exception.VolumeBackendAPIException(
+ _('Snapshot res "%s" that is not deployed anywhere?') %
+ (dres))
+
+ props = self._priv_hash_from_volume(snapshot)
+ res = self.call_or_reconnect(self.odm.create_snapshot,
+ dres, sn_name, nodes, props)
+ return self._check_result(res)
+
+ def delete_snapshot(self, snapshot):
+ """Deletes a snapshot."""
+
+ force = False # during testing
+ dres, sname, _ = self._resource_and_snap_data_from_snapshot(
+ snapshot, empty_ok=not force)
+
+ if not dres:
+ # resource already gone?
+ if force:
+ return 0
+ raise exception.VolumeBackendAPIException(
+ _('Resource "%(res)s" for snapshot "%(sn)s" not found') %
+ {"res": dres, "sn": sname})
+
+ res = self.call_or_reconnect(self.odm.remove_snapshot,
+ dres, sname, force)
+ return self._check_result(res, ignore=[dm_exc.DM_ENOENT])
+
+ # ####### Interface methods for DataPath (Target Driver) ########
+
+ def ensure_export(self, context, volume):
+ return self.target_driver.ensure_export(
+ context,
+ volume,
+ volume_path=self.local_path(volume))
+
+ def create_export(self, context, volume):
+ export_info = self.target_driver.create_export(
+ context,
+ volume, volume_path=self.local_path(volume))
+ return {'provider_location': export_info['location'],
+ 'provider_auth': export_info['auth'], }
+
+ def remove_export(self, context, volume):
+ return self.target_driver.remove_export(context, volume)
+
+ def initialize_connection(self, volume, connector):
+ return self.target_driver.initialize_connection(volume, connector)
+
+ def validate_connector(self, connector):
+ return self.target_driver.validate_connector(connector)
+
+ def terminate_connection(self, volume, connector, **kwargs):
+ return None