Merge "NetApp unified driver implementation."

This commit is contained in:
Jenkins 2013-06-21 01:42:50 +00:00 committed by Gerrit Code Review
commit a23df3db13
9 changed files with 713 additions and 3524 deletions

View File

@ -29,10 +29,6 @@ NEXENTA_MODULE = "cinder.volume.drivers.nexenta.volume.NexentaDriver"
SAN_MODULE = "cinder.volume.drivers.san.san.SanISCSIDriver" SAN_MODULE = "cinder.volume.drivers.san.san.SanISCSIDriver"
SOLARIS_MODULE = "cinder.volume.drivers.san.solaris.SolarisISCSIDriver" SOLARIS_MODULE = "cinder.volume.drivers.san.solaris.SolarisISCSIDriver"
LEFTHAND_MODULE = "cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver" LEFTHAND_MODULE = "cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver"
NETAPP_MODULE = "cinder.volume.drivers.netapp.iscsi.NetAppISCSIDriver"
NETAPP_CMODE_MODULE =\
"cinder.volume.drivers.netapp.iscsi.NetAppCmodeISCSIDriver"
NETAPP_NFS_MODULE = "cinder.volume.drivers.netapp.nfs.NetAppNFSDriver"
NFS_MODULE = "cinder.volume.drivers.nfs.NfsDriver" NFS_MODULE = "cinder.volume.drivers.nfs.NfsDriver"
SOLIDFIRE_MODULE = "cinder.volume.drivers.solidfire.SolidFire" SOLIDFIRE_MODULE = "cinder.volume.drivers.solidfire.SolidFire"
STORWIZE_SVC_MODULE = "cinder.volume.drivers.storwize_svc.StorwizeSVCDriver" STORWIZE_SVC_MODULE = "cinder.volume.drivers.storwize_svc.StorwizeSVCDriver"
@ -114,30 +110,6 @@ class VolumeDriverCompatibility(test.TestCase):
self._load_driver(LEFTHAND_MODULE) self._load_driver(LEFTHAND_MODULE)
self.assertEquals(self._driver_module_name(), LEFTHAND_MODULE) self.assertEquals(self._driver_module_name(), LEFTHAND_MODULE)
def test_netapp_old(self):
self._load_driver('cinder.volume.netapp.NetAppISCSIDriver')
self.assertEquals(self._driver_module_name(), NETAPP_MODULE)
def test_netapp_new(self):
self._load_driver(NETAPP_MODULE)
self.assertEquals(self._driver_module_name(), NETAPP_MODULE)
def test_netapp_cmode_old(self):
self._load_driver('cinder.volume.netapp.NetAppCmodeISCSIDriver')
self.assertEquals(self._driver_module_name(), NETAPP_CMODE_MODULE)
def test_netapp_cmode_new(self):
self._load_driver(NETAPP_CMODE_MODULE)
self.assertEquals(self._driver_module_name(), NETAPP_CMODE_MODULE)
def test_netapp_nfs_old(self):
self._load_driver('cinder.volume.netapp_nfs.NetAppNFSDriver')
self.assertEquals(self._driver_module_name(), NETAPP_NFS_MODULE)
def test_netapp_nfs_new(self):
self._load_driver(NETAPP_NFS_MODULE)
self.assertEquals(self._driver_module_name(), NETAPP_NFS_MODULE)
def test_nfs_old(self): def test_nfs_old(self):
self._load_driver('cinder.volume.nfs.NfsDriver') self._load_driver('cinder.volume.nfs.NfsDriver')
self.assertEquals(self._driver_module_name(), NFS_MODULE) self.assertEquals(self._driver_module_name(), NFS_MODULE)

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
"""Unit tests for the NetApp-specific NFS driver module (netapp_nfs).""" """Unit tests for the NetApp-specific NFS driver module."""
from cinder import context from cinder import context
from cinder import exception from cinder import exception
@ -23,15 +23,12 @@ from cinder import test
from cinder.volume import configuration as conf from cinder.volume import configuration as conf
from cinder.volume.drivers.netapp import api from cinder.volume.drivers.netapp import api
from cinder.volume.drivers.netapp import nfs as netapp_nfs from cinder.volume.drivers.netapp import nfs as netapp_nfs
from cinder.volume.drivers import nfs
from lxml import etree from lxml import etree
from mox import IgnoreArg from mox import IgnoreArg
from mox import IsA from mox import IsA
from mox import MockObject from mox import MockObject
import mox import mox
import suds
import types
def create_configuration(): def create_configuration():
@ -74,63 +71,11 @@ class FakeResponce(object):
self.Reason = 'Sample error' self.Reason = 'Sample error'
class NetappNfsDriverTestCase(test.TestCase): class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
"""Test case for NetApp specific NFS clone driver.""" """Test direct NetApp C Mode driver."""
def setUp(self): def setUp(self):
super(NetappNfsDriverTestCase, self).setUp() super(NetappDirectCmodeNfsDriverTestCase, self).setUp()
self._custom_setup()
self._driver = netapp_nfs.NetAppNFSDriver(
configuration=create_configuration())
def test_check_for_setup_error(self):
mox = self.mox
drv = self._driver
required_flags = ['netapp_wsdl_url',
'netapp_login',
'netapp_password',
'netapp_server_hostname',
'netapp_server_port']
# set required flags
for flag in required_flags:
setattr(drv.configuration, flag, None)
# check exception raises when flags are not set
self.assertRaises(exception.CinderException,
drv.check_for_setup_error)
# set required flags
for flag in required_flags:
setattr(drv.configuration, flag, 'val')
mox.StubOutWithMock(nfs.NfsDriver, 'check_for_setup_error')
nfs.NfsDriver.check_for_setup_error()
mox.ReplayAll()
drv.check_for_setup_error()
mox.VerifyAll()
# restore initial FLAGS
for flag in required_flags:
delattr(drv.configuration, flag)
def test_do_setup(self):
mox = self.mox
drv = self._driver
mox.StubOutWithMock(drv, 'check_for_setup_error')
mox.StubOutWithMock(drv, '_get_client')
drv.check_for_setup_error()
drv._get_client()
mox.ReplayAll()
drv.do_setup(IsA(context.RequestContext))
mox.VerifyAll()
def test_create_snapshot(self): def test_create_snapshot(self):
"""Test snapshot can be created and deleted.""" """Test snapshot can be created and deleted."""
@ -174,212 +119,6 @@ class NetappNfsDriverTestCase(test.TestCase):
mox.VerifyAll() mox.VerifyAll()
def _prepare_delete_snapshot_mock(self, snapshot_exists):
drv = self._driver
mox = self.mox
mox.StubOutWithMock(drv, '_get_provider_location')
mox.StubOutWithMock(drv, '_volume_not_present')
if snapshot_exists:
mox.StubOutWithMock(drv, '_execute')
mox.StubOutWithMock(drv, '_get_volume_path')
drv._get_provider_location(IgnoreArg())
drv._volume_not_present(IgnoreArg(),
IgnoreArg()).AndReturn(not snapshot_exists)
if snapshot_exists:
drv._get_volume_path(IgnoreArg(), IgnoreArg())
drv._execute('rm', None, run_as_root=True)
mox.ReplayAll()
return mox
def test_delete_existing_snapshot(self):
drv = self._driver
mox = self._prepare_delete_snapshot_mock(True)
drv.delete_snapshot(FakeSnapshot())
mox.VerifyAll()
def test_delete_missing_snapshot(self):
drv = self._driver
mox = self._prepare_delete_snapshot_mock(False)
drv.delete_snapshot(FakeSnapshot())
mox.VerifyAll()
def _prepare_clone_mock(self, status):
drv = self._driver
mox = self.mox
volume = FakeVolume()
setattr(volume, 'provider_location', '127.0.0.1:/nfs')
drv._client = MockObject(suds.client.Client)
drv._client.factory = MockObject(suds.client.Factory)
drv._client.service = MockObject(suds.client.ServiceSelector)
# ApiProxy() method is generated by ServiceSelector at runtime from the
# XML, so mocking is impossible.
setattr(drv._client.service,
'ApiProxy',
types.MethodType(lambda *args, **kwargs: FakeResponce(status),
suds.client.ServiceSelector))
mox.StubOutWithMock(drv, '_get_host_id')
mox.StubOutWithMock(drv, '_get_full_export_path')
drv._get_host_id(IgnoreArg()).AndReturn('10')
drv._get_full_export_path(IgnoreArg(), IgnoreArg()).AndReturn('/nfs')
return mox
def test_successfull_clone_volume(self):
drv = self._driver
mox = self._prepare_clone_mock('passed')
# set required flags
setattr(drv.configuration, 'synchronous_snapshot_create', False)
mox.ReplayAll()
volume_name = 'volume_name'
clone_name = 'clone_name'
volume_id = volume_name + str(hash(volume_name))
drv._clone_volume(volume_name, clone_name, volume_id)
mox.VerifyAll()
def test_failed_clone_volume(self):
drv = self._driver
mox = self._prepare_clone_mock('failed')
mox.ReplayAll()
volume_name = 'volume_name'
clone_name = 'clone_name'
volume_id = volume_name + str(hash(volume_name))
self.assertRaises(exception.CinderException,
drv._clone_volume,
volume_name, clone_name, volume_id)
mox.VerifyAll()
def test_cloned_volume_size_fail(self):
volume_clone_fail = FakeVolume(1)
volume_src = FakeVolume(2)
try:
self._driver.create_cloned_volume(volume_clone_fail,
volume_src)
raise AssertionError()
except exception.CinderException:
pass
class NetappCmodeNfsDriverTestCase(test.TestCase):
"""Test case for NetApp C Mode specific NFS clone driver"""
def setUp(self):
super(NetappCmodeNfsDriverTestCase, self).setUp()
self._custom_setup()
def _custom_setup(self):
self._driver = netapp_nfs.NetAppCmodeNfsDriver(
configuration=create_configuration())
def test_check_for_setup_error(self):
mox = self.mox
drv = self._driver
required_flags = [
'netapp_wsdl_url',
'netapp_login',
'netapp_password',
'netapp_server_hostname',
'netapp_server_port']
# set required flags
for flag in required_flags:
setattr(drv.configuration, flag, None)
# check exception raises when flags are not set
self.assertRaises(exception.CinderException,
drv.check_for_setup_error)
# set required flags
for flag in required_flags:
setattr(drv.configuration, flag, 'val')
mox.ReplayAll()
drv.check_for_setup_error()
mox.VerifyAll()
# restore initial FLAGS
for flag in required_flags:
delattr(drv.configuration, flag)
def test_do_setup(self):
mox = self.mox
drv = self._driver
mox.StubOutWithMock(drv, 'check_for_setup_error')
mox.StubOutWithMock(drv, '_get_client')
drv.check_for_setup_error()
drv._get_client()
mox.ReplayAll()
drv.do_setup(IsA(context.RequestContext))
mox.VerifyAll()
def test_create_snapshot(self):
"""Test snapshot can be created and deleted"""
mox = self.mox
drv = self._driver
mox.StubOutWithMock(drv, '_clone_volume')
drv._clone_volume(IgnoreArg(), IgnoreArg(), IgnoreArg())
mox.ReplayAll()
drv.create_snapshot(FakeSnapshot())
mox.VerifyAll()
def test_create_volume_from_snapshot(self):
"""Tests volume creation from snapshot"""
drv = self._driver
mox = self.mox
volume = FakeVolume(1)
snapshot = FakeSnapshot(2)
self.assertRaises(exception.CinderException,
drv.create_volume_from_snapshot,
volume,
snapshot)
snapshot = FakeSnapshot(1)
location = '127.0.0.1:/nfs'
expected_result = {'provider_location': location}
mox.StubOutWithMock(drv, '_clone_volume')
mox.StubOutWithMock(drv, '_get_volume_location')
drv._clone_volume(IgnoreArg(), IgnoreArg(), IgnoreArg())
drv._get_volume_location(IgnoreArg()).AndReturn(location)
mox.ReplayAll()
loc = drv.create_volume_from_snapshot(volume, snapshot)
self.assertEquals(loc, expected_result)
mox.VerifyAll()
def _prepare_delete_snapshot_mock(self, snapshot_exists): def _prepare_delete_snapshot_mock(self, snapshot_exists):
drv = self._driver drv = self._driver
mox = self.mox mox = self.mox
@ -419,44 +158,6 @@ class NetappCmodeNfsDriverTestCase(test.TestCase):
mox.VerifyAll() mox.VerifyAll()
def _prepare_clone_mock(self, status):
drv = self._driver
mox = self.mox
volume = FakeVolume()
setattr(volume, 'provider_location', '127.0.0.1:/nfs')
drv._client = MockObject(suds.client.Client)
drv._client.factory = MockObject(suds.client.Factory)
drv._client.service = MockObject(suds.client.ServiceSelector)
# CloneNasFile method is generated by ServiceSelector at runtime from
# the
# XML, so mocking is impossible.
setattr(drv._client.service,
'CloneNasFile',
types.MethodType(lambda *args, **kwargs: FakeResponce(status),
suds.client.ServiceSelector))
mox.StubOutWithMock(drv, '_get_host_ip')
mox.StubOutWithMock(drv, '_get_export_path')
drv._get_host_ip(IgnoreArg()).AndReturn('127.0.0.1')
drv._get_export_path(IgnoreArg()).AndReturn('/nfs')
return mox
def test_clone_volume(self):
drv = self._driver
mox = self._prepare_clone_mock('passed')
mox.ReplayAll()
volume_name = 'volume_name'
clone_name = 'clone_name'
volume_id = volume_name + str(hash(volume_name))
drv._clone_volume(volume_name, clone_name, volume_id)
mox.VerifyAll()
def test_cloned_volume_size_fail(self): def test_cloned_volume_size_fail(self):
volume_clone_fail = FakeVolume(1) volume_clone_fail = FakeVolume(1)
volume_src = FakeVolume(2) volume_src = FakeVolume(2)
@ -467,12 +168,12 @@ class NetappCmodeNfsDriverTestCase(test.TestCase):
except exception.CinderException: except exception.CinderException:
pass pass
class NetappDirectCmodeNfsDriverTestCase(NetappCmodeNfsDriverTestCase):
"""Test direct NetApp C Mode driver"""
def _custom_setup(self): def _custom_setup(self):
kwargs = {}
kwargs['netapp_mode'] = 'proxy'
kwargs['configuration'] = create_configuration()
self._driver = netapp_nfs.NetAppDirectCmodeNfsDriver( self._driver = netapp_nfs.NetAppDirectCmodeNfsDriver(
configuration=create_configuration()) **kwargs)
def test_check_for_setup_error(self): def test_check_for_setup_error(self):
mox = self.mox mox = self.mox
@ -591,7 +292,7 @@ class NetappDirectCmodeNfsDriverTestCase(NetappCmodeNfsDriverTestCase):
class NetappDirect7modeNfsDriverTestCase(NetappDirectCmodeNfsDriverTestCase): class NetappDirect7modeNfsDriverTestCase(NetappDirectCmodeNfsDriverTestCase):
"""Test direct NetApp C Mode driver""" """Test direct NetApp C Mode driver."""
def _custom_setup(self): def _custom_setup(self):
self._driver = netapp_nfs.NetAppDirect7modeNfsDriver( self._driver = netapp_nfs.NetAppDirect7modeNfsDriver(
configuration=create_configuration()) configuration=create_configuration())

