add IPACL support to the Lightbits Cinder driver

Lightbits storage clusters have the ability to block access to a
volume unless it comes from a specific IP, via a non-standard
extension to NVMe/TCP called "IPACL", which augments the standard
string-based ACLs. This patch adds support for Lightbits IPACL to the
Lightbits Cinder driver. We get the IPs to set from the os_brick
running on the nova node that will be accessing the volumes (see the
corresponding Lightbits connector os_brick patch) and set them when
updating the volume's ACLs on creation and on volume update due to
attachment and disconnection.
os_brick patch: https://review.opendev.org/c/openstack/os-brick/+/903574

Depends-on: Ia22322bb8a8097900d5509b6c540355eb474f19d
Change-Id: Id2540cbb9567f8242c76806318cdde9a80e791e1
This commit is contained in:
Rahman LBL 2023-12-07 10:57:56 +02:00 committed by yuval brave
parent 3d654c7f79
commit 444c18a38f
3 changed files with 209 additions and 21 deletions

View File

@ -63,6 +63,7 @@ FAKE_LIGHTOS_CLUSTER_INFO: Dict[str, str] = {
} }
FAKE_CLIENT_HOSTNQN = "hostnqn1" FAKE_CLIENT_HOSTNQN = "hostnqn1"
FAKE_HOST_IPS = ['10.10.0.1']
VOLUME_BACKEND_NAME = "lightos_backend" VOLUME_BACKEND_NAME = "lightos_backend"
RESERVED_PERCENTAGE = 30 RESERVED_PERCENTAGE = 30
DEVICE_SCAN_ATTEMPTS_DEFAULT = 5 DEVICE_SCAN_ATTEMPTS_DEFAULT = 5
@ -85,6 +86,7 @@ class InitiatorConnectorFactoryMocker:
class InitialConnectorMock: class InitialConnectorMock:
nqn = FAKE_CLIENT_HOSTNQN nqn = FAKE_CLIENT_HOSTNQN
found_discovery_client = True found_discovery_client = True
host_ips = FAKE_HOST_IPS
def get_hostnqn(self): def get_hostnqn(self):
return self.__class__.nqn return self.__class__.nqn
@ -92,15 +94,18 @@ class InitialConnectorMock:
def find_dsc(self): def find_dsc(self):
return self.__class__.found_discovery_client return self.__class__.found_discovery_client
def get_host_ips(self):
return self.__class__.host_ips
def get_connector_properties(self, root): def get_connector_properties(self, root):
return dict(nqn=self.__class__.nqn, return dict(nqn=self.__class__.nqn,
found_dsc=self.__class__.found_discovery_client) found_dsc=self.__class__.found_discovery_client,
host_ips=self.__class__.host_ips)
def get_connector_properties(): def get_connector_properties():
connector = InitialConnectorMock() connector = InitialConnectorMock()
return dict(nqn=connector.get_hostnqn(), return connector.get_connector_properties(None)
found_dsc=connector.find_dsc())
def get_vol_etag(volume): def get_vol_etag(volume):
@ -172,6 +177,9 @@ class DBMock(object):
volume["size"] = kwargs["size"] volume["size"] = kwargs["size"]
if kwargs.get("acl", None): if kwargs.get("acl", None):
volume["acl"] = {'values': kwargs.get('acl')} volume["acl"] = {'values': kwargs.get('acl')}
if kwargs.get("ip_acl", None):
volume["IPAcl"] = {'values': kwargs.get('ip_acl')}
volume["ETag"] = get_vol_etag(volume) volume["ETag"] = get_vol_etag(volume)
return httpstatus.OK, volume return httpstatus.OK, volume
@ -202,7 +210,7 @@ class DBMock(object):
return httpstatus.OK, vol return httpstatus.OK, vol
def update_volume(self, **kwargs): def update_volume(self, **kwargs):
assert ("project_name" in kwargs and kwargs["project_name"]), \ assert "project_name" in kwargs and kwargs["project_name"], \
"project_name must be provided" "project_name must be provided"
def create_snapshot(self, snapshot) -> Tuple[int, Dict]: def create_snapshot(self, snapshot) -> Tuple[int, Dict]:
@ -275,6 +283,7 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
configuration.lightos_default_compression_enabled = ( configuration.lightos_default_compression_enabled = (
DEFAULT_COMPRESSION) DEFAULT_COMPRESSION)
configuration.lightos_default_num_replicas = 3 configuration.lightos_default_num_replicas = 3
configuration.lightos_use_ipacl = True
configuration.num_volume_device_scan_tries = ( configuration.num_volume_device_scan_tries = (
DEVICE_SCAN_ATTEMPTS_DEFAULT) DEVICE_SCAN_ATTEMPTS_DEFAULT)
configuration.lightos_api_service_timeout = LIGHTOS_API_SERVICE_TIMEOUT configuration.lightos_api_service_timeout = LIGHTOS_API_SERVICE_TIMEOUT
@ -314,6 +323,10 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
return (httpstatus.OK, FAKE_LIGHTOS_CLUSTER_INFO) return (httpstatus.OK, FAKE_LIGHTOS_CLUSTER_INFO)
elif cmd == "create_volume": elif cmd == "create_volume":
project_name = kwargs["project_name"] project_name = kwargs["project_name"]
ipacl = (
{'values': ['ALLOW_NONE']}
if self.driver.configuration.lightos_use_ipacl
else {'values': ['ALLOW_ANY']})
volume = { volume = {
"project_name": project_name, "project_name": project_name,
"name": kwargs["name"], "name": kwargs["name"],
@ -322,6 +335,7 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
"compression": kwargs["compression"], "compression": kwargs["compression"],
"src_snapshot_name": kwargs["src_snapshot_name"], "src_snapshot_name": kwargs["src_snapshot_name"],
"acl": {'values': kwargs.get('acl')}, "acl": {'values': kwargs.get('acl')},
"IPAcl": ipacl,
"state": "Available", "state": "Available",
} }
volume["ETag"] = get_vol_etag(volume) volume["ETag"] = get_vol_etag(volume)
@ -396,6 +410,20 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
self.driver.delete_volume(volume) self.driver.delete_volume(volume)
db.volume_destroy(self.ctxt, volume.id) db.volume_destroy(self.ctxt, volume.id)
def test_create_volume_ipacl_off(self):
"""Test that lightos_client succeed. ipacl false"""
self.driver.configuration.lightos_use_ipacl = False
self.driver.do_setup(None)
vol_type = test_utils.create_volume_type(self.ctxt, self,
name='my_vol_type')
volume = test_utils.create_volume(self.ctxt, size=4,
volume_type_id=vol_type.id)
self.driver.create_volume(volume)
self.driver.delete_volume(volume)
db.volume_destroy(self.ctxt, volume.id)
def test_create_volume_same_volume_twice_succeed(self): def test_create_volume_same_volume_twice_succeed(self):
"""Test succeed to create an exiting volume.""" """Test succeed to create an exiting volume."""
self.driver.do_setup(None) self.driver.do_setup(None)
@ -418,6 +446,10 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
def send_cmd_mock(cmd, **kwargs): def send_cmd_mock(cmd, **kwargs):
if cmd == "create_volume": if cmd == "create_volume":
project_name = kwargs["project_name"] project_name = kwargs["project_name"]
ipacl = (
{'values': ['ALLOW_NONE']}
if self.driver.configuration.lightos_use_ipacl
else {'values': ['ALLOW_ANY']})
volume = { volume = {
"project_name": project_name, "project_name": project_name,
"name": kwargs["name"], "name": kwargs["name"],
@ -426,6 +458,7 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
"compression": kwargs["compression"], "compression": kwargs["compression"],
"src_snapshot_name": kwargs["src_snapshot_name"], "src_snapshot_name": kwargs["src_snapshot_name"],
"acl": {'values': kwargs.get('acl')}, "acl": {'values': kwargs.get('acl')},
"IPAcl": ipacl,
"state": vol_state, "state": vol_state,
} }
volume["ETag"] = get_vol_etag(volume) volume["ETag"] = get_vol_etag(volume)
@ -462,6 +495,10 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
def send_cmd_mock(cmd, **kwargs): def send_cmd_mock(cmd, **kwargs):
if cmd == "create_volume": if cmd == "create_volume":
project_name = kwargs["project_name"] project_name = kwargs["project_name"]
ipacl = (
{'values': ['ALLOW_NONE']}
if self.driver.configuration.lightos_use_ipacl
else {'values': ['ALLOW_ANY']})
volume = { volume = {
"project_name": project_name, "project_name": project_name,
"name": kwargs["name"], "name": kwargs["name"],
@ -470,6 +507,7 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
"compression": kwargs["compression"], "compression": kwargs["compression"],
"src_snapshot_name": kwargs["src_snapshot_name"], "src_snapshot_name": kwargs["src_snapshot_name"],
"acl": {'values': kwargs.get('acl')}, "acl": {'values': kwargs.get('acl')},
"IPAcl": ipacl,
"state": "Migrating", "state": "Migrating",
} }
volume["ETag"] = get_vol_etag(volume) volume["ETag"] = get_vol_etag(volume)
@ -641,6 +679,7 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
def test_initialize_connection(self): def test_initialize_connection(self):
InitialConnectorMock.nqn = "hostnqn1" InitialConnectorMock.nqn = "hostnqn1"
InitialConnectorMock.found_discovery_client = True InitialConnectorMock.found_discovery_client = True
InitialConnectorMock.host_ips = FAKE_HOST_IPS
self.driver.do_setup(None) self.driver.do_setup(None)
vol_type = test_utils.create_volume_type(self.ctxt, self, vol_type = test_utils.create_volume_type(self.ctxt, self,
name='my_vol_type') name='my_vol_type')
@ -657,6 +696,37 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
self.assertEqual( self.assertEqual(
self.db.data['projects']['default']['volumes'][0]['UUID'], self.db.data['projects']['default']['volumes'][0]['UUID'],
connection_props['data']['uuid']) connection_props['data']['uuid'])
self.assertEqual(
self.db.data['projects']['default']['volumes'][0]['IPAcl'],
{'values': FAKE_HOST_IPS})
self.driver.delete_volume(volume)
db.volume_destroy(self.ctxt, volume.id)
def test_initialize_connection_ipacl_disabled(self):
self.driver.configuration.lightos_use_ipacl = False
InitialConnectorMock.nqn = "hostnqn1"
InitialConnectorMock.found_discovery_client = True
self.driver.do_setup(None)
vol_type = test_utils.create_volume_type(self.ctxt, self,
name='my_vol_type')
volume = test_utils.create_volume(self.ctxt, size=4,
volume_type_id=vol_type.id)
self.driver.create_volume(volume)
connection_props = \
self.driver.initialize_connection(volume,
get_connector_properties())
self.assertIn('driver_volume_type', connection_props)
self.assertEqual('lightos', connection_props['driver_volume_type'])
self.assertEqual(FAKE_LIGHTOS_CLUSTER_INFO['subsystemNQN'],
connection_props['data']['subsysnqn'])
self.assertEqual(
self.db.data['projects']['default']['volumes'][0]['UUID'],
connection_props['data']['uuid'])
self.assertEqual(
self.db.data['projects']['default']['volumes'][0]['IPAcl'],
{'values': ['ALLOW_ANY']})
self.driver.delete_volume(volume) self.driver.delete_volume(volume)
db.volume_destroy(self.ctxt, volume.id) db.volume_destroy(self.ctxt, volume.id)
@ -668,6 +738,10 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
def send_cmd_mock(cmd, **kwargs): def send_cmd_mock(cmd, **kwargs):
if cmd == "create_volume": if cmd == "create_volume":
project_name = kwargs["project_name"] project_name = kwargs["project_name"]
ipacl = (
{'values': ['ALLOW_NONE']}
if self.driver.configuration.lightos_use_ipacl
else {'values': ['ALLOW_ANY']})
volume = { volume = {
"project_name": project_name, "project_name": project_name,
"name": kwargs["name"], "name": kwargs["name"],
@ -676,6 +750,7 @@ class LightOSStorageVolumeDriverTest(test.TestCase):
"compression": kwargs["compression"], "compression": kwargs["compression"],
"src_snapshot_name": kwargs["src_snapshot_name"], "src_snapshot_name": kwargs["src_snapshot_name"],
"acl": {'values': kwargs.get('acl')}, "acl": {'values': kwargs.get('acl')},
"IPAcl": ipacl,
"state": "Migrating", "state": "Migrating",
} }
volume["ETag"] = get_vol_etag(volume) volume["ETag"] = get_vol_etag(volume)

