# under the License.
"""Unit tests for the NetApp-specific NFS driver module."""
+from lxml import etree
+import mox
+from mox import IgnoreArg
+from mox import IsA
+import os
+import socket
+
from cinder import context
from cinder import exception
+from cinder.image import image_utils
+from cinder.openstack.common import log as logging
from cinder import test
-
from cinder.volume import configuration as conf
from cinder.volume.drivers.netapp import api
from cinder.volume.drivers.netapp import nfs as netapp_nfs
-from lxml import etree
-from mox import IgnoreArg
-from mox import IsA
-from mox import MockObject
-import mox
+
+LOG = logging.getLogger(__name__)
def create_configuration():
mox = self.mox
drv = self._driver
+ mox.StubOutWithMock(netapp_nfs.NetAppNFSDriver, 'do_setup')
mox.StubOutWithMock(drv, 'check_for_setup_error')
mox.StubOutWithMock(drv, '_get_client')
mox.StubOutWithMock(drv, '_do_custom_setup')
+ netapp_nfs.NetAppNFSDriver.do_setup(IgnoreArg())
drv.check_for_setup_error()
drv._get_client()
drv._do_custom_setup(IgnoreArg())
mox.VerifyAll()
+ def test_register_img_in_cache_noshare(self):
+ volume = {'id': '1', 'name': 'testvol'}
+ volume['provider_location'] = '10.61.170.1:/share/path'
+ drv = self._driver
+ mox = self.mox
+ mox.StubOutWithMock(drv, '_do_clone_rel_img_cache')
+
+ drv._do_clone_rel_img_cache('testvol', 'img-cache-12345',
+ '10.61.170.1:/share/path',
+ 'img-cache-12345')
+
+ mox.ReplayAll()
+ drv._register_image_in_cache(volume, '12345')
+ mox.VerifyAll()
+
+ def test_register_img_in_cache_with_share(self):
+ volume = {'id': '1', 'name': 'testvol'}
+ volume['provider_location'] = '10.61.170.1:/share/path'
+ drv = self._driver
+ mox = self.mox
+ mox.StubOutWithMock(drv, '_do_clone_rel_img_cache')
+
+ drv._do_clone_rel_img_cache('testvol', 'img-cache-12345',
+ '10.61.170.1:/share/path',
+ 'img-cache-12345')
+
+ mox.ReplayAll()
+ drv._register_image_in_cache(volume, '12345')
+ mox.VerifyAll()
+
+ def test_find_image_in_cache_no_shares(self):
+ drv = self._driver
+ drv._mounted_shares = []
+ result = drv._find_image_in_cache('image_id')
+ if not result:
+ pass
+ else:
+ self.fail('Return result is unexpected')
+
+ def test_find_image_in_cache_shares(self):
+ drv = self._driver
+ mox = self.mox
+ drv._mounted_shares = ['testshare']
+ mox.StubOutWithMock(drv, '_get_mount_point_for_share')
+ mox.StubOutWithMock(os.path, 'exists')
+
+ drv._get_mount_point_for_share('testshare').AndReturn('/mnt')
+ os.path.exists('/mnt/img-cache-id').AndReturn(True)
+ mox.ReplayAll()
+ result = drv._find_image_in_cache('id')
+ (share, file_name) = result[0]
+ mox.VerifyAll()
+ drv._mounted_shares.remove('testshare')
+
+ if (share == 'testshare' and file_name == 'img-cache-id'):
+ pass
+ else:
+ LOG.warn(_("Share %(share)s and file name %(file_name)s")
+ % {'share': share, 'file_name': file_name})
+ self.fail('Return result is unexpected')
+
+ def test_find_old_cache_files_notexists(self):
+ drv = self._driver
+ mox = self.mox
+ cmd = ['find', '/mnt', '-maxdepth', '1', '-name',
+ 'img-cache*', '-amin', '+720']
+ setattr(drv.configuration, 'expiry_thres_minutes', 720)
+ mox.StubOutWithMock(drv, '_get_mount_point_for_share')
+ mox.StubOutWithMock(drv, '_execute')
+
+ drv._get_mount_point_for_share(IgnoreArg()).AndReturn('/mnt')
+ drv._execute(*cmd, run_as_root=True).AndReturn((None, ''))
+ mox.ReplayAll()
+ res = drv._find_old_cache_files('share')
+ mox.VerifyAll()
+ if len(res) == 0:
+ pass
+ else:
+ self.fail('No files expected but got return values.')
+
+ def test_find_old_cache_files_exists(self):
+ drv = self._driver
+ mox = self.mox
+ cmd = ['find', '/mnt', '-maxdepth', '1', '-name',
+ 'img-cache*', '-amin', '+720']
+ setattr(drv.configuration, 'expiry_thres_minutes', '720')
+ files = '/mnt/img-id1\n/mnt/img-id2\n'
+ r_files = ['img-id1', 'img-id2']
+ mox.StubOutWithMock(drv, '_get_mount_point_for_share')
+ mox.StubOutWithMock(drv, '_execute')
+ mox.StubOutWithMock(drv, '_shortlist_del_eligible_files')
+
+ drv._get_mount_point_for_share('share').AndReturn('/mnt')
+ drv._execute(*cmd, run_as_root=True).AndReturn((files, None))
+ drv._shortlist_del_eligible_files(
+ IgnoreArg(), r_files).AndReturn(r_files)
+ mox.ReplayAll()
+ res = drv._find_old_cache_files('share')
+ mox.VerifyAll()
+ if len(res) == len(r_files):
+ for f in res:
+ r_files.remove(f)
+ else:
+ self.fail('Returned files not same as expected.')
+
+ def test_delete_files_till_bytes_free_success(self):
+ drv = self._driver
+ mox = self.mox
+ files = [('img-cache-1', 230), ('img-cache-2', 380)]
+ mox.StubOutWithMock(drv, '_get_mount_point_for_share')
+ mox.StubOutWithMock(drv, '_delete_file')
+
+ drv._get_mount_point_for_share(IgnoreArg()).AndReturn('/mnt')
+ drv._delete_file('/mnt/img-cache-2').AndReturn(True)
+ drv._delete_file('/mnt/img-cache-1').AndReturn(True)
+ mox.ReplayAll()
+ drv._delete_files_till_bytes_free(files, 'share', bytes_to_free=1024)
+ mox.VerifyAll()
+
+ def test_clean_image_cache_exec(self):
+ drv = self._driver
+ mox = self.mox
+ drv.configuration.thres_avl_size_perc_start = 20
+ drv.configuration.thres_avl_size_perc_stop = 50
+ drv._mounted_shares = ['testshare']
+
+ mox.StubOutWithMock(drv, '_find_old_cache_files')
+ mox.StubOutWithMock(drv, '_delete_files_till_bytes_free')
+ mox.StubOutWithMock(drv, '_get_capacity_info')
+
+ drv._get_capacity_info('testshare').AndReturn((100, 19, 81))
+ drv._find_old_cache_files('testshare').AndReturn(['f1', 'f2'])
+ drv._delete_files_till_bytes_free(
+ ['f1', 'f2'], 'testshare', bytes_to_free=31)
+ mox.ReplayAll()
+ drv._clean_image_cache()
+ mox.VerifyAll()
+ drv._mounted_shares.remove('testshare')
+ if not drv.cleaning:
+ pass
+ else:
+ self.fail('Clean image cache failed.')
+
+ def test_clean_image_cache_noexec(self):
+ drv = self._driver
+ mox = self.mox
+ drv.configuration.thres_avl_size_perc_start = 20
+ drv.configuration.thres_avl_size_perc_stop = 50
+ drv._mounted_shares = ['testshare']
+
+ mox.StubOutWithMock(drv, '_get_capacity_info')
+
+ drv._get_capacity_info('testshare').AndReturn((100, 30, 70))
+ mox.ReplayAll()
+ drv._clean_image_cache()
+ mox.VerifyAll()
+ drv._mounted_shares.remove('testshare')
+ if not drv.cleaning:
+ pass
+ else:
+ self.fail('Clean image cache failed.')
+
+ def test_clone_image_fromcache(self):
+ drv = self._driver
+ mox = self.mox
+ volume = {'name': 'vol', 'size': '20'}
+ mox.StubOutWithMock(drv, '_find_image_in_cache')
+ mox.StubOutWithMock(drv, '_do_clone_rel_img_cache')
+ mox.StubOutWithMock(drv, '_post_clone_image')
+ mox.StubOutWithMock(drv, '_is_share_eligible')
+
+ drv._find_image_in_cache(IgnoreArg()).AndReturn(
+ [('share', 'file_name')])
+ drv._is_share_eligible(IgnoreArg(), IgnoreArg()).AndReturn(True)
+ drv._do_clone_rel_img_cache('file_name', 'vol', 'share', 'file_name')
+ drv._post_clone_image(volume)
+
+ mox.ReplayAll()
+ drv. clone_image(volume, ('image_location', None), 'image_id')
+ mox.VerifyAll()
+
+ def get_img_info(self, format):
+ class img_info(object):
+ def __init__(self, fmt):
+ self.file_format = fmt
+
+ return img_info(format)
+
+ def test_clone_image_cloneableshare_nospace(self):
+ drv = self._driver
+ mox = self.mox
+ volume = {'name': 'vol', 'size': '20'}
+ mox.StubOutWithMock(drv, '_find_image_in_cache')
+ mox.StubOutWithMock(drv, '_is_cloneable_share')
+ mox.StubOutWithMock(drv, '_is_share_eligible')
+
+ drv._find_image_in_cache(IgnoreArg()).AndReturn([])
+ drv._is_cloneable_share(IgnoreArg()).AndReturn('127.0.0.1:/share')
+ drv._is_share_eligible(IgnoreArg(), IgnoreArg()).AndReturn(False)
+
+ mox.ReplayAll()
+ (prop, cloned) = drv. clone_image(
+ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
+ mox.VerifyAll()
+ if not cloned and not prop['provider_location']:
+ pass
+ else:
+ self.fail('Expected not cloned, got cloned.')
+
+ def test_clone_image_cloneableshare_raw(self):
+ drv = self._driver
+ mox = self.mox
+ volume = {'name': 'vol', 'size': '20'}
+ mox.StubOutWithMock(drv, '_find_image_in_cache')
+ mox.StubOutWithMock(drv, '_is_cloneable_share')
+ mox.StubOutWithMock(drv, '_get_mount_point_for_share')
+ mox.StubOutWithMock(image_utils, 'qemu_img_info')
+ mox.StubOutWithMock(drv, '_clone_volume')
+ mox.StubOutWithMock(drv, '_discover_file_till_timeout')
+ mox.StubOutWithMock(drv, '_set_rw_permissions_for_all')
+ mox.StubOutWithMock(drv, '_resize_image_file')
+ mox.StubOutWithMock(drv, '_is_share_eligible')
+
+ drv._find_image_in_cache(IgnoreArg()).AndReturn([])
+ drv._is_cloneable_share(IgnoreArg()).AndReturn('127.0.0.1:/share')
+ drv._is_share_eligible(IgnoreArg(), IgnoreArg()).AndReturn(True)
+ drv._get_mount_point_for_share(IgnoreArg()).AndReturn('/mnt')
+ image_utils.qemu_img_info('/mnt/img-id').AndReturn(
+ self.get_img_info('raw'))
+ drv._clone_volume(
+ 'img-id', 'vol', share='127.0.0.1:/share', volume_id=None)
+ drv._get_mount_point_for_share(IgnoreArg()).AndReturn('/mnt')
+ drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True)
+ drv._set_rw_permissions_for_all('/mnt/vol')
+ drv._resize_image_file({'name': 'vol'}, IgnoreArg())
+
+ mox.ReplayAll()
+ drv. clone_image(
+ volume, ('nfs://127.0.0.1:/share/img-id', None), 'image_id')
+ mox.VerifyAll()
+
+ def test_clone_image_cloneableshare_notraw(self):
+ drv = self._driver
+ mox = self.mox
+ volume = {'name': 'vol', 'size': '20'}
+ mox.StubOutWithMock(drv, '_find_image_in_cache')
+ mox.StubOutWithMock(drv, '_is_cloneable_share')
+ mox.StubOutWithMock(drv, '_get_mount_point_for_share')
+ mox.StubOutWithMock(image_utils, 'qemu_img_info')
+ mox.StubOutWithMock(drv, '_clone_volume')
+ mox.StubOutWithMock(drv, '_discover_file_till_timeout')
+ mox.StubOutWithMock(drv, '_set_rw_permissions_for_all')
+ mox.StubOutWithMock(drv, '_resize_image_file')
+ mox.StubOutWithMock(image_utils, 'convert_image')
+ mox.StubOutWithMock(drv, '_register_image_in_cache')
+ mox.StubOutWithMock(drv, '_is_share_eligible')
+
+ drv._find_image_in_cache(IgnoreArg()).AndReturn([])
+ drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
+ '127.0.0.1:/share')
+ drv._is_share_eligible(IgnoreArg(), IgnoreArg()).AndReturn(True)
+ drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt')
+ image_utils.qemu_img_info('/mnt/img-id').AndReturn(
+ self.get_img_info('notraw'))
+ image_utils.convert_image(IgnoreArg(), IgnoreArg(), 'raw')
+ image_utils.qemu_img_info('/mnt/vol').AndReturn(
+ self.get_img_info('raw'))
+ drv._register_image_in_cache(IgnoreArg(), IgnoreArg())
+ drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt')
+ drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True)
+ drv._set_rw_permissions_for_all('/mnt/vol')
+ drv._resize_image_file({'name': 'vol'}, IgnoreArg())
+
+ mox.ReplayAll()
+ drv. clone_image(
+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
+ mox.VerifyAll()
+
+ def test_clone_image_file_not_discovered(self):
+ drv = self._driver
+ mox = self.mox
+ volume = {'name': 'vol', 'size': '20'}
+ mox.StubOutWithMock(drv, '_find_image_in_cache')
+ mox.StubOutWithMock(drv, '_is_cloneable_share')
+ mox.StubOutWithMock(drv, '_get_mount_point_for_share')
+ mox.StubOutWithMock(image_utils, 'qemu_img_info')
+ mox.StubOutWithMock(drv, '_clone_volume')
+ mox.StubOutWithMock(drv, '_discover_file_till_timeout')
+ mox.StubOutWithMock(image_utils, 'convert_image')
+ mox.StubOutWithMock(drv, '_register_image_in_cache')
+ mox.StubOutWithMock(drv, '_is_share_eligible')
+ mox.StubOutWithMock(drv, 'local_path')
+ mox.StubOutWithMock(os.path, 'exists')
+ mox.StubOutWithMock(drv, '_delete_file')
+
+ drv._find_image_in_cache(IgnoreArg()).AndReturn([])
+ drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
+ '127.0.0.1:/share')
+ drv._is_share_eligible(IgnoreArg(), IgnoreArg()).AndReturn(True)
+ drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt')
+ image_utils.qemu_img_info('/mnt/img-id').AndReturn(
+ self.get_img_info('notraw'))
+ image_utils.convert_image(IgnoreArg(), IgnoreArg(), 'raw')
+ image_utils.qemu_img_info('/mnt/vol').AndReturn(
+ self.get_img_info('raw'))
+ drv._register_image_in_cache(IgnoreArg(), IgnoreArg())
+ drv.local_path(IgnoreArg()).AndReturn('/mnt/vol')
+ drv._discover_file_till_timeout(IgnoreArg()).AndReturn(False)
+ drv.local_path(IgnoreArg()).AndReturn('/mnt/vol')
+ os.path.exists('/mnt/vol').AndReturn(True)
+ drv._delete_file('/mnt/vol')
+
+ mox.ReplayAll()
+ vol_dict, result = drv. clone_image(
+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
+ mox.VerifyAll()
+ self.assertFalse(result)
+ self.assertFalse(vol_dict['bootable'])
+ self.assertEqual(vol_dict['provider_location'], None)
+
+ def test_clone_image_resizefails(self):
+ drv = self._driver
+ mox = self.mox
+ volume = {'name': 'vol', 'size': '20'}
+ mox.StubOutWithMock(drv, '_find_image_in_cache')
+ mox.StubOutWithMock(drv, '_is_cloneable_share')
+ mox.StubOutWithMock(drv, '_get_mount_point_for_share')
+ mox.StubOutWithMock(image_utils, 'qemu_img_info')
+ mox.StubOutWithMock(drv, '_clone_volume')
+ mox.StubOutWithMock(drv, '_discover_file_till_timeout')
+ mox.StubOutWithMock(drv, '_set_rw_permissions_for_all')
+ mox.StubOutWithMock(drv, '_resize_image_file')
+ mox.StubOutWithMock(image_utils, 'convert_image')
+ mox.StubOutWithMock(drv, '_register_image_in_cache')
+ mox.StubOutWithMock(drv, '_is_share_eligible')
+ mox.StubOutWithMock(drv, 'local_path')
+ mox.StubOutWithMock(os.path, 'exists')
+ mox.StubOutWithMock(drv, '_delete_file')
+
+ drv._find_image_in_cache(IgnoreArg()).AndReturn([])
+ drv._is_cloneable_share('nfs://127.0.0.1/share/img-id').AndReturn(
+ '127.0.0.1:/share')
+ drv._is_share_eligible(IgnoreArg(), IgnoreArg()).AndReturn(True)
+ drv._get_mount_point_for_share('127.0.0.1:/share').AndReturn('/mnt')
+ image_utils.qemu_img_info('/mnt/img-id').AndReturn(
+ self.get_img_info('notraw'))
+ image_utils.convert_image(IgnoreArg(), IgnoreArg(), 'raw')
+ image_utils.qemu_img_info('/mnt/vol').AndReturn(
+ self.get_img_info('raw'))
+ drv._register_image_in_cache(IgnoreArg(), IgnoreArg())
+ drv.local_path(IgnoreArg()).AndReturn('/mnt/vol')
+ drv._discover_file_till_timeout(IgnoreArg()).AndReturn(True)
+ drv._set_rw_permissions_for_all('/mnt/vol')
+ drv._resize_image_file(
+ IgnoreArg(), IgnoreArg()).AndRaise(exception.InvalidResults())
+ drv.local_path(IgnoreArg()).AndReturn('/mnt/vol')
+ os.path.exists('/mnt/vol').AndReturn(True)
+ drv._delete_file('/mnt/vol')
+
+ mox.ReplayAll()
+ vol_dict, result = drv. clone_image(
+ volume, ('nfs://127.0.0.1/share/img-id', None), 'image_id')
+ mox.VerifyAll()
+ self.assertFalse(result)
+ self.assertFalse(vol_dict['bootable'])
+ self.assertEqual(vol_dict['provider_location'], None)
+
+ def test_is_cloneable_share_badformats(self):
+ drv = self._driver
+ strgs = ['10.61.666.22:/share/img',
+ 'nfs://10.61.666.22:/share/img',
+ 'nfs://10.61.666.22//share/img',
+ 'nfs://com.netapp.com:/share/img',
+ 'nfs://com.netapp.com//share/img',
+ 'com.netapp.com://share/im\g',
+ 'http://com.netapp.com://share/img',
+ 'nfs://com.netapp.com:/share/img',
+ 'nfs://com.netapp.com:8080//share/img'
+ 'nfs://com.netapp.com//img',
+ 'nfs://[ae::sr::ty::po]/img']
+ for strg in strgs:
+ res = drv._is_cloneable_share(strg)
+ if res:
+ msg = 'Invalid format matched for url %s.' % strg
+ self.fail(msg)
+
+ def test_is_cloneable_share_goodformat1(self):
+ drv = self._driver
+ mox = self.mox
+ strg = 'nfs://10.61.222.333/share/img'
+ mox.StubOutWithMock(drv, '_check_share_in_use')
+ drv._check_share_in_use(IgnoreArg(), IgnoreArg()).AndReturn('share')
+ mox.ReplayAll()
+ drv._is_cloneable_share(strg)
+ mox.VerifyAll()
+
+ def test_is_cloneable_share_goodformat2(self):
+ drv = self._driver
+ mox = self.mox
+ strg = 'nfs://10.61.222.333:8080/share/img'
+ mox.StubOutWithMock(drv, '_check_share_in_use')
+ drv._check_share_in_use(IgnoreArg(), IgnoreArg()).AndReturn('share')
+ mox.ReplayAll()
+ drv._is_cloneable_share(strg)
+ mox.VerifyAll()
+
+ def test_is_cloneable_share_goodformat3(self):
+ drv = self._driver
+ mox = self.mox
+ strg = 'nfs://com.netapp:8080/share/img'
+ mox.StubOutWithMock(drv, '_check_share_in_use')
+ drv._check_share_in_use(IgnoreArg(), IgnoreArg()).AndReturn('share')
+ mox.ReplayAll()
+ drv._is_cloneable_share(strg)
+ mox.VerifyAll()
+
+ def test_is_cloneable_share_goodformat4(self):
+ drv = self._driver
+ mox = self.mox
+ strg = 'nfs://netapp.com/share/img'
+ mox.StubOutWithMock(drv, '_check_share_in_use')
+ drv._check_share_in_use(IgnoreArg(), IgnoreArg()).AndReturn('share')
+ mox.ReplayAll()
+ drv._is_cloneable_share(strg)
+ mox.VerifyAll()
+
+ def test_is_cloneable_share_goodformat5(self):
+ drv = self._driver
+ mox = self.mox
+ strg = 'nfs://netapp.com/img'
+ mox.StubOutWithMock(drv, '_check_share_in_use')
+ drv._check_share_in_use(IgnoreArg(), IgnoreArg()).AndReturn('share')
+ mox.ReplayAll()
+ drv._is_cloneable_share(strg)
+ mox.VerifyAll()
+
+ def test_check_share_in_use_no_conn(self):
+ drv = self._driver
+ share = drv._check_share_in_use(None, '/dir')
+ if share:
+ self.fail('Unexpected share detected.')
+
+ def test_check_share_in_use_invalid_conn(self):
+ drv = self._driver
+ share = drv._check_share_in_use(':8989', '/dir')
+ if share:
+ self.fail('Unexpected share detected.')
+
+ def test_check_share_in_use_incorrect_host(self):
+ drv = self._driver
+ mox = self.mox
+ mox.StubOutWithMock(socket, 'gethostbyname')
+ socket.gethostbyname(IgnoreArg()).AndRaise(Exception())
+ mox.ReplayAll()
+ share = drv._check_share_in_use('incorrect:8989', '/dir')
+ mox.VerifyAll()
+ if share:
+ self.fail('Unexpected share detected.')
+
+ def test_check_share_in_use_success(self):
+ drv = self._driver
+ mox = self.mox
+ drv._mounted_shares = ['127.0.0.1:/dir/share']
+ mox.StubOutWithMock(socket, 'gethostbyname')
+ mox.StubOutWithMock(drv, '_share_match_for_ip')
+ socket.gethostbyname(IgnoreArg()).AndReturn('10.22.33.44')
+ drv._share_match_for_ip(
+ '10.22.33.44', ['127.0.0.1:/dir/share']).AndReturn('share')
+ mox.ReplayAll()
+ share = drv._check_share_in_use('127.0.0.1:8989', '/dir/share')
+ mox.VerifyAll()
+ if not share:
+ self.fail('Expected share not detected')
+
+ def test_construct_image_url_loc(self):
+ drv = self._driver
+ img_loc = (None,
+ [{'metadata':
+ {'share_location': 'nfs://host/path',
+ 'mount_point': '/opt/stack/data/glance',
+ 'type': 'nfs'},
+ 'url': 'file:///opt/stack/data/glance/image-id'}])
+ location = drv._construct_image_nfs_url(img_loc)
+ if location != "nfs://host/path/image-id":
+ self.fail("Unexpected direct url.")
+
+ def test_construct_image_url_direct(self):
+ drv = self._driver
+ img_loc = ("nfs://host/path/image-id", None)
+ location = drv._construct_image_nfs_url(img_loc)
+ if location != "nfs://host/path/image-id":
+ self.fail("Unexpected direct url.")
+
class NetappDirect7modeNfsDriverTestCase(NetappDirectCmodeNfsDriverTestCase):
"""Test direct NetApp C Mode driver."""
def test_do_setup(self):
mox = self.mox
drv = self._driver
-
+ mox.StubOutWithMock(netapp_nfs.NetAppNFSDriver, 'do_setup')
mox.StubOutWithMock(drv, 'check_for_setup_error')
mox.StubOutWithMock(drv, '_get_client')
mox.StubOutWithMock(drv, '_do_custom_setup')
-
+ netapp_nfs.NetAppNFSDriver.do_setup(IgnoreArg())
drv.check_for_setup_error()
drv._get_client()
drv._do_custom_setup(IgnoreArg())
volume = FakeVolume()
setattr(volume, 'provider_location', '127.0.0.1:/nfs')
- mox.StubOutWithMock(drv, '_get_export_path')
+ mox.StubOutWithMock(drv, '_get_export_ip_path')
mox.StubOutWithMock(drv, '_get_actual_path_for_export')
mox.StubOutWithMock(drv, '_start_clone')
mox.StubOutWithMock(drv, '_wait_for_clone_finish')
if status == 'fail':
mox.StubOutWithMock(drv, '_clear_clone')
- drv._get_export_path(IgnoreArg()).AndReturn('/nfs')
+ drv._get_export_ip_path(
+ IgnoreArg(), IgnoreArg()).AndReturn(('127.0.0.1', '/nfs'))
drv._get_actual_path_for_export(IgnoreArg()).AndReturn('/vol/vol1/nfs')
drv._start_clone(IgnoreArg(), IgnoreArg()).AndReturn(('1', '2'))
if status == 'fail':
import copy
import os
+import re
+import socket
+from threading import Timer
import time
+import urlparse
from oslo.config import cfg
from cinder import exception
+from cinder.image import image_utils
from cinder.openstack.common import log as logging
from cinder.openstack.common import processutils
from cinder import units
from cinder.volume.drivers.netapp.options import netapp_basicauth_opts
from cinder.volume.drivers.netapp.options import netapp_cluster_opts
from cinder.volume.drivers.netapp.options import netapp_connection_opts
+from cinder.volume.drivers.netapp.options import netapp_img_cache_opts
from cinder.volume.drivers.netapp.options import netapp_transport_opts
from cinder.volume.drivers.netapp import ssc_utils
+from cinder.volume.drivers.netapp import utils as na_utils
from cinder.volume.drivers.netapp.utils import get_volume_extra_specs
from cinder.volume.drivers.netapp.utils import provide_ems
from cinder.volume.drivers.netapp.utils import validate_instantiation
CONF.register_opts(netapp_connection_opts)
CONF.register_opts(netapp_transport_opts)
CONF.register_opts(netapp_basicauth_opts)
+CONF.register_opts(netapp_img_cache_opts)
class NetAppNFSDriver(nfs.NfsDriver):
self.configuration.append_config_values(netapp_connection_opts)
self.configuration.append_config_values(netapp_basicauth_opts)
self.configuration.append_config_values(netapp_transport_opts)
+ self.configuration.append_config_values(netapp_img_cache_opts)
def set_execute(self, execute):
self._execute = execute
def do_setup(self, context):
- raise NotImplementedError()
+ super(NetAppNFSDriver, self).do_setup(context)
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
export_path = self._get_export_path(volume_id)
return (nfs_server_ip + ':' + export_path)
- def _clone_volume(self, volume_name, clone_name, volume_id):
- """Clones mounted volume with OnCommand proxy API."""
+ def _clone_volume(self, volume_name, clone_name, volume_id, share=None):
+ """Clones mounted volume using NetApp api."""
raise NotImplementedError()
def _get_provider_location(self, volume_id):
def _update_volume_stats(self):
"""Retrieve stats info from volume group."""
super(NetAppNFSDriver, self)._update_volume_stats()
+ self._spawn_clean_cache_job()
+
+ def copy_image_to_volume(self, context, volume, image_service, image_id):
+ """Fetch the image from image_service and write it to the volume."""
+ super(NetAppNFSDriver, self).copy_image_to_volume(
+ context, volume, image_service, image_id)
+ LOG.info(_('Copied image to volume %s'), volume['name'])
+ self._register_image_in_cache(volume, image_id)
+
+ def _register_image_in_cache(self, volume, image_id):
+ """Stores image in the cache."""
+ file_name = 'img-cache-%s' % image_id
+ LOG.info(_("Registering image in cache %s"), file_name)
+ try:
+ self._do_clone_rel_img_cache(
+ volume['name'], file_name,
+ volume['provider_location'], file_name)
+ except Exception as e:
+ LOG.warn(
+ _('Exception while registering image %(image_id)s'
+ ' in cache. Exception: %(exc)s')
+ % {'image_id': image_id, 'exc': e.__str__()})
+
+ def _find_image_in_cache(self, image_id):
+ """Finds image in cache and returns list of shares with file name."""
+ result = []
+ if getattr(self, '_mounted_shares', None):
+ for share in self._mounted_shares:
+ dir = self._get_mount_point_for_share(share)
+ file_name = 'img-cache-%s' % image_id
+ file_path = '%s/%s' % (dir, file_name)
+ if os.path.exists(file_path):
+ LOG.debug(_('Found cache file for image %(image_id)s'
+ ' on share %(share)s')
+ % {'image_id': image_id, 'share': share})
+ result.append((share, file_name))
+ return result
+
+ def _do_clone_rel_img_cache(self, src, dst, share, cache_file):
+ """Do clone operation w.r.t image cache file."""
+ @utils.synchronized(cache_file, external=True)
+ def _do_clone():
+ dir = self._get_mount_point_for_share(share)
+ file_path = '%s/%s' % (dir, dst)
+ if not os.path.exists(file_path):
+ LOG.info(_('Cloning img from cache for %s'), dst)
+ self._clone_volume(src, dst, volume_id=None, share=share)
+ _do_clone()
+
+ @utils.synchronized('clean_cache')
+ def _spawn_clean_cache_job(self):
+ """Spawns a clean task if not running."""
+ if getattr(self, 'cleaning', None):
+ LOG.debug(_('Image cache cleaning in progress. Returning... '))
+ return
+ else:
+ #set cleaning to True
+ self.cleaning = True
+ t = Timer(0, self._clean_image_cache)
+ t.start()
+
+ def _clean_image_cache(self):
+ """Clean the image cache files in cache of space crunch."""
+ try:
+ LOG.debug(_('Image cache cleaning in progress.'))
+ thres_size_perc_start =\
+ self.configuration.thres_avl_size_perc_start
+ thres_size_perc_stop =\
+ self.configuration.thres_avl_size_perc_stop
+ for share in getattr(self, '_mounted_shares', []):
+ try:
+ total_size, total_avl, total_alc =\
+ self._get_capacity_info(share)
+ avl_percent = int((total_avl / total_size) * 100)
+ if avl_percent <= thres_size_perc_start:
+ LOG.info(_('Cleaning cache for share %s.'), share)
+ eligible_files = self._find_old_cache_files(share)
+ threshold_size = int(
+ (thres_size_perc_stop * total_size) / 100)
+ bytes_to_free = int(threshold_size - total_avl)
+ LOG.debug(_('Files to be queued for deletion %s'),
+ eligible_files)
+ self._delete_files_till_bytes_free(
+ eligible_files, share, bytes_to_free)
+ else:
+ continue
+ except Exception as e:
+ LOG.warn(_(
+ 'Exception during cache cleaning'
+ ' %(share)s. Message - %(ex)s')
+ % {'share': share, 'ex': e.__str__()})
+ continue
+ finally:
+ LOG.debug(_('Image cache cleaning done.'))
+ self.cleaning = False
+
+ def _shortlist_del_eligible_files(self, share, old_files):
+ """Prepares list of eligible files to be deleted from cache."""
+ raise NotImplementedError()
+
+ def _find_old_cache_files(self, share):
+ """Finds the old files in cache."""
+ mount_fs = self._get_mount_point_for_share(share)
+ threshold_minutes = self.configuration.expiry_thres_minutes
+ cmd = ['find', mount_fs, '-maxdepth', '1', '-name',
+ 'img-cache*', '-amin', '+%s' % (threshold_minutes)]
+ res, __ = self._execute(*cmd, run_as_root=True)
+ if res:
+ old_file_paths = res.strip('\n').split('\n')
+ mount_fs_len = len(mount_fs)
+ old_files = [x[mount_fs_len + 1:] for x in old_file_paths]
+ eligible_files = self._shortlist_del_eligible_files(
+ share, old_files)
+ return eligible_files
+ return []
+
+ def _delete_files_till_bytes_free(self, file_list, share, bytes_to_free=0):
+ """Delete files from disk till bytes are freed or list exhausted."""
+ LOG.debug(_('Bytes to free %s'), bytes_to_free)
+ if file_list and bytes_to_free > 0:
+ sorted_files = sorted(file_list, key=lambda x: x[1], reverse=True)
+ mount_fs = self._get_mount_point_for_share(share)
+ for f in sorted_files:
+ if f:
+ file_path = '%s/%s' % (mount_fs, f[0])
+ LOG.debug(_('Delete file path %s'), file_path)
+
+ @utils.synchronized(f[0], external=True)
+ def _do_delete():
+ if self._delete_file(file_path):
+ return True
+ return False
+ if _do_delete():
+ bytes_to_free = bytes_to_free - int(f[1])
+ if bytes_to_free <= 0:
+ return
+
+ def _delete_file(self, path):
+ """Delete file from disk and return result as boolean."""
+ try:
+ LOG.debug(_('Deleting file at path %s'), path)
+ cmd = ['rm', '-f', path]
+ self._execute(*cmd, run_as_root=True)
+ return True
+ except Exception as ex:
+ LOG.warning(_('Exception during deleting %s'), ex.__str__())
+ return False
+
+ def clone_image(self, volume, image_location, image_id):
+ """Create a volume efficiently from an existing image.
+
+ image_location is a string whose format depends on the
+ image service backend in use. The driver should use it
+ to determine whether cloning is possible.
+
+ image_id is a string which represents id of the image.
+ It can be used by the driver to introspect internal
+ stores or registry to do an efficient image clone.
+
+ Returns a dict of volume properties eg. provider_location,
+ boolean indicating whether cloning occurred.
+ """
+
+ cloned = False
+ post_clone = False
+ share = None
+ try:
+ cache_result = self._find_image_in_cache(image_id)
+ if cache_result:
+ cloned = self._clone_from_cache(volume, image_id, cache_result)
+ else:
+ cloned = self._direct_nfs_clone(volume, image_location,
+ image_id)
+ if cloned:
+ post_clone = self._post_clone_image(volume)
+ except Exception as e:
+ msg = e.msg if getattr(e, 'msg', None) else e.__str__()
+ LOG.warn(_('Unexpected exception in cloning image'
+ ' %(image_id)s. Message: %(msg)s')
+ % {'image_id': image_id, 'msg': msg})
+ vol_path = self.local_path(volume)
+ volume['provider_location'] = None
+ if os.path.exists(vol_path):
+ self._delete_file(vol_path)
+ finally:
+ cloned = cloned and post_clone
+ share = volume['provider_location'] if cloned else None
+ bootable = True if cloned else False
+ return {'provider_location': share, 'bootable': bootable}, cloned
+
+ def _clone_from_cache(self, volume, image_id, cache_result):
+ """Clones a copy from image cache."""
+ cloned = False
+ LOG.info(_('Cloning image %s from cache'), image_id)
+ for res in cache_result:
+ # Repeat tries in other shares if failed in some
+ (share, file_name) = res
+ LOG.debug(_('Cache share: %s'), share)
+ if (share and
+ self._is_share_eligible(share, volume['size'])):
+ try:
+ self._do_clone_rel_img_cache(
+ file_name, volume['name'], share, file_name)
+ cloned = True
+ volume['provider_location'] = share
+ break
+ except Exception:
+ LOG.warn(_('Unexpected exception during'
+ ' image cloning in share %s'), share)
+ return cloned
+
+ def _direct_nfs_clone(self, volume, image_location, image_id):
+ """Clone directly in nfs share."""
+ LOG.info(_('Cloning image %s directly in share'), image_id)
+ cloned = False
+ image_location = self._construct_image_nfs_url(image_location)
+ share = self._is_cloneable_share(image_location)
+ if share and self._is_share_eligible(share, volume['size']):
+ LOG.debug(_('Share is cloneable %s'), share)
+ volume['provider_location'] = share
+ (__, ___, img_file) = image_location.rpartition('/')
+ dir_path = self._get_mount_point_for_share(share)
+ img_path = '%s/%s' % (dir_path, img_file)
+ img_info = image_utils.qemu_img_info(img_path)
+ if img_info.file_format == 'raw':
+ LOG.debug(_('Image is raw %s'), image_id)
+ self._clone_volume(
+ img_file, volume['name'],
+ volume_id=None, share=share)
+ cloned = True
+ else:
+ LOG.info(
+ _('Image will locally be converted to raw %s'),
+ image_id)
+ dst = '%s/%s' % (dir_path, volume['name'])
+ image_utils.convert_image(img_path, dst, 'raw')
+ data = image_utils.qemu_img_info(dst)
+ if data.file_format != "raw":
+ raise exception.InvalidResults(
+ _("Converted to raw, but"
+ " format is now %s") % data.file_format)
+ else:
+ cloned = True
+ self._register_image_in_cache(
+ volume, image_id)
+ return cloned
+
+ def _post_clone_image(self, volume):
+ """Do operations post image cloning."""
+ LOG.info(_('Performing post clone for %s'), volume['name'])
+ vol_path = self.local_path(volume)
+ if self._discover_file_till_timeout(vol_path):
+ self._set_rw_permissions_for_all(vol_path)
+ self._resize_image_file(vol_path, volume['size'])
+ return True
+ raise exception.InvalidResults(
+ _("NFS file could not be discovered."))
+
+ def _resize_image_file(self, path, new_size):
+ """Resize the image file on share to new size."""
+ LOG.debug(_('Checking file for resize'))
+ if self._is_file_size_equal(path, new_size):
+ return
+ else:
+ LOG.info(_('Resizing file to %sG'), new_size)
+ image_utils.resize_image(path, new_size)
+ if self._is_file_size_equal(path, new_size):
+ return
+ else:
+ raise exception.InvalidResults(
+ _('Resizing image file failed.'))
+
+ def _is_file_size_equal(self, path, size):
+ """Checks if file size at path is equal to size."""
+ data = image_utils.qemu_img_info(path)
+ virt_size = data.virtual_size / units.GiB
+ if virt_size == size:
+ return True
+ else:
+ return False
+
+ def _discover_file_till_timeout(self, path, timeout=45):
+ """Checks if file size at path is equal to size."""
+ # Sometimes nfs takes time to discover file
+ # Retrying in case any unexpected situation occurs
+ retry_seconds = timeout
+ sleep_interval = 2
+ while True:
+ if os.path.exists(path):
+ return True
+ else:
+ if retry_seconds <= 0:
+ LOG.warn(_('Discover file retries exhausted.'))
+ return False
+ else:
+ time.sleep(sleep_interval)
+ retry_seconds = retry_seconds - sleep_interval
+
+ def _is_cloneable_share(self, image_location):
+ """Finds if the image at location is cloneable.
+
+ WebNFS url format with relative-path is supported.
+ Accepting all characters in path-names and checking
+ against the mounted shares which will contain only
+ allowed path segments.
+ """
+
+ nfs_loc_pattern =\
+ '^nfs://(([\w\-\.]+:{1}[\d]+|[\w\-\.]+)(/[^\/].*)*(/[^\/\\\\]+)$)'
+ matched = re.match(nfs_loc_pattern, image_location, flags=0)
+ if not matched:
+ LOG.debug(_('Image location not in the'
+ ' expected format %s'), image_location)
+ return None
+ conn = matched.group(2)
+ dir = matched.group(3) or '/'
+ return self._check_share_in_use(conn, dir)
+
+ def _share_match_for_ip(self, ip, shares):
+ """Returns the share that is served by ip.
+
+ Multiple shares can have same dir path but
+ can be served using different ips. It finds the
+ share which is served by ip on same nfs server.
+ """
+ raise NotImplementedError()
+
+ def _check_share_in_use(self, conn, dir):
+ """Checks if share is cinder mounted and returns it. """
+ try:
+ if conn:
+ host = conn.split(':')[0]
+ ipv4 = socket.gethostbyname(host)
+ share_candidates = []
+ for sh in self._mounted_shares:
+ sh_exp = sh.split(':')[1]
+ if sh_exp == dir:
+ share_candidates.append(sh)
+ if share_candidates:
+ LOG.debug(_('Found possible share matches %s'),
+ share_candidates)
+ return self._share_match_for_ip(ipv4, share_candidates)
+ except Exception:
+ LOG.warn(_("Unexpected exception while short listing used share."))
+ return None
+
+ def _construct_image_nfs_url(self, image_location):
+ """Construct direct url for nfs backend.
+
+ It creates direct url from image_location
+ which is a tuple with direct_url and locations.
+ Returns url with nfs scheme if nfs store
+ else returns url. It needs to be verified
+ by backend before use.
+ """
+
+ direct_url, locations = image_location
+
+ # Locations will be always a list of one until
+ # bp multiple-image-locations is introduced
+ if not locations:
+ return direct_url
+ location = locations[0]
+ url = location['url']
+ if not location['metadata']:
+ return url
+ location_type = location['metadata'].get('type')
+ if not location_type or location_type.lower() != "nfs":
+ return url
+ share_location = location['metadata'].get('share_location')
+ mount_point = location['metadata'].get('mount_point')
+ if not share_location or not mount_point:
+ return url
+ url_parse = urlparse.urlparse(url)
+ abs_path = os.path.join(url_parse.netloc, url_parse.path)
+ rel_path = os.path.relpath(abs_path, mount_point)
+ direct_url = "%s/%s" % (share_location, rel_path)
+ return direct_url
class NetAppDirectNfsDriver (NetAppNFSDriver):
super(NetAppDirectNfsDriver, self).__init__(*args, **kwargs)
def do_setup(self, context):
+ super(NetAppDirectNfsDriver, self).do_setup(context)
self._context = context
self.check_for_setup_error()
self._client = self._get_client()
"""Returns an error if prerequisites aren't met."""
self._check_flags()
- def _clone_volume(self, volume_name, clone_name, volume_id):
- """Clones mounted volume on NetApp filer."""
- raise NotImplementedError()
-
def _check_flags(self):
- """Raises error if any required configuration flag for NetApp
- filer is missing.
- """
+ """Raises error if any required configuration flag is missing."""
required_flags = ['netapp_login',
'netapp_password',
'netapp_server_hostname',
minor = res.get_child_content('minor-version')
return (major, minor)
+ def _get_export_ip_path(self, volume_id=None, share=None):
+ """Returns export ip and path.
+
+ One of volume id or share is used to return the values.
+ """
+
+ if volume_id:
+ host_ip = self._get_host_ip(volume_id)
+ export_path = self._get_export_path(volume_id)
+ elif share:
+ host_ip = share.split(':')[0]
+ export_path = share.split(':')[1]
+ else:
+ raise exception.InvalidInput('None of vol id or share specified.')
+ return (host_ip, export_path)
+
+ def _create_file_usage_req(self, path):
+ """Creates the request element for file_usage_get."""
+ file_use = NaElement.create_node_with_children(
+ 'file-usage-get', **{'path': path})
+ return file_use
+
class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
"""Executes commands related to volumes on c mode."""
sorted(containers, key=lambda x: x[1], reverse=True)]
return containers
- def _clone_volume(self, volume_name, clone_name, volume_id):
+ def _clone_volume(self, volume_name, clone_name,
+ volume_id, share=None):
"""Clones mounted volume on NetApp Cluster."""
- host_ip = self._get_host_ip(volume_id)
- export_path = self._get_export_path(volume_id)
+ (vserver, exp_volume) = self._get_vserver_and_exp_vol(volume_id, share)
+ self._clone_file(exp_volume, volume_name, clone_name, vserver)
+
+ def _get_vserver_and_exp_vol(self, volume_id=None, share=None):
+ """Gets the vserver and export volume for share."""
+ (host_ip, export_path) = self._get_export_ip_path(volume_id, share)
ifs = self._get_if_info_by_ip(host_ip)
vserver = ifs[0].get_child_content('vserver')
exp_volume = self._get_vol_by_junc_vserver(vserver, export_path)
- self._clone_file(exp_volume, volume_name, clone_name, vserver)
+ return (vserver, exp_volume)
def _get_if_info_by_ip(self, ip):
"""Gets the network interface info by ip."""
_('No interface found on cluster for ip %s')
% (ip))
+ def _get_verver_ips(self, vserver):
+ """Get ips for the vserver."""
+ result = na_utils.invoke_api(
+ self._client, api_name='net-interface-get-iter',
+ is_iter=True, tunnel=vserver)
+ if_list = []
+ for res in result:
+ records = res.get_child_content('num-records')
+ if records > 0:
+ attr_list = res['attributes-list']
+ ifs = attr_list.get_children()
+ if_list.extend(ifs)
+ return if_list
+
def _get_vol_by_junc_vserver(self, vserver, junction):
"""Gets the volume by junction path and vserver."""
vol_iter = NaElement('volume-get-iter')
def _clone_file(self, volume, src_path, dest_path, vserver=None):
"""Clones file on vserver."""
- msg = _("""Cloning with params volume %(volume)s,src %(src_path)s,
+ msg = _("""Cloning with params volume %(volume)s, src %(src_path)s,
dest %(dest_path)s, vserver %(vserver)s""")
msg_fmt = {'volume': volume, 'src_path': src_path,
'dest_path': dest_path, 'vserver': vserver}
LOG.warn(_("No shares found hence skipping ssc refresh."))
return
mnt_share_vols = set()
+ vs_ifs = self._get_verver_ips(self.vserver)
for vol in vols['all']:
for sh in self._mounted_shares:
+ host = sh.split(':')[0]
junction = sh.split(':')[1]
- if junction == vol.id['junction_path']:
+ ipv4 = socket.gethostbyname(host)
+ if (self._ip_in_ifs(ipv4, vs_ifs) and
+ junction == vol.id['junction_path']):
mnt_share_vols.add(vol)
vol.export['path'] = sh
break
vols[key] = vols[key] & mnt_share_vols
self.ssc_vols = vols
+ def _ip_in_ifs(self, ip, api_ifs):
+ """Checks if ip is listed for ifs in api format."""
+ if api_ifs is None:
+ return False
+ for ifc in api_ifs:
+ ifc_ip = ifc.get_child_content("address")
+ if ifc_ip == ip:
+ return True
+ return False
+
+ def _shortlist_del_eligible_files(self, share, old_files):
+ """Prepares list of eligible files to be deleted from cache."""
+ file_list = []
+ (vserver, exp_volume) = self._get_vserver_and_exp_vol(
+ volume_id=None, share=share)
+ for file in old_files:
+ path = '/vol/%s/%s' % (exp_volume, file)
+ u_bytes = self._get_cluster_file_usage(path, vserver)
+ file_list.append((file, u_bytes))
+ LOG.debug(_('Shortlisted del elg files %s'), file_list)
+ return file_list
+
+ def _get_cluster_file_usage(self, path, vserver):
+ """Gets the file unique bytes."""
+ LOG.debug(_('Getting file usage for %s'), path)
+ file_use = NaElement.create_node_with_children(
+ 'file-usage-get', **{'path': path})
+ res = self._invoke_successfully(file_use, vserver)
+ bytes = res.get_child_content('unique-bytes')
+ LOG.debug(_('file-usage for path %(path)s is %(bytes)s')
+ % {'path': path, 'bytes': bytes})
+ return bytes
+
+ def _share_match_for_ip(self, ip, shares):
+ """Returns the share that is served by ip.
+
+ Multiple shares can have same dir path but
+ can be served using different ips. It finds the
+ share which is served by ip on same nfs server.
+ """
+ ip_vserver = self._get_vserver_for_ip(ip)
+ if ip_vserver and shares:
+ for share in shares:
+ ip_sh = share.split(':')[0]
+ sh_vserver = self._get_vserver_for_ip(ip_sh)
+ if sh_vserver == ip_vserver:
+ LOG.debug(_('Share match found for ip %s'), ip)
+ return share
+ LOG.debug(_('No share match found for ip %s'), ip)
+ return None
+
+ def _get_vserver_for_ip(self, ip):
+ """Get vserver for the mentioned ip."""
+ try:
+ ifs = self._get_if_info_by_ip(ip)
+ vserver = ifs[0].get_child_content('vserver')
+ return vserver
+ except Exception:
+ return None
+
class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
"""Executes commands related to volumes on 7 mode."""
result = server.invoke_successfully(na_element, True)
return result
- def _clone_volume(self, volume_name, clone_name, volume_id):
+ def _clone_volume(self, volume_name, clone_name,
+ volume_id, share=None):
"""Clones mounted volume with NetApp filer."""
- export_path = self._get_export_path(volume_id)
+ (host_ip, export_path) = self._get_export_ip_path(volume_id, share)
storage_path = self._get_actual_path_for_export(export_path)
target_path = '%s/%s' % (storage_path, clone_name)
(clone_id, vol_uuid) = self._start_clone('%s/%s' % (storage_path,
except NaApiError as e:
if e.code != 'UnknownCloneId':
self._clear_clone(clone_id)
- raise
+ raise e
def _get_actual_path_for_export(self, export_path):
"""Gets the actual path on the filer for export path."""
self._stats["driver_version"] = self.VERSION
provide_ems(self, self._client, self._stats, netapp_backend,
server_type="7mode")
+
+ def _shortlist_del_eligible_files(self, share, old_files):
+ """Prepares list of eligible files to be deleted from cache."""
+ file_list = []
+ exp_volume = self._get_actual_path_for_export(share)
+ for file in old_files:
+ path = '/vol/%s/%s' % (exp_volume, file)
+ u_bytes = self._get_filer_file_usage(path)
+ file_list.append((file, u_bytes))
+ LOG.debug(_('Shortlisted del elg files %s'), file_list)
+ return file_list
+
+ def _get_filer_file_usage(self, path):
+ """Gets the file unique bytes."""
+ LOG.debug(_('Getting file usage for %s'), path)
+ file_use = NaElement.create_node_with_children(
+ 'file-usage-get', **{'path': path})
+ res = self._invoke_successfully(file_use)
+ bytes = res.get_child_content('unique-bytes')
+ LOG.debug(_('file-usage for path %(path)s is %(bytes)s')
+ % {'path': path, 'bytes': bytes})
+ return bytes
+
+ def _is_filer_ip(self, ip):
+ """Checks whether ip is on the same filer."""
+ try:
+ ifconfig = NaElement('net-ifconfig-get')
+ res = self._invoke_successfully(ifconfig, None)
+ if_info = res.get_child_by_name('interface-config-info')
+ if if_info:
+ ifs = if_info.get_children()
+ for intf in ifs:
+ v4_addr = intf.get_child_by_name('v4-primary-address')
+ if v4_addr:
+ ip_info = v4_addr.get_child_by_name('ip-address-info')
+ if ip_info:
+ address = ip_info.get_child_content('address')
+ if ip == address:
+ return True
+ else:
+ continue
+ except Exception:
+ return False
+ return False
+
+ def _share_match_for_ip(self, ip, shares):
+ """Returns the share that is served by ip.
+
+ Multiple shares can have same dir path but
+ can be served using different ips. It finds the
+ share which is served by ip on same nfs server.
+ """
+ if self._is_filer_ip(ip) and shares:
+ for share in shares:
+ ip_sh = share.split(':')[0]
+ if self._is_filer_ip(ip_sh):
+ LOG.debug(_('Share match found for ip %s'), ip)
+ return share
+ LOG.debug(_('No share match found for ip %s'), ip)
+ return None