+++ /dev/null
-#
-# Copyright 2011 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.
-"""
-Unit tests for OpenStack Cinder volume driver
-"""
-
-import base64
-import urllib2
-
-import mox as mox_lib
-from oslo_utils import units
-
-from cinder import context
-from cinder import db
-from cinder import test
-from cinder.volume import configuration as conf
-from cinder.volume.drivers import nexenta
-from cinder.volume.drivers.nexenta import iscsi
-from cinder.volume.drivers.nexenta import jsonrpc
-from cinder.volume.drivers.nexenta import nfs
-from cinder.volume.drivers.nexenta import utils
-
-
-class TestNexentaISCSIDriver(test.TestCase):
- TEST_VOLUME_NAME = 'volume1'
- TEST_VOLUME_NAME2 = 'volume2'
- TEST_SNAPSHOT_NAME = 'snapshot1'
- TEST_VOLUME_REF = {
- 'name': TEST_VOLUME_NAME,
- 'size': 1,
- 'id': '1',
- 'status': 'available'
- }
- TEST_VOLUME_REF2 = {
- 'name': TEST_VOLUME_NAME2,
- 'size': 1,
- 'id': '2',
- 'status': 'in-use'
- }
- TEST_SNAPSHOT_REF = {
- 'name': TEST_SNAPSHOT_NAME,
- 'volume_name': TEST_VOLUME_NAME,
- }
-
- def __init__(self, method):
- super(TestNexentaISCSIDriver, self).__init__(method)
-
- def setUp(self):
- super(TestNexentaISCSIDriver, self).setUp()
- self.configuration = mox_lib.MockObject(conf.Configuration)
- self.configuration.nexenta_host = '1.1.1.1'
- self.configuration.nexenta_user = 'admin'
- self.configuration.nexenta_password = 'nexenta'
- self.configuration.nexenta_volume = 'cinder'
- self.configuration.nexenta_rest_port = 2000
- self.configuration.nexenta_rest_protocol = 'http'
- self.configuration.nexenta_iscsi_target_portal_port = 3260
- self.configuration.nexenta_target_prefix = 'iqn:'
- self.configuration.nexenta_target_group_prefix = 'cinder/'
- self.configuration.nexenta_blocksize = '8K'
- self.configuration.nexenta_sparse = True
- self.configuration.nexenta_rrmgr_compression = 1
- self.configuration.nexenta_rrmgr_tcp_buf_size = 1024
- self.configuration.nexenta_rrmgr_connections = 2
- self.nms_mock = self.mox.CreateMockAnything()
- for mod in ['volume', 'zvol', 'iscsitarget', 'appliance',
- 'stmf', 'scsidisk', 'snapshot']:
- setattr(self.nms_mock, mod, self.mox.CreateMockAnything())
- self.stubs.Set(jsonrpc, 'NexentaJSONProxy',
- lambda *_, **__: self.nms_mock)
- self.drv = iscsi.NexentaISCSIDriver(configuration=self.configuration)
- self.drv.do_setup({})
-
- def test_setup_error(self):
- self.nms_mock.volume.object_exists('cinder').AndReturn(True)
- self.mox.ReplayAll()
- self.drv.check_for_setup_error()
-
- def test_setup_error_fail(self):
- self.nms_mock.volume.object_exists('cinder').AndReturn(False)
- self.mox.ReplayAll()
- self.assertRaises(LookupError, self.drv.check_for_setup_error)
-
- def test_local_path(self):
- self.assertRaises(NotImplementedError, self.drv.local_path, '')
-
- def test_create_volume(self):
- self.nms_mock.zvol.create('cinder/volume1', '1G', '8K', True)
- self.nms_mock.stmf.list_targets()
- self.nms_mock.iscsitarget.create_target({'target_name': 'iqn:volume1'})
- self.nms_mock.stmf.list_targetgroups()
- self.nms_mock.stmf.create_targetgroup('cinder/volume1')
- self.nms_mock.stmf.list_targetgroup_members('cinder/volume1')
- self.nms_mock.stmf.add_targetgroup_member('cinder/volume1',
- 'iqn:volume1')
- self.nms_mock.scsidisk.lu_exists('cinder/volume1')
- self.nms_mock.scsidisk.create_lu('cinder/volume1', {})
- self.nms_mock.scsidisk.lu_shared('cinder/volume1')
- self.nms_mock.scsidisk.add_lun_mapping_entry(
- 'cinder/volume1', {'target_group': 'cinder/volume1'})
- self.mox.ReplayAll()
- self.drv.create_volume(self.TEST_VOLUME_REF)
-
- def test_delete_volume(self):
- self.nms_mock.zvol.get_child_props('cinder/volume1',
- 'origin').AndReturn({})
- self.nms_mock.zvol.destroy('cinder/volume1', '')
- self.mox.ReplayAll()
- self.drv.delete_volume(self.TEST_VOLUME_REF)
- self.mox.ResetAll()
-
- c = self.nms_mock.zvol.get_child_props('cinder/volume1', 'origin')
- c.AndReturn({'origin': 'cinder/volume0@snapshot'})
- self.nms_mock.zvol.destroy('cinder/volume1', '')
- self.mox.ReplayAll()
- self.drv.delete_volume(self.TEST_VOLUME_REF)
- self.mox.ResetAll()
-
- c = self.nms_mock.zvol.get_child_props('cinder/volume1', 'origin')
- c.AndReturn({'origin': 'cinder/volume0@cinder-clone-snapshot-1'})
- self.nms_mock.zvol.destroy('cinder/volume1', '')
- self.nms_mock.snapshot.destroy(
- 'cinder/volume0@cinder-clone-snapshot-1', '')
- self.mox.ReplayAll()
- self.drv.delete_volume(self.TEST_VOLUME_REF)
- self.mox.ResetAll()
-
- def test_create_cloned_volume(self):
- vol = self.TEST_VOLUME_REF2
- src_vref = self.TEST_VOLUME_REF
- snapshot = {
- 'volume_name': src_vref['name'],
- 'name': 'cinder-clone-snapshot-%s' % vol['id'],
- }
- self.nms_mock.zvol.create_snapshot('cinder/%s' % src_vref['name'],
- snapshot['name'], '')
- self.nms_mock.zvol.clone('cinder/%s@%s' % (src_vref['name'],
- snapshot['name']),
- 'cinder/%s' % vol['name'])
- self.mox.ReplayAll()
- self.drv.create_cloned_volume(vol, src_vref)
-
- def test_migrate_volume(self):
- volume = self.TEST_VOLUME_REF
- host = {
- 'capabilities': {
- 'vendor_name': 'Nexenta',
- 'location_info': 'NexentaISCSIDriver:1.1.1.1:cinder',
- 'free_capacity_gb': 1,
- 'iscsi_target_portal_port': 3260,
- 'nms_url': 'http://admin:password@1.1.1.1:2000'
- }
- }
- snapshot = {
- 'volume_name': volume['name'],
- 'name': 'cinder-migrate-snapshot-%s' % volume['id'],
- }
- self.nms_mock.appliance.ssh_list_bindings().AndReturn([])
- self.nms_mock.zvol.create_snapshot('cinder/%s' % volume['name'],
- snapshot['name'], '')
-
- src = '%(volume)s/%(zvol)s@%(snapshot)s' % {
- 'volume': 'cinder',
- 'zvol': volume['name'],
- 'snapshot': snapshot['name']
- }
- dst = '1.1.1.1:cinder'
- cmd = ' '.join(['rrmgr -s zfs -c 1 -q -e -w 1024 -n 2', src, dst])
-
- self.nms_mock.appliance.execute(cmd)
-
- snapshot_name = 'cinder/%(volume)s@%(snapshot)s' % {
- 'volume': volume['name'],
- 'snapshot': snapshot['name']
- }
- self.nms_mock.snapshot.destroy(snapshot_name, '')
- volume_name = 'cinder/%s' % volume['name']
- self.nms_mock.zvol.get_child_props(volume_name,
- 'origin').AndReturn(None)
- self.nms_mock.zvol.destroy(volume_name, '')
- self.nms_mock.snapshot.destroy('cinder/%(volume)s@%(snapshot)s' % {
- 'volume': volume['name'],
- 'snapshot': snapshot['name']
- }, '')
-
- self.mox.ReplayAll()
- self.drv.migrate_volume(None, volume, host)
-
- def test_create_snapshot(self):
- self.nms_mock.zvol.create_snapshot('cinder/volume1', 'snapshot1', '')
- self.mox.ReplayAll()
- self.drv.create_snapshot(self.TEST_SNAPSHOT_REF)
-
- def test_create_volume_from_snapshot(self):
- self.nms_mock.zvol.clone('cinder/volume1@snapshot1', 'cinder/volume2')
- self.mox.ReplayAll()
- self.drv.create_volume_from_snapshot(self.TEST_VOLUME_REF2,
- self.TEST_SNAPSHOT_REF)
-
- def test_delete_snapshot(self):
- self.nms_mock.snapshot.destroy('cinder/volume1@snapshot1', '')
- self.mox.ReplayAll()
- self.drv.delete_snapshot(self.TEST_SNAPSHOT_REF)
- self.mox.ResetAll()
-
- # Check that exception not raised if snapshot does not exist
- mock = self.nms_mock.snapshot.destroy('cinder/volume1@snapshot1', '')
- mock.AndRaise(nexenta.NexentaException(
- 'Snapshot cinder/volume1@snapshot1 does not exist'))
- self.mox.ReplayAll()
- self.drv.delete_snapshot(self.TEST_SNAPSHOT_REF)
-
- _CREATE_EXPORT_METHODS = [
- ('stmf', 'list_targets', tuple(), [], False, ),
- ('iscsitarget', 'create_target', ({'target_name': 'iqn:volume1'},),
- u'Unable to create iscsi target\n'
- u' iSCSI target iqn.1986-03.com.sun:02:cinder-volume1 already'
- u' configured\n'
- u' itadm create-target failed with error 17\n', True, ),
- ('stmf', 'list_targetgroups', tuple(), [], False, ),
- ('stmf', 'create_targetgroup', ('cinder/volume1',),
- u'Unable to create targetgroup: stmfadm: cinder/volume1:'
- u' already exists\n', True, ),
- ('stmf', 'list_targetgroup_members', ('cinder/volume1', ), [],
- False, ),
- ('stmf', 'add_targetgroup_member', ('cinder/volume1', 'iqn:volume1'),
- u'Unable to add member to targetgroup: stmfadm:'
- u' iqn.1986-03.com.sun:02:cinder-volume1: already exists\n',
- True, ),
- ('scsidisk', 'lu_exists', ('cinder/volume1', ), 0, False, ),
- ('scsidisk', 'create_lu', ('cinder/volume1', {}),
- u"Unable to create lu with zvol 'cinder/volume1':\n"
- u" sbdadm: filename /dev/zvol/rdsk/cinder/volume1: in use\n",
- True, ),
- ('scsidisk', 'lu_shared', ('cinder/volume1', ), 0, False, ),
- ('scsidisk', 'add_lun_mapping_entry', ('cinder/volume1', {
- 'target_group': 'cinder/volume1'}),
- u"Unable to add view to zvol 'cinder/volume1' (LUNs in use: ):\n"
- u" stmfadm: view entry exists\n", True, ),
- ]
-
- def _stub_export_method(self, module, method, args, error, raise_exception,
- fail=False):
- m = getattr(self.nms_mock, module)
- m = getattr(m, method)
- mock = m(*args)
- if raise_exception and fail:
- mock.AndRaise(nexenta.NexentaException(error))
- else:
- mock.AndReturn(error)
-
- def _stub_all_export_methods(self, fail=False):
- for params in self._CREATE_EXPORT_METHODS:
- self._stub_export_method(*params, fail=fail)
-
- def test_create_export(self):
- self._stub_all_export_methods()
- self.mox.ReplayAll()
- retval = self.drv.create_export({}, self.TEST_VOLUME_REF)
- location = '%(host)s:%(port)s,1 %(prefix)s%(volume)s 0' % {
- 'host': self.configuration.nexenta_host,
- 'port': self.configuration.nexenta_iscsi_target_portal_port,
- 'prefix': self.configuration.nexenta_target_prefix,
- 'volume': self.TEST_VOLUME_NAME
- }
- self.assertEqual(retval, {'provider_location': location})
-
- def __get_test(i):
- def _test_create_export_fail(self):
- for params in self._CREATE_EXPORT_METHODS[:i]:
- self._stub_export_method(*params)
- self._stub_export_method(*self._CREATE_EXPORT_METHODS[i],
- fail=True)
- self.mox.ReplayAll()
- self.assertRaises(nexenta.NexentaException,
- self.drv.create_export,
- {},
- self.TEST_VOLUME_REF)
- return _test_create_export_fail
-
- for i in range(len(_CREATE_EXPORT_METHODS)):
- if i % 2:
- locals()['test_create_export_fail_%d' % i] = __get_test(i)
-
- def test_ensure_export(self):
- self._stub_all_export_methods(fail=True)
- self.mox.ReplayAll()
- self.drv.ensure_export({}, self.TEST_VOLUME_REF)
-
- def test_remove_export(self):
- self.nms_mock.scsidisk.delete_lu('cinder/volume1')
- self.nms_mock.stmf.destroy_targetgroup('cinder/volume1')
- self.nms_mock.iscsitarget.delete_target('iqn:volume1')
- self.mox.ReplayAll()
- self.drv.remove_export({}, self.TEST_VOLUME_REF)
-
- def test_remove_export_fail_0(self):
- self.nms_mock.scsidisk.delete_lu('cinder/volume1')
- self.nms_mock.stmf.destroy_targetgroup(
- 'cinder/volume1').AndRaise(nexenta.NexentaException())
- self.nms_mock.iscsitarget.delete_target('iqn:volume1')
- self.mox.ReplayAll()
- self.drv.remove_export({}, self.TEST_VOLUME_REF)
-
- def test_remove_export_fail_1(self):
- self.nms_mock.scsidisk.delete_lu('cinder/volume1')
- self.nms_mock.stmf.destroy_targetgroup('cinder/volume1')
- self.nms_mock.iscsitarget.delete_target(
- 'iqn:volume1').AndRaise(nexenta.NexentaException())
- self.mox.ReplayAll()
- self.drv.remove_export({}, self.TEST_VOLUME_REF)
-
- def test_get_volume_stats(self):
- stats = {'size': '5368709120G',
- 'used': '5368709120G',
- 'available': '5368709120G',
- 'health': 'ONLINE'}
- self.nms_mock.volume.get_child_props(
- self.configuration.nexenta_volume,
- 'health|size|used|available').AndReturn(stats)
- self.mox.ReplayAll()
- stats = self.drv.get_volume_stats(True)
- self.assertEqual(stats['storage_protocol'], 'iSCSI')
- self.assertEqual(stats['total_capacity_gb'], 5368709120.0)
- self.assertEqual(stats['free_capacity_gb'], 5368709120.0)
- self.assertEqual(stats['reserved_percentage'], 0)
- self.assertEqual(stats['QoS_support'], False)
-
-
-class TestNexentaJSONRPC(test.TestCase):
- HOST = 'example.com'
- URL = 'http://%s/' % HOST
- URL_S = 'https://%s/' % HOST
- USER = 'user'
- PASSWORD = 'password'
- HEADERS = {
- 'Authorization':
- 'Basic %s' % base64.b64encode('%s:%s' % (USER, PASSWORD)),
- 'Content-Type': 'application/json'
- }
- REQUEST = 'the request'
-
- def setUp(self):
- super(TestNexentaJSONRPC, self).setUp()
- self.proxy = jsonrpc.NexentaJSONProxy(
- 'http', self.HOST, 2000, '/', self.USER, self.PASSWORD, auto=True)
- self.mox.StubOutWithMock(urllib2, 'Request', True)
- self.mox.StubOutWithMock(urllib2, 'urlopen')
- self.resp_mock = self.mox.CreateMockAnything()
- self.resp_info_mock = self.mox.CreateMockAnything()
- self.resp_mock.info().AndReturn(self.resp_info_mock)
- urllib2.urlopen(self.REQUEST).AndReturn(self.resp_mock)
-
- def test_call(self):
- urllib2.Request(
- 'http://%s:2000/' % self.HOST,
- '{"object": null, "params": ["arg1", "arg2"], "method": null}',
- self.HEADERS).AndReturn(self.REQUEST)
- self.resp_info_mock.status = ''
- self.resp_mock.read().AndReturn(
- '{"error": null, "result": "the result"}')
- self.mox.ReplayAll()
- result = self.proxy('arg1', 'arg2')
- self.assertEqual("the result", result)
-
- def test_call_deep(self):
- urllib2.Request(
- 'http://%s:2000/' % self.HOST,
- '{"object": "obj1.subobj", "params": ["arg1", "arg2"],'
- ' "method": "meth"}',
- self.HEADERS).AndReturn(self.REQUEST)
- self.resp_info_mock.status = ''
- self.resp_mock.read().AndReturn(
- '{"error": null, "result": "the result"}')
- self.mox.ReplayAll()
- result = self.proxy.obj1.subobj.meth('arg1', 'arg2')
- self.assertEqual("the result", result)
-
- def test_call_auto(self):
- urllib2.Request(
- 'http://%s:2000/' % self.HOST,
- '{"object": null, "params": ["arg1", "arg2"], "method": null}',
- self.HEADERS).AndReturn(self.REQUEST)
- urllib2.Request(
- 'https://%s:2000/' % self.HOST,
- '{"object": null, "params": ["arg1", "arg2"], "method": null}',
- self.HEADERS).AndReturn(self.REQUEST)
- self.resp_info_mock.status = 'EOF in headers'
- self.resp_mock.read().AndReturn(
- '{"error": null, "result": "the result"}')
- urllib2.urlopen(self.REQUEST).AndReturn(self.resp_mock)
- self.mox.ReplayAll()
- result = self.proxy('arg1', 'arg2')
- self.assertEqual("the result", result)
-
- def test_call_error(self):
- urllib2.Request(
- 'http://%s:2000/' % self.HOST,
- '{"object": null, "params": ["arg1", "arg2"], "method": null}',
- self.HEADERS).AndReturn(self.REQUEST)
- self.resp_info_mock.status = ''
- self.resp_mock.read().AndReturn(
- '{"error": {"message": "the error"}, "result": "the result"}')
- self.mox.ReplayAll()
- self.assertRaises(jsonrpc.NexentaJSONException,
- self.proxy, 'arg1', 'arg2')
-
- def test_call_fail(self):
- urllib2.Request(
- 'http://%s:2000/' % self.HOST,
- '{"object": null, "params": ["arg1", "arg2"], "method": null}',
- self.HEADERS).AndReturn(self.REQUEST)
- self.resp_info_mock.status = 'EOF in headers'
- self.proxy.auto = False
- self.mox.ReplayAll()
- self.assertRaises(jsonrpc.NexentaJSONException,
- self.proxy, 'arg1', 'arg2')
-
-
-class TestNexentaNfsDriver(test.TestCase):
- TEST_EXPORT1 = 'host1:/volumes/stack/share'
- TEST_NMS1 = 'http://admin:nexenta@host1:2000'
-
- TEST_EXPORT2 = 'host2:/volumes/stack/share'
- TEST_NMS2 = 'http://admin:nexenta@host2:2000'
-
- TEST_EXPORT2_OPTIONS = '-o intr'
-
- TEST_FILE_NAME = 'test.txt'
- TEST_SHARES_CONFIG_FILE = '/etc/cinder/nexenta-shares.conf'
-
- TEST_SHARE_SVC = 'svc:/network/nfs/server:default'
-
- TEST_SHARE_OPTS = {
- 'read_only': '',
- 'read_write': '*',
- 'recursive': 'true',
- 'anonymous_rw': 'true',
- 'extra_options': 'anon=0',
- 'root': 'nobody'
- }
-
- def _create_volume_db_entry(self):
- vol = {
- 'id': '1',
- 'size': 1,
- 'status': 'available',
- 'provider_location': self.TEST_EXPORT1
- }
- return db.volume_create(self.ctxt, vol)['id']
-
- def setUp(self):
- super(TestNexentaNfsDriver, self).setUp()
- self.ctxt = context.get_admin_context()
- self.configuration = mox_lib.MockObject(conf.Configuration)
- self.configuration.nexenta_shares_config = None
- self.configuration.nexenta_mount_point_base = '$state_path/mnt'
- self.configuration.nexenta_sparsed_volumes = True
- self.configuration.nexenta_volume_compression = 'on'
- self.configuration.nfs_mount_point_base = '/mnt/test'
- self.configuration.nfs_mount_options = None
- self.configuration.nas_mount_options = None
- self.configuration.nexenta_nms_cache_volroot = False
- self.nms_mock = self.mox.CreateMockAnything()
- for mod in ('appliance', 'folder', 'server', 'volume', 'netstorsvc',
- 'snapshot'):
- setattr(self.nms_mock, mod, self.mox.CreateMockAnything())
- self.nms_mock.__hash__ = lambda *_, **__: 1
- self.stubs.Set(jsonrpc, 'NexentaJSONProxy',
- lambda *_, **__: self.nms_mock)
- self.drv = nfs.NexentaNfsDriver(configuration=self.configuration)
- self.drv.shares = {}
- self.drv.share2nms = {}
-
- def test_check_for_setup_error(self):
- self.drv.share2nms = {
- 'host1:/volumes/stack/share': self.nms_mock
- }
-
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.volume.object_exists('stack').AndReturn(True)
- self.nms_mock.folder.object_exists('stack/share').AndReturn(True)
- share_opts = {
- 'read_write': '*',
- 'read_only': '',
- 'root': 'nobody',
- 'extra_options': 'anon=0',
- 'recursive': 'true',
- 'anonymous_rw': 'true',
- }
- self.nms_mock.netstorsvc.share_folder(
- 'svc:/network/nfs/server:default', 'stack/share', share_opts)
-
- self.mox.ReplayAll()
-
- self.drv.check_for_setup_error()
-
- self.mox.ResetAll()
-
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.volume.object_exists('stack').AndReturn(False)
-
- self.mox.ReplayAll()
-
- self.assertRaises(LookupError, self.drv.check_for_setup_error)
-
- self.mox.ResetAll()
-
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.volume.object_exists('stack').AndReturn(True)
- self.nms_mock.folder.object_exists('stack/share').AndReturn(False)
-
- self.mox.ReplayAll()
-
- self.assertRaises(LookupError, self.drv.check_for_setup_error)
-
- def test_initialize_connection(self):
- self.drv.shares = {
- self.TEST_EXPORT1: None
- }
- volume = {
- 'provider_location': self.TEST_EXPORT1,
- 'name': 'volume'
- }
- result = self.drv.initialize_connection(volume, None)
- self.assertEqual(result['data']['export'],
- '%s/volume' % self.TEST_EXPORT1)
-
- def test_do_create_volume(self):
- volume = {
- 'provider_location': self.TEST_EXPORT1,
- 'size': 1,
- 'name': 'volume-1'
- }
- self.drv.shares = {self.TEST_EXPORT1: None}
- self.drv.share2nms = {self.TEST_EXPORT1: self.nms_mock}
-
- compression = self.configuration.nexenta_volume_compression
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.folder.create_with_props(
- 'stack', 'share/volume-1', {'compression': compression})
- self.nms_mock.netstorsvc.share_folder(self.TEST_SHARE_SVC,
- 'stack/share/volume-1',
- self.TEST_SHARE_OPTS)
- self.nms_mock.appliance.execute(
- 'truncate --size 1G /volumes/stack/share/volume-1/volume')
- self.nms_mock.appliance.execute('chmod ugo+rw '
- '/volumes/stack/share/volume-1/volume')
-
- self.mox.ReplayAll()
-
- self.drv._do_create_volume(volume)
-
- self.mox.ResetAll()
-
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.folder.create_with_props(
- 'stack', 'share/volume-1', {'compression': compression})
- self.nms_mock.netstorsvc.share_folder(
- self.TEST_SHARE_SVC, 'stack/share/volume-1',
- self.TEST_SHARE_OPTS).AndRaise(nexenta.NexentaException('-'))
- self.nms_mock.folder.destroy('stack/share/volume-1')
-
- self.mox.ReplayAll()
-
- self.assertRaises(nexenta.NexentaException, self.drv._do_create_volume,
- volume)
-
- def test_create_sparsed_file(self):
- self.nms_mock.appliance.execute('truncate --size 1G /tmp/path')
- self.mox.ReplayAll()
-
- self.drv._create_sparsed_file(self.nms_mock, '/tmp/path', 1)
-
- def test_create_regular_file(self):
- self.nms_mock.appliance.execute('dd if=/dev/zero of=/tmp/path bs=1M '
- 'count=1024')
- self.mox.ReplayAll()
-
- self.drv._create_regular_file(self.nms_mock, '/tmp/path', 1)
-
- def test_set_rw_permissions_for_all(self):
- path = '/tmp/path'
- self.nms_mock.appliance.execute('chmod ugo+rw %s' % path)
- self.mox.ReplayAll()
-
- self.drv._set_rw_permissions_for_all(self.nms_mock, path)
-
- def test_local_path(self):
- volume = {'provider_location': self.TEST_EXPORT1, 'name': 'volume-1'}
- path = self.drv.local_path(volume)
- self.assertEqual(
- path,
- '$state_path/mnt/b3f660847a52b29ac330d8555e4ad669/volume-1/volume'
- )
-
- def test_remote_path(self):
- volume = {'provider_location': self.TEST_EXPORT1, 'name': 'volume-1'}
- path = self.drv.remote_path(volume)
- self.assertEqual(path, '/volumes/stack/share/volume-1/volume')
-
- def test_share_folder(self):
- path = 'stack/share/folder'
- self.nms_mock.netstorsvc.share_folder(self.TEST_SHARE_SVC, path,
- self.TEST_SHARE_OPTS)
- self.mox.ReplayAll()
-
- self.drv._share_folder(self.nms_mock, 'stack', 'share/folder')
-
- def test_load_shares_config(self):
- self.drv.configuration.nfs_shares_config = self.TEST_SHARES_CONFIG_FILE
-
- self.mox.StubOutWithMock(self.drv, '_read_config_file')
- config_data = [
- '%s %s' % (self.TEST_EXPORT1, self.TEST_NMS1),
- '# %s %s' % (self.TEST_EXPORT2, self.TEST_NMS2),
- '',
- '%s %s %s' % (self.TEST_EXPORT2, self.TEST_NMS2,
- self.TEST_EXPORT2_OPTIONS)
- ]
-
- self.drv._read_config_file(self.TEST_SHARES_CONFIG_FILE).\
- AndReturn(config_data)
- self.mox.ReplayAll()
-
- self.drv._load_shares_config(self.drv.configuration.nfs_shares_config)
-
- self.assertIn(self.TEST_EXPORT1, self.drv.shares)
- self.assertIn(self.TEST_EXPORT2, self.drv.shares)
- self.assertEqual(len(self.drv.shares), 2)
-
- self.assertIn(self.TEST_EXPORT1, self.drv.share2nms)
- self.assertIn(self.TEST_EXPORT2, self.drv.share2nms)
- self.assertEqual(len(self.drv.share2nms.keys()), 2)
-
- self.assertEqual(self.drv.shares[self.TEST_EXPORT2],
- self.TEST_EXPORT2_OPTIONS)
-
- self.mox.VerifyAll()
-
- def test_get_capacity_info(self):
- self.drv.share2nms = {self.TEST_EXPORT1: self.nms_mock}
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.folder.get_child_props('stack/share', '').AndReturn({
- 'available': '1G',
- 'used': '2G'
- })
- self.mox.ReplayAll()
- total, free, allocated = self.drv._get_capacity_info(self.TEST_EXPORT1)
-
- self.assertEqual(total, 3 * units.Gi)
- self.assertEqual(free, units.Gi)
- self.assertEqual(allocated, 2 * units.Gi)
-
- def test_get_share_datasets(self):
- self.drv.share2nms = {self.TEST_EXPORT1: self.nms_mock}
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.mox.ReplayAll()
-
- volume_name, folder_name = \
- self.drv._get_share_datasets(self.TEST_EXPORT1)
-
- self.assertEqual(volume_name, 'stack')
- self.assertEqual(folder_name, 'share')
-
- def test_delete_snapshot(self):
- self.drv.share2nms = {self.TEST_EXPORT1: self.nms_mock}
- self._create_volume_db_entry()
-
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.snapshot.destroy('stack/share/volume-1@snapshot1', '')
- self.mox.ReplayAll()
- self.drv.delete_snapshot({'volume_id': '1', 'name': 'snapshot1'})
- self.mox.ResetAll()
-
- # Check that exception not raised if snapshot does not exist on
- # NexentaStor appliance.
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- mock = self.nms_mock.snapshot.destroy('stack/share/volume-1@snapshot1',
- '')
- mock.AndRaise(nexenta.NexentaException("Snapshot does not exist"))
- self.mox.ReplayAll()
- self.drv.delete_snapshot({'volume_id': '1', 'name': 'snapshot1'})
- self.mox.ResetAll()
-
- def test_delete_volume(self):
- self.drv.share2nms = {self.TEST_EXPORT1: self.nms_mock}
- self._create_volume_db_entry()
-
- self.drv._ensure_share_mounted = lambda *_, **__: 0
- self.drv._execute = lambda *_, **__: 0
-
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.folder.get_child_props('stack/share/volume-1',
- 'origin').AndReturn(None)
- self.nms_mock.folder.destroy('stack/share/volume-1', '-r')
- self.mox.ReplayAll()
- self.drv.delete_volume({
- 'id': '1',
- 'name': 'volume-1',
- 'provider_location': self.TEST_EXPORT1
- })
- self.mox.ResetAll()
-
- # Check that exception not raised if folder does not exist on
- # NexentaStor appliance.
- self.nms_mock.server.get_prop('volroot').AndReturn('/volumes')
- self.nms_mock.folder.get_child_props('stack/share/volume-1',
- 'origin').AndReturn(None)
- mock = self.nms_mock.folder.destroy('stack/share/volume-1', '-r')
- mock.AndRaise(nexenta.NexentaException("Folder does not exist"))
- self.mox.ReplayAll()
- self.drv.delete_volume({
- 'id': '1',
- 'name': 'volume-1',
- 'provider_location': self.TEST_EXPORT1
- })
- self.mox.ResetAll()
-
-
-class TestNexentaUtils(test.TestCase):
-
- def test_str2size(self):
- values_to_test = (
- # Test empty value
- (None, 0),
- ('', 0),
- ('0', 0),
- ('12', 12),
- # Test int and long values
- (10, 10),
- (long(10), 10),
- # Test bytes string
- ('1b', 1),
- ('1B', 1),
- ('1023b', 1023),
- ('0B', 0),
- # Test other units
- ('1M', units.Mi),
- ('1.0M', units.Mi),
- )
-
- for value, result in values_to_test:
- self.assertEqual(utils.str2size(value), result)
-
- # Invalid format value
- self.assertRaises(ValueError, utils.str2size, 'A')
-
- def test_str2gib_size(self):
- self.assertEqual(utils.str2gib_size('1024M'), 1)
- self.assertEqual(utils.str2gib_size('300M'),
- 300 * units.Mi // units.Gi)
- self.assertEqual(utils.str2gib_size('1.2T'),
- 1.2 * units.Ti // units.Gi)
- self.assertRaises(ValueError, utils.str2gib_size, 'A')
-
- def test_parse_nms_url(self):
- urls = (
- ('auto://192.168.1.1/', (True, 'http', 'admin', 'nexenta',
- '192.168.1.1', '2000', '/rest/nms/')),
- ('http://192.168.1.1/', (False, 'http', 'admin', 'nexenta',
- '192.168.1.1', '2000', '/rest/nms/')),
- ('http://192.168.1.1:8080', (False, 'http', 'admin', 'nexenta',
- '192.168.1.1', '8080', '/rest/nms/')),
- ('https://root:password@192.168.1.1:8080',
- (False, 'https', 'root', 'password', '192.168.1.1', '8080',
- '/rest/nms/')),
- )
- for url, result in urls:
- self.assertEqual(utils.parse_nms_url(url), result)
+++ /dev/null
-# Copyright 2011 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.
-"""
-:mod:`nexenta` -- Package contains Nexenta-specific modules
-=====================================================================
-
-.. automodule:: nexenta
-.. moduleauthor:: Yuriy Taraday <yorik.sar@gmail.com>
-.. moduleauthor:: Mikhail Khodos <mikhail.khodos@nexenta.com>
-"""
-
-
-class NexentaException(Exception):
- pass
+++ /dev/null
-# Copyright 2011 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.
-"""
-:mod:`nexenta.iscsi` -- Driver to store volumes on Nexenta Appliance
-=====================================================================
-
-.. automodule:: nexenta.volume
-.. moduleauthor:: Victor Rodionov <victor.rodionov@nexenta.com>
-.. moduleauthor:: Mikhail Khodos <mikhail.khodos@nexenta.com>
-.. moduleauthor:: Yuriy Taraday <yorik.sar@gmail.com>
-"""
-
-from oslo_log import log as logging
-
-from cinder import exception
-from cinder.i18n import _, _LE, _LI, _LW
-from cinder.volume import driver
-from cinder.volume.drivers import nexenta
-from cinder.volume.drivers.nexenta import jsonrpc
-from cinder.volume.drivers.nexenta import options
-from cinder.volume.drivers.nexenta import utils
-
-VERSION = '1.2.1'
-LOG = logging.getLogger(__name__)
-
-
-class NexentaISCSIDriver(driver.ISCSIDriver): # pylint: disable=R0921
- """Executes volume driver commands on Nexenta Appliance.
-
- Version history:
- 1.0.0 - Initial driver version.
- 1.0.1 - Fixed bug #1236626: catch "does not exist" exception of
- lu_exists.
- 1.1.0 - Changed class name to NexentaISCSIDriver.
- 1.1.1 - Ignore "does not exist" exception of nms.snapshot.destroy.
- 1.1.2 - Optimized create_cloned_volume, replaced zfs send recv with zfs
- clone.
- 1.1.3 - Extended volume stats provided by _update_volume_stats method.
- 1.2.0 - Added volume migration with storage assist method.
- 1.2.1 - Fixed bug #1263258: now migrate_volume update provider_location
- of migrated volume; after migrating volume migrate_volume
- destroy snapshot on migration destination.
- """
-
- VERSION = VERSION
-
- def __init__(self, *args, **kwargs):
- super(NexentaISCSIDriver, self).__init__(*args, **kwargs)
- self.nms = None
- if self.configuration:
- self.configuration.append_config_values(
- options.NEXENTA_CONNECTION_OPTIONS)
- self.configuration.append_config_values(
- options.NEXENTA_ISCSI_OPTIONS)
- self.configuration.append_config_values(
- options.NEXENTA_VOLUME_OPTIONS)
- self.configuration.append_config_values(
- options.NEXENTA_RRMGR_OPTIONS)
- self.nms_protocol = self.configuration.nexenta_rest_protocol
- self.nms_host = self.configuration.nexenta_host
- self.nms_port = self.configuration.nexenta_rest_port
- self.nms_user = self.configuration.nexenta_user
- self.nms_password = self.configuration.nexenta_password
- self.volume = self.configuration.nexenta_volume
- self.rrmgr_compression = self.configuration.nexenta_rrmgr_compression
- self.rrmgr_tcp_buf_size = self.configuration.nexenta_rrmgr_tcp_buf_size
- self.rrmgr_connections = self.configuration.nexenta_rrmgr_connections
- self.iscsi_target_portal_port = \
- self.configuration.nexenta_iscsi_target_portal_port
-
- @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.nms_protocol == 'auto':
- protocol, auto = 'http', True
- else:
- protocol, auto = self.nms_protocol, False
- self.nms = jsonrpc.NexentaJSONProxy(
- protocol, self.nms_host, self.nms_port, '/rest/nms', self.nms_user,
- self.nms_password, auto=auto)
-
- def check_for_setup_error(self):
- """Verify that the volume for our zvols exists.
-
- :raise: :py:exc:`LookupError`
- """
- if not self.nms.volume.object_exists(self.volume):
- raise LookupError(_("Volume %s does not exist in Nexenta SA"),
- self.volume)
-
- def _get_zvol_name(self, volume_name):
- """Return zvol name that corresponds given volume name."""
- return '%s/%s' % (self.volume, volume_name)
-
- def _get_target_name(self, volume_name):
- """Return iSCSI target name to access volume."""
- return '%s%s' % (self.configuration.nexenta_target_prefix, volume_name)
-
- def _get_target_group_name(self, volume_name):
- """Return Nexenta iSCSI target group name for volume."""
- return '%s%s' % (self.configuration.nexenta_target_group_prefix,
- volume_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
-
- @staticmethod
- def _is_clone_snapshot_name(snapshot):
- """Check if snapshot is created for cloning."""
- name = snapshot.split('@')[-1]
- return name.startswith('cinder-clone-snapshot-')
-
- def create_volume(self, volume):
- """Create a zvol on appliance.
-
- :param volume: volume reference
- :return: model update dict for volume reference
- """
- self.nms.zvol.create(
- self._get_zvol_name(volume['name']),
- '%sG' % (volume['size'],),
- self.configuration.nexenta_blocksize,
- self.configuration.nexenta_sparse)
- return self.create_export(None, volume)
-
- def extend_volume(self, volume, new_size):
- """Extend an existing volume.
-
- :param volume: volume reference
- :param new_size: volume new size in GB
- """
- LOG.info(_LI('Extending volume: %(id)s New size: %(size)s GB'),
- {'id': volume['id'], 'size': new_size})
- self.nms.zvol.set_child_prop(self._get_zvol_name(volume['name']),
- 'volsize', '%sG' % new_size)
-
- def delete_volume(self, volume):
- """Destroy a zvol on appliance.
-
- :param volume: volume reference
- """
- volume_name = self._get_zvol_name(volume['name'])
- props = self.nms.zvol.get_child_props(volume_name, 'origin') or {}
- try:
- self.nms.zvol.destroy(volume_name, '')
- except nexenta.NexentaException as exc:
- if 'does not exist' in exc.args[0]:
- LOG.info(_LI('Volume %s does not exist, it '
- 'seems it was already deleted.'), volume_name)
- return
- if 'zvol has children' in exc.args[0]:
- raise exception.VolumeIsBusy(volume_name=volume_name)
- raise
- origin = props.get('origin')
- if origin and self._is_clone_snapshot_name(origin):
- volume, snapshot = origin.split('@')
- volume = volume.lstrip('%s/' % self.configuration.nexenta_volume)
- try:
- self.delete_snapshot({'volume_name': volume, 'name': snapshot})
- except nexenta.NexentaException as exc:
- LOG.warning(_LW('Cannot delete snapshot %(origin)s: %(exc)s'),
- {'origin': origin, 'exc': exc})
-
- def create_cloned_volume(self, volume, src_vref):
- """Creates a clone of the specified volume.
-
- :param volume: new volume reference
- :param src_vref: source volume reference
- """
- snapshot = {'volume_name': src_vref['name'],
- 'name': self._get_clone_snapshot_name(volume)}
- LOG.debug('Creating temp snapshot of the original volume: '
- '%(volume_name)s@%(name)s', snapshot)
- # We don't delete this snapshot, because this snapshot will be origin
- # of new volume. This snapshot will be automatically promoted by NMS
- # when user will delete origin volume. But when cloned volume deleted
- # we check its origin property and delete source snapshot if needed.
- self.create_snapshot(snapshot)
- try:
- self.create_volume_from_snapshot(volume, snapshot)
- except nexenta.NexentaException:
- LOG.error(_LE('Volume creation failed, deleting created snapshot '
- '%(volume_name)s@%(name)s'), snapshot)
- try:
- self.delete_snapshot(snapshot)
- except (nexenta.NexentaException, exception.SnapshotIsBusy):
- LOG.warning(_LW('Failed to delete zfs snapshot '
- '%(volume_name)s@%(name)s'), snapshot)
- raise
-
- def _get_zfs_send_recv_cmd(self, src, dst):
- """Returns rrmgr command for source and destination."""
- return utils.get_rrmgr_cmd(src, dst,
- compression=self.rrmgr_compression,
- tcp_buf_size=self.rrmgr_tcp_buf_size,
- connections=self.rrmgr_connections)
-
- @staticmethod
- def get_nms_for_url(url):
- """Returns initialized nms object for url."""
- auto, scheme, user, password, host, port, path =\
- utils.parse_nms_url(url)
- return jsonrpc.NexentaJSONProxy(scheme, host, port, path, user,
- password, auto=auto)
-
- def migrate_volume(self, ctxt, volume, host):
- """Migrate if volume and host are managed by Nexenta appliance.
-
- :param ctxt: context
- :param volume: a dictionary describing the volume to migrate
- :param host: a dictionary describing the host to migrate to
- """
- LOG.debug('Enter: migrate_volume: id=%(id)s, host=%(host)s' %
- {'id': volume['id'], 'host': host})
-
- false_ret = (False, None)
-
- if volume['status'] != 'available':
- return false_ret
-
- if 'capabilities' not in host:
- return false_ret
-
- capabilities = host['capabilities']
-
- if 'location_info' not in capabilities or \
- 'iscsi_target_portal_port' not in capabilities or \
- 'nms_url' not in capabilities:
- return false_ret
-
- iscsi_target_portal_port = capabilities['iscsi_target_portal_port']
- nms_url = capabilities['nms_url']
- dst_parts = capabilities['location_info'].split(':')
-
- if capabilities.get('vendor_name') != 'Nexenta' or \
- dst_parts[0] != self.__class__.__name__ or \
- capabilities['free_capacity_gb'] < volume['size']:
- return false_ret
-
- dst_host, dst_volume = dst_parts[1:]
-
- ssh_bound = False
- ssh_bindings = self.nms.appliance.ssh_list_bindings()
- for bind in ssh_bindings:
- if bind.index(dst_host) != -1:
- ssh_bound = True
- break
- if not ssh_bound:
- LOG.warning(_LW("Remote NexentaStor appliance at %s should be "
- "SSH-bound."), dst_host)
-
- # Create temporary snapshot of volume on NexentaStor Appliance.
- snapshot = {
- 'volume_name': volume['name'],
- 'name': utils.get_migrate_snapshot_name(volume)
- }
- self.create_snapshot(snapshot)
-
- src = '%(volume)s/%(zvol)s@%(snapshot)s' % {
- 'volume': self.volume,
- 'zvol': volume['name'],
- 'snapshot': snapshot['name']
- }
- dst = ':'.join([dst_host, dst_volume])
-
- try:
- self.nms.appliance.execute(self._get_zfs_send_recv_cmd(src, dst))
- except nexenta.NexentaException as exc:
- LOG.warning(_LW("Cannot send source snapshot %(src)s to "
- "destination %(dst)s. Reason: %(exc)s"),
- {'src': src, 'dst': dst, 'exc': exc})
- return false_ret
- finally:
- try:
- self.delete_snapshot(snapshot)
- except nexenta.NexentaException as exc:
- LOG.warning(_LW("Cannot delete temporary source snapshot "
- "%(src)s on NexentaStor Appliance: %(exc)s"),
- {'src': src, 'exc': exc})
- try:
- self.delete_volume(volume)
- except nexenta.NexentaException as exc:
- LOG.warning(_LW("Cannot delete source volume %(volume)s on "
- "NexentaStor Appliance: %(exc)s"),
- {'volume': volume['name'], 'exc': exc})
-
- dst_nms = self.get_nms_for_url(nms_url)
- dst_snapshot = '%s/%s@%s' % (dst_volume, volume['name'],
- snapshot['name'])
- try:
- dst_nms.snapshot.destroy(dst_snapshot, '')
- except nexenta.NexentaException as exc:
- LOG.warning(_LW("Cannot delete temporary destination snapshot "
- "%(dst)s on NexentaStor Appliance: %(exc)s"),
- {'dst': dst_snapshot, 'exc': exc})
-
- provider_location = '%(host)s:%(port)s,1 %(name)s 0' % {
- 'host': dst_host,
- 'port': iscsi_target_portal_port,
- 'name': self._get_target_name(volume['name'])
- }
-
- return True, {'provider_location': provider_location}
-
- def create_snapshot(self, snapshot):
- """Create snapshot of existing zvol on appliance.
-
- :param snapshot: snapshot reference
- """
- self.nms.zvol.create_snapshot(
- self._get_zvol_name(snapshot['volume_name']),
- snapshot['name'], '')
-
- def create_volume_from_snapshot(self, volume, snapshot):
- """Create new volume from other's snapshot on appliance.
-
- :param volume: reference of volume to be created
- :param snapshot: reference of source snapshot
- """
- self.nms.zvol.clone(
- '%s@%s' % (self._get_zvol_name(snapshot['volume_name']),
- snapshot['name']),
- self._get_zvol_name(volume['name']))
-
- def delete_snapshot(self, snapshot):
- """Delete volume's snapshot on appliance.
-
- :param snapshot: snapshot reference
- """
- volume_name = self._get_zvol_name(snapshot['volume_name'])
- snapshot_name = '%s@%s' % (volume_name, snapshot['name'])
- try:
- self.nms.snapshot.destroy(snapshot_name, '')
- except nexenta.NexentaException as exc:
- if "does not exist" in exc.args[0]:
- LOG.info(_LI('Snapshot %s does not exist, it seems it was '
- 'already deleted.'), snapshot_name)
- return
- if "snapshot has dependent clones" in exc.args[0]:
- raise exception.SnapshotIsBusy(snapshot_name=snapshot['name'])
- raise
-
- def local_path(self, volume):
- """Return local path to existing local volume.
-
- We never have local volumes, so it raises NotImplementedError.
-
- :raise: :py:exc:`NotImplementedError`
- """
- raise NotImplementedError
-
- def _target_exists(self, target):
- """Check if iSCSI target exist.
-
- :param target: target name
- :return: True if target exist, else False
- """
- targets = self.nms.stmf.list_targets()
- if not targets:
- return False
- return target in self.nms.stmf.list_targets()
-
- def _target_group_exists(self, target_group):
- """Check if target group exist.
-
- :param target_group: target group
- :return: True if target group exist, else False
- """
- groups = self.nms.stmf.list_targetgroups()
- if not groups:
- return False
- return target_group in groups
-
- def _target_member_in_target_group(self, target_group, target_member):
- """Check if target member in target group.
-
- :param target_group: target group
- :param target_member: target member
- :return: True if target member in target group, else False
- :raises: NexentaException if target group doesn't exist
- """
- members = self.nms.stmf.list_targetgroup_members(target_group)
- if not members:
- return False
- return target_member in members
-
- def _lu_exists(self, zvol_name):
- """Check if LU exists on appliance.
-
- :param zvol_name: Zvol name
- :raises: NexentaException if zvol not exists
- :return: True if LU exists, else False
- """
- try:
- return bool(self.nms.scsidisk.lu_exists(zvol_name))
- except nexenta.NexentaException as exc:
- if 'does not exist' not in exc.args[0]:
- raise
- return False
-
- def _is_lu_shared(self, zvol_name):
- """Check if LU exists on appliance and shared.
-
- :param zvol_name: Zvol name
- :raises: NexentaException if Zvol not exist
- :return: True if LU exists and shared, else False
- """
- try:
- shared = self.nms.scsidisk.lu_shared(zvol_name) > 0
- except nexenta.NexentaException as exc:
- if 'does not exist for zvol' not in exc.args[0]:
- raise # Zvol does not exists
- shared = False # LU does not exist
- return shared
-
- def _is_volume_exported(self, volume):
- """Check if volume exported.
-
- :param volume: volume object
- :return: True if volume exported, else False
- """
- zvol_name = self._get_zvol_name(volume['name'])
- target_name = self._get_target_name(volume['name'])
- target_group_name = self._get_target_group_name(volume['name'])
- return (self._target_exists(target_name) and
- self._target_group_exists(target_group_name) and
- self._target_member_in_target_group(target_group_name,
- target_name) and
- self._lu_exists(zvol_name) and
- self._is_lu_shared(zvol_name))
-
- def _get_provider_location(self, volume):
- """Returns volume iscsiadm-formatted provider location string."""
- return '%(host)s:%(port)s,1 %(name)s 0' % {
- 'host': self.nms_host,
- 'port': self.configuration.nexenta_iscsi_target_portal_port,
- 'name': self._get_target_name(volume['name'])
- }
-
- def _do_export(self, _ctx, volume, ensure=False):
- """Do all steps to get zvol exported as LUN 0 at separate target.
-
- :param volume: reference of volume to be exported
- :param ensure: if True, ignore errors caused by already existing
- resources
- """
- zvol_name = self._get_zvol_name(volume['name'])
- target_name = self._get_target_name(volume['name'])
- target_group_name = self._get_target_group_name(volume['name'])
-
- if not self._target_exists(target_name):
- try:
- self.nms.iscsitarget.create_target({
- 'target_name': target_name})
- except nexenta.NexentaException as exc:
- if ensure and 'already configured' in exc.args[0]:
- LOG.info(_LI('Ignored target creation error "%s" while '
- 'ensuring export'), exc)
- else:
- raise
- if not self._target_group_exists(target_group_name):
- try:
- self.nms.stmf.create_targetgroup(target_group_name)
- except nexenta.NexentaException as exc:
- if ((ensure and 'already exists' in exc.args[0]) or
- 'target must be offline' in exc.args[0]):
- LOG.info(_LI('Ignored target group creation error "%s" '
- 'while ensuring export'), exc)
- else:
- raise
- if not self._target_member_in_target_group(target_group_name,
- target_name):
- try:
- self.nms.stmf.add_targetgroup_member(target_group_name,
- target_name)
- except nexenta.NexentaException as exc:
- if ((ensure and 'already exists' in exc.args[0]) or
- 'target must be offline' in exc.args[0]):
- LOG.info(_LI('Ignored target group member addition error '
- '"%s" while ensuring export'), exc)
- else:
- raise
- if not self._lu_exists(zvol_name):
- try:
- self.nms.scsidisk.create_lu(zvol_name, {})
- except nexenta.NexentaException as exc:
- if not ensure or 'in use' not in exc.args[0]:
- raise
- LOG.info(_LI('Ignored LU creation error "%s" while ensuring '
- 'export'), exc)
- if not self._is_lu_shared(zvol_name):
- try:
- self.nms.scsidisk.add_lun_mapping_entry(zvol_name, {
- 'target_group': target_group_name})
- except nexenta.NexentaException as exc:
- if not ensure or 'view entry exists' not in exc.args[0]:
- raise
- LOG.info(_LI('Ignored LUN mapping entry addition error "%s" '
- 'while ensuring export'), exc)
-
- def create_export(self, _ctx, volume):
- """Create new export for zvol.
-
- :param volume: reference of volume to be exported
- :return: iscsiadm-formatted provider location string
- """
- self._do_export(_ctx, volume, ensure=False)
- return {'provider_location': self._get_provider_location(volume)}
-
- def ensure_export(self, _ctx, volume):
- """Recreate parts of export if necessary.
-
- :param volume: reference of volume to be exported
- """
- self._do_export(_ctx, volume, ensure=True)
-
- def remove_export(self, _ctx, volume):
- """Destroy all resources created to export zvol.
-
- :param volume: reference of volume to be unexported
- """
- zvol_name = self._get_zvol_name(volume['name'])
- target_name = self._get_target_name(volume['name'])
- target_group_name = self._get_target_group_name(volume['name'])
- self.nms.scsidisk.delete_lu(zvol_name)
-
- try:
- self.nms.stmf.destroy_targetgroup(target_group_name)
- except nexenta.NexentaException as exc:
- # We assume that target group is already gone
- LOG.warn(_LW('Got error trying to destroy target group'
- ' %(target_group)s, assuming it is '
- 'already gone: %(exc)s'),
- {'target_group': target_group_name, 'exc': exc})
- try:
- self.nms.iscsitarget.delete_target(target_name)
- except nexenta.NexentaException as exc:
- # We assume that target is gone as well
- LOG.warn(_LW('Got error trying to delete target %(target)s,'
- ' assuming it is already gone: %(exc)s'),
- {'target': target_name, 'exc': exc})
-
- def get_volume_stats(self, refresh=False):
- """Get volume stats.
-
- If 'refresh' is True, run update the stats first.
- """
- if refresh:
- self._update_volume_stats()
-
- return self._stats
-
- def _update_volume_stats(self):
- """Retrieve stats info for NexentaStor appliance."""
- LOG.debug('Updating volume stats')
-
- stats = self.nms.volume.get_child_props(
- self.configuration.nexenta_volume, 'health|size|used|available')
-
- total_amount = utils.str2gib_size(stats['size'])
- free_amount = utils.str2gib_size(stats['available'])
-
- location_info = '%(driver)s:%(host)s:%(volume)s' % {
- 'driver': self.__class__.__name__,
- 'host': self.nms_host,
- 'volume': self.volume
- }
-
- self._stats = {
- 'vendor_name': 'Nexenta',
- 'driver_version': self.VERSION,
- 'storage_protocol': 'iSCSI',
- 'total_capacity_gb': total_amount,
- 'free_capacity_gb': free_amount,
- 'reserved_percentage': 0,
- 'QoS_support': False,
- 'volume_backend_name': self.backend_name,
- 'location_info': location_info,
- 'iscsi_target_portal_port': self.iscsi_target_portal_port,
- 'nms_url': self.nms.url
- }
+++ /dev/null
-# Copyright 2011 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.
-"""
-:mod:`nexenta.jsonrpc` -- Nexenta-specific JSON RPC client
-=====================================================================
-
-.. automodule:: nexenta.jsonrpc
-.. moduleauthor:: Yuriy Taraday <yorik.sar@gmail.com>
-.. moduleauthor:: Victor Rodionov <victor.rodionov@nexenta.com>
-"""
-
-import urllib2
-
-from oslo_log import log as logging
-from oslo_serialization import jsonutils
-
-from cinder.i18n import _, _LE, _LI
-from cinder.volume.drivers import nexenta
-
-LOG = logging.getLogger(__name__)
-
-
-class NexentaJSONException(nexenta.NexentaException):
- pass
-
-
-class NexentaJSONProxy(object):
-
- def __init__(self, scheme, host, port, path, user, password, auto=False,
- obj=None, method=None):
- self.scheme = scheme.lower()
- self.host = host
- self.port = port
- self.path = path
- self.user = user
- self.password = password
- self.auto = auto
- self.obj = obj
- self.method = method
-
- def __getattr__(self, name):
- if not self.obj:
- obj, method = name, None
- elif not self.method:
- obj, method = self.obj, name
- else:
- obj, method = '%s.%s' % (self.obj, self.method), name
- return NexentaJSONProxy(self.scheme, self.host, self.port, self.path,
- self.user, self.password, self.auto, obj,
- method)
-
- @property
- def url(self):
- return '%s://%s:%s%s' % (self.scheme, self.host, self.port, self.path)
-
- def __hash__(self):
- return self.url.__hash__()
-
- def __repr__(self):
- return 'NMS proxy: %s' % self.url
-
- def __call__(self, *args):
- data = jsonutils.dumps({
- 'object': self.obj,
- 'method': self.method,
- 'params': args
- })
- auth = ('%s:%s' % (self.user, self.password)).encode('base64')[:-1]
- headers = {
- 'Content-Type': 'application/json',
- 'Authorization': 'Basic %s' % auth
- }
- LOG.debug('Sending JSON data: %s', data)
- request = urllib2.Request(self.url, data, headers)
- response_obj = urllib2.urlopen(request)
- if response_obj.info().status == 'EOF in headers':
- if not self.auto or self.scheme != 'http':
- LOG.error(_LE('No headers in server response'))
- raise NexentaJSONException(_('Bad response from server'))
- LOG.info(_LI('Auto switching to HTTPS connection to %s'), self.url)
- self.scheme = 'https'
- request = urllib2.Request(self.url, data, headers)
- response_obj = urllib2.urlopen(request)
-
- response_data = response_obj.read()
- LOG.debug('Got response: %s', response_data)
- response = jsonutils.loads(response_data)
- if response.get('error') is not None:
- raise NexentaJSONException(response['error'].get('message', ''))
- return response.get('result')
+++ /dev/null
-# Copyright 2013 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.
-"""
-:mod:`nexenta.nfs` -- Driver to store volumes on NexentaStor Appliance.
-=======================================================================
-
-.. automodule:: nexenta.nfs
-.. moduleauthor:: Mikhail Khodos <hodosmb@gmail.com>
-.. moduleauthor:: Victor Rodionov <victor.rodionov@nexenta.com>
-"""
-
-import hashlib
-import os
-import re
-
-from oslo_log import log as logging
-from oslo_utils import units
-
-from cinder import context
-from cinder import db
-from cinder import exception
-from cinder.i18n import _, _LE, _LI, _LW
-from cinder.volume.drivers import nexenta
-from cinder.volume.drivers.nexenta import jsonrpc
-from cinder.volume.drivers.nexenta import options
-from cinder.volume.drivers.nexenta import utils
-from cinder.volume.drivers import nfs
-
-VERSION = '1.1.3'
-LOG = logging.getLogger(__name__)
-
-
-class NexentaNfsDriver(nfs.NfsDriver): # pylint: disable=R0921
- """Executes volume driver commands on Nexenta Appliance.
-
- Version history:
- 1.0.0 - Initial driver version.
- 1.1.0 - Auto sharing for enclosing folder.
- 1.1.1 - Added caching for NexentaStor appliance 'volroot' value.
- 1.1.2 - Ignore "folder does not exist" error in delete_volume and
- delete_snapshot method.
- 1.1.3 - Redefined volume_backend_name attribute inherited from
- RemoteFsDriver.
- """
-
- driver_prefix = 'nexenta'
- volume_backend_name = 'NexentaNfsDriver'
- VERSION = VERSION
-
- def __init__(self, *args, **kwargs):
- super(NexentaNfsDriver, self).__init__(*args, **kwargs)
- if self.configuration:
- self.configuration.append_config_values(
- options.NEXENTA_NFS_OPTIONS)
- conf = self.configuration
- self.nms_cache_volroot = conf.nexenta_nms_cache_volroot
- self._nms2volroot = {}
- self.share2nms = {}
-
- def do_setup(self, context):
- super(NexentaNfsDriver, self).do_setup(context)
- self._load_shares_config(getattr(self.configuration,
- self.driver_prefix +
- '_shares_config'))
-
- def check_for_setup_error(self):
- """Verify that the volume for our folder exists.
-
- :raise: :py:exc:`LookupError`
- """
- if self.share2nms:
- for nfs_share in self.share2nms:
- nms = self.share2nms[nfs_share]
- volume_name, dataset = self._get_share_datasets(nfs_share)
- if not nms.volume.object_exists(volume_name):
- raise LookupError(_("Volume %s does not exist in Nexenta "
- "Store appliance"), volume_name)
- folder = '%s/%s' % (volume_name, dataset)
- if not nms.folder.object_exists(folder):
- raise LookupError(_("Folder %s does not exist in Nexenta "
- "Store appliance"), folder)
- self._share_folder(nms, volume_name, dataset)
-
- def initialize_connection(self, volume, connector):
- """Allow connection to connector and return connection info.
-
- :param volume: volume reference
- :param connector: connector reference
- """
- export = '%s/%s' % (volume['provider_location'], volume['name'])
- data = {'export': export, 'name': 'volume'}
- if volume['provider_location'] in self.shares:
- data['options'] = self.shares[volume['provider_location']]
- return {
- 'driver_volume_type': self.driver_volume_type,
- 'data': data
- }
-
- def _do_create_volume(self, volume):
- nfs_share = volume['provider_location']
- nms = self.share2nms[nfs_share]
-
- vol, dataset = self._get_share_datasets(nfs_share)
- folder = '%s/%s' % (dataset, volume['name'])
- LOG.debug('Creating folder on Nexenta Store %s', folder)
- nms.folder.create_with_props(
- vol, folder,
- {'compression': self.configuration.nexenta_volume_compression}
- )
-
- volume_path = self.remote_path(volume)
- volume_size = volume['size']
- try:
- self._share_folder(nms, vol, folder)
-
- if getattr(self.configuration,
- self.driver_prefix + '_sparsed_volumes'):
- self._create_sparsed_file(nms, volume_path, volume_size)
- else:
- compression = nms.folder.get('compression')
- if compression != 'off':
- # Disable compression, because otherwise will not use space
- # on disk.
- nms.folder.set('compression', 'off')
- try:
- self._create_regular_file(nms, volume_path, volume_size)
- finally:
- if compression != 'off':
- # Backup default compression value if it was changed.
- nms.folder.set('compression', compression)
-
- self._set_rw_permissions_for_all(nms, volume_path)
- except nexenta.NexentaException as exc:
- try:
- nms.folder.destroy('%s/%s' % (vol, folder))
- except nexenta.NexentaException:
- LOG.warning(_LW("Cannot destroy created folder: "
- "%(vol)s/%(folder)s"),
- {'vol': vol, 'folder': folder})
- raise exc
-
- def create_volume_from_snapshot(self, volume, snapshot):
- """Create new volume from other's snapshot on appliance.
-
- :param volume: reference of volume to be created
- :param snapshot: reference of source snapshot
- """
- self._ensure_shares_mounted()
-
- snapshot_vol = self._get_snapshot_volume(snapshot)
- nfs_share = snapshot_vol['provider_location']
- volume['provider_location'] = nfs_share
- nms = self.share2nms[nfs_share]
-
- vol, dataset = self._get_share_datasets(nfs_share)
- snapshot_name = '%s/%s/%s@%s' % (vol, dataset, snapshot['volume_name'],
- snapshot['name'])
- folder = '%s/%s' % (dataset, volume['name'])
- nms.folder.clone(snapshot_name, '%s/%s' % (vol, folder))
-
- try:
- self._share_folder(nms, vol, folder)
- except nexenta.NexentaException:
- try:
- nms.folder.destroy('%s/%s' % (vol, folder), '')
- except nexenta.NexentaException:
- LOG.warning(_LW("Cannot destroy cloned folder: "
- "%(vol)s/%(folder)s"),
- {'vol': vol, 'folder': folder})
- raise
-
- return {'provider_location': volume['provider_location']}
-
- def create_cloned_volume(self, volume, src_vref):
- """Creates a clone of the specified volume.
-
- :param volume: new volume reference
- :param src_vref: source volume reference
- """
- LOG.info(_LI('Creating clone of volume: %s'), src_vref['id'])
- snapshot = {'volume_name': src_vref['name'],
- 'volume_id': src_vref['id'],
- 'name': self._get_clone_snapshot_name(volume)}
- # We don't delete this snapshot, because this snapshot will be origin
- # of new volume. This snapshot will be automatically promoted by NMS
- # when user will delete its origin.
- self.create_snapshot(snapshot)
- try:
- return self.create_volume_from_snapshot(volume, snapshot)
- except nexenta.NexentaException:
- LOG.error(_LE('Volume creation failed, deleting created snapshot '
- '%(volume_name)s@%(name)s'), snapshot)
- try:
- self.delete_snapshot(snapshot)
- except (nexenta.NexentaException, exception.SnapshotIsBusy):
- LOG.warning(_LW('Failed to delete zfs snapshot '
- '%(volume_name)s@%(name)s'), snapshot)
- raise
-
- def delete_volume(self, volume):
- """Deletes a logical volume.
-
- :param volume: volume reference
- """
- super(NexentaNfsDriver, self).delete_volume(volume)
-
- nfs_share = volume.get('provider_location')
-
- if nfs_share:
- nms = self.share2nms[nfs_share]
- vol, parent_folder = self._get_share_datasets(nfs_share)
- folder = '%s/%s/%s' % (vol, parent_folder, volume['name'])
- props = nms.folder.get_child_props(folder, 'origin') or {}
- try:
- nms.folder.destroy(folder, '-r')
- except nexenta.NexentaException as exc:
- if 'does not exist' in exc.args[0]:
- LOG.info(_LI('Folder %s does not exist, it was '
- 'already deleted.'), folder)
- return
- raise
- origin = props.get('origin')
- if origin and self._is_clone_snapshot_name(origin):
- try:
- nms.snapshot.destroy(origin, '')
- except nexenta.NexentaException as exc:
- if 'does not exist' in exc.args[0]:
- LOG.info(_LI('Snapshot %s does not exist, it was '
- 'already deleted.'), origin)
- return
- raise
-
- def create_snapshot(self, snapshot):
- """Creates a snapshot.
-
- :param snapshot: snapshot reference
- """
- volume = self._get_snapshot_volume(snapshot)
- nfs_share = volume['provider_location']
- nms = self.share2nms[nfs_share]
- vol, dataset = self._get_share_datasets(nfs_share)
- folder = '%s/%s/%s' % (vol, dataset, volume['name'])
- nms.folder.create_snapshot(folder, snapshot['name'], '-r')
-
- def delete_snapshot(self, snapshot):
- """Deletes a snapshot.
-
- :param snapshot: snapshot reference
- """
- volume = self._get_snapshot_volume(snapshot)
- nfs_share = volume['provider_location']
- nms = self.share2nms[nfs_share]
- vol, dataset = self._get_share_datasets(nfs_share)
- folder = '%s/%s/%s' % (vol, dataset, volume['name'])
- try:
- nms.snapshot.destroy('%s@%s' % (folder, snapshot['name']), '')
- except nexenta.NexentaException as exc:
- if 'does not exist' in exc.args[0]:
- LOG.info(_LI('Snapshot %s does not exist, it was '
- 'already deleted.'), '%s@%s' % (folder, snapshot))
- return
- raise
-
- def _create_sparsed_file(self, nms, path, size):
- """Creates file with 0 disk usage.
-
- :param nms: nms object
- :param path: path to new file
- :param size: size of file
- """
- nms.appliance.execute(
- 'truncate --size %(size)dG %(path)s' % {
- 'path': path,
- 'size': size
- }
- )
-
- def _create_regular_file(self, nms, path, size):
- """Creates regular file of given size.
- Takes a lot of time for large files.
-
- :param nms: nms object
- :param path: path to new file
- :param size: size of file
- """
- block_size_mb = 1
- block_count = size * units.Gi / (block_size_mb * units.Mi)
-
- LOG.info(_LI('Creating regular file: %s.'
- 'This may take some time.') % path)
-
- nms.appliance.execute(
- 'dd if=/dev/zero of=%(path)s bs=%(bs)dM count=%(count)d' % {
- 'path': path,
- 'bs': block_size_mb,
- 'count': block_count
- }
- )
-
- LOG.info(_LI('Regular file: %s created.') % path)
-
- def _set_rw_permissions_for_all(self, nms, path):
- """Sets 666 permissions for the path.
-
- :param nms: nms object
- :param path: path to file
- """
- nms.appliance.execute('chmod ugo+rw %s' % path)
-
- def local_path(self, volume):
- """Get volume path (mounted locally fs path) for given volume.
-
- :param volume: volume reference
- """
- nfs_share = volume['provider_location']
- return os.path.join(self._get_mount_point_for_share(nfs_share),
- volume['name'], 'volume')
-
- def _get_mount_point_for_share(self, nfs_share):
- """Returns path to mount point NFS share.
-
- :param nfs_share: example 172.18.194.100:/var/nfs
- """
- return os.path.join(self.configuration.nexenta_mount_point_base,
- hashlib.md5(nfs_share).hexdigest())
-
- def remote_path(self, volume):
- """Get volume path (mounted remotely fs path) for given volume.
-
- :param volume: volume reference
- """
- nfs_share = volume['provider_location']
- share = nfs_share.split(':')[1].rstrip('/')
- return '%s/%s/volume' % (share, volume['name'])
-
- def _share_folder(self, nms, volume, folder):
- """Share NFS folder on NexentaStor Appliance.
-
- :param nms: nms object
- :param volume: volume name
- :param folder: folder name
- """
- path = '%s/%s' % (volume, folder.lstrip('/'))
- share_opts = {
- 'read_write': '*',
- 'read_only': '',
- 'root': 'nobody',
- 'extra_options': 'anon=0',
- 'recursive': 'true',
- 'anonymous_rw': 'true',
- }
- LOG.debug('Sharing folder %s on Nexenta Store', folder)
- nms.netstorsvc.share_folder('svc:/network/nfs/server:default', path,
- share_opts)
-
- def _load_shares_config(self, share_file):
- self.shares = {}
- self.share2nms = {}
-
- for share in self._read_config_file(share_file):
- # A configuration line may be either:
- # host:/share_name http://user:pass@host:[port]/
- # or
- # host:/share_name http://user:pass@host:[port]/
- # -o options=123,rw --other
- if not share.strip():
- continue
- if share.startswith('#'):
- continue
-
- share_info = re.split(r'\s+', share, 2)
-
- share_address = share_info[0].strip().decode('unicode_escape')
- nms_url = share_info[1].strip()
- share_opts = share_info[2].strip() if len(share_info) > 2 else None
-
- if not re.match(r'.+:/.+', share_address):
- LOG.warn("Share %s ignored due to invalid format. Must be of "
- "form address:/export." % share_address)
- continue
-
- self.shares[share_address] = share_opts
- self.share2nms[share_address] = self._get_nms_for_url(nms_url)
-
- LOG.debug('Shares loaded: %s' % self.shares)
-
- def _get_capacity_info(self, nfs_share):
- """Calculate available space on the NFS share.
-
- :param nfs_share: example 172.18.194.100:/var/nfs
- """
- nms = self.share2nms[nfs_share]
- ns_volume, ns_folder = self._get_share_datasets(nfs_share)
- folder_props = nms.folder.get_child_props('%s/%s' % (ns_volume,
- ns_folder), '')
- free = utils.str2size(folder_props['available'])
- allocated = utils.str2size(folder_props['used'])
- return free + allocated, free, allocated
-
- def _get_nms_for_url(self, url):
- """Returns initialized nms object for url."""
- auto, scheme, user, password, host, port, path =\
- utils.parse_nms_url(url)
- return jsonrpc.NexentaJSONProxy(scheme, host, port, path, user,
- password, auto=auto)
-
- def _get_snapshot_volume(self, snapshot):
- ctxt = context.get_admin_context()
- return db.volume_get(ctxt, snapshot['volume_id'])
-
- def _get_volroot(self, nms):
- """Returns volroot property value from NexentaStor appliance."""
- if not self.nms_cache_volroot:
- return nms.server.get_prop('volroot')
- if nms not in self._nms2volroot:
- self._nms2volroot[nms] = nms.server.get_prop('volroot')
- return self._nms2volroot[nms]
-
- def _get_share_datasets(self, nfs_share):
- nms = self.share2nms[nfs_share]
- volroot = self._get_volroot(nms)
- path = nfs_share.split(':')[1][len(volroot):].strip('/')
- volume_name = path.split('/')[0]
- folder_name = '/'.join(path.split('/')[1:])
- return volume_name, folder_name
-
- def _get_clone_snapshot_name(self, volume):
- """Return name for snapshot that will be used to clone the volume."""
- return 'cinder-clone-snapshot-%(id)s' % volume
-
- def _is_clone_snapshot_name(self, snapshot):
- """Check if snapshot is created for cloning."""
- name = snapshot.split('@')[-1]
- return name.startswith('cinder-clone-snapshot-')
+++ /dev/null
-# Copyright 2013 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.
-"""
-:mod:`nexenta.options` -- Contains configuration options for Nexenta drivers.
-=============================================================================
-
-.. automodule:: nexenta.options
-.. moduleauthor:: Victor Rodionov <victor.rodionov@nexenta.com>
-.. moduleauthor:: Yuriy Taraday <yorik.sar@gmail.com>
-"""
-
-from oslo_config import cfg
-
-
-NEXENTA_CONNECTION_OPTIONS = [
- cfg.StrOpt('nexenta_host',
- default='',
- help='IP address of Nexenta SA'),
- cfg.IntOpt('nexenta_rest_port',
- default=2000,
- help='HTTP port to connect to Nexenta REST API server'),
- cfg.StrOpt('nexenta_rest_protocol',
- default='auto',
- choices=['http', 'https', 'auto'],
- help='Use http or https for REST connection (default auto)'),
- cfg.StrOpt('nexenta_user',
- default='admin',
- help='User name to connect to Nexenta SA'),
- cfg.StrOpt('nexenta_password',
- default='nexenta',
- help='Password to connect to Nexenta SA',
- secret=True),
-]
-
-NEXENTA_ISCSI_OPTIONS = [
- cfg.IntOpt('nexenta_iscsi_target_portal_port',
- default=3260,
- help='Nexenta target portal port'),
- 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-',
- help='IQN prefix for iSCSI targets'),
- cfg.StrOpt('nexenta_target_group_prefix',
- default='cinder/',
- help='Prefix for iSCSI target groups on SA'),
-]
-
-NEXENTA_NFS_OPTIONS = [
- cfg.StrOpt('nexenta_shares_config',
- default='/etc/cinder/nfs_shares',
- help='File with the list of available nfs shares'),
- cfg.StrOpt('nexenta_mount_point_base',
- default='$state_path/mnt',
- help='Base directory that contains NFS share mount points'),
- cfg.BoolOpt('nexenta_sparsed_volumes',
- default=True,
- help='Enables or disables the creation of volumes as '
- 'sparsed files that take no space. If disabled '
- '(False), volume is created as a regular file, '
- 'which takes a long time.'),
- cfg.StrOpt('nexenta_volume_compression',
- default='on',
- choices=['on', 'off'],
- help='Default compression value for new ZFS folders.'),
- cfg.BoolOpt('nexenta_nms_cache_volroot',
- default=True,
- help=('If set True cache NexentaStor appliance volroot option '
- 'value.'))
-]
-
-NEXENTA_VOLUME_OPTIONS = [
- cfg.StrOpt('nexenta_blocksize',
- default='',
- help='Block size for volumes (default=blank means 8KB)'),
- cfg.BoolOpt('nexenta_sparse',
- default=False,
- help='Enables or disables the creation of sparse volumes'),
-]
-
-NEXENTA_RRMGR_OPTIONS = [
- cfg.IntOpt('nexenta_rrmgr_compression',
- default=0,
- help=('Enable stream compression, level 1..9. 1 - gives best '
- 'speed; 9 - gives best compression.')),
- cfg.IntOpt('nexenta_rrmgr_tcp_buf_size',
- default=4096,
- help='TCP Buffer size in KiloBytes.'),
- cfg.IntOpt('nexenta_rrmgr_connections',
- default=2,
- help='Number of TCP connections.'),
-]
-
-CONF = cfg.CONF
-CONF.register_opts(NEXENTA_CONNECTION_OPTIONS)
-CONF.register_opts(NEXENTA_ISCSI_OPTIONS)
-CONF.register_opts(NEXENTA_VOLUME_OPTIONS)
-CONF.register_opts(NEXENTA_NFS_OPTIONS)
-CONF.register_opts(NEXENTA_RRMGR_OPTIONS)
+++ /dev/null
-# Copyright 2013 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.
-"""
-:mod:`nexenta.utils` -- Nexenta-specific utils functions.
-=========================================================
-
-.. automodule:: nexenta.utils
-.. moduleauthor:: Victor Rodionov <victor.rodionov@nexenta.com>
-.. moduleauthor:: Mikhail Khodos <mikhail.khodos@nexenta.com>
-"""
-
-import re
-
-from oslo_utils import units
-import six.moves.urllib.parse as urlparse
-
-from cinder.i18n import _
-
-
-def str2size(s, scale=1024):
- """Convert size-string.
-
- String format: <value>[:space:]<B | K | M | ...> to bytes.
-
- :param s: size-string
- :param scale: base size
- """
- if not s:
- return 0
-
- if isinstance(s, (int, long)):
- return s
-
- match = re.match(r'^([\.\d]+)\s*([BbKkMmGgTtPpEeZzYy]?)', s)
- if match is None:
- raise ValueError(_('Invalid value: "%s"') % s)
-
- groups = match.groups()
- value = float(groups[0])
- suffix = len(groups) > 1 and groups[1].upper() or 'B'
-
- types = ('B', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
- for i, t in enumerate(types):
- if suffix == t:
- return int(value * pow(scale, i))
-
-
-def str2gib_size(s):
- """Covert size-string to size in gigabytes."""
- size_in_bytes = str2size(s)
- return size_in_bytes / units.Gi
-
-
-def get_rrmgr_cmd(src, dst, compression=None, tcp_buf_size=None,
- connections=None):
- """Returns rrmgr command for source and destination."""
- cmd = ['rrmgr', '-s', 'zfs']
- if compression:
- cmd.extend(['-c', '%s' % str(compression)])
- cmd.append('-q')
- cmd.append('-e')
- if tcp_buf_size:
- cmd.extend(['-w', str(tcp_buf_size)])
- if connections:
- cmd.extend(['-n', str(connections)])
- cmd.extend([src, dst])
- return ' '.join(cmd)
-
-
-def parse_nms_url(url):
- """Parse NMS url into normalized parts like scheme, user, host and others.
-
- Example NMS URL:
- auto://admin:nexenta@192.168.1.1:2000/
-
- NMS URL parts:
- auto True if url starts with auto://, protocol will be
- automatically switched to https if http not
- supported;
- scheme (auto) connection protocol (http or https);
- user (admin) NMS user;
- password (nexenta) NMS password;
- host (192.168.1.1) NMS host;
- port (2000) NMS port.
-
- :param url: url string
- :return: tuple (auto, scheme, user, password, host, port, path)
- """
- pr = urlparse.urlparse(url)
- scheme = pr.scheme
- auto = scheme == 'auto'
- if auto:
- scheme = 'http'
- user = 'admin'
- password = 'nexenta'
- if '@' not in pr.netloc:
- host_and_port = pr.netloc
- else:
- user_and_password, host_and_port = pr.netloc.split('@', 1)
- if ':' in user_and_password:
- user, password = user_and_password.split(':')
- else:
- user = user_and_password
- if ':' in host_and_port:
- host, port = host_and_port.split(':', 1)
- else:
- host, port = host_and_port, '2000'
- return auto, scheme, user, password, host, port, '/rest/nms/'
-
-
-def get_migrate_snapshot_name(volume):
- """Return name for snapshot that will be used to migrate the volume."""
- return 'cinder-migrate-snapshot-%(id)s' % volume