cinder/cinder/tests/unit/test_cloudbyte.py

1406 lines
46 KiB
Python

# Copyright 2015 CloudByte 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.
""" Test class for cloudbyte's cinder driver.
This involves mocking of elasticenter's json responses
when a method of this driver is unit tested.
"""
import json
import mock
import testtools
from testtools import matchers
from cinder import exception
from cinder.volume import configuration as conf
from cinder.volume.drivers.cloudbyte import cloudbyte
# A fake list account response of cloudbyte's elasticenter
FAKE_LIST_ACCOUNT_RESPONSE = """{ "listAccountResponse" : {
"count":1 ,
"account" : [{
"id": "d13a4e9e-0c05-4d2d-8a5e-5efd3ef058e0",
"name": "CustomerA",
"simpleid": 1,
"description": "None",
"iqnname": "iqn.2014-05.cvsacc1",
"availIOPS": 508,
"totaliops": 2000,
"usedIOPS": 1492,
"volumes": [],
"storageBuckets": [],
"tsms": [],
"qosgroups": [],
"filesystemslist": [],
"currentUsedSpace": 53179,
"currentAvailableSpace": 1249349,
"currentThroughput": 156,
"currentIOPS": 33,
"currentLatency": 0,
"currentThrottle": 0,
"numericquota": 3145728.0,
"currentnumericquota": 1253376.0,
"currentavailablequota": 1892352.0,
"revisionnumber": 1
}]
}}"""
# A fake list tsm response of cloudbyte's elasticenter
FAKE_LIST_TSM_RESPONSE = """{ "listTsmResponse" : {
"count":1 ,
"listTsm" : [{
"id": "955eaf34-4221-3a77-82d0-99113b126fa8",
"simpleid": 2,
"name": "openstack",
"ipaddress": "172.16.50.40",
"accountname": "CustomerA",
"sitename": "BLR",
"clustername": "HAGrp1",
"controllerName": "Controller",
"controlleripaddress": "172.16.50.6",
"clusterstatus": "Online",
"hapoolstatus": "ONLINE",
"hapoolname": "pool",
"hapoolavailiops": 1700,
"hapoolgrace": true,
"hapoolavailtput": 6800,
"poollatency": 10,
"accountid": "d13a4e9e-0c05-4d2d-8a5e-5efd3ef058e0",
"controllerid": "8c2f7084-99c0-36e6-9cb7-205e3ba4c813",
"poolid": "adcbef8f-2193-3f2c-9bb1-fcaf977ae0fc",
"datasetid": "87a23025-f2b2-39e9-85ac-9cda15bfed1a",
"storageBuckets": [],
"currentUsedSpace": 16384,
"currentAvailableSpace": 188416,
"currentTotalSpace": 204800,
"currentThroughput": 12,
"tpcontrol": "true",
"currentIOPS": 0,
"iopscontrol": "true",
"gracecontrol": "false",
"currentLatency": 0,
"currentThrottle": 0,
"iops": "1000",
"availIOPS": "500",
"availThroughput": "2000",
"usedIOPS": "500",
"usedThroughput": "2000",
"throughput": "4000",
"latency": "15",
"graceallowed": true,
"numericquota": 1048576.0,
"currentnumericquota": 204800.0,
"availablequota": 843776.0,
"blocksize": "4",
"type": "1",
"iqnname": "iqn.2014-05.cvsacc1.openstack",
"interfaceName": "em0",
"revisionnumber": 0,
"status": "Online",
"subnet": "16",
"managedstate": "Available",
"configurationstate": "sync",
"offlinenodes": "",
"pooltakeover": "noTakeOver",
"totalprovisionquota": "536576",
"haNodeStatus": "Available",
"ispooltakeoveronpartialfailure": true,
"filesystemslist": [],
"volumes": [],
"qosgrouplist": []
}]
}}"""
# A fake add QOS group response of cloudbyte's elasticenter
FAKE_ADD_QOS_GROUP_RESPONSE = """{ "addqosgroupresponse" : {
"qosgroup" : {
"id": "d73662ac-6db8-3b2c-981a-012af4e2f7bd",
"name": "QoS_DS1acc1openstacktsm",
"tsmid": "8146146e-f67b-3942-8074-3074599207a4",
"controllerid": "f1603e87-d1e6-3dcb-a549-7a6e77f82d86",
"poolid": "73b567c0-e57d-37b5-b765-9d70725f59af",
"parentid": "81ebdcbb-f73b-3337-8f32-222820e6acb9",
"tsmName": "openstacktsm",
"offlinenodes": "",
"sitename": "site1",
"clustername": "HA1",
"controllerName": "node1",
"clusterstatus": "Online",
"currentThroughput": 0,
"currentIOPS": 0,
"currentLatency": 0,
"currentThrottle": 0,
"iopsvalue": "(0/100)",
"throughputvalue": "(0/400)",
"iops": "100",
"iopscontrol": "true",
"throughput": "400",
"tpcontrol": "true",
"blocksize": "4k",
"latency": "15",
"graceallowed": false,
"type": "1",
"revisionnumber": 0,
"managedstate": "Available",
"configurationstate": "init",
"standardproviops": 0,
"operatingblocksize": 0,
"operatingcachehit": 0,
"operatingiops": 0,
"standardoperatingiops": 0
}
}}"""
# A fake create volume response of cloudbyte's elasticenter
FAKE_CREATE_VOLUME_RESPONSE = """{ "createvolumeresponse" : {
"jobid": "f94e2257-9515-4a44-add0-4b16cb1bcf67"
}}"""
# A fake query async job response of cloudbyte's elasticenter
FAKE_QUERY_ASYNC_JOB_RESULT_RESPONSE = """{ "queryasyncjobresultresponse" : {
"accountid": "e8aca633-7bce-4ab7-915a-6d8847248467",
"userid": "a83d1030-1b85-40f7-9479-f40e4dbdd5d5",
"cmd": "com.cloudbyte.api.commands.CreateVolumeCmd",
"msg": "5",
"jobstatus": 1,
"jobprocstatus": 0,
"jobresultcode": 0,
"jobresulttype": "object",
"jobresult": {
"storage": {
"id": "92cfd601-bc1f-3fa7-8322-c492099f3326",
"name": "DS1",
"simpleid": 20,
"compression": "off",
"sync": "always",
"noofcopies": 1,
"recordsize": "4k",
"deduplication": "off",
"quota": "10G",
"path": "devpool1/acc1openstacktsm/DS1",
"tsmid": "8146146e-f67b-3942-8074-3074599207a4",
"poolid": "73b567c0-e57d-37b5-b765-9d70725f59af",
"mountpoint": "acc1DS1",
"currentUsedSpace": 0,
"currentAvailableSpace": 0,
"currentTotalSpace": 0,
"currentThroughput": 0,
"currentIOPS": 0,
"currentLatency": 0,
"currentThrottle": 0,
"tsmName": "openstacktsm",
"hapoolname": "devpool1",
"revisionnumber": 0,
"blocklength": "512B",
"nfsenabled": false,
"cifsenabled": false,
"iscsienabled": true,
"fcenabled": false
}
},
"created": "2014-06-16 15:49:49",
"jobid": "f94e2257-9515-4a44-add0-4b16cb1bcf67"
}}"""
# A fake list filesystem response of cloudbyte's elasticenter
FAKE_LIST_FILE_SYSTEM_RESPONSE = """{ "listFilesystemResponse" : {
"count":1 ,
"filesystem" : [{
"id": "c93df32e-3a99-3491-8e10-cf318a7f9b7f",
"name": "c93df32e3a9934918e10cf318a7f9b7f",
"simpleid": 34,
"type": "filesystem",
"revisionnumber": 1,
"path": "/cvsacc1DS1",
"clusterid": "8b404f12-7975-4e4e-8549-7abeba397fc9",
"clusterstatus": "Online",
"Tsmid": "955eaf34-4221-3a77-82d0-99113b126fa8",
"tsmType": "1",
"accountid": "d13a4e9e-0c05-4d2d-8a5e-5efd3ef058e0",
"poolid": "adcbef8f-2193-3f2c-9bb1-fcaf977ae0fc",
"controllerid": "8c2f7084-99c0-36e6-9cb7-205e3ba4c813",
"groupid": "663923c9-084b-3778-b13d-72f23d046b8d",
"parentid": "08de7c14-62af-3992-8407-28f5f053e59b",
"compression": "off",
"sync": "always",
"noofcopies": 1,
"recordsize": "4k",
"deduplication": "off",
"quota": "1T",
"unicode": "off",
"casesensitivity": "sensitive",
"readonly": false,
"nfsenabled": true,
"cifsenabled": false,
"iscsienabled": false,
"fcenabled": false,
"currentUsedSpace": 19968,
"currentAvailableSpace": 1028608,
"currentTotalSpace": 1048576,
"currentThroughput": 0,
"currentIOPS": 0,
"currentLatency": 0,
"currentThrottle": 0,
"numericquota": 1048576.0,
"status": "Online",
"managedstate": "Available",
"configurationstate": "sync",
"tsmName": "cvstsm1",
"ipaddress": "172.16.50.35",
"sitename": "BLR",
"clustername": "HAGrp1",
"controllerName": "Controller",
"hapoolname": "pool",
"hapoolgrace": true,
"tsmgrace": true,
"tsmcontrolgrace": "false",
"accountname": "CustomerA",
"groupname": "QoS_DS1cvsacc1cvstsm1",
"iops": "500",
"blocksize": "4",
"throughput": "2000",
"latency": "15",
"graceallowed": false,
"offlinenodes": "",
"tpcontrol": "true",
"iopscontrol": "true",
"tsmAvailIops": "8",
"tsmAvailTput": "32",
"iqnname": "",
"mountpoint": "cvsacc1DS1",
"pooltakeover": "noTakeOver",
"volumeaccessible": "true",
"localschedulecount": 0
}]
}}"""
# A fake list storage snapshot response of cloudbyte's elasticenter
FAKE_LIST_STORAGE_SNAPSHOTS_RESPONSE = """{ "listDatasetSnapshotsResponse" : {
"count":1 ,
"snapshot" : [{
"name": "snap_c60890b1f23646f29e6d51e6e592cee6",
"path": "DS1@snap_c60890b1f23646f29e6d51e6e592cee6",
"availMem": "-",
"usedMem": "0",
"refer": "26K",
"mountpoint": "-",
"timestamp": "Mon Jun 16 2014 14:41",
"clones": 0,
"pooltakeover": "noTakeOver",
"managedstate": "Available"
}]
}}"""
# A fake delete storage snapshot response of cloudbyte's elasticenter
FAKE_DELETE_STORAGE_SNAPSHOT_RESPONSE = """{ "deleteSnapshotResponse" : {
"DeleteSnapshot" : {
"status": "success"
}
}}"""
# A fake update volume iscsi service response of cloudbyte's elasticenter
FAKE_UPDATE_VOLUME_ISCSI_SERVICE_RESPONSE = (
"""{ "updatingvolumeiscsidetails" : {
"viscsioptions" : {
"id": "0426c04a-8fac-30e8-a8ad-ddab2f08013a",
"volume_id": "12371e7c-392b-34b9-ac43-073b3c85f1d1",
"ag_id": "4459248d-e9f1-3d2a-b7e8-b5d9ce587fc1",
"ig_id": "527bd65b-ebec-39ce-a5e9-9dd1106cc0fc",
"iqnname": "iqn.2014-06.acc1.openstacktsm:acc1DS1",
"authmethod": "None",
"status": true,
"usn": "12371e7c392b34b9ac43073b3c85f1d1",
"initialdigest": "Auto",
"queuedepth": "32",
"inqproduct": 0,
"inqrevision": 0,
"blocklength": "512B"
}}
}""")
# A fake list iscsi initiator response of cloudbyte's elasticenter
FAKE_LIST_ISCSI_INITIATOR_RESPONSE = """{ "listInitiatorsResponse" : {
"count":2 ,
"initiator" : [{
"id": "527bd65b-ebec-39ce-a5e9-9dd1106cc0fc",
"accountid": "86c5251a-9044-4690-b924-0d97627aeb8c",
"name": "ALL",
"netmask": "ALL",
"initiatorgroup": "ALL"
},{
"id": "203e0235-1d5a-3130-9204-98e3f642a564",
"accountid": "86c5251a-9044-4690-b924-0d97627aeb8c",
"name": "None",
"netmask": "None",
"initiatorgroup": "None"
}]
}}"""
# A fake delete file system response of cloudbyte's elasticenter
FAKE_DELETE_FILE_SYSTEM_RESPONSE = """{ "deleteResponse" : {
"response" : [{
"code": "0",
"description": "success"
}]
}}"""
# A fake create storage snapshot response of cloudbyte's elasticenter
FAKE_CREATE_STORAGE_SNAPSHOT_RESPONSE = (
"""{ "createStorageSnapshotResponse" : {
"StorageSnapshot" : {
"id": "21d7a92a-f15e-3f5b-b981-cb30697b8028",
"name": "snap_c60890b1f23646f29e6d51e6e592cee6",
"usn": "21d7a92af15e3f5bb981cb30697b8028",
"lunusn": "12371e7c392b34b9ac43073b3c85f1d1",
"lunid": "12371e7c-392b-34b9-ac43-073b3c85f1d1",
"scsiEnabled": false
}}
}""")
# A fake list volume iscsi service response of cloudbyte's elasticenter
FAKE_LIST_VOLUME_ISCSI_SERVICE_RESPONSE = (
"""{ "listVolumeiSCSIServiceResponse" : {
"count":1 ,
"iSCSIService" : [{
"id": "67ddcbf4-6887-3ced-8695-7b9cdffce885",
"volume_id": "c93df32e-3a99-3491-8e10-cf318a7f9b7f",
"ag_id": "4459248d-e9f1-3d2a-b7e8-b5d9ce587fc1",
"ig_id": "203e0235-1d5a-3130-9204-98e3f642a564",
"iqnname": "iqn.2014-06.acc1.openstacktsm:acc1DS1",
"authmethod": "None",
"status": true,
"usn": "92cfd601bc1f3fa78322c492099f3326",
"initialdigest": "Auto",
"queuedepth": "32",
"inqproduct": 0,
"inqrevision": 0,
"blocklength": "512B"
}]
}}""")
# A fake clone dataset snapshot response of cloudbyte's elasticenter
FAKE_CLONE_DATASET_SNAPSHOT_RESPONSE = """{ "cloneDatasetSnapshot" : {
"filesystem" : {
"id": "dcd46a57-e3f4-3fc1-8dd8-2e658d9ebb11",
"name": "DS1Snap1clone1",
"simpleid": 21,
"type": "volume",
"revisionnumber": 1,
"path": "iqn.2014-06.acc1.openstacktsm:acc1DS1Snap1clone1",
"clusterid": "0ff44329-9a69-4611-bac2-6eaf1b08bb18",
"clusterstatus": "Online",
"Tsmid": "8146146e-f67b-3942-8074-3074599207a4",
"tsmType": "1",
"accountid": "86c5251a-9044-4690-b924-0d97627aeb8c",
"poolid": "73b567c0-e57d-37b5-b765-9d70725f59af",
"controllerid": "f1603e87-d1e6-3dcb-a549-7a6e77f82d86",
"groupid": "d73662ac-6db8-3b2c-981a-012af4e2f7bd",
"parentid": "81ebdcbb-f73b-3337-8f32-222820e6acb9",
"compression": "off",
"sync": "always",
"noofcopies": 1,
"recordsize": "4k",
"deduplication": "off",
"quota": "10G",
"unicode": "off",
"casesensitivity": "sensitive",
"readonly": false,
"nfsenabled": false,
"cifsenabled": false,
"iscsienabled": true,
"fcenabled": false,
"currentUsedSpace": 0,
"currentAvailableSpace": 10240,
"currentTotalSpace": 10240,
"currentThroughput": 0,
"currentIOPS": 0,
"currentLatency": 0,
"currentThrottle": 0,
"numericquota": 10240.0,
"status": "Online",
"managedstate": "Available",
"configurationstate": "sync",
"tsmName": "openstacktsm",
"ipaddress": "20.10.22.56",
"sitename": "site1",
"clustername": "HA1",
"controllerName": "node1",
"hapoolname": "devpool1",
"hapoolgrace": true,
"tsmgrace": true,
"tsmcontrolgrace": "false",
"accountname": "acc1",
"groupname": "QoS_DS1acc1openstacktsm",
"iops": "100",
"blocksize": "4k",
"throughput": "400",
"latency": "15",
"graceallowed": false,
"offlinenodes": "",
"tpcontrol": "true",
"iopscontrol": "true",
"tsmAvailIops": "700",
"tsmAvailTput": "2800",
"iqnname": "iqn.2014-06.acc1.openstacktsm:acc1DS1Snap1clone1",
"mountpoint": "acc1DS1Snap1clone1",
"blocklength": "512B",
"volumeaccessible": "true",
"localschedulecount": 0
}
}}"""
# A fake update filesystem response of cloudbyte's elasticenter
FAKE_UPDATE_FILE_SYSTEM_RESPONSE = """{ "updatefilesystemresponse" : {
"count":1 ,
"filesystem" : [{
"id": "92cfd601-bc1f-3fa7-8322-c492099f3326",
"name": "DS1",
"simpleid": 20,
"type": "volume",
"revisionnumber": 1,
"path": "iqn.2014-06.acc1.openstacktsm:acc1DS1",
"clusterid": "0ff44329-9a69-4611-bac2-6eaf1b08bb18",
"clusterstatus": "Online",
"Tsmid": "8146146e-f67b-3942-8074-3074599207a4",
"tsmType": "1",
"accountid": "86c5251a-9044-4690-b924-0d97627aeb8c",
"poolid": "73b567c0-e57d-37b5-b765-9d70725f59af",
"controllerid": "f1603e87-d1e6-3dcb-a549-7a6e77f82d86",
"groupid": "d73662ac-6db8-3b2c-981a-012af4e2f7bd",
"parentid": "81ebdcbb-f73b-3337-8f32-222820e6acb9",
"compression": "off",
"sync": "always",
"noofcopies": 1,
"recordsize": "4k",
"deduplication": "off",
"quota": "12G",
"unicode": "off",
"casesensitivity": "sensitive",
"readonly": false,
"nfsenabled": false,
"cifsenabled": false,
"iscsienabled": true,
"fcenabled": false,
"currentUsedSpace": 0,
"currentAvailableSpace": 10240,
"currentTotalSpace": 10240,
"currentThroughput": 0,
"currentIOPS": 0,
"currentLatency": 0,
"currentThrottle": 0,
"numericquota": 12288.0,
"status": "Online",
"managedstate": "Available",
"configurationstate": "sync",
"tsmName": "openstacktsm",
"ipaddress": "20.10.22.56",
"sitename": "site1",
"clustername": "HA1",
"controllerName": "node1",
"hapoolname": "devpool1",
"hapoolgrace": true,
"tsmgrace": true,
"tsmcontrolgrace": "false",
"accountname": "acc1",
"groupname": "QoS_DS1acc1openstacktsm",
"iops": "100",
"blocksize": "4k",
"throughput": "400",
"latency": "15",
"graceallowed": false,
"offlinenodes": "",
"tpcontrol": "true",
"iopscontrol": "true",
"tsmAvailIops": "700",
"tsmAvailTput": "2800",
"iqnname": "iqn.2014-06.acc1.openstacktsm:acc1DS1",
"mountpoint": "acc1DS1",
"blocklength": "512B",
"volumeaccessible": "true",
"localschedulecount": 0
}]
}}"""
# A fake update QOS group response of cloudbyte's elasticenter
FAKE_UPDATE_QOS_GROUP_RESPONSE = """{ "updateqosresponse" : {
"count":1 ,
"qosgroup" : [{
"id": "d73662ac-6db8-3b2c-981a-012af4e2f7bd",
"name": "QoS_DS1acc1openstacktsm",
"tsmid": "8146146e-f67b-3942-8074-3074599207a4",
"controllerid": "f1603e87-d1e6-3dcb-a549-7a6e77f82d86",
"poolid": "73b567c0-e57d-37b5-b765-9d70725f59af",
"parentid": "81ebdcbb-f73b-3337-8f32-222820e6acb9",
"tsmName": "openstacktsm",
"offlinenodes": "",
"sitename": "site1",
"clustername": "HA1",
"controllerName": "node1",
"clusterstatus": "Online",
"currentThroughput": 0,
"currentIOPS": 0,
"currentLatency": 0,
"currentThrottle": 0,
"iopsvalue": "(0/101)",
"throughputvalue": "(0/404)",
"iops": "101",
"iopscontrol": "true",
"throughput": "404",
"tpcontrol": "true",
"blocksize": "4k",
"latency": "15",
"graceallowed": true,
"type": "1",
"revisionnumber": 2,
"managedstate": "Available",
"configurationstate": "sync",
"status": "Online",
"standardproviops": 0,
"operatingblocksize": 0,
"operatingcachehit": 0,
"operatingiops": 0,
"standardoperatingiops": 0
}]
}}"""
# A fake list iSCSI auth user response of cloudbyte's elasticenter
FAKE_LIST_ISCSI_AUTH_USER_RESPONSE = """{ "listiSCSIAuthUsersResponse" : {
"count":1 ,
"authuser" : [{
"id": "53d00164-a974-31b8-a854-bd346a8ea937",
"accountid": "12d41531-c41a-4ab7-abe2-ce0db2570119",
"authgroupid": "537744eb-c594-3145-85c0-96079922b894",
"chapusername": "fakeauthgroupchapuser",
"chappassword": "fakeauthgroupchapsecret",
"mutualchapusername": "fakeauthgroupmutualchapuser",
"mutualchappassword": "fakeauthgroupmutualchapsecret"
}]
}}"""
# A fake list iSCSI auth group response of cloudbyte's elasticenter
FAKE_LIST_ISCSI_AUTH_GROUP_RESPONSE = """{ "listiSCSIAuthGroupResponse" : {
"count":2 ,
"authgroup" : [{
"id": "32d935ee-a60f-3681-b792-d8ccfe7e8e7f",
"name": "None",
"comment": "None"
}, {
"id": "537744eb-c594-3145-85c0-96079922b894",
"name": "fakeauthgroup",
"comment": "Fake Auth Group For Openstack "
}]
}}"""
# This dict maps the http commands of elasticenter
# with its respective fake responses
MAP_COMMAND_TO_FAKE_RESPONSE = {}
MAP_COMMAND_TO_FAKE_RESPONSE['deleteFileSystem'] = (
json.loads(FAKE_DELETE_FILE_SYSTEM_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["listFileSystem"] = (
json.loads(FAKE_LIST_FILE_SYSTEM_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["deleteSnapshot"] = (
json.loads(FAKE_DELETE_STORAGE_SNAPSHOT_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["updateVolumeiSCSIService"] = (
json.loads(FAKE_UPDATE_VOLUME_ISCSI_SERVICE_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["createStorageSnapshot"] = (
json.loads(FAKE_CREATE_STORAGE_SNAPSHOT_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["listAccount"] = (
json.loads(FAKE_LIST_ACCOUNT_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["listTsm"] = (
json.loads(FAKE_LIST_TSM_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["addQosGroup"] = (
json.loads(FAKE_ADD_QOS_GROUP_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["queryAsyncJobResult"] = (
json.loads(FAKE_QUERY_ASYNC_JOB_RESULT_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["createVolume"] = (
json.loads(FAKE_CREATE_VOLUME_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["listVolumeiSCSIService"] = (
json.loads(FAKE_LIST_VOLUME_ISCSI_SERVICE_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE["listiSCSIInitiator"] = (
json.loads(FAKE_LIST_ISCSI_INITIATOR_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE['cloneDatasetSnapshot'] = (
json.loads(FAKE_CLONE_DATASET_SNAPSHOT_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE['updateFileSystem'] = (
json.loads(FAKE_UPDATE_FILE_SYSTEM_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE['updateQosGroup'] = (
json.loads(FAKE_UPDATE_QOS_GROUP_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE['listStorageSnapshots'] = (
json.loads(FAKE_LIST_STORAGE_SNAPSHOTS_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE['listiSCSIAuthUser'] = (
json.loads(FAKE_LIST_ISCSI_AUTH_USER_RESPONSE))
MAP_COMMAND_TO_FAKE_RESPONSE['listiSCSIAuthGroup'] = (
json.loads(FAKE_LIST_ISCSI_AUTH_GROUP_RESPONSE))
class CloudByteISCSIDriverTestCase(testtools.TestCase):
def setUp(self):
super(CloudByteISCSIDriverTestCase, self).setUp()
self._configure_driver()
def _configure_driver(self):
configuration = conf.Configuration(None, None)
# initialize the elasticenter iscsi driver
self.driver = cloudbyte.CloudByteISCSIDriver(
configuration=configuration)
# override some parts of driver configuration
self.driver.configuration.cb_tsm_name = 'openstack'
self.driver.configuration.cb_account_name = 'CustomerA'
self.driver.configuration.cb_auth_group = 'fakeauthgroup'
def _side_effect_api_req(self, cmd, params, version='1.0'):
"""This is a side effect function.
The return value is determined based on cmd argument.
The signature matches exactly with the method it tries
to mock.
"""
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_api_req_to_create_vol(self, cmd, params, version='1.0'):
"""This is a side effect function."""
if cmd == 'createVolume':
return {}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_api_req_to_query_asyncjob(
self, cmd, params, version='1.0'):
"""This is a side effect function."""
if cmd == 'queryAsyncJobResult':
return {'queryasyncjobresultresponse': {'jobstatus': 0}}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_api_req_to_list_tsm(self, cmd, params, version='1.0'):
"""This is a side effect function."""
if cmd == 'listTsm':
return {}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _none_response_to_list_tsm(self, cmd, params, version='1.0'):
"""This is a side effect function."""
if cmd == 'listTsm':
return {"listTsmResponse": {}}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_api_req_to_list_iscsi_auth_group(self, cmd, params,
version='1.0'):
"""This is a side effect function."""
if cmd == 'listiSCSIAuthGroup':
return {}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_api_req_to_list_iscsi_auth_user(self, cmd, params,
version='1.0'):
"""This is a side effect function."""
if cmd == 'listiSCSIAuthUser':
return {}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_enable_chap(self):
"""This is a side effect function."""
self.driver.cb_use_chap = True
def _side_effect_disable_chap(self):
"""This is a side effect function."""
self.driver.cb_use_chap = False
def _side_effect_api_req_to_list_filesystem(
self, cmd, params, version='1.0'):
"""This is a side effect function."""
if cmd == 'listFileSystem':
return {}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_api_req_to_list_vol_iscsi_service(
self, cmd, params, version='1.0'):
"""This is a side effect function."""
if cmd == 'listVolumeiSCSIService':
return {}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_api_req_to_list_iscsi_initiator(
self, cmd, params, version='1.0'):
"""This is a side effect function."""
if cmd == 'listiSCSIInitiator':
return {}
return MAP_COMMAND_TO_FAKE_RESPONSE[cmd]
def _side_effect_create_vol_from_snap(self, cloned_volume, snapshot):
"""This is a side effect function."""
return {}
def _side_effect_create_snapshot(self, snapshot):
"""This is a side effect function."""
model_update = {}
model_update['provider_id'] = "devpool1/acc1openstacktsm/DS1@DS1Snap1"
return model_update
def _side_effect_get_connection(self, host, url):
"""This is a side effect function."""
return_obj = {}
return_obj['http_status'] = 200
# mock the response data
return_obj['data'] = MAP_COMMAND_TO_FAKE_RESPONSE['listTsm']
return_obj['error'] = None
return return_obj
def _side_effect_get_err_connection(self, host, url):
"""This is a side effect function."""
return_obj = {}
return_obj['http_status'] = 500
# mock the response data
return_obj['data'] = None
return_obj['error'] = "Http status: 500, Error: Elasticenter "
"is not available."
return return_obj
def _side_effect_get_err_connection2(self, host, url):
"""This is a side effect function."""
msg = ("Error executing CloudByte API %(cmd)s , Error: %(err)s" %
{'cmd': 'MockTest', 'err': 'Error'})
raise exception.VolumeBackendAPIException(msg)
def _get_fake_volume_id(self):
# Get the filesystems
fs_list = MAP_COMMAND_TO_FAKE_RESPONSE['listFileSystem']
filesystems = fs_list['listFilesystemResponse']['filesystem']
# Get the volume id from the first filesystem
volume_id = filesystems[0]['id']
return volume_id
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_execute_and_get_response_details')
def test_api_request_for_cloudbyte(self, mock_conn):
# Test - I
# configure the mocks with respective side-effects
mock_conn.side_effect = self._side_effect_get_connection
# run the test
data = self.driver._api_request_for_cloudbyte('listTsm', {})
# assert the data attributes
self.assertEqual(1, data['listTsmResponse']['count'])
# Test - II
# configure the mocks with side-effects
mock_conn.reset_mock()
mock_conn.side_effect = self._side_effect_get_err_connection
# run the test
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
'Bad or unexpected response from the storage volume '
'backend API: Failed to execute CloudByte API'):
self.driver._api_request_for_cloudbyte('listTsm', {})
# Test - III
# configure the mocks with side-effects
mock_conn.reset_mock()
mock_conn.side_effect = self._side_effect_get_err_connection2
# run the test
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
'Error executing CloudByte API'):
self.driver._api_request_for_cloudbyte('listTsm', {})
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_delete_volume(self, mock_api_req):
# prepare the dependencies
fake_volume_id = self._get_fake_volume_id()
volume = {'id': fake_volume_id, 'provider_id': fake_volume_id}
# Test-I
mock_api_req.side_effect = self._side_effect_api_req
# run the test
self.driver.delete_volume(volume)
# assert that 2 api calls were invoked
self.assertEqual(2, mock_api_req.call_count)
# Test-II
# reset & re-configure mock
volume['provider_id'] = None
mock_api_req.reset_mock()
mock_api_req.side_effect = self._side_effect_api_req
# run the test
self.driver.delete_volume(volume)
# assert that no api calls were invoked
self.assertEqual(0, mock_api_req.call_count)
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_delete_snapshot(self, mock_api_req):
snapshot = {
'id': 'SomeID',
'provider_id': 'devpool1/acc1openstacktsm/DS1@DS1Snap1',
'display_name': 'DS1Snap1',
'volume_id': 'SomeVol',
'volume': {
'display_name': 'DS1'
}
}
# Test - I
# now run the test
self.driver.delete_snapshot(snapshot)
# assert that 1 api call was invoked
self.assertEqual(1, mock_api_req.call_count)
# Test - II
# reconfigure the dependencies
snapshot['provider_id'] = None
# reset & reconfigure the mock
mock_api_req.reset_mock()
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
self.driver.delete_snapshot(snapshot)
# assert that no api calls were invoked
self.assertEqual(0, mock_api_req.call_count)
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_create_snapshot(self, mock_api_req):
# prepare the dependencies
fake_volume_id = self._get_fake_volume_id()
snapshot = {
'id': 'c60890b1-f236-46f2-9e6d-51e6e592cee6',
'display_name': 'DS1Snap1',
'volume_id': 'SomeVol',
'volume': {
'display_name': 'DS1',
'provider_id': fake_volume_id
}
}
# Test - I
# configure the mocks with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
model_update = self.driver.create_snapshot(snapshot)
# assert that 2 api calls were invoked
self.assertEqual(2, mock_api_req.call_count)
self.assertEqual('DS1@snap_c60890b1f23646f29e6d51e6e592cee6',
model_update['provider_id'])
# Test - II
# reconfigure the dependencies
snapshot['volume']['provider_id'] = None
# reset & reconfigure the mock
mock_api_req.reset_mock()
mock_api_req.side_effect = self._side_effect_api_req
# now run the test & assert the exception
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
'Bad or unexpected response from the storage volume '
'backend API: Failed to create snapshot'):
self.driver.create_snapshot(snapshot)
# assert that no api calls were invoked
self.assertEqual(0, mock_api_req.call_count)
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_create_volume(self, mock_api_req):
# prepare the dependencies
fake_volume_id = self._get_fake_volume_id()
volume = {
'id': fake_volume_id,
'size': 22
}
# Test - I
# enable CHAP
self._side_effect_enable_chap()
# configure the mocks with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
provider_details = self.driver.create_volume(volume)
# assert equality checks for certain configuration attributes
self.assertEqual(
'openstack', self.driver.configuration.cb_tsm_name)
self.assertEqual(
'CustomerA', self.driver.configuration.cb_account_name)
self.assertEqual(
'fakeauthgroup', self.driver.configuration.cb_auth_group)
# assert the result
self.assertEqual(
'CHAP fakeauthgroupchapuser fakeauthgroupchapsecret',
provider_details['provider_auth'])
self.assertThat(
provider_details['provider_location'],
matchers.Contains('172.16.50.35:3260'))
# assert the invoked api calls to CloudByte Storage
self.assertEqual(11, mock_api_req.call_count)
# Test - II
# reset the mock
mock_api_req.reset_mock()
# disable CHAP
self._side_effect_disable_chap()
# configure the mocks with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
provider_details = self.driver.create_volume(volume)
# assert equality checks for certain configuration attributes
self.assertEqual(
'openstack', self.driver.configuration.cb_tsm_name)
self.assertEqual(
'CustomerA', self.driver.configuration.cb_account_name)
# assert the result
self.assertEqual(
None,
provider_details['provider_auth'])
self.assertThat(
provider_details['provider_location'],
matchers.Contains('172.16.50.35:3260'))
# assert the invoked api calls to CloudByte Storage
self.assertEqual(9, mock_api_req.call_count)
# Test - III
# reconfigure the dependencies
volume['id'] = 'NotExists'
del volume['size']
# reset & reconfigure the mock
mock_api_req.reset_mock()
mock_api_req.side_effect = self._side_effect_api_req
# now run the test & assert the exception
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
"Bad or unexpected response from the storage volume "
"backend API: Volume \[NotExists\] not found in "
"CloudByte storage."):
self.driver.create_volume(volume)
# Test - IV
# reconfigure the dependencies
volume['id'] = 'abc'
# reset the mocks
mock_api_req.reset_mock()
# configure or re-configure the mocks
mock_api_req.side_effect = self._side_effect_api_req_to_create_vol
# now run the test & assert the exception
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
'Bad or unexpected response from the storage volume '
'backend API: Null response received while '
'creating volume'):
self.driver.create_volume(volume)
# Test - V
# reconfigure the dependencies
# reset the mocks
mock_api_req.reset_mock()
# configure or re-configure the mocks
mock_api_req.side_effect = self._side_effect_api_req_to_list_filesystem
# now run the test
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
"Bad or unexpected response from the storage volume "
"backend API: Null response received from CloudByte's "
"list filesystem."):
self.driver.create_volume(volume)
# Test - VI
volume['id'] = fake_volume_id
# reconfigure the dependencies
# reset the mocks
mock_api_req.reset_mock()
# configure or re-configure the mocks
mock_api_req.side_effect = (
self._side_effect_api_req_to_list_vol_iscsi_service)
# now run the test
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
"Bad or unexpected response from the storage volume "
"backend API: Null response received from CloudByte's "
"list volume iscsi service."):
self.driver.create_volume(volume)
# Test - VII
# reconfigure the dependencies
# reset the mocks
mock_api_req.reset_mock()
# configure or re-configure the mocks
mock_api_req.side_effect = (
self._side_effect_api_req_to_list_iscsi_initiator)
# now run the test
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
"Bad or unexpected response from the storage volume "
"backend API: Null response received from CloudByte's "
"list iscsi initiators."):
self.driver.create_volume(volume)
# Test - VIII
volume['id'] = fake_volume_id
volume['size'] = 22
# reconfigure the dependencies
# reset the mocks
mock_api_req.reset_mock()
# configure or re-configure the mocks
mock_api_req.side_effect = (
self._none_response_to_list_tsm)
# now run the test
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
"Bad or unexpected response from the storage volume "
"backend API: TSM \[openstack\] was not found in CloudByte "
"storage for account \[CustomerA\]."):
self.driver.create_volume(volume)
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'create_volume_from_snapshot')
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'create_snapshot')
def test_create_cloned_volume(self, mock_create_snapshot,
mock_create_vol_from_snap, mock_api_req):
# prepare the input test data
fake_volume_id = self._get_fake_volume_id()
src_volume = {'display_name': 'DS1Snap1'}
cloned_volume = {
'source_volid': fake_volume_id,
'id': 'SomeNewID',
'display_name': 'CloneOfDS1Snap1'
}
# Test - I
# configure the mocks with respective sideeffects
mock_api_req.side_effect = self._side_effect_api_req
mock_create_vol_from_snap.side_effect = (
self._side_effect_create_vol_from_snap)
mock_create_snapshot.side_effect = (
self._side_effect_create_snapshot)
# now run the test
self.driver.create_cloned_volume(cloned_volume, src_volume)
# assert that n api calls were invoked
self.assertEqual(0, mock_api_req.call_count)
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_create_volume_from_snapshot(self, mock_api_req):
# prepare the input test data
fake_volume_id = self._get_fake_volume_id()
snapshot = {
'volume_id': fake_volume_id,
'provider_id': 'devpool1/acc1openstacktsm/DS1@DS1Snap1',
'id': 'SomeSnapID',
'volume': {
'provider_id': fake_volume_id
}
}
cloned_volume = {
'display_name': 'CloneOfDS1Snap1',
'id': 'ClonedVolID'
}
# Test - I
# enable CHAP
self._side_effect_enable_chap()
# configure the mocks with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
provider_details = (
self.driver.create_volume_from_snapshot(cloned_volume, snapshot))
# assert the result
self.assertEqual(
'CHAP fakeauthgroupchapuser fakeauthgroupchapsecret',
provider_details['provider_auth'])
self.assertEqual(
'20.10.22.56:3260 '
'iqn.2014-06.acc1.openstacktsm:acc1DS1Snap1clone1 0',
provider_details['provider_location'])
# assert the invoked api calls to CloudByte Storage
self.assertEqual(4, mock_api_req.call_count)
# Test - II
# reset the mocks
mock_api_req.reset_mock()
# disable CHAP
self._side_effect_disable_chap()
# configure the mocks with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
provider_details = (
self.driver.create_volume_from_snapshot(cloned_volume, snapshot))
# assert the result
self.assertEqual(
None,
provider_details['provider_auth'])
self.assertEqual(
'20.10.22.56:3260 '
'iqn.2014-06.acc1.openstacktsm:acc1DS1Snap1clone1 0',
provider_details['provider_location'])
# assert n api calls were invoked
self.assertEqual(1, mock_api_req.call_count)
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_extend_volume(self, mock_api_req):
# prepare the input test data
fake_volume_id = self._get_fake_volume_id()
volume = {
'id': 'SomeID',
'provider_id': fake_volume_id
}
new_size = '2'
# Test - I
# configure the mock with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
self.driver.extend_volume(volume, new_size)
# assert n api calls were invoked
self.assertEqual(1, mock_api_req.call_count)
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_create_export(self, mock_api_req):
# Test - I
# enable CHAP
self._side_effect_enable_chap()
# configure the mocks with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
model_update = self.driver.create_export({}, {})
# assert the result
self.assertEqual('CHAP fakeauthgroupchapuser fakeauthgroupchapsecret',
model_update['provider_auth'])
# Test - II
# reset the mocks
mock_api_req.reset_mock()
# disable CHAP
self._side_effect_disable_chap()
# configure the mocks with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
model_update = self.driver.create_export({}, {})
# assert the result
self.assertEqual(None,
model_update['provider_auth'])
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_ensure_export(self, mock_api_req):
# Test - I
# enable CHAP
self._side_effect_enable_chap()
# configure the mock with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
model_update = self.driver.ensure_export({}, {})
# assert the result to have a provider_auth attribute
self.assertEqual('CHAP fakeauthgroupchapuser fakeauthgroupchapsecret',
model_update['provider_auth'])
# Test - II
# reset the mocks
mock_api_req.reset_mock()
# disable CHAP
self._side_effect_disable_chap()
# configure the mocks with respective side-effects
mock_api_req.side_effect = self._side_effect_api_req
# now run the test
model_update = self.driver.create_export({}, {})
# assert the result
self.assertEqual(None,
model_update['provider_auth'])
@mock.patch.object(cloudbyte.CloudByteISCSIDriver,
'_api_request_for_cloudbyte')
def test_get_volume_stats(self, mock_api_req):
# configure the mock with a side-effect
mock_api_req.side_effect = self._side_effect_api_req
# Test - I
# run the test
vol_stats = self.driver.get_volume_stats()
# assert 0 api calls were invoked
self.assertEqual(0, mock_api_req.call_count)
# Test - II
# run the test with refresh as True
vol_stats = self.driver.get_volume_stats(refresh=True)
# assert n api calls were invoked
self.assertEqual(1, mock_api_req.call_count)
# assert the result attributes with respective values
self.assertEqual(1024.0, vol_stats['total_capacity_gb'])
self.assertEqual(824.0, vol_stats['free_capacity_gb'])
self.assertEqual(0, vol_stats['reserved_percentage'])
self.assertEqual('CloudByte', vol_stats['vendor_name'])
self.assertEqual('iSCSI', vol_stats['storage_protocol'])
# Test - III
# configure the mocks with side-effect
mock_api_req.reset_mock()
mock_api_req.side_effect = self._side_effect_api_req_to_list_tsm
# run the test with refresh as True
with testtools.ExpectedException(
exception.VolumeBackendAPIException,
"Bad or unexpected response from the storage volume "
"backend API: No response was received from CloudByte "
"storage list tsm API call."):
self.driver.get_volume_stats(refresh=True)