Remove unsupported NexentaEdge driver

This driver was marked as unsupported in the Ocata cycle due to
no CI. CI issues have not been resolved, so this now removes the
driver.

Checking name: Nexenta Edge CI
last seen: 2017-02-07 14:54:01 (0:30:45 old)
last success: NOT FOUND
success rate: 0%

Change-Id: I61672882a8c77f430c85393d219174788d34efcc
This commit is contained in:
Sean McGinnis
2017-02-07 09:57:54 -06:00
parent 45f983807a
commit b6f48edc64
6 changed files with 0 additions and 1538 deletions

View File

@@ -1,273 +0,0 @@
#
# 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 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_URL = 'service/isc/iscsi'
NEDGE_BUCKET = 'c/t/bk'
NEDGE_SERVICE = 'isc'
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'
}
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.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.replication_device = []
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': {'value': ISCSI_TARGET_STATUS}
}
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, {
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL['id'],
'volSizeMB': MOCK_VOL['size'] * 1024,
'blockSize': NEDGE_BLOCKSIZE,
'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)
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)
self.mock_api.assert_called_with(NEDGE_URL, {
'objectPath': NEDGE_BUCKET + '/' + MOCK_VOL2['id'],
'volSizeMB': MOCK_VOL2['size'] * 1024,
'blockSize': NEDGE_BLOCKSIZE,
'chunkSize': NEDGE_CHUNKSIZE
})
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

