Add NexentaEdge drivers
NexentaEdge drivers were kicked from OpenStack pike release due to CI failures. This patch implements NexentaEdge iSCSI drivers. Change-Id: I1a2dd99fab742f7ad4d9aa470652bb5decb35359 Implements: blueprint nexentaedge-driver
This commit is contained in:
parent
9d263f711d
commit
e2bd03ef75
268
cinder/tests/unit/volume/drivers/nexenta/test_nexenta_edge.py
Normal file
268
cinder/tests/unit/volume/drivers/nexenta/test_nexenta_edge.py
Normal 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)
|
330
cinder/volume/drivers/nexenta/nexentaedge/iscsi.py
Normal file
330
cinder/volume/drivers/nexenta/nexentaedge/iscsi.py
Normal 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
|
||||||
|
}
|
97
cinder/volume/drivers/nexenta/nexentaedge/jsonrpc.py
Normal file
97
cinder/volume/drivers/nexenta/nexentaedge/jsonrpc.py
Normal 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')
|
@ -17,20 +17,22 @@ from oslo_config import cfg
|
|||||||
|
|
||||||
from cinder.volume import configuration as conf
|
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 = [
|
NEXENTA_EDGE_OPTS = [
|
||||||
cfg.StrOpt('nexenta_nbd_symlinks_dir',
|
cfg.StrOpt('nexenta_nbd_symlinks_dir',
|
||||||
default='/dev/disk/by-path',
|
default='/dev/disk/by-path',
|
||||||
help='NexentaEdge logical path of directory to store symbolic '
|
help='NexentaEdge logical path of directory to store symbolic '
|
||||||
'links to NBDs'),
|
'links to NBDs'),
|
||||||
cfg.StrOpt('nexenta_rest_address',
|
|
||||||
default='',
|
|
||||||
help='IP address of NexentaEdge management REST API endpoint'),
|
|
||||||
cfg.StrOpt('nexenta_rest_user',
|
cfg.StrOpt('nexenta_rest_user',
|
||||||
default='admin',
|
default='admin',
|
||||||
help='User name to connect to NexentaEdge'),
|
help='User name to connect to NexentaEdge.'),
|
||||||
cfg.StrOpt('nexenta_rest_password',
|
cfg.StrOpt('nexenta_rest_password',
|
||||||
default='nexenta',
|
default='nexenta',
|
||||||
help='Password to connect to NexentaEdge',
|
help='Password to connect to NexentaEdge.',
|
||||||
secret=True),
|
secret=True),
|
||||||
cfg.StrOpt('nexenta_lun_container',
|
cfg.StrOpt('nexenta_lun_container',
|
||||||
default='',
|
default='',
|
||||||
@ -39,19 +41,42 @@ NEXENTA_EDGE_OPTS = [
|
|||||||
default='',
|
default='',
|
||||||
help='NexentaEdge iSCSI service name'),
|
help='NexentaEdge iSCSI service name'),
|
||||||
cfg.StrOpt('nexenta_client_address',
|
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='',
|
default='',
|
||||||
help='NexentaEdge iSCSI Gateway client '
|
help='NexentaEdge iSCSI Gateway client '
|
||||||
'address for non-VIP service'),
|
'address for non-VIP service'),
|
||||||
|
cfg.IntOpt('nexenta_iops_limit',
|
||||||
|
default=0,
|
||||||
|
help='NexentaEdge iSCSI LUN object IOPS limit'),
|
||||||
cfg.IntOpt('nexenta_chunksize',
|
cfg.IntOpt('nexenta_chunksize',
|
||||||
default=32768,
|
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 = [
|
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',
|
cfg.StrOpt('nexenta_host',
|
||||||
default='',
|
default='',
|
||||||
help='IP address of Nexenta SA'),
|
help='IP address of Nexenta SA'),
|
||||||
cfg.IntOpt('nexenta_rest_port',
|
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,
|
default=0,
|
||||||
help='HTTP(S) port to connect to Nexenta REST API server. '
|
help='HTTP(S) port to connect to Nexenta REST API server. '
|
||||||
'If it is equal zero, 8443 for HTTPS and 8080 for HTTP '
|
'If it is equal zero, 8443 for HTTPS and 8080 for HTTP '
|
||||||
@ -63,31 +88,59 @@ NEXENTA_CONNECTION_OPTS = [
|
|||||||
cfg.BoolOpt('nexenta_use_https',
|
cfg.BoolOpt('nexenta_use_https',
|
||||||
default=True,
|
default=True,
|
||||||
help='Use secure HTTP for REST connection (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',
|
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',
|
default='admin',
|
||||||
help='User name to connect to Nexenta SA'),
|
help='User name to connect to Nexenta SA'),
|
||||||
cfg.StrOpt('nexenta_password',
|
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',
|
default='nexenta',
|
||||||
help='Password to connect to Nexenta SA',
|
help='Password to connect to Nexenta SA',
|
||||||
secret=True),
|
secret=True),
|
||||||
]
|
]
|
||||||
|
|
||||||
NEXENTA_ISCSI_OPTS = [
|
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',
|
cfg.IntOpt('nexenta_iscsi_target_portal_port',
|
||||||
default=3260,
|
default=3260,
|
||||||
help='Nexenta target portal port'),
|
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',
|
cfg.StrOpt('nexenta_volume',
|
||||||
default='cinder',
|
default='cinder',
|
||||||
help='SA Pool that holds all volumes'),
|
help='SA Pool that holds all volumes'),
|
||||||
cfg.StrOpt('nexenta_target_prefix',
|
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'),
|
help='IQN prefix for iSCSI targets'),
|
||||||
cfg.StrOpt('nexenta_target_group_prefix',
|
cfg.StrOpt('nexenta_target_group_prefix',
|
||||||
default='cinder/',
|
default='cinder',
|
||||||
help='Prefix for iSCSI target groups on SA'),
|
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',
|
cfg.StrOpt('nexenta_volume_group',
|
||||||
default='iscsi',
|
default='iscsi',
|
||||||
help='Volume group for ns5'),
|
help='Volume group for NexentaStor5 iSCSI'),
|
||||||
]
|
]
|
||||||
|
|
||||||
NEXENTA_NFS_OPTS = [
|
NEXENTA_NFS_OPTS = [
|
||||||
@ -120,6 +173,9 @@ NEXENTA_DATASET_OPTS = [
|
|||||||
default='off',
|
default='off',
|
||||||
choices=['on', 'off', 'sha256', 'verify', 'sha256, verify'],
|
choices=['on', 'off', 'sha256', 'verify', 'sha256, verify'],
|
||||||
help='Deduplication value for new ZFS folders.'),
|
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',
|
cfg.StrOpt('nexenta_dataset_description',
|
||||||
default='',
|
default='',
|
||||||
help='Human-readable description for the folder.'),
|
help='Human-readable description for the folder.'),
|
||||||
|
@ -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
|
@ -58,6 +58,7 @@ Driver Configuration Reference
|
|||||||
drivers/nec-storage-m-series-driver
|
drivers/nec-storage-m-series-driver
|
||||||
drivers/netapp-volume-driver
|
drivers/netapp-volume-driver
|
||||||
drivers/nimble-volume-driver
|
drivers/nimble-volume-driver
|
||||||
|
drivers/nexentaedge-driver
|
||||||
drivers/nexentastor4-driver
|
drivers/nexentastor4-driver
|
||||||
drivers/nexentastor5-driver
|
drivers/nexentastor5-driver
|
||||||
drivers/prophetstor-dpl-driver
|
drivers/prophetstor-dpl-driver
|
||||||
|
46
doc/source/configuration/tables/cinder-nexenta_edge.inc
Normal file
46
doc/source/configuration/tables/cinder-nexenta_edge.inc
Normal 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
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added backend driver for Nexenta Edge iSCSI storage.
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user