+++ /dev/null
-# 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_log import log as logging
-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 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)
+++ /dev/null
-# 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.
-
-"""
-Fusion-io Driver for the ioControl Hybrid storage subsystem
-"""
-
-import copy
-import hashlib
-import json
-import random
-import uuid
-
-from oslo_config import cfg
-from oslo_log import log as logging
-from oslo_utils import units
-import requests
-
-from cinder import exception
-from cinder.i18n import _, _LW
-from cinder.openstack.common import loopingcall
-from cinder.volume.drivers.san import san
-from cinder.volume import qos_specs
-from cinder.volume import volume_types
-
-LOG = logging.getLogger(__name__)
-
-fusionio_iocontrol_opts = [
- cfg.IntOpt('fusionio_iocontrol_targetdelay',
- default=5,
- help='amount of time wait for iSCSI target to come online'),
- cfg.IntOpt('fusionio_iocontrol_retry',
- default=3,
- help='number of retries for GET operations'),
- cfg.BoolOpt('fusionio_iocontrol_verify_cert',
- default=True,
- help='verify the array certificate on each transaction'), ]
-
-CONF = cfg.CONF
-CONF.register_opts(fusionio_iocontrol_opts)
-
-
-class FIOconnection(object):
- """Connection class for connection to ioControl array."""
-
- APIVERSION = '1.1'
-
- def _complete_uri(self, suburi=None, ver='1', loc='en'):
- uri = "https://" + self.array_addr + "/API/"
- if ver is not None:
- uri = uri + ver + "/"
- if loc is not None:
- uri = uri + loc + "/"
- if suburi is not None:
- uri = uri + suburi
- return uri
-
- def __init__(self, array_addr, array_login, array_passwd, retry, verify):
- self.client = "client=openstack"
- self.defhdrs = {"User-Agent": "OpenStack-agent",
- "Content-Type": "application/json"}
- self.array_addr = array_addr
- self.array_login = array_login
- self.hashpass = hashlib.md5()
- self.hashpass.update(array_passwd)
- self.login_content = ("username=" + array_login + "&hash=" +
- self.hashpass.hexdigest())
- self.retry = retry
- self.verify = verify
- # check the version of the API on the array. We only support 1.1
- # for now.
- resp = requests.get(url=("https://" + array_addr + "/AUTH/Version"),
- headers=self.defhdrs, verify=self.verify)
- resp.raise_for_status()
- dictresp = resp.json()
- if dictresp["Version"] != self.APIVERSION:
- msg = _("FIO ioControl API version not supported")
- raise exception.VolumeDriverException(message=msg)
- LOG.debug('FIO Connection initialized to %s' % array_addr)
-
- def _create_session(self):
- # get the session id
- res = requests.post(url=("https://" + self.array_addr +
- "/AUTH/SESSION"),
- data=self.client,
- headers=self.defhdrs,
- verify=self.verify)
- res.raise_for_status()
- result = res.json()
- session_key = result["id"]
- hdrs = copy.deepcopy(self.defhdrs)
- hdrs["Cookie"] = "session=" + session_key
- # Authenticate the session
- res = requests.put(url=("https://" + self.array_addr +
- "/AUTH/SESSION/" + session_key),
- data=self.login_content,
- headers=self.defhdrs,
- verify=self.verify)
- try:
- res.raise_for_status()
- except requests.exceptions:
- self._delete_session(hdrs)
- raise
- result = res.json()
- if result["Status"] != 1:
- # Authentication error delete the session ID
- self._delete_session(hdrs)
- msg = (_('FIO ioControl Authentication Error: %s') % (result))
- raise exception.VolumeDriverException(message=msg)
- return hdrs
-
- def _delete_session(self, hdrs):
- session = hdrs["Cookie"].split('=')[1]
- requests.delete(url=("https://" + self.array_addr +
- "/AUTH/SESSION/" + session),
- headers=self.defhdrs,
- verify=self.verify)
-
- def get(self, suburl):
- session_hdrs = self._create_session()
- trynum = 0
- try:
- while (trynum < self.retry):
- trynum += 1
- res = requests.get(url=self._complete_uri(suburl),
- headers=session_hdrs,
- verify=self.verify)
- res.raise_for_status()
- # work around a bug whereby bad json is returned by the array
- try:
- jres = res.json()
- break
- except Exception:
- if (trynum == self.retry):
- # this shouldn't happen, but check for it
- msg = (_('FIO ioControl persistent json Error.'))
- raise exception.VolumeDriverException(message=msg)
- pass
- finally:
- # deal with the bad result here
- self._delete_session(session_hdrs)
- return jres
-
- def put(self, suburl, content=None):
- session_hdrs = self._create_session()
- try:
- result = requests.put(url=self._complete_uri(suburl),
- data=json.dumps(content,
- sort_keys=True),
- headers=session_hdrs,
- verify=self.verify)
- result.raise_for_status()
- finally:
- self._delete_session(session_hdrs)
- return
-
- def post(self, suburl, content=None):
- session_hdrs = self._create_session()
- try:
- result = requests.post(url=self._complete_uri(suburl),
- data=json.dumps(content,
- sort_keys=True),
- headers=session_hdrs,
- verify=self.verify)
- result.raise_for_status()
- finally:
- self._delete_session(session_hdrs)
- return
-
- def delete(self, suburl,):
- session_hdrs = self._create_session()
- try:
- result = requests.delete(url=self._complete_uri(suburl),
- headers=session_hdrs,
- verify=self.verify)
- result.raise_for_status()
- finally:
- self._delete_session(session_hdrs)
- return
-
-
-class FIOioControlDriver(san.SanISCSIDriver):
- """Fusion-io ioControl iSCSI volume driver."""
-
- VERSION = '1.0.0'
-
- def __init__(self, *args, **kwargs):
- super(FIOioControlDriver, self).__init__(*args, **kwargs)
- LOG.debug('FIO __init__ w/ %s' % kwargs)
- self.configuration.append_config_values(fusionio_iocontrol_opts)
- self.fio_qos_dict = {}
-
- def _get_volume_by_name(self, name):
- result = self.conn.get("TierStore/Volumes/by-id/")
- vol = [x for x in result
- if x['Name'] == name]
- if len(vol) == 1:
- return vol[0]
- elif len(vol) == 0:
- raise exception.VolumeNotFound(name)
- else:
- msg = (_("FIO _get_volume_by_name Error: %(name)s, %(len)s") %
- {'name': name,
- 'len': len(vol)})
- raise exception.VolumeDriverException(msg)
-
- def _get_acl_by_name(self, name):
- result = self.conn.get("TierStore/ACLGroup/by-id/")
- acl = [x for x in result
- if x['GroupName'] == name]
- if len(acl) == 1:
- return acl[0]
- elif len(acl) == 0:
- return []
- else:
- msg = (_("FIO _get_acl_by_name Error: %(name)s, %(len)s") %
- {'name': name,
- 'len': len(acl), })
- raise exception.VolumeDriverException(message=msg)
-
- def _get_snapshot_by_name(self, name):
- result = self.conn.get("TierStore/Snapshots/by-id/")
- snap = [x for x in result
- if x['Name'] == name]
- if len(snap) == 1:
- return snap[0]
- elif len(snap) == 0:
- raise exception.SnapshotNotFound(name)
- else:
- msg = (_("FIO _get_snapshot_by_name Error: %(name)s, %(len)s") %
- {'name': name,
- 'len': len(snap), })
- raise exception.VolumeDriverException(message=msg)
-
- def _set_qos_presets(self, volume):
- valid_presets = self.fio_qos_dict.keys()
-
- presets = [i.value for i in volume.get('volume_metadata')
- if i.key == 'fio-qos' and i.value in valid_presets]
- if len(presets) > 0:
- if len(presets) > 1:
- LOG.warning(_LW('More than one valid preset was '
- 'detected, using %s') % presets[0])
- return self.fio_qos_dict[presets[0]]
-
- def _set_qos_by_volume_type(self, type_id):
- valid_presets = self.fio_qos_dict.keys()
- volume_type = volume_types.get_volume_type(ctxt=None,
- id=type_id)
- qos_specs_id = volume_type.get('qos_specs_id')
- specs = volume_type.get('extra_specs')
- if qos_specs_id is not None:
- kvs = qos_specs.get_qos_specs(ctxt=None,
- id=qos_specs_id)['specs']
- else:
- kvs = specs
- for key, value in kvs.iteritems():
- if ':' in key:
- fields = key.split(':')
- key = fields[1]
- if 'fio-qos' in key:
- if value in valid_presets:
- return self.fio_qos_dict[value]
-
- def do_setup(self, context):
- LOG.debug('FIO do_setup() called')
- required_flags = ['san_ip',
- 'san_login',
- 'san_password', ]
- for flag in required_flags:
- if not getattr(self.configuration, flag, None):
- raise exception.InvalidInput(reason=_('%s is not set') % flag)
- if not (self.configuration.san_ip and
- self.configuration.san_login and
- self.configuration.san_password):
- raise exception.InvalidInput(
- reason=_('All of '
- 'san_ip '
- 'san_login '
- 'san_password '
- 'must be set'))
- self.conn = FIOconnection(self.configuration.san_ip,
- self.configuration.san_login,
- self.configuration.san_password,
- self.configuration.fusionio_iocontrol_retry,
- (self.configuration.
- fusionio_iocontrol_verify_cert))
- result = self.conn.get("TierStore/Policies/by-id/")
- for x in result:
- self.fio_qos_dict[x['Name']] = x['id']
-
- def check_for_setup_error(self):
- pass
-
- def create_volume(self, volume):
- LOG.debug('FIO create_volume() called: %s' % (volume['id']))
- # Roughly we pick the less full pool.
- # Someday change the default the policy to be configurable
- qos = self.fio_qos_dict['Policy 5']
- result = self.conn.get("TierStore/Pools/by-id/")
- poola = result[0]['PagingTotalMB'] - result[0]['ExportedVolumeMB']
- poolb = result[1]['PagingTotalMB'] - result[1]['ExportedVolumeMB']
- if poola >= poolb:
- pool = result[0]['id']
- else:
- pool = result[1]['id']
- if volume.get('volume_metadata')is not None:
- qos = self._set_qos_presets(volume)
-
- type_id = volume['volume_type_id']
- if type_id is not None:
- qos = self._set_qos_by_volume_type(type_id)
-
- cmd = {"Size": int(volume['size']) * units.Gi,
- "PolicyUUID": qos,
- "PoolUUID": pool,
- "Name": volume['id'], }
- self.conn.post("TierStore/Volumes/by-id/", cmd)
- LOG.debug(('FIO create_vol(%(id)s) on %(pool)s vals %(poola)s '
- '%(poolb)s') %
- {'id': volume['id'],
- 'pool': pool,
- 'poola': poola,
- 'poolb': poolb})
-
- def delete_volume(self, volume):
- LOG.debug('FIO delete_volume() volID %s' % (volume['id']))
- vol = self._get_volume_by_name(volume['id'])
- self.conn.delete("TierStore/Volumes/by-id/" + vol['id'])
-
- def ensure_export(self, context, volume):
- pass
-
- def create_export(self, context, volume):
- pass
-
- def remove_export(self, context, volume):
- pass
-
- def initialize_connection(self, volume, connector):
- LOG.debug('FIO init_connection() w/ %(id)s and %(conn)s' %
- {'id': volume['id'],
- 'conn': connector['initiator']})
- # setup the access group each initiator will go in a unique access
- # group.
- # TODO(ebalduf) implement w/ CHAP
- volumedata = self._get_volume_by_name(volume['id'])
- cmd = {"GroupName": connector['initiator'],
- "InitiatorList": [connector['initiator']]}
- self.conn.post("TierStore/ACLGroup/by-id/", cmd)
-
- acl = self._get_acl_by_name(connector['initiator'])
- if acl is not []:
- cmd = {"AclGroupList": [str(acl['id'])], }
- self.conn.put("TierStore/Volumes/by-id/" + volumedata['id'], cmd)
- else:
- # this should never happen, but check for it in case
- msg = _('FIO: ACL does not exist!')
- raise exception.VolumeDriverException(message=msg)
- # handle the fact that the Application of the ACL to the volume
- # is asynchronous. In the future we'll add a call back to the API
-
- def _wait_routine():
- # unfortunately, the array API at this time doesn't have any
- # way to poll. In the future we will add that ability and
- # this routine is where were will poll for ready.
- if self._looping_count == 0:
- self._looping_count += 1
- else:
- raise loopingcall.LoopingCallDone()
-
- # time.sleep(self.configuration.fusionio_iocontrol_targetdelay)
- self._looping_count = 0
- timer = loopingcall.FixedIntervalLoopingCall(_wait_routine)
- timer.start(
- interval=self.configuration.fusionio_iocontrol_targetdelay).wait()
- volumedata = self._get_volume_by_name(volume['id'])
-
- properties = {}
- properties['target_discovered'] = False
- properties['target_iqn'] = volumedata['IQN']
- properties['target_lun'] = 0
- properties['volume_id'] = volume['id']
-
- result = self.conn.get("System/Network/by-id/")
-
- # probably way too complicated, but pick a random network interface
- # on the controller this LUN is owned by
- networksinfo = [x for x in result
- if x['OperationalState'] == 'up'
- if x['IsManagementPort'] is not True
- if x['IsReplicationPort'] is not True
- if x['ControllerUID'] ==
- volumedata['CurrentOwnerUUID']]
- LOG.debug('NetworkInfo %s' % (networksinfo))
- if len(networksinfo):
- ipaddr = (networksinfo[random.randint(0, len(networksinfo) - 1)]
- ['NetworkAddress'])
- else:
- msg = _('No usable Networks found: %s') % (result)
- raise exception.VolumeDriverException(message=msg)
- properties['target_portal'] = unicode('%s:%s' % (ipaddr, '3260'))
-
- auth = volume['provider_auth']
- if auth:
- (auth_method, auth_username, auth_secret) = auth.split()
-
- properties['auth_method'] = auth_method
- properties['auth_username'] = auth_username
- properties['auth_password'] = auth_secret
-
- LOG.debug('Result from initialize connection: %s' % properties)
- return {
- 'driver_volume_type': 'iscsi',
- 'data': properties,
- }
-
- def create_snapshot(self, snapshot):
- LOG.debug(('FIO create_snapshot() vol ID: %(volID)s snapID '
- '%(snapID)s') %
- {'volID': snapshot['volume_id'],
- 'snapID': snapshot['id']})
- vol = self._get_volume_by_name(snapshot['volume_id'])
- cmd = {"VolumeUUID": vol['id'],
- "Name": snapshot['id'], }
- self.conn.post("TierStore/Snapshots/by-id/", cmd)
-
- def delete_snapshot(self, snapshot):
- LOG.debug('FIO delete_snapshot() SnapID: %s' % (snapshot['id']))
- snap = self._get_snapshot_by_name(snapshot['id'])
- self.conn.delete("TierStore/Snapshots/by-id/" + snap['id'])
-
- def create_volume_from_snapshot(self, volume, snapshot):
- LOG.debug('FIO create_volume_from_snapshot() w/ %s' %
- volume['id'])
-
- qos = self.fio_qos_dict['Policy 5']
- if volume.get('volume_metadata')is not None:
- qos = self._set_qos_presets(volume)
-
- type_id = volume['volume_type_id']
- if type_id is not None:
- qos = self._set_qos_by_volume_type(type_id)
- snap = self._get_snapshot_by_name(snapshot['id'])
- cmd = {"ParentLayerId": snap['id'],
- "Name": volume['id'],
- "PolicyUUID": qos}
- self.conn.put("TierStore/Snapshots/functions/CloneSnapshot", cmd)
-
- def _delete_acl_by_name(self, name):
- aclname = self._get_acl_by_name(name)
- if aclname is []:
- return
- result = self.conn.get("TierStore/Volumes/by-id/")
- inuse = False
- for vol in result:
- for acl in vol['AclGroupList']:
- if int(acl) == aclname['id']:
- inuse = True
- break
- if inuse:
- break
- if not inuse:
- result = self.conn.delete("TierStore/ACLGroup/by-id/" +
- str(aclname['id']))
-
- def terminate_connection(self, volume, connector, **kwargs):
- LOG.debug('FIO terminate_connection() w/ %(id)s %(conn)s ' %
- {'id': volume['id'],
- 'conn': connector['initiator']})
- vol = self._get_volume_by_name(volume['id'])
- acl = self._get_acl_by_name("Deny Access")
- if acl is []:
- msg = _('FIO: ACL does not exist!')
- raise exception.VolumeDriverException(message=msg)
- cmd = {"AclGroupList": [str(acl['id'])], }
- self.conn.put("TierStore/Volumes/by-id/" + vol['id'], cmd)
- self._delete_acl_by_name(connector['initiator'])
-
- def create_cloned_volume(self, volume, src_vref):
- LOG.debug('FIO create_cloned_volume() w/ %(id)s %(src)s' %
- {'id': volume['id'],
- 'src': src_vref})
- qos = self.fio_qos_dict['Policy 5']
- # take a snapshot of the volume (use random UUID for name)
- snapshotname = str(uuid.uuid4())
- vol = self._get_volume_by_name(src_vref['id'])
- cmd = {"VolumeUUID": vol['id'],
- "Name": snapshotname, }
- self.conn.post("TierStore/Snapshots/by-id/", cmd)
-
- # create a volume from the snapshot with the new name.
- # Rollback = Delete the snapshot if needed.
- if volume.get('volume_metadata')is not None:
- qos = self._set_qos_presets(volume)
-
- type_id = volume['volume_type_id']
- if type_id is not None:
- qos = self._set_qos_by_volume_type(type_id)
-
- snap = self._get_snapshot_by_name(snapshotname)
- cmd = {"ParentLayerId": snap['id'],
- "Name": volume['id'],
- "PolicyUUID": qos, }
- try:
- # watch for any issues here, and if there are, clean up the
- # snapshot and re-raise
- self.conn.put("TierStore/Snapshots/functions/CloneSnapshot", cmd)
- except Exception:
- snap = self._get_snapshot_by_name(snapshotname)
- self.conn.delete("TierStore/Snapshots/by-id/" + snap['id'])
- raise
-
- def get_volume_stats(self, refresh=False):
- """Retrieve status info from volume group."""
- LOG.debug("FIO Updating volume status")
- if refresh:
- result = self.conn.get("TierStore/Pools/by-id/")
- data = {}
- backend_name = self.configuration.safe_get('volume_backend_name')
- data["volume_backend_name"] = (backend_name
- or self.__class__.__name__)
- data["vendor_name"] = 'Fusion-io Inc'
- data["driver_version"] = self.VERSION
- data["storage_protocol"] = 'iSCSI'
- data['total_capacity_gb'] = (result[0]['PagingTotalMB'] +
- result[1]['PagingTotalMB'])
- data['free_capacity_gb'] = (max((result[0]['PagingTotalMB'] -
- result[0]['ExportedVolumeMB']),
- (result[1]['PagingTotalMB'] -
- result[1]['ExportedVolumeMB'])))
- data['reserved_percentage'] = 10
- data['QoS_support'] = True
- self._stats = data
-
- LOG.debug('Result from status: %s' % data)
- return self._stats
-
- def extend_volume(self, volume, new_size):
- LOG.debug("FIO extend_volume %(id)s to %(size)s" %
- {'id': volume['id'],
- 'size': new_size})
- cmd = {"Size": int(new_size) * units.Gi}
- vol = self._get_volume_by_name(volume['id'])
- self.conn.put("TierStore/Volumes/by-id/" + vol['id'], cmd)