Merge "Add NexentaEdge drivers"

This commit is contained in:
Zuul 2018-06-08 01:22:11 +00:00 committed by Gerrit Code Review
commit 875596b4ea
9 changed files with 900 additions and 9 deletions

View File

@ -0,0 +1,268 @@
#
# Copyright 2015 Nexenta Systems, Inc.
# All Rights Reserved.
#
# 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.
import json
import mock
from mock import patch
from cinder import context
from cinder import exception
from cinder import test
from cinder.volume import configuration as conf
from cinder.volume.drivers.nexenta.nexentaedge import iscsi
NEDGE_BUCKET = 'c/t/bk'
NEDGE_SERVICE = 'isc'
NEDGE_URL = 'service/%s/iscsi' % NEDGE_SERVICE
NEDGE_BLOCKSIZE = 4096
NEDGE_CHUNKSIZE = 16384
MOCK_VOL = {
'id': 'vol1',
'name': 'vol1',
'size': 1
}
MOCK_VOL2 = {
'id': 'vol2',
'name': 'vol2',
'size': 1
}
MOCK_VOL3 = {
'id': 'vol3',
'name': 'vol3',
'size': 2
}
MOCK_SNAP = {
'id': 'snap1',
'name': 'snap1',
'volume_name': 'vol1',
'volume_size': 1
}
NEW_VOL_SIZE = 2
ISCSI_TARGET_NAME = 'iscsi_target_name:'
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
self.cfg.nexenta_rest_protocol = 'http'
self.cfg.nexenta_iscsi_target_portal_port = 3260
self.cfg.nexenta_rest_user = 'admin'
self.cfg.driver_ssl_cert_verify = False
self.cfg.nexenta_rest_password = 'admin'
self.cfg.nexenta_lun_container = NEDGE_BUCKET
self.cfg.nexenta_iscsi_service = NEDGE_SERVICE
self.cfg.nexenta_blocksize = NEDGE_BLOCKSIZE
self.cfg.nexenta_chunksize = NEDGE_CHUNKSIZE
self.cfg.nexenta_replication_count = 2
self.cfg.nexenta_encryption = True
self.cfg.replication_device = None
self.cfg.nexenta_iops_limit = 0
mock_exec = mock.Mock()
mock_exec.return_value = ('', '')
self.driver = iscsi.NexentaEdgeISCSIDriver(execute=mock_exec,
configuration=self.cfg)
self.api_patcher = mock.patch('cinder.volume.drivers.nexenta.'
'nexentaedge.jsonrpc.'
'NexentaEdgeJSONProxy.__call__')
self.mock_api = self.api_patcher.start()
self.mock_api.return_value = {
'data': {
'X-ISCSI-TargetName': ISCSI_TARGET_NAME,
'X-ISCSI-TargetID': 1}
}
self.driver.do_setup(self.context)
self.addCleanup(self.api_patcher.stop)
def test_check_do_setup(self):
self.assertEqual('%s1' % 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):
return {'data': {
'X-ISCSI-TargetName': ISCSI_TARGET_NAME,
'X-ISCSI-TargetID': 1,
'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.split('/')[0])
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):
return {'data': {
'X-ISCSI-TargetName': ISCSI_TARGET_NAME,
'X-ISCSI-TargetID': 1,
'X-VIPS': json.dumps(vips)}
}
self.mock_api.side_effect = my_side_effect
self.assertRaises(exception.NexentaException,
self.driver.do_setup, self.context)
def check_for_setup_error(self):
self.mock_api.side_effect = exception.VolumeBackendAPIException
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.check_for_setup_error)
@patch('cinder.volume.drivers.nexenta.nexentaedge.iscsi.'
'NexentaEdgeISCSIDriver._get_lu_number')
def test_create_volume(self, lun):
lun.return_value = 1
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,
'optionsObject': {
'ccow-replication-count': 2,
'ccow-encryption-enabled': True,
'ccow-iops-rate-lim': 0}
})
@patch('cinder.volume.drivers.nexenta.nexentaedge.iscsi.'
'NexentaEdgeISCSIDriver._get_lu_number')
def test_create_volume__vip(self, lun):
lun.return_value = 1
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',
'optionsObject': {
'ccow-replication-count': 2,
'ccow-encryption-enabled': True,
'ccow-iops-rate-lim': 0}
})
def test_create_volume_fail(self):
self.mock_api.side_effect = RuntimeError
self.assertRaises(RuntimeError, self.driver.create_volume, MOCK_VOL)
def test_delete_volume(self):
self.mock_api.side_effect = exception.VolumeBackendAPIException(
'No volume')
self.driver.delete_volume(MOCK_VOL)
self.mock_api.assert_called_with(NEDGE_URL, {
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id']
})
def test_delete_volume_fail(self):
self.mock_api.side_effect = RuntimeError
self.assertRaises(RuntimeError, self.driver.delete_volume, MOCK_VOL)
def test_extend_volume(self):
self.driver.extend_volume(MOCK_VOL, NEW_VOL_SIZE)
self.mock_api.assert_called_with(NEDGE_URL + '/resize', {
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
'newSizeMB': NEW_VOL_SIZE * 1024
})
def test_extend_volume_fail(self):
self.mock_api.side_effect = RuntimeError
self.assertRaises(RuntimeError, self.driver.extend_volume,
MOCK_VOL, NEW_VOL_SIZE)
def test_create_snapshot(self):
self.driver.create_snapshot(MOCK_SNAP)
self.mock_api.assert_called_with(NEDGE_URL + '/snapshot', {
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
'snapName': MOCK_SNAP['id']
})
def test_create_snapshot_fail(self):
self.mock_api.side_effect = RuntimeError
self.assertRaises(RuntimeError, self.driver.create_snapshot, MOCK_SNAP)
def test_delete_snapshot(self):
self.driver.delete_snapshot(MOCK_SNAP)
self.mock_api.assert_called_with(NEDGE_URL + '/snapshot', {
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
'snapName': MOCK_SNAP['id']
})
def test_delete_snapshot_fail(self):
self.mock_api.side_effect = RuntimeError
self.assertRaises(RuntimeError, self.driver.delete_snapshot, MOCK_SNAP)
def test_create_volume_from_snapshot(self):
self.driver.create_volume_from_snapshot(MOCK_VOL2, MOCK_SNAP)
self.mock_api.assert_called_with(NEDGE_URL + '/snapshot/clone', {
'objectPath': NEDGE_BUCKET + '/' + MOCK_SNAP['volume_name'],
'clonePath': NEDGE_BUCKET + '/' + MOCK_VOL2['id'],
'snapName': MOCK_SNAP['id']
})
def test_create_volume_from_snapshot_fail(self):
self.mock_api.side_effect = RuntimeError
self.assertRaises(RuntimeError,
self.driver.create_volume_from_snapshot,
MOCK_VOL2, MOCK_SNAP)
def test_create_cloned_volume(self):
self.driver.create_cloned_volume(MOCK_VOL2, MOCK_VOL)
url = '%s/snapshot/clone' % NEDGE_URL
self.mock_api.assert_called_with(url, {
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
'clonePath': NEDGE_BUCKET + '/' + MOCK_VOL2['id'],
'snapName': 'cinder-clone-snapshot-vol2'
})
def test_create_cloned_volume_larger(self):
self.driver.create_cloned_volume(MOCK_VOL3, MOCK_VOL)
# ignore the clone call, this has been tested before
self.mock_api.assert_called_with(NEDGE_URL + '/resize', {
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL3['id'],
'newSizeMB': MOCK_VOL3['size'] * 1024
})
def test_create_cloned_volume_fail(self):
self.mock_api.side_effect = RuntimeError
self.assertRaises(RuntimeError, self.driver.create_cloned_volume,
MOCK_VOL2, MOCK_VOL)

