# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2012 NetApp, Inc.
# 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.
"""
Tests for NetApp volume driver
"""
import BaseHTTPServer
import httplib
import StringIO
from lxml import etree
from nova import log as logging
from nova import test
from nova.volume import netapp
LOG = logging.getLogger("nova.volume.driver")
WSDL_HEADER = """
"""
WSDL_TYPES = """
    
        
            
            
            
            
        
    
    
        
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
        
            
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
    
        
            
        
    
    
    
        
            
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
    
        
            
            
        
    
    
        
            
            
        
    
    
        
            
            
            
            
        
    
    
        
            
            
        
    
    
        
            
            
        
    
    
    
        
            
            
            
        
    
    
    
        
            
        
    
    
    
        
            
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
        
    
    
        
            
        
    
    
    
        
            
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
    
        
            
            
        
    
    
        
            
            
        
    
    
        
            
        
    
    
        
            
            
        
    
    
        
            
            
            
            
        
    
    
        
            
            
            
        
    
    
        
    
    
        
    
    
        
    
    
        
    
    
        
    
    
        
    
    
        
    
    
        
    
    
        
        
    
    
    
        
    
    
        
    
    
        
        
    
    
        
    
    
        
        
        
    
    
    
    
        
        
        
    
    
        
    
    
        
        
    
    
    
    
    
    
        
    
    
        
        
        
        
    
    
        
            
                
                    
                
            
        
        
        
    
    
        
        
        
            
                
                    
                
            
        
        
    
    
        
        
        
    
"""
WSDL_TRAILER = """
"""
RESPONSE_PREFIX = """
"""
RESPONSE_SUFFIX = """"""
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 = {}
class FakeDfmServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    """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('' % api)
            out.write('' % api)
            out.write('')
            out.write('' % api)
            out.write('' % api)
            out.write('')
        out.write('')
        for api in APIS:
            out.write('' % api)
            out.write('' % api)
            out.write('' % api)
            out.write('')
        out.write('')
        out.write('')
        out.write('')
        for api in APIS:
            out.write('' % api)
            out.write('' % api)
            out.write('')
            out.write('')
            out.write('')
        out.write('')
        out.write(WSDL_TRAILER)
        return
    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:
            body = """
                    1
                    dataset
                """
        elif 'DatasetListInfoIterNext' == api:
            body = """
                    
                        
                            0
                        
                    
                    1
                """
        elif 'DatasetListInfoIterEnd' == api:
            body = """"""
        elif 'DatasetEditBegin' == api:
            body = """
                    0
                """
        elif 'DatasetEditCommit' == api:
            body = """
                    false
                    
                        
                            0
                        
                    
                """
        elif 'DatasetProvisionMember' == api:
            body = """"""
        elif 'DatasetRemoveMember' == api:
            body = """"""
        elif 'DfmAbout' == api:
            body = """"""
        elif 'DpJobProgressEventListIterStart' == api:
            iter_name = 'dpjobprogress_%s' % iter_count
            iter_count = iter_count + 1
            iter_table[iter_name] = 0
            body = """
                    2
                    %s
                """ % 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 = """"""
            else:
                iter_table[iter_name] = 1
                body = """
                        
                            
                                normal
                                lun-create
                                
                                    0
                                 
                            
                            
                                normal
                                job-end
                            
                        
                        2
                    """
        elif 'DpJobProgressEventListIterEnd' == api:
            body = """"""
        elif 'DatasetMemberListInfoIterStart' == api:
            body = """
                    1
                    dataset-member
                """
        elif 'DatasetMemberListInfoIterNext' == api:
            name = 'filer:/OpenStack_testproj/volume-00000001/volume-00000001'
            body = """
                    
                        
                            0
                            %s
                        
                    
                    1
                """ % name
        elif 'DatasetMemberListInfoIterEnd' == api:
            body = """"""
        elif 'HostListInfoIterStart' == api:
            body = """
                    1
                    host
                """
        elif 'HostListInfoIterNext' == api:
            body = """
                    
                        
                            1.2.3.4
                            0
                            filer
                        
                    
                    1
                """
        elif 'HostListInfoIterEnd' == api:
            body = """"""
        elif 'LunListInfoIterStart' == api:
            body = """
                    1
                    lun
                """
        elif 'LunListInfoIterNext' == api:
            path = 'OpenStack_testproj/volume-00000001/volume-00000001'
            body = """
                    
                        
                            0
                            %s
                        
                    
                    1
                """ % path
        elif 'LunListInfoIterEnd' == api:
            body = """"""
        elif 'ApiProxy' == api:
            names = body.xpath('na:ApiProxy/na:Request/na:Name',
                               namespaces=nsmap)
            proxy = names[0].text
            if 'igroup-list-info' == proxy:
                igroup = 'openstack-iqn.1993-08.org.debian:01:23456789'
                initiator = 'iqn.1993-08.org.debian:01:23456789'
                proxy_body = """
                        
                            %s
                            iscsi
                       linux
                            
                                
                                    %s
                                
                            
                        
                    """ % (igroup, initiator)
            elif 'igroup-create' == proxy:
                proxy_body = ''
            elif 'igroup-add' == proxy:
                proxy_body = ''
            elif 'lun-map-list-info' == proxy:
                proxy_body = ''
            elif 'lun-map' == proxy:
                proxy_body = '0'
            elif 'lun-unmap' == proxy:
                proxy_body = ''
            elif 'iscsi-portal-list-info' == proxy:
                proxy_body = """
                        
                            1.2.3.4
                            3260
                            1000
                        
                    """
            elif 'iscsi-node-get-name' == proxy:
                target = 'iqn.1992-08.com.netapp:sn.111111111'
                proxy_body = '%s' % target
            else:
                # Unknown proxy API
                s.send_response(500)
                s.end_headers
                return
            api = api + ':' + proxy
            proxy_header = ''
            proxy_trailer = """passed
                """
            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)
        return
class FakeHttplibSocket(object):
    """A fake socket implementation for httplib.HTTPResponse"""
    def __init__(self, value):
        self._rbuffer = StringIO.StringIO(value)
        self._wbuffer = StringIO.StringIO('')
        oldclose = self._wbuffer.close
        def newclose():
            self.result = self._wbuffer.getvalue()
            oldclose()
        self._wbuffer.close = newclose
    def makefile(self, mode, _other):
        """Returns the socket's internal buffer"""
        if mode == 'r' or mode == 'rb':
            return self._rbuffer
        if mode == 'w' or mode == 'wb':
            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 = 'Thin Provisioned Space for VMFS Datastores'
    PROJECT_ID = 'testproj'
    VOLUME_NAME = 'volume-00000001'
    VOLUME_SIZE = 2147483648L  # 2 GB
    INITIATOR = 'iqn.1993-08.org.debian:01:23456789'
    def setUp(self):
        super(NetAppDriverTestCase, self).setUp()
        driver = netapp.NetAppISCSIDriver()
        self.stubs.Set(httplib, 'HTTPConnection', FakeHTTPConnection)
        driver._create_client('http://localhost:8088/dfm.wsdl',
                              'root', 'password', 'localhost', 8088)
        driver._set_storage_service(self.STORAGE_SERVICE)
        self.driver = driver
    def test_connect(self):
        self.driver.check_for_setup_error()
    def test_create_destroy(self):
        self.driver._provision(self.VOLUME_NAME, None, self.PROJECT_ID,
                               self.VOLUME_SIZE)
        self.driver._remove_destroy(self.VOLUME_NAME, self.PROJECT_ID)
    def test_map_unmap(self):
        self.driver._provision(self.VOLUME_NAME, None, self.PROJECT_ID,
                               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)