NetApp unified driver implementation.

NetApp has growing number of multiple drivers depending on storage families
and technologies. NetApp unified driver simplifies configuration
and provides single entry point for all storage technologies/drivers.
It provides new mechanism to support multiple NetApp technologies
and block drivers related to them. Deprecated 7mode dfm and c mode
webservice based drivers.

blueprint netapp-unified-driver

Change-Id: Ife3cb12ef2256e0124c9a02a968c20a580b5df93
This commit is contained in:
Navneet Singh 2013-05-17 04:19:50 -07:00
parent d6df6ce398
commit d92f73e04d
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"
SOLARIS_MODULE = "cinder.volume.drivers.san.solaris.SolarisISCSIDriver"
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"
SOLIDFIRE_MODULE = "cinder.volume.drivers.solidfire.SolidFire"
STORWIZE_SVC_MODULE = "cinder.volume.drivers.storwize_svc.StorwizeSVCDriver"
@ -114,30 +110,6 @@ class VolumeDriverCompatibility(test.TestCase):
self._load_driver(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):
self._load_driver('cinder.volume.nfs.NfsDriver')
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
# License for the specific language governing permissions and limitations
# 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 exception
@ -23,15 +23,12 @@ 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 cinder.volume.drivers import nfs
from lxml import etree
from mox import IgnoreArg
from mox import IsA
from mox import MockObject
import mox
import suds
import types
def create_configuration():
@ -74,63 +71,11 @@ class FakeResponce(object):
self.Reason = 'Sample error'
class NetappNfsDriverTestCase(test.TestCase):
"""Test case for NetApp specific NFS clone driver."""
class NetappDirectCmodeNfsDriverTestCase(test.TestCase):
"""Test direct NetApp C Mode driver."""
def setUp(self):
super(NetappNfsDriverTestCase, self).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()
super(NetappDirectCmodeNfsDriverTestCase, self).setUp()
self._custom_setup()
def test_create_snapshot(self):
"""Test snapshot can be created and deleted."""
@ -174,212 +119,6 @@ class NetappNfsDriverTestCase(test.TestCase):
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):
drv = self._driver
mox = self.mox
@ -419,44 +158,6 @@ class NetappCmodeNfsDriverTestCase(test.TestCase):
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):
volume_clone_fail = FakeVolume(1)
volume_src = FakeVolume(2)
@ -467,12 +168,12 @@ class NetappCmodeNfsDriverTestCase(test.TestCase):
except exception.CinderException:
pass
class NetappDirectCmodeNfsDriverTestCase(NetappCmodeNfsDriverTestCase):
"""Test direct NetApp C Mode driver"""
def _custom_setup(self):
kwargs = {}
kwargs['netapp_mode'] = 'proxy'
kwargs['configuration'] = create_configuration()
self._driver = netapp_nfs.NetAppDirectCmodeNfsDriver(
configuration=create_configuration())
**kwargs)
def test_check_for_setup_error(self):
mox = self.mox
@ -591,7 +292,7 @@ class NetappDirectCmodeNfsDriverTestCase(NetappCmodeNfsDriverTestCase):
class NetappDirect7modeNfsDriverTestCase(NetappDirectCmodeNfsDriverTestCase):
"""Test direct NetApp C Mode driver"""
"""Test direct NetApp C Mode driver."""
def _custom_setup(self):
self._driver = netapp_nfs.NetAppDirect7modeNfsDriver(
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 time
from oslo.config import cfg
import suds
from suds.sax import text
from cinder import exception
from cinder.openstack.common import log as logging
from cinder.volume.drivers.netapp.api import NaApiError
from cinder.volume.drivers.netapp.api import NaElement
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 oslo.config import cfg
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.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):
"""Executes commands relating to Volumes."""
"""Base class for NetApp NFS driver.
Executes commands relating to Volumes.
"""
def __init__(self, *args, **kwargs):
# NOTE(vish): db is set by Manager
validate_instantiation(**kwargs)
self._execute = None
self._context = None
super(NetAppNFSDriver, self).__init__(*args, **kwargs)
self.configuration.append_config_values(netapp_opts)
self.configuration.append_config_values(netapp_nfs_opts)
self.configuration.append_config_values(netapp_connection_opts)
self.configuration.append_config_values(netapp_basicauth_opts)
self.configuration.append_config_values(netapp_transport_opts)
def set_execute(self, execute):
self._execute = execute
def do_setup(self, context):
self._context = context
self.check_for_setup_error()
self._client = self._get_client()
raise NotImplementedError()
def check_for_setup_error(self):
"""Returns an error if prerequisites aren't met."""
self._check_dfm_flags()
super(NetAppNFSDriver, self).check_for_setup_error()
raise NotImplementedError()
def create_volume_from_snapshot(self, volume, snapshot):
"""Creates a volume from a snapshot."""
@ -75,10 +75,10 @@ class NetAppNFSDriver(nfs.NfsDriver):
snap_size = snapshot.volume_size
if vol_size != snap_size:
msg = (_('Cannot create volume of size %(vol_size)s from '
'snapshot of size %(snap_size)s') %
{'vol_size': vol_size, 'snap_size': snap_size})
raise exception.CinderException(msg)
msg = _('Cannot create volume of size %(vol_size)s from '
'snapshot of size %(snap_size)s')
msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size}
raise exception.CinderException(msg % msg_fmt)
self._clone_volume(snapshot.name, volume.name, 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),
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):
"""Creates SOAP _client for ONTAP-7 DataFabric Service."""
client = suds.client.Client(
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
"""Creates client for server."""
raise NotImplementedError()
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)
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."""
host_id = self._get_host_id(volume_id)
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)
raise NotImplementedError()
def _get_provider_location(self, volume_id):
"""
Returns provider location for given volume
:param volume_id:
"""
"""Returns provider location for given volume."""
volume = self.db.volume_get(self._context, volume_id)
return volume.provider_location
@ -201,38 +128,6 @@ class NetAppNFSDriver(nfs.NfsDriver):
"""Returns NFS export path for the given volume."""
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):
"""Check if volume exists."""
try:
@ -262,7 +157,8 @@ class NetAppNFSDriver(nfs.NfsDriver):
def _get_volume_path(self, nfs_share, volume_name):
"""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 volume_name string,
example volume-91ee65ec-c473-4391-8c09-162b00c68a8c
@ -276,10 +172,10 @@ class NetAppNFSDriver(nfs.NfsDriver):
src_vol_size = src_vref.size
if vol_size != src_vol_size:
msg = (_('Cannot create clone of size %(vol_size)s from '
'volume of size %(src_vol_size)s') %
{'vol_size': vol_size, 'src_vol_size': src_vol_size})
raise exception.CinderException(msg)
msg = _('Cannot create clone of size %(vol_size)s from '
'volume of size %(src_vol_size)s')
msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size}
raise exception.CinderException(msg % msg_fmt)
self._clone_volume(src_vref.name, volume.name, 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."""
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):
"""Executes commands related to volumes on NetApp filer."""
@ -409,26 +240,10 @@ class NetAppDirectNfsDriver (NetAppNFSDriver):
if not isinstance(elem, 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):
"""Gets the supported 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')
minor = res.get_child_content('minor-version')
return (major, minor)
@ -447,6 +262,22 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
(major, minor) = self._get_ontapi_version()
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):
"""Clones mounted volume on NetApp Cluster."""
host_ip = self._get_host_ip(volume_id)
@ -495,17 +326,18 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
vols = attr_list.get_children()
vol_id = vols[0].get_child_by_name('volume-id-attributes')
return vol_id.get_child_content('name')
raise exception.NotFound(_("No volume on cluster with vserver"
"%(vserver)s and junction path "
"%(junction)s"), {'vserver': vserver,
'junction': junction})
msg_fmt = {'vserver': vserver, 'junction': junction}
raise exception.NotFound(_("""No volume on cluster with vserver
%(vserver)s and junction path %(junction)s
""") % msg_fmt)
def _clone_file(self, volume, src_path, dest_path, vserver=None):
"""Clones file on vserver."""
LOG.debug(_("Cloning with params volume %(volume)s,src %(src_path)s,"
"dest %(dest_path)s, vserver %(vserver)s"),
{'volume': volume, 'src_path': src_path,
'dest_path': dest_path, 'vserver': vserver})
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.debug(msg % msg_fmt)
clone_create = NaElement.create_node_with_children(
'clone-create',
**{'volume': volume, 'source-path': src_path,
@ -515,12 +347,13 @@ class NetAppDirectCmodeNfsDriver (NetAppDirectNfsDriver):
def _update_volume_status(self):
"""Retrieve status info from volume group."""
super(NetAppDirectCmodeNfsDriver, self)._update_volume_status()
netapp_backend = 'NetApp_NFS_cluster_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (backend_name or
'NetApp_NFS_cluster_direct')
netapp_backend)
self._stats["vendor_name"] = 'NetApp'
self._stats["driver_version"] = '1.0'
provide_ems(self, self._client, self._stats, netapp_backend)
class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
@ -534,6 +367,22 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
(major, minor) = self._get_ontapi_version()
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):
"""Clones mounted volume with NetApp filer."""
export_path = self._get_export_path(volume_id)
@ -565,8 +414,9 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
:returns: clone-id
"""
LOG.debug(_("Cloning with src %(src_path)s, dest %(dest_path)s"),
{'src_path': src_path, 'dest_path': dest_path})
msg_fmt = {'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',
**{'source-path': src_path,
@ -629,9 +479,11 @@ class NetAppDirect7modeNfsDriver (NetAppDirectNfsDriver):
def _update_volume_status(self):
"""Retrieve status info from volume group."""
super(NetAppDirect7modeNfsDriver, self)._update_volume_status()
netapp_backend = 'NetApp_NFS_7mode_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
self._stats["volume_backend_name"] = (backend_name or
'NetApp_NFS_7mode_direct')
self._stats["vendor_name"] = 'NetApp'
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.san.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.drivers.nfs.NfsDriver',
'cinder.volume.solidfire.SolidFire':