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"
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)
import BaseHTTPServer
import httplib
-import logging as generic_logging
-import shutil
import StringIO
-import tempfile
from lxml import etree
+from cinder.exception import InvalidInput
from cinder.exception import VolumeBackendAPIException
from cinder.openstack.common import log as logging
from cinder import test
from cinder.volume import configuration as conf
-from cinder.volume.drivers.netapp import iscsi
-from cinder.volume.drivers.netapp.iscsi import netapp_opts
+from cinder.volume.drivers.netapp import common
+from cinder.volume.drivers.netapp.options import netapp_7mode_opts
+from cinder.volume.drivers.netapp.options import netapp_basicauth_opts
+from cinder.volume.drivers.netapp.options import netapp_cluster_opts
+from cinder.volume.drivers.netapp.options import netapp_connection_opts
+from cinder.volume.drivers.netapp.options import netapp_provisioning_opts
+from cinder.volume.drivers.netapp.options import netapp_transport_opts
LOG = logging.getLogger("cinder.volume.driver")
-#NOTE(rushiagr): A bug in Suds package
-# (https://fedorahosted.org/suds/ticket/359) causes nasty errors
-# with tests while using debug-level logging. Unfortunately,
-# the maintainers of the package stopped tending to any patch
-# requests almost two years back. So setting the logging level to
-# INFO here seems the only plausible workaround.
-generic_logging.getLogger('suds.mx.core').setLevel(generic_logging.INFO)
-
-WSDL_HEADER = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
- xmlns:na="http://www.netapp.com/management/v1"
- xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="NetAppDfm"
- targetNamespace="http://www.netapp.com/management/v1">"""
-
-WSDL_TYPES = """<types>
-<xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
- targetNamespace="http://www.netapp.com/management/v1">
-<xsd:element name="ApiProxy">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Request" type="na:Request"/>
- <xsd:element name="Target" type="xsd:string"/>
- <xsd:element minOccurs="0" name="Timeout" type="xsd:integer"/>
- <xsd:element minOccurs="0" name="Username" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="ApiProxyResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Response" type="na:Response"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetEditBegin">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="DatasetNameOrId" type="na:ObjNameOrId"/>
- <xsd:element minOccurs="0" name="Force" type="xsd:boolean"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetEditBeginResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="EditLockId" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetEditCommit">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="AssumeConfirmation"
- type="xsd:boolean"/>
- <xsd:element name="EditLockId" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetEditCommitResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="IsProvisioningFailure"
- type="xsd:boolean"/>
- <xsd:element minOccurs="0" name="JobIds" type="na:ArrayOfJobInfo"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetEditRollback">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="EditLockId" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetEditRollbackResult">
- <xsd:complexType/>
-</xsd:element>
-<xsd:element name="DatasetListInfoIterEnd">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetListInfoIterEndResult">
- <xsd:complexType/>
-</xsd:element>
-<xsd:element name="DatasetListInfoIterNext">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Maximum" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetListInfoIterNextResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Datasets" type="na:ArrayOfDatasetInfo"/>
- <xsd:element name="Records" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetListInfoIterStart">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="ObjectNameOrId"
- type="na:ObjNameOrId"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetListInfoIterStartResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Records" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetMemberListInfoIterEnd">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetMemberListInfoIterEndResult">
- <xsd:complexType/>
-</xsd:element>
-<xsd:element name="DatasetMemberListInfoIterNext">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Maximum" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetMemberListInfoIterNextResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="DatasetMembers"
- type="na:ArrayOfDatasetMemberInfo"/>
- <xsd:element name="Records" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetMemberListInfoIterStart">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="DatasetNameOrId" type="na:ObjNameOrId"/>
- <xsd:element minOccurs="0" name="IncludeExportsInfo"
- type="xsd:boolean"/>
- <xsd:element minOccurs="0" name="IncludeIndirect"
- type="xsd:boolean"/>
- <xsd:element minOccurs="0" name="MemberType" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetMemberListInfoIterStartResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Records" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetProvisionMember">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="EditLockId" type="xsd:integer"/>
- <xsd:element name="ProvisionMemberRequestInfo"
- type="na:ProvisionMemberRequestInfo"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetProvisionMemberResult">
- <xsd:complexType/>
-</xsd:element>
-<xsd:element name="DatasetRemoveMember">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="DatasetMemberParameters"
- type="na:ArrayOfDatasetMemberParameter"/>
- <xsd:element minOccurs="0" name="Destroy" type="xsd:boolean"/>
- <xsd:element name="EditLockId" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DatasetRemoveMemberResult">
- <xsd:complexType/>
-</xsd:element>
-<xsd:element name="DpJobProgressEventListIterEnd">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DpJobProgressEventListIterEndResult">
- <xsd:complexType/>
-</xsd:element>
-<xsd:element name="DpJobProgressEventListIterNext">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Maximum" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DpJobProgressEventListIterNextResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="ProgressEvents"
- type="na:ArrayOfDpJobProgressEventInfo"/>
- <xsd:element name="Records" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DpJobProgressEventListIterStart">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="JobId" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DpJobProgressEventListIterStartResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Records" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DfmAbout">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="IncludeDirectorySizeInfo"
- type="xsd:boolean"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="DfmAboutResult">
- <xsd:complexType>
- <xsd:all/>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="HostListInfoIterEnd">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="HostListInfoIterEndResult">
- <xsd:complexType/>
-</xsd:element>
-<xsd:element name="HostListInfoIterNext">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Maximum" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="HostListInfoIterNextResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Hosts" type="na:ArrayOfHostInfo"/>
- <xsd:element name="Records" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="HostListInfoIterStart">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="ObjectNameOrId"
- type="na:ObjNameOrId"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="HostListInfoIterStartResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Records" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="LunListInfoIterEnd">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="LunListInfoIterEndResult">
- <xsd:complexType/>
-</xsd:element>
-<xsd:element name="LunListInfoIterNext">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Maximum" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="LunListInfoIterNextResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Luns" type="na:ArrayOfLunInfo"/>
- <xsd:element name="Records" type="xsd:integer"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="LunListInfoIterStart">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="ObjectNameOrId"
- type="na:ObjNameOrId"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="LunListInfoIterStartResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element name="Records" type="xsd:integer"/>
- <xsd:element name="Tag" type="xsd:string"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="StorageServiceDatasetProvision">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="AssumeConfirmation"
- type="xsd:boolean"/>
- <xsd:element name="DatasetName" type="na:ObjName"/>
- <xsd:element name="StorageServiceNameOrId" type="na:ObjNameOrId"/>
- <xsd:element minOccurs="0" name="StorageSetDetails"
- type="na:ArrayOfStorageSetInfo"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:element name="StorageServiceDatasetProvisionResult">
- <xsd:complexType>
- <xsd:all>
- <xsd:element minOccurs="0" name="ConformanceAlerts"
- type="na:ArrayOfConformanceAlert"/>
- <xsd:element name="DatasetId" type="na:ObjId"/>
- <xsd:element minOccurs="0" name="DryRunResults"
- type="na:ArrayOfDryRunResult"/>
- </xsd:all>
- </xsd:complexType>
-</xsd:element>
-<xsd:complexType name="ArrayOfDatasetInfo">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="DatasetInfo"
- type="na:DatasetInfo"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="ArrayOfDatasetMemberInfo">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="DatasetMemberInfo"
- type="na:DatasetMemberInfo"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="ArrayOfDatasetMemberParameter">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="DatasetMemberParameter"
- type="na:DatasetMemberParameter"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="ArrayOfDfmMetadataField">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="DfmMetadataField"
- type="na:DfmMetadataField"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="ArrayOfDpJobProgressEventInfo">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="DpJobProgressEventInfo"
- type="na:DpJobProgressEventInfo"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="ArrayOfHostInfo">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="HostInfo" type="na:HostInfo"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="ArrayOfJobInfo">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="JobInfo" type="na:JobInfo"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="ArrayOfLunInfo">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="LunInfo" type="na:LunInfo"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="ArrayOfStorageSetInfo">
- <xsd:sequence>
- <xsd:element maxOccurs="unbounded" name="StorageSetInfo"
- type="na:StorageSetInfo"/>
- </xsd:sequence>
-</xsd:complexType>
-<xsd:complexType name="DatasetExportInfo">
- <xsd:all>
- <xsd:element minOccurs="0" name="DatasetExportProtocol"
- type="na:DatasetExportProtocol"/>
- <xsd:element minOccurs="0" name="DatasetLunMappingInfo"
- type="na:DatasetLunMappingInfo"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:simpleType name="DatasetExportProtocol">
- <xsd:restriction base="xsd:string"/>
-</xsd:simpleType>
-<xsd:complexType name="DatasetInfo">
- <xsd:all>
- <xsd:element name="DatasetId" type="na:ObjId"/>
- <xsd:element name="DatasetName" type="na:ObjName"/>
- <xsd:element name="DatasetMetadata" type="na:ArrayOfDfmMetadataField"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="DatasetLunMappingInfo">
- <xsd:all>
- <xsd:element name="IgroupOsType" type="xsd:string"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="DatasetMemberInfo">
- <xsd:all>
- <xsd:element name="MemberId" type="na:ObjId"/>
- <xsd:element name="MemberName" type="na:ObjName"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="DatasetMemberParameter">
- <xsd:all>
- <xsd:element name="ObjectNameOrId" type="na:ObjNameOrId"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="DfmMetadataField">
- <xsd:all>
- <xsd:element name="FieldName" type="xsd:string"/>
- <xsd:element name="FieldValue" type="xsd:string"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="DpJobProgressEventInfo">
- <xsd:all>
- <xsd:element name="EventStatus" type="na:ObjStatus"/>
- <xsd:element name="EventType" type="xsd:string"/>
- <xsd:element minOccurs="0" name="ProgressLunInfo"
- type="na:ProgressLunInfo"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:simpleType name="DpPolicyNodeName">
- <xsd:restriction base="xsd:string"/>
-</xsd:simpleType>
-<xsd:simpleType name="HostId">
- <xsd:restriction base="xsd:integer"/>
-</xsd:simpleType>
-<xsd:complexType name="HostInfo">
- <xsd:all>
- <xsd:element name="HostAddress" type="xsd:string"/>
- <xsd:element name="HostId" type="na:HostId"/>
- <xsd:element name="HostName" type="xsd:string"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="JobInfo">
- <xsd:all>
- <xsd:element name="JobId" type="xsd:integer"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="LunInfo">
- <xsd:all>
- <xsd:element name="HostId" type="na:ObjId"/>
- <xsd:element name="LunPath" type="na:ObjName"/>
- <xsd:element name="QtreeId" type="na:ObjName"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:simpleType name="ObjId">
- <xsd:restriction base="xsd:integer"/>
-</xsd:simpleType>
-<xsd:simpleType name="ObjName">
- <xsd:restriction base="xsd:string"/>
-</xsd:simpleType>
-<xsd:simpleType name="ObjNameOrId">
- <xsd:restriction base="xsd:string"/>
-</xsd:simpleType>
-<xsd:simpleType name="ObjStatus">
- <xsd:restriction base="xsd:string"/>
-</xsd:simpleType>
-<xsd:complexType name="ProgressLunInfo">
- <xsd:all>
- <xsd:element name="LunPathId" type="na:ObjId"/>
- <xsd:element name="LunName" type="na:ObjName"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="ProvisionMemberRequestInfo">
- <xsd:all>
- <xsd:element minOccurs="0" name="Description" type="xsd:string"/>
- <xsd:element minOccurs="0" name="MaximumSnapshotSpace"
- type="xsd:integer"/>
- <xsd:element name="Name" type="xsd:string"/>
- <xsd:element name="Size" type="xsd:integer"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="Request">
- <xsd:all>
- <xsd:element minOccurs="0" name="Args">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:any maxOccurs="unbounded" minOccurs="0"/>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="Name" type="xsd:string">
- </xsd:element>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="Response">
- <xsd:all>
- <xsd:element minOccurs="0" name="Errno" type="xsd:integer"/>
- <xsd:element minOccurs="0" name="Reason" type="xsd:string"/>
- <xsd:element minOccurs="0" name="Results">
- <xsd:complexType>
- <xsd:sequence>
- <xsd:any maxOccurs="unbounded" minOccurs="0"/>
- </xsd:sequence>
- </xsd:complexType>
- </xsd:element>
- <xsd:element name="Status" type="xsd:string"/>
- </xsd:all>
-</xsd:complexType>
-<xsd:complexType name="StorageSetInfo">
- <xsd:all>
- <xsd:element minOccurs="0" name="DatasetExportInfo"
- type="na:DatasetExportInfo"/>
- <xsd:element minOccurs="0" name="DpNodeName"
- type="na:DpPolicyNodeName"/>
- <xsd:element minOccurs="0" name="ServerNameOrId"
- type="na:ObjNameOrId"/>
- </xsd:all>
-</xsd:complexType>
-</xsd:schema></types>"""
-
-WSDL_TRAILER = """<service name="DfmService">
-<port binding="na:DfmBinding" name="DfmPort">
-<soap:address location="https://HOST_NAME:8488/apis/soap/v1"/>
-</port></service></definitions>"""
-
-RESPONSE_PREFIX = """<?xml version="1.0" encoding="UTF-8"?>
-<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
- xmlns:na="http://www.netapp.com/management/v1"><env:Header/><env:Body>"""
-
-RESPONSE_SUFFIX = """</env:Body></env:Envelope>"""
-
-APIS = ['ApiProxy', 'DatasetListInfoIterStart', 'DatasetListInfoIterNext',
- 'DatasetListInfoIterEnd', 'DatasetEditBegin', 'DatasetEditCommit',
- 'DatasetProvisionMember', 'DatasetRemoveMember', 'DfmAbout',
- 'DpJobProgressEventListIterStart', 'DpJobProgressEventListIterNext',
- 'DpJobProgressEventListIterEnd', 'DatasetMemberListInfoIterStart',
- 'DatasetMemberListInfoIterNext', 'DatasetMemberListInfoIterEnd',
- 'HostListInfoIterStart', 'HostListInfoIterNext', 'HostListInfoIterEnd',
- 'LunListInfoIterStart', 'LunListInfoIterNext', 'LunListInfoIterEnd',
- 'StorageServiceDatasetProvision']
-
-iter_count = 0
-iter_table = {}
-
def create_configuration():
configuration = conf.Configuration(None)
- configuration.append_config_values(netapp_opts)
+ configuration.append_config_values(netapp_connection_opts)
+ configuration.append_config_values(netapp_transport_opts)
+ configuration.append_config_values(netapp_basicauth_opts)
+ configuration.append_config_values(netapp_cluster_opts)
+ configuration.append_config_values(netapp_7mode_opts)
+ configuration.append_config_values(netapp_provisioning_opts)
return configuration
pass
-class FakeDfmServerHandler(FakeHTTPRequestHandler):
- """HTTP handler that fakes enough stuff to allow the driver to run."""
-
- def do_GET(s):
- """Respond to a GET request."""
- if '/dfm.wsdl' != s.path:
- s.send_response(404)
- s.end_headers
- return
- s.send_response(200)
- s.send_header("Content-Type", "application/wsdl+xml")
- s.end_headers()
- out = s.wfile
- out.write(WSDL_HEADER)
- out.write(WSDL_TYPES)
- for api in APIS:
- out.write('<message name="%sRequest">' % api)
- out.write('<part element="na:%s" name="parameters"/>' % api)
- out.write('</message>')
- out.write('<message name="%sResponse">' % api)
- out.write('<part element="na:%sResult" name="results"/>' % api)
- out.write('</message>')
- out.write('<portType name="DfmInterface">')
- for api in APIS:
- out.write('<operation name="%s">' % api)
- out.write('<input message="na:%sRequest"/>' % api)
- out.write('<output message="na:%sResponse"/>' % api)
- out.write('</operation>')
- out.write('</portType>')
- out.write('<binding name="DfmBinding" type="na:DfmInterface">')
- out.write('<soap:binding style="document" ' +
- 'transport="http://schemas.xmlsoap.org/soap/http"/>')
- for api in APIS:
- out.write('<operation name="%s">' % api)
- out.write('<soap:operation soapAction="urn:%s"/>' % api)
- out.write('<input><soap:body use="literal"/></input>')
- out.write('<output><soap:body use="literal"/></output>')
- out.write('</operation>')
- out.write('</binding>')
- out.write(WSDL_TRAILER)
-
- def do_POST(s):
- """Respond to a POST request."""
- if '/apis/soap/v1' != s.path:
- s.send_response(404)
- s.end_headers
- return
- request_xml = s.rfile.read(int(s.headers['Content-Length']))
- ntap_ns = 'http://www.netapp.com/management/v1'
- nsmap = {'env': 'http://schemas.xmlsoap.org/soap/envelope/',
- 'na': ntap_ns}
- root = etree.fromstring(request_xml)
-
- body = root.xpath('/env:Envelope/env:Body', namespaces=nsmap)[0]
- request = body.getchildren()[0]
- tag = request.tag
- if not tag.startswith('{' + ntap_ns + '}'):
- s.send_response(500)
- s.end_headers
- return
- api = tag[(2 + len(ntap_ns)):]
- global iter_count
- global iter_table
- if 'DatasetListInfoIterStart' == api:
- iter_name = 'dataset_%s' % iter_count
- iter_count = iter_count + 1
- iter_table[iter_name] = 0
- body = """<na:DatasetListInfoIterStartResult>
- <na:Records>1</na:Records>
- <na:Tag>%s</na:Tag>
- </na:DatasetListInfoIterStartResult>""" % iter_name
- elif 'DatasetListInfoIterNext' == api:
- tags = body.xpath('na:DatasetListInfoIterNext/na:Tag',
- namespaces=nsmap)
- iter_name = tags[0].text
- if iter_table[iter_name]:
- body = """<na:DatasetListInfoIterNextResult>
- <na:Datasets></na:Datasets>
- <na:Records>0</na:Records>
- </na:DatasetListInfoIterNextResult>"""
- else:
- iter_table[iter_name] = 1
- body = """<na:DatasetListInfoIterNextResult>
- <na:Datasets>
- <na:DatasetInfo>
- <na:DatasetId>0</na:DatasetId>
- <na:DatasetMetadata>
- <na:DfmMetadataField>
- <na:FieldName>OpenStackProject</na:FieldName>
- <na:FieldValue>testproj</na:FieldValue>
- </na:DfmMetadataField>
- <na:DfmMetadataField>
- <na:FieldName>OpenStackVolType</na:FieldName>
- <na:FieldValue></na:FieldValue>
- </na:DfmMetadataField>
- </na:DatasetMetadata>
- <na:DatasetName>OpenStack_testproj</na:DatasetName>
- </na:DatasetInfo>
- </na:Datasets>
- <na:Records>1</na:Records>
- </na:DatasetListInfoIterNextResult>"""
- elif 'DatasetListInfoIterEnd' == api:
- body = """<na:DatasetListInfoIterEndResult/>"""
- elif 'DatasetEditBegin' == api:
- body = """<na:DatasetEditBeginResult>
- <na:EditLockId>0</na:EditLockId>
- </na:DatasetEditBeginResult>"""
- elif 'DatasetEditCommit' == api:
- body = """<na:DatasetEditCommitResult>
- <na:IsProvisioningFailure>false</na:IsProvisioningFailure>
- <na:JobIds>
- <na:JobInfo>
- <na:JobId>0</na:JobId>
- </na:JobInfo>
- </na:JobIds>
- </na:DatasetEditCommitResult>"""
- elif 'DatasetProvisionMember' == api:
- body = """<na:DatasetProvisionMemberResult/>"""
- elif 'DatasetRemoveMember' == api:
- body = """<na:DatasetRemoveMemberResult/>"""
- elif 'DfmAbout' == api:
- body = """<na:DfmAboutResult/>"""
- elif 'DpJobProgressEventListIterStart' == api:
- iter_name = 'dpjobprogress_%s' % iter_count
- iter_count = iter_count + 1
- iter_table[iter_name] = 0
- body = """<na:DpJobProgressEventListIterStartResult>
- <na:Records>2</na:Records>
- <na:Tag>%s</na:Tag>
- </na:DpJobProgressEventListIterStartResult>""" % iter_name
- elif 'DpJobProgressEventListIterNext' == api:
- tags = body.xpath('na:DpJobProgressEventListIterNext/na:Tag',
- namespaces=nsmap)
- iter_name = tags[0].text
- if iter_table[iter_name]:
- body = """<na:DpJobProgressEventListIterNextResult/>"""
- else:
- iter_table[iter_name] = 1
- name = ('filer:/OpenStack_testproj/volume-00000001/'
- 'volume-00000001')
- body = """<na:DpJobProgressEventListIterNextResult>
- <na:ProgressEvents>
- <na:DpJobProgressEventInfo>
- <na:EventStatus>normal</na:EventStatus>
- <na:EventType>lun-create</na:EventType>
- <na:ProgressLunInfo>
- <na:LunPathId>0</na:LunPathId>
- <na:LunName>%s</na:LunName>
- </na:ProgressLunInfo>
- </na:DpJobProgressEventInfo>
- <na:DpJobProgressEventInfo>
- <na:EventStatus>normal</na:EventStatus>
- <na:EventType>job-end</na:EventType>
- </na:DpJobProgressEventInfo>
- </na:ProgressEvents>
- <na:Records>2</na:Records>
- </na:DpJobProgressEventListIterNextResult>""" % name
- elif 'DpJobProgressEventListIterEnd' == api:
- body = """<na:DpJobProgressEventListIterEndResult/>"""
- elif 'DatasetMemberListInfoIterStart' == api:
- iter_name = 'datasetmember_%s' % iter_count
- iter_count = iter_count + 1
- iter_table[iter_name] = 0
- body = """<na:DatasetMemberListInfoIterStartResult>
- <na:Records>1</na:Records>
- <na:Tag>%s</na:Tag>
- </na:DatasetMemberListInfoIterStartResult>""" % iter_name
- elif 'DatasetMemberListInfoIterNext' == api:
- tags = body.xpath('na:DatasetMemberListInfoIterNext/na:Tag',
- namespaces=nsmap)
- iter_name = tags[0].text
- if iter_table[iter_name]:
- body = """<na:DatasetMemberListInfoIterNextResult>
- <na:DatasetMembers></na:DatasetMembers>
- <na:Records>0</na:Records>
- </na:DatasetMemberListInfoIterNextResult>"""
- else:
- iter_table[iter_name] = 1
- name = ('filer:/OpenStack_testproj/volume-00000001/'
- 'volume-00000001')
- body = """<na:DatasetMemberListInfoIterNextResult>
- <na:DatasetMembers>
- <na:DatasetMemberInfo>
- <na:MemberId>0</na:MemberId>
- <na:MemberName>%s</na:MemberName>
- </na:DatasetMemberInfo>
- </na:DatasetMembers>
- <na:Records>1</na:Records>
- </na:DatasetMemberListInfoIterNextResult>""" % name
- elif 'DatasetMemberListInfoIterEnd' == api:
- body = """<na:DatasetMemberListInfoIterEndResult/>"""
- elif 'HostListInfoIterStart' == api:
- body = """<na:HostListInfoIterStartResult>
- <na:Records>1</na:Records>
- <na:Tag>host</na:Tag>
- </na:HostListInfoIterStartResult>"""
- elif 'HostListInfoIterNext' == api:
- body = """<na:HostListInfoIterNextResult>
- <na:Hosts>
- <na:HostInfo>
- <na:HostAddress>1.2.3.4</na:HostAddress>
- <na:HostId>0</na:HostId>
- <na:HostName>filer</na:HostName>
- </na:HostInfo>
- </na:Hosts>
- <na:Records>1</na:Records>
- </na:HostListInfoIterNextResult>"""
- elif 'HostListInfoIterEnd' == api:
- body = """<na:HostListInfoIterEndResult/>"""
- elif 'LunListInfoIterStart' == api:
- body = """<na:LunListInfoIterStartResult>
- <na:Records>1</na:Records>
- <na:Tag>lun</na:Tag>
- </na:LunListInfoIterStartResult>"""
- elif 'LunListInfoIterNext' == api:
- path = 'OpenStack_testproj/volume-00000001/volume-00000001'
- body = """<na:LunListInfoIterNextResult>
- <na:Luns>
- <na:LunInfo>
- <na:HostId>0</na:HostId>
- <na:LunPath>%s</na:LunPath>
- <na:QtreeId>volume-00000001</na:QtreeId>
- </na:LunInfo>
- </na:Luns>
- <na:Records>1</na:Records>
- </na:LunListInfoIterNextResult>""" % path
- elif 'LunListInfoIterEnd' == api:
- body = """<na:LunListInfoIterEndResult/>"""
- elif 'ApiProxy' == api:
- names = body.xpath('na:ApiProxy/na:Request/na:Name',
- namespaces=nsmap)
- proxy = names[0].text
- if 'clone-list-status' == proxy:
- op_elem = body.xpath('na:ApiProxy/na:Request/na:Args/'
- 'clone-id/clone-id-info/clone-op-id',
- namespaces=nsmap)
- proxy_body = """<status>
- <ops-info>
- <clone-state>completed</clone-state>
- </ops-info>
- </status>"""
- if '0' == op_elem[0].text:
- proxy_body = ''
- elif 'clone-start' == proxy:
- proxy_body = """<clone-id>
- <clone-id-info>
- <clone-op-id>1</clone-op-id>
- <volume-uuid>xxx</volume-uuid>
- </clone-id-info>
- </clone-id>"""
- elif 'igroup-list-info' == proxy:
- igroup = 'openstack-iqn.1993-08.org.debian:01:23456789'
- initiator = 'iqn.1993-08.org.debian:01:23456789'
- proxy_body = """<initiator-groups>
- <initiator-group-info>
- <initiator-group-name>%s</initiator-group-name>
- <initiator-group-type>iscsi</initiator-group-type>
- <initiator-group-os-type>linux</initiator-group-os-type>
- <initiators>
- <initiator-info>
- <initiator-name>%s</initiator-name>
- </initiator-info>
- </initiators>
- </initiator-group-info>
- </initiator-groups>""" % (igroup, initiator)
- elif 'igroup-create' == proxy:
- proxy_body = ''
- elif 'igroup-add' == proxy:
- proxy_body = ''
- elif 'lun-map-list-info' == proxy:
- proxy_body = '<initiator-groups/>'
- elif 'lun-map' == proxy:
- proxy_body = '<lun-id-assigned>0</lun-id-assigned>'
- elif 'lun-unmap' == proxy:
- proxy_body = ''
- elif 'iscsi-portal-list-info' == proxy:
- proxy_body = """<iscsi-portal-list-entries>
- <iscsi-portal-list-entry-info>
- <ip-address>1.2.3.4</ip-address>
- <ip-port>3260</ip-port>
- <tpgroup-tag>1000</tpgroup-tag>
- </iscsi-portal-list-entry-info>
- </iscsi-portal-list-entries>"""
- elif 'iscsi-node-get-name' == proxy:
- target = 'iqn.1992-08.com.netapp:sn.111111111'
- proxy_body = '<node-name>%s</node-name>' % target
- else:
- # Unknown proxy API
- s.send_response(500)
- s.end_headers
- return
- api = api + ':' + proxy
- proxy_header = '<na:ApiProxyResult><na:Response><na:Results>'
- proxy_trailer = """</na:Results><na:Status>passed</na:Status>
- </na:Response></na:ApiProxyResult>"""
- body = proxy_header + proxy_body + proxy_trailer
- else:
- # Unknown API
- s.send_response(500)
- s.end_headers
- return
- s.send_response(200)
- s.send_header("Content-Type", "text/xml; charset=utf-8")
- s.end_headers()
- s.wfile.write(RESPONSE_PREFIX)
- s.wfile.write(body)
- s.wfile.write(RESPONSE_SUFFIX)
-
-
class FakeHttplibSocket(object):
"""A fake socket implementation for httplib.HTTPResponse"""
def __init__(self, value):
return self._wbuffer
-class FakeHTTPConnection(object):
- """A fake httplib.HTTPConnection for netapp tests
-
- Requests made via this connection actually get translated and routed into
- the fake Dfm handler above, we then turn the response into
- the httplib.HTTPResponse that the caller expects.
- """
- def __init__(self, host, timeout=None):
- self.host = host
-
- def request(self, method, path, data=None, headers=None):
- if not headers:
- headers = {}
- req_str = '%s %s HTTP/1.1\r\n' % (method, path)
- for key, value in headers.iteritems():
- req_str += "%s: %s\r\n" % (key, value)
- if data:
- req_str += '\r\n%s' % data
-
- # NOTE(vish): normally the http transport normailizes from unicode
- sock = FakeHttplibSocket(req_str.decode("latin-1").encode("utf-8"))
- # NOTE(vish): stop the server from trying to look up address from
- # the fake socket
- FakeDfmServerHandler.address_string = lambda x: '127.0.0.1'
- self.app = FakeDfmServerHandler(sock, '127.0.0.1:8088', None)
-
- self.sock = FakeHttplibSocket(sock.result)
- self.http_response = httplib.HTTPResponse(self.sock)
-
- def set_debuglevel(self, level):
- pass
-
- def getresponse(self):
- self.http_response.begin()
- return self.http_response
-
- def getresponsebody(self):
- return self.sock.result
-
-
-class NetAppDriverTestCase(test.TestCase):
- """Test case for NetAppISCSIDriver"""
- STORAGE_SERVICE = 'Openstack Service'
- STORAGE_SERVICE_PREFIX = 'Openstack Service-'
- PROJECT_ID = 'testproj'
- VOLUME_NAME = 'volume-00000001'
- VOLUME_TYPE = ''
- VOLUME_SIZE = 2147483648L # 2 GB
- INITIATOR = 'iqn.1993-08.org.debian:01:23456789'
-
- def setUp(self):
- super(NetAppDriverTestCase, self).setUp()
- self.tempdir = tempfile.mkdtemp()
- self.flags(lock_path=self.tempdir)
- driver = iscsi.NetAppISCSIDriver(configuration=create_configuration())
- self.stubs.Set(httplib, 'HTTPConnection', FakeHTTPConnection)
- driver._create_client(wsdl_url='http://localhost:8088/dfm.wsdl',
- login='root', password='password',
- hostname='localhost', port=8088, cache=False)
- driver._set_storage_service(self.STORAGE_SERVICE)
- driver._set_storage_service_prefix(self.STORAGE_SERVICE_PREFIX)
- driver._set_vfiler('')
- self.driver = driver
-
- def tearDown(self):
- shutil.rmtree(self.tempdir)
- super(NetAppDriverTestCase, self).tearDown()
-
- def test_connect(self):
- self.driver.check_for_setup_error()
-
- def test_create_destroy(self):
- self.driver._discover_luns()
- self.driver._provision(self.VOLUME_NAME, None, self.PROJECT_ID,
- self.VOLUME_TYPE, self.VOLUME_SIZE)
- self.driver._remove_destroy(self.VOLUME_NAME, self.PROJECT_ID)
-
- def test_destroy_uncreated_volume(self):
- self.driver._remove_destroy('fake-nonexistent-volume', self.PROJECT_ID)
-
- def test_map_unmap(self):
- self.driver._discover_luns()
- self.driver._provision(self.VOLUME_NAME, None, self.PROJECT_ID,
- self.VOLUME_TYPE, self.VOLUME_SIZE)
- volume = {'name': self.VOLUME_NAME, 'project_id': self.PROJECT_ID,
- 'id': 0, 'provider_auth': None}
- updates = self.driver._get_export(volume)
- self.assertTrue(updates['provider_location'])
- volume['provider_location'] = updates['provider_location']
- connector = {'initiator': self.INITIATOR}
- connection_info = self.driver.initialize_connection(volume, connector)
- self.assertEqual(connection_info['driver_volume_type'], 'iscsi')
- properties = connection_info['data']
- self.driver.terminate_connection(volume, connector)
- self.driver._remove_destroy(self.VOLUME_NAME, self.PROJECT_ID)
-
- def test_clone(self):
- self.driver._discover_luns()
- self.driver._clone_lun(0, '/vol/vol/qtree/src', '/vol/vol/qtree/dst',
- False)
-
- def test_clone_fail(self):
- self.driver._discover_luns()
- self.driver._is_clone_done(0, '0', 'xxx')
-
- def test_cloned_volume_size_fail(self):
- volume_clone_fail = {'name': 'fail', 'size': '2'}
- volume_src = {'name': 'source_vol', 'size': '1'}
- try:
- self.driver.create_cloned_volume(volume_clone_fail,
- volume_src)
- raise AssertionError()
- except VolumeBackendAPIException:
- pass
-
-
-WSDL_HEADER_CMODE = """<?xml version="1.0" encoding="UTF-8"?>
-<definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
- xmlns:na="http://cloud.netapp.com/"
-xmlns:xsd="http://www.w3.org/2001/XMLSchema"
-xmlns="http://schemas.xmlsoap.org/wsdl/"
-targetNamespace="http://cloud.netapp.com/" name="CloudStorageService">
-"""
-
-WSDL_TYPES_CMODE = """<types>
-<xs:schema xmlns:na="http://cloud.netapp.com/"
-xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0"
-targetNamespace="http://cloud.netapp.com/">
-
- <xs:element name="ProvisionLun">
- <xs:complexType>
- <xs:all>
- <xs:element name="Name" type="xs:string"/>
- <xs:element name="Size" type="xsd:long"/>
- <xs:element name="Metadata" type="na:Metadata" minOccurs="0"
- maxOccurs="unbounded"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
- <xs:element name="ProvisionLunResult">
- <xs:complexType>
- <xs:all>
- <xs:element name="Lun" type="na:Lun"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
-
- <xs:element name="DestroyLun">
- <xs:complexType>
- <xs:all>
- <xs:element name="Handle" type="xsd:string"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
- <xs:element name="DestroyLunResult">
- <xs:complexType>
- <xs:all/>
- </xs:complexType>
- </xs:element>
-
- <xs:element name="CloneLun">
- <xs:complexType>
- <xs:all>
- <xs:element name="Handle" type="xsd:string"/>
- <xs:element name="NewName" type="xsd:string"/>
- <xs:element name="Metadata" type="na:Metadata" minOccurs="0"
- maxOccurs="unbounded"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
- <xs:element name="CloneLunResult">
- <xs:complexType>
- <xs:all>
- <xs:element name="Lun" type="na:Lun"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
-
- <xs:element name="MapLun">
- <xs:complexType>
- <xs:all>
- <xs:element name="Handle" type="xsd:string"/>
- <xs:element name="InitiatorType" type="xsd:string"/>
- <xs:element name="InitiatorName" type="xsd:string"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
- <xs:element name="MapLunResult">
- <xs:complexType>
- <xs:all/>
- </xs:complexType>
- </xs:element>
-
- <xs:element name="UnmapLun">
- <xs:complexType>
- <xs:all>
- <xs:element name="Handle" type="xsd:string"/>
- <xs:element name="InitiatorType" type="xsd:string"/>
- <xs:element name="InitiatorName" type="xsd:string"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
- <xs:element name="UnmapLunResult">
- <xs:complexType>
- <xs:all/>
- </xs:complexType>
- </xs:element>
-
- <xs:element name="ListLuns">
- <xs:complexType>
- <xs:all>
- <xs:element name="NameFilter" type="xsd:string" minOccurs="0"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
- <xs:element name="ListLunsResult">
- <xs:complexType>
- <xs:all>
- <xs:element name="Lun" type="na:Lun" minOccurs="0"
- maxOccurs="unbounded"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
-
- <xs:element name="GetLunTargetDetails">
- <xs:complexType>
- <xs:all>
- <xs:element name="Handle" type="xsd:string"/>
- <xs:element name="InitiatorType" type="xsd:string"/>
- <xs:element name="InitiatorName" type="xsd:string"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
- <xs:element name="GetLunTargetDetailsResult">
- <xs:complexType>
- <xs:all>
- <xs:element name="TargetDetails" type="na:TargetDetails"
- minOccurs="0" maxOccurs="unbounded"/>
- </xs:all>
- </xs:complexType>
- </xs:element>
-
- <xs:complexType name="Metadata">
- <xs:sequence>
- <xs:element name="Key" type="xs:string"/>
- <xs:element name="Value" type="xs:string"/>
- </xs:sequence>
- </xs:complexType>
-
- <xs:complexType name="Lun">
- <xs:sequence>
- <xs:element name="Name" type="xs:string"/>
- <xs:element name="Size" type="xs:long"/>
- <xs:element name="Handle" type="xs:string"/>
- <xs:element name="Metadata" type="na:Metadata" minOccurs="0"
- maxOccurs="unbounded"/>
- </xs:sequence>
- </xs:complexType>
-
- <xs:complexType name="TargetDetails">
- <xs:sequence>
- <xs:element name="Address" type="xs:string"/>
- <xs:element name="Port" type="xs:int"/>
- <xs:element name="Portal" type="xs:int"/>
- <xs:element name="Iqn" type="xs:string"/>
- <xs:element name="LunNumber" type="xs:int"/>
- </xs:sequence>
- </xs:complexType>
-
- </xs:schema></types>"""
-
-WSDL_TRAILER_CMODE = """<service name="CloudStorageService">
- <port name="CloudStoragePort" binding="na:CloudStorageBinding">
- <soap:address location="http://hostname:8080/ws/ntapcloud"/>
- </port>
- </service>
-</definitions>"""
-
-RESPONSE_PREFIX_CMODE = """<?xml version='1.0' encoding='UTF-8'?>
-<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
-<soapenv:Body>"""
-
-RESPONSE_SUFFIX_CMODE = """</soapenv:Body></soapenv:Envelope>"""
-
-CMODE_APIS = ['ProvisionLun', 'DestroyLun', 'CloneLun', 'MapLun', 'UnmapLun',
- 'ListLuns', 'GetLunTargetDetails']
-
-
-class FakeCMODEServerHandler(FakeHTTPRequestHandler):
- """HTTP handler that fakes enough stuff to allow the driver to run"""
-
- def do_GET(s):
- """Respond to a GET request."""
- if '/ntap_cloud.wsdl' != s.path:
- s.send_response(404)
- s.end_headers
- return
- s.send_response(200)
- s.send_header("Content-Type", "application/wsdl+xml")
- s.end_headers()
- out = s.wfile
- out.write(WSDL_HEADER_CMODE)
- out.write(WSDL_TYPES_CMODE)
- for api in CMODE_APIS:
- out.write('<message name="%sRequest">' % api)
- out.write('<part element="na:%s" name="req"/>' % api)
- out.write('</message>')
- out.write('<message name="%sResponse">' % api)
- out.write('<part element="na:%sResult" name="res"/>' % api)
- out.write('</message>')
- out.write('<portType name="CloudStorage">')
- for api in CMODE_APIS:
- out.write('<operation name="%s">' % api)
- out.write('<input message="na:%sRequest"/>' % api)
- out.write('<output message="na:%sResponse"/>' % api)
- out.write('</operation>')
- out.write('</portType>')
- out.write('<binding name="CloudStorageBinding" '
- 'type="na:CloudStorage">')
- out.write('<soap:binding style="document" ' +
- 'transport="http://schemas.xmlsoap.org/soap/http"/>')
- for api in CMODE_APIS:
- out.write('<operation name="%s">' % api)
- out.write('<soap:operation soapAction=""/>')
- out.write('<input><soap:body use="literal"/></input>')
- out.write('<output><soap:body use="literal"/></output>')
- out.write('</operation>')
- out.write('</binding>')
- out.write(WSDL_TRAILER_CMODE)
-
- def do_POST(s):
- """Respond to a POST request."""
- if '/ws/ntapcloud' != s.path:
- s.send_response(404)
- s.end_headers
- return
- request_xml = s.rfile.read(int(s.headers['Content-Length']))
- ntap_ns = 'http://cloud.netapp.com/'
- nsmap = {'soapenv': 'http://schemas.xmlsoap.org/soap/envelope/',
- 'na': ntap_ns}
- root = etree.fromstring(request_xml)
-
- body = root.xpath('/soapenv:Envelope/soapenv:Body',
- namespaces=nsmap)[0]
- request = body.getchildren()[0]
- tag = request.tag
- if not tag.startswith('{' + ntap_ns + '}'):
- s.send_response(500)
- s.end_headers
- return
- api = tag[(2 + len(ntap_ns)):]
- if 'ProvisionLun' == api:
- body = """<ns:ProvisionLunResult xmlns:ns=
- "http://cloud.netapp.com/">
- <Lun><Name>lun1</Name><Size>20</Size>
- <Handle>1d9c006c-a406-42f6-a23f-5ed7a6dc33e3</Handle>
- <Metadata><Key>OsType</Key>
- <Value>linux</Value></Metadata></Lun>
- </ns:ProvisionLunResult>"""
- elif 'DestroyLun' == api:
- body = """<ns:DestroyLunResult xmlns:ns="http://cloud.netapp.com/"
- />"""
- elif 'CloneLun' == api:
- body = """<ns:CloneLunResult xmlns:ns="http://cloud.netapp.com/">
- <Lun><Name>snapshot1</Name><Size>2</Size>
- <Handle>98ea1791d228453899d422b4611642c3</Handle>
- <Metadata><Key>OsType</Key>
- <Value>linux</Value></Metadata>
- </Lun></ns:CloneLunResult>"""
- elif 'MapLun' == api:
- body = """<ns1:MapLunResult xmlns:ns="http://cloud.netapp.com/"
- />"""
- elif 'Unmap' == api:
- body = """<ns1:UnmapLunResult xmlns:ns="http://cloud.netapp.com/"
- />"""
- elif 'ListLuns' == api:
- body = """<ns:ListLunsResult xmlns:ns="http://cloud.netapp.com/">
- <Lun>
- <Name>lun1</Name>
- <Size>20</Size>
- <Handle>asdjdnsd</Handle>
- </Lun>
- </ns:ListLunsResult>"""
- elif 'GetLunTargetDetails' == api:
- body = """<ns:GetLunTargetDetailsResult
- xmlns:ns="http://cloud.netapp.com/">
- <TargetDetail>
- <Address>1.2.3.4</Address>
- <Port>3260</Port>
- <Portal>1000</Portal>
- <Iqn>iqn.199208.com.netapp:sn.123456789</Iqn>
- <LunNumber>0</LunNumber>
- </TargetDetail>
- </ns:GetLunTargetDetailsResult>"""
- else:
- # Unknown API
- s.send_response(500)
- s.end_headers
- return
- s.send_response(200)
- s.send_header("Content-Type", "text/xml; charset=utf-8")
- s.end_headers()
- s.wfile.write(RESPONSE_PREFIX_CMODE)
- s.wfile.write(body)
- s.wfile.write(RESPONSE_SUFFIX_CMODE)
-
-
-class FakeCmodeHTTPConnection(object):
- """A fake httplib.HTTPConnection for netapp tests
-
- Requests made via this connection actually get translated and routed into
- the fake Dfm handler above, we then turn the response into
- the httplib.HTTPResponse that the caller expects.
- """
- def __init__(self, host, timeout=None):
- self.host = host
-
- def request(self, method, path, data=None, headers=None):
- if not headers:
- headers = {}
- req_str = '%s %s HTTP/1.1\r\n' % (method, path)
- for key, value in headers.iteritems():
- req_str += "%s: %s\r\n" % (key, value)
- if data:
- req_str += '\r\n%s' % data
-
- # NOTE(vish): normally the http transport normailizes from unicode
- sock = FakeHttplibSocket(req_str.decode("latin-1").encode("utf-8"))
- # NOTE(vish): stop the server from trying to look up address from
- # the fake socket
- FakeCMODEServerHandler.address_string = lambda x: '127.0.0.1'
- self.app = FakeCMODEServerHandler(sock, '127.0.0.1:8080', None)
-
- self.sock = FakeHttplibSocket(sock.result)
- self.http_response = httplib.HTTPResponse(self.sock)
-
- def set_debuglevel(self, level):
- pass
-
- def getresponse(self):
- self.http_response.begin()
- return self.http_response
-
- def getresponsebody(self):
- return self.sock.result
-
-
-class NetAppCmodeISCSIDriverTestCase(test.TestCase):
- """Test case for NetAppISCSIDriver"""
- volume = {'name': 'lun1', 'size': 2, 'volume_name': 'lun1',
- 'os_type': 'linux', 'provider_location': 'lun1',
- 'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
- 'display_name': None, 'display_description': 'lun1',
- 'volume_type_id': None}
- snapshot = {'name': 'snapshot1', 'size': 2, 'volume_name': 'lun1',
- 'volume_size': 2, 'project_id': 'project',
- 'display_name': None, 'display_description': 'lun1',
- 'volume_type_id': None}
- snapshot_fail = {'name': 'snapshot2', 'size': 2, 'volume_name': 'lun1',
- 'volume_size': 1, 'project_id': 'project'}
- volume_sec = {'name': 'vol_snapshot', 'size': 2, 'volume_name': 'lun1',
- 'os_type': 'linux', 'provider_location': 'lun1',
- 'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
- 'display_name': None, 'display_description': 'lun1',
- 'volume_type_id': None}
- volume_clone_fail = {'name': 'cl_fail', 'size': 1, 'volume_name': 'fail',
- 'os_type': 'linux', 'provider_location': 'cl_fail',
- 'id': 'lun1', 'provider_auth': None,
- 'project_id': 'project', 'display_name': None,
- 'display_description': 'lun1',
- 'volume_type_id': None}
- connector = {'initiator': 'iqn.1993-08.org.debian:01:10'}
-
- def setUp(self):
- super(NetAppCmodeISCSIDriverTestCase, self).setUp()
- self._custom_setup()
-
- def _custom_setup(self):
- driver = iscsi.NetAppCmodeISCSIDriver(
- configuration=create_configuration())
- self.stubs.Set(httplib, 'HTTPConnection', FakeCmodeHTTPConnection)
- driver._create_client(wsdl_url='http://localhost:8080/ntap_cloud.wsdl',
- login='root', password='password',
- hostname='localhost', port=8080, cache=False)
- self.driver = driver
-
- def test_connect(self):
- self.driver.check_for_setup_error()
-
- def test_create_destroy(self):
- self.driver.create_volume(self.volume)
- self.driver.delete_volume(self.volume)
-
- def test_create_vol_snapshot_destroy(self):
- self.driver.create_volume(self.volume)
- self.driver.create_snapshot(self.snapshot)
- self.driver.create_volume_from_snapshot(self.volume_sec, self.snapshot)
- self.driver.delete_snapshot(self.snapshot)
- self.driver.delete_volume(self.volume)
-
- def test_map_unmap(self):
- self.driver.create_volume(self.volume)
- updates = self.driver.create_export(None, self.volume)
- self.assertTrue(updates['provider_location'])
- self.volume['provider_location'] = updates['provider_location']
-
- connection_info = self.driver.initialize_connection(self.volume,
- self.connector)
- self.assertEqual(connection_info['driver_volume_type'], 'iscsi')
- properties = connection_info['data']
- if not properties:
- raise AssertionError('Target portal is none')
- self.driver.terminate_connection(self.volume, self.connector)
- self.driver.delete_volume(self.volume)
-
- def test_fail_vol_from_snapshot_creation(self):
- self.driver.create_volume(self.volume)
- try:
- self.driver.create_volume_from_snapshot(self.volume,
- self.snapshot_fail)
- raise AssertionError()
- except VolumeBackendAPIException:
- pass
- finally:
- self.driver.delete_volume(self.volume)
-
- def test_cloned_volume_destroy(self):
- self.driver.create_volume(self.volume)
- self.driver.create_cloned_volume(self.snapshot, self.volume)
- self.driver.delete_volume(self.snapshot)
- self.driver.delete_volume(self.volume)
-
- def test_fail_cloned_volume_creation(self):
- self.driver.create_volume(self.volume)
- try:
- self.driver.create_cloned_volume(self.volume_clone_fail,
- self.volume)
- raise AssertionError()
- except VolumeBackendAPIException:
- pass
- finally:
- self.driver.delete_volume(self.volume)
-
-
RESPONSE_PREFIX_DIRECT_CMODE = """<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE netapp SYSTEM 'file:/etc/netapp_gx.dtd'>"""
<major-version>1</major-version>
<minor-version>19</minor-version>
</results>"""
+ elif 'vserver-get-iter' == api:
+ body = """<results status="passed"><attributes-list>
+ <vserver-info>
+ <vserver-name>vserver</vserver-name>
+ <vserver-type>node</vserver-type>
+ </vserver-info>
+ </attributes-list>
+ <num-records>1</num-records></results>"""
+ elif 'ems-autosupport-log' == api:
+ body = """<results status="passed"/>"""
else:
# Unknown API
s.send_response(500)
return self.sock.result
-class NetAppDirectCmodeISCSIDriverTestCase(NetAppCmodeISCSIDriverTestCase):
+class NetAppDirectCmodeISCSIDriverTestCase(test.TestCase):
"""Test case for NetAppISCSIDriver"""
+ volume = {'name': 'lun1', 'size': 2, 'volume_name': 'lun1',
+ 'os_type': 'linux', 'provider_location': 'lun1',
+ 'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
+ 'display_name': None, 'display_description': 'lun1',
+ 'volume_type_id': None}
+ snapshot = {'name': 'snapshot1', 'size': 2, 'volume_name': 'lun1',
+ 'volume_size': 2, 'project_id': 'project',
+ 'display_name': None, 'display_description': 'lun1',
+ 'volume_type_id': None}
+ snapshot_fail = {'name': 'snapshot2', 'size': 2, 'volume_name': 'lun1',
+ 'volume_size': 1, 'project_id': 'project'}
+ volume_sec = {'name': 'vol_snapshot', 'size': 2, 'volume_name': 'lun1',
+ 'os_type': 'linux', 'provider_location': 'lun1',
+ 'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
+ 'display_name': None, 'display_description': 'lun1',
+ 'volume_type_id': None}
+ volume_clone_fail = {'name': 'cl_fail', 'size': 1, 'volume_name': 'fail',
+ 'os_type': 'linux', 'provider_location': 'cl_fail',
+ 'id': 'lun1', 'provider_auth': None,
+ 'project_id': 'project', 'display_name': None,
+ 'display_description': 'lun1',
+ 'volume_type_id': None}
+ connector = {'initiator': 'iqn.1993-08.org.debian:01:10'}
vol_fail = {'name': 'lun_fail', 'size': 10000, 'volume_name': 'lun1',
'os_type': 'linux', 'provider_location': 'lun1',
'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
def setUp(self):
super(NetAppDirectCmodeISCSIDriverTestCase, self).setUp()
+ self._custom_setup()
def _custom_setup(self):
- driver = iscsi.NetAppDirectCmodeISCSIDriver(
- configuration=create_configuration())
+ configuration = self._set_config(create_configuration())
+ driver = common.NetAppDriver(configuration=configuration)
self.stubs.Set(httplib, 'HTTPConnection',
FakeDirectCmodeHTTPConnection)
- driver._create_client(transport_type='http',
- login='admin', password='pass',
- hostname='127.0.0.1',
- port='80')
- driver.vserver = 'openstack'
- driver.client.set_api_version(1, 15)
+ driver.do_setup(context='')
+ client = driver.client
+ client.set_api_version(1, 15)
self.driver = driver
+ def _set_config(self, configuration):
+ configuration.netapp_storage_protocol = 'iscsi'
+ configuration.netapp_login = 'admin'
+ configuration.netapp_password = 'pass'
+ configuration.netapp_server_hostname = '127.0.0.1'
+ configuration.netapp_transport_type = 'http'
+ configuration.netapp_server_port = '80'
+ configuration.netapp_vserver = 'openstack'
+ return configuration
+
+ def test_connect(self):
+ self.driver.check_for_setup_error()
+
+ def test_create_destroy(self):
+ self.driver.create_volume(self.volume)
+ self.driver.delete_volume(self.volume)
+
+ def test_create_vol_snapshot_destroy(self):
+ self.driver.create_volume(self.volume)
+ self.driver.create_snapshot(self.snapshot)
+ self.driver.create_volume_from_snapshot(self.volume_sec, self.snapshot)
+ self.driver.delete_snapshot(self.snapshot)
+ self.driver.delete_volume(self.volume)
+
+ def test_map_unmap(self):
+ self.driver.create_volume(self.volume)
+ updates = self.driver.create_export(None, self.volume)
+ self.assertTrue(updates['provider_location'])
+ self.volume['provider_location'] = updates['provider_location']
+
+ connection_info = self.driver.initialize_connection(self.volume,
+ self.connector)
+ self.assertEqual(connection_info['driver_volume_type'], 'iscsi')
+ properties = connection_info['data']
+ if not properties:
+ raise AssertionError('Target portal is none')
+ self.driver.terminate_connection(self.volume, self.connector)
+ self.driver.delete_volume(self.volume)
+
+ def test_fail_vol_from_snapshot_creation(self):
+ self.driver.create_volume(self.volume)
+ try:
+ self.driver.create_volume_from_snapshot(self.volume,
+ self.snapshot_fail)
+ raise AssertionError()
+ except VolumeBackendAPIException:
+ pass
+ finally:
+ self.driver.delete_volume(self.volume)
+
+ def test_cloned_volume_destroy(self):
+ self.driver.create_volume(self.volume)
+ self.driver.create_cloned_volume(self.snapshot, self.volume)
+ self.driver.delete_volume(self.snapshot)
+ self.driver.delete_volume(self.volume)
+
+ def test_fail_cloned_volume_creation(self):
+ self.driver.create_volume(self.volume)
+ try:
+ self.driver.create_cloned_volume(self.volume_clone_fail,
+ self.volume)
+ raise AssertionError()
+ except VolumeBackendAPIException:
+ pass
+ finally:
+ self.driver.delete_volume(self.volume)
+
def test_map_by_creating_igroup(self):
self.driver.create_volume(self.volume)
updates = self.driver.create_export(None, self.volume)
self.assertRaises(VolumeBackendAPIException,
self.driver.create_volume, self.vol_fail)
+ def test_vol_stats(self):
+ self.driver.get_volume_stats(refresh=True)
+
+
+class NetAppDriverNegativeTestCase(test.TestCase):
+ """Test case for NetAppDriver"""
+
+ def setUp(self):
+ super(NetAppDriverNegativeTestCase, self).setUp()
+
+ def test_incorrect_family(self):
+ configuration = create_configuration()
+ configuration.netapp_storage_family = 'xyz_abc'
+ try:
+ driver = common.NetAppDriver(configuration=configuration)
+ raise AssertionError('Wrong storage family is getting accepted.')
+ except InvalidInput:
+ pass
+
+ def test_incorrect_protocol(self):
+ configuration = create_configuration()
+ configuration.netapp_storage_family = 'ontap'
+ configuration.netapp_storage_protocol = 'ontap'
+ try:
+ driver = common.NetAppDriver(configuration=configuration)
+ raise AssertionError('Wrong storage protocol is getting accepted.')
+ except InvalidInput:
+ pass
+
+ def test_non_netapp_driver(self):
+ configuration = create_configuration()
+ common.netapp_unified_plugin_registry['test_family'] =\
+ {'iscsi': 'cinder.volume.drivers.arbitrary.IscsiDriver'}
+ configuration.netapp_storage_family = 'test_family'
+ configuration.netapp_storage_protocol = 'iscsi'
+ try:
+ driver = common.NetAppDriver(configuration=configuration)
+ raise AssertionError('Non NetApp driver is getting instantiated.')
+ except InvalidInput:
+ pass
+ finally:
+ common.netapp_unified_plugin_registry.pop('test_family')
+
class FakeDirect7MODEServerHandler(FakeHTTPRequestHandler):
"""HTTP handler that fakes enough stuff to allow the driver to run"""
</results>"""
elif 'lun-set-space-reservation-info' == api:
body = """<results status="passed"/>"""
+ elif 'ems-autosupport-log' == api:
+ body = """<results status="passed"/>"""
else:
# Unknown API
s.send_response(500)
super(NetAppDirect7modeISCSIDriverTestCase_NV, self).setUp()
def _custom_setup(self):
- driver = iscsi.NetAppDirect7modeISCSIDriver(
- configuration=create_configuration())
- self.stubs.Set(httplib,
- 'HTTPConnection', FakeDirect7modeHTTPConnection)
- driver._create_client(transport_type='http',
- login='admin', password='pass',
- hostname='127.0.0.1',
- port='80')
- driver.vfiler = None
- driver.volume_list = None
+ configuration = self._set_config(create_configuration())
+ driver = common.NetAppDriver(configuration=configuration)
+ self.stubs.Set(httplib, 'HTTPConnection',
+ FakeDirect7modeHTTPConnection)
+ driver.do_setup(context='')
+ client = driver.client
+ client.set_api_version(1, 7)
self.driver = driver
+ def _set_config(self, configuration):
+ configuration.netapp_storage_family = 'ontap_7mode'
+ configuration.netapp_storage_protocol = 'iscsi'
+ configuration.netapp_login = 'admin'
+ configuration.netapp_password = 'pass'
+ configuration.netapp_server_hostname = '127.0.0.1'
+ configuration.netapp_transport_type = 'http'
+ configuration.netapp_server_port = '80'
+ return configuration
+
def test_create_on_select_vol(self):
self.driver.volume_list = ['vol0', 'vol1']
self.driver.create_volume(self.volume)
super(NetAppDirect7modeISCSIDriverTestCase_WV, self).setUp()
def _custom_setup(self):
- driver = iscsi.NetAppDirect7modeISCSIDriver(
- configuration=create_configuration())
+ configuration = self._set_config(create_configuration())
+ driver = common.NetAppDriver(configuration=configuration)
self.stubs.Set(httplib, 'HTTPConnection',
FakeDirect7modeHTTPConnection)
- driver._create_client(transport_type='http',
- login='admin', password='pass',
- hostname='127.0.0.1',
- port='80')
- driver.vfiler = 'vfiler'
- driver.client.set_api_version(1, 7)
- driver.volume_list = None
+ driver.do_setup(context='')
+ client = driver.client
+ client.set_api_version(1, 7)
self.driver = driver
+
+ def _set_config(self, configuration):
+ configuration.netapp_storage_family = 'ontap_7mode'
+ configuration.netapp_storage_protocol = 'iscsi'
+ configuration.netapp_login = 'admin'
+ configuration.netapp_password = 'pass'
+ configuration.netapp_server_hostname = '127.0.0.1'
+ configuration.netapp_transport_type = 'http'
+ configuration.netapp_server_port = '80'
+ configuration.netapp_vfiler = 'openstack'
+ return configuration
# 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
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():
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."""
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
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)
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
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())
--- /dev/null
+# 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."))
# License for the specific language governing permissions and limitations
# under the License.
"""
-Volume driver for NetApp storage systems.
-
-This driver requires NetApp OnCommand 5.0 and one or more Data
-ONTAP 7-mode storage systems with installed iSCSI licenses.
+Volume driver for NetApp iSCSI storage systems.
+This driver requires NetApp Clustered Data ONTAP or 7-mode
+storage systems with installed iSCSI licenses.
"""
import time
import uuid
-from oslo.config import cfg
-import suds
-from suds import client
-from suds.sax import text
-
from cinder import exception
from cinder.openstack.common import log as logging
-from cinder import utils
from cinder.volume import driver
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.options import netapp_7mode_opts
+from cinder.volume.drivers.netapp.options import netapp_basicauth_opts
+from cinder.volume.drivers.netapp.options import netapp_cluster_opts
+from cinder.volume.drivers.netapp.options import netapp_connection_opts
+from cinder.volume.drivers.netapp.options import netapp_provisioning_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 import volume_types
+from oslo.config import cfg
-LOG = logging.getLogger(__name__)
-netapp_opts = [
- cfg.StrOpt('netapp_wsdl_url',
- default=None,
- help='URL of the WSDL file for the DFM/Webservice server'),
- cfg.StrOpt('netapp_login',
- default=None,
- help='User name for the DFM/Controller server'),
- cfg.StrOpt('netapp_password',
- default=None,
- help='Password for the DFM/Controller server',
- secret=True),
- cfg.StrOpt('netapp_server_hostname',
- default=None,
- help='Hostname for the DFM/Controller server'),
- cfg.IntOpt('netapp_server_port',
- default=8088,
- help='Port number for the DFM/Controller server'),
- cfg.StrOpt('netapp_storage_service',
- default=None,
- help=('Storage service to use for provisioning '
- '(when volume_type=None)')),
- cfg.StrOpt('netapp_storage_service_prefix',
- default=None,
- help=('Prefix of storage service name to use for '
- 'provisioning (volume_type name will be appended)')),
- cfg.StrOpt('netapp_vfiler',
- default=None,
- help='Vfiler to use for provisioning'),
- cfg.StrOpt('netapp_transport_type',
- default='http',
- help='Transport type protocol'),
- cfg.StrOpt('netapp_vserver',
- default='openstack',
- help='Cluster vserver to use for provisioning'),
- cfg.FloatOpt('netapp_size_multiplier',
- default=1.2,
- help='Volume size multiplier to ensure while creation'),
- cfg.StrOpt('netapp_volume_list',
- default='',
- help='Comma separated eligible volumes for provisioning on'
- ' 7 mode'), ]
+LOG = logging.getLogger(__name__)
CONF = cfg.CONF
-CONF.register_opts(netapp_opts)
-
-
-class DfmDataset(object):
- def __init__(self, id, name, project, type):
- self.id = id
- self.name = name
- self.project = project
- self.type = type
-
-
-class DfmLun(object):
- def __init__(self, dataset, lunpath, id):
- self.dataset = dataset
- self.lunpath = lunpath
- self.id = id
-
-
-class NetAppISCSIDriver(driver.ISCSIDriver):
- """NetApp iSCSI volume driver."""
-
- IGROUP_PREFIX = 'openstack-'
- DATASET_PREFIX = 'OpenStack_'
- DATASET_METADATA_PROJECT_KEY = 'OpenStackProject'
- DATASET_METADATA_VOL_TYPE_KEY = 'OpenStackVolType'
-
- def __init__(self, *args, **kwargs):
- super(NetAppISCSIDriver, self).__init__(*args, **kwargs)
- self.configuration.append_config_values(netapp_opts)
- self.discovered_luns = []
- self.discovered_datasets = []
- self.lun_table = {}
-
- def _check_fail(self, request, response):
- """Utility routine to handle checking ZAPI failures."""
- if 'failed' == response.Status:
- msg = _('API %(name)s failed: %(reason)s')
- msg_fmt = {'name': request.Name, 'reason': response.Reason}
- raise exception.VolumeBackendAPIException(msg % msg_fmt)
-
- def _create_client(self, **kwargs):
- """Instantiate a web services client.
-
- This method creates a "suds" client to make web services calls to the
- DFM server. Note that the WSDL file is quite large and may take
- a few seconds to parse.
- """
- wsdl_url = kwargs['wsdl_url']
- LOG.debug(_('Using WSDL: %s') % wsdl_url)
- if kwargs['cache']:
- self.client = client.Client(wsdl_url, username=kwargs['login'],
- password=kwargs['password'])
- else:
- self.client = client.Client(wsdl_url, username=kwargs['login'],
- password=kwargs['password'],
- cache=None)
- soap_url = 'http://%s:%s/apis/soap/v1' % (kwargs['hostname'],
- kwargs['port'])
- LOG.debug(_('Using DFM server: %s') % soap_url)
- self.client.set_options(location=soap_url)
-
- def _set_storage_service(self, storage_service):
- """Set the storage service to use for provisioning."""
- LOG.debug(_('Using storage service: %s') % storage_service)
- self.storage_service = storage_service
-
- def _set_storage_service_prefix(self, storage_service_prefix):
- """Set the storage service prefix to use for provisioning."""
- LOG.debug(_('Using storage service prefix: %s') %
- storage_service_prefix)
- self.storage_service_prefix = storage_service_prefix
-
- def _set_vfiler(self, vfiler):
- """Set the vfiler to use for provisioning."""
- LOG.debug(_('Using vfiler: %s') % vfiler)
- self.vfiler = vfiler
-
- def _check_flags(self):
- """Ensure that the flags we care about are set."""
- 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.InvalidInput(reason=_('%s is not set') % flag)
- if not (self.configuration.netapp_storage_service or
- self.configuration.netapp_storage_service_prefix):
- raise exception.InvalidInput(
- reason=_('Either '
- 'netapp_storage_service or '
- 'netapp_storage_service_prefix must '
- 'be set'))
-
- def do_setup(self, context):
- """Setup the NetApp Volume driver.
-
- Called one time by the manager after the driver is loaded.
- Validate the flags we care about and setup the suds (web services)
- client.
- """
- self._check_flags()
- self._create_client(
- wsdl_url=self.configuration.netapp_wsdl_url,
- login=self.configuration.netapp_login,
- password=self.configuration.netapp_password,
- hostname=self.configuration.netapp_server_hostname,
- port=self.configuration.netapp_server_port, cache=True)
- self._set_storage_service(self.configuration.netapp_storage_service)
- self._set_storage_service_prefix(
- self.configuration.netapp_storage_service_prefix)
- self._set_vfiler(self.configuration.netapp_vfiler)
-
- def check_for_setup_error(self):
- """Check that the driver is working and can communicate.
-
- Invoke a web services API to make sure we can talk to the server.
- Also perform the discovery of datasets and LUNs from DFM.
- """
- self.client.service.DfmAbout()
- LOG.debug(_("Connected to DFM server"))
- self._discover_luns()
-
- def _get_datasets(self):
- """Get the list of datasets from DFM."""
- server = self.client.service
- res = server.DatasetListInfoIterStart(IncludeMetadata=True)
- tag = res.Tag
- datasets = []
- try:
- while True:
- res = server.DatasetListInfoIterNext(Tag=tag, Maximum=100)
- if not res.Datasets:
- break
- datasets.extend(res.Datasets.DatasetInfo)
- finally:
- server.DatasetListInfoIterEnd(Tag=tag)
- return datasets
-
- def _discover_dataset_luns(self, dataset, volume):
- """Discover all of the LUNs in a dataset."""
- server = self.client.service
- res = server.DatasetMemberListInfoIterStart(
- DatasetNameOrId=dataset.id,
- IncludeExportsInfo=True,
- IncludeIndirect=True,
- MemberType='lun_path')
- tag = res.Tag
- suffix = None
- if volume:
- suffix = '/' + volume
- try:
- while True:
- res = server.DatasetMemberListInfoIterNext(Tag=tag,
- Maximum=100)
- if (not hasattr(res, 'DatasetMembers') or
- not res.DatasetMembers):
- break
- for member in res.DatasetMembers.DatasetMemberInfo:
- if suffix and not member.MemberName.endswith(suffix):
- continue
- # MemberName is the full LUN path in this format:
- # host:/volume/qtree/lun
- lun = DfmLun(dataset, member.MemberName, member.MemberId)
- self.discovered_luns.append(lun)
- finally:
- server.DatasetMemberListInfoIterEnd(Tag=tag)
-
- def _discover_luns(self):
- """Discover the LUNs from DFM.
-
- Discover all of the OpenStack-created datasets and LUNs in the DFM
- database.
- """
- datasets = self._get_datasets()
- self.discovered_datasets = []
- self.discovered_luns = []
- for dataset in datasets:
- if not dataset.DatasetName.startswith(self.DATASET_PREFIX):
- continue
- if (not hasattr(dataset, 'DatasetMetadata') or
- not dataset.DatasetMetadata):
- continue
- project = None
- type = None
- for field in dataset.DatasetMetadata.DfmMetadataField:
- if field.FieldName == self.DATASET_METADATA_PROJECT_KEY:
- project = field.FieldValue
- elif field.FieldName == self.DATASET_METADATA_VOL_TYPE_KEY:
- type = field.FieldValue
- if not project:
- continue
- ds = DfmDataset(dataset.DatasetId, dataset.DatasetName,
- project, type)
- self.discovered_datasets.append(ds)
- self._discover_dataset_luns(ds, None)
- msg = (_("Discovered %(dataset_count)s datasets and %(lun_count)s"
- "LUNs") % {'dataset_count': len(self.discovered_datasets),
- 'lun_count': len(self.discovered_luns)})
- LOG.debug(msg)
- self.lun_table = {}
-
- def _get_job_progress(self, job_id):
- """Get progress of one running DFM job.
-
- Obtain the latest progress report for the job and return the
- list of progress events.
- """
- server = self.client.service
- res = server.DpJobProgressEventListIterStart(JobId=job_id)
- tag = res.Tag
- event_list = []
- try:
- while True:
- res = server.DpJobProgressEventListIterNext(Tag=tag,
- Maximum=100)
- if not hasattr(res, 'ProgressEvents'):
- break
- event_list += res.ProgressEvents.DpJobProgressEventInfo
- finally:
- server.DpJobProgressEventListIterEnd(Tag=tag)
- return event_list
-
- def _wait_for_job(self, job_id):
- """Wait until a job terminates.
-
- Poll the job until it completes or an error is detected. Return the
- final list of progress events if it completes successfully.
- """
- while True:
- events = self._get_job_progress(job_id)
- for event in events:
- if event.EventStatus == 'error':
- msg = _('Job failed: %s') % (event.ErrorMessage)
- raise exception.VolumeBackendAPIException(data=msg)
- if event.EventType == 'job-end':
- return events
- time.sleep(5)
-
- def _dataset_name(self, project, ss_type):
- """Return the dataset name for a given project and volume type."""
- _project = project.replace(' ', '_').replace('-', '_')
- dataset_name = self.DATASET_PREFIX + _project
- if not ss_type:
- return dataset_name
- _type = ss_type.replace(' ', '_').replace('-', '_')
- return dataset_name + '_' + _type
-
- def _get_dataset(self, dataset_name):
- """Lookup a dataset by name in the list of discovered datasets."""
- for dataset in self.discovered_datasets:
- if dataset.name == dataset_name:
- return dataset
- return None
-
- def _create_dataset(self, dataset_name, project, ss_type):
- """Create a new dataset using the storage service.
-
- The export settings are set to create iSCSI LUNs aligned for Linux.
- Returns the ID of the new dataset.
- """
- if ss_type and not self.storage_service_prefix:
- msg = _('Attempt to use volume_type without specifying '
- 'netapp_storage_service_prefix flag.')
- raise exception.VolumeBackendAPIException(data=msg)
- if not (ss_type or self.storage_service):
- msg = _('You must set the netapp_storage_service flag in order to '
- 'create volumes with no volume_type.')
- raise exception.VolumeBackendAPIException(data=msg)
- storage_service = self.storage_service
- if ss_type:
- storage_service = self.storage_service_prefix + ss_type
-
- factory = self.client.factory
-
- lunmap = factory.create('DatasetLunMappingInfo')
- lunmap.IgroupOsType = 'linux'
- export = factory.create('DatasetExportInfo')
- export.DatasetExportProtocol = 'iscsi'
- export.DatasetLunMappingInfo = lunmap
- detail = factory.create('StorageSetInfo')
- detail.DpNodeName = 'Primary data'
- detail.DatasetExportInfo = export
- if hasattr(self, 'vfiler') and self.vfiler:
- detail.ServerNameOrId = self.vfiler
- details = factory.create('ArrayOfStorageSetInfo')
- details.StorageSetInfo = [detail]
- field1 = factory.create('DfmMetadataField')
- field1.FieldName = self.DATASET_METADATA_PROJECT_KEY
- field1.FieldValue = project
- field2 = factory.create('DfmMetadataField')
- field2.FieldName = self.DATASET_METADATA_VOL_TYPE_KEY
- field2.FieldValue = ss_type
- metadata = factory.create('ArrayOfDfmMetadataField')
- metadata.DfmMetadataField = [field1, field2]
-
- res = self.client.service.StorageServiceDatasetProvision(
- StorageServiceNameOrId=storage_service,
- DatasetName=dataset_name,
- AssumeConfirmation=True,
- StorageSetDetails=details,
- DatasetMetadata=metadata)
-
- ds = DfmDataset(res.DatasetId, dataset_name, project, ss_type)
- self.discovered_datasets.append(ds)
- return ds
-
- @utils.synchronized('netapp_dfm', external=True)
- def _provision(self, name, description, project, ss_type, size):
- """Provision a LUN through provisioning manager.
-
- The LUN will be created inside a dataset associated with the project.
- If the dataset doesn't already exist, we create it using the storage
- service specified in the cinder conf.
- """
- dataset_name = self._dataset_name(project, ss_type)
- dataset = self._get_dataset(dataset_name)
- if not dataset:
- dataset = self._create_dataset(dataset_name, project, ss_type)
-
- info = self.client.factory.create('ProvisionMemberRequestInfo')
- info.Name = name
- if description:
- info.Description = description
- info.Size = size
- info.MaximumSnapshotSpace = 2 * long(size)
-
- server = self.client.service
- lock_id = server.DatasetEditBegin(DatasetNameOrId=dataset.id)
- try:
- server.DatasetProvisionMember(EditLockId=lock_id,
- ProvisionMemberRequestInfo=info)
- res = server.DatasetEditCommit(EditLockId=lock_id,
- AssumeConfirmation=True)
- except (suds.WebFault, Exception):
- server.DatasetEditRollback(EditLockId=lock_id)
- msg = _('Failed to provision dataset member')
- raise exception.VolumeBackendAPIException(data=msg)
-
- lun_id = None
- lunpath = None
-
- for info in res.JobIds.JobInfo:
- events = self._wait_for_job(info.JobId)
- for event in events:
- if event.EventType != 'lun-create':
- continue
- lunpath = event.ProgressLunInfo.LunName
- lun_id = event.ProgressLunInfo.LunPathId
-
- if not lun_id:
- msg = _('No LUN was created by the provision job')
- raise exception.VolumeBackendAPIException(data=msg)
-
- lun = DfmLun(dataset, lunpath, lun_id)
- self.discovered_luns.append(lun)
- self.lun_table[name] = lun
-
- def _get_ss_type(self, volume):
- """Get the storage service type for a volume."""
- id = volume['volume_type_id']
- if not id:
- return None
- volume_type = volume_types.get_volume_type(None, id)
- if not volume_type:
- return None
- return volume_type['name']
-
- @utils.synchronized('netapp_dfm', external=True)
- def _remove_destroy(self, name, project):
- """Remove the LUN from the dataset, also destroying it.
-
- Remove the LUN from the dataset and destroy the actual LUN and Qtree
- on the storage system.
- """
- try:
- lun = self._lookup_lun_for_volume(name, project)
- lun_details = self._get_lun_details(lun.id)
- except exception.VolumeBackendAPIException:
- LOG.debug(_("No entry in LUN table for volume %(name)s."),
- {'name': name})
- return
-
- member = self.client.factory.create('DatasetMemberParameter')
- member.ObjectNameOrId = lun.id
- members = self.client.factory.create('ArrayOfDatasetMemberParameter')
- members.DatasetMemberParameter = [member]
-
- server = self.client.service
- lock_id = server.DatasetEditBegin(DatasetNameOrId=lun.dataset.id)
- try:
- server.DatasetRemoveMember(EditLockId=lock_id, Destroy=True,
- DatasetMemberParameters=members)
- res = server.DatasetEditCommit(EditLockId=lock_id,
- AssumeConfirmation=True)
- except (suds.WebFault, Exception):
- server.DatasetEditRollback(EditLockId=lock_id)
- msg = _('Failed to remove and delete dataset LUN member')
- raise exception.VolumeBackendAPIException(data=msg)
-
- for info in res.JobIds.JobInfo:
- self._wait_for_job(info.JobId)
-
- # Note: it's not possible to delete Qtree & his LUN in one transaction
- member.ObjectNameOrId = lun_details.QtreeId
- lock_id = server.DatasetEditBegin(DatasetNameOrId=lun.dataset.id)
- try:
- server.DatasetRemoveMember(EditLockId=lock_id, Destroy=True,
- DatasetMemberParameters=members)
- server.DatasetEditCommit(EditLockId=lock_id,
- AssumeConfirmation=True)
- except (suds.WebFault, Exception):
- server.DatasetEditRollback(EditLockId=lock_id)
- msg = _('Failed to remove and delete dataset Qtree member')
- raise exception.VolumeBackendAPIException(data=msg)
-
- def create_volume(self, volume):
- """Driver entry point for creating a new volume."""
- default_size = '104857600' # 100 MB
- gigabytes = 1073741824L # 2^30
- name = volume['name']
- project = volume['project_id']
- display_name = volume['display_name']
- display_description = volume['display_description']
- description = None
- if display_name:
- if display_description:
- description = display_name + "\n" + display_description
- else:
- description = display_name
- elif display_description:
- description = display_description
- if int(volume['size']) == 0:
- size = default_size
- else:
- size = str(int(volume['size']) * gigabytes)
- ss_type = self._get_ss_type(volume)
- self._provision(name, description, project, ss_type, size)
-
- def _lookup_lun_for_volume(self, name, project):
- """Lookup the LUN that corresponds to the give volume.
-
- Initial lookups involve a table scan of all of the discovered LUNs,
- but later lookups are done instantly from the hashtable.
- """
- if name in self.lun_table:
- return self.lun_table[name]
- lunpath_suffix = '/' + name
- for lun in self.discovered_luns:
- if lun.dataset.project != project:
- continue
- if lun.lunpath.endswith(lunpath_suffix):
- self.lun_table[name] = lun
- return lun
- msg = _("No entry in LUN table for volume %s") % (name)
- raise exception.VolumeBackendAPIException(data=msg)
-
- def delete_volume(self, volume):
- """Driver entry point for destroying existing volumes."""
- name = volume['name']
- project = volume['project_id']
- self._remove_destroy(name, project)
-
- def _get_lun_details(self, lun_id):
- """Given the ID of a LUN, get the details about that LUN."""
- server = self.client.service
- res = server.LunListInfoIterStart(ObjectNameOrId=lun_id)
- tag = res.Tag
- try:
- res = server.LunListInfoIterNext(Tag=tag, Maximum=1)
- if hasattr(res, 'Luns') and res.Luns.LunInfo:
- return res.Luns.LunInfo[0]
- finally:
- server.LunListInfoIterEnd(Tag=tag)
- msg = _('Failed to get LUN details for LUN ID %s')
- raise exception.VolumeBackendAPIException(data=msg % lun_id)
-
- def _get_host_details(self, host_id):
- """Given the ID of a host, get the details about it.
-
- A "host" is a storage system here.
- """
- server = self.client.service
- res = server.HostListInfoIterStart(ObjectNameOrId=host_id)
- tag = res.Tag
- try:
- res = server.HostListInfoIterNext(Tag=tag, Maximum=1)
- if hasattr(res, 'Hosts') and res.Hosts.HostInfo:
- return res.Hosts.HostInfo[0]
- finally:
- server.HostListInfoIterEnd(Tag=tag)
- msg = _('Failed to get host details for host ID %s')
- raise exception.VolumeBackendAPIException(data=msg % host_id)
-
- def _get_iqn_for_host(self, host_id):
- """Get the iSCSI Target Name for a storage system."""
- request = self.client.factory.create('Request')
- request.Name = 'iscsi-node-get-name'
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- return response.Results['node-name'][0]
-
- def _api_elem_is_empty(self, elem):
- """Return true if the API element should be considered empty.
-
- Helper routine to figure out if a list returned from a proxy API
- is empty. This is necessary because the API proxy produces nasty
- looking XML.
- """
- if type(elem) is not list:
- return True
- if 0 == len(elem):
- return True
- child = elem[0]
- if isinstance(child, text.Text):
- return True
- if type(child) is str:
- return True
- return False
-
- def _get_target_portal_for_host(self, host_id, host_address):
- """Get iSCSI target portal for a storage system.
-
- Get the iSCSI Target Portal details for a particular IP address
- on a storage system.
- """
- request = self.client.factory.create('Request')
- request.Name = 'iscsi-portal-list-info'
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- portal = {}
- portals = response.Results['iscsi-portal-list-entries']
- if self._api_elem_is_empty(portals):
- return portal
- portal_infos = portals[0]['iscsi-portal-list-entry-info']
- for portal_info in portal_infos:
- portal['address'] = portal_info['ip-address'][0]
- portal['port'] = portal_info['ip-port'][0]
- portal['portal'] = portal_info['tpgroup-tag'][0]
- if host_address == portal['address']:
- break
- return portal
-
- def _get_export(self, volume):
- """Get the iSCSI export details for a volume.
-
- Looks up the LUN in DFM based on the volume and project name, then get
- the LUN's ID. We store that value in the database instead of the iSCSI
- details because we will not have the true iSCSI details until masking
- time (when initialize_connection() is called).
- """
- name = volume['name']
- project = volume['project_id']
- lun = self._lookup_lun_for_volume(name, project)
- return {'provider_location': lun.id}
-
- def ensure_export(self, context, volume):
- """Driver entry point to get the export info for an existing volume."""
- return self._get_export(volume)
-
- def create_export(self, context, volume):
- """Driver entry point to get the export info for a new volume."""
- return self._get_export(volume)
-
- def remove_export(self, context, volume):
- """Driver exntry point to remove an export for a volume.
-
- Since exporting is idempotent in this driver, we have nothing
- to do for unexporting.
- """
- pass
-
- def _find_igroup_for_initiator(self, host_id, initiator_name):
- """Get the igroup for an initiator.
-
- Look for an existing igroup (initiator group) on the storage system
- containing a given iSCSI initiator and return the name of the igroup.
- """
- request = self.client.factory.create('Request')
- request.Name = 'igroup-list-info'
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- igroups = response.Results['initiator-groups']
- if self._api_elem_is_empty(igroups):
- return None
- igroup_infos = igroups[0]['initiator-group-info']
- for igroup_info in igroup_infos:
- if ('iscsi' != igroup_info['initiator-group-type'][0] or
- 'linux' != igroup_info['initiator-group-os-type'][0]):
- continue
- igroup_name = igroup_info['initiator-group-name'][0]
- if not igroup_name.startswith(self.IGROUP_PREFIX):
- continue
- initiators = igroup_info['initiators'][0]['initiator-info']
- for initiator in initiators:
- if initiator_name == initiator['initiator-name'][0]:
- return igroup_name
- return None
-
- def _create_igroup(self, host_id, initiator_name):
- """Create a new igroup.
-
- Create a new igroup (initiator group) on the storage system to hold
- the given iSCSI initiator. The group will only have 1 member and will
- be named "openstack-${initiator_name}".
- """
- igroup_name = self.IGROUP_PREFIX + initiator_name
- request = self.client.factory.create('Request')
- request.Name = 'igroup-create'
- igroup_create_xml = (
- '<initiator-group-name>%s</initiator-group-name>'
- '<initiator-group-type>iscsi</initiator-group-type>'
- '<os-type>linux</os-type><ostype>linux</ostype>')
- request.Args = text.Raw(igroup_create_xml % igroup_name)
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- request = self.client.factory.create('Request')
- request.Name = 'igroup-add'
- igroup_add_xml = (
- '<initiator-group-name>%s</initiator-group-name>'
- '<initiator>%s</initiator>')
- request.Args = text.Raw(igroup_add_xml % (igroup_name, initiator_name))
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- return igroup_name
-
- def _get_lun_mappping(self, host_id, lunpath, igroup_name):
- """Get the mapping between a LUN and an igroup.
-
- Check if a given LUN is already mapped to the given igroup (initiator
- group). If the LUN is mapped, also return the LUN number for the
- mapping.
- """
- request = self.client.factory.create('Request')
- request.Name = 'lun-map-list-info'
- request.Args = text.Raw('<path>%s</path>' % (lunpath))
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- igroups = response.Results['initiator-groups']
- if self._api_elem_is_empty(igroups):
- return {'mapped': False}
- igroup_infos = igroups[0]['initiator-group-info']
- for igroup_info in igroup_infos:
- if igroup_name == igroup_info['initiator-group-name'][0]:
- return {'mapped': True, 'lun_num': igroup_info['lun-id'][0]}
- return {'mapped': False}
-
- def _map_initiator(self, host_id, lunpath, igroup_name):
- """Map a LUN to an igroup.
-
- Map the given LUN to the given igroup (initiator group). Return the LUN
- number that the LUN was mapped to (the filer will choose the lowest
- available number).
- """
- request = self.client.factory.create('Request')
- request.Name = 'lun-map'
- lun_map_xml = ('<initiator-group>%s</initiator-group>'
- '<path>%s</path>')
- request.Args = text.Raw(lun_map_xml % (igroup_name, lunpath))
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- return response.Results['lun-id-assigned'][0]
-
- def _unmap_initiator(self, host_id, lunpath, igroup_name):
- """Unmap the given LUN from the given igroup (initiator group)."""
- request = self.client.factory.create('Request')
- request.Name = 'lun-unmap'
- lun_unmap_xml = ('<initiator-group>%s</initiator-group>'
- '<path>%s</path>')
- request.Args = text.Raw(lun_unmap_xml % (igroup_name, lunpath))
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
-
- def _ensure_initiator_mapped(self, host_id, lunpath, initiator_name):
- """Ensure that a LUN is mapped to a particular initiator.
-
- Check if a LUN is mapped to a given initiator already and create
- the mapping if it is not. A new igroup will be created if needed.
- Returns the LUN number for the mapping between the LUN and initiator
- in both cases.
- """
- lunpath = '/vol/' + lunpath
- igroup_name = self._find_igroup_for_initiator(host_id, initiator_name)
- if not igroup_name:
- igroup_name = self._create_igroup(host_id, initiator_name)
-
- mapping = self._get_lun_mappping(host_id, lunpath, igroup_name)
- if mapping['mapped']:
- return mapping['lun_num']
- return self._map_initiator(host_id, lunpath, igroup_name)
-
- def _ensure_initiator_unmapped(self, host_id, lunpath, initiator_name):
- """Ensure that a LUN is not mapped to a particular initiator.
-
- Check if a LUN is mapped to a given initiator and remove the
- mapping if it is. This does not destroy the igroup.
- """
- lunpath = '/vol/' + lunpath
- igroup_name = self._find_igroup_for_initiator(host_id, initiator_name)
- if not igroup_name:
- return
-
- mapping = self._get_lun_mappping(host_id, lunpath, igroup_name)
- if mapping['mapped']:
- self._unmap_initiator(host_id, lunpath, igroup_name)
-
- def initialize_connection(self, volume, connector):
- """Driver entry point to attach a volume to an instance.
-
- Do the LUN masking on the storage system so the initiator can access
- the LUN on the target. Also return the iSCSI properties so the
- initiator can find the LUN. This implementation does not call
- _get_iscsi_properties() to get the properties because cannot store the
- LUN number in the database. We only find out what the LUN number will
- be during this method call so we construct the properties dictionary
- ourselves.
- """
- initiator_name = connector['initiator']
- lun_id = volume['provider_location']
- if not lun_id:
- msg = _("No LUN ID for volume %s") % volume['name']
- raise exception.VolumeBackendAPIException(data=msg)
- lun = self._get_lun_details(lun_id)
- lun_num = self._ensure_initiator_mapped(lun.HostId, lun.LunPath,
- initiator_name)
- host = self._get_host_details(lun.HostId)
- portal = self._get_target_portal_for_host(host.HostId,
- host.HostAddress)
- if not portal:
- msg = _('Failed to get target portal for filer: %s')
- raise exception.VolumeBackendAPIException(data=msg % host.HostName)
-
- iqn = self._get_iqn_for_host(host.HostId)
- if not iqn:
- msg = _('Failed to get target IQN for filer: %s')
- raise exception.VolumeBackendAPIException(data=msg % host.HostName)
-
- properties = {}
- properties['target_discovered'] = False
- (address, port) = (portal['address'], portal['port'])
- properties['target_portal'] = '%s:%s' % (address, port)
- properties['target_iqn'] = iqn
- properties['target_lun'] = lun_num
- properties['volume_id'] = volume['id']
-
- auth = volume['provider_auth']
- if auth:
- (auth_method, auth_username, auth_secret) = auth.split()
-
- properties['auth_method'] = auth_method
- properties['auth_username'] = auth_username
- properties['auth_password'] = auth_secret
-
- return {
- 'driver_volume_type': 'iscsi',
- 'data': properties,
- }
-
- def terminate_connection(self, volume, connector, **kwargs):
- """Driver entry point to unattach a volume from an instance.
-
- Unmask the LUN on the storage system so the given intiator can no
- longer access it.
- """
- initiator_name = connector['initiator']
- lun_id = volume['provider_location']
- if not lun_id:
- msg = _('No LUN ID for volume %s') % volume['name']
- raise exception.VolumeBackendAPIException(data=msg)
- lun = self._get_lun_details(lun_id)
- self._ensure_initiator_unmapped(lun.HostId, lun.LunPath,
- initiator_name)
-
- def _is_clone_done(self, host_id, clone_op_id, volume_uuid):
- """Check the status of a clone operation.
-
- Return True if done, False otherwise.
- """
- request = self.client.factory.create('Request')
- request.Name = 'clone-list-status'
- clone_list_status_xml = (
- '<clone-id><clone-id-info>'
- '<clone-op-id>%s</clone-op-id>'
- '<volume-uuid>%s</volume-uuid>'
- '</clone-id-info></clone-id>')
- request.Args = text.Raw(clone_list_status_xml % (clone_op_id,
- volume_uuid))
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- if isinstance(response.Results, text.Text):
- return False
- status = response.Results['status']
- if self._api_elem_is_empty(status):
- return False
- ops_info = status[0]['ops-info'][0]
- state = ops_info['clone-state'][0]
- return 'completed' == state
-
- def _clone_lun(self, host_id, src_path, dest_path, snap):
- """Create a clone of a NetApp LUN.
-
- The clone initially consumes no space and is not space reserved.
- """
- request = self.client.factory.create('Request')
- request.Name = 'clone-start'
- clone_start_xml = (
- '<source-path>%s</source-path><no-snap>%s</no-snap>'
- '<destination-path>%s</destination-path>')
- if snap:
- no_snap = 'false'
- else:
- no_snap = 'true'
- request.Args = text.Raw(clone_start_xml % (src_path, no_snap,
- dest_path))
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- clone_id = response.Results['clone-id'][0]
- clone_id_info = clone_id['clone-id-info'][0]
- clone_op_id = clone_id_info['clone-op-id'][0]
- volume_uuid = clone_id_info['volume-uuid'][0]
- while not self._is_clone_done(host_id, clone_op_id, volume_uuid):
- time.sleep(5)
-
- def _refresh_dfm_luns(self, host_id):
- """Refresh the LUN list for one filer in DFM."""
- server = self.client.service
- refresh_started_at = time.time()
- monitor_names = self.client.factory.create('ArrayOfMonitorName')
- monitor_names.MonitorName = ['file_system', 'lun']
- server.DfmObjectRefresh(ObjectNameOrId=host_id,
- MonitorNames=monitor_names)
-
- max_wait = 10 * 60 # 10 minutes
-
- while True:
- if time.time() - refresh_started_at > max_wait:
- msg = _('Failed to get LUN list. Is the DFM host'
- ' time-synchronized with Cinder host?')
- raise exception.VolumeBackendAPIException(msg)
-
- LOG.info('Refreshing LUN list on DFM...')
- time.sleep(15)
- res = server.DfmMonitorTimestampList(HostNameOrId=host_id)
- timestamps = dict((t.MonitorName, t.LastMonitoringTimestamp)
- for t in res.DfmMonitoringTimestamp)
- ts_fs = timestamps['file_system']
- ts_lun = timestamps['lun']
-
- if ts_fs > refresh_started_at and ts_lun > refresh_started_at:
- return # both monitor jobs finished
- elif ts_fs == 0 or ts_lun == 0:
- pass # lun or file_system is still in progress, wait
- else:
- monitor_names.MonitorName = []
- if ts_fs <= refresh_started_at:
- monitor_names.MonitorName.append('file_system')
- if ts_lun <= refresh_started_at:
- monitor_names.MonitorName.append('lun')
- LOG.debug('Rerunning refresh for monitors: ' +
- str(monitor_names.MonitorName))
- server.DfmObjectRefresh(ObjectNameOrId=host_id,
- MonitorNames=monitor_names)
-
- def _destroy_lun(self, host_id, lun_path):
- """Destroy a LUN on the filer."""
- request = self.client.factory.create('Request')
- request.Name = 'lun-offline'
- path_xml = '<path>%s</path>'
- request.Args = text.Raw(path_xml % lun_path)
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
- request = self.client.factory.create('Request')
- request.Name = 'lun-destroy'
- request.Args = text.Raw(path_xml % lun_path)
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
-
- def _resize_volume(self, host_id, vol_name, new_size):
- """Resize the volume by the amount requested."""
- request = self.client.factory.create('Request')
- request.Name = 'volume-size'
- volume_size_xml = (
- '<volume>%s</volume><new-size>%s</new-size>')
- request.Args = text.Raw(volume_size_xml % (vol_name, new_size))
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
-
- def _create_qtree(self, host_id, vol_name, qtree_name):
- """Create a qtree the filer."""
- request = self.client.factory.create('Request')
- request.Name = 'qtree-create'
- qtree_create_xml = (
- '<mode>0755</mode><volume>%s</volume><qtree>%s</qtree>')
- request.Args = text.Raw(qtree_create_xml % (vol_name, qtree_name))
- response = self.client.service.ApiProxy(Target=host_id,
- Request=request)
- self._check_fail(request, response)
-
- def create_snapshot(self, snapshot):
- """Driver entry point for creating a snapshot.
-
- This driver implements snapshots by using efficient single-file
- (LUN) cloning.
- """
- vol_name = snapshot['volume_name']
- snapshot_name = snapshot['name']
- project = snapshot['project_id']
- lun = self._lookup_lun_for_volume(vol_name, project)
- lun_id = lun.id
- lun = self._get_lun_details(lun_id)
- extra_gb = snapshot['volume_size']
- new_size = '+%dg' % extra_gb
- self._resize_volume(lun.HostId, lun.VolumeName, new_size)
- # LunPath is the partial LUN path in this format: volume/qtree/lun
- lun_path = str(lun.LunPath)
- lun_name = lun_path[lun_path.rfind('/') + 1:]
- qtree_path = '/vol/%s/%s' % (lun.VolumeName, lun.QtreeName)
- src_path = '%s/%s' % (qtree_path, lun_name)
- dest_path = '%s/%s' % (qtree_path, snapshot_name)
- self._clone_lun(lun.HostId, src_path, dest_path, True)
-
- def delete_snapshot(self, snapshot):
- """Driver entry point for deleting a snapshot."""
- vol_name = snapshot['volume_name']
- snapshot_name = snapshot['name']
- project = snapshot['project_id']
- lun = self._lookup_lun_for_volume(vol_name, project)
- lun_id = lun.id
- lun = self._get_lun_details(lun_id)
- lun_path = '/vol/%s/%s/%s' % (lun.VolumeName, lun.QtreeName,
- snapshot_name)
- self._destroy_lun(lun.HostId, lun_path)
- extra_gb = snapshot['volume_size']
- new_size = '-%dg' % extra_gb
- self._resize_volume(lun.HostId, lun.VolumeName, new_size)
-
- def create_volume_from_snapshot(self, volume, snapshot):
- """Driver entry point for creating a new volume from a snapshot.
-
- Many would call this "cloning" and in fact we use cloning to implement
- this feature.
- """
- vol_size = volume['size']
- 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')
- msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size}
- raise exception.VolumeBackendAPIException(msg % msg_fmt)
- vol_name = snapshot['volume_name']
- snapshot_name = snapshot['name']
- project = snapshot['project_id']
- lun = self._lookup_lun_for_volume(vol_name, project)
- lun_id = lun.id
- dataset = lun.dataset
- old_type = dataset.type
- new_type = self._get_ss_type(volume)
- if new_type != old_type:
- msg = _('Cannot create volume of type %(new_type)s from '
- 'snapshot of type %(old_type)s')
- msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size}
- raise exception.VolumeBackendAPIException(msg % msg_fmt)
- lun = self._get_lun_details(lun_id)
- extra_gb = vol_size
- new_size = '+%dg' % extra_gb
- self._resize_volume(lun.HostId, lun.VolumeName, new_size)
- clone_name = volume['name']
- self._create_qtree(lun.HostId, lun.VolumeName, clone_name)
- src_path = '/vol/%s/%s/%s' % (lun.VolumeName, lun.QtreeName,
- snapshot_name)
- dest_path = '/vol/%s/%s/%s' % (lun.VolumeName, clone_name, clone_name)
- self._clone_lun(lun.HostId, src_path, dest_path, False)
- self._refresh_dfm_luns(lun.HostId)
- self._discover_dataset_luns(dataset, clone_name)
-
- def create_cloned_volume(self, volume, src_vref):
- """Creates a clone of the specified volume."""
- vol_size = volume['size']
- 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')
- msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size}
- raise exception.VolumeBackendAPIException(msg % msg_fmt)
- src_vol_name = src_vref['name']
- project = src_vref['project_id']
- lun = self._lookup_lun_for_volume(src_vol_name, project)
- lun_id = lun.id
- dataset = lun.dataset
- old_type = dataset.type
- new_type = self._get_ss_type(volume)
- if new_type != old_type:
- msg = (_('Cannot create clone of type %(new_type)s from '
- 'volume of type %(old_type)s') %
- {'new_type': new_type, 'old_type': old_type})
- raise exception.VolumeBackendAPIException(data=msg)
- lun = self._get_lun_details(lun_id)
- extra_gb = vol_size
- new_size = '+%dg' % extra_gb
- self._resize_volume(lun.HostId, lun.VolumeName, new_size)
- clone_name = volume['name']
- self._create_qtree(lun.HostId, lun.VolumeName, clone_name)
- src_path = '/vol/%s/%s/%s' % (lun.VolumeName, lun.QtreeName,
- src_vol_name)
- dest_path = '/vol/%s/%s/%s' % (lun.VolumeName, clone_name, clone_name)
- self._clone_lun(lun.HostId, src_path, dest_path, False)
- self._refresh_dfm_luns(lun.HostId)
- self._discover_dataset_luns(dataset, clone_name)
-
- def get_volume_stats(self, refresh=False):
- """Get volume status.
-
- If 'refresh' is True, run update the stats first.
- """
- if refresh:
- self._update_volume_status()
-
- return self._stats
-
- def _update_volume_status(self):
- """Retrieve status info from volume group."""
-
- LOG.debug(_("Updating volume status"))
- data = {}
- backend_name = self.configuration.safe_get('volume_backend_name')
- data["volume_backend_name"] = backend_name or 'NetApp_iSCSI_7mode'
- data["vendor_name"] = 'NetApp'
- data["driver_version"] = '1.0'
- data["storage_protocol"] = 'iSCSI'
-
- data['total_capacity_gb'] = 'infinite'
- data['free_capacity_gb'] = 'infinite'
- data['reserved_percentage'] = 0
- data['QoS_support'] = False
- self._stats = data
+CONF.register_opts(netapp_connection_opts)
+CONF.register_opts(netapp_transport_opts)
+CONF.register_opts(netapp_basicauth_opts)
+CONF.register_opts(netapp_cluster_opts)
+CONF.register_opts(netapp_7mode_opts)
+CONF.register_opts(netapp_provisioning_opts)
class NetAppLun(object):
if prop in self.metadata:
return self.metadata[prop]
name = self.name
- msg = _("No metadata property %(prop)s defined for the LUN "
- "%(name)s") % {'prop': prop, 'name': name}
- LOG.debug(msg)
+ msg = _("No metadata property %(prop)s defined for the"
+ " LUN %(name)s")
+ msg_fmt = {'prop': prop, 'name': name}
+ LOG.debug(msg % msg_fmt)
def __str__(self, *args, **kwargs):
return 'NetApp Lun[handle:%s, name:%s, size:%s, metadata:%s]'\
% (self.handle, self.name, self.size, self.metadata)
-class NetAppCmodeISCSIDriver(driver.ISCSIDriver):
- """NetApp C-mode iSCSI volume driver."""
-
- def __init__(self, *args, **kwargs):
- super(NetAppCmodeISCSIDriver, self).__init__(*args, **kwargs)
- self.configuration.append_config_values(netapp_opts)
- self.lun_table = {}
-
- def _create_client(self, **kwargs):
- """Instantiate a web services client.
-
- This method creates a "suds" client to make web services calls to the
- DFM server. Note that the WSDL file is quite large and may take
- a few seconds to parse.
- """
- wsdl_url = kwargs['wsdl_url']
- LOG.debug(_('Using WSDL: %s') % wsdl_url)
- if kwargs['cache']:
- self.client = client.Client(wsdl_url, username=kwargs['login'],
- password=kwargs['password'])
- else:
- self.client = client.Client(wsdl_url, username=kwargs['login'],
- password=kwargs['password'],
- cache=None)
-
- def _check_flags(self):
- """Ensure that the flags we care about are set."""
- 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):
- msg = _('%s is not set') % flag
- raise exception.InvalidInput(data=msg)
-
- def do_setup(self, context):
- """Setup the NetApp Volume driver.
-
- Called one time by the manager after the driver is loaded.
- Validate the flags we care about and setup the suds (web services)
- client.
- """
- self._check_flags()
- self._create_client(
- wsdl_url=self.configuration.netapp_wsdl_url,
- login=self.configuration.netapp_login,
- password=self.configuration.netapp_password,
- hostname=self.configuration.netapp_server_hostname,
- port=self.configuration.netapp_server_port, cache=True)
-
- def check_for_setup_error(self):
- """Check that the driver is working and can communicate.
-
- Discovers the LUNs on the NetApp server.
- """
- self.lun_table = {}
- luns = self.client.service.ListLuns()
- for lun in luns:
- meta_dict = {}
- if hasattr(lun, 'Metadata'):
- meta_dict = self._create_dict_from_meta(lun.Metadata)
- discovered_lun = NetAppLun(lun.Handle,
- lun.Name,
- lun.Size,
- meta_dict)
- self._add_lun_to_table(discovered_lun)
- LOG.debug(_("Success getting LUN list from server"))
-
- def create_volume(self, volume):
- """Driver entry point for creating a new volume."""
- default_size = '104857600' # 100 MB
- gigabytes = 1073741824L # 2^30
- name = volume['name']
- if int(volume['size']) == 0:
- size = default_size
- else:
- size = str(int(volume['size']) * gigabytes)
- extra_args = {}
- extra_args['OsType'] = 'linux'
- extra_args['QosType'] = self._get_qos_type(volume)
- extra_args['Container'] = volume['project_id']
- extra_args['Display'] = volume['display_name']
- extra_args['Description'] = volume['display_description']
- extra_args['SpaceReserved'] = True
- server = self.client.service
- metadata = self._create_metadata_list(extra_args)
- lun = server.ProvisionLun(Name=name, Size=size,
- Metadata=metadata)
- LOG.debug(_("Created LUN with name %s") % name)
- self._add_lun_to_table(
- NetAppLun(lun.Handle,
- lun.Name,
- lun.Size,
- self._create_dict_from_meta(lun.Metadata)))
-
- def delete_volume(self, volume):
- """Driver entry point for destroying existing volumes."""
- name = volume['name']
- handle = self._get_lun_handle(name)
- if not handle:
- LOG.warn(_("No entry in LUN table for volume %(name)s."),
- {'name': name})
- return
- self.client.service.DestroyLun(Handle=handle)
- LOG.debug(_("Destroyed LUN %s") % handle)
- self.lun_table.pop(name)
-
- def ensure_export(self, context, volume):
- """Driver entry point to get the export info for an existing volume."""
- handle = self._get_lun_handle(volume['name'])
- return {'provider_location': handle}
-
- def create_export(self, context, volume):
- """Driver entry point to get the export info for a new volume."""
- handle = self._get_lun_handle(volume['name'])
- return {'provider_location': handle}
-
- def remove_export(self, context, volume):
- """Driver exntry point to remove an export for a volume.
-
- Since exporting is idempotent in this driver, we have nothing
- to do for unexporting.
- """
- pass
-
- def initialize_connection(self, volume, connector):
- """Driver entry point to attach a volume to an instance.
-
- Do the LUN masking on the storage system so the initiator can access
- the LUN on the target. Also return the iSCSI properties so the
- initiator can find the LUN. This implementation does not call
- _get_iscsi_properties() to get the properties because cannot store the
- LUN number in the database. We only find out what the LUN number will
- be during this method call so we construct the properties dictionary
- ourselves.
- """
- initiator_name = connector['initiator']
- handle = volume['provider_location']
- server = self.client.service
- server.MapLun(Handle=handle, InitiatorType="iscsi",
- InitiatorName=initiator_name)
- LOG.debug(_("Mapped LUN %(handle)s to the initiator "
- "%(initiator_name)s"),
- {'handle': handle, 'initiator_name': initiator_name})
-
- target_details_list = server.GetLunTargetDetails(
- Handle=handle,
- InitiatorType="iscsi",
- InitiatorName=initiator_name)
- LOG.debug(_("Succesfully fetched target details for LUN %(handle)s and"
- " initiator %(initiator_name)s"),
- {'handle': handle, 'initiator_name': initiator_name})
-
- if not target_details_list:
- msg = _('Failed to get LUN target details for the LUN %s')
- raise exception.VolumeBackendAPIException(data=msg % handle)
- target_details = target_details_list[0]
- if not target_details.Address and target_details.Port:
- msg = _('Failed to get target portal for the LUN %s')
- raise exception.VolumeBackendAPIException(data=msg % handle)
- iqn = target_details.Iqn
- if not iqn:
- msg = _('Failed to get target IQN for the LUN %s')
- raise exception.VolumeBackendAPIException(data=msg % handle)
-
- properties = {}
- properties['target_discovered'] = False
- (address, port) = (target_details.Address, target_details.Port)
- properties['target_portal'] = '%s:%s' % (address, port)
- properties['target_iqn'] = iqn
- properties['target_lun'] = target_details.LunNumber
- properties['volume_id'] = volume['id']
-
- auth = volume['provider_auth']
- if auth:
- (auth_method, auth_username, auth_secret) = auth.split()
- properties['auth_method'] = auth_method
- properties['auth_username'] = auth_username
- properties['auth_password'] = auth_secret
-
- return {
- 'driver_volume_type': 'iscsi',
- 'data': properties,
- }
-
- def terminate_connection(self, volume, connector, **kwargs):
- """Driver entry point to unattach a volume from an instance.
-
- Unmask the LUN on the storage system so the given intiator can no
- longer access it.
- """
- initiator_name = connector['initiator']
- handle = volume['provider_location']
- self.client.service.UnmapLun(Handle=handle, InitiatorType="iscsi",
- InitiatorName=initiator_name)
- msg = _("Unmapped LUN %(handle)s from the initiator "
- "%(initiator_name)s") % {'handle': handle,
- 'initiator_name': initiator_name}
- LOG.debug(msg)
-
- def create_snapshot(self, snapshot):
- """Driver entry point for creating a snapshot.
-
- This driver implements snapshots by using efficient single-file
- (LUN) cloning.
- """
- vol_name = snapshot['volume_name']
- snapshot_name = snapshot['name']
- lun = self.lun_table[vol_name]
- extra_args = {'SpaceReserved': False}
- self._clone_lun(lun.handle, snapshot_name, extra_args)
-
- def delete_snapshot(self, snapshot):
- """Driver entry point for deleting a snapshot."""
- name = snapshot['name']
- handle = self._get_lun_handle(name)
- if not handle:
- LOG.warn(_("No entry in LUN table for snapshot %(name)s."),
- {'name': name})
- return
- self.client.service.DestroyLun(Handle=handle)
- LOG.debug(_("Destroyed LUN %s") % handle)
- self.lun_table.pop(snapshot['name'])
-
- def create_volume_from_snapshot(self, volume, snapshot):
- """Driver entry point for creating a new volume from a snapshot.
-
- Many would call this "cloning" and in fact we use cloning to implement
- this feature.
- """
- vol_size = volume['size']
- 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')
- msg_fmt = {'vol_size': vol_size, 'snap_size': snap_size}
- raise exception.VolumeBackendAPIException(msg % msg_fmt)
- snapshot_name = snapshot['name']
- lun = self.lun_table[snapshot_name]
- new_name = volume['name']
- extra_args = {}
- extra_args['OsType'] = 'linux'
- extra_args['QosType'] = self._get_qos_type(volume)
- extra_args['Container'] = volume['project_id']
- extra_args['Display'] = volume['display_name']
- extra_args['Description'] = volume['display_description']
- extra_args['SpaceReserved'] = True
- self._clone_lun(lun.handle, new_name, extra_args)
-
- def _get_qos_type(self, volume):
- """Get the storage service type for a volume."""
- type_id = volume['volume_type_id']
- if not type_id:
- return None
- volume_type = volume_types.get_volume_type(None, type_id)
- if not volume_type:
- return None
- return volume_type['name']
-
- def _add_lun_to_table(self, lun):
- """Adds LUN to cache table."""
- if not isinstance(lun, NetAppLun):
- msg = _("Object is not a NetApp LUN.")
- raise exception.VolumeBackendAPIException(data=msg)
- self.lun_table[lun.name] = lun
-
- def _clone_lun(self, handle, new_name, extra_args):
- """Clone LUN with the given handle to the new name."""
- server = self.client.service
- metadata = self._create_metadata_list(extra_args)
- lun = server.CloneLun(Handle=handle, NewName=new_name,
- Metadata=metadata)
- LOG.debug(_("Cloned LUN with new name %s") % new_name)
- self._add_lun_to_table(
- NetAppLun(lun.Handle,
- lun.Name,
- lun.Size,
- self._create_dict_from_meta(lun.Metadata)))
-
- def _create_metadata_list(self, extra_args):
- """Creates metadata from kwargs."""
- metadata = []
- for key in extra_args.keys():
- meta = self.client.factory.create("Metadata")
- meta.Key = key
- meta.Value = extra_args[key]
- metadata.append(meta)
- return metadata
-
- def _get_lun_handle(self, name):
- """Get the details for a LUN from our cache table."""
- if name not in self.lun_table:
- LOG.warn(_("Could not find handle for LUN named %s") % name)
- return None
- return self.lun_table[name].handle
-
- def _create_dict_from_meta(self, metadata):
- """Creates dictionary from metadata array."""
- meta_dict = {}
- if not metadata:
- return meta_dict
- for meta in metadata:
- meta_dict[meta.Key] = meta.Value
- return meta_dict
-
- def create_cloned_volume(self, volume, src_vref):
- """Creates a clone of the specified volume."""
- vol_size = volume['size']
- src_vol = self.lun_table[src_vref['name']]
- src_vol_size = src_vref['size']
- if vol_size != src_vol_size:
- msg = _('Cannot clone volume of size %(vol_size)s from '
- 'src volume of size %(src_vol_size)s')
- msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size}
- raise exception.VolumeBackendAPIException(msg % msg_fmt)
- new_name = volume['name']
- extra_args = {}
- extra_args['OsType'] = 'linux'
- extra_args['QosType'] = self._get_qos_type(volume)
- extra_args['Container'] = volume['project_id']
- extra_args['Display'] = volume['display_name']
- extra_args['Description'] = volume['display_description']
- extra_args['SpaceReserved'] = True
- self._clone_lun(src_vol.handle, new_name, extra_args)
-
- def get_volume_stats(self, refresh=False):
- """Get volume status.
-
- If 'refresh' is True, run update the stats first.
- """
- if refresh:
- self._update_volume_status()
-
- return self._stats
-
- def _update_volume_status(self):
- """Retrieve status info from volume group."""
-
- LOG.debug(_("Updating volume status"))
- data = {}
- backend_name = self.configuration.safe_get('volume_backend_name')
- data["volume_backend_name"] = backend_name or 'NetApp_iSCSI_Cluster'
- data["vendor_name"] = 'NetApp'
- data["driver_version"] = '1.0'
- data["storage_protocol"] = 'iSCSI'
-
- data['total_capacity_gb'] = 'infinite'
- data['free_capacity_gb'] = 'infinite'
- data['reserved_percentage'] = 100
- data['QoS_support'] = False
- self._stats = data
-
-
class NetAppDirectISCSIDriver(driver.ISCSIDriver):
"""NetApp Direct iSCSI volume driver."""
def __init__(self, *args, **kwargs):
super(NetAppDirectISCSIDriver, self).__init__(*args, **kwargs)
- self.configuration.append_config_values(netapp_opts)
+ validate_instantiation(**kwargs)
+ self.configuration.append_config_values(netapp_connection_opts)
+ self.configuration.append_config_values(netapp_basicauth_opts)
+ self.configuration.append_config_values(netapp_transport_opts)
+ self.configuration.append_config_values(netapp_provisioning_opts)
self.lun_table = {}
def _create_client(self, **kwargs):
name = volume['name']
metadata = self._get_lun_attr(name, 'metadata')
if not metadata:
- LOG.warn(_("No entry in LUN table for volume/snapshot %(name)s."),
- {'name': name})
+ msg = _("No entry in LUN table for volume/snapshot %(name)s.")
+ msg_fmt = {'name': name}
+ LOG.warn(msg % msg_fmt)
return
lun_destroy = NaElement.create_node_with_children(
'lun-destroy',
initiator_name = connector['initiator']
name = volume['name']
lun_id = self._map_lun(name, initiator_name, 'iscsi', None)
- msg = (_("Mapped LUN %(name)s to the initiator %(initiator_name)s") %
- {'name': name, 'initiator_name': initiator_name})
- LOG.debug(msg)
+ msg = _("Mapped LUN %(name)s to the initiator %(initiator_name)s")
+ msg_fmt = {'name': name, 'initiator_name': initiator_name}
+ LOG.debug(msg % msg_fmt)
iqn = self._get_iscsi_service_details()
target_details_list = self._get_target_details()
- msg = (_("Succesfully fetched target details for LUN %(name)s and "
- "initiator %(initiator_name)s") %
- {'name': name, 'initiator_name': initiator_name})
- LOG.debug(msg)
+ msg = _("Succesfully fetched target details for LUN %(name)s and "
+ "initiator %(initiator_name)s")
+ msg_fmt = {'name': name, 'initiator_name': initiator_name}
+ LOG.debug(msg % msg_fmt)
if not target_details_list:
msg = _('Failed to get LUN target details for the LUN %s')
vol_size = volume['size']
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.VolumeBackendAPIException(data=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.VolumeBackendAPIException(data=msg % msg_fmt)
snapshot_name = snapshot['name']
new_name = volume['name']
self._clone_lun(snapshot_name, new_name, 'true')
metadata = self._get_lun_attr(name, 'metadata')
path = metadata['Path']
self._unmap_lun(path, initiator_name)
- msg = (_("Unmapped LUN %(name)s from the initiator "
- "%(initiator_name)s") %
- {'name': name, 'initiator_name': initiator_name})
- LOG.debug(msg)
+ msg = _("Unmapped LUN %(name)s from the initiator "
+ "%(initiator_name)s")
+ msg_fmt = {'name': name, 'initiator_name': initiator_name}
+ LOG.debug(msg % msg_fmt)
def _get_ontapi_version(self):
"""Gets the supported ontapi version."""
result = self.client.invoke_successfully(lun_map, True)
return result.get_child_content('lun-id-assigned')
except NaApiError as e:
- msg = (_("Error mapping lun. Code :%(code)s,"
- " Message:%(message)s") %
- {'code': e.code, 'message': e.message})
- LOG.warn(msg)
+ code = e.code
+ message = e.message
+ msg = _('Error mapping lun. Code :%(code)s, Message:%(message)s')
+ msg_fmt = {'code': code, 'message': message}
+ LOG.warn(msg % msg_fmt)
(igroup, lun_id) = self._find_mapped_lun_igroup(path, initiator)
if lun_id is not None:
return lun_id
try:
self.client.invoke_successfully(lun_unmap, True)
except NaApiError as e:
- msg = (_("Error unmapping lun. Code :%(code)s,"
- " Message:%(message)s") %
- {'code': e.code, 'message': e.message})
- LOG.warn(msg)
+ msg = _("Error unmapping lun. Code :%(code)s,"
+ " Message:%(message)s")
+ msg_fmt = {'code': e.code, 'message': e.message}
+ LOG.warn(msg % msg_fmt)
# if the lun is already unmapped
if e.code == '13115' or e.code == '9016':
pass
src_vol = self.lun_table[src_vref['name']]
src_vol_size = src_vref['size']
if vol_size != src_vol_size:
- msg = (_("Cannot clone volume of size %(vol_size)s from "
- "src volume of size %(src_vol_size)s") %
- {'vol_size': vol_size, 'src_vol_size': src_vol_size})
- raise exception.VolumeBackendAPIException(data=msg)
+ msg = _('Cannot clone volume of size %(vol_size)s from '
+ 'src volume of size %(src_vol_size)s')
+ msg_fmt = {'vol_size': vol_size, 'src_vol_size': src_vol_size}
+ raise exception.VolumeBackendAPIException(data=msg % msg_fmt)
new_name = volume['name']
self._clone_lun(src_vol.name, new_name, 'true')
def __init__(self, *args, **kwargs):
super(NetAppDirectCmodeISCSIDriver, self).__init__(*args, **kwargs)
+ self.configuration.append_config_values(netapp_cluster_opts)
def _do_custom_setup(self):
"""Does custom setup for ontap cluster."""
LOG.debug(_("Updating volume status"))
data = {}
+ netapp_backend = 'NetApp_iSCSI_Cluster_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
- data["volume_backend_name"] = (backend_name
- or 'NetApp_iSCSI_Cluster_direct')
+ data["volume_backend_name"] = (
+ backend_name or netapp_backend)
data["vendor_name"] = 'NetApp'
data["driver_version"] = '1.0'
data["storage_protocol"] = 'iSCSI'
data['free_capacity_gb'] = 'infinite'
data['reserved_percentage'] = 100
data['QoS_support'] = False
+ provide_ems(self, self.client, data, netapp_backend)
self._stats = data
def __init__(self, *args, **kwargs):
super(NetAppDirect7modeISCSIDriver, self).__init__(*args, **kwargs)
+ self.configuration.append_config_values(netapp_7mode_opts)
def _do_custom_setup(self):
"""Does custom setup depending on the type of filer."""
if luns:
lun_list.extend(luns)
except NaApiError:
- LOG.warn(_("Error finding luns for volume %(vol)s."
- " Verify volume exists."), {'vol': vol})
+ LOG.warn(_("Error finding luns for volume %s."
+ " Verify volume exists.") % (vol))
else:
luns = self._get_vol_luns(None)
lun_list.extend(luns)
break
else:
if clone_ops_info:
+ fmt = {'name': name, 'new_name': new_name}
if clone_ops_info.get_child_content('clone-state')\
== 'completed':
LOG.debug(_("Clone operation with src %(name)s"
- " and dest %(new_name)s completed"),
- {'name': name, 'new_name': new_name})
+ " and dest %(new_name)s completed") % fmt)
else:
LOG.debug(_("Clone operation with src %(name)s"
- " and dest %(new_name)s failed"),
- {'name': name, 'new_name': new_name})
+ " and dest %(new_name)s failed") % fmt)
raise NaApiError(
clone_ops_info.get_child_content('error'),
clone_ops_info.get_child_content('reason'))
LOG.debug(_("Updating volume status"))
data = {}
+ netapp_backend = 'NetApp_iSCSI_7mode_direct'
backend_name = self.configuration.safe_get('volume_backend_name')
- data["volume_backend_name"] = (backend_name
- or 'NetApp_iSCSI_7mode_direct')
+ data["volume_backend_name"] = (
+ backend_name or 'NetApp_iSCSI_7mode_direct')
data["vendor_name"] = 'NetApp'
data["driver_version"] = '1.0'
data["storage_protocol"] = 'iSCSI'
data['free_capacity_gb'] = 'infinite'
data['reserved_percentage'] = 100
data['QoS_support'] = False
+ provide_ems(self, self.client, data, netapp_backend,
+ server_type="7mode")
self._stats = data
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')]
+LOG = logging.getLogger(__name__)
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."""
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)
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
"""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:
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
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)
"""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."""
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)
(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)
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,
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):
(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)
: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,
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")
--- /dev/null
+# 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'), ]
--- /dev/null
+# 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."))
'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':