View File

@ -0,0 +1,147 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 NetApp, Inc.
# Copyright (c) 2012 OpenStack LLC.
# 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.
"""
Unified driver for NetApp storage systems.
Supports call to multiple storage systems of different families and protocols.
"""
from cinder import exception
from cinder.openstack.common import importutils
from cinder.openstack.common import log as logging
from cinder.volume.drivers.netapp.options import netapp_proxy_opts
from oslo.config import cfg
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONF.register_opts(netapp_proxy_opts)
#NOTE(singn): Holds family:{protocol:driver} registration information.
#Plug in new families and protocols to support new drivers.
#No other code modification required.
netapp_unified_plugin_registry =\
{'ontap_cluster':
{
'iscsi':
'cinder.volume.drivers.netapp.iscsi.NetAppDirectCmodeISCSIDriver',
'nfs': 'cinder.volume.drivers.netapp.nfs.NetAppDirectCmodeNfsDriver'
}, 'ontap_7mode':
{
'iscsi':
'cinder.volume.drivers.netapp.iscsi.NetAppDirect7modeISCSIDriver',
'nfs':
'cinder.volume.drivers.netapp.nfs.NetAppDirect7modeNfsDriver'
},
}
#NOTE(singn): Holds family:protocol information.
#Protocol represents the default protocol driver option
#in case no protocol is specified by the user in configuration.
netapp_family_default =\
{
'ontap_cluster': 'nfs',
'ontap_7mode': 'nfs'
}
class NetAppDriver(object):
""""NetApp unified block storage driver.
Acts as a mediator to NetApp storage drivers.
Proxies requests based on the storage family and protocol configured.
Override the proxy driver method by adding method in this driver.
"""
def __init__(self, *args, **kwargs):
super(NetAppDriver, self).__init__()
self.configuration = kwargs.get('configuration', None)
if self.configuration:
self.configuration.append_config_values(netapp_proxy_opts)
else:
raise exception.InvalidInput(
reason=_("Required configuration not found"))
self.driver = NetAppDriverFactory.create_driver(
self.configuration.netapp_storage_family,
self.configuration.netapp_storage_protocol,
*args, **kwargs)
def __setattr__(self, name, value):
"""Sets the attribute."""
if getattr(self, 'driver', None):
self.driver.__setattr__(name, value)
return
object.__setattr__(self, name, value)
def __getattr__(self, name):
""""Gets the attribute."""
drv = object.__getattribute__(self, 'driver')
return getattr(drv, name)
class NetAppDriverFactory(object):
"""Factory to instantiate appropriate NetApp driver."""
@staticmethod
def create_driver(
storage_family, storage_protocol, *args, **kwargs):
""""Creates an appropriate driver based on family and protocol."""
fmt = {'storage_family': storage_family,
'storage_protocol': storage_protocol}
LOG.info(_('Requested unified config: %(storage_family)s and '
'%(storage_protocol)s') % fmt)
storage_family = storage_family.lower()
family_meta = netapp_unified_plugin_registry.get(storage_family)
if not family_meta:
raise exception.InvalidInput(
reason=_('Storage family %s is not supported')
% storage_family)
if not storage_protocol:
storage_protocol = netapp_family_default.get(storage_family)
if not storage_protocol:
msg_fmt = {'storage_family': storage_family}
raise exception.InvalidInput(
reason=_('No default storage protocol found'
' for storage family %(storage_family)s')
% msg_fmt)
storage_protocol = storage_protocol.lower()
driver_loc = family_meta.get(storage_protocol)
if not driver_loc:
msg_fmt = {'storage_protocol': storage_protocol,
'storage_family': storage_family}
raise exception.InvalidInput(
reason=_('Protocol %(storage_protocol)s is not supported'
' for storage family %(storage_family)s')
% msg_fmt)
NetAppDriverFactory.check_netapp_driver(driver_loc)
kwargs = kwargs or {}
kwargs['netapp_mode'] = 'proxy'
driver = importutils.import_object(driver_loc, *args, **kwargs)
LOG.info(_('NetApp driver of family %(storage_family)s and protocol'
' %(storage_protocol)s loaded') % fmt)
return driver
@staticmethod
def check_netapp_driver(location):
"""Checks if the driver requested is a netapp driver."""
if location.find(".netapp.") == -1:
raise exception.InvalidInput(
reason=_("Only loading netapp drivers supported."))

