NexentaStor5: Added extend method to NFS driver

Added extend method and support for extending the volume in
create_volume_from_snapshot if the size of new volume is larger than
original volume size.
Added a method to replace / with %2F in URLs when needed.

Change-Id: I5debfccaa81ecf8d58b118f5ca0cccc735d35d24
DocImpact
Closes-Bug: #1562167
This commit is contained in:
Aleksey Ruban 2017-01-09 10:39:24 -07:00
parent 7502e1fed3
commit e0a6071b59
3 changed files with 98 additions and 19 deletions

View File

@ -156,11 +156,13 @@ class TestNexentaNfsDriver(test.TestCase):
self.drv.delete_snapshot(self.TEST_SNAPSHOT) self.drv.delete_snapshot(self.TEST_SNAPSHOT)
self.nef_mock.delete.assert_called_with(url) self.nef_mock.delete.assert_called_with(url)
@patch('cinder.volume.drivers.nexenta.ns5.nfs.'
'NexentaNfsDriver.extend_volume')
@patch('cinder.volume.drivers.nexenta.ns5.nfs.' @patch('cinder.volume.drivers.nexenta.ns5.nfs.'
'NexentaNfsDriver.local_path') 'NexentaNfsDriver.local_path')
@patch('cinder.volume.drivers.nexenta.ns5.nfs.' @patch('cinder.volume.drivers.nexenta.ns5.nfs.'
'NexentaNfsDriver._share_folder') 'NexentaNfsDriver._share_folder')
def test_create_volume_from_snapshot(self, share, path): def test_create_volume_from_snapshot(self, share, path, extend):
self._create_volume_db_entry() self._create_volume_db_entry()
url = ('storage/pools/%(pool)s/' url = ('storage/pools/%(pool)s/'
'filesystems/%(fs)s/snapshots/%(snap)s/clone') % { 'filesystems/%(fs)s/snapshots/%(snap)s/clone') % {
@ -174,6 +176,43 @@ class TestNexentaNfsDriver(test.TestCase):
self.TEST_VOLUME2, self.TEST_SNAPSHOT) self.TEST_VOLUME2, self.TEST_SNAPSHOT)
self.nef_mock.post.assert_called_with(url, data) self.nef_mock.post.assert_called_with(url, data)
# make sure the volume get extended!
extend.assert_called_once_with(self.TEST_VOLUME2, 2)
@patch('cinder.volume.drivers.nexenta.ns5.nfs.'
'NexentaNfsDriver.local_path')
@patch('oslo_concurrency.processutils.execute')
def test_extend_volume_sparsed(self, _execute, path):
self._create_volume_db_entry()
path.return_value = 'path'
self.drv.extend_volume(self.TEST_VOLUME, 2)
_execute.assert_called_with(
'truncate', '-s', '2G',
'path',
root_helper='sudo cinder-rootwrap /etc/cinder/rootwrap.conf',
run_as_root=True)
@patch('cinder.volume.drivers.nexenta.ns5.nfs.'
'NexentaNfsDriver.local_path')
@patch('oslo_concurrency.processutils.execute')
def test_extend_volume_nonsparsed(self, _execute, path):
self._create_volume_db_entry()
path.return_value = 'path'
with mock.patch.object(self.drv,
'sparsed_volumes',
False):
self.drv.extend_volume(self.TEST_VOLUME, 2)
_execute.assert_called_with(
'dd', 'if=/dev/zero', 'seek=1073741824',
'of=path',
'bs=1M', 'count=1024',
root_helper='sudo cinder-rootwrap /etc/cinder/rootwrap.conf',
run_as_root=True)
def test_get_capacity_info(self): def test_get_capacity_info(self):
self.nef_mock.get.return_value = { self.nef_mock.get.return_value = {
'bytesAvailable': 1000, 'bytesAvailable': 1000,

View File

@ -17,6 +17,7 @@ import hashlib
import os import os
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import units
from cinder import context from cinder import context
from cinder import db from cinder import db
@ -28,7 +29,7 @@ from cinder.volume.drivers.nexenta import options
from cinder.volume.drivers.nexenta import utils from cinder.volume.drivers.nexenta import utils
from cinder.volume.drivers import nfs from cinder.volume.drivers import nfs
VERSION = '1.1.0' VERSION = '1.2.0'
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
@ -40,6 +41,10 @@ class NexentaNfsDriver(nfs.NfsDriver):
1.0.0 - Initial driver version. 1.0.0 - Initial driver version.
1.1.0 - Added HTTPS support. 1.1.0 - Added HTTPS support.
Added use of sessions for REST calls. Added use of sessions for REST calls.
1.2.0 - Support for extend volume.
Support for extending the volume in
create_volume_from_snapshot if the size of new volume is larger
than original volume size.
""" """
driver_prefix = 'nexenta' driver_prefix = 'nexenta'
@ -94,21 +99,21 @@ class NexentaNfsDriver(nfs.NfsDriver):
:raise: :py:exc:`LookupError` :raise: :py:exc:`LookupError`
""" """
pool_name, fs = self._get_share_datasets(self.share) pool_name, fs = self._get_share_datasets(self.share)
url = 'storage/pools/%s' % (pool_name) url = 'storage/pools/%s' % pool_name
self.nef.get(url) self.nef.get(url)
url = 'storage/pools/%s/filesystems/%s' % (pool_name, fs) url = 'storage/pools/%s/filesystems/%s' % (
pool_name, self._escape_path(fs))
self.nef.get(url) self.nef.get(url)
path = '/'.join([pool_name, fs])
shared = False shared = False
response = self.nef.get('nas/nfs') response = self.nef.get('nas/nfs')
for share in response['data']: for share in response['data']:
if share.get('filesystem') == path: if share.get('filesystem') == self.share:
shared = True shared = True
break break
if not shared: if not shared:
raise LookupError(_("Dataset %s is not shared in Nexenta " raise LookupError(_("Dataset %s is not shared in Nexenta "
"Store appliance") % path) "Store appliance") % self.share)
def initialize_connection(self, volume, connector): def initialize_connection(self, volume, connector):
"""Allow connection to connector and return connection info. """Allow connection to connector and return connection info.
@ -157,7 +162,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
self._create_sparsed_file(self.local_path(volume), volume_size) self._create_sparsed_file(self.local_path(volume), volume_size)
else: else:
url = 'storage/pools/%s/filesystems/%s' % ( url = 'storage/pools/%s/filesystems/%s' % (
pool, '%2F'.join([fs, volume['name']])) pool, '%2F'.join([self._escape_path(fs), volume['name']]))
compression = self.nef.get(url).get('compressionMode') compression = self.nef.get(url).get('compressionMode')
if compression != 'off': if compression != 'off':
# Disable compression, because otherwise will not use space # Disable compression, because otherwise will not use space
@ -174,7 +179,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
except exception.NexentaException: except exception.NexentaException:
try: try:
url = 'storage/pools/%s/filesystems/%s' % ( url = 'storage/pools/%s/filesystems/%s' % (
pool, '%2F'.join([fs, volume['name']])) pool, '%2F'.join([self._escape_path(fs), volume['name']]))
self.nef.delete(url) self.nef.delete(url)
except exception.NexentaException: except exception.NexentaException:
LOG.warning(_LW("Cannot destroy created folder: " LOG.warning(_LW("Cannot destroy created folder: "
@ -188,7 +193,8 @@ class NexentaNfsDriver(nfs.NfsDriver):
:param volume: volume reference :param volume: volume reference
""" """
pool, fs = self._get_share_datasets(self.share) pool, fs_ = self._get_share_datasets(self.share)
fs = self._escape_path(fs_)
url = ('storage/pools/%(pool)s/filesystems/%(fs)s') % { url = ('storage/pools/%(pool)s/filesystems/%(fs)s') % {
'pool': pool, 'pool': pool,
'fs': '%2F'.join([fs, volume['name']]) 'fs': '%2F'.join([fs, volume['name']])
@ -221,7 +227,31 @@ class NexentaNfsDriver(nfs.NfsDriver):
if 'does not exist' in exc.args[0]: if 'does not exist' in exc.args[0]:
LOG.debug( LOG.debug(
'Volume %s does not exist on appliance', '/'.join( 'Volume %s does not exist on appliance', '/'.join(
[pool, fs])) [pool, fs_]))
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})
if self.sparsed_volumes:
self._execute('truncate', '-s', '%sG' % new_size,
self.local_path(volume),
run_as_root=self._execute_as_root)
else:
block_size_mb = 1
block_count = ((new_size - volume['size']) * units.Gi //
(block_size_mb * units.Mi))
self._execute(
'dd', 'if=/dev/zero',
'seek=%d' % (volume['size'] * units.Gi / block_size_mb),
'of=%s' % self.local_path(volume),
'bs=%dM' % block_size_mb,
'count=%d' % block_count,
run_as_root=True)
def create_snapshot(self, snapshot): def create_snapshot(self, snapshot):
"""Creates a snapshot. """Creates a snapshot.
@ -232,7 +262,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
pool, fs = self._get_share_datasets(self.share) pool, fs = self._get_share_datasets(self.share)
url = 'storage/pools/%(pool)s/filesystems/%(fs)s/snapshots' % { url = 'storage/pools/%(pool)s/filesystems/%(fs)s/snapshots' % {
'pool': pool, 'pool': pool,
'fs': '%2F'.join([fs, volume['name']]), 'fs': self._escape_path('/'.join([fs, volume['name']])),
} }
data = {'name': snapshot['name']} data = {'name': snapshot['name']}
self.nef.post(url, data) self.nef.post(url, data)
@ -247,7 +277,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
url = ('storage/pools/%(pool)s/' url = ('storage/pools/%(pool)s/'
'filesystems/%(fs)s/snapshots/%(snap)s') % { 'filesystems/%(fs)s/snapshots/%(snap)s') % {
'pool': pool, 'pool': pool,
'fs': '%2F'.join([fs, volume['name']]), 'fs': self._escape_path('/'.join([fs, volume['name']])),
'snap': snapshot['name'] 'snap': snapshot['name']
} }
try: try:
@ -272,7 +302,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
url = ('storage/pools/%(pool)s/' url = ('storage/pools/%(pool)s/'
'filesystems/%(fs)s/snapshots/%(snap)s/clone') % { 'filesystems/%(fs)s/snapshots/%(snap)s/clone') % {
'pool': pool, 'pool': pool,
'fs': '%2F'.join([fs, snapshot_vol['name']]), 'fs': self._escape_path('/'.join([fs, snapshot_vol['name']])),
'snap': snapshot['name'] 'snap': snapshot['name']
} }
path = '/'.join([pool, fs, volume['name']]) path = '/'.join([pool, fs, volume['name']])
@ -286,7 +316,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
url = ('storage/pools/%(pool)s/' url = ('storage/pools/%(pool)s/'
'filesystems/%(fs)s') % { 'filesystems/%(fs)s') % {
'pool': pool, 'pool': pool,
'fs': volume['name'] 'fs': self._escape_path('/'.join([fs, volume['name']]))
} }
self.nef.delete(url) self.nef.delete(url)
except exception.NexentaException: except exception.NexentaException:
@ -295,7 +325,11 @@ class NexentaNfsDriver(nfs.NfsDriver):
{'vol': dataset_path, {'vol': dataset_path,
'filesystem': volume['name']}) 'filesystem': volume['name']})
raise raise
if volume['size'] > snapshot['volume_size']:
new_size = volume['size']
volume['size'] = snapshot['volume_size']
self.extend_volume(volume, new_size)
volume['size'] = new_size
return {'provider_location': volume['provider_location']} return {'provider_location': volume['provider_location']}
def create_cloned_volume(self, volume, src_vref): def create_cloned_volume(self, volume, src_vref):
@ -307,6 +341,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
LOG.info(_LI('Creating clone of volume: %s'), src_vref['id']) LOG.info(_LI('Creating clone of volume: %s'), src_vref['id'])
snapshot = {'volume_name': src_vref['name'], snapshot = {'volume_name': src_vref['name'],
'volume_id': src_vref['id'], 'volume_id': src_vref['id'],
'volume_size': src_vref['size'],
'name': self._get_clone_snapshot_name(volume)} 'name': self._get_clone_snapshot_name(volume)}
self.create_snapshot(snapshot) self.create_snapshot(snapshot)
try: try:
@ -320,7 +355,6 @@ class NexentaNfsDriver(nfs.NfsDriver):
LOG.warning(_LW('Failed to delete zfs snapshot ' LOG.warning(_LW('Failed to delete zfs snapshot '
'%(volume_name)s@%(name)s'), snapshot) '%(volume_name)s@%(name)s'), snapshot)
raise raise
self.delete_snapshot(snapshot)
def local_path(self, volume): def local_path(self, volume):
"""Get volume path (mounted locally fs path) for given volume. """Get volume path (mounted locally fs path) for given volume.
@ -351,7 +385,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
LOG.debug( LOG.debug(
'Creating ACL for filesystem %s on Nexenta Store', filesystem) 'Creating ACL for filesystem %s on Nexenta Store', filesystem)
url = 'storage/pools/%s/filesystems/%s/acl' % ( url = 'storage/pools/%s/filesystems/%s/acl' % (
pool, '%2F'.join([path.replace('/', '%2F'), filesystem])) pool, self._escape_path('/'.join([path, filesystem])))
data = { data = {
"type": "allow", "type": "allow",
"principal": "everyone@", "principal": "everyone@",
@ -392,7 +426,7 @@ class NexentaNfsDriver(nfs.NfsDriver):
""" """
pool, fs = self._get_share_datasets(path) pool, fs = self._get_share_datasets(path)
url = 'storage/pools/%s/filesystems/%s' % ( url = 'storage/pools/%s/filesystems/%s' % (
pool, fs) pool, self._escape_path(fs))
data = self.nef.get(url) data = self.nef.get(url)
total = utils.str2size(data['bytesAvailable']) total = utils.str2size(data['bytesAvailable'])
allocated = utils.str2size(data['bytesUsed']) allocated = utils.str2size(data['bytesUsed'])
@ -444,3 +478,6 @@ class NexentaNfsDriver(nfs.NfsDriver):
'volume_backend_name': self.backend_name, 'volume_backend_name': self.backend_name,
'nfs_mount_point_base': self.nfs_mount_point_base 'nfs_mount_point_base': self.nfs_mount_point_base
} }
def _escape_path(self, path):
return path.replace('/', '%2F')

View File

@ -0,0 +1,3 @@
---
features:
- Added extend method to NFS driver for NexentaStor 5.