View File

@ -25,6 +25,7 @@ from urllib.parse import urlparse
from oslo_config import cfg from oslo_config import cfg
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import importutils from oslo_utils import importutils
from oslo_utils import netutils
from oslo_utils import units from oslo_utils import units
import requests import requests
import urllib3 import urllib3
@ -77,7 +78,16 @@ lightos_opts = [
cfg.IntOpt('lightos_api_service_timeout', cfg.IntOpt('lightos_api_service_timeout',
default=30, default=30,
help='The default amount of time (in seconds) to wait for' help='The default amount of time (in seconds) to wait for'
' an API endpoint response.') ' an API endpoint response.'),
cfg.BoolOpt('lightos_use_ipacl',
default=True,
help='IPACL work in conjunction with the standard NVME ACL.'
' A host must be in both the IPACL and the ACL of a volume to'
' access that volume. Cinder always sets the volume`s ACL.'
' If lightos_use_ipacl is set to True, Cinder will also add'
' the host`s IP addresses to a volume IPACL. If set to'
' False, any IP address may access the volume. The default'
' is True.'),
] ]
CONF = cfg.CONF CONF = cfg.CONF
@ -152,6 +162,9 @@ class LightOSConnection(object):
'acl': { 'acl': {
'values': kwargs.get('acl'), 'values': kwargs.get('acl'),
}, },
'IPAcl': {
'values': kwargs.get('ip_acl'),
},
'sourceSnapshotUUID': kwargs.get( 'sourceSnapshotUUID': kwargs.get(
'src_snapshot_uuid'), 'src_snapshot_uuid'),
'sourceSnapshotName': kwargs.get( 'sourceSnapshotName': kwargs.get(
@ -170,6 +183,9 @@ class LightOSConnection(object):
'acl': { 'acl': {
'values': kwargs.get('acl'), 'values': kwargs.get('acl'),
}, },
'IPAcl': {
'values': kwargs.get('ip_acl'),
},
}), }),
'extend_volume': ('PUT', 'extend_volume': ('PUT',
@ -598,6 +614,7 @@ class LightOSVolumeDriver(driver.VolumeDriver):
src_snapshot_lightos_name=None): src_snapshot_lightos_name=None):
"""Create a new LightOS volume for this openstack volume.""" """Create a new LightOS volume for this openstack volume."""
(compression, num_replicas, _) = self._get_volume_specs(os_volume) (compression, num_replicas, _) = self._get_volume_specs(os_volume)
vol_ipAcl = ['ALLOW_NONE'] if self.use_ip_acl() else ['ALLOW_ANY']
return self.cluster.send_cmd( return self.cluster.send_cmd(
cmd='create_volume', cmd='create_volume',
project_name=project_name, project_name=project_name,
@ -607,7 +624,8 @@ class LightOSVolumeDriver(driver.VolumeDriver):
n_replicas=num_replicas, n_replicas=num_replicas,
compression=compression, compression=compression,
src_snapshot_name=src_snapshot_lightos_name, src_snapshot_name=src_snapshot_lightos_name,
acl=['ALLOW_NONE'] acl=['ALLOW_NONE'],
ip_acl=vol_ipAcl
) )
def _get_lightos_uuid(self, project_name, volume): def _get_lightos_uuid(self, project_name, volume):
@ -1034,17 +1052,22 @@ class LightOSVolumeDriver(driver.VolumeDriver):
return server_properties return server_properties
def set_volume_acl(self, project_name, lightos_uuid, acl, etag): def set_volume_acl(self, project_name, lightos_uuid, acl, ip_acl, etag):
return self.cluster.send_cmd( return self.cluster.send_cmd(
cmd='update_volume', cmd='update_volume',
project_name=project_name, project_name=project_name,
timeout=self.logical_op_timeout, timeout=self.logical_op_timeout,
volume_uuid=lightos_uuid, volume_uuid=lightos_uuid,
acl=acl, acl=acl,
ip_acl=ip_acl,
etag=etag etag=etag
) )
def __add_volume_acl(self, project_name, lightos_volname, acl_to_add): def use_ip_acl(self):
return self.configuration.lightos_use_ipacl
def __add_volume_acl(self, project_name, lightos_volname, acl_to_add,
host_ips):
(status, data) = self._get_lightos_volume(project_name, (status, data) = self._get_lightos_volume(project_name,
self.logical_op_timeout, self.logical_op_timeout,
vol_name=lightos_volname) vol_name=lightos_volname)
@ -1063,7 +1086,13 @@ class LightOSVolumeDriver(driver.VolumeDriver):
LOG.warning('Got LightOS volume without ACL?! data: %s', data) LOG.warning('Got LightOS volume without ACL?! data: %s', data)
return False return False
ip_acl = data.get('IPAcl')
if self.use_ip_acl() and not ip_acl:
LOG.warning('Got LightOS volume without IP ACL?! data: %s', data)
return False
acl = acl.get('values', []) acl = acl.get('values', [])
ip_acl = ip_acl.get('values', [])
# remove ALLOW_NONE and add our acl_to_add if not already there # remove ALLOW_NONE and add our acl_to_add if not already there
if 'ALLOW_NONE' in acl: if 'ALLOW_NONE' in acl:
@ -1071,15 +1100,53 @@ class LightOSVolumeDriver(driver.VolumeDriver):
if acl_to_add not in acl: if acl_to_add not in acl:
acl.append(acl_to_add) acl.append(acl_to_add)
if 'ALLOW_NONE' in ip_acl:
ip_acl.remove('ALLOW_NONE')
if self.use_ip_acl():
ip_acl = list(set(ip_acl).union(set(host_ips)))
else:
ip_acl = ['ALLOW_ANY']
# The max (16) elemenets are allowed in IPACL.
# if elements are more than 16 then remove
# less-frequently used IPv6 address(s), and IPv4 if needed.
ipv4addrs = [addr for addr in ip_acl if netutils.is_valid_ipv4(addr)]
ipv6addrs = [addr for addr in ip_acl if netutils.is_valid_ipv6(addr)]
IpAcl_size = 16
if len(ipv4addrs) > IpAcl_size:
LOG.warning(
'IPv4 address(es) are more than maximum (%s)'
' allowed in IP-ACL of volume, therefore reducing'
' IPv4 address(es) written to IP-ACL of volume %s'
' of project %s', IpAcl_size, lightos_volname,
project_name)
ip_acl = ipv4addrs[0: IpAcl_size]
elif len(ip_acl) > IpAcl_size:
LOG.warning(
'Combined IPv4 and IPv6 address(es) are more than'
' maximum (%s) allowed in IP-ACL of volume, therefore'
' reducing IPv6 address(es) written to IP-ACL of'
' volume %s of project %s', IpAcl_size,
lightos_volname, project_name)
ipv6addrs_count = IpAcl_size - len(ipv4addrs)
ip_acl = ipv4addrs + (ipv6addrs[0: ipv6addrs_count])
return self.set_volume_acl( return self.set_volume_acl(
project_name, project_name,
lightos_uuid, lightos_uuid,
acl, acl,
ip_acl,
etag=data.get( etag=data.get(
'ETag', 'ETag',
'')) ''))
def add_volume_acl(self, project_name, volume, acl_to_add): def add_volume_acl(self, project_name, volume, acl_to_add, host_ips):
LOG.debug( LOG.debug(
'add_volume_acl got volume %s project %s acl %s', 'add_volume_acl got volume %s project %s acl %s',
volume, volume,
@ -1090,13 +1157,15 @@ class LightOSVolumeDriver(driver.VolumeDriver):
self.__add_volume_acl, self.__add_volume_acl,
project_name, project_name,
lightos_volname, lightos_volname,
acl_to_add) acl_to_add,
host_ips)
def __remove_volume_acl( def __remove_volume_acl(
self, self,
project_name, project_name,
lightos_volname, lightos_volname,
acl_to_remove): acl_to_remove,
host_ips):
(status, data) = self._get_lightos_volume(project_name, (status, data) = self._get_lightos_volume(project_name,
self.logical_op_timeout, self.logical_op_timeout,
vol_name=lightos_volname) vol_name=lightos_volname)
@ -1138,19 +1207,41 @@ class LightOSVolumeDriver(driver.VolumeDriver):
if not acl: if not acl:
acl.append('ALLOW_NONE') acl.append('ALLOW_NONE')
ip_acl = data.get('IPAcl')
if self.use_ip_acl() and not ip_acl:
LOG.warning('Got LightOS volume without IP ACL?! data: %s', data)
return False
ip_acl = ip_acl.get('values')
if self.use_ip_acl() and not ip_acl:
LOG.warning(
'Got LightOS volume without IP ACL values?! data: %s', data)
return False
for ip in host_ips:
try:
ip_acl.remove(ip)
except ValueError:
LOG.warning(
'Could not find matching ip %s in ip-acl of volume %s ',
ip, lightos_volname)
if not ip_acl:
ip_acl.append('ALLOW_NONE')
return self.set_volume_acl( return self.set_volume_acl(
project_name, project_name,
lightos_uuid, lightos_uuid,
acl, acl,
etag=data.get( ip_acl,
'ETag', etag=data.get('ETag', ''))
''))
def __overwrite_volume_acl( def __overwrite_volume_acl(
self, self,
project_name, project_name,
lightos_volname, lightos_volname,
acl): acl,
host_ips):
status, data = self._get_lightos_volume(project_name, status, data = self._get_lightos_volume(project_name,
self.logical_op_timeout, self.logical_op_timeout,
vol_name=lightos_volname) vol_name=lightos_volname)
@ -1170,11 +1261,12 @@ class LightOSVolumeDriver(driver.VolumeDriver):
project_name, project_name,
lightos_uuid, lightos_uuid,
acl, acl,
host_ips,
etag=data.get( etag=data.get(
'ETag', 'ETag',
'')) ''))
def remove_volume_acl(self, project_name, volume, acl_to_remove): def remove_volume_acl(self, project_name, volume, acl_to_remove, host_ips):
lightos_volname = self._lightos_volname(volume) lightos_volname = self._lightos_volname(volume)
LOG.debug('remove_volume_acl volume %s project %s acl %s', LOG.debug('remove_volume_acl volume %s project %s acl %s',
volume, project_name, acl_to_remove) volume, project_name, acl_to_remove)
@ -1182,7 +1274,8 @@ class LightOSVolumeDriver(driver.VolumeDriver):
self.__remove_volume_acl, self.__remove_volume_acl,
project_name, project_name,
lightos_volname, lightos_volname,
acl_to_remove) acl_to_remove,
host_ips)
def remove_all_volume_acls(self, project_name, volume): def remove_all_volume_acls(self, project_name, volume):
lightos_volname = self._lightos_volname(volume) lightos_volname = self._lightos_volname(volume)
@ -1192,9 +1285,11 @@ class LightOSVolumeDriver(driver.VolumeDriver):
self.__overwrite_volume_acl, self.__overwrite_volume_acl,
project_name, project_name,
lightos_volname, lightos_volname,
['ALLOW_NONE'],
['ALLOW_NONE']) ['ALLOW_NONE'])
def update_volume_acl(self, func, project_name, lightos_volname, acl): def update_volume_acl(self, func, project_name, lightos_volname, acl,
host_ips):
# loop because lightos api is async # loop because lightos api is async
end = time.time() + self.logical_op_timeout end = time.time() + self.logical_op_timeout
first_iteration = True first_iteration = True
@ -1202,7 +1297,7 @@ class LightOSVolumeDriver(driver.VolumeDriver):
if not first_iteration: if not first_iteration:
time.sleep(1) time.sleep(1)
first_iteration = False first_iteration = False
res = func(project_name, lightos_volname, acl) res = func(project_name, lightos_volname, acl, host_ips)
if not isinstance(res, tuple): if not isinstance(res, tuple):
LOG.debug('Update_volume: func %s(%s project %s) failed', LOG.debug('Update_volume: func %s(%s project %s) failed',
func, lightos_volname, project_name) func, lightos_volname, project_name)
@ -1410,6 +1505,10 @@ class LightOSVolumeDriver(driver.VolumeDriver):
def initialize_connection(self, volume, connector): def initialize_connection(self, volume, connector):
hostnqn = connector.get('nqn') hostnqn = connector.get('nqn')
found_dsc = connector.get('found_dsc') found_dsc = connector.get('found_dsc')
host_ips = connector.get('host_ips', [])
LOG.info('Current host hostNQN is %s and IP(s) are %s',
hostnqn,
host_ips)
LOG.debug( LOG.debug(
'initialize_connection: connector hostnqn is %s found_dsc %s', 'initialize_connection: connector hostnqn is %s found_dsc %s',
hostnqn, hostnqn,
@ -1424,9 +1523,14 @@ class LightOSVolumeDriver(driver.VolumeDriver):
'client, aborting' % (connector)) 'client, aborting' % (connector))
raise exception.VolumeBackendAPIException(message=_(msg)) raise exception.VolumeBackendAPIException(message=_(msg))
if not host_ips:
msg = 'Connector (%s) did not find host IPs, aborting' % (
connector)
raise exception.VolumeBackendAPIException(message=_(msg))
lightos_volname = self._lightos_volname(volume) lightos_volname = self._lightos_volname(volume)
project_name = self._get_lightos_project_name(volume) project_name = self._get_lightos_project_name(volume)
success = self.add_volume_acl(project_name, volume, hostnqn) success = self.add_volume_acl(project_name, volume, hostnqn, host_ips)
if not success or not self._wait_for_volume_acl( if not success or not self._wait_for_volume_acl(
project_name, lightos_volname, hostnqn, True): project_name, lightos_volname, hostnqn, True):
msg = ('Could not add ACL for hostnqn %s LightOS volume' msg = ('Could not add ACL for hostnqn %s LightOS volume'
@ -1439,6 +1543,7 @@ class LightOSVolumeDriver(driver.VolumeDriver):
def terminate_connection(self, volume, connector, **kwargs): def terminate_connection(self, volume, connector, **kwargs):
force = 'force' in kwargs force = 'force' in kwargs
hostnqn = connector.get('nqn') if connector else None hostnqn = connector.get('nqn') if connector else None
host_ips = connector.get('host_ips', []) if connector else []
LOG.debug( LOG.debug(
'terminate_connection: force %s kwargs %s hostnqn %s', 'terminate_connection: force %s kwargs %s hostnqn %s',
force, force,
@ -1462,7 +1567,8 @@ class LightOSVolumeDriver(driver.VolumeDriver):
lightos_volname = self._lightos_volname(volume) lightos_volname = self._lightos_volname(volume)
project_name = self._get_lightos_project_name(volume) project_name = self._get_lightos_project_name(volume)
success = self.remove_volume_acl(project_name, volume, hostnqn) success = self.remove_volume_acl(project_name, volume, hostnqn,
host_ips)
if not success or not self._wait_for_volume_acl( if not success or not self._wait_for_volume_acl(
project_name, lightos_volname, hostnqn, False): project_name, lightos_volname, hostnqn, False):
LOG.warning( LOG.warning(

View File

@ -0,0 +1,7 @@
---
features:
- |
Lightbits driver: Added a new configuration option
``lightos_use_ipacl``, defaulting to true. When set to true, the
Cinder driver will restrict access to each volume to the IP
addresses of the host machine that the volume is attached to.