843 lines
40 KiB
Python
843 lines
40 KiB
Python
# Copyright (c) 2014 Fusion-io, 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.
|
|
|
|
import copy
|
|
import json
|
|
|
|
import mock
|
|
from oslo_utils import timeutils
|
|
from oslo_utils import units
|
|
import requests
|
|
|
|
from cinder import context
|
|
from cinder.db.sqlalchemy import models
|
|
from cinder import exception
|
|
from cinder.openstack.common import log as logging
|
|
from cinder import test
|
|
from cinder.volume import configuration as conf
|
|
from cinder.volume.drivers.fusionio import ioControl
|
|
from cinder.volume import qos_specs
|
|
from cinder.volume import volume_types
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
basic_net_response = [{"IsManagementPort": True,
|
|
"NetworkAddress": "10.10.1.82",
|
|
"IsReplicationPort": True, "OperationalState": "up",
|
|
"ControllerUID": "FakeControl1_UID",
|
|
"IfIndex": 2},
|
|
{"IsManagementPort": True,
|
|
"NetworkAddress": "10.10.1.83",
|
|
"IsReplicationPort": True, "OperationalState": "up",
|
|
"ControllerUID": "FakeControl1_UID",
|
|
"IfIndex": 3},
|
|
{"IsManagementPort": False,
|
|
"NetworkAddress": "",
|
|
"IsReplicationPort": False, "OperationalState": "down",
|
|
"ControllerUID": "FakeControl1_UID",
|
|
"IfIndex": 4},
|
|
{"IsManagementPort": True,
|
|
"NetworkAddress": "10.10.2.88",
|
|
"IsReplicationPort": True, "OperationalState": "up",
|
|
"ControllerUID": "FakeControl2_UID",
|
|
"IfIndex": 2},
|
|
{"IsManagementPort": False,
|
|
"NetworkAddress": "10.10.2.84",
|
|
"IsReplicationPort": False, "OperationalState": "up",
|
|
"ControllerUID": "FakeControl2_UID",
|
|
"IfIndex": 3},
|
|
{"IsManagementPort": False,
|
|
"NetworkAddress": "",
|
|
"IsReplicationPort": False, "OperationalState": "down",
|
|
"ControllerUID": "FakeControl2_UID",
|
|
"IfIndex": 4}]
|
|
|
|
basic_pools_response = [{"TotalMB": 5079, "Name": "PoolOwnerA",
|
|
"ExportedVolumeMB": 2049,
|
|
"basetype": "StoragePool", "UsedVolumeMB": 3,
|
|
"ObjectPath": "", "UsedMetaMB": 4, "UsedMB": 4,
|
|
"SizeMB": 68677278, "UsedSnapMB": 0,
|
|
"PagingUsedMB": 4,
|
|
"CurrentOwnerUUID": "FakeControl1_UID",
|
|
"TaskId": "", "PagingTotalMB": 5079, "Ready": True,
|
|
"id": "FakePoolA_id",
|
|
"Size": 72013345456128},
|
|
{"TotalMB": 5079, "Name": "PoolOwnerB",
|
|
"ExportedVolumeMB": 2049,
|
|
"basetype": "StoragePool", "UsedVolumeMB": 193,
|
|
"ObjectPath": "", "UsedMetaMB": 3, "UsedMB": 211,
|
|
"SizeMB": 68677278, "UsedSnapMB": 0,
|
|
"PagingUsedMB": 211,
|
|
"CurrentOwnerUUID": "FakeControl2_UID",
|
|
"TaskId": "", "PagingTotalMB": 5079, "Ready": True,
|
|
"id": "FakePoolB_id",
|
|
"Size": 72013345456128}
|
|
]
|
|
|
|
basic_vol_response = [{"basetype": "Volume", "ObjectPath": "", "TaskId": "",
|
|
"id": "FakeBasicVolID",
|
|
"Name": "cinderVolumeID",
|
|
"IQN": "iqn.2010-11.com.ngs:Volume:FakeBasicVolID",
|
|
"Size": 1074266112, "SizeMB": 1024, "HighWaterMark": 0,
|
|
"HighWaterMarkMB": 0, "MetadataSize": 262144,
|
|
"MetadataSizeMB": 0, "DupedSize": 1074266112,
|
|
"DupedSizeMB": 1024, "FaultTolerance": 0,
|
|
"PathTolerance": 0,
|
|
"AllowedTierMask": 18446744073709551615,
|
|
"RequiredTierMask": 0, "NumberOfPagesPerChapter": 0,
|
|
"CreateDateTime": 1390837136,
|
|
"LayerId": "407115424bb9539c",
|
|
"ParentLayerId": "0", "Protocol": "iscsi",
|
|
"PoolUUID": "FakePoolB_id",
|
|
"PolicyUUID": "00000000-00000000-0000-000000000000",
|
|
"CurrentOwnerUUID": "FakeControl2_UID",
|
|
"AclGroupList": ["1"], "ReplicaPeerList": [],
|
|
"SnapshotRetention": 0}
|
|
]
|
|
|
|
basic_policy_response = [{"id": "00000000-00000000-0000-000000000000",
|
|
"Name": "Policy 5", },
|
|
{"id": "00000000-00000000-0000-000000000002",
|
|
"Name": "Policy 4", },
|
|
{"id": "00000000-00000000-0000-000000000004",
|
|
"Name": "Policy 3", },
|
|
{"id": "00000000-00000000-0000-000000000008",
|
|
"Name": "Policy 2", },
|
|
{"id": "00000000-00000000-0000-000000000010",
|
|
"Name": "Policy 1", },
|
|
]
|
|
|
|
basic_snapshot_response = [{"basetype": "Snapshot", "ObjectPath": "",
|
|
"TaskId": "", "id": "407115424bb9539c",
|
|
"Name": "cinderSnapshotID",
|
|
"VolumeUUID": "FakeBasicVolID",
|
|
"PoolUUID": "FakePoolB_id",
|
|
"ParentUUID": "0", "Size": 1074266112,
|
|
"SizeMB": 1024, "SizeUsed": 0, "SizeUsedMB": 0,
|
|
"SizeReclaimable": 0, "SizeReclaimableMB": 0,
|
|
"CreateDateTime": 1390952554, "ChildCount": 1,
|
|
"IsMounted": False, "IsHostConsistent": False,
|
|
"ReplicaInfoList": []}
|
|
]
|
|
|
|
basic_acl_group_response = [{"id": 1,
|
|
"GroupName": "Deny Access",
|
|
"InitiatorList": [], },
|
|
{"id": 2,
|
|
"GroupName": "Allow Access",
|
|
"InitiatorList": ["iqn*"], },
|
|
{"id": 3,
|
|
"GroupName": "fake:01", "Description": "",
|
|
"InitiatorList": ["fake:01"], },
|
|
{"id": 4,
|
|
"GroupName": "iqn.1994-05.com.redhat:fake1",
|
|
"InitiatorList": ["iqn.1994-05.com.rhel:fake1"],
|
|
},
|
|
{"id": 5,
|
|
"GroupName": "MyGroup", "Description": "",
|
|
"InitiatorList": "iqn.1994-05.com.rhel:fake2", }
|
|
]
|
|
|
|
|
|
def create_configuration():
|
|
configuration = conf.Configuration(None)
|
|
configuration.san_ip = "10.123.10.123"
|
|
configuration.san_login = "fioTestUser"
|
|
configuration.san_password = "fioTestUserPassword"
|
|
# we can set targetdelay to 0 for testing
|
|
configuration.fusionio_iocontrol_targetdelay = 0
|
|
configuration.fusionio_iocontrol_retry = 3
|
|
configuration.fusionio_iocontrol_verify_cert = True
|
|
return configuration
|
|
|
|
|
|
class FIOFakeResponse(object):
|
|
"""Fake response to requests."""
|
|
|
|
def __init__(self, code=None, text=None):
|
|
self.status_code = code
|
|
self.text = text
|
|
|
|
def json(self):
|
|
return json.loads(self.text)
|
|
|
|
def raise_for_status(self):
|
|
if self.status_code > 300:
|
|
raise requests.exceptions.HTTPError
|
|
|
|
|
|
class FIOioControlConnectionTests(test.TestCase):
|
|
|
|
VERSION = '1.0.0'
|
|
fakeSessionID = '12345678'
|
|
|
|
def setUp(self):
|
|
super(FIOioControlConnectionTests, self).setUp()
|
|
self.configuration = create_configuration()
|
|
self.ctxt = context.get_admin_context()
|
|
return_text = json.dumps(
|
|
{"Version": ioControl.FIOconnection.APIVERSION})
|
|
get_return = FIOFakeResponse(code=200,
|
|
text=return_text)
|
|
requests.get = mock.Mock(return_value=get_return)
|
|
self.conn = ioControl.FIOconnection(
|
|
self.configuration.san_ip,
|
|
self.configuration.san_login,
|
|
self.configuration.san_password,
|
|
self.configuration.fusionio_iocontrol_retry,
|
|
(self.configuration.
|
|
fusionio_iocontrol_verify_cert),)
|
|
|
|
def test_conn_init_sucess(self):
|
|
expected = [mock.call(url=("https://" +
|
|
self.configuration.san_ip +
|
|
"/AUTH/Version"),
|
|
headers=self.conn.defhdrs,
|
|
verify=True)]
|
|
requests.get.assert_has_calls(expected)
|
|
|
|
def test_wrong_version(self):
|
|
expected = json.dumps(
|
|
{"Version": (ioControl.FIOconnection.APIVERSION + ".1")})
|
|
get_return = FIOFakeResponse(code=200,
|
|
text=expected)
|
|
requests.get = mock.Mock(return_value=get_return)
|
|
self.assertRaises(exception.VolumeDriverException,
|
|
ioControl.FIOconnection,
|
|
self.configuration.san_ip,
|
|
self.configuration.san_login,
|
|
self.configuration.san_password,
|
|
self.configuration.fusionio_iocontrol_retry,
|
|
self.configuration.fusionio_iocontrol_verify_cert,)
|
|
|
|
def test_create_session_sucess(self):
|
|
expected_text = json.dumps({"id": self.fakeSessionID})
|
|
post_return = FIOFakeResponse(code=201,
|
|
text=expected_text)
|
|
put_return = FIOFakeResponse(code=201,
|
|
text=json.dumps({"Status": 1}))
|
|
requests.post = mock.Mock(return_value=post_return)
|
|
requests.put = mock.Mock(return_value=put_return)
|
|
result = self.conn._create_session()
|
|
expectedhdr = copy.deepcopy(self.conn.defhdrs)
|
|
expectedhdr["Cookie"] = 'session=' + self.fakeSessionID
|
|
assert result == expectedhdr
|
|
|
|
def test_create_session_auth_fail(self):
|
|
expected_text = json.dumps({"id": self.fakeSessionID})
|
|
post_return = FIOFakeResponse(code=201,
|
|
text=expected_text)
|
|
put_return = FIOFakeResponse(code=201,
|
|
text=json.dumps({"Status": (-1)}))
|
|
requests.post = mock.Mock(return_value=post_return)
|
|
requests.put = mock.Mock(return_value=put_return)
|
|
requests.delete = mock.Mock()
|
|
self.assertRaises(exception.VolumeDriverException,
|
|
self.conn._create_session,)
|
|
|
|
def test_delete_session_sucess(self):
|
|
requests.delete = mock.Mock(return_value=True)
|
|
hdrs = copy.deepcopy(self.conn.defhdrs)
|
|
hdrs["Cookie"] = 'session=' + self.fakeSessionID
|
|
self.conn._delete_session(hdrs)
|
|
expected = [mock.call(url=("https://" +
|
|
self.configuration.san_ip +
|
|
"/AUTH/SESSION/" + self.fakeSessionID),
|
|
headers=self.conn.defhdrs,
|
|
verify=True), ]
|
|
requests.delete.assert_has_calls(expected)
|
|
|
|
def test_put_sucess(self):
|
|
put_return = FIOFakeResponse(code=201,
|
|
text=json.dumps({"Status": 1}))
|
|
requests.put = mock.Mock(return_value=put_return)
|
|
expectedhdr = copy.deepcopy(self.conn.defhdrs)
|
|
expectedhdr["Cookie"] = 'session=' + self.fakeSessionID
|
|
self.conn._create_session = mock.Mock(return_value=expectedhdr)
|
|
self.conn._delete_session = mock.Mock()
|
|
testurl = '/test/url/'
|
|
testcontent = {'testdict': 'testvalue'}
|
|
self.conn.put(testurl, testcontent)
|
|
self.conn.post(testurl, testcontent)
|
|
expected = [mock.call(), ]
|
|
self.conn._create_session.assert_has_calls(expected)
|
|
expected = [mock.call(expectedhdr), ]
|
|
self.conn._delete_session.assert_has_calls(expected)
|
|
expected = [mock.call(url=self.conn._complete_uri(testurl),
|
|
data=json.dumps(testcontent, sort_keys=True),
|
|
headers=expectedhdr, verify=True), ]
|
|
requests.put.assert_has_calls(expected)
|
|
|
|
def test_post_sucess(self):
|
|
expected_text = json.dumps({"id": self.fakeSessionID})
|
|
post_return = FIOFakeResponse(code=201,
|
|
text=expected_text)
|
|
requests.post = mock.Mock(return_value=post_return)
|
|
expectedhdr = copy.deepcopy(self.conn.defhdrs)
|
|
expectedhdr["Cookie"] = 'session=' + self.fakeSessionID
|
|
self.conn._create_session = mock.Mock(return_value=expectedhdr)
|
|
self.conn._delete_session = mock.Mock()
|
|
testurl = '/test/url/'
|
|
testcontent = {'testdict': 'testvalue'}
|
|
self.conn.post(testurl, testcontent)
|
|
expected = [mock.call(), ]
|
|
self.conn._create_session.assert_has_calls(expected)
|
|
expected = [mock.call(expectedhdr), ]
|
|
self.conn._delete_session.assert_has_calls(expected)
|
|
expected = [mock.call(url=self.conn._complete_uri(testurl),
|
|
data=json.dumps(testcontent, sort_keys=True),
|
|
headers=expectedhdr, verify=True), ]
|
|
requests.post.assert_has_calls(expected)
|
|
|
|
def test_delete_sucess(self):
|
|
del_return = FIOFakeResponse(code=201, text=json.dumps({}))
|
|
requests.delete = mock.Mock(return_value=del_return)
|
|
expectedhdr = copy.deepcopy(self.conn.defhdrs)
|
|
expectedhdr["Cookie"] = 'session=' + self.fakeSessionID
|
|
self.conn._create_session = mock.Mock(return_value=expectedhdr)
|
|
self.conn._delete_session = mock.Mock()
|
|
testurl = '/test/url/'
|
|
self.conn.delete(testurl,)
|
|
expected = [mock.call(), ]
|
|
self.conn._create_session.assert_has_calls(expected)
|
|
expected = [mock.call(expectedhdr), ]
|
|
self.conn._delete_session.assert_has_calls(expected)
|
|
expected = [mock.call(url=self.conn._complete_uri(testurl),
|
|
headers=expectedhdr, verify=True), ]
|
|
requests.delete.assert_has_calls(expected)
|
|
|
|
def test_get_sucess(self):
|
|
get_return = FIOFakeResponse(code=200,
|
|
text=json.dumps(basic_acl_group_response))
|
|
expectedhdr = copy.deepcopy(self.conn.defhdrs)
|
|
expectedhdr["Cookie"] = 'session=' + self.fakeSessionID
|
|
self.conn._create_session = mock.Mock(return_value=expectedhdr)
|
|
self.conn._delete_session = mock.Mock()
|
|
requests.get = mock.Mock(return_value=get_return)
|
|
testurl = '/test/url/'
|
|
result = self.conn.get(testurl,)
|
|
expected = [mock.call(), ]
|
|
self.conn._create_session.assert_has_calls(expected)
|
|
expected = [mock.call(expectedhdr), ]
|
|
self.conn._delete_session.assert_has_calls(expected)
|
|
expected = [mock.call(url=self.conn._complete_uri(testurl),
|
|
headers=expectedhdr, verify=True), ]
|
|
requests.get.assert_has_calls(expected)
|
|
assert result == basic_acl_group_response
|
|
|
|
def test_get_bad_json_once(self):
|
|
expectedhdr = copy.deepcopy(self.conn.defhdrs)
|
|
expectedhdr["Cookie"] = 'session=' + self.fakeSessionID
|
|
self.conn._create_session = mock.Mock(return_value=expectedhdr)
|
|
self.conn._delete_session = mock.Mock()
|
|
expected_text = json.dumps(basic_acl_group_response)
|
|
jsonErrEffect = [FIOFakeResponse(code=200,
|
|
text='{"badjson":"bad",,}'),
|
|
FIOFakeResponse(code=200,
|
|
text=expected_text)]
|
|
requests.get = mock.Mock(side_effect=jsonErrEffect)
|
|
testurl = '/test/url/'
|
|
result = self.conn.get(testurl,)
|
|
expected = [mock.call(), ]
|
|
self.conn._create_session.assert_has_calls(expected)
|
|
expected = [mock.call(expectedhdr), ]
|
|
self.conn._delete_session.assert_has_calls(expected)
|
|
expected = [mock.call(url=self.conn._complete_uri(testurl),
|
|
headers=expectedhdr, verify=True), ]
|
|
requests.get.assert_has_calls(expected)
|
|
assert result == basic_acl_group_response
|
|
|
|
def test_get_bad_json_retry_expire(self):
|
|
get_return = FIOFakeResponse(code=200, text='{"badjson":"bad",,}')
|
|
expectedhdr = copy.deepcopy(self.conn.defhdrs)
|
|
expectedhdr["Cookie"] = 'session=' + self.fakeSessionID
|
|
self.conn._create_session = mock.Mock(return_value=expectedhdr)
|
|
self.conn._delete_session = mock.Mock()
|
|
requests.get = mock.Mock(return_value=get_return)
|
|
testurl = '/test/url/'
|
|
self.assertRaises(exception.VolumeDriverException,
|
|
self.conn.get, testurl)
|
|
expected = [mock.call(), ]
|
|
self.conn._create_session.assert_has_calls(expected)
|
|
expected = [mock.call(expectedhdr), ]
|
|
self.conn._delete_session.assert_has_calls(expected)
|
|
expected = [mock.call(url=self.conn._complete_uri(testurl),
|
|
headers=expectedhdr, verify=True),
|
|
mock.call(url=self.conn._complete_uri(testurl),
|
|
headers=expectedhdr, verify=True),
|
|
mock.call(url=self.conn._complete_uri(testurl),
|
|
headers=expectedhdr, verify=True), ]
|
|
requests.get.assert_has_calls(expected)
|
|
|
|
def test_get_failed_http_response(self):
|
|
get_return = FIOFakeResponse(code=404,
|
|
text=json.dumps(basic_acl_group_response))
|
|
expectedhdr = copy.deepcopy(self.conn.defhdrs)
|
|
expectedhdr["Cookie"] = 'session=' + self.fakeSessionID
|
|
self.conn._create_session = mock.Mock(return_value=expectedhdr)
|
|
self.conn._delete_session = mock.Mock()
|
|
requests.get = mock.Mock(return_value=get_return)
|
|
testurl = '/test/url/'
|
|
self.assertRaises(requests.exceptions.HTTPError,
|
|
self.conn.get, testurl)
|
|
expected = [mock.call(), ]
|
|
self.conn._create_session.assert_has_calls(expected)
|
|
expected = [mock.call(expectedhdr), ]
|
|
self.conn._delete_session.assert_has_calls(expected)
|
|
expected = [mock.call(url=self.conn._complete_uri(testurl),
|
|
headers=expectedhdr, verify=True), ]
|
|
requests.get.assert_has_calls(expected)
|
|
|
|
|
|
@mock.patch('cinder.volume.drivers.fusionio.ioControl.FIOconnection',
|
|
autospec=True)
|
|
class FIOioControlTestCases(test.TestCase):
|
|
|
|
VERSION = '1.0.0'
|
|
policyTable = {'Policy 4': '00000000-00000000-0000-000000000002',
|
|
'Policy 5': '00000000-00000000-0000-000000000000',
|
|
'Policy 2': '00000000-00000000-0000-000000000008',
|
|
'Policy 3': '00000000-00000000-0000-000000000004',
|
|
'Policy 1': '00000000-00000000-0000-000000000010'}
|
|
|
|
def setUp(self):
|
|
super(FIOioControlTestCases, self).setUp()
|
|
self.configuration = create_configuration()
|
|
self.ctxt = context.get_admin_context()
|
|
self.drv = ioControl.FIOioControlDriver(
|
|
configuration=self.configuration)
|
|
self.drv.fio_qos_dict = self.policyTable
|
|
|
|
def test_do_setup_sucess(self, connmock):
|
|
# erase policy table, then make sure drv.do_setup builds it
|
|
self.drv.fio_qos_dict = {}
|
|
instance = connmock.return_value
|
|
instance.get.return_value = basic_policy_response
|
|
self.drv.do_setup(context="")
|
|
self.assertEqual(self.policyTable, self.drv.fio_qos_dict,
|
|
"wrong policy table built")
|
|
|
|
def test_create_volume_simple_success_poolA(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
bPoolResponse = copy.deepcopy(basic_pools_response)
|
|
bPoolResponse[1]['ExportedVolumeMB'] = 5009
|
|
self.drv.conn.get.return_value = bPoolResponse
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
self.drv.create_volume(testvol)
|
|
cmd = {"Size": int(testvol['size']) * units.Gi,
|
|
"PolicyUUID": '00000000-00000000-0000-000000000000',
|
|
"PoolUUID": "FakePoolA_id",
|
|
"Name": testvol['id'], }
|
|
expected = [mock.call.get('TierStore/Pools/by-id/'),
|
|
mock.call.post('TierStore/Volumes/by-id/', cmd)]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_create_volume_simple_success_poolB(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
bPoolResponse = copy.deepcopy(basic_pools_response)
|
|
bPoolResponse[0]['ExportedVolumeMB'] = 5009
|
|
self.drv.conn.get.return_value = bPoolResponse
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
self.drv.create_volume(testvol)
|
|
cmd = {"Size": int(testvol['size']) * units.Gi,
|
|
"PolicyUUID": '00000000-00000000-0000-000000000000',
|
|
"PoolUUID": "FakePoolB_id",
|
|
"Name": testvol['id'], }
|
|
expected = [mock.call.get('TierStore/Pools/by-id/'),
|
|
mock.call.post('TierStore/Volumes/by-id/', cmd)]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_delete_volume_sucess(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
self.drv.conn.get.return_value = basic_vol_response
|
|
self.drv.delete_volume(testvol)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.delete('TierStore/Volumes/by-id/FakeBasicVolID')]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_create_snapshot_sucess(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
snapshot = {'volume_id': 'cinderVolumeID',
|
|
'id': 'a720b3c0-d1f0-11e1-9b23-1234500cab39', }
|
|
self.drv.conn.get.return_value = basic_vol_response
|
|
cmd = {"VolumeUUID": "FakeBasicVolID",
|
|
"Name": snapshot['id'], }
|
|
self.drv.create_snapshot(snapshot)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.post('TierStore/Snapshots/by-id/', cmd), ]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_delete_snapshot_sucess(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
snapshot = {'volume_id': '1dead3c0-d1f0-beef-9b23-1274500cab58',
|
|
'id': 'cinderSnapshotID'}
|
|
self.drv.conn.get.return_value = basic_snapshot_response
|
|
self.drv.delete_snapshot(snapshot)
|
|
expected = [mock.call.get('TierStore/Snapshots/by-id/'),
|
|
mock.call.delete(
|
|
('TierStore/Snapshots/by-id/' +
|
|
'407115424bb9539c')), ]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_create_volume_from_snapshot_simple_sucess(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
snapshot = {'volume_id': testvol['id'],
|
|
'id': 'cinderSnapshotID'}
|
|
self.drv.conn.get.return_value = basic_snapshot_response
|
|
cmd = {"ParentLayerId": "407115424bb9539c",
|
|
"Name": testvol['id'],
|
|
"PolicyUUID": '00000000-00000000-0000-000000000000'}
|
|
self.drv.create_volume_from_snapshot(testvol, snapshot)
|
|
expected = [mock.call.get('TierStore/Snapshots/by-id/'),
|
|
mock.call.put(
|
|
'TierStore/Snapshots/functions/CloneSnapshot', cmd), ]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_initialize_connection_no_usable_Networks_fail(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
connector = {'initiator': 'fake:01'}
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow(),
|
|
'provider_auth': {}}
|
|
cmd = {"GroupName": "fake:01",
|
|
"InitiatorList": ["fake:01"]}
|
|
cmd2 = {"AclGroupList": ["3"], }
|
|
netResponse = copy.deepcopy(basic_net_response)
|
|
netResponse[4]['OperationalState'] = "down"
|
|
get_effect = [basic_vol_response,
|
|
basic_acl_group_response,
|
|
basic_vol_response,
|
|
netResponse, ]
|
|
self.drv.conn.get.side_effect = get_effect
|
|
self.assertRaises(exception.VolumeDriverException,
|
|
self.drv.initialize_connection, testvol,
|
|
connector)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.post('TierStore/ACLGroup/by-id/', cmd),
|
|
mock.call.get('TierStore/ACLGroup/by-id/'),
|
|
mock.call.put('TierStore/Volumes/by-id/FakeBasicVolID',
|
|
cmd2),
|
|
mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.get('System/Network/by-id/'), ]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_initialize_connection_simple_sucess(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
connector = {'initiator': 'fake:01'}
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow(),
|
|
'provider_auth': {}}
|
|
cmd = {"GroupName": "fake:01",
|
|
"InitiatorList": ["fake:01"]}
|
|
cmd2 = {"AclGroupList": ["3"], }
|
|
netResponse = copy.deepcopy(basic_net_response)
|
|
netResponse[2]['OperationalState'] = "up"
|
|
get_effect = [basic_vol_response,
|
|
basic_acl_group_response,
|
|
basic_vol_response,
|
|
netResponse, ]
|
|
self.drv.conn.get.side_effect = get_effect
|
|
result = self.drv.initialize_connection(testvol, connector)
|
|
expected = {'driver_volume_type': 'iscsi',
|
|
'data': {'target_lun': 0,
|
|
'target_portal': u'10.10.2.84:3260',
|
|
'target_iqn': (
|
|
'iqn.2010-11.com.ngs:Volume:FakeBasicVolID'),
|
|
'target_discovered': False,
|
|
'volume_id': 'cinderVolumeID'}}
|
|
self.assertEqual(result, expected, "wrong result from init connection")
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.post('TierStore/ACLGroup/by-id/', cmd),
|
|
mock.call.get('TierStore/ACLGroup/by-id/'),
|
|
mock.call.put('TierStore/Volumes/by-id/FakeBasicVolID',
|
|
cmd2),
|
|
mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.get('System/Network/by-id/'), ]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_terminate_connection_single_delete_sucess(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
connector = {'initiator': 'fake:01'}
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow(),
|
|
'provider_auth': {}}
|
|
cmd = {"AclGroupList": ["1"], }
|
|
get_effect = [basic_vol_response,
|
|
basic_acl_group_response,
|
|
basic_acl_group_response,
|
|
basic_vol_response, ]
|
|
self.drv.conn.get.side_effect = get_effect
|
|
self.drv.terminate_connection(testvol, connector)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.get('TierStore/ACLGroup/by-id/'),
|
|
mock.call.put('TierStore/Volumes/by-id/FakeBasicVolID',
|
|
cmd),
|
|
mock.call.get('TierStore/ACLGroup/by-id/'),
|
|
mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.delete('TierStore/ACLGroup/by-id/3')]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_terminate_connection_multiple_no_delete(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
connector = {'initiator': 'fake:01'}
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow(),
|
|
'provider_auth': {}}
|
|
cmd = {"AclGroupList": ["1"], }
|
|
return2vol = copy.deepcopy(basic_vol_response)
|
|
return2vol.append(copy.deepcopy(basic_vol_response[0]))
|
|
return2vol[1]['AclGroupList'] = ["3"]
|
|
get_effect = [basic_vol_response,
|
|
basic_acl_group_response,
|
|
basic_acl_group_response,
|
|
return2vol, ]
|
|
self.drv.conn.get.side_effect = get_effect
|
|
self.drv.terminate_connection(testvol, connector)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.get('TierStore/ACLGroup/by-id/'),
|
|
mock.call.put('TierStore/Volumes/by-id/FakeBasicVolID',
|
|
cmd),
|
|
mock.call.get('TierStore/ACLGroup/by-id/'),
|
|
mock.call.get('TierStore/Volumes/by-id/')]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_terminate_connection_multiple_delete(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
connector = {'initiator': 'fake:01'}
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow(),
|
|
'provider_auth': {}}
|
|
cmd = {"AclGroupList": ["1"], }
|
|
return2vol = copy.deepcopy(basic_vol_response)
|
|
return2vol.append(copy.deepcopy(basic_vol_response[0]))
|
|
get_effect = [basic_vol_response,
|
|
basic_acl_group_response,
|
|
basic_acl_group_response,
|
|
return2vol, ]
|
|
self.drv.conn.get.side_effect = get_effect
|
|
self.drv.terminate_connection(testvol, connector)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.get('TierStore/ACLGroup/by-id/'),
|
|
mock.call.put('TierStore/Volumes/by-id/FakeBasicVolID',
|
|
cmd),
|
|
mock.call.get('TierStore/ACLGroup/by-id/'),
|
|
mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.delete('TierStore/ACLGroup/by-id/3')]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_create_cloned_volume_simple_sucess(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
srcvol = {'id': 'cinderVolumeID'}
|
|
dstvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID-dst',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
cmd = {'VolumeUUID': 'FakeBasicVolID',
|
|
'Name': 'mockedFakeUUID'}
|
|
# also mock _getSnapshotByName because of the random snapshotname.
|
|
self.drv._get_snapshot_by_name = mock.MagicMock()
|
|
self.drv._get_snapshot_by_name.return_value = \
|
|
basic_snapshot_response[0]
|
|
cmd2 = {"ParentLayerId": "407115424bb9539c",
|
|
"Name": "cinderVolumeID-dst",
|
|
"PolicyUUID": "00000000-00000000-0000-000000000000"}
|
|
get_effect = [basic_vol_response, ]
|
|
self.drv.conn.get.side_effect = get_effect
|
|
|
|
with mock.patch('cinder.volume.drivers.fusionio.ioControl.uuid',
|
|
autospec=True) as uuidmock:
|
|
uuidmock.uuid4.return_value = cmd['Name']
|
|
self.drv.create_cloned_volume(dstvol, srcvol)
|
|
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.post('TierStore/Snapshots/by-id/', cmd),
|
|
mock.call.put(('TierStore/Snapshots/functions/' +
|
|
'CloneSnapshot'), cmd2), ]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_create_cloned_volume_snapfails(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
# this operation is a 2 part process, snap, then clone.
|
|
# This tests for the snap failing
|
|
srcvol = {'id': 'cinderVolumeID'}
|
|
dstvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID-dst',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
cmd = {'VolumeUUID': 'FakeBasicVolID',
|
|
'Name': 'mockedFakeUUID'}
|
|
get_effect = [basic_vol_response, ]
|
|
self.drv.conn.get.side_effect = get_effect
|
|
self.drv.conn.post.side_effect = requests.exceptions.HTTPError
|
|
with mock.patch('cinder.volume.drivers.fusionio.ioControl.uuid',
|
|
autospec=True) as uuidmock:
|
|
uuidmock.uuid4.return_value = cmd['Name']
|
|
self.assertRaises(requests.exceptions.HTTPError,
|
|
self.drv.create_cloned_volume,
|
|
dstvol, srcvol)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.post('TierStore/Snapshots/by-id/', cmd), ]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_create_cloned_volume_clonefails(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
srcvol = {'id': 'cinderVolumeID'}
|
|
dstvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID-dst',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
get_effect = [basic_vol_response,
|
|
basic_snapshot_response[0], ]
|
|
self.drv.conn.get.side_effect = get_effect
|
|
# also mock _getSnapshotByName because of the random snapshotname.
|
|
self.drv._get_snapshot_by_name = mock.MagicMock()
|
|
self.drv._get_snapshot_by_name.return_value = \
|
|
basic_snapshot_response[0]
|
|
cmd = {'VolumeUUID': 'FakeBasicVolID',
|
|
'Name': 'mockedFakeUUID'}
|
|
cmd2 = {"ParentLayerId": "407115424bb9539c",
|
|
"Name": "cinderVolumeID-dst",
|
|
"PolicyUUID": "00000000-00000000-0000-000000000000"}
|
|
self.drv.conn.put.side_effect = requests.exceptions.HTTPError
|
|
with mock.patch('cinder.volume.drivers.fusionio.ioControl.uuid',
|
|
autospec=True) as uuidmock:
|
|
uuidmock.uuid4.return_value = cmd['Name']
|
|
self.assertRaises(requests.exceptions.HTTPError,
|
|
self.drv.create_cloned_volume,
|
|
dstvol, srcvol)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.post('TierStore/Snapshots/by-id/', cmd),
|
|
mock.call.put(('TierStore/Snapshots/functions/' +
|
|
'CloneSnapshot'), cmd2),
|
|
mock.call.delete(('TierStore/Snapshots/by-id/' +
|
|
cmd2['ParentLayerId'])), ]
|
|
self.drv.conn.assert_has_calls(expected)
|
|
|
|
def test_get_volume_stats_simple_sucess(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
self.drv.conn.get.side_effect = [basic_pools_response, ]
|
|
result = self.drv.get_volume_stats(refresh=True)
|
|
self.assertEqual(basic_pools_response[0]['PagingTotalMB'] +
|
|
basic_pools_response[1]['PagingTotalMB'],
|
|
result['total_capacity_gb'],
|
|
"capacity calc wrong")
|
|
self.assertEqual(self.VERSION, result['driver_version'],
|
|
"Driver/Test version Mismatch")
|
|
|
|
def test_create_volume_QoS_by_presets(self, connmock):
|
|
preset_qos = models.VolumeMetadata(key='fio-qos', value='Policy 2')
|
|
testvol = {'project_id': 'testprjid',
|
|
'name': 'testvol',
|
|
'size': 1,
|
|
'id': 'a720b3c0-d1f0-11e1-9b23-0800200c9a66',
|
|
'volume_metadata': [preset_qos],
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
|
|
expected_qos_result = '00000000-00000000-0000-000000000008' # Policy 2
|
|
qos = self.drv._set_qos_presets(testvol)
|
|
self.assertEqual(qos, expected_qos_result)
|
|
|
|
def test_create_volume_Qos_by_volumeType_QoSspec(self, connmock):
|
|
qos_ref = qos_specs.create(self.ctxt,
|
|
'qos-specs-1', {'fio-qos': 'Policy 2'})
|
|
type_ref = volume_types.create(self.ctxt,
|
|
"type1",
|
|
{"volume_backend_name": "fio-ioControl",
|
|
"qos:fio-qos": "Policy 4"}
|
|
)
|
|
qos_specs.associate_qos_with_type(self.ctxt,
|
|
qos_ref['id'],
|
|
type_ref['id'])
|
|
expected_qos_result = '00000000-00000000-0000-000000000008' # Policy 2
|
|
qos = self.drv._set_qos_by_volume_type(type_ref['id'])
|
|
self.assertEqual(qos, expected_qos_result)
|
|
|
|
def test_create_volume_Qos_by_volumeType_extraSpec(self, connmock):
|
|
type_ref = volume_types.create(self.ctxt,
|
|
"type1",
|
|
{"volume_backend_name": "fio-ioControl",
|
|
"qos:fio-qos": "Policy 4"}
|
|
)
|
|
expected_qos_result = '00000000-00000000-0000-000000000002' # Policy 4
|
|
qos = self.drv._set_qos_by_volume_type(type_ref['id'])
|
|
self.assertEqual(qos, expected_qos_result)
|
|
|
|
def test_extend_volume_simple_success(self, connmock):
|
|
self.drv.conn = connmock.return_value
|
|
testvol = {'project_id': 'testproject',
|
|
'name': 'cinderVolumeName',
|
|
'size': 1,
|
|
'id': 'cinderVolumeID',
|
|
'volume_type_id': None,
|
|
'created_at': timeutils.utcnow()}
|
|
new_size = 10
|
|
cmd = {"Size": int(new_size) * units.Gi}
|
|
self.drv.conn.get.side_effect = [basic_vol_response, ]
|
|
self.drv.extend_volume(testvol, new_size)
|
|
expected = [mock.call.get('TierStore/Volumes/by-id/'),
|
|
mock.call.put('TierStore/Volumes/by-id/FakeBasicVolID',
|
|
cmd)]
|
|
self.drv.conn.assert_has_calls(expected)
|