@@ -1,504 +0,0 @@
# Copyright 2016 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 socket
import mock
from mock import patch
from oslo_serialization import jsonutils
from oslo_utils import units
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 jsonrpc
from cinder.volume.drivers.nexenta.nexentaedge import nbd
class FakeResponse(object):
def __init__(self, response):
self.response = response
super(FakeResponse, self).__init__()
def json(self):
return self.response
def close(self):
pass
class RequestParams(object):
def __init__(self, scheme, host, port, user, password):
self.scheme = scheme.lower()
self.host = host
self.port = port
self.user = user
self.password = password
def url(self, path=''):
return '%s://%s:%s/%s' % (
self.scheme, self.host, self.port, path)
def build_post_args(self, args):
return jsonutils.dumps(args)
class TestNexentaEdgeNBDDriver(test.TestCase):
def setUp(self):
def _safe_get(opt):
return getattr(self.cfg, opt)
super(TestNexentaEdgeNBDDriver, self).setUp()
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_rest_protocol = 'http'
self.cfg.nexenta_rest_address = '127.0.0.1'
self.cfg.nexenta_rest_port = 8080
self.cfg.nexenta_rest_user = 'admin'
self.cfg.nexenta_rest_password = '0'
self.cfg.nexenta_lun_container = 'cluster/tenant/bucket'
self.cfg.nexenta_nbd_symlinks_dir = '/dev/disk/by-path'
self.cfg.volume_dd_blocksize = 512
self.cfg.nexenta_blocksize = 512
self.cfg.nexenta_chunksize = 4096
self.cfg.reserved_percentage = 0
self.cfg.replication_device = []
self.ctx = context.get_admin_context()
self.drv = nbd.NexentaEdgeNBDDriver(configuration=self.cfg)
session = mock.Mock()
session.get = mock.Mock()
session.post = mock.Mock()
session.put = mock.Mock()
session.delete = mock.Mock()
self.drv.do_setup(self.ctx)
self.drv.restapi.session = session
self.mock_api = session
self.request_params = RequestParams(
'http', self.cfg.nexenta_rest_address, self.cfg.nexenta_rest_port,
self.cfg.nexenta_rest_user, self.cfg.nexenta_rest_password)
def test_check_do_setup__symlinks_dir_not_specified(self):
self.drv.symlinks_dir = None
self.assertRaises(
exception.NexentaException, self.drv.check_for_setup_error)
def test_check_do_setup__symlinks_dir_doesnt_exist(self):
self.drv.symlinks_dir = '/some/random/path'
self.assertRaises(
exception.NexentaException, self.drv.check_for_setup_error)
@patch('os.path.exists')
def test_check_do_setup__empty_response(self, exists):
self.mock_api.get.return_value = FakeResponse({})
exists.return_value = True
self.assertRaises(exception.VolumeBackendAPIException,
self.drv.check_for_setup_error)
@patch('os.path.exists')
def test_check_do_setup(self, exists):
self.mock_api.get.return_value = FakeResponse({'response': 'OK'})
exists.return_value = True
self.drv.check_for_setup_error()
self.mock_api.get.assert_any_call(
self.request_params.url(self.drv.bucket_url + '/objects/'),
timeout=jsonrpc.TIMEOUT)
def test_local_path__error(self):
self.drv._get_nbd_number = lambda volume_: -1
volume = {'name': 'volume'}
self.assertRaises(exception.VolumeBackendAPIException,
self.drv.local_path, volume)
def test_local_path(self):
volume = {
'name': 'volume',
'host': 'myhost@backend#pool'
}
_get_host_info__response = {
'stats': {
'servers': {
'host1': {
'hostname': 'host1',
'ipv6addr': 'fe80::fc16:3eff:fedb:bd69'},
'host2': {
'hostname': 'myhost',
'ipv6addr': 'fe80::fc16:3eff:fedb:bd68'}
}
}
}
_get_nbd_devices__response = {
'value': jsonutils.dumps([
{
'objectPath': '/'.join(
(self.cfg.nexenta_lun_container, 'some_volume')),
'number': 1
},
{
'objectPath': '/'.join(
(self.cfg.nexenta_lun_container, volume['name'])),
'number': 2
}
])
}
def my_side_effect(*args, **kwargs):
if args[0] == self.request_params.url('system/stats'):
return FakeResponse({'response': _get_host_info__response})
elif args[0].startswith(
self.request_params.url('sysconfig/nbd/devices')):
return FakeResponse({'response': _get_nbd_devices__response})
else:
raise Exception('Unexpected request')
self.mock_api.get.side_effect = my_side_effect
self.drv.local_path(volume)
def test_local_path__host_not_found(self):
volume = {
'name': 'volume',
'host': 'unknown-host@backend#pool'
}
_get_host_info__response = {
'stats': {
'servers': {
'host1': {
'hostname': 'host1',
'ipv6addr': 'fe80::fc16:3eff:fedb:bd69'},
'host2': {
'hostname': 'myhost',
'ipv6addr': 'fe80::fc16:3eff:fedb:bd68'}
}
}
}
_get_nbd_devices__response = {
'value': jsonutils.dumps([
{
'objectPath': '/'.join(
(self.cfg.nexenta_lun_container, 'some_volume')),
'number': 1
},
{
'objectPath': '/'.join(
(self.cfg.nexenta_lun_container, volume['name'])),
'number': 2
}
])
}
def my_side_effect(*args, **kwargs):
if args[0] == self.request_params.url('system/stats'):
return FakeResponse({'response': _get_host_info__response})
elif args[0].startswith(
self.request_params.url('sysconfig/nbd/devices')):
return FakeResponse({'response': _get_nbd_devices__response})
else:
raise Exception('Unexpected request')
self.mock_api.get.side_effect = my_side_effect
self.assertRaises(exception.VolumeBackendAPIException,
self.drv.local_path, volume)
@patch('cinder.utils.execute')
def test_create_volume(self, execute):
self.mock_api.post.returning_value = FakeResponse({})
volume = {
'host': 'host@backend#pool info',
'size': 1,
'name': 'volume'
}
number = 5
remote_url = ''
self.drv._get_remote_url = lambda host_: remote_url
self.drv._get_nbd_number = lambda volume_: number
self.drv.create_volume(volume)
self.mock_api.post.assert_called_with(
self.request_params.url('nbd' + remote_url),
data=self.request_params.build_post_args({
'objectPath': '/'.join((self.cfg.nexenta_lun_container,
volume['name'])),
'volSizeMB': volume['size'] * units.Ki,
'blockSize': self.cfg.nexenta_blocksize,
'chunkSize': self.cfg.nexenta_chunksize}),
timeout=jsonrpc.TIMEOUT)
def test_delete_volume(self):
self.mock_api.delete.returning_value = FakeResponse({})
volume = {
'host': 'host@backend#pool info',
'size': 1,
'name': 'volume'
}
number = 5
remote_url = ''
self.drv._get_remote_url = lambda host_: remote_url
self.drv._get_nbd_number = lambda volume_: number
self.drv.delete_volume(volume)
self.mock_api.delete.assert_called_with(
self.request_params.url('nbd' + remote_url),
data=self.request_params.build_post_args({
'objectPath': '/'.join((self.cfg.nexenta_lun_container,
volume['name'])),
'number': number}),
timeout=jsonrpc.TIMEOUT)
def test_delete_volume__not_found(self):
self.mock_api.delete.returning_value = FakeResponse({})
volume = {
'host': 'host@backend#pool info',
'size': 1,
'name': 'volume'
}
remote_url = ''
self.drv._get_remote_url = lambda host_: remote_url
self.drv._get_nbd_number = lambda volume_: -1
self.drv.delete_volume(volume)
self.mock_api.delete.assert_not_called()
def test_extend_volume(self):
self.mock_api.put.returning_value = FakeResponse({})
volume = {
'host': 'host@backend#pool info',
'size': 1,
'name': 'volume'
}
new_size = 5
remote_url = ''
self.drv._get_remote_url = lambda host_: remote_url
self.drv.extend_volume(volume, new_size)
self.mock_api.put.assert_called_with(
self.request_params.url('nbd/resize' + remote_url),
data=self.request_params.build_post_args({
'objectPath': '/'.join((self.cfg.nexenta_lun_container,
volume['name'])),
'newSizeMB': new_size * units.Ki}),
timeout=jsonrpc.TIMEOUT)
def test_create_snapshot(self):
self.mock_api.post.returning_value = FakeResponse({})
snapshot = {
'name': 'dsfsdsdgfdf',
'volume_name': 'volume'
}
self.drv.create_snapshot(snapshot)
self.mock_api.post.assert_called_with(
self.request_params.url('nbd/snapshot'),
data=self.request_params.build_post_args({
'objectPath': '/'.join((self.cfg.nexenta_lun_container,
snapshot['volume_name'])),
'snapName': snapshot['name']}),
timeout=jsonrpc.TIMEOUT)
def test_delete_snapshot(self):
self.mock_api.delete.returning_value = FakeResponse({})
snapshot = {
'name': 'dsfsdsdgfdf',
'volume_name': 'volume'
}
self.drv.delete_snapshot(snapshot)
self.mock_api.delete.assert_called_with(
self.request_params.url('nbd/snapshot'),
data=self.request_params.build_post_args({
'objectPath': '/'.join((self.cfg.nexenta_lun_container,
snapshot['volume_name'])),
'snapName': snapshot['name']}),
timeout=jsonrpc.TIMEOUT)
def test_create_volume_from_snapshot(self):
self.mock_api.put.returning_value = FakeResponse({})
snapshot = {
'name': 'dsfsdsdgfdf',
'volume_size': 1,
'volume_name': 'volume'
}
volume = {
'host': 'host@backend#pool info',
'size': 2,
'name': 'volume'
}
remote_url = ''
self.drv._get_remote_url = lambda host_: remote_url
self.drv.extend_volume = lambda v, s: None
self.drv.create_volume_from_snapshot(volume, snapshot)
self.mock_api.put.assert_called_with(
self.request_params.url('nbd/snapshot/clone' + remote_url),
data=self.request_params.build_post_args({
'objectPath': '/'.join((self.cfg.nexenta_lun_container,
snapshot['volume_name'])),
'snapName': snapshot['name'],
'clonePath': '/'.join((self.cfg.nexenta_lun_container,
volume['name']))
}),
timeout=jsonrpc.TIMEOUT)
def test_create_cloned_volume(self):
self.mock_api.post.returning_value = FakeResponse({})
volume = {
'host': 'host@backend#pool info',
'size': 1,
'name': 'volume'
}
src_vref = {
'size': 1,
'name': 'qwerty'
}
container = self.cfg.nexenta_lun_container
remote_url = ''
self.drv._get_remote_url = lambda host_: remote_url
self.drv.create_cloned_volume(volume, src_vref)
self.mock_api.post.assert_called_with(
self.request_params.url('nbd' + remote_url),
data=self.request_params.build_post_args({
'objectPath': '/'.join((container, volume['name'])),
'volSizeMB': src_vref['size'] * units.Ki,
'blockSize': self.cfg.nexenta_blocksize,
'chunkSize': self.cfg.nexenta_chunksize
}),
timeout=jsonrpc.TIMEOUT)
def test_create_cloned_volume_gt_src(self):
self.mock_api.post.returning_value = FakeResponse({})
volume = {
'host': 'host@backend#pool info',
'size': 2,
'name': 'volume'
}
src_vref = {
'size': 1,
'name': 'qwerty'
}
container = self.cfg.nexenta_lun_container
remote_url = ''
self.drv._get_remote_url = lambda host_: remote_url
self.drv.create_cloned_volume(volume, src_vref)
self.mock_api.post.assert_called_with(
self.request_params.url('nbd' + remote_url),
data=self.request_params.build_post_args({
'objectPath': '/'.join((container, volume['name'])),
'volSizeMB': volume['size'] * units.Ki,
'blockSize': self.cfg.nexenta_blocksize,
'chunkSize': self.cfg.nexenta_chunksize
}),
timeout=jsonrpc.TIMEOUT)
def test_get_volume_stats(self):
self.cfg.volume_backend_name = None
self.mock_api.get.return_value = FakeResponse({
'response': {
'stats': {
'summary': {
'total_capacity': units.Gi,
'total_available': units.Gi
}
}
}
})
location_info = '%(driver)s:%(host)s:%(bucket)s' % {
'driver': self.drv.__class__.__name__,
'host': socket.gethostname(),
'bucket': self.cfg.nexenta_lun_container
}
expected = {
'vendor_name': 'Nexenta',
'driver_version': self.drv.VERSION,
'storage_protocol': 'NBD',
'reserved_percentage': self.cfg.reserved_percentage,
'total_capacity_gb': 1,
'free_capacity_gb': 1,
'QoS_support': False,
'volume_backend_name': self.drv.__class__.__name__,
'location_info': location_info,
'restapi_url': '%s://%s:%s/' % (
'http', self.cfg.nexenta_rest_address,
self.cfg.nexenta_rest_port)
}
self.assertEqual(expected, self.drv.get_volume_stats())
@patch('cinder.image.image_utils.fetch_to_raw')
def test_copy_image_to_volume(self, fetch_to_raw):
volume = {
'host': 'host@backend#pool info',
'size': 1,
'name': 'volume'
}
self.drv.local_path = lambda host: 'local_path'
self.drv.copy_image_to_volume(self.ctx, volume, 'image_service',
'image_id')
fetch_to_raw.assert_called_with(
self.ctx, 'image_service', 'image_id', 'local_path',
self.cfg.volume_dd_blocksize, size=volume['size'])
@patch('cinder.image.image_utils.upload_volume')
def test_copy_volume_to_image(self, upload_volume):
volume = {
'host': 'host@backend#pool info',
'size': 1,
'name': 'volume'
}
self.drv.local_path = lambda host: 'local_path'
self.drv.copy_volume_to_image(self.ctx, volume, 'image_service',
'image_meta')
upload_volume.assert_called_with(
self.ctx, 'image_service', 'image_meta', 'local_path')
def test_validate_connector(self):
connector = {'host': 'host2'}
r = {
'stats': {
'servers': {
'host1': {'hostname': 'host1'},
'host2': {'hostname': 'host2'}
}
}
}
self.mock_api.get.return_value = FakeResponse({'response': r})
self.drv.validate_connector(connector)
self.mock_api.get.assert_called_with(
self.request_params.url('system/stats'),
timeout=jsonrpc.TIMEOUT)
def test_validate_connector__host_not_found(self):
connector = {'host': 'host3'}
r = {
'stats': {
'servers': {
'host1': {'hostname': 'host1'},
'host2': {'hostname': 'host2'}
}
}
}
self.mock_api.get.return_value = FakeResponse({'response': r})
self.assertRaises(exception.VolumeBackendAPIException,
self.drv.validate_connector, connector)
def test_initialize_connection(self):
connector = {'host': 'host'}
volume = {
'host': 'host@backend#pool info',
'size': 1,
'name': 'volume'
}
self.drv.local_path = lambda host: 'local_path'
self.assertEqual({
'driver_volume_type': 'local',
'data': {'device_path': 'local_path'}},
self.drv.initialize_connection(volume, connector))

