From 8e6a32a440aa907ca81dbbc8bfb6cfc91f760a3d Mon Sep 17 00:00:00 2001 From: Sean McGinnis Date: Wed, 6 Sep 2017 09:12:34 -0500 Subject: [PATCH] Remove Blockbridge volume driver This driver was marked as deprecated and unsupported in Pike due to lack of CI. There has been no change in CI status, therefore per our CI policy it is now being removed. Change-Id: I31f8ecafe23c5144b11240b8bba0cd94e001bbe4 --- cinder/opts.py | 3 - .../unit/volume/drivers/test_blockbridge.py | 582 ----------------- cinder/volume/drivers/blockbridge.py | 604 ------------------ .../drivers/blockbridge-eps-driver.rst | 244 ------- .../block-storage/volume-drivers.rst | 1 - .../tables/cinder-blockbridge.inc | 36 -- ...ueens-driver-removal-72a1a36689b6d890.yaml | 1 + 7 files changed, 1 insertion(+), 1470 deletions(-) delete mode 100644 cinder/tests/unit/volume/drivers/test_blockbridge.py delete mode 100644 cinder/volume/drivers/blockbridge.py delete mode 100644 doc/source/configuration/block-storage/drivers/blockbridge-eps-driver.rst delete mode 100644 doc/source/configuration/tables/cinder-blockbridge.inc diff --git a/cinder/opts.py b/cinder/opts.py index 3bfbdb29587..8cb685bd9a5 100644 --- a/cinder/opts.py +++ b/cinder/opts.py @@ -70,8 +70,6 @@ from cinder import ssh_utils as cinder_sshutils from cinder.transfer import api as cinder_transfer_api from cinder.volume import api as cinder_volume_api from cinder.volume import driver as cinder_volume_driver -from cinder.volume.drivers import blockbridge as \ - cinder_volume_drivers_blockbridge from cinder.volume.drivers.coprhd import common as \ cinder_volume_drivers_coprhd_common from cinder.volume.drivers.coprhd import scaleio as \ @@ -297,7 +295,6 @@ def list_opts(): itertools.chain( cinder_volume_driver.volume_opts, cinder_volume_driver.iser_opts, - cinder_volume_drivers_blockbridge.blockbridge_opts, cinder_volume_drivers_coprhd_common.volume_opts, cinder_volume_drivers_coprhd_scaleio.scaleio_opts, cinder_volume_drivers_datera_dateraiscsi.d_opts, diff --git a/cinder/tests/unit/volume/drivers/test_blockbridge.py b/cinder/tests/unit/volume/drivers/test_blockbridge.py deleted file mode 100644 index 86660a238b2..00000000000 --- a/cinder/tests/unit/volume/drivers/test_blockbridge.py +++ /dev/null @@ -1,582 +0,0 @@ -# Copyright 2015 Blockbridge Networks, LLC. -# -# 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. -""" -Blockbridge EPS iSCSI Volume Driver Tests -""" - -import base64 - -import mock -from oslo_serialization import jsonutils -from oslo_utils import units -import six -from six.moves import http_client -from six.moves import urllib - -from cinder import context -from cinder import exception -from cinder import test -from cinder.volume import configuration as conf -import cinder.volume.drivers.blockbridge as bb - - -DEFAULT_POOL_NAME = "OpenStack" -DEFAULT_POOL_QUERY = "+openstack" - -FIXTURE_VOL_EXPORT_OK = """{ - "target_ip":"127.0.0.1", - "target_port":3260, - "target_iqn":"iqn.2009-12.com.blockbridge:t-pjxczxh-t001", - "target_lun":0, - "initiator_login":"mock-user-abcdef123456" -} -""" - -POOL_STATS_WITHOUT_USAGE = { - 'driver_version': '1.3.0', - 'pools': [{ - 'filter_function': None, - 'free_capacity_gb': 'unknown', - 'goodness_function': None, - 'location_info': 'BlockbridgeDriver:unknown:OpenStack', - 'max_over_subscription_ratio': None, - 'pool_name': 'OpenStack', - 'thin_provisioning_support': True, - 'reserved_percentage': 0, - 'total_capacity_gb': 'unknown'}, - ], - 'storage_protocol': 'iSCSI', - 'vendor_name': 'Blockbridge', - 'volume_backend_name': 'BlockbridgeISCSIDriver', -} - - -def common_mocks(f): - """Decorator to set mocks common to all tests. - - The point of doing these mocks here is so that we don't accidentally set - mocks that can't/don't get unset. - """ - def _common_inner_inner1(inst, *args, **kwargs): - @mock.patch("six.moves.http_client.HTTPSConnection", autospec=True) - def _common_inner_inner2(mock_conn): - inst.mock_httplib = mock_conn - inst.mock_conn = mock_conn.return_value - inst.mock_response = mock.Mock() - - inst.mock_response.read.return_value = '{}' - inst.mock_response.status = http_client.OK - - inst.mock_conn.request.return_value = True - inst.mock_conn.getresponse.return_value = inst.mock_response - - return f(inst, *args, **kwargs) - - return _common_inner_inner2() - - return _common_inner_inner1 - - -class BlockbridgeISCSIDriverTestCase(test.TestCase): - - def setUp(self): - super(BlockbridgeISCSIDriverTestCase, self).setUp() - - self.cfg = mock.Mock(spec=conf.Configuration) - self.cfg.blockbridge_api_host = 'ut-api.blockbridge.com' - self.cfg.blockbridge_api_port = None - self.cfg.blockbridge_auth_scheme = 'token' - self.cfg.blockbridge_auth_token = '0//kPIw7Ck7PUkPSKY...' - self.cfg.blockbridge_pools = {DEFAULT_POOL_NAME: DEFAULT_POOL_QUERY} - self.cfg.blockbridge_default_pool = None - self.cfg.filter_function = None - self.cfg.goodness_function = None - - def _cfg_safe_get(arg): - return getattr(self.cfg, arg, None) - - self.cfg.safe_get.side_effect = _cfg_safe_get - - mock_exec = mock.Mock() - mock_exec.return_value = ('', '') - - self.real_client = bb.BlockbridgeAPIClient(configuration=self.cfg) - self.mock_client = mock.Mock(wraps=self.real_client) - - self.driver = bb.BlockbridgeISCSIDriver(execute=mock_exec, - client=self.mock_client, - configuration=self.cfg) - - self.user_id = '2c13bc8ef717015fda1e12e70dab24654cb6a6da' - self.project_id = '62110b9d37f1ff3ea1f51e75812cb92ed9a08b28' - - self.volume_name = u'testvol-1' - self.volume_id = '6546b9e9-1980-4241-a4e9-0ad9d382c032' - self.volume_size = 1 - self.volume = dict( - name=self.volume_name, - size=self.volume_size, - id=self.volume_id, - user_id=self.user_id, - project_id=self.project_id, - host='fake-host') - - self.snapshot_name = u'testsnap-1' - self.snapshot_id = '207c12af-85a7-4da6-8d39-a7457548f965' - self.snapshot = dict( - volume_name=self.volume_name, - name=self.snapshot_name, - id=self.snapshot_id, - volume_id='55ff8a46-c35f-4ca3-9991-74e1697b220e', - user_id=self.user_id, - project_id=self.project_id) - - self.connector = dict( - initiator='iqn.1994-05.com.redhat:6a528422b61') - - self.driver.do_setup(context.get_admin_context()) - - @common_mocks - def test_http_mock_success(self): - self.mock_response.read.return_value = '{}' - self.mock_response.status = http_client.OK - - conn = http_client.HTTPSConnection('whatever', None) - conn.request('GET', '/blah', '{}', {}) - rsp = conn.getresponse() - - self.assertEqual('{}', rsp.read()) - self.assertEqual(http_client.OK, rsp.status) - - @common_mocks - def test_http_mock_failure(self): - mock_body = '{"error": "no results matching query", "status": 413}' - - self.mock_response.read.return_value = mock_body - self.mock_response.status = http_client.REQUEST_ENTITY_TOO_LARGE - - conn = http_client.HTTPSConnection('whatever', None) - conn.request('GET', '/blah', '{}', {}) - rsp = conn.getresponse() - - self.assertEqual(mock_body, rsp.read()) - self.assertEqual(http_client.REQUEST_ENTITY_TOO_LARGE, rsp.status) - - @common_mocks - def test_cfg_api_host(self): - with mock.patch.object(self.cfg, 'blockbridge_api_host', 'test.host'): - self.driver.get_volume_stats(True) - self.mock_httplib.assert_called_once_with('test.host', None) - - @common_mocks - def test_cfg_api_port(self): - with mock.patch.object(self.cfg, 'blockbridge_api_port', 1234): - self.driver.get_volume_stats(True) - self.mock_httplib.assert_called_once_with( - self.cfg.blockbridge_api_host, 1234) - - @common_mocks - def test_cfg_api_auth_scheme_password(self): - self.cfg.blockbridge_auth_scheme = 'password' - self.cfg.blockbridge_auth_user = 'mock-user' - self.cfg.blockbridge_auth_password = 'mock-password' - with mock.patch.object(self.driver, 'hostname', 'mock-hostname'): - self.driver.get_volume_stats(True) - - creds = "%s:%s" % (self.cfg.blockbridge_auth_user, - self.cfg.blockbridge_auth_password) - if six.PY3: - creds = creds.encode('utf-8') - b64_creds = base64.encodestring(creds).decode('ascii') - else: - b64_creds = base64.encodestring(creds) - - params = dict( - hostname='mock-hostname', - version=self.driver.VERSION, - backend_name='BlockbridgeISCSIDriver', - pool='OpenStack', - query='+openstack') - - headers = { - 'Accept': 'application/vnd.blockbridge-3+json', - 'Authorization': "Basic %s" % b64_creds.replace("\n", ""), - 'User-Agent': "cinder-volume/%s" % self.driver.VERSION, - } - - self.mock_conn.request.assert_called_once_with( - 'GET', mock.ANY, None, headers) - # Parse the URL instead of comparing directly both URLs. - # On Python 3, parameters are formatted in a random order because - # of the hash randomization. - conn_url = self.mock_conn.request.call_args[0][1] - conn_params = dict(urllib.parse.parse_qsl(conn_url.split("?", 1)[1])) - self.assertTrue(conn_url.startswith("/api/cinder/status?"), - repr(conn_url)) - self.assertEqual(params, conn_params) - - @common_mocks - def test_create_volume(self): - self.driver.create_volume(self.volume) - - url = "/volumes/%s" % self.volume_id - create_params = dict( - name=self.volume_name, - query=DEFAULT_POOL_QUERY, - capacity=self.volume_size * units.Gi) - - kwargs = dict( - method='PUT', - params=create_params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - full_url = "/api/cinder" + url - tsk_header = "ext_auth=keystone/%(project_id)s/%(user_id)s" % kwargs - authz_header = "Bearer %s" % self.cfg.blockbridge_auth_token - headers = { - 'X-Blockbridge-Task': tsk_header, - 'Accept': 'application/vnd.blockbridge-3+json', - 'Content-Type': 'application/json', - 'Authorization': authz_header, - 'User-Agent': "cinder-volume/%s" % self.driver.VERSION, - } - - # This is split up because assert_called_once_with won't handle - # randomly ordered dictionaries. - args, kwargs = self.mock_conn.request.call_args - self.assertEqual(args[0], 'PUT') - self.assertEqual(args[1], full_url) - self.assertDictEqual(jsonutils.loads(args[2]), create_params) - self.assertDictEqual(args[3], headers) - - @common_mocks - def test_create_volume_no_results(self): - mock_body = '{"message": "no results matching query", "status": 413}' - - self.mock_response.read.return_value = mock_body - self.mock_response.status = http_client.REQUEST_ENTITY_TOO_LARGE - - self.assertRaisesRegex(exception.VolumeBackendAPIException, - "no results matching query", - self.driver.create_volume, - self.volume) - - create_params = dict( - name=self.volume_name, - query=DEFAULT_POOL_QUERY, - capacity=self.volume_size * units.Gi) - - kwargs = dict( - method='PUT', - params=create_params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with( - "/volumes/%s" % self.volume_id, **kwargs) - - @common_mocks - def test_create_volume_from_snapshot(self): - self.driver.create_volume_from_snapshot(self.volume, self.snapshot) - - vol_src = dict( - snapshot_id=self.snapshot_id, - volume_id=self.snapshot['volume_id']) - create_params = dict( - name=self.volume_name, - capacity=self.volume_size * units.Gi, - src=vol_src) - kwargs = dict( - method='PUT', - params=create_params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with( - "/volumes/%s" % self.volume_id, **kwargs) - - @common_mocks - def test_create_volume_from_snapshot_overquota(self): - mock_body = '{"message": "over quota", "status": 413}' - - self.mock_response.read.return_value = mock_body - self.mock_response.status = http_client.REQUEST_ENTITY_TOO_LARGE - - self.assertRaisesRegex(exception.VolumeBackendAPIException, - "over quota", - self.driver.create_volume_from_snapshot, - self.volume, - self.snapshot) - - vol_src = dict( - snapshot_id=self.snapshot_id, - volume_id=self.snapshot['volume_id']) - create_params = dict( - name=self.volume_name, - capacity=self.volume_size * units.Gi, - src=vol_src) - kwargs = dict( - method='PUT', - params=create_params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with( - "/volumes/%s" % self.volume_id, **kwargs) - - @common_mocks - def test_create_cloned_volume(self): - src_vref = dict( - name='cloned_volume_source', - size=self.volume_size, - id='5d734467-5d77-461c-b5ac-5009dbeaa5d5', - user_id=self.user_id, - project_id=self.project_id) - - self.driver.create_cloned_volume(self.volume, src_vref) - - create_params = dict( - name=self.volume_name, - capacity=self.volume_size * units.Gi, - src=dict(volume_id=src_vref['id'])) - kwargs = dict( - method='PUT', - params=create_params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with( - "/volumes/%s" % self.volume_id, **kwargs) - - @common_mocks - def test_create_cloned_volume_overquota(self): - mock_body = '{"message": "over quota", "status": 413}' - - self.mock_response.read.return_value = mock_body - self.mock_response.status = http_client.REQUEST_ENTITY_TOO_LARGE - - src_vref = dict( - name='cloned_volume_source', - size=self.volume_size, - id='5d734467-5d77-461c-b5ac-5009dbeaa5d5', - user_id=self.user_id, - project_id=self.project_id) - - self.assertRaisesRegex(exception.VolumeBackendAPIException, - "over quota", - self.driver.create_cloned_volume, - self.volume, - src_vref) - - create_params = dict( - name=self.volume_name, - capacity=self.volume_size * units.Gi, - src=dict(volume_id=src_vref['id'])) - kwargs = dict( - method='PUT', - params=create_params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with( - "/volumes/%s" % self.volume_id, **kwargs) - - @common_mocks - def test_extend_volume(self): - self.driver.extend_volume(self.volume, 2) - - url = "/volumes/%s" % self.volume_id - kwargs = dict( - action='grow', - method='POST', - params=dict(capacity=(2 * units.Gi)), - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - @common_mocks - def test_extend_volume_overquota(self): - mock_body = '{"message": "over quota", "status": 413}' - self.mock_response.read.return_value = mock_body - self.mock_response.status = http_client.REQUEST_ENTITY_TOO_LARGE - - self.assertRaisesRegex(exception.VolumeBackendAPIException, - "over quota", - self.driver.extend_volume, - self.volume, - 2) - - url = "/volumes/%s" % self.volume_id - kwargs = dict( - action='grow', - method='POST', - params=dict(capacity=(2 * units.Gi)), - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - @common_mocks - def test_delete_volume(self): - self.driver.delete_volume(self.volume) - - url = "/volumes/%s" % self.volume_id - kwargs = dict( - method='DELETE', - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - @common_mocks - def test_create_snapshot(self): - self.driver.create_snapshot(self.snapshot) - - url = "/volumes/%s/snapshots/%s" % (self.snapshot['volume_id'], - self.snapshot['id']) - create_params = dict( - name=self.snapshot_name) - kwargs = dict( - method='PUT', - params=create_params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - @common_mocks - def test_create_snapshot_overquota(self): - mock_body = '{"message": "over quota", "status": 413}' - self.mock_response.read.return_value = mock_body - self.mock_response.status = http_client.REQUEST_ENTITY_TOO_LARGE - - self.assertRaisesRegex(exception.VolumeBackendAPIException, - "over quota", - self.driver.create_snapshot, - self.snapshot) - - url = "/volumes/%s/snapshots/%s" % (self.snapshot['volume_id'], - self.snapshot['id']) - create_params = dict( - name=self.snapshot_name) - kwargs = dict( - method='PUT', - params=create_params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - @common_mocks - def test_delete_snapshot(self): - self.driver.delete_snapshot(self.snapshot) - - url = "/volumes/%s/snapshots/%s" % (self.snapshot['volume_id'], - self.snapshot['id']) - kwargs = dict( - method='DELETE', - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - @common_mocks - @mock.patch('cinder.volume.utils.generate_username') - @mock.patch('cinder.volume.utils.generate_password') - def test_initialize_connection(self, - mock_generate_password, - mock_generate_username): - mock_generate_username.return_value = 'mock-user-abcdef123456' - mock_generate_password.return_value = 'mock-password-abcdef123456' - - self.mock_response.read.return_value = FIXTURE_VOL_EXPORT_OK - self.mock_response.status = http_client.OK - - props = self.driver.initialize_connection(self.volume, self.connector) - - expected_props = dict( - driver_volume_type="iscsi", - data=dict( - auth_method="CHAP", - auth_username='mock-user-abcdef123456', - auth_password='mock-password-abcdef123456', - target_discovered=False, - target_iqn="iqn.2009-12.com.blockbridge:t-pjxczxh-t001", - target_lun=0, - target_portal="127.0.0.1:3260", - volume_id=self.volume_id)) - - self.assertEqual(expected_props, props) - - ini_name = urllib.parse.quote(self.connector["initiator"], "") - url = "/volumes/%s/exports/%s" % (self.volume_id, ini_name) - params = dict( - chap_user="mock-user-abcdef123456", - chap_secret="mock-password-abcdef123456") - kwargs = dict( - method='PUT', - params=params, - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - @common_mocks - def test_terminate_connection(self): - self.driver.terminate_connection(self.volume, self.connector) - - ini_name = urllib.parse.quote(self.connector["initiator"], "") - url = "/volumes/%s/exports/%s" % (self.volume_id, ini_name) - kwargs = dict( - method='DELETE', - user_id=self.user_id, - project_id=self.project_id) - - self.mock_client.submit.assert_called_once_with(url, **kwargs) - - @common_mocks - def test_get_volume_stats_without_usage(self): - with mock.patch.object(self.driver, 'hostname', 'mock-hostname'): - self.driver.get_volume_stats(True) - - p = { - 'query': '+openstack', - 'pool': 'OpenStack', - 'hostname': 'mock-hostname', - 'version': '1.3.0', - 'backend_name': 'BlockbridgeISCSIDriver', - } - - self.mock_client.submit.assert_called_once_with('/status', params=p) - self.assertEqual(POOL_STATS_WITHOUT_USAGE, self.driver._stats) - - @common_mocks - def test_get_volume_stats_forbidden(self): - self.mock_response.status = http_client.FORBIDDEN - self.assertRaisesRegex(exception.NotAuthorized, - "Insufficient privileges", - self.driver.get_volume_stats, - True) - - @common_mocks - def test_get_volume_stats_unauthorized(self): - self.mock_response.status = http_client.UNAUTHORIZED - self.assertRaisesRegex(exception.NotAuthorized, - "Invalid credentials", - self.driver.get_volume_stats, - True) diff --git a/cinder/volume/drivers/blockbridge.py b/cinder/volume/drivers/blockbridge.py deleted file mode 100644 index 84936bca8d1..00000000000 --- a/cinder/volume/drivers/blockbridge.py +++ /dev/null @@ -1,604 +0,0 @@ -# Copyright 2013-2015 Blockbridge Networks, LLC. -# -# 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. -""" -Blockbridge EPS iSCSI Volume Driver -""" - -import base64 -import socket - -from oslo_config import cfg -from oslo_log import log as logging -from oslo_serialization import jsonutils -from oslo_utils import units -import six -from six.moves import http_client -from six.moves import urllib - -from cinder import context -from cinder import exception -from cinder.i18n import _ -from cinder import interface -from cinder.volume import configuration -from cinder.volume import driver -from cinder.volume import utils as volume_utils - -LOG = logging.getLogger(__name__) - -blockbridge_opts = [ - cfg.StrOpt("blockbridge_api_host", - help="IP address/hostname of Blockbridge API."), - cfg.IntOpt("blockbridge_api_port", - help="Override HTTPS port to connect to Blockbridge " - "API server."), - cfg.StrOpt("blockbridge_auth_scheme", - default='token', - choices=['token', 'password'], - help="Blockbridge API authentication scheme (token " - "or password)"), - cfg.StrOpt("blockbridge_auth_token", - help="Blockbridge API token (for auth scheme 'token')", - secret=True), - cfg.StrOpt("blockbridge_auth_user", - help="Blockbridge API user (for auth scheme 'password')"), - cfg.StrOpt("blockbridge_auth_password", - help="Blockbridge API password (for auth scheme 'password')", - secret=True), - cfg.DictOpt("blockbridge_pools", - default={'OpenStack': '+openstack'}, - help="Defines the set of exposed pools and their associated " - "backend query strings"), - cfg.StrOpt("blockbridge_default_pool", - help="Default pool name if unspecified."), -] - -CONF = cfg.CONF -CONF.register_opts(blockbridge_opts, group=configuration.SHARED_CONF_GROUP) - - -class BlockbridgeAPIClient(object): - _api_cfg = None - - def __init__(self, configuration=None): - self.configuration = configuration - - def _get_api_cfg(self): - if self._api_cfg: - # return cached configuration - return self._api_cfg - - if self.configuration.blockbridge_auth_scheme == 'password': - user = self.configuration.safe_get('blockbridge_auth_user') - pw = self.configuration.safe_get('blockbridge_auth_password') - creds = "%s:%s" % (user, pw) - if six.PY3: - creds = creds.encode('utf-8') - b64_creds = base64.encodestring(creds).decode('ascii') - else: - b64_creds = base64.encodestring(creds) - authz = "Basic %s" % b64_creds.replace("\n", "") - elif self.configuration.blockbridge_auth_scheme == 'token': - token = self.configuration.blockbridge_auth_token or '' - authz = "Bearer %s" % token - - # set and return cached api cfg - self._api_cfg = { - 'host': self.configuration.blockbridge_api_host, - 'port': self.configuration.blockbridge_api_port, - 'base_url': '/api/cinder', - 'default_headers': { - 'User-Agent': ("cinder-volume/%s" % - BlockbridgeISCSIDriver.VERSION), - 'Accept': 'application/vnd.blockbridge-3+json', - 'Authorization': authz, - }, - } - - return self._api_cfg - - def submit(self, rel_url, method='GET', params=None, user_id=None, - project_id=None, req_id=None, action=None, **kwargs): - """Submit a request to the configured API endpoint.""" - - cfg = self._get_api_cfg() - if cfg is None: - msg = _("Failed to determine blockbridge API configuration") - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - # alter the url appropriately if an action is requested - if action: - rel_url += "/actions/%s" % action - - headers = cfg['default_headers'].copy() - url = cfg['base_url'] + rel_url - body = None - - # include user, project and req-id, if supplied - tsk_ctx = [] - if user_id and project_id: - tsk_ctx.append("ext_auth=keystone/%s/%s" % (project_id, user_id)) - if req_id: - tsk_ctx.append("id=%s", req_id) - - if tsk_ctx: - headers['X-Blockbridge-Task'] = ','.join(tsk_ctx) - - # encode params based on request method - if method in ['GET', 'DELETE']: - # For GET method add parameters to the URL - if params: - url += '?' + urllib.parse.urlencode(params) - elif method in ['POST', 'PUT', 'PATCH']: - body = jsonutils.dumps(params) - headers['Content-Type'] = 'application/json' - else: - raise exception.UnknownCmd(cmd=method) - - # connect and execute the request - connection = http_client.HTTPSConnection(cfg['host'], cfg['port']) - connection.request(method, url, body, headers) - response = connection.getresponse() - - # read response data - rsp_body = response.read() - rsp_data = jsonutils.loads(rsp_body) - - connection.close() - - code = response.status - if code in [200, 201, 202, 204]: - pass - elif code == 401: - raise exception.NotAuthorized(_("Invalid credentials")) - elif code == 403: - raise exception.NotAuthorized(_("Insufficient privileges")) - else: - raise exception.VolumeBackendAPIException(data=rsp_data['message']) - - return rsp_data - - -@interface.volumedriver -class BlockbridgeISCSIDriver(driver.ISCSIDriver): - """Manages volumes hosted on Blockbridge EPS.""" - - VERSION = '1.3.0' - - # ThirdPartySystems wiki page - CI_WIKI_NAME = "Blockbridge_EPS_CI" - - # TODO(smcginnis) Either remove this if CI requirements are met, or - # remove this driver in the Queens release per normal deprecation - SUPPORTED = False - - def __init__(self, *args, **kwargs): - super(BlockbridgeISCSIDriver, self).__init__(*args, **kwargs) - - self.client = kwargs.get('client', None) or ( - BlockbridgeAPIClient(configuration=self.configuration)) - - self.configuration.append_config_values(blockbridge_opts) - self.hostname = socket.gethostname() - - def do_setup(self, context): - """Set up the Blockbridge volume driver.""" - pass - - def check_for_setup_error(self): - """Verify configuration is valid.""" - - # ensure the host is configured - if self.configuration.safe_get('blockbridge_api_host') is None: - raise exception.InvalidInput( - reason=_("Blockbridge api host not configured")) - - # ensure the auth scheme is valid and has the necessary configuration. - auth_scheme = self.configuration.safe_get("blockbridge_auth_scheme") - - if auth_scheme == 'password': - auth_user = self.configuration.safe_get('blockbridge_auth_user') - auth_pw = self.configuration.safe_get('blockbridge_auth_password') - if auth_user is None: - raise exception.InvalidInput( - reason=_("Blockbridge user not configured (required for " - "auth scheme 'password')")) - if auth_pw is None: - raise exception.InvalidInput( - reason=_("Blockbridge password not configured (required " - "for auth scheme 'password')")) - elif auth_scheme == 'token': - token = self.configuration.safe_get('blockbridge_auth_token') - if token is None: - raise exception.InvalidInput( - reason=_("Blockbridge token not configured (required " - "for auth scheme 'token')")) - else: - raise exception.InvalidInput( - reason=(_("Blockbridge configured with invalid auth scheme " - "'%(auth_scheme)s'") % {'auth_scheme': auth_scheme})) - - # ensure at least one pool is defined - pools = self.configuration.safe_get('blockbridge_pools') - if pools is None: - raise exception.InvalidInput( - reason=_("Blockbridge pools not configured")) - - default_pool = self.configuration.safe_get('blockbridge_default_pool') - if default_pool and default_pool not in pools: - raise exception.InvalidInput( - reason=_("Blockbridge default pool does not exist")) - - def _vol_api_submit(self, vol_id, **kwargs): - vol_id = urllib.parse.quote(vol_id, '') - rel_url = "/volumes/%s" % vol_id - - return self.client.submit(rel_url, **kwargs) - - def _create_volume(self, vol_id, params, **kwargs): - """Execute a backend volume create operation.""" - - self._vol_api_submit(vol_id, method='PUT', params=params, **kwargs) - - def _delete_volume(self, vol_id, **kwargs): - """Execute a backend volume delete operation.""" - - self._vol_api_submit(vol_id, method='DELETE', **kwargs) - - def _extend_volume(self, vol_id, capacity, **kwargs): - """Execute a backend volume grow operation.""" - - params = kwargs.get('params', {}) - params['capacity'] = capacity - - self._vol_api_submit(vol_id, method='POST', action='grow', - params=params, **kwargs) - - def _snap_api_submit(self, vol_id, snap_id, **kwargs): - vol_id = urllib.parse.quote(vol_id, '') - snap_id = urllib.parse.quote(snap_id, '') - rel_url = "/volumes/%s/snapshots/%s" % (vol_id, snap_id) - - return self.client.submit(rel_url, **kwargs) - - def _create_snapshot(self, vol_id, snap_id, params, **kwargs): - """Execute a backend snapshot create operation.""" - - self._snap_api_submit(vol_id, snap_id, method='PUT', - params=params, **kwargs) - - def _delete_snapshot(self, vol_id, snap_id, **kwargs): - """Execute a backend snapshot delete operation.""" - - return self._snap_api_submit(vol_id, snap_id, method='DELETE', - **kwargs) - - def _export_api_submit(self, vol_id, ini_name, **kwargs): - vol_id = urllib.parse.quote(vol_id, '') - ini_name = urllib.parse.quote(ini_name, '') - rel_url = "/volumes/%s/exports/%s" % (vol_id, ini_name) - - return self.client.submit(rel_url, **kwargs) - - def _create_export(self, vol_id, ini_name, params, **kwargs): - """Execute a backend volume export operation.""" - - return self._export_api_submit(vol_id, ini_name, method='PUT', - params=params, **kwargs) - - def _delete_export(self, vol_id, ini_name, **kwargs): - """Remove a previously created volume export.""" - - self._export_api_submit(vol_id, ini_name, method='DELETE', - **kwargs) - - def _get_pool_stats(self, pool, query, **kwargs): - """Retrieve pool statistics and capabilities.""" - - pq = { - 'pool': pool, - 'query': query, - } - pq.update(kwargs) - - return self.client.submit('/status', params=pq) - - def _get_dbref_name(self, ref): - display_name = ref.get('display_name') - if not display_name: - return ref.get('name') - return display_name - - def _get_query_string(self, ctxt, volume): - pools = self.configuration.blockbridge_pools - default_pool = self.configuration.blockbridge_default_pool - explicit_pool = volume_utils.extract_host(volume['host'], 'pool') - - pool_name = explicit_pool or default_pool - if pool_name: - return pools[pool_name] - else: - # no pool specified or defaulted -- just pick whatever comes out of - # the dictionary first. - return list(pools.values())[0] - - def create_volume(self, volume): - """Create a volume on a Blockbridge EPS backend. - - :param volume: volume reference - """ - - ctxt = context.get_admin_context() - create_params = { - 'name': self._get_dbref_name(volume), - 'query': self._get_query_string(ctxt, volume), - 'capacity': int(volume['size'] * units.Gi), - } - - LOG.debug("Provisioning %(capacity)s byte volume " - "with query '%(query)s'", create_params, resource=volume) - - return self._create_volume(volume['id'], - create_params, - user_id=volume['user_id'], - project_id=volume['project_id']) - - def create_cloned_volume(self, volume, src_vref): - """Creates a clone of the specified volume.""" - - create_params = { - 'name': self._get_dbref_name(volume), - 'capacity': int(volume['size'] * units.Gi), - 'src': { - 'volume_id': src_vref['id'], - }, - } - - LOG.debug("Cloning source volume %(id)s", src_vref, resource=volume) - - return self._create_volume(volume['id'], - create_params, - user_id=volume['user_id'], - project_id=volume['project_id']) - - def delete_volume(self, volume): - """Remove an existing volume. - - :param volume: volume reference - """ - - LOG.debug("Removing volume %(id)s", volume, resource=volume) - - return self._delete_volume(volume['id'], - user_id=volume['user_id'], - project_id=volume['project_id']) - - def create_snapshot(self, snapshot): - """Create snapshot of existing volume. - - :param snapshot: shapshot reference - """ - - create_params = { - 'name': self._get_dbref_name(snapshot), - } - - LOG.debug("Creating snapshot of volume %(volume_id)s", snapshot, - resource=snapshot) - - return self._create_snapshot(snapshot['volume_id'], - snapshot['id'], - create_params, - user_id=snapshot['user_id'], - project_id=snapshot['project_id']) - - def create_volume_from_snapshot(self, volume, snapshot): - """Create new volume from existing snapshot. - - :param volume: reference of volume to be created - :param snapshot: reference of source snapshot - """ - - create_params = { - 'name': self._get_dbref_name(volume), - 'capacity': int(volume['size'] * units.Gi), - 'src': { - 'volume_id': snapshot['volume_id'], - 'snapshot_id': snapshot['id'], - }, - } - - LOG.debug("Creating volume from snapshot %(id)s", snapshot, - resource=volume) - - return self._create_volume(volume['id'], - create_params, - user_id=volume['user_id'], - project_id=volume['project_id']) - - def delete_snapshot(self, snapshot): - """Delete volume's snapshot. - - :param snapshot: shapshot reference - """ - - LOG.debug("Deleting snapshot of volume %(volume_id)s", snapshot, - resource=snapshot) - - self._delete_snapshot(snapshot['volume_id'], - snapshot['id'], - user_id=snapshot['user_id'], - project_id=snapshot['project_id']) - - def create_export(self, _ctx, volume, connector): - """Do nothing: target created during instance attachment.""" - pass - - def ensure_export(self, _ctx, volume): - """Do nothing: target created during instance attachment.""" - pass - - def remove_export(self, _ctx, volume): - """Do nothing: target created during instance attachment.""" - pass - - def initialize_connection(self, volume, connector, **kwargs): - """Attach volume to initiator/host. - - Creates a profile for the initiator, and adds the new profile to the - target ACL. - - """ - - # generate a CHAP secret here -- there is no way to retrieve an - # existing CHAP secret over the Blockbridge API, so it must be - # supplied by the volume driver. - export_params = { - 'chap_user': ( - kwargs.get('user', volume_utils.generate_username(16))), - 'chap_secret': ( - kwargs.get('password', volume_utils.generate_password(32))), - } - - LOG.debug("Configuring export for %(initiator)s", connector, - resource=volume) - - rsp = self._create_export(volume['id'], - connector['initiator'], - export_params, - user_id=volume['user_id'], - project_id=volume['project_id']) - - # combine locally generated chap credentials with target iqn/lun to - # present the attach properties. - target_portal = "%s:%s" % (rsp['target_ip'], rsp['target_port']) - - properties = { - 'target_discovered': False, - 'target_portal': target_portal, - 'target_iqn': rsp['target_iqn'], - 'target_lun': rsp['target_lun'], - 'volume_id': volume['id'], - 'auth_method': 'CHAP', - 'auth_username': rsp['initiator_login'], - 'auth_password': export_params['chap_secret'], - } - - LOG.debug("Attach properties: %(properties)s", - {'properties': properties}) - - return { - 'driver_volume_type': 'iscsi', - 'data': properties, - } - - def terminate_connection(self, volume, connector, **kwargs): - """Detach volume from the initiator. - - Removes initiator profile entry from target ACL. - - """ - - LOG.debug("Unconfiguring export for %(initiator)s", connector, - resource=volume) - - self._delete_export(volume['id'], - connector['initiator'], - user_id=volume['user_id'], - project_id=volume['project_id']) - - def extend_volume(self, volume, new_size): - """Extend an existing volume.""" - - capacity = new_size * units.Gi - - LOG.debug("Extending volume to %(capacity)s bytes", - {'capacity': capacity}, resource=volume) - - self._extend_volume(volume['id'], - int(new_size * units.Gi), - user_id=volume['user_id'], - project_id=volume['project_id']) - - def get_volume_stats(self, refresh=False): - if refresh: - self._update_volume_stats() - return self._stats - - def _update_volume_stats(self): - if self.configuration: - cfg_name = self.configuration.safe_get('volume_backend_name') - backend_name = cfg_name or self.__class__.__name__ - - driver_cfg = { - 'hostname': self.hostname, - 'version': self.VERSION, - 'backend_name': backend_name, - } - - filter_function = self.get_filter_function() - goodness_function = self.get_goodness_function() - pools = [] - - LOG.debug("Updating volume driver statistics", - resource={'type': 'driver', 'id': backend_name}) - - for pool_name, query in self.configuration.blockbridge_pools.items(): - stats = self._get_pool_stats(pool_name, query, **driver_cfg) - - system_serial = stats.get('system_serial', 'unknown') - free_capacity = stats.get('free_capacity', None) - total_capacity = stats.get('total_capacity', None) - provisioned_capacity = stats.get('provisioned_capacity', None) - - if free_capacity is None: - free_capacity = 'unknown' - else: - free_capacity = int(free_capacity / units.Gi) - - if total_capacity is None: - total_capacity = 'unknown' - else: - total_capacity = int(total_capacity / units.Gi) - - pool = { - 'pool_name': pool_name, - 'location_info': ('BlockbridgeDriver:%(sys_id)s:%(pool)s' % - {'sys_id': system_serial, - 'pool': pool_name}), - 'max_over_subscription_ratio': ( - self.configuration.safe_get('max_over_subscription_ratio') - ), - 'free_capacity_gb': free_capacity, - 'total_capacity_gb': total_capacity, - 'reserved_percentage': 0, - 'thin_provisioning_support': True, - 'filter_function': filter_function, - 'goodness_function': goodness_function, - } - - if provisioned_capacity is not None: - pool['provisioned_capacity_gb'] = int( - provisioned_capacity / units.Gi - ) - - pools.append(pool) - - self._stats = { - 'volume_backend_name': backend_name, - 'vendor_name': 'Blockbridge', - 'driver_version': self.VERSION, - 'storage_protocol': 'iSCSI', - 'pools': pools, - } diff --git a/doc/source/configuration/block-storage/drivers/blockbridge-eps-driver.rst b/doc/source/configuration/block-storage/drivers/blockbridge-eps-driver.rst deleted file mode 100644 index ac72ffc40f1..00000000000 --- a/doc/source/configuration/block-storage/drivers/blockbridge-eps-driver.rst +++ /dev/null @@ -1,244 +0,0 @@ -=============== -Blockbridge EPS -=============== - -Introduction -~~~~~~~~~~~~ - -Blockbridge is software that transforms commodity infrastructure into -secure multi-tenant storage that operates as a programmable service. It -provides automatic encryption, secure deletion, quality of service (QoS), -replication, and programmable security capabilities on your choice of -hardware. Blockbridge uses micro-segmentation to provide isolation that allows -you to concurrently operate OpenStack, Docker, and bare-metal workflows on -shared resources. When used with OpenStack, isolated management domains are -dynamically created on a per-project basis. All volumes and clones, within and -between projects, are automatically cryptographically isolated and implement -secure deletion. - -Architecture reference -~~~~~~~~~~~~~~~~~~~~~~ - -**Blockbridge architecture** - -.. figure:: ../../figures/bb-cinder-fig1.png - :width: 100% - - -Control paths -------------- - -The Blockbridge driver is packaged with the core distribution of -OpenStack. Operationally, it executes in the context of the Block -Storage service. The driver communicates with an OpenStack-specific API -provided by the Blockbridge EPS platform. Blockbridge optionally -communicates with Identity, Compute, and Block Storage -services. - -Block storage API ------------------ - -Blockbridge is API driven software-defined storage. The system -implements a native HTTP API that is tailored to the specific needs of -OpenStack. Each Block Storage service operation maps to a single -back-end API request that provides ACID semantics. The API is -specifically designed to reduce, if not eliminate, the possibility of -inconsistencies between the Block Storage service and external storage -infrastructure in the event of hardware, software or data center -failure. - -Extended management -------------------- - -OpenStack users may utilize Blockbridge interfaces to manage -replication, auditing, statistics, and performance information on a -per-project and per-volume basis. In addition, they can manage low-level -data security functions including verification of data authenticity and -encryption key delegation. Native integration with the Identity Service -allows tenants to use a single set of credentials. Integration with -Block storage and Compute services provides dynamic metadata mapping -when using Blockbridge management APIs and tools. - -Attribute-based provisioning ----------------------------- - -Blockbridge organizes resources using descriptive identifiers called -*attributes*. Attributes are assigned by administrators of the -infrastructure. They are used to describe the characteristics of storage -in an application-friendly way. Applications construct queries that -describe storage provisioning constraints and the Blockbridge storage -stack assembles the resources as described. - -Any given instance of a Blockbridge volume driver specifies a *query* -for resources. For example, a query could specify -``'+ssd +10.0.0.0 +6nines -production iops.reserve=1000 -capacity.reserve=30%'``. This query is satisfied by selecting SSD -resources, accessible on the 10.0.0.0 network, with high resiliency, for -non-production workloads, with guaranteed IOPS of 1000 and a storage -reservation for 30% of the volume capacity specified at create time. -Queries and parameters are completely administrator defined: they -reflect the layout, resource, and organizational goals of a specific -deployment. - -Supported operations -~~~~~~~~~~~~~~~~~~~~ - -- Create, delete, clone, attach, and detach volumes -- Create and delete volume snapshots -- Create a volume from a snapshot -- Copy an image to a volume -- Copy a volume to an image -- Extend a volume -- Get volume statistics - -Supported protocols -~~~~~~~~~~~~~~~~~~~ - -Blockbridge provides iSCSI access to storage. A unique iSCSI data fabric -is programmatically assembled when a volume is attached to an instance. -A fabric is disassembled when a volume is detached from an instance. -Each volume is an isolated SCSI device that supports persistent -reservations. - -Configuration steps -~~~~~~~~~~~~~~~~~~~ - -.. _cg_create_an_authentication_token: - -Create an authentication token ------------------------------- - -Whenever possible, avoid using password-based authentication. Even if -you have created a role-restricted administrative user via Blockbridge, -token-based authentication is preferred. You can generate persistent -authentication tokens using the Blockbridge command-line tool as -follows: - -.. code-block:: console - - $ bb -H bb-mn authorization create --notes "OpenStack" --restrict none - Authenticating to https://bb-mn/api - - Enter user or access token: system - Password for system: - Authenticated; token expires in 3599 seconds. - - == Authorization: ATH4762894C40626410 - notes OpenStack - serial ATH4762894C40626410 - account system (ACT0762594C40626440) - user system (USR1B62094C40626440) - enabled yes - created at 2015-10-24 22:08:48 +0000 - access type online - token suffix xaKUy3gw - restrict none - - == Access Token - access token 1/elvMWilMvcLAajl...3ms3U1u2KzfaMw6W8xaKUy3gw - - *** Remember to record your access token! - -Create volume type ------------------- - -Before configuring and enabling the Blockbridge volume driver, register -an OpenStack volume type and associate it with a -``volume_backend_name``. In this example, a volume type, 'Production', -is associated with the ``volume_backend_name`` 'blockbridge\_prod': - -.. code-block:: console - - $ openstack volume type create Production - $ openstack volume type set --property volume_backend_name=blockbridge_prod Production - -Specify volume driver ---------------------- - -Configure the Blockbridge volume driver in ``/etc/cinder/cinder.conf``. -Your ``volume_backend_name`` must match the value specified in the -:command:`openstack volume type set` command in the previous step. - -.. code-block:: ini - - volume_driver = cinder.volume.drivers.blockbridge.BlockbridgeISCSIDriver - volume_backend_name = blockbridge_prod - -Specify API endpoint and authentication ---------------------------------------- - -Configure the API endpoint and authentication. The following example -uses an authentication token. You must create your own as described in -:ref:`cg_create_an_authentication_token`. - -.. code-block:: ini - - blockbridge_api_host = [ip or dns of management cluster] - blockbridge_auth_token = 1/elvMWilMvcLAajl...3ms3U1u2KzfaMw6W8xaKUy3gw - -Specify resource query ----------------------- - -By default, a single pool is configured (implied) with a default -resource query of ``'+openstack'``. Within Blockbridge, datastore -resources that advertise the 'openstack' attribute will be selected to -fulfill OpenStack provisioning requests. If you prefer a more specific -query, define a custom pool configuration. - -.. code-block:: ini - - blockbridge_pools = Production: +production +qos iops.reserve=5000 - -Pools support storage systems that offer multiple classes of service. -You may wish to configure multiple pools to implement more sophisticated -scheduling capabilities. - -Configuration options -~~~~~~~~~~~~~~~~~~~~~ - -.. include:: ../../tables/cinder-blockbridge.inc - -.. _cg_configuration_example: - -Configuration example -~~~~~~~~~~~~~~~~~~~~~ - -``cinder.conf`` example file - -.. code-block:: ini - - [Default] - enabled_backends = bb_devel bb_prod - - [bb_prod] - volume_driver = cinder.volume.drivers.blockbridge.BlockbridgeISCSIDriver - volume_backend_name = blockbridge_prod - blockbridge_api_host = [ip or dns of management cluster] - blockbridge_auth_token = 1/elvMWilMvcLAajl...3ms3U1u2KzfaMw6W8xaKUy3gw - blockbridge_pools = Production: +production +qos iops.reserve=5000 - - [bb_devel] - volume_driver = cinder.volume.drivers.blockbridge.BlockbridgeISCSIDriver - volume_backend_name = blockbridge_devel - blockbridge_api_host = [ip or dns of management cluster] - blockbridge_auth_token = 1/elvMWilMvcLAajl...3ms3U1u2KzfaMw6W8xaKUy3gw - blockbridge_pools = Development: +development - -Multiple volume types -~~~~~~~~~~~~~~~~~~~~~ - -Volume *types* are exposed to tenants, *pools* are not. To offer -multiple classes of storage to OpenStack tenants, you should define -multiple volume types. Simply repeat the process above for each desired -type. Be sure to specify a unique ``volume_backend_name`` and pool -configuration for each type. The -:ref:`cinder.conf ` example included with -this documentation illustrates configuration of multiple types. - -Testing resources -~~~~~~~~~~~~~~~~~ - -Blockbridge is freely available for testing purposes and deploys in -seconds as a Docker container. This is the same container used to run -continuous integration for OpenStack. For more information visit -`www.blockbridge.io `__. diff --git a/doc/source/configuration/block-storage/volume-drivers.rst b/doc/source/configuration/block-storage/volume-drivers.rst index aeca4f689df..4ae159cbd22 100644 --- a/doc/source/configuration/block-storage/volume-drivers.rst +++ b/doc/source/configuration/block-storage/volume-drivers.rst @@ -13,7 +13,6 @@ Volume drivers drivers/nfs-volume-driver.rst drivers/sheepdog-driver.rst drivers/smbfs-volume-driver.rst - drivers/blockbridge-eps-driver.rst drivers/cloudbyte-driver.rst drivers/coprhd-driver.rst drivers/datera-volume-driver.rst diff --git a/doc/source/configuration/tables/cinder-blockbridge.inc b/doc/source/configuration/tables/cinder-blockbridge.inc deleted file mode 100644 index f828eab7683..00000000000 --- a/doc/source/configuration/tables/cinder-blockbridge.inc +++ /dev/null @@ -1,36 +0,0 @@ -.. - Warning: Do not edit this file. It is automatically generated from the - software project's code and your changes will be overwritten. - - The tool to generate this file lives in openstack-doc-tools repository. - - Please make any changes needed in the code, then run the - autogenerate-config-doc tool from the openstack-doc-tools repository, or - ask for help on the documentation mailing list, IRC channel or meeting. - -.. _cinder-blockbridge: - -.. list-table:: Description of BlockBridge EPS volume driver configuration options - :header-rows: 1 - :class: config-ref-table - - * - Configuration option = Default value - - Description - * - **[DEFAULT]** - - - * - ``blockbridge_api_host`` = ``None`` - - (String) IP address/hostname of Blockbridge API. - * - ``blockbridge_api_port`` = ``None`` - - (Integer) Override HTTPS port to connect to Blockbridge API server. - * - ``blockbridge_auth_password`` = ``None`` - - (String) Blockbridge API password (for auth scheme 'password') - * - ``blockbridge_auth_scheme`` = ``token`` - - (String) Blockbridge API authentication scheme (token or password) - * - ``blockbridge_auth_token`` = ``None`` - - (String) Blockbridge API token (for auth scheme 'token') - * - ``blockbridge_auth_user`` = ``None`` - - (String) Blockbridge API user (for auth scheme 'password') - * - ``blockbridge_default_pool`` = ``None`` - - (String) Default pool name if unspecified. - * - ``blockbridge_pools`` = ``{'OpenStack': '+openstack'}`` - - (Dict) Defines the set of exposed pools and their associated backend query strings diff --git a/releasenotes/notes/queens-driver-removal-72a1a36689b6d890.yaml b/releasenotes/notes/queens-driver-removal-72a1a36689b6d890.yaml index d0db9aa52a6..677084eba57 100644 --- a/releasenotes/notes/queens-driver-removal-72a1a36689b6d890.yaml +++ b/releasenotes/notes/queens-driver-removal-72a1a36689b6d890.yaml @@ -5,5 +5,6 @@ upgrade: now been removed: * Block device driver + * Blockbridge * Coho