From ebce3c376cb336d1a7a92a143f12099067eca3fe Mon Sep 17 00:00:00 2001 From: Philipp Reisner Date: Mon, 30 Nov 2015 10:41:39 +0100 Subject: [PATCH] DRBD connector class Our DRBD block storage driver for Cinder can be used to make Nova directly connect to the DRBD 9 storage servers, instead of needing an iSCSI hop. Blueprint: https://blueprints.launchpad.net/cinder/+spec/drbd-transport Please see http://drbd.linbit.com/users-guide-9.0/s-openstack-transport-protocol.html for more details Change-Id: I7c02850ba1e9626b99a295c72175815b3dd1bdf3 --- etc/os-brick/rootwrap.d/os-brick.filters | 3 + os_brick/initiator/connector.py | 84 ++++++++++++++++++++++ os_brick/tests/initiator/test_connector.py | 60 ++++++++++++++++ 3 files changed, 147 insertions(+) diff --git a/etc/os-brick/rootwrap.d/os-brick.filters b/etc/os-brick/rootwrap.d/os-brick.filters index eb90ec8b0..18735ebe7 100644 --- a/etc/os-brick/rootwrap.d/os-brick.filters +++ b/etc/os-brick/rootwrap.d/os-brick.filters @@ -58,6 +58,9 @@ drv_cfg: CommandFilter, /opt/emc/scaleio/sdc/bin/drv_cfg, root, /opt/emc/scaleio # initiator/connector.py sds_cli: CommandFilter, /usr/local/bin/sds/sds_cli, root +# initiator/connector.py +drbdadm: CommandFilter, drbdadm, root + # initiator/connector.py: 'vgc-cluster', 'domain-list', '-l' # initiator/connector.py: 'vgc-cluster', 'space-set-apphosts', '-n'... vgc-cluster: CommandFilter, vgc-cluster, root diff --git a/os_brick/initiator/connector.py b/os_brick/initiator/connector.py index aef99cc5f..0796bcf06 100644 --- a/os_brick/initiator/connector.py +++ b/os_brick/initiator/connector.py @@ -30,6 +30,7 @@ import re import requests import socket import sys +import tempfile import time from oslo_concurrency import lockutils @@ -66,6 +67,7 @@ ISCSI = "ISCSI" ISER = "ISER" FIBRE_CHANNEL = "FIBRE_CHANNEL" AOE = "AOE" +DRBD = "DRBD" NFS = "NFS" GLUSTERFS = "GLUSTERFS" LOCAL = "LOCAL" @@ -201,6 +203,11 @@ class InitiatorConnector(executor.Executor): execute=execute, device_scan_attempts=device_scan_attempts, *args, **kwargs) + elif protocol == DRBD: + return DRBDConnector(root_helper=root_helper, + driver=driver, + execute=execute, + *args, **kwargs) elif protocol == LOCAL: return LocalConnector(root_helper=root_helper, driver=driver, @@ -1479,6 +1486,83 @@ class LocalConnector(InitiatorConnector): pass +class DRBDConnector(InitiatorConnector): + """"Connector class to attach/detach DRBD resources.""" + + def __init__(self, root_helper, driver=None, + execute=putils.execute, *args, **kwargs): + + super(DRBDConnector, self).__init__(root_helper, driver=driver, + execute=execute, *args, **kwargs) + + self._execute = execute + self._root_helper = root_helper + + def check_valid_device(self, path, run_as_root=True): + """Verify an existing volume.""" + # TODO(linbit): check via drbdsetup first, to avoid blocking/hanging + # in case of network problems? + + return super(DRBDConnector, self).check_valid_device(path, run_as_root) + + def get_all_available_volumes(self, connection_properties=None): + + base = "/dev/" + blkdev_list = [] + + for e in os.listdir(base): + path = base + e + if os.path.isblk(path): + blkdev_list.append(path) + + return blkdev_list + + def _drbdadm_command(self, cmd, data_dict, sh_secret): + # TODO(linbit): Write that resource file to a permanent location? + tmp = tempfile.NamedTemporaryFile(suffix="res", delete=False, mode="w") + try: + kv = {'shared-secret': sh_secret} + tmp.write(data_dict['config'] % kv) + tmp.close() + + (out, err) = self._execute('drbdadm', cmd, + "-c", tmp.name, + data_dict['name'], + run_as_root=True, + root_helper=self._root_helper) + finally: + os.unlink(tmp.name) + + return (out, err) + + def connect_volume(self, connection_properties): + """Attach the volume.""" + + self._drbdadm_command("adjust", connection_properties, + connection_properties['provider_auth']) + + device_info = { + 'type': 'block', + 'path': connection_properties['device'], + } + + return device_info + + def disconnect_volume(self, connection_properties, device_info): + """Detach the volume.""" + + self._drbdadm_command("down", connection_properties, + connection_properties['provider_auth']) + + def get_volume_paths(self, connection_properties): + path = connection_properties['device'] + return [path] + + def get_search_path(self): + # TODO(linbit): is it allowed to return "/dev", or is that too broad? + return None + + class HuaweiStorHyperConnector(InitiatorConnector): """"Connector class to attach/detach SDSHypervisor volumes.""" attached_success_code = 0 diff --git a/os_brick/tests/initiator/test_connector.py b/os_brick/tests/initiator/test_connector.py index 82d78533a..8d21f9b89 100644 --- a/os_brick/tests/initiator/test_connector.py +++ b/os_brick/tests/initiator/test_connector.py @@ -1756,6 +1756,66 @@ class RBDConnectorTestCase(ConnectorTestCase): self.assertEqual(1, volume_close.call_count) +class DRBDConnectorTestCase(ConnectorTestCase): + + RESOURCE_TEMPLATE = ''' + resource r0 { + on host1 { + } + net { + shared-secret "%(shared-secret)s"; + } + } +''' + + def setUp(self): + super(DRBDConnectorTestCase, self).setUp() + + self.connector = connector.DRBDConnector( + None, execute=self._fake_exec) + + self.execs = [] + + def _fake_exec(self, *cmd, **kwargs): + self.execs.append(cmd) + + # out, err + return ('', '') + + def test_connect_volume(self): + """Test connect_volume.""" + + cprop = { + 'provider_auth': 'my-secret', + 'config': self.RESOURCE_TEMPLATE, + 'name': 'my-precious', + 'device': '/dev/drbd951722', + 'data': {}, + } + + res = self.connector.connect_volume(cprop) + + self.assertEqual(cprop['device'], res['path']) + self.assertEqual('adjust', self.execs[0][1]) + self.assertEqual(cprop['name'], self.execs[0][4]) + + def test_disconnect_volume(self): + """Test the disconnect volume case.""" + + cprop = { + 'provider_auth': 'my-secret', + 'config': self.RESOURCE_TEMPLATE, + 'name': 'my-precious', + 'device': '/dev/drbd951722', + 'data': {}, + } + dev_info = {} + + self.connector.disconnect_volume(cprop, dev_info) + + self.assertEqual('down', self.execs[0][1]) + + class ScaleIOConnectorTestCase(ConnectorTestCase): """Test cases for ScaleIO connector""" # Fake volume information