Nexenta: Added HA support and enhance get_volume_stats()
Added HA support in NexentaEdge iSCSI driver get_volume_stats function now returns total and free capacity Change-Id: I7a27d94fef9309ebd1bb82dbb2c518f917ab32ca
This commit is contained in:
parent
483bc008f3
commit
672120b372
@ -14,6 +14,7 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
@ -56,8 +57,14 @@ ISCSI_TARGET_STATUS = 'Target 1: ' + ISCSI_TARGET_NAME
|
|||||||
class TestNexentaEdgeISCSIDriver(test.TestCase):
|
class TestNexentaEdgeISCSIDriver(test.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
def _safe_get(opt):
|
||||||
|
return getattr(self.cfg, opt)
|
||||||
super(TestNexentaEdgeISCSIDriver, self).setUp()
|
super(TestNexentaEdgeISCSIDriver, self).setUp()
|
||||||
|
self.context = context.get_admin_context()
|
||||||
self.cfg = mock.Mock(spec=conf.Configuration)
|
self.cfg = mock.Mock(spec=conf.Configuration)
|
||||||
|
self.cfg.safe_get = mock.Mock(side_effect=_safe_get)
|
||||||
|
self.cfg.trace_flags = 'fake_trace_flags'
|
||||||
|
self.cfg.driver_data_namespace = 'fake_driver_data_namespace'
|
||||||
self.cfg.nexenta_client_address = '0.0.0.0'
|
self.cfg.nexenta_client_address = '0.0.0.0'
|
||||||
self.cfg.nexenta_rest_address = '0.0.0.0'
|
self.cfg.nexenta_rest_address = '0.0.0.0'
|
||||||
self.cfg.nexenta_rest_port = 8080
|
self.cfg.nexenta_rest_port = 8080
|
||||||
@ -82,13 +89,82 @@ class TestNexentaEdgeISCSIDriver(test.TestCase):
|
|||||||
self.mock_api.return_value = {
|
self.mock_api.return_value = {
|
||||||
'data': {'value': ISCSI_TARGET_STATUS}
|
'data': {'value': ISCSI_TARGET_STATUS}
|
||||||
}
|
}
|
||||||
self.driver.do_setup(context.get_admin_context())
|
self.driver.do_setup(self.context)
|
||||||
|
|
||||||
self.addCleanup(self.api_patcher.stop)
|
self.addCleanup(self.api_patcher.stop)
|
||||||
|
|
||||||
def test_check_do_setup(self):
|
def test_check_do_setup(self):
|
||||||
self.assertEqual(ISCSI_TARGET_NAME, self.driver.target_name)
|
self.assertEqual(ISCSI_TARGET_NAME, self.driver.target_name)
|
||||||
|
|
||||||
|
def test_check_do_setup__vip(self):
|
||||||
|
first_vip = '/'.join((self.cfg.nexenta_client_address, '32'))
|
||||||
|
vips = [
|
||||||
|
[{'ip': first_vip}],
|
||||||
|
[{'ip': '0.0.0.1/32'}]
|
||||||
|
]
|
||||||
|
|
||||||
|
def my_side_effect(*args, **kwargs):
|
||||||
|
if args[0] == 'service/isc/iscsi/status':
|
||||||
|
return {'data': {'value': ISCSI_TARGET_STATUS}}
|
||||||
|
else:
|
||||||
|
return {'data': {'X-VIPS': json.dumps(vips)}}
|
||||||
|
|
||||||
|
self.mock_api.side_effect = my_side_effect
|
||||||
|
self.driver.do_setup(self.context)
|
||||||
|
self.assertEqual(self.driver.ha_vip, first_vip)
|
||||||
|
|
||||||
|
def test_check_do_setup__vip_not_in_xvips(self):
|
||||||
|
first_vip = '1.2.3.4/32'
|
||||||
|
vips = [
|
||||||
|
[{'ip': first_vip}],
|
||||||
|
[{'ip': '0.0.0.1/32'}]
|
||||||
|
]
|
||||||
|
|
||||||
|
def my_side_effect(*args, **kwargs):
|
||||||
|
if args[0] == 'service/isc/iscsi/status':
|
||||||
|
return {'data': {'value': ISCSI_TARGET_STATUS}}
|
||||||
|
else:
|
||||||
|
return {'data': {'X-VIPS': json.dumps(vips)}}
|
||||||
|
|
||||||
|
self.mock_api.side_effect = my_side_effect
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.driver.do_setup, self.context)
|
||||||
|
|
||||||
|
def test_check_do_setup__vip_no_client_address(self):
|
||||||
|
self.cfg.nexenta_client_address = None
|
||||||
|
first_vip = '1.2.3.4/32'
|
||||||
|
vips = [
|
||||||
|
[{'ip': first_vip}]
|
||||||
|
]
|
||||||
|
|
||||||
|
def my_side_effect(*args, **kwargs):
|
||||||
|
if args[0] == 'service/isc/iscsi/status':
|
||||||
|
return {'data': {'value': ISCSI_TARGET_STATUS}}
|
||||||
|
else:
|
||||||
|
return {'data': {'X-VIPS': json.dumps(vips)}}
|
||||||
|
|
||||||
|
self.mock_api.side_effect = my_side_effect
|
||||||
|
self.driver.do_setup(self.context)
|
||||||
|
self.assertEqual(self.driver.ha_vip, first_vip)
|
||||||
|
|
||||||
|
def test_check_do_setup__vip_no_client_address_2_xvips(self):
|
||||||
|
self.cfg.nexenta_client_address = None
|
||||||
|
first_vip = '1.2.3.4/32'
|
||||||
|
vips = [
|
||||||
|
[{'ip': first_vip}],
|
||||||
|
[{'ip': '0.0.0.1/32'}]
|
||||||
|
]
|
||||||
|
|
||||||
|
def my_side_effect(*args, **kwargs):
|
||||||
|
if args[0] == 'service/isc/iscsi/status':
|
||||||
|
return {'data': {'value': ISCSI_TARGET_STATUS}}
|
||||||
|
else:
|
||||||
|
return {'data': {'X-VIPS': json.dumps(vips)}}
|
||||||
|
|
||||||
|
self.mock_api.side_effect = my_side_effect
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.driver.do_setup, self.context)
|
||||||
|
|
||||||
def test_create_volume(self):
|
def test_create_volume(self):
|
||||||
self.driver.create_volume(MOCK_VOL)
|
self.driver.create_volume(MOCK_VOL)
|
||||||
self.mock_api.assert_called_with(NEDGE_URL, {
|
self.mock_api.assert_called_with(NEDGE_URL, {
|
||||||
@ -98,6 +174,17 @@ class TestNexentaEdgeISCSIDriver(test.TestCase):
|
|||||||
'chunkSize': NEDGE_CHUNKSIZE
|
'chunkSize': NEDGE_CHUNKSIZE
|
||||||
})
|
})
|
||||||
|
|
||||||
|
def test_create_volume__vip(self):
|
||||||
|
self.driver.ha_vip = self.cfg.nexenta_client_address + '/32'
|
||||||
|
self.driver.create_volume(MOCK_VOL)
|
||||||
|
self.mock_api.assert_called_with(NEDGE_URL, {
|
||||||
|
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
|
||||||
|
'volSizeMB': MOCK_VOL['size'] * 1024,
|
||||||
|
'blockSize': NEDGE_BLOCKSIZE,
|
||||||
|
'chunkSize': NEDGE_CHUNKSIZE,
|
||||||
|
'vip': self.cfg.nexenta_client_address + '/32'
|
||||||
|
})
|
||||||
|
|
||||||
def test_create_volume_fail(self):
|
def test_create_volume_fail(self):
|
||||||
self.mock_api.side_effect = RuntimeError
|
self.mock_api.side_effect = RuntimeError
|
||||||
self.assertRaises(RuntimeError, self.driver.create_volume, MOCK_VOL)
|
self.assertRaises(RuntimeError, self.driver.create_volume, MOCK_VOL)
|
||||||
|
@ -25,6 +25,7 @@ from cinder import interface
|
|||||||
from cinder.volume import driver
|
from cinder.volume import driver
|
||||||
from cinder.volume.drivers.nexenta.nexentaedge import jsonrpc
|
from cinder.volume.drivers.nexenta.nexentaedge import jsonrpc
|
||||||
from cinder.volume.drivers.nexenta import options
|
from cinder.volume.drivers.nexenta import options
|
||||||
|
from cinder.volume.drivers.nexenta import utils as nexenta_utils
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -37,9 +38,10 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
|||||||
Version history:
|
Version history:
|
||||||
1.0.0 - Initial driver version.
|
1.0.0 - Initial driver version.
|
||||||
1.0.1 - Moved opts to options.py.
|
1.0.1 - Moved opts to options.py.
|
||||||
|
1.0.2 - Added HA support.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = '1.0.1'
|
VERSION = '1.0.2'
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(NexentaEdgeISCSIDriver, self).__init__(*args, **kwargs)
|
super(NexentaEdgeISCSIDriver, self).__init__(*args, **kwargs)
|
||||||
@ -67,6 +69,7 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
|||||||
self.iscsi_target_port = (self.configuration.
|
self.iscsi_target_port = (self.configuration.
|
||||||
nexenta_iscsi_target_portal_port)
|
nexenta_iscsi_target_portal_port)
|
||||||
self.target_vip = None
|
self.target_vip = None
|
||||||
|
self.ha_vip = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def backend_name(self):
|
def backend_name(self):
|
||||||
@ -78,6 +81,13 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
|||||||
return backend_name
|
return backend_name
|
||||||
|
|
||||||
def do_setup(self, context):
|
def do_setup(self, context):
|
||||||
|
def get_ip(host):
|
||||||
|
hm = host[0 if len(host) == 1 else 1]['ip'].split('/', 1)
|
||||||
|
return {
|
||||||
|
'ip': hm[0],
|
||||||
|
'mask': hm[1] if len(hm) > 1 else '32'
|
||||||
|
}
|
||||||
|
|
||||||
if self.restapi_protocol == 'auto':
|
if self.restapi_protocol == 'auto':
|
||||||
protocol, auto = 'http', True
|
protocol, auto = 'http', True
|
||||||
else:
|
else:
|
||||||
@ -93,22 +103,37 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
|||||||
data_keys = rsp['data'][list(rsp['data'].keys())[0]]
|
data_keys = rsp['data'][list(rsp['data'].keys())[0]]
|
||||||
self.target_name = data_keys.split('\n', 1)[0].split(' ')[2]
|
self.target_name = data_keys.split('\n', 1)[0].split(' ')[2]
|
||||||
|
|
||||||
|
target_vip = self.configuration.safe_get(
|
||||||
|
'nexenta_client_address')
|
||||||
rsp = self.restapi.get('service/' + self.iscsi_service)
|
rsp = self.restapi.get('service/' + self.iscsi_service)
|
||||||
if 'X-VIPS' in rsp['data']:
|
if 'X-VIPS' in rsp['data']:
|
||||||
vips = json.loads(rsp['data']['X-VIPS'])
|
vips = json.loads(rsp['data']['X-VIPS'])
|
||||||
if len(vips[0]) == 1:
|
vips = [get_ip(host) for host in vips]
|
||||||
self.target_vip = vips[0][0]['ip'].split('/', 1)[0]
|
if target_vip:
|
||||||
|
found = False
|
||||||
|
for host in vips:
|
||||||
|
if target_vip == host['ip']:
|
||||||
|
self.ha_vip = '/'.join((host['ip'], host['mask']))
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
raise exception.VolumeBackendAPIException(
|
||||||
|
message=_("nexenta_client_address doesn't match "
|
||||||
|
"any VIPs provided by service: {}"
|
||||||
|
).format(
|
||||||
|
", ".join([host['ip'] for host in vips])))
|
||||||
else:
|
else:
|
||||||
self.target_vip = vips[0][1]['ip'].split('/', 1)[0]
|
if len(vips) == 1:
|
||||||
else:
|
target_vip = vips[0]['ip']
|
||||||
self.target_vip = self.configuration.safe_get(
|
self.ha_vip = '/'.join(
|
||||||
'nexenta_client_address')
|
(vips[0]['ip'], vips[0]['mask']))
|
||||||
if not self.target_vip:
|
if not target_vip:
|
||||||
LOG.error(_LE('No VIP configured for service %s'),
|
LOG.error(_LE('No VIP configured for service %s'),
|
||||||
self.iscsi_service)
|
self.iscsi_service)
|
||||||
raise exception.VolumeBackendAPIException(
|
raise exception.VolumeBackendAPIException(
|
||||||
_('No service VIP configured and '
|
message=_('No service VIP configured and '
|
||||||
'no nexenta_client_address'))
|
'no nexenta_client_address'))
|
||||||
|
self.target_vip = target_vip
|
||||||
except exception.VolumeBackendAPIException:
|
except exception.VolumeBackendAPIException:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.exception(_LE('Error verifying iSCSI service %(serv)s on '
|
LOG.exception(_LE('Error verifying iSCSI service %(serv)s on '
|
||||||
@ -149,13 +174,16 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def create_volume(self, volume):
|
def create_volume(self, volume):
|
||||||
try:
|
data = {
|
||||||
self.restapi.post('service/' + self.iscsi_service + '/iscsi', {
|
|
||||||
'objectPath': self.bucket_path + '/' + volume['name'],
|
'objectPath': self.bucket_path + '/' + volume['name'],
|
||||||
'volSizeMB': int(volume['size']) * units.Ki,
|
'volSizeMB': int(volume['size']) * units.Ki,
|
||||||
'blockSize': self.blocksize,
|
'blockSize': self.blocksize,
|
||||||
'chunkSize': self.chunksize
|
'chunkSize': self.chunksize
|
||||||
})
|
}
|
||||||
|
if self.ha_vip:
|
||||||
|
data['vip'] = self.ha_vip
|
||||||
|
try:
|
||||||
|
self.restapi.post('service/' + self.iscsi_service + '/iscsi', data)
|
||||||
except exception.VolumeBackendAPIException:
|
except exception.VolumeBackendAPIException:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.exception(_LE('Error creating volume'))
|
LOG.exception(_LE('Error creating volume'))
|
||||||
@ -239,8 +267,7 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
|||||||
except exception.VolumeBackendAPIException:
|
except exception.VolumeBackendAPIException:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.exception(_LE('Error creating cloned volume'))
|
LOG.exception(_LE('Error creating cloned volume'))
|
||||||
|
if volume['size'] > src_vref['size']:
|
||||||
if (('size' in volume) and (volume['size'] > src_vref['size'])):
|
|
||||||
self.extend_volume(volume, volume['size'])
|
self.extend_volume(volume, volume['size'])
|
||||||
|
|
||||||
def create_export(self, context, volume, connector=None):
|
def create_export(self, context, volume, connector=None):
|
||||||
@ -256,6 +283,11 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def get_volume_stats(self, refresh=False):
|
def get_volume_stats(self, refresh=False):
|
||||||
|
resp = self.restapi.get('system/stats')
|
||||||
|
summary = resp['stats']['summary']
|
||||||
|
total = nexenta_utils.str2gib_size(summary['total_capacity'])
|
||||||
|
free = nexenta_utils.str2gib_size(summary['total_available'])
|
||||||
|
|
||||||
location_info = '%(driver)s:%(host)s:%(bucket)s' % {
|
location_info = '%(driver)s:%(host)s:%(bucket)s' % {
|
||||||
'driver': self.__class__.__name__,
|
'driver': self.__class__.__name__,
|
||||||
'host': self._get_target_address(None),
|
'host': self._get_target_address(None),
|
||||||
@ -266,8 +298,8 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
|
|||||||
'driver_version': self.VERSION,
|
'driver_version': self.VERSION,
|
||||||
'storage_protocol': 'iSCSI',
|
'storage_protocol': 'iSCSI',
|
||||||
'reserved_percentage': 0,
|
'reserved_percentage': 0,
|
||||||
'total_capacity_gb': 'unknown',
|
'total_capacity_gb': total,
|
||||||
'free_capacity_gb': 'unknown',
|
'free_capacity_gb': free,
|
||||||
'QoS_support': False,
|
'QoS_support': False,
|
||||||
'volume_backend_name': self.backend_name,
|
'volume_backend_name': self.backend_name,
|
||||||
'location_info': location_info,
|
'location_info': location_info,
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added HA support for NexentaEdge iSCSI driver
|
Loading…
Reference in New Issue
Block a user