Merge "NetApp unified driver implementation."
This commit is contained in:
commit
a23df3db13
@ -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
@ -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())
|
||||||
|
147
cinder/volume/drivers/netapp/common.py
Normal file
147
cinder/volume/drivers/netapp/common.py
Normal 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
@ -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")
|
||||||
|
77
cinder/volume/drivers/netapp/options.py
Normal file
77
cinder/volume/drivers/netapp/options.py
Normal 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'), ]
|
120
cinder/volume/drivers/netapp/utils.py
Normal file
120
cinder/volume/drivers/netapp/utils.py
Normal 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."))
|
@ -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':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user