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:
Aleksey Ruban 2016-06-29 07:35:20 -06:00
parent 483bc008f3
commit 672120b372
3 changed files with 146 additions and 24 deletions

View File

@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import json
import mock
from cinder import context
@ -56,8 +57,14 @@ ISCSI_TARGET_STATUS = 'Target 1: ' + ISCSI_TARGET_NAME
class TestNexentaEdgeISCSIDriver(test.TestCase):
def setUp(self):
def _safe_get(opt):
return getattr(self.cfg, opt)
super(TestNexentaEdgeISCSIDriver, self).setUp()
self.context = context.get_admin_context()
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_rest_address = '0.0.0.0'
self.cfg.nexenta_rest_port = 8080
@ -82,13 +89,82 @@ class TestNexentaEdgeISCSIDriver(test.TestCase):
self.mock_api.return_value = {
'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)
def test_check_do_setup(self):
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):
self.driver.create_volume(MOCK_VOL)
self.mock_api.assert_called_with(NEDGE_URL, {
@ -98,6 +174,17 @@ class TestNexentaEdgeISCSIDriver(test.TestCase):
'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):
self.mock_api.side_effect = RuntimeError
self.assertRaises(RuntimeError, self.driver.create_volume, MOCK_VOL)

View File

@ -25,6 +25,7 @@ from cinder import interface
from cinder.volume import driver
from cinder.volume.drivers.nexenta.nexentaedge import jsonrpc
from cinder.volume.drivers.nexenta import options
from cinder.volume.drivers.nexenta import utils as nexenta_utils
LOG = logging.getLogger(__name__)
@ -37,9 +38,10 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
Version history:
1.0.0 - Initial driver version.
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):
super(NexentaEdgeISCSIDriver, self).__init__(*args, **kwargs)
@ -67,6 +69,7 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
self.iscsi_target_port = (self.configuration.
nexenta_iscsi_target_portal_port)
self.target_vip = None
self.ha_vip = None
@property
def backend_name(self):
@ -78,6 +81,13 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
return backend_name
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':
protocol, auto = 'http', True
else:
@ -93,22 +103,37 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
data_keys = rsp['data'][list(rsp['data'].keys())[0]]
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)
if 'X-VIPS' in rsp['data']:
vips = json.loads(rsp['data']['X-VIPS'])
if len(vips[0]) == 1:
self.target_vip = vips[0][0]['ip'].split('/', 1)[0]
vips = [get_ip(host) for host in vips]
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:
self.target_vip = vips[0][1]['ip'].split('/', 1)[0]
else:
self.target_vip = self.configuration.safe_get(
'nexenta_client_address')
if not self.target_vip:
LOG.error(_LE('No VIP configured for service %s'),
self.iscsi_service)
raise exception.VolumeBackendAPIException(
_('No service VIP configured and '
'no nexenta_client_address'))
if len(vips) == 1:
target_vip = vips[0]['ip']
self.ha_vip = '/'.join(
(vips[0]['ip'], vips[0]['mask']))
if not target_vip:
LOG.error(_LE('No VIP configured for service %s'),
self.iscsi_service)
raise exception.VolumeBackendAPIException(
message=_('No service VIP configured and '
'no nexenta_client_address'))
self.target_vip = target_vip
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error verifying iSCSI service %(serv)s on '
@ -149,13 +174,16 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
}
def create_volume(self, volume):
data = {
'objectPath': self.bucket_path + '/' + volume['name'],
'volSizeMB': int(volume['size']) * units.Ki,
'blockSize': self.blocksize,
'chunkSize': self.chunksize
}
if self.ha_vip:
data['vip'] = self.ha_vip
try:
self.restapi.post('service/' + self.iscsi_service + '/iscsi', {
'objectPath': self.bucket_path + '/' + volume['name'],
'volSizeMB': int(volume['size']) * units.Ki,
'blockSize': self.blocksize,
'chunkSize': self.chunksize
})
self.restapi.post('service/' + self.iscsi_service + '/iscsi', data)
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating volume'))
@ -239,8 +267,7 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating cloned volume'))
if (('size' in volume) and (volume['size'] > src_vref['size'])):
if volume['size'] > src_vref['size']:
self.extend_volume(volume, volume['size'])
def create_export(self, context, volume, connector=None):
@ -256,6 +283,11 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
raise NotImplementedError
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' % {
'driver': self.__class__.__name__,
'host': self._get_target_address(None),
@ -266,8 +298,8 @@ class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
'driver_version': self.VERSION,
'storage_protocol': 'iSCSI',
'reserved_percentage': 0,
'total_capacity_gb': 'unknown',
'free_capacity_gb': 'unknown',
'total_capacity_gb': total,
'free_capacity_gb': free,
'QoS_support': False,
'volume_backend_name': self.backend_name,
'location_info': location_info,

View File

@ -0,0 +1,3 @@
---
features:
- Added HA support for NexentaEdge iSCSI driver