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
This commit is contained in:
Philipp Reisner 2015-11-30 10:41:39 +01:00
parent 782c10207c
commit ebce3c376c
3 changed files with 147 additions and 0 deletions

View File

@ -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

View File

@ -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

View File

@ -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