File diff suppressed because it is too large Load Diff

View File

@ -22,52 +22,52 @@ import copy
import os import os
import time import time
from oslo.config import cfg
import suds
from suds.sax import text
from cinder import exception from cinder import exception
from cinder.openstack.common import log as logging from cinder.openstack.common import log as logging
from cinder.volume.drivers.netapp.api import NaApiError from cinder.volume.drivers.netapp.api import NaApiError
from cinder.volume.drivers.netapp.api import NaElement from cinder.volume.drivers.netapp.api import NaElement
from cinder.volume.drivers.netapp.api import NaServer from cinder.volume.drivers.netapp.api import NaServer
from cinder.volume.drivers.netapp.iscsi import netapp_opts from cinder.volume.drivers.netapp.options import netapp_basicauth_opts
from cinder.volume.drivers.netapp.options import netapp_connection_opts
from cinder.volume.drivers.netapp.options import netapp_transport_opts
from cinder.volume.drivers.netapp.utils import provide_ems
from cinder.volume.drivers.netapp.utils import validate_instantiation
from cinder.volume.drivers import nfs from cinder.volume.drivers import nfs
from oslo.config import cfg
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
netapp_nfs_opts = [
cfg.IntOpt('synchronous_snapshot_create',
default=0,
help='Does snapshot creation call returns immediately')]
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(netapp_nfs_opts) CONF.register_opts(netapp_connection_opts)
CONF.register_opts(netapp_transport_opts)
CONF.register_opts(netapp_basicauth_opts)
class NetAppNFSDriver(nfs.NfsDriver): class NetAppNFSDriver(nfs.NfsDriver):
"""Executes commands relating to Volumes.""" """Base class for NetApp NFS driver.
Executes commands relating to Volumes.
"""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# NOTE(vish): db is set by Manager # NOTE(vish): db is set by Manager
validate_instantiation(**kwargs)
self._execute = None self._execute = None
self._context = None self._context = None
super(NetAppNFSDriver, self).__init__(*args, **kwargs) super(NetAppNFSDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(netapp_opts) self.configuration.append_config_values(netapp_connection_opts)
self.configuration.append_config_values(netapp_nfs_opts) self.configuration.append_config_values(netapp_basicauth_opts)
self.configuration.append_config_values(netapp_transport_opts)
def set_execute(self, execute): def set_execute(self, execute):
self._execute = execute self._execute = execute
def do_setup(self, context): def do_setup(self, context):
self._context = context raise NotImplementedError()
self.check_for_setup_error()
self._client = self._get_client()
def check_for_setup_error(self): def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met.""" """Returns an error if prerequisites aren't met."""
self._check_dfm_flags() raise NotImplementedError()
super(NetAppNFSDriver, self).check_for_setup_error()
def create_volume_from_snapshot(self, volume, snapshot): def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot.""" """Creates a volume from a snapshot."""
@ -75,10 +75,10 @@ class NetAppNFSDriver(nfs.NfsDriver):
snap_size = snapshot.volume_size snap_size = snapshot.volume_size
if vol_size != snap_size: if vol_size != snap_size:
msg = (_('Cannot create volume of size %(vol_size)s from ' msg = _('Cannot create volume of size %(vol_size)s from '
'snapshot of size %(snap_size)s') % 'snapshot of size %(snap_size)s')
{'vol_size': vol_size, 'snap_size': snap_size}) msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size}
raise exception.CinderException(msg) raise exception.CinderException(msg % msg_fmt)
self._clone_volume(snapshot.name, volume.name, snapshot.volume_id) self._clone_volume(snapshot.name, volume.name, snapshot.volume_id)
share = self._get_volume_location(snapshot.volume_id) share = self._get_volume_location(snapshot.volume_id)
@ -101,95 +101,22 @@ class NetAppNFSDriver(nfs.NfsDriver):
self._execute('rm', self._get_volume_path(nfs_mount, snapshot.name), self._execute('rm', self._get_volume_path(nfs_mount, snapshot.name),
run_as_root=True) run_as_root=True)
def _check_dfm_flags(self):
"""Raises error if any required configuration flag for OnCommand proxy
is missing.
"""
required_flags = ['netapp_wsdl_url',
'netapp_login',
'netapp_password',
'netapp_server_hostname',
'netapp_server_port']
for flag in required_flags:
if not getattr(self.configuration, flag, None):
raise exception.CinderException(_('%s is not set') % flag)
def _get_client(self): def _get_client(self):
"""Creates SOAP _client for ONTAP-7 DataFabric Service.""" """Creates client for server."""
client = suds.client.Client( raise NotImplementedError()
self.configuration.netapp_wsdl_url,
username=self.configuration.netapp_login,
password=self.configuration.netapp_password)
soap_url = 'http://%s:%s/apis/soap/v1' % (
self.configuration.netapp_server_hostname,
self.configuration.netapp_server_port)
client.set_options(location=soap_url)
return client
def _get_volume_location(self, volume_id): def _get_volume_location(self, volume_id):
"""Returns NFS mount address as <nfs_ip_address>:<nfs_mount_dir>""" """Returns NFS mount address as <nfs_ip_address>:<nfs_mount_dir>."""
nfs_server_ip = self._get_host_ip(volume_id) nfs_server_ip = self._get_host_ip(volume_id)
export_path = self._get_export_path(volume_id) export_path = self._get_export_path(volume_id)
return (nfs_server_ip + ':' + export_path) return (nfs_server_ip + ':' + export_path)
def _clone_volume(self, volume_name, clone_name, volume_id): def _clone_volume(self, volume_name, clone_name, volume_id):
"""Clones mounted volume with OnCommand proxy API.""" """Clones mounted volume with OnCommand proxy API."""
host_id = self._get_host_id(volume_id) raise NotImplementedError()
export_path = self._get_full_export_path(volume_id, host_id)
request = self._client.factory.create('Request')
request.Name = 'clone-start'
clone_start_args = ('<source-path>%s/%s</source-path>'
'<destination-path>%s/%s</destination-path>')
request.Args = text.Raw(clone_start_args % (export_path,
volume_name,
export_path,
clone_name))
resp = self._client.service.ApiProxy(Target=host_id,
Request=request)
if (resp.Status == 'passed' and
self.configuration.synchronous_snapshot_create):
clone_id = resp.Results['clone-id'][0]
clone_id_info = clone_id['clone-id-info'][0]
clone_operation_id = int(clone_id_info['clone-op-id'][0])
self._wait_for_clone_finished(clone_operation_id, host_id)
elif resp.Status == 'failed':
raise exception.CinderException(resp.Reason)
def _wait_for_clone_finished(self, clone_operation_id, host_id):
"""
Polls ONTAP7 for clone status. Returns once clone is finished.
:param clone_operation_id: Identifier of ONTAP clone operation
"""
clone_list_options = ('<clone-id>'
'<clone-id-info>'
'<clone-op-id>%d</clone-op-id>'
'<volume-uuid></volume-uuid>'
'</clone-id>'
'</clone-id-info>')
request = self._client.factory.create('Request')
request.Name = 'clone-list-status'
request.Args = text.Raw(clone_list_options % clone_operation_id)
resp = self._client.service.ApiProxy(Target=host_id, Request=request)
while resp.Status != 'passed':
time.sleep(1)
resp = self._client.service.ApiProxy(Target=host_id,
Request=request)
def _get_provider_location(self, volume_id): def _get_provider_location(self, volume_id):
""" """Returns provider location for given volume."""
Returns provider location for given volume
:param volume_id:
"""
volume = self.db.volume_get(self._context, volume_id) volume = self.db.volume_get(self._context, volume_id)
return volume.provider_location return volume.provider_location
@ -201,38 +128,6 @@ class NetAppNFSDriver(nfs.NfsDriver):
"""Returns NFS export path for the given volume.""" """Returns NFS export path for the given volume."""
return self._get_provider_location(volume_id).split(':')[1] return self._get_provider_location(volume_id).split(':')[1]
def _get_host_id(self, volume_id):
"""Returns ID of the ONTAP-7 host."""
host_ip = self._get_host_ip(volume_id)
server = self._client.service
resp = server.HostListInfoIterStart(ObjectNameOrId=host_ip)
tag = resp.Tag
try:
res = server.HostListInfoIterNext(Tag=tag, Maximum=1)
if hasattr(res, 'Hosts') and res.Hosts.HostInfo:
return res.Hosts.HostInfo[0].HostId
finally:
server.HostListInfoIterEnd(Tag=tag)
def _get_full_export_path(self, volume_id, host_id):
"""Returns full path to the NFS share, e.g. /vol/vol0/home."""
export_path = self._get_export_path(volume_id)
command_args = '<pathname>%s</pathname>'
request = self._client.factory.create('Request')
request.Name = 'nfs-exportfs-storage-path'
request.Args = text.Raw(command_args % export_path)
resp = self._client.service.ApiProxy(Target=host_id,
Request=request)
if resp.Status == 'passed':
return resp.Results['actual-pathname'][0]
elif resp.Status == 'failed':
raise exception.CinderException(resp.Reason)
def _volume_not_present(self, nfs_mount, volume_name): def _volume_not_present(self, nfs_mount, volume_name):
"""Check if volume exists.""" """Check if volume exists."""
try: try:
@ -262,7 +157,8 @@ class NetAppNFSDriver(nfs.NfsDriver):
def _get_volume_path(self, nfs_share, volume_name): def _get_volume_path(self, nfs_share, volume_name):
"""Get volume path (local fs path) for given volume name on given nfs """Get volume path (local fs path) for given volume name on given nfs
share share.
@param nfs_share string, example 172.18.194.100:/var/nfs @param nfs_share string, example 172.18.194.100:/var/nfs
@param volume_name string, @param volume_name string,
example volume-91ee65ec-c473-4391-8c09-162b00c68a8c example volume-91ee65ec-c473-4391-8c09-162b00c68a8c
@ -276,10 +172,10 @@ class NetAppNFSDriver(nfs.NfsDriver):
src_vol_size = src_vref.size src_vol_size = src_vref.size
if vol_size != src_vol_size: if vol_size != src_vol_size:
msg = (_('Cannot create clone of size %(vol_size)s from ' msg = _('Cannot create clone of size %(vol_size)s from '
'volume of size %(src_vol_size)s') % 'volume of size %(src_vol_size)s')
{'vol_size': vol_size, 'src_vol_size': src_vol_size}) msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size}
raise exception.CinderException(msg) raise exception.CinderException(msg % msg_fmt)
self._clone_volume(src_vref.name, volume.name, src_vref.id) self._clone_volume(src_vref.name, volume.name, src_vref.id)
share = self._get_volume_location(src_vref.id) share = self._get_volume_location(src_vref.id)
@ -290,71 +186,6 @@ class NetAppNFSDriver(nfs.NfsDriver):
"""Retrieve status info from volume group.""" """Retrieve status info from volume group."""
super(NetAppNFSDriver, self)._update_volume_status() super(NetAppNFSDriver, self)._update_volume_status()
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (backend_name or
'NetApp_NFS_7mode')
self._stats["vendor_name"] = 'NetApp'
self._stats["driver_version"] = '1.0'
class NetAppCmodeNfsDriver (NetAppNFSDriver):
"""Executes commands related to volumes on c mode."""
def __init__(self, *args, **kwargs):
super(NetAppCmodeNfsDriver, self).__init__(*args, **kwargs)
def do_setup(self, context):
self._context = context
self.check_for_setup_error()
self._client = self._get_client()
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self._check_flags()
def _clone_volume(self, volume_name, clone_name, volume_id):
"""Clones mounted volume with NetApp Cloud Services."""
host_ip = self._get_host_ip(volume_id)
export_path = self._get_export_path(volume_id)
LOG.debug(_("Cloning with params ip %(host_ip)s, exp_path"
"%(export_path)s, vol %(volume_name)s, "
"clone_name %(clone_name)s"),
{'host_ip': host_ip, 'export_path': export_path,
'volume_name': volume_name, 'clone_name': clone_name})
self._client.service.CloneNasFile(host_ip, export_path,
volume_name, clone_name)
def _check_flags(self):
"""Raises error if any required configuration flag for NetApp Cloud
Webservices is missing.
"""
required_flags = ['netapp_wsdl_url',
'netapp_login',
'netapp_password',
'netapp_server_hostname',
'netapp_server_port']
for flag in required_flags:
if not getattr(self.configuration, flag, None):
raise exception.CinderException(_('%s is not set') % flag)
def _get_client(self):
"""Creates SOAP _client for NetApp Cloud service."""
client = suds.client.Client(
self.configuration.netapp_wsdl_url,
username=self.configuration.netapp_login,
password=self.configuration.netapp_password)
return client
def _update_volume_status(self):
"""Retrieve status info from volume group."""
super(NetAppCmodeNfsDriver, self)._update_volume_status()
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (backend_name or
'NetApp_NFS_Cluster')
self._stats["vendor_name"] = 'NetApp'
self._stats["driver_version"] = '1.0'
class NetAppDirectNfsDriver (NetAppNFSDriver): class NetAppDirectNfsDriver (NetAppNFSDriver):
"""Executes commands related to volumes on NetApp filer.""" """Executes commands related to volumes on NetApp filer."""
@ -409,26 +240,10 @@ class NetAppDirectNfsDriver (NetAppNFSDriver):
if not isinstance(elem, NaElement): if not isinstance(elem, NaElement):
raise ValueError('Expects NaElement') raise ValueError('Expects NaElement')
def _invoke_successfully(self, na_element, vserver=None):
"""Invoke the api for successful result.
If vserver is present then invokes vserver/vfiler api
else filer/Cluster api.
:param vserver: vserver/vfiler name.
"""
self._is_naelement(na_element)
server = copy.copy(self._client)
if vserver:
server.set_vserver(vserver)
else:
server.set_vserver(None)
result = server.invoke_successfully(na_element, True)
return result
def _get_ontapi_version(self): def _get_ontapi_version(self):
"""Gets the supported ontapi version.""" """Gets the supported ontapi version."""
ontapi_version = NaElement('system-get-ontapi-version') ontapi_version = NaElement('system-get-ontapi-version')
res = self._invoke_successfully(ontapi_version, False) res = self._client.invoke_successfully(ontapi_version, False)
major = res.get_child_content('major-version') major = res.get_child_content('major-version')
minor = res.get_child_content('minor-version') minor = res.get_child_content('minor-version')
return (major, minor) return (major, minor)
@ -447,6 +262,22 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
(major, minor) = self._get_ontapi_version() (major, minor) = self._get_ontapi_version()
client.set_api_version(major, minor) client.set_api_version(major, minor)
def _invoke_successfully(self, na_element, vserver=None):
"""Invoke the api for successful result.
If vserver is present then invokes vserver api
else Cluster api.
:param vserver: vserver name.
"""
self._is_naelement(na_element)
server = copy.copy(self._client)
if vserver:
server.set_vserver(vserver)
else:
server.set_vserver(None)
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):
"""Clones mounted volume on NetApp Cluster.""" """Clones mounted volume on NetApp Cluster."""
host_ip = self._get_host_ip(volume_id) host_ip = self._get_host_ip(volume_id)
@ -495,17 +326,18 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
vols = attr_list.get_children() vols = attr_list.get_children()
vol_id = vols[0].get_child_by_name('volume-id-attributes') vol_id = vols[0].get_child_by_name('volume-id-attributes')
return vol_id.get_child_content('name') return vol_id.get_child_content('name')
raise exception.NotFound(_("No volume on cluster with vserver" msg_fmt = {'vserver': vserver, 'junction': junction}
"%(vserver)s and junction path " raise exception.NotFound(_("""No volume on cluster with vserver
"%(junction)s"), {'vserver': vserver, %(vserver)s and junction path %(junction)s
'junction': junction}) """) % msg_fmt)
def _clone_file(self, volume, src_path, dest_path, vserver=None): def _clone_file(self, volume, src_path, dest_path, vserver=None):
"""Clones file on vserver.""" """Clones file on vserver."""
LOG.debug(_("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"), dest %(dest_path)s, vserver %(vserver)s""")
{'volume': volume, 'src_path': src_path, msg_fmt = {'volume': volume, 'src_path': src_path,
'dest_path': dest_path, 'vserver': vserver}) 'dest_path': dest_path, 'vserver': vserver}
LOG.debug(msg % msg_fmt)
clone_create = NaElement.create_node_with_children( clone_create = NaElement.create_node_with_children(
'clone-create', 'clone-create',
**{'volume': volume, 'source-path': src_path, **{'volume': volume, 'source-path': src_path,
@ -515,12 +347,13 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
def _update_volume_status(self): def _update_volume_status(self):
"""Retrieve status info from volume group.""" """Retrieve status info from volume group."""
super(NetAppDirectCmodeNfsDriver, self)._update_volume_status() super(NetAppDirectCmodeNfsDriver, self)._update_volume_status()
netapp_backend = 'NetApp_NFS_cluster_direct'
backend_name = self.configuration.safe_get('volume_backend_name') backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (backend_name or self._stats["volume_backend_name"] = (backend_name or
'NetApp_NFS_cluster_direct') netapp_backend)
self._stats["vendor_name"] = 'NetApp' self._stats["vendor_name"] = 'NetApp'
self._stats["driver_version"] = '1.0' self._stats["driver_version"] = '1.0'
provide_ems(self, self._client, self._stats, netapp_backend)
class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver): class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
@ -534,6 +367,22 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
(major, minor) = self._get_ontapi_version() (major, minor) = self._get_ontapi_version()
client.set_api_version(major, minor) client.set_api_version(major, minor)
def _invoke_successfully(self, na_element, vfiler=None):
"""Invoke the api for successful result.
If vfiler is present then invokes vfiler api
else filer api.
:param vfiler: vfiler name.
"""
self._is_naelement(na_element)
server = copy.copy(self._client)
if vfiler:
server.set_vfiler(vfiler)
else:
server.set_vfiler(None)
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):
"""Clones mounted volume with NetApp filer.""" """Clones mounted volume with NetApp filer."""
export_path = self._get_export_path(volume_id) export_path = self._get_export_path(volume_id)
@ -565,8 +414,9 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
:returns: clone-id :returns: clone-id
""" """
LOG.debug(_("Cloning with src %(src_path)s, dest %(dest_path)s"), msg_fmt = {'src_path': src_path, 'dest_path': dest_path}
{'src_path': src_path, 'dest_path': dest_path}) LOG.debug(_("""Cloning with src %(src_path)s, dest %(dest_path)s""")
% msg_fmt)
clone_start = NaElement.create_node_with_children( clone_start = NaElement.create_node_with_children(
'clone-start', 'clone-start',
**{'source-path': src_path, **{'source-path': src_path,
@ -629,9 +479,11 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
def _update_volume_status(self): def _update_volume_status(self):
"""Retrieve status info from volume group.""" """Retrieve status info from volume group."""
super(NetAppDirect7modeNfsDriver, self)._update_volume_status() super(NetAppDirect7modeNfsDriver, self)._update_volume_status()
netapp_backend = 'NetApp_NFS_7mode_direct'
backend_name = self.configuration.safe_get('volume_backend_name') backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (backend_name or self._stats["volume_backend_name"] = (backend_name or
'NetApp_NFS_7mode_direct') 'NetApp_NFS_7mode_direct')
self._stats["vendor_name"] = 'NetApp' self._stats["vendor_name"] = 'NetApp'
self._stats["driver_version"] = '1.0' self._stats["driver_version"] = '1.0'
provide_ems(self, self._client, self._stats, netapp_backend,
server_type="7mode")

View File

@ -0,0 +1,77 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 NetApp, Inc.
# Copyright (c) 2012 OpenStack LLC.
# 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.
"""Contains configuration options for NetApp drivers.
Common place to hold configuration options for all NetApp drivers.
Options need to be grouped into granular units to be able to be reused
by different modules and classes. This does not restrict declaring options in
individual modules. If options are not re usable then can be declared in
individual modules. It is recommended to Keep options at a single
place to ensure re usability and better management of configuration options.
"""
from oslo.config import cfg
netapp_proxy_opts = [
cfg.StrOpt('netapp_storage_family',
default='ontap_cluster',
help='Storage family type.'),
cfg.StrOpt('netapp_storage_protocol',
default=None,
help='Storage protocol type.'), ]
netapp_connection_opts = [
cfg.StrOpt('netapp_server_hostname',
default=None,
help='Host name for the storage controller'),
cfg.IntOpt('netapp_server_port',
default=80,
help='Port number for the storage controller'), ]
netapp_transport_opts = [
cfg.StrOpt('netapp_transport_type',
default='http',
help='Transport type protocol'), ]
netapp_basicauth_opts = [
cfg.StrOpt('netapp_login',
default=None,
help='User name for the storage controller'),
cfg.StrOpt('netapp_password',
default=None,
help='Password for the storage controller',
secret=True), ]
netapp_provisioning_opts = [
cfg.FloatOpt('netapp_size_multiplier',
default=1.2,
help='Volume size multiplier to ensure while creation'),
cfg.StrOpt('netapp_volume_list',
default=None,
help='Comma separated volumes to be used for provisioning'), ]
netapp_cluster_opts = [
cfg.StrOpt('netapp_vserver',
default='openstack',
help='Cluster vserver to use for provisioning'), ]
netapp_7mode_opts = [
cfg.StrOpt('netapp_vfiler',
default=None,
help='Vfiler to use for provisioning'), ]

View File

@ -0,0 +1,120 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 NetApp, Inc.
# Copyright (c) 2012 OpenStack LLC.
# 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.
"""
Utilities for NetApp drivers.
This module contains common utilities to be used by one or more
NetApp drivers to achieve the desired functionality.
"""
import copy
import socket
from cinder.openstack.common import log as logging
from cinder.openstack.common import timeutils
from cinder.volume.drivers.netapp.api import NaApiError
from cinder.volume.drivers.netapp.api import NaElement
LOG = logging.getLogger(__name__)
def provide_ems(requester, server, stats, netapp_backend,
server_type="cluster"):
"""Provide ems with volume stats for the requester.
:param server_type: cluster or 7mode.
"""
def _create_ems(stats, netapp_backend, server_type):
"""Create ems api request."""
ems_log = NaElement('ems-autosupport-log')
host = socket.getfqdn() or 'Cinder_node'
dest = "cluster node" if server_type == "cluster"\
else "7 mode controller"
ems_log.add_new_child('computer-name', host)
ems_log.add_new_child('event-id', '0')
ems_log.add_new_child('event-source',
'Cinder driver %s' % netapp_backend)
ems_log.add_new_child('app-version', stats.get('driver_version',
'Undefined'))
ems_log.add_new_child('category', 'provisioning')
ems_log.add_new_child('event-description',
'OpenStack volume created on %s' % dest)
ems_log.add_new_child('log-level', '6')
ems_log.add_new_child('auto-support', 'true')
return ems_log
def _create_vs_get():
"""Create vs_get api request."""
vs_get = NaElement('vserver-get-iter')
vs_get.add_new_child('max-records', '1')
query = NaElement('query')
query.add_node_with_children('vserver-info',
**{'vserver-type': 'node'})
vs_get.add_child_elem(query)
desired = NaElement('desired-attributes')
desired.add_node_with_children(
'vserver-info', **{'vserver-name': '', 'vserver-type': ''})
vs_get.add_child_elem(desired)
return vs_get
def _get_cluster_node(na_server):
"""Get the cluster node for ems."""
na_server.set_vserver(None)
vs_get = _create_vs_get()
res = na_server.invoke_successfully(vs_get)
if (res.get_child_content('num-records') and
int(res.get_child_content('num-records')) > 0):
attr_list = res.get_child_by_name('attributes-list')
vs_info = attr_list.get_child_by_name('vserver-info')
vs_name = vs_info.get_child_content('vserver-name')
return vs_name
raise NaApiError(code='Not found', message='No records found')
do_ems = True
if hasattr(requester, 'last_ems'):
sec_limit = 604800
if not (timeutils.is_older_than(requester.last_ems, sec_limit) or
timeutils.is_older_than(requester.last_ems, sec_limit - 59)):
do_ems = False
if do_ems:
na_server = copy.copy(server)
na_server.set_timeout(25)
ems = _create_ems(stats, netapp_backend, server_type)
try:
if server_type == "cluster":
node = _get_cluster_node(na_server)
na_server.set_vserver(node)
else:
na_server.set_vfiler(None)
na_server.invoke_successfully(ems, True)
requester.last_ems = timeutils.utcnow()
LOG.debug(_("ems executed successfully."))
except NaApiError as e:
LOG.debug(_("Failed to invoke ems. Message : %s") % e)
def validate_instantiation(**kwargs):
"""Checks if a driver is instantiated other than by the unified driver.
Helps check direct instantiation of netapp drivers.
Call this function in every netapp block driver constructor.
"""
if kwargs and kwargs.get('netapp_mode') == 'proxy':
return
LOG.warn(_("It is not the recommended way to use drivers by NetApp. "
"Please use NetAppDriver to achieve the functionality."))

View File

@ -83,12 +83,6 @@ MAPPING = {
'cinder.volume.drivers.san.solaris.SolarisISCSIDriver', 'cinder.volume.drivers.san.solaris.SolarisISCSIDriver',
'cinder.volume.san.HpSanISCSIDriver': 'cinder.volume.san.HpSanISCSIDriver':
'cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver', 'cinder.volume.drivers.san.hp_lefthand.HpSanISCSIDriver',
'cinder.volume.netapp.NetAppISCSIDriver':
'cinder.volume.drivers.netapp.iscsi.NetAppISCSIDriver',
'cinder.volume.netapp.NetAppCmodeISCSIDriver':
'cinder.volume.drivers.netapp.iscsi.NetAppCmodeISCSIDriver',
'cinder.volume.netapp_nfs.NetAppNFSDriver':
'cinder.volume.drivers.netapp.nfs.NetAppNFSDriver',
'cinder.volume.nfs.NfsDriver': 'cinder.volume.nfs.NfsDriver':
'cinder.volume.drivers.nfs.NfsDriver', 'cinder.volume.drivers.nfs.NfsDriver',
'cinder.volume.solidfire.SolidFire': 'cinder.volume.solidfire.SolidFire':