View File

@@ -1,315 +0,0 @@
# 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
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 _, _LE, _LI
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.
"""
VERSION = '1.0.2'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "Nexenta_Edge_CI"
# TODO(smcginnis) Either remove this if CI requirements are met, or
# remove this driver in the Pike release per normal deprecation
SUPPORTED = False
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)
self.restapi_protocol = self.configuration.nexenta_rest_protocol
self.restapi_host = self.configuration.nexenta_rest_address
self.restapi_port = self.configuration.nexenta_rest_port
self.restapi_user = self.configuration.nexenta_rest_user
self.restapi_password = self.configuration.nexenta_rest_password
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.bucket_url = ('clusters/' + self.cluster + '/tenants/' +
self.tenant + '/buckets/' + self.bucket)
self.iscsi_target_port = (self.configuration.
nexenta_iscsi_target_portal_port)
self.target_vip = None
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):
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:
protocol, auto = self.restapi_protocol, False
try:
self.restapi = jsonrpc.NexentaEdgeJSONProxy(
protocol, self.restapi_host, self.restapi_port, '/',
self.restapi_user, self.restapi_password, auto=auto)
rsp = self.restapi.get('service/'
+ self.iscsi_service + '/iscsi/status')
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'])
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:
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 '
'host %(hst)s'), {'serv': self.iscsi_service,
'hst': self.restapi_host})
def check_for_setup_error(self):
try:
self.restapi.get(self.bucket_url + '/objects/')
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error verifying LUN container %(bkt)s'),
{'bkt': self.bucket_path})
def _get_lun_number(self, volname):
try:
rsp = self.restapi.put(
'service/' + self.iscsi_service + '/iscsi/number',
{
'objectPath': self.bucket_path + '/' + volname
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error retrieving LUN %(vol)s number'),
{'vol': volname})
return rsp['data']
def _get_target_address(self, volname):
return self.target_vip
def _get_provider_location(self, volume):
return '%(host)s:%(port)s,1 %(name)s %(number)s' % {
'host': self._get_target_address(volume['name']),
'port': self.iscsi_target_port,
'name': self.target_name,
'number': self._get_lun_number(volume['name'])
}
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', data)
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating volume'))
def delete_volume(self, volume):
try:
self.restapi.delete('service/' + self.iscsi_service +
'/iscsi', {'objectPath': self.bucket_path +
'/' + volume['name']})
except exception.VolumeBackendAPIException:
LOG.info(
_LI('Volume was already deleted from appliance, skipping.'),
resource=volume)
def extend_volume(self, volume, new_size):
try:
self.restapi.put('service/' + self.iscsi_service + '/iscsi/resize',
{'objectPath': self.bucket_path +
'/' + volume['name'],
'newSizeMB': new_size * units.Ki})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error extending volume'))
def create_volume_from_snapshot(self, volume, snapshot):
try:
self.restapi.put(
'service/' + self.iscsi_service + '/iscsi/snapshot/clone',
{
'objectPath': self.bucket_path + '/' +
snapshot['volume_name'],
'clonePath': self.bucket_path + '/' + volume['name'],
'snapName': snapshot['name']
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error cloning volume'))
def create_snapshot(self, snapshot):
try:
self.restapi.post(
'service/' + self.iscsi_service + '/iscsi/snapshot',
{
'objectPath': self.bucket_path + '/' +
snapshot['volume_name'],
'snapName': snapshot['name']
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating snapshot'))
def delete_snapshot(self, snapshot):
try:
self.restapi.delete(
'service/' + self.iscsi_service + '/iscsi/snapshot',
{
'objectPath': self.bucket_path + '/' +
snapshot['volume_name'],
'snapName': snapshot['name']
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error deleting snapshot'))
def create_cloned_volume(self, volume, src_vref):
vol_url = (self.bucket_url + '/objects/' +
src_vref['name'] + '/clone')
clone_body = {
'tenant_name': self.tenant,
'bucket_name': self.bucket,
'object_name': volume['name']
}
try:
self.restapi.post(vol_url, clone_body)
self.restapi.post('service/' + self.iscsi_service + '/iscsi', {
'objectPath': self.bucket_path + '/' + volume['name'],
'volSizeMB': int(src_vref['size']) * units.Ki,
'blockSize': self.blocksize,
'chunkSize': self.chunksize
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating cloned volume'))
if volume['size'] > src_vref['size']:
self.extend_volume(volume, volume['size'])
def create_export(self, context, volume, connector=None):
return {'provider_location': self._get_provider_location(volume)}
def ensure_export(self, context, volume):
pass
def remove_export(self, context, volume):
pass
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._get_target_address(None),
'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

@@ -1,96 +0,0 @@
# 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, 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.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.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}
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

@@ -1,350 +0,0 @@
# Copyright 2016 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 os
import six
import socket
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 _, _LE, _LI
from cinder.image import image_utils
from cinder import interface
from cinder import utils as cinder_utils
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
from cinder.volume import utils as volutils
LOG = logging.getLogger(__name__)
@interface.volumedriver
class NexentaEdgeNBDDriver(driver.VolumeDriver):
"""Executes commands relating to NBD Volumes.
Version history:
1.0.0 - Initial driver version.
"""
VERSION = '1.0.0'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "Nexenta_Edge_CI"
# TODO(smcginnis) Either remove this if CI requirements are met, or
# remove this driver in the Pike release per normal deprecation
SUPPORTED = False
def __init__(self, vg_obj=None, *args, **kwargs):
LOG.debug('NexentaEdgeNBDDriver. Trying to initialize.')
super(NexentaEdgeNBDDriver, self).__init__(*args, **kwargs)
if self.configuration:
self.configuration.append_config_values(
options.NEXENTA_CONNECTION_OPTS)
self.configuration.append_config_values(
options.NEXENTA_DATASET_OPTS)
self.configuration.append_config_values(
options.NEXENTA_EDGE_OPTS)
self.restapi_protocol = self.configuration.nexenta_rest_protocol
self.restapi_host = self.configuration.nexenta_rest_address
self.restapi_port = self.configuration.nexenta_rest_port
self.restapi_user = self.configuration.nexenta_rest_user
self.restapi_password = self.configuration.nexenta_rest_password
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.bucket_url = ('clusters/' + self.cluster + '/tenants/' +
self.tenant + '/buckets/' + self.bucket)
self.hostname = socket.gethostname()
self.symlinks_dir = self.configuration.nexenta_nbd_symlinks_dir
self.reserved_percentage = self.configuration.reserved_percentage
LOG.debug('NexentaEdgeNBDDriver. Initialized successfully.')
@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
self.restapi = jsonrpc.NexentaEdgeJSONProxy(
protocol, self.restapi_host, self.restapi_port, '',
self.restapi_user, self.restapi_password, auto=auto)
def check_for_setup_error(self):
try:
if not self.symlinks_dir:
msg = _("nexenta_nbd_symlinks_dir option is not specified")
raise exception.NexentaException(message=msg)
if not os.path.exists(self.symlinks_dir):
msg = _("NexentaEdge NBD symlinks directory doesn't exist")
raise exception.NexentaException(message=msg)
self.restapi.get(self.bucket_url + '/objects/')
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error verifying container %(bkt)s'),
{'bkt': self.bucket_path})
def _get_nbd_devices(self, host):
try:
rsp = self.restapi.get('sysconfig/nbd/devices' +
self._get_remote_url(host))
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error getting NBD list'))
return json.loads(rsp['value'])
def _get_nbd_number(self, volume):
host = volutils.extract_host(volume['host'], 'host')
nbds = self._get_nbd_devices(host)
for dev in nbds:
if dev['objectPath'] == self.bucket_path + '/' + volume['name']:
return dev['number']
return -1
def _get_host_info(self, host):
try:
res = self.restapi.get('system/stats')
servers = res['stats']['servers']
for sid in servers:
if host == sid or host == servers[sid]['hostname']:
return servers[sid]
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error getting host info'))
raise exception.VolumeBackendAPIException(
data=_('No %s hostname in NEdge cluster') % host)
def _get_remote_url(self, host):
return '?remote=' + (
six.text_type(self._get_host_info(host)['ipv6addr']))
def _get_symlink_path(self, number):
return os.path.join(self.symlinks_dir, 'nbd' + six.text_type(number))
def local_path(self, volume):
number = self._get_nbd_number(volume)
if number == -1:
msg = _('No NBD device for volume %s') % volume['name']
raise exception.VolumeBackendAPIException(data=msg)
return self._get_symlink_path(number)
def create_volume(self, volume):
LOG.debug('Create volume')
host = volutils.extract_host(volume['host'], 'host')
try:
self.restapi.post('nbd' + self._get_remote_url(host), {
'objectPath': self.bucket_path + '/' + volume['name'],
'volSizeMB': int(volume['size']) * units.Ki,
'blockSize': self.blocksize,
'chunkSize': self.chunksize
})
number = self._get_nbd_number(volume)
cinder_utils.execute(
'ln', '--symbolic', '--force',
'/dev/nbd' + six.text_type(number),
self._get_symlink_path(number), run_as_root=True,
check_exit_code=True)
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating volume'))
def delete_volume(self, volume):
LOG.debug('Delete volume')
number = self._get_nbd_number(volume)
if number == -1:
LOG.info(_LI('Volume %(volume)s does not exist at %(path)s '
'path') % {
'volume': volume['name'],
'path': self.bucket_path
})
return
host = volutils.extract_host(volume['host'], 'host')
try:
self.restapi.delete('nbd' + self._get_remote_url(host), {
'objectPath': self.bucket_path + '/' + volume['name'],
'number': number
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error deleting volume'))
def extend_volume(self, volume, new_size):
LOG.debug('Extend volume')
host = volutils.extract_host(volume['host'], 'host')
try:
self.restapi.put('nbd/resize' + self._get_remote_url(host), {
'objectPath': self.bucket_path + '/' + volume['name'],
'newSizeMB': new_size * units.Ki
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error extending volume'))
def create_snapshot(self, snapshot):
LOG.debug('Create snapshot')
try:
self.restapi.post('nbd/snapshot', {
'objectPath': self.bucket_path + '/' + snapshot['volume_name'],
'snapName': snapshot['name']
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating snapshot'))
def delete_snapshot(self, snapshot):
LOG.debug('Delete snapshot')
# There is no way to figure out whether a snapshot exists in current
# version of the API. This REST function always reports OK even a
# snapshot doesn't exist.
try:
self.restapi.delete('nbd/snapshot', {
'objectPath': self.bucket_path + '/' + snapshot['volume_name'],
'snapName': snapshot['name']
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error deleting snapshot'))
def create_volume_from_snapshot(self, volume, snapshot):
LOG.debug('Create volume from snapshot')
host = volutils.extract_host(volume['host'], 'host')
remotehost = self._get_remote_url(host)
try:
self.restapi.put('nbd/snapshot/clone' + remotehost, {
'objectPath': self.bucket_path + '/' + snapshot['volume_name'],
'snapName': snapshot['name'],
'clonePath': self.bucket_path + '/' + volume['name']
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error cloning snapshot'))
if volume['size'] > snapshot['volume_size']:
self.extend_volume(volume, volume['size'])
def create_cloned_volume(self, volume, src_vref):
LOG.debug('Create cloned volume')
vol_url = (self.bucket_url + '/objects/' +
src_vref['name'] + '/clone')
clone_body = {
'tenant_name': self.tenant,
'bucket_name': self.bucket,
'object_name': volume['name']
}
host = volutils.extract_host(volume['host'], 'host')
size = volume['size'] if volume['size'] > src_vref['size'] else (
src_vref['size'])
try:
self.restapi.post(vol_url, clone_body)
self.restapi.post('nbd' + self._get_remote_url(host), {
'objectPath': self.bucket_path + '/' + volume['name'],
'volSizeMB': int(size) * units.Ki,
'blockSize': self.blocksize,
'chunkSize': self.chunksize
})
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating cloned volume'))
def get_volume_stats(self, refresh=False):
LOG.debug('Get volume stats')
try:
resp = self.restapi.get('system/stats')
location_info = '%(driver)s:%(host)s:%(bucket)s' % {
'driver': self.__class__.__name__,
'host': self.hostname,
'bucket': self.bucket_path
}
summary = resp['stats']['summary']
total = nexenta_utils.str2gib_size(summary['total_capacity'])
free = nexenta_utils.str2gib_size(summary['total_available'])
return {
'vendor_name': 'Nexenta',
'driver_version': self.VERSION,
'storage_protocol': 'NBD',
'reserved_percentage': self.reserved_percentage,
'total_capacity_gb': total,
'free_capacity_gb': free,
'QoS_support': False,
'volume_backend_name': self.backend_name,
'location_info': location_info,
'restapi_url': self.restapi.url
}
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error creating snapshot'))
def copy_image_to_volume(self, context, volume, image_service, image_id):
LOG.debug('Copy image to volume')
image_utils.fetch_to_raw(context,
image_service,
image_id,
self.local_path(volume),
self.configuration.volume_dd_blocksize,
size=volume['size'])
def copy_volume_to_image(self, context, volume, image_service, image_meta):
LOG.debug('Copy volume to image')
image_utils.upload_volume(context,
image_service,
image_meta,
self.local_path(volume))
def ensure_export(self, context, volume):
pass
def create_export(self, context, volume, connector, vg=None):
pass
def remove_export(self, context, volume):
pass
def validate_connector(self, connector):
LOG.debug('Validate connector')
try:
res = self.restapi.get('system/stats')
servers = res['stats']['servers']
for sid in servers:
if (connector['host'] == sid or
connector['host'] == servers[sid]['hostname']):
return
except exception.VolumeBackendAPIException:
with excutils.save_and_reraise_exception():
LOG.exception(_LE('Error retrieving cluster stats'))
raise exception.VolumeBackendAPIException(
data=_('No %s hostname in NEdge cluster') % connector['host'])
def initialize_connection(self, volume, connector, initiator_data=None):
LOG.debug('Initialize connection')
return {
'driver_volume_type': 'local',
'data': {'device_path': self.local_path(volume)},
}