1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright (c) 2012 NetApp, Inc.
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7
# not use this file except in compliance with the License. You may obtain
8
# a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
# License for the specific language governing permissions and limitations
18
Tests for NetApp volume driver
26
from lxml import etree
28
from nova.openstack.common import log as logging
30
from nova.volume import netapp
33
LOG = logging.getLogger(__name__)
36
WSDL_HEADER = """<?xml version="1.0" encoding="UTF-8" standalone="no"?>
37
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
38
xmlns:na="http://www.netapp.com/management/v1"
39
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
40
xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="NetAppDfm"
41
targetNamespace="http://www.netapp.com/management/v1">"""
43
WSDL_TYPES = """<types>
44
<xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified"
45
targetNamespace="http://www.netapp.com/management/v1">
46
<xsd:element name="ApiProxy">
49
<xsd:element name="Request" type="na:Request"/>
50
<xsd:element name="Target" type="xsd:string"/>
51
<xsd:element minOccurs="0" name="Timeout" type="xsd:integer"/>
52
<xsd:element minOccurs="0" name="Username" type="xsd:string"/>
56
<xsd:element name="ApiProxyResult">
59
<xsd:element name="Response" type="na:Response"/>
63
<xsd:element name="DatasetEditBegin">
66
<xsd:element name="DatasetNameOrId" type="na:ObjNameOrId"/>
67
<xsd:element minOccurs="0" name="Force" type="xsd:boolean"/>
71
<xsd:element name="DatasetEditBeginResult">
74
<xsd:element name="EditLockId" type="xsd:integer"/>
78
<xsd:element name="DatasetEditCommit">
81
<xsd:element minOccurs="0" name="AssumeConfirmation"
83
<xsd:element name="EditLockId" type="xsd:integer"/>
87
<xsd:element name="DatasetEditCommitResult">
90
<xsd:element minOccurs="0" name="IsProvisioningFailure"
92
<xsd:element minOccurs="0" name="JobIds" type="na:ArrayOfJobInfo"/>
96
<xsd:element name="DatasetEditRollback">
99
<xsd:element name="EditLockId" type="xsd:integer"/>
103
<xsd:element name="DatasetEditRollbackResult">
106
<xsd:element name="DatasetListInfoIterEnd">
109
<xsd:element name="Tag" type="xsd:string"/>
113
<xsd:element name="DatasetListInfoIterEndResult">
116
<xsd:element name="DatasetListInfoIterNext">
119
<xsd:element name="Maximum" type="xsd:integer"/>
120
<xsd:element name="Tag" type="xsd:string"/>
124
<xsd:element name="DatasetListInfoIterNextResult">
127
<xsd:element name="Datasets" type="na:ArrayOfDatasetInfo"/>
128
<xsd:element name="Records" type="xsd:integer"/>
132
<xsd:element name="DatasetListInfoIterStart">
135
<xsd:element minOccurs="0" name="ObjectNameOrId"
136
type="na:ObjNameOrId"/>
140
<xsd:element name="DatasetListInfoIterStartResult">
143
<xsd:element name="Records" type="xsd:integer"/>
144
<xsd:element name="Tag" type="xsd:string"/>
148
<xsd:element name="DatasetMemberListInfoIterEnd">
151
<xsd:element name="Tag" type="xsd:string"/>
155
<xsd:element name="DatasetMemberListInfoIterEndResult">
158
<xsd:element name="DatasetMemberListInfoIterNext">
161
<xsd:element name="Maximum" type="xsd:integer"/>
162
<xsd:element name="Tag" type="xsd:string"/>
166
<xsd:element name="DatasetMemberListInfoIterNextResult">
169
<xsd:element name="DatasetMembers"
170
type="na:ArrayOfDatasetMemberInfo"/>
171
<xsd:element name="Records" type="xsd:integer"/>
175
<xsd:element name="DatasetMemberListInfoIterStart">
178
<xsd:element name="DatasetNameOrId" type="na:ObjNameOrId"/>
179
<xsd:element minOccurs="0" name="IncludeExportsInfo"
181
<xsd:element minOccurs="0" name="IncludeIndirect"
183
<xsd:element minOccurs="0" name="MemberType" type="xsd:string"/>
187
<xsd:element name="DatasetMemberListInfoIterStartResult">
190
<xsd:element name="Records" type="xsd:integer"/>
191
<xsd:element name="Tag" type="xsd:string"/>
195
<xsd:element name="DatasetProvisionMember">
198
<xsd:element name="EditLockId" type="xsd:integer"/>
199
<xsd:element name="ProvisionMemberRequestInfo"
200
type="na:ProvisionMemberRequestInfo"/>
204
<xsd:element name="DatasetProvisionMemberResult">
207
<xsd:element name="DatasetRemoveMember">
210
<xsd:element name="DatasetMemberParameters"
211
type="na:ArrayOfDatasetMemberParameter"/>
212
<xsd:element minOccurs="0" name="Destroy" type="xsd:boolean"/>
213
<xsd:element name="EditLockId" type="xsd:integer"/>
217
<xsd:element name="DatasetRemoveMemberResult">
220
<xsd:element name="DpJobProgressEventListIterEnd">
223
<xsd:element name="Tag" type="xsd:string"/>
227
<xsd:element name="DpJobProgressEventListIterEndResult">
230
<xsd:element name="DpJobProgressEventListIterNext">
233
<xsd:element name="Maximum" type="xsd:integer"/>
234
<xsd:element name="Tag" type="xsd:string"/>
238
<xsd:element name="DpJobProgressEventListIterNextResult">
241
<xsd:element name="ProgressEvents"
242
type="na:ArrayOfDpJobProgressEventInfo"/>
243
<xsd:element name="Records" type="xsd:integer"/>
247
<xsd:element name="DpJobProgressEventListIterStart">
250
<xsd:element minOccurs="0" name="JobId" type="xsd:integer"/>
254
<xsd:element name="DpJobProgressEventListIterStartResult">
257
<xsd:element name="Records" type="xsd:integer"/>
258
<xsd:element name="Tag" type="xsd:string"/>
262
<xsd:element name="DfmAbout">
265
<xsd:element minOccurs="0" name="IncludeDirectorySizeInfo"
270
<xsd:element name="DfmAboutResult">
275
<xsd:element name="HostListInfoIterEnd">
278
<xsd:element name="Tag" type="xsd:string"/>
282
<xsd:element name="HostListInfoIterEndResult">
285
<xsd:element name="HostListInfoIterNext">
288
<xsd:element name="Maximum" type="xsd:integer"/>
289
<xsd:element name="Tag" type="xsd:string"/>
293
<xsd:element name="HostListInfoIterNextResult">
296
<xsd:element name="Hosts" type="na:ArrayOfHostInfo"/>
297
<xsd:element name="Records" type="xsd:integer"/>
301
<xsd:element name="HostListInfoIterStart">
304
<xsd:element minOccurs="0" name="ObjectNameOrId"
305
type="na:ObjNameOrId"/>
309
<xsd:element name="HostListInfoIterStartResult">
312
<xsd:element name="Records" type="xsd:integer"/>
313
<xsd:element name="Tag" type="xsd:string"/>
317
<xsd:element name="LunListInfoIterEnd">
320
<xsd:element name="Tag" type="xsd:string"/>
324
<xsd:element name="LunListInfoIterEndResult">
327
<xsd:element name="LunListInfoIterNext">
330
<xsd:element name="Maximum" type="xsd:integer"/>
331
<xsd:element name="Tag" type="xsd:string"/>
335
<xsd:element name="LunListInfoIterNextResult">
338
<xsd:element name="Luns" type="na:ArrayOfLunInfo"/>
339
<xsd:element name="Records" type="xsd:integer"/>
343
<xsd:element name="LunListInfoIterStart">
346
<xsd:element minOccurs="0" name="ObjectNameOrId"
347
type="na:ObjNameOrId"/>
351
<xsd:element name="LunListInfoIterStartResult">
354
<xsd:element name="Records" type="xsd:integer"/>
355
<xsd:element name="Tag" type="xsd:string"/>
359
<xsd:element name="StorageServiceDatasetProvision">
362
<xsd:element minOccurs="0" name="AssumeConfirmation"
364
<xsd:element name="DatasetName" type="na:ObjName"/>
365
<xsd:element name="StorageServiceNameOrId" type="na:ObjNameOrId"/>
366
<xsd:element minOccurs="0" name="StorageSetDetails"
367
type="na:ArrayOfStorageSetInfo"/>
371
<xsd:element name="StorageServiceDatasetProvisionResult">
374
<xsd:element minOccurs="0" name="ConformanceAlerts"
375
type="na:ArrayOfConformanceAlert"/>
376
<xsd:element name="DatasetId" type="na:ObjId"/>
377
<xsd:element minOccurs="0" name="DryRunResults"
378
type="na:ArrayOfDryRunResult"/>
382
<xsd:complexType name="ArrayOfDatasetInfo">
384
<xsd:element maxOccurs="unbounded" name="DatasetInfo"
385
type="na:DatasetInfo"/>
388
<xsd:complexType name="ArrayOfDatasetMemberInfo">
390
<xsd:element maxOccurs="unbounded" name="DatasetMemberInfo"
391
type="na:DatasetMemberInfo"/>
394
<xsd:complexType name="ArrayOfDatasetMemberParameter">
396
<xsd:element maxOccurs="unbounded" name="DatasetMemberParameter"
397
type="na:DatasetMemberParameter"/>
400
<xsd:complexType name="ArrayOfDfmMetadataField">
402
<xsd:element maxOccurs="unbounded" name="DfmMetadataField"
403
type="na:DfmMetadataField"/>
406
<xsd:complexType name="ArrayOfDpJobProgressEventInfo">
408
<xsd:element maxOccurs="unbounded" name="DpJobProgressEventInfo"
409
type="na:DpJobProgressEventInfo"/>
412
<xsd:complexType name="ArrayOfHostInfo">
414
<xsd:element maxOccurs="unbounded" name="HostInfo" type="na:HostInfo"/>
417
<xsd:complexType name="ArrayOfJobInfo">
419
<xsd:element maxOccurs="unbounded" name="JobInfo" type="na:JobInfo"/>
422
<xsd:complexType name="ArrayOfLunInfo">
424
<xsd:element maxOccurs="unbounded" name="LunInfo" type="na:LunInfo"/>
427
<xsd:complexType name="ArrayOfStorageSetInfo">
429
<xsd:element maxOccurs="unbounded" name="StorageSetInfo"
430
type="na:StorageSetInfo"/>
433
<xsd:complexType name="DatasetExportInfo">
435
<xsd:element minOccurs="0" name="DatasetExportProtocol"
436
type="na:DatasetExportProtocol"/>
437
<xsd:element minOccurs="0" name="DatasetLunMappingInfo"
438
type="na:DatasetLunMappingInfo"/>
441
<xsd:simpleType name="DatasetExportProtocol">
442
<xsd:restriction base="xsd:string"/>
444
<xsd:complexType name="DatasetInfo">
446
<xsd:element name="DatasetId" type="na:ObjId"/>
447
<xsd:element name="DatasetName" type="na:ObjName"/>
448
<xsd:element name="DatasetMetadata" type="na:ArrayOfDfmMetadataField"/>
451
<xsd:complexType name="DatasetLunMappingInfo">
453
<xsd:element name="IgroupOsType" type="xsd:string"/>
456
<xsd:complexType name="DatasetMemberInfo">
458
<xsd:element name="MemberId" type="na:ObjId"/>
459
<xsd:element name="MemberName" type="na:ObjName"/>
462
<xsd:complexType name="DatasetMemberParameter">
464
<xsd:element name="ObjectNameOrId" type="na:ObjNameOrId"/>
467
<xsd:complexType name="DfmMetadataField">
469
<xsd:element name="FieldName" type="xsd:string"/>
470
<xsd:element name="FieldValue" type="xsd:string"/>
473
<xsd:complexType name="DpJobProgressEventInfo">
475
<xsd:element name="EventStatus" type="na:ObjStatus"/>
476
<xsd:element name="EventType" type="xsd:string"/>
477
<xsd:element minOccurs="0" name="ProgressLunInfo"
478
type="na:ProgressLunInfo"/>
481
<xsd:simpleType name="DpPolicyNodeName">
482
<xsd:restriction base="xsd:string"/>
484
<xsd:simpleType name="HostId">
485
<xsd:restriction base="xsd:integer"/>
487
<xsd:complexType name="HostInfo">
489
<xsd:element name="HostAddress" type="xsd:string"/>
490
<xsd:element name="HostId" type="na:HostId"/>
491
<xsd:element name="HostName" type="xsd:string"/>
494
<xsd:complexType name="JobInfo">
496
<xsd:element name="JobId" type="xsd:integer"/>
499
<xsd:complexType name="LunInfo">
501
<xsd:element name="HostId" type="na:ObjId"/>
502
<xsd:element name="LunPath" type="na:ObjName"/>
505
<xsd:simpleType name="ObjId">
506
<xsd:restriction base="xsd:integer"/>
508
<xsd:simpleType name="ObjName">
509
<xsd:restriction base="xsd:string"/>
511
<xsd:simpleType name="ObjNameOrId">
512
<xsd:restriction base="xsd:string"/>
514
<xsd:simpleType name="ObjStatus">
515
<xsd:restriction base="xsd:string"/>
517
<xsd:complexType name="ProgressLunInfo">
519
<xsd:element name="LunPathId" type="na:ObjId"/>
520
<xsd:element name="LunName" type="na:ObjName"/>
523
<xsd:complexType name="ProvisionMemberRequestInfo">
525
<xsd:element minOccurs="0" name="Description" type="xsd:string"/>
526
<xsd:element minOccurs="0" name="MaximumSnapshotSpace"
528
<xsd:element name="Name" type="xsd:string"/>
529
<xsd:element name="Size" type="xsd:integer"/>
532
<xsd:complexType name="Request">
534
<xsd:element minOccurs="0" name="Args">
537
<xsd:any maxOccurs="unbounded" minOccurs="0"/>
541
<xsd:element name="Name" type="xsd:string">
545
<xsd:complexType name="Response">
547
<xsd:element minOccurs="0" name="Errno" type="xsd:integer"/>
548
<xsd:element minOccurs="0" name="Reason" type="xsd:string"/>
549
<xsd:element minOccurs="0" name="Results">
552
<xsd:any maxOccurs="unbounded" minOccurs="0"/>
556
<xsd:element name="Status" type="xsd:string"/>
559
<xsd:complexType name="StorageSetInfo">
561
<xsd:element minOccurs="0" name="DatasetExportInfo"
562
type="na:DatasetExportInfo"/>
563
<xsd:element minOccurs="0" name="DpNodeName"
564
type="na:DpPolicyNodeName"/>
565
<xsd:element minOccurs="0" name="ServerNameOrId"
566
type="na:ObjNameOrId"/>
569
</xsd:schema></types>"""
571
WSDL_TRAILER = """<service name="DfmService">
572
<port binding="na:DfmBinding" name="DfmPort">
573
<soap:address location="https://HOST_NAME:8488/apis/soap/v1"/>
574
</port></service></definitions>"""
576
RESPONSE_PREFIX = """<?xml version="1.0" encoding="UTF-8"?>
577
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
578
xmlns:na="http://www.netapp.com/management/v1"><env:Header/><env:Body>"""
580
RESPONSE_SUFFIX = """</env:Body></env:Envelope>"""
582
APIS = ['ApiProxy', 'DatasetListInfoIterStart', 'DatasetListInfoIterNext',
583
'DatasetListInfoIterEnd', 'DatasetEditBegin', 'DatasetEditCommit',
584
'DatasetProvisionMember', 'DatasetRemoveMember', 'DfmAbout',
585
'DpJobProgressEventListIterStart', 'DpJobProgressEventListIterNext',
586
'DpJobProgressEventListIterEnd', 'DatasetMemberListInfoIterStart',
587
'DatasetMemberListInfoIterNext', 'DatasetMemberListInfoIterEnd',
588
'HostListInfoIterStart', 'HostListInfoIterNext', 'HostListInfoIterEnd',
589
'LunListInfoIterStart', 'LunListInfoIterNext', 'LunListInfoIterEnd',
590
'StorageServiceDatasetProvision']
596
class FakeDfmServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
597
"""HTTP handler that fakes enough stuff to allow the driver to run"""
600
"""Respond to a GET request."""
601
if '/dfm.wsdl' != s.path:
606
s.send_header("Content-Type", "application/wsdl+xml")
609
out.write(WSDL_HEADER)
610
out.write(WSDL_TYPES)
612
out.write('<message name="%sRequest">' % api)
613
out.write('<part element="na:%s" name="parameters"/>' % api)
614
out.write('</message>')
615
out.write('<message name="%sResponse">' % api)
616
out.write('<part element="na:%sResult" name="results"/>' % api)
617
out.write('</message>')
618
out.write('<portType name="DfmInterface">')
620
out.write('<operation name="%s">' % api)
621
out.write('<input message="na:%sRequest"/>' % api)
622
out.write('<output message="na:%sResponse"/>' % api)
623
out.write('</operation>')
624
out.write('</portType>')
625
out.write('<binding name="DfmBinding" type="na:DfmInterface">')
626
out.write('<soap:binding style="document" ' +
627
'transport="http://schemas.xmlsoap.org/soap/http"/>')
629
out.write('<operation name="%s">' % api)
630
out.write('<soap:operation soapAction="urn:%s"/>' % api)
631
out.write('<input><soap:body use="literal"/></input>')
632
out.write('<output><soap:body use="literal"/></output>')
633
out.write('</operation>')
634
out.write('</binding>')
635
out.write(WSDL_TRAILER)
638
"""Respond to a POST request."""
639
if '/apis/soap/v1' != s.path:
643
request_xml = s.rfile.read(int(s.headers['Content-Length']))
644
ntap_ns = 'http://www.netapp.com/management/v1'
645
nsmap = {'env': 'http://schemas.xmlsoap.org/soap/envelope/',
647
root = etree.fromstring(request_xml)
649
body = root.xpath('/env:Envelope/env:Body', namespaces=nsmap)[0]
650
request = body.getchildren()[0]
652
if not tag.startswith('{' + ntap_ns + '}'):
656
api = tag[(2 + len(ntap_ns)):]
659
if 'DatasetListInfoIterStart' == api:
660
iter_name = 'dataset_%s' % iter_count
661
iter_count = iter_count + 1
662
iter_table[iter_name] = 0
663
body = """<na:DatasetListInfoIterStartResult>
664
<na:Records>1</na:Records>
666
</na:DatasetListInfoIterStartResult>""" % iter_name
667
elif 'DatasetListInfoIterNext' == api:
668
tags = body.xpath('na:DatasetListInfoIterNext/na:Tag',
670
iter_name = tags[0].text
671
if iter_table[iter_name]:
672
body = """<na:DatasetListInfoIterNextResult>
673
<na:Datasets></na:Datasets>
674
<na:Records>0</na:Records>
675
</na:DatasetListInfoIterNextResult>"""
677
iter_table[iter_name] = 1
678
body = """<na:DatasetListInfoIterNextResult>
681
<na:DatasetId>0</na:DatasetId>
683
<na:DfmMetadataField>
684
<na:FieldName>OpenStackProject</na:FieldName>
685
<na:FieldValue>testproj</na:FieldValue>
686
</na:DfmMetadataField>
687
<na:DfmMetadataField>
688
<na:FieldName>OpenStackVolType</na:FieldName>
689
<na:FieldValue></na:FieldValue>
690
</na:DfmMetadataField>
691
</na:DatasetMetadata>
692
<na:DatasetName>OpenStack_testproj</na:DatasetName>
695
<na:Records>1</na:Records>
696
</na:DatasetListInfoIterNextResult>"""
697
elif 'DatasetListInfoIterEnd' == api:
698
body = """<na:DatasetListInfoIterEndResult/>"""
699
elif 'DatasetEditBegin' == api:
700
body = """<na:DatasetEditBeginResult>
701
<na:EditLockId>0</na:EditLockId>
702
</na:DatasetEditBeginResult>"""
703
elif 'DatasetEditCommit' == api:
704
body = """<na:DatasetEditCommitResult>
705
<na:IsProvisioningFailure>false</na:IsProvisioningFailure>
708
<na:JobId>0</na:JobId>
711
</na:DatasetEditCommitResult>"""
712
elif 'DatasetProvisionMember' == api:
713
body = """<na:DatasetProvisionMemberResult/>"""
714
elif 'DatasetRemoveMember' == api:
715
body = """<na:DatasetRemoveMemberResult/>"""
716
elif 'DfmAbout' == api:
717
body = """<na:DfmAboutResult/>"""
718
elif 'DpJobProgressEventListIterStart' == api:
719
iter_name = 'dpjobprogress_%s' % iter_count
720
iter_count = iter_count + 1
721
iter_table[iter_name] = 0
722
body = """<na:DpJobProgressEventListIterStartResult>
723
<na:Records>2</na:Records>
725
</na:DpJobProgressEventListIterStartResult>""" % iter_name
726
elif 'DpJobProgressEventListIterNext' == api:
727
tags = body.xpath('na:DpJobProgressEventListIterNext/na:Tag',
729
iter_name = tags[0].text
730
if iter_table[iter_name]:
731
body = """<na:DpJobProgressEventListIterNextResult/>"""
733
iter_table[iter_name] = 1
734
name = ('filer:/OpenStack_testproj/volume-00000001/'
736
body = """<na:DpJobProgressEventListIterNextResult>
738
<na:DpJobProgressEventInfo>
739
<na:EventStatus>normal</na:EventStatus>
740
<na:EventType>lun-create</na:EventType>
742
<na:LunPathId>0</na:LunPathId>
743
<na:LunName>%s</na:LunName>
744
</na:ProgressLunInfo>
745
</na:DpJobProgressEventInfo>
746
<na:DpJobProgressEventInfo>
747
<na:EventStatus>normal</na:EventStatus>
748
<na:EventType>job-end</na:EventType>
749
</na:DpJobProgressEventInfo>
751
<na:Records>2</na:Records>
752
</na:DpJobProgressEventListIterNextResult>""" % name
753
elif 'DpJobProgressEventListIterEnd' == api:
754
body = """<na:DpJobProgressEventListIterEndResult/>"""
755
elif 'DatasetMemberListInfoIterStart' == api:
756
iter_name = 'datasetmember_%s' % iter_count
757
iter_count = iter_count + 1
758
iter_table[iter_name] = 0
759
body = """<na:DatasetMemberListInfoIterStartResult>
760
<na:Records>1</na:Records>
762
</na:DatasetMemberListInfoIterStartResult>""" % iter_name
763
elif 'DatasetMemberListInfoIterNext' == api:
764
tags = body.xpath('na:DatasetMemberListInfoIterNext/na:Tag',
766
iter_name = tags[0].text
767
if iter_table[iter_name]:
768
body = """<na:DatasetMemberListInfoIterNextResult>
769
<na:DatasetMembers></na:DatasetMembers>
770
<na:Records>0</na:Records>
771
</na:DatasetMemberListInfoIterNextResult>"""
773
iter_table[iter_name] = 1
774
name = ('filer:/OpenStack_testproj/volume-00000001/'
776
body = """<na:DatasetMemberListInfoIterNextResult>
778
<na:DatasetMemberInfo>
779
<na:MemberId>0</na:MemberId>
780
<na:MemberName>%s</na:MemberName>
781
</na:DatasetMemberInfo>
783
<na:Records>1</na:Records>
784
</na:DatasetMemberListInfoIterNextResult>""" % name
785
elif 'DatasetMemberListInfoIterEnd' == api:
786
body = """<na:DatasetMemberListInfoIterEndResult/>"""
787
elif 'HostListInfoIterStart' == api:
788
body = """<na:HostListInfoIterStartResult>
789
<na:Records>1</na:Records>
790
<na:Tag>host</na:Tag>
791
</na:HostListInfoIterStartResult>"""
792
elif 'HostListInfoIterNext' == api:
793
body = """<na:HostListInfoIterNextResult>
796
<na:HostAddress>1.2.3.4</na:HostAddress>
797
<na:HostId>0</na:HostId>
798
<na:HostName>filer</na:HostName>
801
<na:Records>1</na:Records>
802
</na:HostListInfoIterNextResult>"""
803
elif 'HostListInfoIterEnd' == api:
804
body = """<na:HostListInfoIterEndResult/>"""
805
elif 'LunListInfoIterStart' == api:
806
body = """<na:LunListInfoIterStartResult>
807
<na:Records>1</na:Records>
809
</na:LunListInfoIterStartResult>"""
810
elif 'LunListInfoIterNext' == api:
811
path = 'OpenStack_testproj/volume-00000001/volume-00000001'
812
body = """<na:LunListInfoIterNextResult>
815
<na:HostId>0</na:HostId>
816
<na:LunPath>%s</na:LunPath>
819
<na:Records>1</na:Records>
820
</na:LunListInfoIterNextResult>""" % path
821
elif 'LunListInfoIterEnd' == api:
822
body = """<na:LunListInfoIterEndResult/>"""
823
elif 'ApiProxy' == api:
824
names = body.xpath('na:ApiProxy/na:Request/na:Name',
826
proxy = names[0].text
827
if 'igroup-list-info' == proxy:
828
igroup = 'openstack-iqn.1993-08.org.debian:01:23456789'
829
initiator = 'iqn.1993-08.org.debian:01:23456789'
830
proxy_body = """<initiator-groups>
831
<initiator-group-info>
832
<initiator-group-name>%s</initiator-group-name>
833
<initiator-group-type>iscsi</initiator-group-type>
834
<initiator-group-os-type>linux</initiator-group-os-type>
837
<initiator-name>%s</initiator-name>
840
</initiator-group-info>
841
</initiator-groups>""" % (igroup, initiator)
842
elif 'igroup-create' == proxy:
844
elif 'igroup-add' == proxy:
846
elif 'lun-map-list-info' == proxy:
847
proxy_body = '<initiator-groups/>'
848
elif 'lun-map' == proxy:
849
proxy_body = '<lun-id-assigned>0</lun-id-assigned>'
850
elif 'lun-unmap' == proxy:
852
elif 'iscsi-portal-list-info' == proxy:
853
proxy_body = """<iscsi-portal-list-entries>
854
<iscsi-portal-list-entry-info>
855
<ip-address>1.2.3.4</ip-address>
856
<ip-port>3260</ip-port>
857
<tpgroup-tag>1000</tpgroup-tag>
858
</iscsi-portal-list-entry-info>
859
</iscsi-portal-list-entries>"""
860
elif 'iscsi-node-get-name' == proxy:
861
target = 'iqn.1992-08.com.netapp:sn.111111111'
862
proxy_body = '<node-name>%s</node-name>' % target
868
api = api + ':' + proxy
869
proxy_header = '<na:ApiProxyResult><na:Response><na:Results>'
870
proxy_trailer = """</na:Results><na:Status>passed</na:Status>
871
</na:Response></na:ApiProxyResult>"""
872
body = proxy_header + proxy_body + proxy_trailer
879
s.send_header("Content-Type", "text/xml; charset=utf-8")
881
s.wfile.write(RESPONSE_PREFIX)
883
s.wfile.write(RESPONSE_SUFFIX)
886
class FakeHttplibSocket(object):
887
"""A fake socket implementation for httplib.HTTPResponse"""
888
def __init__(self, value):
889
self._rbuffer = StringIO.StringIO(value)
890
self._wbuffer = StringIO.StringIO('')
891
oldclose = self._wbuffer.close
894
self.result = self._wbuffer.getvalue()
896
self._wbuffer.close = newclose
898
def makefile(self, mode, _other):
899
"""Returns the socket's internal buffer"""
900
if mode == 'r' or mode == 'rb':
902
if mode == 'w' or mode == 'wb':
906
class FakeHTTPConnection(object):
907
"""A fake httplib.HTTPConnection for netapp tests
909
Requests made via this connection actually get translated and routed into
910
the fake Dfm handler above, we then turn the response into
911
the httplib.HTTPResponse that the caller expects.
913
def __init__(self, host, timeout=None):
916
def request(self, method, path, data=None, headers=None):
919
req_str = '%s %s HTTP/1.1\r\n' % (method, path)
920
for key, value in headers.iteritems():
921
req_str += "%s: %s\r\n" % (key, value)
923
req_str += '\r\n%s' % data
925
# NOTE(vish): normally the http transport normailizes from unicode
926
sock = FakeHttplibSocket(req_str.decode("latin-1").encode("utf-8"))
927
# NOTE(vish): stop the server from trying to look up address from
929
FakeDfmServerHandler.address_string = lambda x: '127.0.0.1'
930
self.app = FakeDfmServerHandler(sock, '127.0.0.1:8088', None)
932
self.sock = FakeHttplibSocket(sock.result)
933
self.http_response = httplib.HTTPResponse(self.sock)
935
def set_debuglevel(self, level):
938
def getresponse(self):
939
self.http_response.begin()
940
return self.http_response
942
def getresponsebody(self):
943
return self.sock.result
946
class NetAppDriverTestCase(test.TestCase):
947
"""Test case for NetAppISCSIDriver"""
948
STORAGE_SERVICE = 'Openstack Service'
949
STORAGE_SERVICE_PREFIX = 'Openstack Service-'
950
PROJECT_ID = 'testproj'
951
VOLUME_NAME = 'volume-00000001'
953
VOLUME_SIZE = 2147483648L # 2 GB
954
INITIATOR = 'iqn.1993-08.org.debian:01:23456789'
957
super(NetAppDriverTestCase, self).setUp()
958
driver = netapp.NetAppISCSIDriver()
959
self.stubs.Set(httplib, 'HTTPConnection', FakeHTTPConnection)
960
driver._create_client(wsdl_url='http://localhost:8088/dfm.wsdl',
961
login='root', password='password',
962
hostname='localhost', port=8088, cache=False)
963
driver._set_storage_service(self.STORAGE_SERVICE)
964
driver._set_storage_service_prefix(self.STORAGE_SERVICE_PREFIX)
965
driver._set_vfiler('')
968
def test_connect(self):
969
self.driver.check_for_setup_error()
971
def test_create_destroy(self):
972
self.driver._discover_luns()
973
self.driver._provision(self.VOLUME_NAME, None, self.PROJECT_ID,
974
self.VOLUME_TYPE, self.VOLUME_SIZE)
975
self.driver._remove_destroy(self.VOLUME_NAME, self.PROJECT_ID)
977
def test_map_unmap(self):
978
self.driver._discover_luns()
979
self.driver._provision(self.VOLUME_NAME, None, self.PROJECT_ID,
980
self.VOLUME_TYPE, self.VOLUME_SIZE)
981
volume = {'name': self.VOLUME_NAME, 'project_id': self.PROJECT_ID,
982
'id': 0, 'provider_auth': None}
983
updates = self.driver._get_export(volume)
984
self.assertTrue(updates['provider_location'])
985
volume['provider_location'] = updates['provider_location']
986
connector = {'initiator': self.INITIATOR}
987
connection_info = self.driver.initialize_connection(volume, connector)
988
self.assertEqual(connection_info['driver_volume_type'], 'iscsi')
989
properties = connection_info['data']
990
self.driver.terminate_connection(volume, connector)
991
self.driver._remove_destroy(self.VOLUME_NAME, self.PROJECT_ID)
994
WSDL_HEADER_CMODE = """<?xml version="1.0" encoding="UTF-8"?>
995
<definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
996
xmlns:na="http://cloud.netapp.com/"
997
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
998
xmlns="http://schemas.xmlsoap.org/wsdl/"
999
targetNamespace="http://cloud.netapp.com/" name="CloudStorageService">
1002
WSDL_TYPES_CMODE = """<types>
1003
<xs:schema xmlns:na="http://cloud.netapp.com/"
1004
xmlns:xs="http://www.w3.org/2001/XMLSchema" version="1.0"
1005
targetNamespace="http://cloud.netapp.com/">
1007
<xs:element name="ProvisionLun">
1010
<xs:element name="Name" type="xs:string"/>
1011
<xs:element name="Size" type="xsd:long"/>
1012
<xs:element name="Metadata" type="na:Metadata" minOccurs="0"
1013
maxOccurs="unbounded"/>
1017
<xs:element name="ProvisionLunResult">
1020
<xs:element name="Lun" type="na:Lun"/>
1025
<xs:element name="DestroyLun">
1028
<xs:element name="Handle" type="xsd:string"/>
1032
<xs:element name="DestroyLunResult">
1038
<xs:element name="CloneLun">
1041
<xs:element name="Handle" type="xsd:string"/>
1042
<xs:element name="NewName" type="xsd:string"/>
1043
<xs:element name="Metadata" type="na:Metadata" minOccurs="0"
1044
maxOccurs="unbounded"/>
1048
<xs:element name="CloneLunResult">
1051
<xs:element name="Lun" type="na:Lun"/>
1056
<xs:element name="MapLun">
1059
<xs:element name="Handle" type="xsd:string"/>
1060
<xs:element name="InitiatorType" type="xsd:string"/>
1061
<xs:element name="InitiatorName" type="xsd:string"/>
1065
<xs:element name="MapLunResult">
1071
<xs:element name="UnmapLun">
1074
<xs:element name="Handle" type="xsd:string"/>
1075
<xs:element name="InitiatorType" type="xsd:string"/>
1076
<xs:element name="InitiatorName" type="xsd:string"/>
1080
<xs:element name="UnmapLunResult">
1086
<xs:element name="ListLuns">
1089
<xs:element name="NameFilter" type="xsd:string" minOccurs="0"/>
1093
<xs:element name="ListLunsResult">
1096
<xs:element name="Lun" type="na:Lun" minOccurs="0"
1097
maxOccurs="unbounded"/>
1102
<xs:element name="GetLunTargetDetails">
1105
<xs:element name="Handle" type="xsd:string"/>
1106
<xs:element name="InitiatorType" type="xsd:string"/>
1107
<xs:element name="InitiatorName" type="xsd:string"/>
1111
<xs:element name="GetLunTargetDetailsResult">
1114
<xs:element name="TargetDetails" type="na:TargetDetails"
1115
minOccurs="0" maxOccurs="unbounded"/>
1120
<xs:complexType name="Metadata">
1122
<xs:element name="Key" type="xs:string"/>
1123
<xs:element name="Value" type="xs:string"/>
1127
<xs:complexType name="Lun">
1129
<xs:element name="Name" type="xs:string"/>
1130
<xs:element name="Size" type="xs:long"/>
1131
<xs:element name="Handle" type="xs:string"/>
1132
<xs:element name="Metadata" type="na:Metadata" minOccurs="0"
1133
maxOccurs="unbounded"/>
1137
<xs:complexType name="TargetDetails">
1139
<xs:element name="Address" type="xs:string"/>
1140
<xs:element name="Port" type="xs:int"/>
1141
<xs:element name="Portal" type="xs:int"/>
1142
<xs:element name="Iqn" type="xs:string"/>
1143
<xs:element name="LunNumber" type="xs:int"/>
1147
</xs:schema></types>"""
1149
WSDL_TRAILER_CMODE = """<service name="CloudStorageService">
1150
<port name="CloudStoragePort" binding="na:CloudStorageBinding">
1151
<soap:address location="http://hostname:8080/ws/ntapcloud"/>
1156
RESPONSE_PREFIX_CMODE = """<?xml version='1.0' encoding='UTF-8'?>
1157
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
1160
RESPONSE_SUFFIX_CMODE = """</soapenv:Body></soapenv:Envelope>"""
1162
CMODE_APIS = ['ProvisionLun', 'DestroyLun', 'CloneLun', 'MapLun', 'UnmapLun',
1163
'ListLuns', 'GetLunTargetDetails']
1166
class FakeCMODEServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
1167
"""HTTP handler that fakes enough stuff to allow the driver to run"""
1170
"""Respond to a GET request."""
1171
if '/ntap_cloud.wsdl' != s.path:
1172
s.send_response(404)
1175
s.send_response(200)
1176
s.send_header("Content-Type", "application/wsdl+xml")
1179
out.write(WSDL_HEADER_CMODE)
1180
out.write(WSDL_TYPES_CMODE)
1181
for api in CMODE_APIS:
1182
out.write('<message name="%sRequest">' % api)
1183
out.write('<part element="na:%s" name="req"/>' % api)
1184
out.write('</message>')
1185
out.write('<message name="%sResponse">' % api)
1186
out.write('<part element="na:%sResult" name="res"/>' % api)
1187
out.write('</message>')
1188
out.write('<portType name="CloudStorage">')
1189
for api in CMODE_APIS:
1190
out.write('<operation name="%s">' % api)
1191
out.write('<input message="na:%sRequest"/>' % api)
1192
out.write('<output message="na:%sResponse"/>' % api)
1193
out.write('</operation>')
1194
out.write('</portType>')
1195
out.write('<binding name="CloudStorageBinding" '
1196
'type="na:CloudStorage">')
1197
out.write('<soap:binding style="document" ' +
1198
'transport="http://schemas.xmlsoap.org/soap/http"/>')
1199
for api in CMODE_APIS:
1200
out.write('<operation name="%s">' % api)
1201
out.write('<soap:operation soapAction=""/>')
1202
out.write('<input><soap:body use="literal"/></input>')
1203
out.write('<output><soap:body use="literal"/></output>')
1204
out.write('</operation>')
1205
out.write('</binding>')
1206
out.write(WSDL_TRAILER_CMODE)
1209
"""Respond to a POST request."""
1210
if '/ws/ntapcloud' != s.path:
1211
s.send_response(404)
1214
request_xml = s.rfile.read(int(s.headers['Content-Length']))
1215
ntap_ns = 'http://cloud.netapp.com/'
1216
nsmap = {'soapenv': 'http://schemas.xmlsoap.org/soap/envelope/',
1218
root = etree.fromstring(request_xml)
1220
body = root.xpath('/soapenv:Envelope/soapenv:Body',
1221
namespaces=nsmap)[0]
1222
request = body.getchildren()[0]
1224
if not tag.startswith('{' + ntap_ns + '}'):
1225
s.send_response(500)
1228
api = tag[(2 + len(ntap_ns)):]
1229
if 'ProvisionLun' == api:
1230
body = """<ns:ProvisionLunResult xmlns:ns=
1231
"http://cloud.netapp.com/">
1232
<Lun><Name>lun1</Name><Size>20</Size>
1233
<Handle>1d9c006c-a406-42f6-a23f-5ed7a6dc33e3</Handle>
1234
<Metadata><Key>OsType</Key>
1235
<Value>linux</Value></Metadata></Lun>
1236
</ns:ProvisionLunResult>"""
1237
elif 'DestroyLun' == api:
1238
body = """<ns:DestroyLunResult xmlns:ns="http://cloud.netapp.com/"
1240
elif 'CloneLun' == api:
1241
body = """<ns:CloneLunResult xmlns:ns="http://cloud.netapp.com/">
1242
<Lun><Name>lun2</Name><Size>2</Size>
1243
<Handle>98ea1791d228453899d422b4611642c3</Handle>
1244
<Metadata><Key>OsType</Key>
1245
<Value>linux</Value></Metadata>
1246
</Lun></ns:CloneLunResult>"""
1247
elif 'MapLun' == api:
1248
body = """<ns1:MapLunResult xmlns:ns="http://cloud.netapp.com/"
1250
elif 'Unmap' == api:
1251
body = """<ns1:UnmapLunResult xmlns:ns="http://cloud.netapp.com/"
1253
elif 'ListLuns' == api:
1254
body = """<ns:ListLunsResult xmlns:ns="http://cloud.netapp.com/">
1258
<Handle>asdjdnsd</Handle>
1260
</ns:ListLunsResult>"""
1261
elif 'GetLunTargetDetails' == api:
1262
body = """<ns:GetLunTargetDetailsResult
1263
xmlns:ns="http://cloud.netapp.com/">
1265
<Address>1.2.3.4</Address>
1267
<Portal>1000</Portal>
1268
<Iqn>iqn.199208.com.netapp:sn.123456789</Iqn>
1269
<LunNumber>0</LunNumber>
1271
</ns:GetLunTargetDetailsResult>"""
1274
s.send_response(500)
1277
s.send_response(200)
1278
s.send_header("Content-Type", "text/xml; charset=utf-8")
1280
s.wfile.write(RESPONSE_PREFIX_CMODE)
1282
s.wfile.write(RESPONSE_SUFFIX_CMODE)
1285
class FakeCmodeHTTPConnection(object):
1286
"""A fake httplib.HTTPConnection for netapp tests
1288
Requests made via this connection actually get translated and routed into
1289
the fake Dfm handler above, we then turn the response into
1290
the httplib.HTTPResponse that the caller expects.
1292
def __init__(self, host, timeout=None):
1295
def request(self, method, path, data=None, headers=None):
1298
req_str = '%s %s HTTP/1.1\r\n' % (method, path)
1299
for key, value in headers.iteritems():
1300
req_str += "%s: %s\r\n" % (key, value)
1302
req_str += '\r\n%s' % data
1304
# NOTE(vish): normally the http transport normailizes from unicode
1305
sock = FakeHttplibSocket(req_str.decode("latin-1").encode("utf-8"))
1306
# NOTE(vish): stop the server from trying to look up address from
1308
FakeCMODEServerHandler.address_string = lambda x: '127.0.0.1'
1309
self.app = FakeCMODEServerHandler(sock, '127.0.0.1:8080', None)
1311
self.sock = FakeHttplibSocket(sock.result)
1312
self.http_response = httplib.HTTPResponse(self.sock)
1314
def set_debuglevel(self, level):
1317
def getresponse(self):
1318
self.http_response.begin()
1319
return self.http_response
1321
def getresponsebody(self):
1322
return self.sock.result
1325
class NetAppCmodeISCSIDriverTestCase(test.TestCase):
1326
"""Test case for NetAppISCSIDriver"""
1328
'name': 'lun1', 'size': 1, 'volume_name': 'lun1',
1329
'os_type': 'linux', 'provider_location': 'lun1',
1330
'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
1331
'display_name': None, 'display_description': 'lun1',
1332
'volume_type_id': None
1335
'name': 'lun2', 'size': 1, 'volume_name': 'lun1',
1336
'volume_size': 1, 'project_id': 'project'
1339
'name': 'vol_snapshot', 'size': 1, 'volume_name': 'lun1',
1340
'os_type': 'linux', 'provider_location': 'lun1',
1341
'id': 'lun1', 'provider_auth': None, 'project_id': 'project',
1342
'display_name': None, 'display_description': 'lun1',
1343
'volume_type_id': None
1347
super(NetAppCmodeISCSIDriverTestCase, self).setUp()
1348
driver = netapp.NetAppCmodeISCSIDriver()
1349
self.stubs.Set(httplib, 'HTTPConnection', FakeCmodeHTTPConnection)
1350
driver._create_client(wsdl_url='http://localhost:8080/ntap_cloud.wsdl',
1351
login='root', password='password',
1352
hostname='localhost', port=8080, cache=False)
1353
self.driver = driver
1355
def test_connect(self):
1356
self.driver.check_for_setup_error()
1358
def test_create_destroy(self):
1359
self.driver.create_volume(self.volume)
1360
self.driver.delete_volume(self.volume)
1362
def test_create_vol_snapshot_destroy(self):
1363
self.driver.create_volume(self.volume)
1364
self.driver.create_snapshot(self.snapshot)
1365
self.driver.create_volume_from_snapshot(self.volume_sec, self.snapshot)
1366
self.driver.delete_snapshot(self.snapshot)
1367
self.driver.delete_volume(self.volume)
1369
def test_map_unmap(self):
1370
self.driver.create_volume(self.volume)
1371
updates = self.driver.create_export(None, self.volume)
1372
self.assertTrue(updates['provider_location'])
1373
self.volume['provider_location'] = updates['provider_location']
1374
connector = {'initiator': 'init1'}
1375
connection_info = self.driver.initialize_connection(self.volume,
1377
self.assertEqual(connection_info['driver_volume_type'], 'iscsi')
1378
properties = connection_info['data']
1379
self.driver.terminate_connection(self.volume, connector)
1380
self.driver.delete_volume(self.volume)