View File

@ -0,0 +1,330 @@
# Copyright 2015 Nexenta Systems, Inc.
# All Rights Reserved.
#
# 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.
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_utils import units
from cinder import exception
from cinder.i18n import _
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__)
@interface.volumedriver
class NexentaEdgeISCSIDriver(driver.ISCSIDriver):
"""Executes volume driver commands on NexentaEdge cluster.
Version history:
1.0.0 - Initial driver version.
1.0.1 - Moved opts to options.py.
1.0.2 - Added HA support.
1.0.3 - Added encryption and replication count support.
1.0.4 - Added initialize_connection.
1.0.5 - Driver re-introduced in OpenStack.
"""
VERSION = '1.0.5'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "Nexenta_Edge_CI"
def __init__(self, *args, **kwargs):
super(NexentaEdgeISCSIDriver, self).__init__(*args, **kwargs)
if self.configuration:
self.configuration.append_config_values(
options.NEXENTA_CONNECTION_OPTS)
self.configuration.append_config_values(
options.NEXENTA_ISCSI_OPTS)
self.configuration.append_config_values(
options.NEXENTA_DATASET_OPTS)
self.configuration.append_config_values(
options.NEXENTA_EDGE_OPTS)
if self.configuration.nexenta_rest_address:
self.restapi_host = self.configuration.nexenta_rest_address
else:
self.restapi_host = self.configuration.san_ip
if self.configuration.nexenta_rest_port:
self.restapi_port = self.configuration.nexenta_rest_port
else:
self.restapi_port = self.configuration.san_api_port
if self.configuration.nexenta_client_address:
self.target_vip = self.configuration.nexenta_client_address
else:
self.target_vip = self.configuration.target_ip_address
if self.configuration.nexenta_rest_password:
self.restapi_password = (
self.configuration.nexenta_rest_password)
else:
self.restapi_password = (
self.configuration.san_password)
if self.configuration.nexenta_rest_user:
self.restapi_user = self.configuration.nexenta_rest_user
else:
self.restapi_user = self.configuration.san_login
self.verify_ssl = self.configuration.driver_ssl_cert_verify
self.restapi_protocol = self.configuration.nexenta_rest_protocol
self.iscsi_service = self.configuration.nexenta_iscsi_service
self.bucket_path = self.configuration.nexenta_lun_container
self.blocksize = self.configuration.nexenta_blocksize
self.chunksize = self.configuration.nexenta_chunksize
self.cluster, self.tenant, self.bucket = self.bucket_path.split('/')
self.repcount = self.configuration.nexenta_replication_count
self.encryption = self.configuration.nexenta_encryption
self.iscsi_target_port = (self.configuration.
nexenta_iscsi_target_portal_port)
self.ha_vip = None
@property
def backend_name(self):
backend_name = None
if self.configuration:
backend_name = self.configuration.safe_get('volume_backend_name')
if not backend_name:
backend_name = self.__class__.__name__
return backend_name
def do_setup(self, context):
if self.restapi_protocol == 'auto':
protocol, auto = 'http', True
else:
protocol, auto = self.restapi_protocol, False
try:
self.restapi = jsonrpc.NexentaEdgeJSONProxy(
protocol, self.restapi_host, self.restapi_port, '/',
self.restapi_user, self.restapi_password,
self.verify_ssl, auto=auto)
data = self.restapi.get('service/' + self.iscsi_service)['data']
self.target_name = '%s%s' % (
data['X-ISCSI-TargetName'], data['X-ISCSI-TargetID'])
if 'X-VIPS' in data:
if self.target_vip not in data['X-VIPS']:
raise exception.NexentaException(
'Configured client IP address does not match any VIP'
' provided by iSCSI service %s' % self.iscsi_service)
else:
self.ha_vip = self.target_vip
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception('Error verifying iSCSI service %(serv)s on '
'host %(hst)s', {
'serv': self.iscsi_service,
'hst': self.restapi_host})
def check_for_setup_error(self):
url = 'clusters/%s/tenants/%s/buckets' % (self.cluster, self.tenant)
if self.bucket not in self.restapi.get(url):
raise exception.VolumeBackendAPIException(
message=_('Bucket %s does not exist') % self.bucket)
def _get_lu_number(self, volname):
rsp = self.restapi.get('service/' + self.iscsi_service + '/iscsi')
path = '%s/%s' % (self.bucket_path, volname)
for mapping in rsp['data']:
if mapping['objectPath'] == path:
return mapping['number']
return None
def _get_provider_location(self, volume):
lun = self._get_lu_number(volume['name'])
if not lun:
return None
return '%(host)s:%(port)s,1 %(name)s %(number)s' % {
'host': self.target_vip,
'port': self.iscsi_target_port,
'name': self.target_name,
'number': lun
}
def create_volume(self, volume):
data = {
'objectPath': '%s/%s' % (
self.bucket_path, volume['name']),
'volSizeMB': int(volume['size']) * units.Ki,
'blockSize': self.blocksize,
'chunkSize': self.chunksize,
'optionsObject': {
'ccow-replication-count': self.repcount,
'ccow-iops-rate-lim': self.configuration.nexenta_iops_limit}
}
if self.encryption:
data['optionsObject']['ccow-encryption-enabled'] = True
if self.ha_vip:
data['vip'] = self.ha_vip
try:
self.restapi.post('service/' + self.iscsi_service + '/iscsi', data)
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(
'Error creating LUN for volume %s', volume['name'])
return {'provider_location': self._get_provider_location(volume)}
def delete_volume(self, volume):
data = {
'objectPath': '%s/%s' % (
self.bucket_path, volume['name'])
}
try:
self.restapi.delete(
'service/' + self.iscsi_service + '/iscsi', data)
except exception.VolumeBackendAPIException:
LOG.info(
'Error deleting LUN for volume %s', volume['name'])
def create_export(self, context, volume, connector=None):
pass
def ensure_export(self, context, volume):
pass
def remove_export(self, context, volume):
pass
def initialize_connection(self, volume, connector):
return {
'driver_volume_type': 'iscsi',
'data': {
'target_discovered': False,
'encrypted': False,
'qos_specs': None,
'target_iqn': self.target_name,
'target_portal': '%s:%s' % (
self.target_vip, self.iscsi_target_port),
'volume_id': volume['id'],
'target_lun': self._get_lu_number(volume['name']),
'access_mode': 'rw',
}
}
def extend_volume(self, volume, new_size):
try:
self.restapi.put('service/' + self.iscsi_service + '/iscsi/resize',
{'objectPath': '%s/%s' % (
self.bucket_path, volume['name']),
'newSizeMB': new_size * units.Ki})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception('Error extending volume %s', volume['name'])
def create_volume_from_snapshot(self, volume, snapshot):
try:
self.restapi.put(
'service/' + self.iscsi_service + '/iscsi/snapshot/clone',
{
'objectPath': '%s/%s' % (
self.bucket_path, snapshot['volume_name']),
'clonePath': '%s/%s' % (
self.bucket_path, volume['name']),
'snapName': snapshot['name']
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(
'Error creating volume from snapshot %s', snapshot['name'])
if (('size' in volume) and (
volume['size'] > snapshot['volume_size'])):
self.extend_volume(volume, volume['size'])
def create_snapshot(self, snapshot):
try:
self.restapi.post(
'service/' + self.iscsi_service + '/iscsi/snapshot',
{
'objectPath': '%s/%s' % (
self.bucket_path, snapshot['volume_name']),
'snapName': snapshot['name']
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception('Error creating snapshot %s', snapshot['name'])
def delete_snapshot(self, snapshot):
try:
self.restapi.delete(
'service/' + self.iscsi_service + '/iscsi/snapshot',
{
'objectPath': '%s/%s' % (
self.bucket_path, snapshot['volume_name']),
'snapName': snapshot['name']
})
except exception.VolumeBackendAPIException:
LOG.info('Error deleting snapshot %s', snapshot['name'])
@staticmethod
def _get_clone_snapshot_name(volume):
"""Return name for snapshot that will be used to clone the volume."""
return 'cinder-clone-snapshot-%(id)s' % volume
def create_cloned_volume(self, volume, src_vref):
snapshot = {'volume_name': src_vref['name'],
'volume_id': src_vref['id'],
'volume_size': src_vref['size'],
'name': self._get_clone_snapshot_name(volume)}
LOG.debug('Creating temp snapshot of the original volume: '
'%s@%s', snapshot['volume_name'], snapshot['name'])
self.create_snapshot(snapshot)
try:
self.create_volume_from_snapshot(volume, snapshot)
except exception.NexentaException:
LOG.error('Volume creation failed, deleting created snapshot '
'%s', '@'.join([snapshot['volume_name'],
snapshot['name']]))
try:
self.delete_snapshot(snapshot)
except (exception.NexentaException, exception.SnapshotIsBusy):
LOG.warning('Failed to delete zfs snapshot '
'%s', '@'.join([snapshot['volume_name'],
snapshot['name']]))
raise
if volume['size'] > src_vref['size']:
self.extend_volume(volume, volume['size'])
def local_path(self, volume):
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.target_vip,
'bucket': self.bucket_path
}
return {
'vendor_name': 'Nexenta',
'driver_version': self.VERSION,
'storage_protocol': 'iSCSI',
'reserved_percentage': 0,
'total_capacity_gb': total,
'free_capacity_gb': free,
'QoS_support': False,
'volume_backend_name': self.backend_name,
'location_info': location_info,
'iscsi_target_portal_port': self.iscsi_target_port,
'restapi_url': self.restapi.url
}

View File

@ -0,0 +1,97 @@
# Copyright 2015 Nexenta Systems, Inc.
# All Rights Reserved.
#
# 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.
import json
import requests
from oslo_log import log as logging
from cinder import exception
from cinder.i18n import _
from cinder.utils import retry
LOG = logging.getLogger(__name__)
TIMEOUT = 60
class NexentaEdgeJSONProxy(object):
retry_exc_tuple = (
requests.exceptions.ConnectionError,
requests.exceptions.ConnectTimeout
)
def __init__(self, protocol, host, port, path, user, password, verify,
auto=False, method=None, session=None):
if session:
self.session = session
else:
self.session = requests.Session()
self.session.auth = (user, password)
self.session.headers.update({'Content-Type': 'application/json'})
self.protocol = protocol.lower()
self.verify = verify
self.host = host
self.port = port
self.path = path
self.user = user
self.password = password
self.auto = auto
self.method = method
@property
def url(self):
return '%s://%s:%s/%s' % (
self.protocol, self.host, self.port, self.path)
def __getattr__(self, name):
if name in ('get', 'post', 'put', 'delete'):
return NexentaEdgeJSONProxy(
self.protocol, self.host, self.port, self.path, self.user,
self.password, self.verify, self.auto, name, self.session)
return super(NexentaEdgeJSONProxy, self).__getattr__(name)
def __hash__(self):
return self.url.__hash__()
def __repr__(self):
return 'HTTP JSON proxy: %s' % self.url
@retry(retry_exc_tuple, interval=1, retries=6)
def __call__(self, *args):
self.path = args[0]
kwargs = {'timeout': TIMEOUT, 'verify': self.verify}
data = None
if len(args) > 1:
data = json.dumps(args[1])
kwargs['data'] = data
LOG.debug('Sending JSON data: %s, method: %s, data: %s',
self.url, self.method, data)
func = getattr(self.session, self.method)
if func:
req = func(self.url, **kwargs)
else:
raise exception.VolumeDriverException(
message=_('Unsupported method: %s') % self.method)
rsp = req.json()
LOG.debug('Got response: %s', rsp)
if rsp.get('response') is None:
raise exception.VolumeBackendAPIException(
data=_('Error response: %s') % rsp)
return rsp.get('response')

View File

@ -17,20 +17,22 @@ from oslo_config import cfg
from cinder.volume import configuration as conf
POLL_RETRIES = 5
DEFAULT_ISCSI_PORT = 3260
DEFAULT_HOST_GROUP = 'all'
DEFAULT_TARGET_GROUP = 'all'
NEXENTA_EDGE_OPTS = [
cfg.StrOpt('nexenta_nbd_symlinks_dir',
default='/dev/disk/by-path',
help='NexentaEdge logical path of directory to store symbolic '
'links to NBDs'),
cfg.StrOpt('nexenta_rest_address',
default='',
help='IP address of NexentaEdge management REST API endpoint'),
cfg.StrOpt('nexenta_rest_user',
default='admin',
help='User name to connect to NexentaEdge'),
help='User name to connect to NexentaEdge.'),
cfg.StrOpt('nexenta_rest_password',
default='nexenta',
help='Password to connect to NexentaEdge',
help='Password to connect to NexentaEdge.',
secret=True),
cfg.StrOpt('nexenta_lun_container',
default='',
@ -39,19 +41,42 @@ NEXENTA_EDGE_OPTS = [
default='',
help='NexentaEdge iSCSI service name'),
cfg.StrOpt('nexenta_client_address',
deprecated_for_removal=True,
deprecated_reason='iSCSI target address should now be set using'
' the common param target_ip_address.',
default='',
help='NexentaEdge iSCSI Gateway client '
'address for non-VIP service'),
cfg.IntOpt('nexenta_iops_limit',
default=0,
help='NexentaEdge iSCSI LUN object IOPS limit'),
cfg.IntOpt('nexenta_chunksize',
default=32768,
help='NexentaEdge iSCSI LUN object chunk size')
help='NexentaEdge iSCSI LUN object chunk size'),
cfg.IntOpt('nexenta_replication_count',
default=3,
help='NexentaEdge iSCSI LUN object replication count.'),
cfg.BoolOpt('nexenta_encryption',
default=False,
help='Defines whether NexentaEdge iSCSI LUN object '
'has encryption enabled.')
]
NEXENTA_CONNECTION_OPTS = [
cfg.StrOpt('nexenta_rest_address',
deprecated_for_removal=True,
deprecated_reason='Rest address should now be set using '
'the common param depending on driver type, '
'san_ip or nas_host',
default='',
help='IP address of NexentaEdge management REST API endpoint'),
cfg.StrOpt('nexenta_host',
default='',
help='IP address of Nexenta SA'),
cfg.IntOpt('nexenta_rest_port',
deprecated_for_removal=True,
deprecated_reason='Rest address should now be set using '
'the common param san_api_port.',
default=0,
help='HTTP(S) port to connect to Nexenta REST API server. '
'If it is equal zero, 8443 for HTTPS and 8080 for HTTP '
@ -63,31 +88,59 @@ NEXENTA_CONNECTION_OPTS = [
cfg.BoolOpt('nexenta_use_https',
default=True,
help='Use secure HTTP for REST connection (default True)'),
cfg.BoolOpt('nexenta_lu_writebackcache_disabled',
default=False,
help='Postponed write to backing store or not'),
cfg.StrOpt('nexenta_user',
deprecated_for_removal=True,
deprecated_reason='Common user parameters should be used '
'depending on the driver type: '
'san_login or nas_login',
default='admin',
help='User name to connect to Nexenta SA'),
cfg.StrOpt('nexenta_password',
deprecated_for_removal=True,
deprecated_reason='Common password parameters should be used '
'depending on the driver type: '
'san_password or nas_password',
default='nexenta',
help='Password to connect to Nexenta SA',
secret=True),
]
NEXENTA_ISCSI_OPTS = [
cfg.StrOpt('nexenta_iscsi_target_portal_groups',
default='',
help='Nexenta target portal groups'),
cfg.StrOpt('nexenta_iscsi_target_portals',
default='',
help='Comma separated list of portals for NexentaStor5, in'
'format of IP1:port1,IP2:port2. Port is optional, '
'default=3260. Example: 10.10.10.1:3267,10.10.1.2'),
cfg.StrOpt('nexenta_iscsi_target_host_group',
default='all',
help='Group of hosts which are allowed to access volumes'),
cfg.IntOpt('nexenta_iscsi_target_portal_port',
default=3260,
help='Nexenta target portal port'),
cfg.IntOpt('nexenta_luns_per_target',
default=100,
help='Amount of iSCSI LUNs per each target'),
cfg.StrOpt('nexenta_volume',
default='cinder',
help='SA Pool that holds all volumes'),
cfg.StrOpt('nexenta_target_prefix',
default='iqn.1986-03.com.sun:02:cinder-',
default='iqn.1986-03.com.sun:02:cinder',
help='IQN prefix for iSCSI targets'),
cfg.StrOpt('nexenta_target_group_prefix',
default='cinder/',
default='cinder',
help='Prefix for iSCSI target groups on SA'),
cfg.StrOpt('nexenta_host_group_prefix',
default='cinder',
help='Prefix for iSCSI host groups on SA'),
cfg.StrOpt('nexenta_volume_group',
default='iscsi',
help='Volume group for ns5'),
help='Volume group for NexentaStor5 iSCSI'),
]
NEXENTA_NFS_OPTS = [
@ -120,6 +173,9 @@ NEXENTA_DATASET_OPTS = [
default='off',
choices=['on', 'off', 'sha256', 'verify', 'sha256, verify'],
help='Deduplication value for new ZFS folders.'),
cfg.StrOpt('nexenta_folder',
default='',
help='A folder where cinder created datasets will reside.'),
cfg.StrOpt('nexenta_dataset_description',
default='',
help='Human-readable description for the folder.'),

View File

@ -0,0 +1,89 @@
===============================
NexentaEdge NBD & iSCSI drivers
===============================
NexentaEdge is designed from the ground-up to deliver high performance Block
and Object storage services and limitless scalability to next generation
OpenStack clouds, petabyte scale active archives and Big Data applications.
NexentaEdge runs on shared nothing clusters of industry standard Linux
servers, and builds on Nexenta IP and patent pending Cloud Copy On Write (CCOW)
technology to break new ground in terms of reliability, functionality and cost
efficiency.
For NexentaEdge user documentation, visit https://nexentaedge.github.io.
iSCSI driver
~~~~~~~~~~~~
The NexentaEdge cluster must be installed and configured according to the
relevant Nexenta documentation. A cluster, tenant, bucket must be pre-created,
as well as an iSCSI service on the NexentaEdge gateway node.
The NexentaEdge iSCSI driver is selected using the normal procedures for one
or multiple back-end volume drivers.
You must configure these items for each NexentaEdge cluster that the iSCSI
volume driver controls:
#. Make the following changes on the volume node ``/etc/cinder/cinder.conf``
file.
.. code-block:: ini
# Enable Nexenta iSCSI driver
volume_driver = cinder.volume.drivers.nexenta.nexentaedge.iscsi.NexentaEdgeISCSIDriver
# Specify the ip address for Rest API (string value)
nexenta_rest_address = MANAGEMENT-NODE-IP
# Port for Rest API (integer value)
nexenta_rest_port=8080
# Protocol used for Rest calls (string value, default=htpp)
nexenta_rest_protocol = http
# Username for NexentaEdge Rest (string value)
nexenta_rest_user=USERNAME
# Password for NexentaEdge Rest (string value)
nexenta_rest_password=PASSWORD
# Path to bucket containing iSCSI LUNs (string value)
nexenta_lun_container = CLUSTER/TENANT/BUCKET
# Name of pre-created iSCSI service (string value)
nexenta_iscsi_service = SERVICE-NAME
# IP address of the gateway node attached to iSCSI service above or
# virtual IP address if an iSCSI Storage Service Group is configured in
# HA mode (string value)
nexenta_client_address = GATEWAY-NODE-IP
#. Save the changes to the ``/etc/cinder/cinder.conf`` file and
restart the ``cinder-volume`` service.
Supported operations
--------------------
* Create, delete, attach, and detach volumes.
* Create, list, and delete volume snapshots.
* Create a volume from a snapshot.
* Copy an image to a volume.
* Copy a volume to an image.
* Clone a volume.
* Extend a volume.
Driver options
~~~~~~~~~~~~~~
Nexenta Driver supports these options:
.. include:: ../../tables/cinder-nexenta_edge.inc

View File

@ -58,6 +58,7 @@ Driver Configuration Reference
drivers/nec-storage-m-series-driver
drivers/netapp-volume-driver
drivers/nimble-volume-driver
drivers/nexentaedge-driver
drivers/nexentastor4-driver
drivers/nexentastor5-driver
drivers/prophetstor-dpl-driver

View File

@ -0,0 +1,46 @@
..
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-nexenta_edge:
.. list-table:: Description of NexentaEdge driver configuration options
:header-rows: 1
:class: config-ref-table
* - Configuration option = Default value
- Description
* - **[DEFAULT]**
-
* - ``nexenta_blocksize`` = ``4096``
- (Integer) Block size for datasets
* - ``nexenta_chunksize`` = ``32768``
- (Integer) NexentaEdge iSCSI LUN object chunk size
* - ``nexenta_client_address`` =
- (String) NexentaEdge iSCSI Gateway client address for non-VIP service
* - ``nexenta_iscsi_service`` =
- (String) NexentaEdge iSCSI service name
* - ``nexenta_iscsi_target_portal_port`` = ``3260``
- (Integer) Nexenta target portal port
* - ``nexenta_lun_container`` =
- (String) NexentaEdge logical path of bucket for LUNs
* - ``nexenta_rest_address`` =
- (String) IP address of NexentaEdge management REST API endpoint
* - ``nexenta_rest_password`` = ``nexenta``
- (String) Password to connect to NexentaEdge
* - ``nexenta_rest_port`` = ``8080``
- (Integer) HTTP port to connect to Nexenta REST API server
* - ``nexenta_rest_protocol`` = ``auto``
- (String) Use http or https for REST connection (default auto)
* - ``nexenta_rest_user`` = ``admin``
- (String) User name to connect to NexentaEdge
* - ``nexenta_replication_count`` = ``3``
- (String) NexentaEdge iSCSI LUN object replication count.
* - ``nexenta_encryption`` = ``False``
- (String) NexentaEdge iSCSI LUN object encryption

View File

@ -0,0 +1,4 @@
---
features:
- Added backend driver for Nexenta Edge iSCSI storage.