Remove Huawei FusionStorage Driver
The Huawei FusionStorage driver was marked unsupported during the Train release. Since then the 3rd Party CI hasn't been fixed and there has been no sign of activity from the vendor. As a result the driver is being removed. Change-Id: If859d65c18b9aea467de49611480211bcff1aebd
This commit is contained in:
parent
89d6a5042f
commit
120e3f31ec
@ -89,8 +89,6 @@ from cinder.volume.drivers.dell_emc import xtremio as \
|
||||
cinder_volume_drivers_dell_emc_xtremio
|
||||
from cinder.volume.drivers.fujitsu.eternus_dx import eternus_dx_common as \
|
||||
cinder_volume_drivers_fujitsu_eternus_dx_eternusdxcommon
|
||||
from cinder.volume.drivers.fusionstorage import dsware as \
|
||||
cinder_volume_drivers_fusionstorage_dsware
|
||||
from cinder.volume.drivers.hpe import hpe_3par_common as \
|
||||
cinder_volume_drivers_hpe_hpe3parcommon
|
||||
from cinder.volume.drivers.hpe import hpe_lefthand_iscsi as \
|
||||
@ -254,7 +252,6 @@ def list_opts():
|
||||
cinder_volume_driver.scst_opts,
|
||||
cinder_volume_driver.backup_opts,
|
||||
cinder_volume_driver.image_opts,
|
||||
cinder_volume_drivers_fusionstorage_dsware.volume_opts,
|
||||
cinder_volume_drivers_infortrend_raidcmd_cli_commoncli.
|
||||
infortrend_opts,
|
||||
cinder_volume_drivers_inspur_as13000_as13000driver.
|
||||
|
@ -1,478 +0,0 @@
|
||||
# Copyright (c) 2018 Huawei Technologies Co., Ltd.
|
||||
# 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 ddt
|
||||
import json
|
||||
|
||||
import mock
|
||||
import uuid
|
||||
|
||||
from cinder import exception
|
||||
from cinder import objects
|
||||
from cinder import test
|
||||
from cinder.volume import configuration as config
|
||||
from cinder.volume.drivers.fusionstorage import dsware
|
||||
from cinder.volume.drivers.fusionstorage import fs_client
|
||||
from cinder.volume.drivers.fusionstorage import fs_conf
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
|
||||
class FakeDSWAREDriver(dsware.DSWAREDriver):
|
||||
def __init__(self):
|
||||
self.configuration = config.Configuration(None)
|
||||
self.conf = fs_conf.FusionStorageConf(self.configuration, "cinder@fs")
|
||||
self.client = None
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class TestDSWAREDriver(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestDSWAREDriver, self).setUp()
|
||||
self.fake_driver = FakeDSWAREDriver()
|
||||
self.client = fs_client.RestCommon(None, None, None)
|
||||
|
||||
def tearDown(self):
|
||||
super(TestDSWAREDriver, self).tearDown()
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'login')
|
||||
def test_do_setup(self, mock_login):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
update_mocker = self.mock_object(
|
||||
self.fake_driver.conf, 'update_config_value')
|
||||
self.fake_driver.configuration.san_address = 'https://fake_rest_site'
|
||||
self.fake_driver.configuration.san_user = 'fake_san_user'
|
||||
self.fake_driver.configuration.san_password = 'fake_san_password'
|
||||
|
||||
self.fake_driver.do_setup('context')
|
||||
update_mocker.assert_called_once_with()
|
||||
mock_login.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'query_pool_info')
|
||||
def test_check_for_setup_error(self, mock_query_pool_info):
|
||||
self.fake_driver.configuration.pools_name = ['fake_pool_name']
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
result1 = [{'poolName': 'fake_pool_name'},
|
||||
{'poolName': 'fake_pool_name1'}]
|
||||
result2 = [{'poolName': 'fake_pool_name1'},
|
||||
{'poolName': 'fake_pool_name2'}]
|
||||
|
||||
mock_query_pool_info.return_value = result1
|
||||
retval = self.fake_driver.check_for_setup_error()
|
||||
self.assertIsNone(retval)
|
||||
|
||||
mock_query_pool_info.return_value = result2
|
||||
try:
|
||||
self.fake_driver.check_for_setup_error()
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.InvalidInput, type(e))
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'query_pool_info')
|
||||
def test__update_pool_stats(self, mock_query_pool_info):
|
||||
self.fake_driver.configuration.pools_name = ['fake_pool_name']
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
result = [{'poolName': 'fake_pool_name',
|
||||
'totalCapacity': 2048, 'usedCapacity': 1024},
|
||||
{'poolName': 'fake_pool_name1',
|
||||
'totalCapacity': 2048, 'usedCapacity': 1024}]
|
||||
|
||||
mock_query_pool_info.return_value = result
|
||||
retval = self.fake_driver._update_pool_stats()
|
||||
self.assertDictEqual(
|
||||
{"volume_backend_name": 'FakeDSWAREDriver',
|
||||
"driver_version": "2.0.9",
|
||||
"QoS_support": False,
|
||||
"thin_provisioning_support": False,
|
||||
"vendor_name": "Huawei",
|
||||
"pools":
|
||||
[{"pool_name": 'fake_pool_name', "total_capacity_gb": 2,
|
||||
"free_capacity_gb": 1}]}, retval)
|
||||
mock_query_pool_info.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'keep_alive')
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_update_pool_stats')
|
||||
def test_get_volume_stats(self, mock__update_pool_stats, mock_keep_alive):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
result = {"success"}
|
||||
mock__update_pool_stats.return_value = result
|
||||
retval = self.fake_driver.get_volume_stats()
|
||||
self.assertEqual(result, retval)
|
||||
mock_keep_alive.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'query_volume_by_name')
|
||||
def test__check_volume_exist(self, mock_query_volume_by_name):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
result1 = {'volName': 'fake_name'}
|
||||
result2 = None
|
||||
|
||||
mock_query_volume_by_name.return_value = result1
|
||||
retval = self.fake_driver._check_volume_exist(volume)
|
||||
self.assertEqual(retval, result1)
|
||||
|
||||
mock_query_volume_by_name.return_value = result2
|
||||
retval = self.fake_driver._check_volume_exist(volume)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@mock.patch.object(volume_utils, 'extract_host')
|
||||
@mock.patch.object(fs_client.RestCommon, 'query_pool_info')
|
||||
def test__get_pool_id(self, mock_query_pool_info, mock_extract_host):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(host='host')
|
||||
pool_name1 = 'fake_pool_name1'
|
||||
pool_name2 = 'fake_pool_name2'
|
||||
pool_info = [{'poolName': 'fake_pool_name', 'poolId': 'fake_id'},
|
||||
{'poolName': 'fake_pool_name1', 'poolId': 'fake_id1'}]
|
||||
|
||||
mock_query_pool_info.return_value = pool_info
|
||||
mock_extract_host.return_value = pool_name1
|
||||
retval = self.fake_driver._get_pool_id(volume)
|
||||
self.assertEqual('fake_id1', retval)
|
||||
|
||||
mock_extract_host.return_value = pool_name2
|
||||
try:
|
||||
self.fake_driver._get_pool_id(volume)
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.InvalidInput, type(e))
|
||||
|
||||
def test__get_vol_name(self):
|
||||
volume1 = objects.Volume(_name_id=uuid.uuid4())
|
||||
volume1.update(
|
||||
{"provider_location": json.dumps({"name": "fake_name"})})
|
||||
volume2 = objects.Volume(_name_id=uuid.uuid4())
|
||||
|
||||
retval = self.fake_driver._get_vol_name(volume1)
|
||||
self.assertEqual("fake_name", retval)
|
||||
|
||||
retval = self.fake_driver._get_vol_name(volume2)
|
||||
self.assertEqual(volume2.name, retval)
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'create_volume')
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_get_pool_id')
|
||||
def test_create_volume(self, mock__get_pool_id, mock_create_volume):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4(), size=1)
|
||||
mock__get_pool_id.return_value = 'fake_poolID'
|
||||
mock_create_volume.return_value = {'result': 0}
|
||||
|
||||
retval = self.fake_driver.create_volume(volume)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist')
|
||||
@mock.patch.object(fs_client.RestCommon, 'delete_volume')
|
||||
def test_delete_volume(self, mock_delete_volume, mock__check_volume_exist):
|
||||
result = True
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
mock_delete_volume.return_value = {'result': 0}
|
||||
|
||||
mock__check_volume_exist.return_value = result
|
||||
retval = self.fake_driver.delete_volume(volume)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
mock__check_volume_exist.return_value = False
|
||||
retval = self.fake_driver.delete_volume(volume)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist')
|
||||
@mock.patch.object(fs_client.RestCommon, 'expand_volume')
|
||||
def test_extend_volume(self, mock_expand_volume, mock__check_volume_exist):
|
||||
result1 = True
|
||||
result2 = False
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4(), size=2)
|
||||
mock_expand_volume.return_value = {
|
||||
'volName': 'fake_name', 'size': 'new_size'}
|
||||
|
||||
mock__check_volume_exist.return_value = result1
|
||||
retval = self.fake_driver.extend_volume(volume=volume, new_size=3)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
mock__check_volume_exist.return_value = result2
|
||||
try:
|
||||
self.fake_driver.extend_volume(volume=volume, new_size=3)
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist')
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_snapshot_exist')
|
||||
@mock.patch.object(fs_client.RestCommon, 'create_volume_from_snapshot')
|
||||
def test_create_volume_from_snapshot(
|
||||
self, mock_create_volume_from_snapshot,
|
||||
mock_check_snapshot_exist, mock_check_volume_exist):
|
||||
result1 = True
|
||||
result2 = False
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
snapshot = objects.Snapshot(
|
||||
id=uuid.uuid4(), volume_size=2, volume=volume)
|
||||
|
||||
volume1 = objects.Volume(_name_id=uuid.uuid4(), size=2)
|
||||
volume2 = objects.Volume(_name_id=uuid.uuid4(), size=1)
|
||||
mock_create_volume_from_snapshot.return_value = {'result': 0}
|
||||
|
||||
mock_check_volume_exist.return_value = result2
|
||||
mock_check_snapshot_exist.return_value = result1
|
||||
retval = self.fake_driver.create_volume_from_snapshot(
|
||||
volume1, snapshot)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
mock_check_volume_exist.return_value = result1
|
||||
try:
|
||||
self.fake_driver.create_volume_from_snapshot(volume1, snapshot)
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
mock_check_volume_exist.return_value = result2
|
||||
mock_check_snapshot_exist.return_value = result2
|
||||
try:
|
||||
self.fake_driver.create_volume_from_snapshot(volume1, snapshot)
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
mock_check_volume_exist.return_value = result2
|
||||
mock_check_snapshot_exist.return_value = result1
|
||||
try:
|
||||
self.fake_driver.create_volume_from_snapshot(volume2, snapshot)
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist')
|
||||
@mock.patch.object(fs_client.RestCommon, 'create_volume_from_volume')
|
||||
def test_cloned_volume(
|
||||
self, mock_create_volume_from_volume, mock__check_volume_exist):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4(), size=1)
|
||||
src_volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
result1 = True
|
||||
result2 = False
|
||||
|
||||
mock__check_volume_exist.return_value = result1
|
||||
retval = self.fake_driver.create_cloned_volume(volume, src_volume)
|
||||
self.assertIsNone(retval)
|
||||
mock_create_volume_from_volume.assert_called_once_with(
|
||||
vol_name=volume.name, vol_size=volume.size * 1024,
|
||||
src_vol_name=src_volume.name)
|
||||
|
||||
mock__check_volume_exist.return_value = result2
|
||||
try:
|
||||
self.fake_driver.create_cloned_volume(volume, src_volume)
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
def test__get_snapshot_name(self):
|
||||
snapshot1 = objects.Snapshot(id=uuid.uuid4())
|
||||
snapshot1.update(
|
||||
{"provider_location": json.dumps({"name": "fake_name"})})
|
||||
snapshot2 = objects.Snapshot(id=uuid.uuid4())
|
||||
|
||||
retval = self.fake_driver._get_snapshot_name(snapshot1)
|
||||
self.assertEqual("fake_name", retval)
|
||||
|
||||
retval = self.fake_driver._get_snapshot_name(snapshot2)
|
||||
self.assertEqual(snapshot2.name, retval)
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'query_snapshot_by_name')
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_get_pool_id')
|
||||
def test__check_snapshot_exist(
|
||||
self, mock_get_pool_id, mock_query_snapshot_by_name):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
snapshot = objects.Snapshot(id=uuid.uuid4())
|
||||
result1 = {'name': 'fake_name', 'totalNum': 1}
|
||||
result2 = {'name': 'fake_name', 'totalNum': 0}
|
||||
mock_get_pool_id.return_value = "fake_pool_id"
|
||||
|
||||
mock_query_snapshot_by_name.return_value = result1
|
||||
retval = self.fake_driver._check_snapshot_exist(volume, snapshot)
|
||||
self.assertEqual({'name': 'fake_name', 'totalNum': 1}, retval)
|
||||
|
||||
mock_query_snapshot_by_name.return_value = result2
|
||||
retval = self.fake_driver._check_snapshot_exist(volume, snapshot)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'create_snapshot')
|
||||
def test_create_snapshot(self, mock_create_snapshot):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
snapshot = objects.Snapshot(id=uuid.uuid4(),
|
||||
volume_id=uuid.uuid4(), volume=volume)
|
||||
|
||||
retval = self.fake_driver.create_snapshot(snapshot)
|
||||
self.assertIsNone(retval)
|
||||
mock_create_snapshot.assert_called_once_with(
|
||||
snapshot_name=snapshot.name, vol_name=volume.name)
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_snapshot_exist')
|
||||
@mock.patch.object(fs_client.RestCommon, 'delete_snapshot')
|
||||
def test_delete_snapshot(self, mock_delete_snapshot,
|
||||
mock_check_snapshot_exist):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(id=uuid.uuid4())
|
||||
snapshot = objects.Snapshot(id=uuid.uuid4(), volume=volume)
|
||||
result = True
|
||||
mock_delete_snapshot.return_valume = {'result': 0}
|
||||
|
||||
mock_check_snapshot_exist.return_value = result
|
||||
retval = self.fake_driver.delete_snapshot(snapshot)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
mock_check_snapshot_exist.return_value = False
|
||||
retval = self.fake_driver.delete_snapshot(snapshot)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
def test__get_manager_ip(self):
|
||||
context = {'host': 'host1'}
|
||||
host1 = {'host1': '1.1.1.1'}
|
||||
host2 = {'host2': '1.1.1.1'}
|
||||
|
||||
self.fake_driver.configuration.manager_ips = host1
|
||||
retval = self.fake_driver._get_manager_ip(context)
|
||||
self.assertEqual('1.1.1.1', retval)
|
||||
|
||||
self.fake_driver.configuration.manager_ips = host2
|
||||
try:
|
||||
self.fake_driver._get_manager_ip(context)
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist')
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_get_manager_ip')
|
||||
@mock.patch.object(fs_client.RestCommon, 'attach_volume')
|
||||
def test__attach_volume(self, mock_attach_volume,
|
||||
mock__get_manager_ip, mock__check_volume_exist):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
attach_result1 = {volume.name: [{'devName': 'fake_path'}]}
|
||||
attach_result2 = {volume.name: [{'devName': ''}]}
|
||||
result1 = True
|
||||
result2 = False
|
||||
mock__get_manager_ip.return_value = 'fake_ip'
|
||||
|
||||
mock__check_volume_exist.return_value = result1
|
||||
mock_attach_volume.return_value = attach_result1
|
||||
retval, vol = self.fake_driver._attach_volume(
|
||||
"context", volume, "properties")
|
||||
self.assertEqual(
|
||||
({'device': {'path': b'fake_path'}}, volume), (retval, vol))
|
||||
mock__get_manager_ip.assert_called_once_with("properties")
|
||||
mock__check_volume_exist.assert_called_once_with(volume)
|
||||
mock_attach_volume.assert_called_once_with(volume.name, 'fake_ip')
|
||||
|
||||
mock__check_volume_exist.return_value = result2
|
||||
try:
|
||||
self.fake_driver._attach_volume("context", volume, "properties")
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
mock__check_volume_exist.return_value = result1
|
||||
mock_attach_volume.return_value = attach_result2
|
||||
try:
|
||||
self.fake_driver._attach_volume("context", volume, "properties")
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist')
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_get_manager_ip')
|
||||
@mock.patch.object(fs_client.RestCommon, 'detach_volume')
|
||||
def test__detach_volume(self, mock_detach_volume,
|
||||
mock__get_manager_ip, mock__check_volume_exist):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
result1 = True
|
||||
result2 = False
|
||||
|
||||
mock__get_manager_ip.return_value = 'fake_ip'
|
||||
mock_detach_volume.return_value = {'result': 0}
|
||||
|
||||
mock__check_volume_exist.return_value = result1
|
||||
retval = self.fake_driver._detach_volume(
|
||||
'context', 'attach_info', volume, 'properties')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
mock__check_volume_exist.return_value = result2
|
||||
retval = self.fake_driver._detach_volume(
|
||||
'context', 'attach_info', volume, 'properties')
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist')
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_get_manager_ip')
|
||||
@mock.patch.object(fs_client.RestCommon, 'attach_volume')
|
||||
@mock.patch.object(fs_client.RestCommon, 'query_volume_by_name')
|
||||
def test_initialize_connection(self, mock_query_volume_by_name,
|
||||
mock_attach_volume,
|
||||
mock__get_manager_ip,
|
||||
mock__check_volume_exist):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
attach_result = {volume.name: [{'devName': 'fake_path'}]}
|
||||
|
||||
result1 = True
|
||||
result2 = False
|
||||
mock__get_manager_ip.return_value = 'fake_ip'
|
||||
mock_query_volume_by_name.return_value = {'wwn': 'fake_wwn',
|
||||
'volName': 'fake_name'}
|
||||
mock_attach_volume.return_value = attach_result
|
||||
|
||||
mock__check_volume_exist.return_value = result1
|
||||
retval = self.fake_driver.initialize_connection(volume, 'connector')
|
||||
self.assertDictEqual(
|
||||
{'driver_volume_type': 'local',
|
||||
'data': {'device_path': '/dev/disk/by-id/wwn-0xfake_wwn'}},
|
||||
retval)
|
||||
|
||||
mock__check_volume_exist.return_value = result2
|
||||
try:
|
||||
self.fake_driver.initialize_connection(volume, 'connector')
|
||||
except Exception as e:
|
||||
self.assertEqual(exception.VolumeBackendAPIException, type(e))
|
||||
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_check_volume_exist')
|
||||
@mock.patch.object(dsware.DSWAREDriver, '_get_manager_ip')
|
||||
@mock.patch.object(fs_client.RestCommon, 'detach_volume')
|
||||
def test_terminate_connection(self, mock_detach_volume,
|
||||
mock__get_manager_ip,
|
||||
mock__check_volume_exist):
|
||||
self.fake_driver.client = fs_client.RestCommon(
|
||||
'https://fake_rest_site', 'user', 'password')
|
||||
volume = objects.Volume(_name_id=uuid.uuid4())
|
||||
result1 = True
|
||||
result2 = False
|
||||
mock__get_manager_ip.return_value = 'fake_ip'
|
||||
|
||||
mock__check_volume_exist.return_value = result1
|
||||
retval = self.fake_driver.terminate_connection(volume, 'connector')
|
||||
self.assertIsNone(retval)
|
||||
mock_detach_volume.assert_called_once_with(volume.name, 'fake_ip')
|
||||
|
||||
mock__check_volume_exist.return_value = result2
|
||||
retval = self.fake_driver.terminate_connection('volume', 'connector')
|
||||
self.assertIsNone(retval)
|
@ -1,272 +0,0 @@
|
||||
# Copyright (c) 2018 Huawei Technologies Co., Ltd
|
||||
# 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 json
|
||||
import mock
|
||||
import requests
|
||||
|
||||
from cinder import test
|
||||
from cinder.tests.unit.volume.drivers.fusionstorage import test_utils
|
||||
from cinder.volume.drivers.fusionstorage import fs_client
|
||||
|
||||
|
||||
class FakeSession(test_utils.FakeBaseSession):
|
||||
method_map = {
|
||||
'get': {
|
||||
'rest/version':
|
||||
{'currentVersion': 'fake_version'},
|
||||
'/storagePool$':
|
||||
{'storagePools': [{'poolName': 'fake_pool_name',
|
||||
'poolId': 'fake_pool_id'}]},
|
||||
r'/storagePool\?poolId=0':
|
||||
{'storagePools': [{'poolName': 'fake_pool_name1',
|
||||
'poolId': 0}]},
|
||||
r'/volume/queryByName\?volName=fake_name':
|
||||
{'errorCode': 0, 'lunDetailInfo':
|
||||
[{'volume_id': 'fake_id',
|
||||
'volume_name': 'fake_name'}]},
|
||||
r'/volume/queryById\?volId=fake_id':
|
||||
{'errorCode': 0, 'lunDetailInfo':
|
||||
[{'volume_id': 'fake_id',
|
||||
'volume_name': 'fake_name'}]},
|
||||
r'/lun/wwn/list\?wwn=fake_wwn':
|
||||
{'errorCode': 0, 'lunDetailInfo':
|
||||
[{'volume_id': 'fake_id',
|
||||
'volume_wwn': 'fake_wwn'}]},
|
||||
},
|
||||
'post': {
|
||||
'/sec/login': {},
|
||||
'/sec/logout': {'res': 'fake_logout'},
|
||||
'/sec/keepAlive': {'res': 'fake_keepAlive'},
|
||||
'/volume/list': {'errorCode': 0, 'volumeList': [
|
||||
{'volName': 'fake_name1', 'volId': 'fake_id1'},
|
||||
{'volName': 'fake_name2', 'volId': 'fake_id2'}]},
|
||||
'/volume/create': {'ID': 'fake_volume_create_id'},
|
||||
'/volume/delete': {'ID': 'fake_volume_delete_id'},
|
||||
'/volume/attach':
|
||||
{'fake_name': [{'errorCode': '0', 'ip': 'fake_ip'}]},
|
||||
'/volume/detach/': {'ID': 'fake_volume_detach_id'},
|
||||
'/volume/expand': {'ID': 'fake_volume_expend_id'},
|
||||
'/volume/snapshot/list':
|
||||
{"snapshotList": [{"snapshot": "fake_name",
|
||||
"size": "fake_size"}]},
|
||||
'/snapshot/list': {'totalNum': 'fake_snapshot_num',
|
||||
'snapshotList':
|
||||
[{'snapName': 'fake_snapName'}]},
|
||||
'/snapshot/create/': {'ID': 'fake_snapshot_create_id'},
|
||||
'/snapshot/delete/': {'ID': 'fake_snapshot_delete_id'},
|
||||
'/snapshot/rollback': {'ID': 'fake_snapshot_delete_id'},
|
||||
'/snapshot/volume/create/': {'ID': 'fake_vol_from_snap_id'},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class TestFsclient(test.TestCase):
|
||||
def setUp(self):
|
||||
super(TestFsclient, self).setUp()
|
||||
self.mock_object(requests, 'Session', FakeSession)
|
||||
self.client = fs_client.RestCommon('https://fake_rest_site',
|
||||
'fake_user',
|
||||
'fake_password')
|
||||
self.client.login()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestFsclient, self).tearDown()
|
||||
|
||||
def test_login(self):
|
||||
self.assertEqual('fake_version',
|
||||
self.client.version)
|
||||
self.assertEqual('fake_token',
|
||||
self.client.session.headers['X-Auth-Token'])
|
||||
|
||||
def test_keep_alive(self):
|
||||
retval = self.client.keep_alive()
|
||||
self.assertIsNone(retval)
|
||||
|
||||
def test_logout(self):
|
||||
self.assertIsNone(self.client.logout())
|
||||
|
||||
def test_query_all_pool_info(self):
|
||||
with mock.patch.object(self.client.session, 'get',
|
||||
wraps=self.client.session.get) as mocker:
|
||||
retval = self.client.query_pool_info()
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/'
|
||||
'fake_version/storagePool', timeout=50)
|
||||
self.assertListEqual(
|
||||
[{'poolName': 'fake_pool_name',
|
||||
'poolId': 'fake_pool_id'}], retval)
|
||||
|
||||
def test_query_pool_info(self):
|
||||
with mock.patch.object(self.client.session, 'get',
|
||||
wraps=self.client.session.get) as mocker:
|
||||
retval = self.client.query_pool_info(pool_id=0)
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/'
|
||||
'fake_version/storagePool?poolId=0', timeout=50)
|
||||
self.assertListEqual(
|
||||
[{'poolName': 'fake_pool_name1', 'poolId': 0}], retval)
|
||||
|
||||
def test_query_volume_by_name(self):
|
||||
with mock.patch.object(self.client.session, 'get',
|
||||
wraps=self.client.session.get) as mocker:
|
||||
retval = self.client.query_volume_by_name(vol_name='fake_name')
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'volume/queryByName?volName=fake_name', timeout=50)
|
||||
self.assertListEqual(
|
||||
[{'volume_id': 'fake_id', 'volume_name': 'fake_name'}], retval)
|
||||
|
||||
def test_query_volume_by_id(self):
|
||||
with mock.patch.object(self.client.session, 'get',
|
||||
wraps=self.client.session.get) as mocker:
|
||||
retval = self.client.query_volume_by_id(vol_id='fake_id')
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'volume/queryById?volId=fake_id', timeout=50)
|
||||
self.assertListEqual(
|
||||
[{'volume_id': 'fake_id', 'volume_name': 'fake_name'}], retval)
|
||||
|
||||
def test_create_volume(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.create_volume(
|
||||
vol_name='fake_name', vol_size=1, pool_id='fake_id')
|
||||
except_data = json.dumps(
|
||||
{"volName": "fake_name", "volSize": 1, "poolId": "fake_id"})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'volume/create', data=except_data, timeout=50)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
def test_delete_volume(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.delete_volume(vol_name='fake_name')
|
||||
except_data = json.dumps({"volNames": ['fake_name']})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'volume/delete', data=except_data, timeout=50)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
def test_attach_volume(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.attach_volume(
|
||||
vol_name='fake_name', manage_ip='fake_ip')
|
||||
except_data = json.dumps(
|
||||
{"volName": ['fake_name'], "ipList": ['fake_ip']})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'volume/attach', data=except_data, timeout=50)
|
||||
self.assertDictEqual(
|
||||
{'result': 0,
|
||||
'fake_name': [{'errorCode': '0', 'ip': 'fake_ip'}]},
|
||||
retval)
|
||||
|
||||
def test_detach_volume(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.detach_volume(
|
||||
vol_name='fake_name', manage_ip='fake_ip')
|
||||
except_data = json.dumps(
|
||||
{"volName": ['fake_name'], "ipList": ['fake_ip']})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'volume/detach/', data=except_data, timeout=50)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
def test_expand_volume(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.expand_volume(
|
||||
vol_name='fake_name', new_vol_size=2)
|
||||
except_data = json.dumps({"volName": 'fake_name', "newVolSize": 2})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'volume/expand', data=except_data, timeout=50)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
def test_query_snapshot_by_name(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.query_snapshot_by_name(
|
||||
pool_id='fake_id', snapshot_name='fake_name')
|
||||
except_data = json.dumps(
|
||||
{"poolId": 'fake_id', "pageNum": 1,
|
||||
"pageSize": 1000, "filters": {"volumeName": 'fake_name'}})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'snapshot/list', data=except_data, timeout=50)
|
||||
self.assertDictEqual(
|
||||
{'result': 0, 'totalNum': 'fake_snapshot_num',
|
||||
'snapshotList': [{'snapName': 'fake_snapName'}]}, retval)
|
||||
|
||||
def test_create_snapshot(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.create_snapshot(
|
||||
snapshot_name='fake_snap', vol_name='fake_name')
|
||||
except_data = json.dumps(
|
||||
{"volName": "fake_name", "snapshotName": "fake_snap"})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'snapshot/create/', data=except_data, timeout=50)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
def test_delete_snapshot(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.delete_snapshot(snapshot_name='fake_snap')
|
||||
except_data = json.dumps({"snapshotName": "fake_snap"})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'snapshot/delete/', data=except_data, timeout=50)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
def test_create_volume_from_snapshot(self):
|
||||
with mock.patch.object(self.client.session, 'post',
|
||||
wraps=self.client.session.post) as mocker:
|
||||
retval = self.client.create_volume_from_snapshot(
|
||||
snapshot_name='fake_snap', vol_name='fake_name', vol_size=2)
|
||||
except_data = json.dumps({"src": 'fake_snap',
|
||||
"volName": 'fake_name',
|
||||
"volSize": 2})
|
||||
mocker.assert_called_once_with(
|
||||
'https://fake_rest_site/dsware/service/fake_version/'
|
||||
'snapshot/volume/create/', data=except_data, timeout=50)
|
||||
self.assertIsNone(retval)
|
||||
|
||||
@mock.patch.object(fs_client.RestCommon, 'create_snapshot')
|
||||
@mock.patch.object(fs_client.RestCommon, 'create_volume_from_snapshot')
|
||||
@mock.patch.object(fs_client.RestCommon, 'delete_snapshot')
|
||||
def test_create_volume_from_volume(
|
||||
self, mock_delete_snapshot, mock_volume_from_snapshot,
|
||||
mock_create_snapshot):
|
||||
vol_name = 'fake_name'
|
||||
vol_size = 3
|
||||
src_vol_name = 'src_fake_name'
|
||||
temp_snapshot_name = "temp" + src_vol_name + "clone" + vol_name
|
||||
|
||||
retval = self.client.create_volume_from_volume(
|
||||
vol_name, vol_size, src_vol_name)
|
||||
mock_create_snapshot.assert_called_once_with(
|
||||
vol_name=src_vol_name, snapshot_name=temp_snapshot_name)
|
||||
mock_volume_from_snapshot.assert_called_once_with(
|
||||
snapshot_name=temp_snapshot_name,
|
||||
vol_name=vol_name, vol_size=vol_size)
|
||||
mock_delete_snapshot.assert_called_once_with(
|
||||
snapshot_name=temp_snapshot_name)
|
||||
self.assertIsNone(retval)
|
@ -1,156 +0,0 @@
|
||||
# Copyright (c) 2018 Huawei Technologies Co., Ltd.
|
||||
# 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 ddt
|
||||
|
||||
import mock
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
from six.moves import configparser
|
||||
|
||||
from cinder import test
|
||||
from cinder.volume.drivers.fusionstorage import fs_conf
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class FusionStorageConfTestCase(test.TestCase):
|
||||
def setUp(self):
|
||||
super(FusionStorageConfTestCase, self).setUp()
|
||||
self.tmp_dir = tempfile.mkdtemp()
|
||||
self.conf = mock.Mock()
|
||||
self._create_fake_conf_file()
|
||||
self.fusionstorage_conf = fs_conf.FusionStorageConf(
|
||||
self.conf, "cinder@fs")
|
||||
|
||||
def tearDown(self):
|
||||
shutil.rmtree(self.tmp_dir)
|
||||
super(FusionStorageConfTestCase, self).tearDown()
|
||||
|
||||
def _create_fake_conf_file(self):
|
||||
self.conf.cinder_fusionstorage_conf_file = (
|
||||
self.tmp_dir + '/cinder.conf')
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
config.add_section('backend_name')
|
||||
config.set('backend_name', 'dsware_rest_url', 'https://fake_rest_site')
|
||||
config.set('backend_name', 'san_login', 'fake_user')
|
||||
config.set('backend_name', 'san_password', 'fake_passwd')
|
||||
config.set('backend_name', 'dsware_storage_pools', 'fake_pool')
|
||||
|
||||
config.add_section('manager_ip')
|
||||
config.set('manager_ip', 'fake_host', 'fake_ip')
|
||||
config.write(open(self.conf.cinder_fusionstorage_conf_file, 'w'))
|
||||
|
||||
@mock.patch.object(fs_conf.FusionStorageConf, '_encode_authentication')
|
||||
@mock.patch.object(fs_conf.FusionStorageConf, '_pools_name')
|
||||
@mock.patch.object(fs_conf.FusionStorageConf, '_san_address')
|
||||
@mock.patch.object(fs_conf.FusionStorageConf, '_san_user')
|
||||
@mock.patch.object(fs_conf.FusionStorageConf, '_san_password')
|
||||
def test_update_config_value(self, mock_san_password, mock_san_user,
|
||||
mock_san_address, mock_pools_name,
|
||||
mock_encode_authentication):
|
||||
self.fusionstorage_conf.update_config_value()
|
||||
mock_encode_authentication.assert_called_once_with()
|
||||
mock_pools_name.assert_called_once_with()
|
||||
mock_san_address.assert_called_once_with()
|
||||
mock_san_user.assert_called_once_with()
|
||||
mock_san_password.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(os.path, 'exists')
|
||||
def test__encode_authentication(self, mock_exists):
|
||||
config = configparser.ConfigParser()
|
||||
config.read(self.conf.cinder_fusionstorage_conf_file)
|
||||
mock_exists.return_value = False
|
||||
|
||||
user_name = 'fake_user'
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=user_name)
|
||||
self.fusionstorage_conf._encode_authentication()
|
||||
|
||||
password = 'fake_passwd'
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=password)
|
||||
self.fusionstorage_conf._encode_authentication()
|
||||
|
||||
@mock.patch.object(os.path, 'exists')
|
||||
@mock.patch.object(configparser.ConfigParser, 'set')
|
||||
def test__rewrite_conf(self, mock_set, mock_exists):
|
||||
mock_exists.return_value = False
|
||||
mock_set.return_value = "success"
|
||||
self.fusionstorage_conf._rewrite_conf('fake_name', 'fake_pwd')
|
||||
|
||||
def test__san_address(self):
|
||||
address = 'https://fake_rest_site'
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=address)
|
||||
self.fusionstorage_conf._san_address()
|
||||
self.assertEqual('https://fake_rest_site',
|
||||
self.fusionstorage_conf.configuration.san_address)
|
||||
|
||||
def test__san_user(self):
|
||||
user = '!&&&ZmFrZV91c2Vy'
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=user)
|
||||
self.fusionstorage_conf._san_user()
|
||||
self.assertEqual(
|
||||
'fake_user', self.fusionstorage_conf.configuration.san_user)
|
||||
|
||||
user = 'fake_user_2'
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=user)
|
||||
self.fusionstorage_conf._san_user()
|
||||
self.assertEqual(
|
||||
'fake_user_2', self.fusionstorage_conf.configuration.san_user)
|
||||
|
||||
def test__san_password(self):
|
||||
password = '!&&&ZmFrZV9wYXNzd2Q='
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=password)
|
||||
self.fusionstorage_conf._san_password()
|
||||
self.assertEqual(
|
||||
'fake_passwd', self.fusionstorage_conf.configuration.san_password)
|
||||
|
||||
password = 'fake_passwd_2'
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=password)
|
||||
self.fusionstorage_conf._san_password()
|
||||
self.assertEqual('fake_passwd_2',
|
||||
self.fusionstorage_conf.configuration.san_password)
|
||||
|
||||
def test__pools_name(self):
|
||||
pools_name = 'fake_pool'
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=pools_name)
|
||||
self.fusionstorage_conf._pools_name()
|
||||
self.assertListEqual(
|
||||
['fake_pool'], self.fusionstorage_conf.configuration.pools_name)
|
||||
|
||||
def test__manager_ip(self):
|
||||
manager_ips = {'fake_host': 'fake_ip'}
|
||||
self.mock_object(
|
||||
self.fusionstorage_conf.configuration, 'safe_get',
|
||||
return_value=manager_ips)
|
||||
self.fusionstorage_conf._manager_ip()
|
||||
self.assertDictEqual({'fake_host': 'fake_ip'},
|
||||
self.fusionstorage_conf.configuration.manager_ips)
|
@ -1,48 +0,0 @@
|
||||
# Copyright (c) 2018 Huawei Technologies Co., Ltd.
|
||||
# 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 json
|
||||
|
||||
import re
|
||||
import requests
|
||||
|
||||
|
||||
class FakeBaseSession(requests.Session):
|
||||
method_map = {}
|
||||
|
||||
def _get_response(self, method, url):
|
||||
url_map = self.method_map.get(method, {})
|
||||
tmp = None
|
||||
data = {}
|
||||
for k in url_map:
|
||||
if re.search(k, url):
|
||||
if not tmp or len(tmp) < len(k):
|
||||
data = url_map[k]
|
||||
tmp = k
|
||||
|
||||
resp_content = {'result': 0}
|
||||
resp_content.update(data)
|
||||
resp = requests.Response()
|
||||
resp.headers['X-Auth-Token'] = 'fake_token'
|
||||
resp.status_code = 0
|
||||
resp.encoding = 'utf-8'
|
||||
resp._content = json.dumps(resp_content).encode('utf-8')
|
||||
|
||||
return resp
|
||||
|
||||
def get(self, url, **kwargs):
|
||||
return self._get_response('get', url)
|
||||
|
||||
def post(self, url, **kwargs):
|
||||
return self._get_response('post', url)
|
@ -1,30 +0,0 @@
|
||||
# Copyright (c) 2016 Huawei Technologies Co., Ltd.
|
||||
# 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.
|
||||
|
||||
DEFAULT_TIMEOUT = 50
|
||||
LOGIN_SOCKET_TIMEOUT = 32
|
||||
|
||||
CONNECT_ERROR = 403
|
||||
ERROR_UNAUTHORIZED = 10000003
|
||||
VOLUME_NOT_EXIST = 31000000
|
||||
|
||||
BASIC_URI = '/dsware/service/'
|
||||
CONF_PATH = "/etc/cinder/cinder.conf"
|
||||
|
||||
CONF_ADDRESS = "dsware_rest_url"
|
||||
CONF_MANAGER_IP = "manager_ips"
|
||||
CONF_POOLS = "dsware_storage_pools"
|
||||
CONF_PWD = "san_password"
|
||||
CONF_USER = "san_login"
|
@ -1,383 +0,0 @@
|
||||
# Copyright (c) 2018 Huawei Technologies Co., Ltd.
|
||||
# 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 json
|
||||
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import units
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import interface
|
||||
from cinder.volume import driver
|
||||
from cinder.volume.drivers.fusionstorage import fs_client
|
||||
from cinder.volume.drivers.fusionstorage import fs_conf
|
||||
from cinder.volume.drivers.san import san
|
||||
from cinder.volume import volume_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
volume_opts = [
|
||||
cfg.BoolOpt("dsware_isthin",
|
||||
default=False,
|
||||
help='The flag of thin storage allocation.',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='14.0.0',
|
||||
deprecated_reason='FusionStorage cinder driver refactored the '
|
||||
'code with Restful method and the old CLI '
|
||||
'mode has been abandon. So those '
|
||||
'configuration items are no longer used.'),
|
||||
cfg.StrOpt("dsware_manager",
|
||||
default='',
|
||||
help='Fusionstorage manager ip addr for cinder-volume.',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='14.0.0',
|
||||
deprecated_reason='FusionStorage cinder driver refactored the '
|
||||
'code with Restful method and the old CLI '
|
||||
'mode has been abandon. So those '
|
||||
'configuration items are no longer used.'),
|
||||
cfg.StrOpt('fusionstorageagent',
|
||||
default='',
|
||||
help='Fusionstorage agent ip addr range',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='14.0.0',
|
||||
deprecated_reason='FusionStorage cinder driver refactored the '
|
||||
'code with Restful method and the old CLI '
|
||||
'mode has been abandon. So those '
|
||||
'configuration items are no longer used.'),
|
||||
cfg.StrOpt('pool_type',
|
||||
default='default',
|
||||
help='Pool type, like sata-2copy',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='14.0.0',
|
||||
deprecated_reason='FusionStorage cinder driver refactored the '
|
||||
'code with Restful method and the old CLI '
|
||||
'mode has been abandon. So those '
|
||||
'configuration items are no longer used.'),
|
||||
cfg.ListOpt('pool_id_filter',
|
||||
default=[],
|
||||
help='Pool id permit to use',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='14.0.0',
|
||||
deprecated_reason='FusionStorage cinder driver refactored the '
|
||||
'code with Restful method and the old CLI '
|
||||
'mode has been abandon. So those '
|
||||
'configuration items are no longer used.'),
|
||||
cfg.IntOpt('clone_volume_timeout',
|
||||
default=680,
|
||||
help='Create clone volume timeout',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_since='14.0.0',
|
||||
deprecated_reason='FusionStorage cinder driver refactored the '
|
||||
'code with Restful method and the old CLI '
|
||||
'mode has been abandon. So those '
|
||||
'configuration items are no longer used.'),
|
||||
cfg.DictOpt('manager_ips',
|
||||
default={},
|
||||
help='This option is to support the FSA to mount across the '
|
||||
'different nodes. The parameters takes the standard dict '
|
||||
'config form, manager_ips = host1:ip1, host2:ip2...'),
|
||||
cfg.StrOpt('dsware_rest_url',
|
||||
default='',
|
||||
help='The address of FusionStorage array. For example, '
|
||||
'"dsware_rest_url=xxx"'),
|
||||
cfg.StrOpt('dsware_storage_pools',
|
||||
default="",
|
||||
help='The list of pools on the FusionStorage array, the '
|
||||
'semicolon(;) was used to split the storage pools, '
|
||||
'"dsware_storage_pools = xxx1; xxx2; xxx3"')
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(volume_opts)
|
||||
|
||||
|
||||
@interface.volumedriver
|
||||
class DSWAREDriver(driver.VolumeDriver):
|
||||
VERSION = '2.0'
|
||||
CI_WIKI_NAME = 'Huawei_FusionStorage_CI'
|
||||
|
||||
# TODO(jsbryant) Remove driver in the 'U' release due to no py37 support.
|
||||
SUPPORTED = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DSWAREDriver, self).__init__(*args, **kwargs)
|
||||
|
||||
if not self.configuration:
|
||||
msg = _('Configuration is not found.')
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
self.configuration.append_config_values(volume_opts)
|
||||
self.configuration.append_config_values(san.san_opts)
|
||||
self.conf = fs_conf.FusionStorageConf(self.configuration, self.host)
|
||||
self.client = None
|
||||
|
||||
@staticmethod
|
||||
def get_driver_options():
|
||||
return volume_opts
|
||||
|
||||
def do_setup(self, context):
|
||||
self.conf.update_config_value()
|
||||
url_str = self.configuration.san_address
|
||||
url_user = self.configuration.san_user
|
||||
url_password = self.configuration.san_password
|
||||
|
||||
self.client = fs_client.RestCommon(
|
||||
fs_address=url_str, fs_user=url_user,
|
||||
fs_password=url_password)
|
||||
self.client.login()
|
||||
|
||||
def check_for_setup_error(self):
|
||||
all_pools = self.client.query_pool_info()
|
||||
all_pools_name = [p['poolName'] for p in all_pools
|
||||
if p.get('poolName')]
|
||||
|
||||
for pool in self.configuration.pools_name:
|
||||
if pool not in all_pools_name:
|
||||
msg = _('Storage pool %(pool)s does not exist '
|
||||
'in the FusionStorage.') % {'pool': pool}
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
def _update_pool_stats(self):
|
||||
backend_name = self.configuration.safe_get(
|
||||
'volume_backend_name') or self.__class__.__name__
|
||||
data = {"volume_backend_name": backend_name,
|
||||
"driver_version": "2.0.9",
|
||||
"QoS_support": False,
|
||||
"thin_provisioning_support": False,
|
||||
"pools": [],
|
||||
"vendor_name": "Huawei"
|
||||
}
|
||||
all_pools = self.client.query_pool_info()
|
||||
|
||||
for pool in all_pools:
|
||||
if pool['poolName'] in self.configuration.pools_name:
|
||||
single_pool_info = self._update_single_pool_info_status(pool)
|
||||
data['pools'].append(single_pool_info)
|
||||
return data
|
||||
|
||||
def _get_capacity(self, pool_info):
|
||||
pool_capacity = {}
|
||||
|
||||
total = float(pool_info['totalCapacity']) / units.Ki
|
||||
free = (float(pool_info['totalCapacity']) -
|
||||
float(pool_info['usedCapacity'])) / units.Ki
|
||||
pool_capacity['total_capacity_gb'] = total
|
||||
pool_capacity['free_capacity_gb'] = free
|
||||
|
||||
return pool_capacity
|
||||
|
||||
def _update_single_pool_info_status(self, pool_info):
|
||||
status = {}
|
||||
capacity = self._get_capacity(pool_info=pool_info)
|
||||
status.update({
|
||||
"pool_name": pool_info['poolName'],
|
||||
"total_capacity_gb": capacity['total_capacity_gb'],
|
||||
"free_capacity_gb": capacity['free_capacity_gb'],
|
||||
})
|
||||
return status
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
self.client.keep_alive()
|
||||
stats = self._update_pool_stats()
|
||||
return stats
|
||||
|
||||
def _check_volume_exist(self, volume):
|
||||
vol_name = self._get_vol_name(volume)
|
||||
result = self.client.query_volume_by_name(vol_name=vol_name)
|
||||
if result:
|
||||
return result
|
||||
|
||||
def _raise_exception(self, msg):
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def _get_pool_id(self, volume):
|
||||
pool_id = None
|
||||
pool_name = volume_utils.extract_host(volume.host, level='pool')
|
||||
all_pools = self.client.query_pool_info()
|
||||
for pool in all_pools:
|
||||
if pool_name == pool['poolName']:
|
||||
pool_id = pool['poolId']
|
||||
|
||||
if pool_id is None:
|
||||
msg = _('Storage pool %(pool)s does not exist on the array. '
|
||||
'Please check.') % {"pool": pool_id}
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
return pool_id
|
||||
|
||||
def _get_vol_name(self, volume):
|
||||
provider_location = volume.get("provider_location", None)
|
||||
if provider_location:
|
||||
vol_name = json.loads(provider_location).get("name")
|
||||
else:
|
||||
vol_name = volume.name
|
||||
return vol_name
|
||||
|
||||
def create_volume(self, volume):
|
||||
pool_id = self._get_pool_id(volume)
|
||||
vol_name = volume.name
|
||||
vol_size = volume.size
|
||||
vol_size *= units.Ki
|
||||
self.client.create_volume(
|
||||
pool_id=pool_id, vol_name=vol_name, vol_size=vol_size)
|
||||
|
||||
def delete_volume(self, volume):
|
||||
vol_name = self._get_vol_name(volume)
|
||||
if self._check_volume_exist(volume):
|
||||
self.client.delete_volume(vol_name=vol_name)
|
||||
|
||||
def extend_volume(self, volume, new_size):
|
||||
vol_name = self._get_vol_name(volume)
|
||||
if not self._check_volume_exist(volume):
|
||||
msg = _("Volume: %(vol_name)s does not exist!"
|
||||
) % {"vol_name": vol_name}
|
||||
self._raise_exception(msg)
|
||||
else:
|
||||
new_size *= units.Ki
|
||||
self.client.expand_volume(vol_name, new_size)
|
||||
|
||||
def _check_snapshot_exist(self, volume, snapshot):
|
||||
pool_id = self._get_pool_id(volume)
|
||||
snapshot_name = self._get_snapshot_name(snapshot)
|
||||
result = self.client.query_snapshot_by_name(
|
||||
pool_id=pool_id, snapshot_name=snapshot_name)
|
||||
if result.get('totalNum'):
|
||||
return result
|
||||
|
||||
def _get_snapshot_name(self, snapshot):
|
||||
provider_location = snapshot.get("provider_location", None)
|
||||
if provider_location:
|
||||
snapshot_name = json.loads(provider_location).get("name")
|
||||
else:
|
||||
snapshot_name = snapshot.name
|
||||
return snapshot_name
|
||||
|
||||
def create_volume_from_snapshot(self, volume, snapshot):
|
||||
vol_name = self._get_vol_name(volume)
|
||||
snapshot_name = self._get_snapshot_name(snapshot)
|
||||
vol_size = volume.size
|
||||
|
||||
if not self._check_snapshot_exist(snapshot.volume, snapshot):
|
||||
msg = _("Snapshot: %(name)s does not exist!"
|
||||
) % {"name": snapshot_name}
|
||||
self._raise_exception(msg)
|
||||
elif self._check_volume_exist(volume):
|
||||
msg = _("Volume: %(vol_name)s already exists!"
|
||||
) % {'vol_name': vol_name}
|
||||
self._raise_exception(msg)
|
||||
else:
|
||||
vol_size *= units.Ki
|
||||
self.client.create_volume_from_snapshot(
|
||||
snapshot_name=snapshot_name, vol_name=vol_name,
|
||||
vol_size=vol_size)
|
||||
|
||||
def create_cloned_volume(self, volume, src_volume):
|
||||
vol_name = self._get_vol_name(volume)
|
||||
src_vol_name = self._get_vol_name(src_volume)
|
||||
|
||||
vol_size = volume.size
|
||||
vol_size *= units.Ki
|
||||
|
||||
if not self._check_volume_exist(src_volume):
|
||||
msg = _("Volume: %(vol_name)s does not exist!"
|
||||
) % {"vol_name": src_vol_name}
|
||||
self._raise_exception(msg)
|
||||
else:
|
||||
self.client.create_volume_from_volume(
|
||||
vol_name=vol_name, vol_size=vol_size,
|
||||
src_vol_name=src_vol_name)
|
||||
|
||||
def create_snapshot(self, snapshot):
|
||||
snapshot_name = self._get_snapshot_name(snapshot)
|
||||
vol_name = self._get_vol_name(snapshot.volume)
|
||||
|
||||
self.client.create_snapshot(
|
||||
snapshot_name=snapshot_name, vol_name=vol_name)
|
||||
|
||||
def delete_snapshot(self, snapshot):
|
||||
snapshot_name = self._get_snapshot_name(snapshot)
|
||||
|
||||
if self._check_snapshot_exist(snapshot.volume, snapshot):
|
||||
self.client.delete_snapshot(snapshot_name=snapshot_name)
|
||||
|
||||
def _get_manager_ip(self, context):
|
||||
if self.configuration.manager_ips.get(context['host']):
|
||||
return self.configuration.manager_ips.get(context['host'])
|
||||
else:
|
||||
msg = _("The required host: %(host)s and its manager ip are not "
|
||||
"included in the configuration file."
|
||||
) % {"host": context['host']}
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(msg)
|
||||
|
||||
def _attach_volume(self, context, volume, properties, remote=False):
|
||||
vol_name = self._get_vol_name(volume)
|
||||
if not self._check_volume_exist(volume):
|
||||
msg = _("Volume: %(vol_name)s does not exist!"
|
||||
) % {"vol_name": vol_name}
|
||||
self._raise_exception(msg)
|
||||
manager_ip = self._get_manager_ip(properties)
|
||||
result = self.client.attach_volume(vol_name, manager_ip)
|
||||
attach_path = result[vol_name][0]['devName'].encode('unicode-escape')
|
||||
attach_info = dict()
|
||||
attach_info['device'] = dict()
|
||||
attach_info['device']['path'] = attach_path
|
||||
if attach_path == '':
|
||||
msg = _("Host attach volume failed!")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return attach_info, volume
|
||||
|
||||
def _detach_volume(self, context, attach_info, volume, properties,
|
||||
force=False, remote=False, ignore_errors=False):
|
||||
vol_name = self._get_vol_name(volume)
|
||||
if self._check_volume_exist(volume):
|
||||
manager_ip = self._get_manager_ip(properties)
|
||||
self.client.detach_volume(vol_name, manager_ip)
|
||||
|
||||
def initialize_connection(self, volume, connector):
|
||||
vol_name = self._get_vol_name(volume)
|
||||
manager_ip = self._get_manager_ip(connector)
|
||||
if not self._check_volume_exist(volume):
|
||||
msg = _("Volume: %(vol_name)s does not exist!"
|
||||
) % {"vol_name": vol_name}
|
||||
self._raise_exception(msg)
|
||||
self.client.attach_volume(vol_name, manager_ip)
|
||||
volume_info = self.client.query_volume_by_name(vol_name=vol_name)
|
||||
vol_wwn = volume_info.get('wwn')
|
||||
by_id_path = "/dev/disk/by-id/" + "wwn-0x%s" % vol_wwn
|
||||
properties = {'device_path': by_id_path}
|
||||
return {'driver_volume_type': 'local',
|
||||
'data': properties}
|
||||
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
if self._check_volume_exist(volume):
|
||||
manager_ip = self._get_manager_ip(connector)
|
||||
vol_name = self._get_vol_name(volume)
|
||||
self.client.detach_volume(vol_name, manager_ip)
|
||||
|
||||
def create_export(self, context, volume, connector):
|
||||
pass
|
||||
|
||||
def ensure_export(self, context, volume):
|
||||
pass
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
pass
|
@ -1,256 +0,0 @@
|
||||
# Copyright (c) 2018 Huawei Technologies Co., Ltd.
|
||||
# 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 json
|
||||
|
||||
from oslo_log import log as logging
|
||||
import requests
|
||||
import six
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder.volume.drivers.fusionstorage import constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RestCommon(object):
|
||||
def __init__(self, fs_address, fs_user, fs_password):
|
||||
self.address = fs_address
|
||||
self.user = fs_user
|
||||
self.password = fs_password
|
||||
|
||||
self.session = None
|
||||
self.token = None
|
||||
self.version = None
|
||||
|
||||
self.init_http_head()
|
||||
|
||||
LOG.warning("Suppressing requests library SSL Warnings")
|
||||
requests.packages.urllib3.disable_warnings(
|
||||
requests.packages.urllib3.exceptions.InsecureRequestWarning)
|
||||
requests.packages.urllib3.disable_warnings(
|
||||
requests.packages.urllib3.exceptions.InsecurePlatformWarning)
|
||||
|
||||
def init_http_head(self):
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
"Content-Type": "application/json;charset=UTF-8",
|
||||
})
|
||||
self.session.verify = False
|
||||
|
||||
def call(self, url, method, data=None,
|
||||
call_timeout=constants.DEFAULT_TIMEOUT,
|
||||
get_version=False, filter_flag=False, json_flag=False):
|
||||
kwargs = {'timeout': call_timeout}
|
||||
if data:
|
||||
kwargs['data'] = json.dumps(data)
|
||||
|
||||
if not get_version:
|
||||
call_url = self.address + constants.BASIC_URI + self.version + url
|
||||
else:
|
||||
call_url = self.address + constants.BASIC_URI + url
|
||||
|
||||
func = getattr(self.session, method.lower())
|
||||
|
||||
try:
|
||||
result = func(call_url, **kwargs)
|
||||
except Exception as err:
|
||||
LOG.error('Bad response from server: %(url)s. '
|
||||
'Error: %(err)s'), {'url': url, 'err': err}
|
||||
return {"error": {
|
||||
"code": constants.CONNECT_ERROR,
|
||||
"description": "Connect to server error."}}
|
||||
|
||||
try:
|
||||
result.raise_for_status()
|
||||
except requests.HTTPError as exc:
|
||||
return {"error": {"code": exc.response.status_code,
|
||||
"description": six.text_type(exc)}}
|
||||
|
||||
if not filter_flag:
|
||||
LOG.info('''
|
||||
Request URL: %(url)s,
|
||||
Call Method: %(method)s,
|
||||
Request Data: %(data)s,
|
||||
Response Data: %(res)s,
|
||||
Result Data: %(res_json)s''', {'url': url, 'method': method,
|
||||
'data': data, 'res': result,
|
||||
'res_json': result.json()})
|
||||
|
||||
if json_flag:
|
||||
return result
|
||||
else:
|
||||
return result.json()
|
||||
|
||||
def _assert_rest_result(self, result, err_str):
|
||||
if result.get('result') != 0:
|
||||
msg = (_('%(err)s\nresult: %(res)s.') % {'err': err_str,
|
||||
'res': result})
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
|
||||
def get_version(self):
|
||||
url = 'rest/version'
|
||||
self.session.headers.update({
|
||||
"Referer": self.address + constants.BASIC_URI
|
||||
})
|
||||
result = self.call(url=url, method='GET', get_version=True)
|
||||
self._assert_rest_result(result, _('Get version session error.'))
|
||||
if result.get("currentVersion"):
|
||||
self.version = result["currentVersion"]
|
||||
|
||||
def login(self):
|
||||
self.get_version()
|
||||
url = '/sec/login'
|
||||
data = {"userName": self.user, "password": self.password}
|
||||
result = self.call(url, 'POST', data=data,
|
||||
call_timeout=constants.LOGIN_SOCKET_TIMEOUT,
|
||||
filter_flag=True, json_flag=True)
|
||||
self._assert_rest_result(result.json(), _('Login session error.'))
|
||||
self.token = result.headers['X-Auth-Token']
|
||||
|
||||
self.session.headers.update({
|
||||
"x-auth-token": self.token
|
||||
})
|
||||
|
||||
def logout(self):
|
||||
url = '/sec/logout'
|
||||
if self.address:
|
||||
result = self.call(url, 'POST')
|
||||
self._assert_rest_result(result, _('Logout session error.'))
|
||||
|
||||
def keep_alive(self):
|
||||
url = '/sec/keepAlive'
|
||||
result = self.call(url, 'POST', filter_flag=True)
|
||||
|
||||
if result.get('result') == constants.ERROR_UNAUTHORIZED:
|
||||
try:
|
||||
self.login()
|
||||
except Exception:
|
||||
LOG.error('The FusionStorage may have been powered off. '
|
||||
'Power on the FusionStorage and then log in.')
|
||||
raise
|
||||
else:
|
||||
self._assert_rest_result(result, _('Keep alive session error.'))
|
||||
|
||||
def query_pool_info(self, pool_id=None):
|
||||
pool_id = str(pool_id)
|
||||
if pool_id != 'None':
|
||||
url = '/storagePool' + '?poolId=' + pool_id
|
||||
else:
|
||||
url = '/storagePool'
|
||||
result = self.call(url, 'GET', filter_flag=True)
|
||||
self._assert_rest_result(result, _("Query pool session error."))
|
||||
return result['storagePools']
|
||||
|
||||
def query_volume_by_name(self, vol_name):
|
||||
url = '/volume/queryByName?volName=' + vol_name
|
||||
result = self.call(url, 'GET')
|
||||
if result.get('errorCode') == constants.VOLUME_NOT_EXIST:
|
||||
return None
|
||||
self._assert_rest_result(
|
||||
result, _("Query volume by name session error"))
|
||||
return result.get('lunDetailInfo')
|
||||
|
||||
def query_volume_by_id(self, vol_id):
|
||||
url = '/volume/queryById?volId=' + vol_id
|
||||
result = self.call(url, 'GET')
|
||||
if result.get('errorCode') == constants.VOLUME_NOT_EXIST:
|
||||
return None
|
||||
self._assert_rest_result(
|
||||
result, _("Query volume by ID session error"))
|
||||
return result.get('lunDetailInfo')
|
||||
|
||||
def create_volume(self, vol_name, vol_size, pool_id):
|
||||
url = '/volume/create'
|
||||
params = {"volName": vol_name, "volSize": vol_size, "poolId": pool_id}
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(result, _('Create volume session error.'))
|
||||
|
||||
def delete_volume(self, vol_name):
|
||||
url = '/volume/delete'
|
||||
params = {"volNames": [vol_name]}
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(result, _('Delete volume session error.'))
|
||||
|
||||
def attach_volume(self, vol_name, manage_ip):
|
||||
url = '/volume/attach'
|
||||
params = {"volName": [vol_name], "ipList": [manage_ip]}
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(result, _('Attach volume session error.'))
|
||||
|
||||
if int(result[vol_name][0]['errorCode']) != 0:
|
||||
msg = _("Host attach volume failed!")
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
return result
|
||||
|
||||
def detach_volume(self, vol_name, manage_ip):
|
||||
url = '/volume/detach/'
|
||||
params = {"volName": [vol_name], "ipList": [manage_ip]}
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(result, _('Detach volume session error.'))
|
||||
|
||||
def expand_volume(self, vol_name, new_vol_size):
|
||||
url = '/volume/expand'
|
||||
params = {"volName": vol_name, "newVolSize": new_vol_size}
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(result, _('Expand volume session error.'))
|
||||
|
||||
def query_snapshot_by_name(self, pool_id, snapshot_name, page_num=1,
|
||||
page_size=1000):
|
||||
# Filter the snapshot according to the name, while the "page_num" and
|
||||
# "page_size" must be set while using the interface.
|
||||
url = '/snapshot/list'
|
||||
params = {"poolId": pool_id, "pageNum": page_num,
|
||||
"pageSize": page_size,
|
||||
"filters": {"volumeName": snapshot_name}}
|
||||
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(
|
||||
result, _('query snapshot list session error.'))
|
||||
return result
|
||||
|
||||
def create_snapshot(self, snapshot_name, vol_name):
|
||||
url = '/snapshot/create/'
|
||||
params = {"volName": vol_name, "snapshotName": snapshot_name}
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(result, _('Create snapshot error.'))
|
||||
|
||||
def delete_snapshot(self, snapshot_name):
|
||||
url = '/snapshot/delete/'
|
||||
params = {"snapshotName": snapshot_name}
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(result, _('Delete snapshot session error.'))
|
||||
|
||||
def create_volume_from_snapshot(self, snapshot_name, vol_name, vol_size):
|
||||
url = '/snapshot/volume/create/'
|
||||
params = {"src": snapshot_name, "volName": vol_name,
|
||||
"volSize": vol_size}
|
||||
result = self.call(url, "POST", params)
|
||||
self._assert_rest_result(
|
||||
result, _('create volume from snapshot session error.'))
|
||||
|
||||
def create_volume_from_volume(self, vol_name, vol_size, src_vol_name):
|
||||
temp_snapshot_name = "temp" + src_vol_name + "clone" + vol_name
|
||||
|
||||
self.create_snapshot(vol_name=src_vol_name,
|
||||
snapshot_name=temp_snapshot_name)
|
||||
|
||||
self.create_volume_from_snapshot(snapshot_name=temp_snapshot_name,
|
||||
vol_name=vol_name, vol_size=vol_size)
|
||||
|
||||
self.delete_snapshot(snapshot_name=temp_snapshot_name)
|
@ -1,127 +0,0 @@
|
||||
# Copyright (c) 2018 Huawei Technologies Co., Ltd.
|
||||
# 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 base64
|
||||
import os
|
||||
import six
|
||||
|
||||
from oslo_log import log as logging
|
||||
from six.moves import configparser
|
||||
|
||||
from cinder import exception
|
||||
from cinder.i18n import _
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.fusionstorage import constants
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FusionStorageConf(object):
|
||||
def __init__(self, configuration, host):
|
||||
self.configuration = configuration
|
||||
self._check_host(host)
|
||||
|
||||
def _check_host(self, host):
|
||||
if host and len(host.split('@')) > 1:
|
||||
self.host = host.split('@')[1]
|
||||
else:
|
||||
msg = _("The host %s is not reliable. Please check cinder-volume "
|
||||
"backend.") % host
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
def update_config_value(self):
|
||||
self._encode_authentication()
|
||||
self._pools_name()
|
||||
self._san_address()
|
||||
self._san_user()
|
||||
self._san_password()
|
||||
|
||||
def _encode_authentication(self):
|
||||
name_node = self.configuration.safe_get(constants.CONF_USER)
|
||||
pwd_node = self.configuration.safe_get(constants.CONF_PWD)
|
||||
|
||||
need_encode = False
|
||||
if name_node is not None and not name_node.startswith('!&&&'):
|
||||
encoded = base64.b64encode(six.b(name_node)).decode()
|
||||
name_node = '!&&&' + encoded
|
||||
need_encode = True
|
||||
|
||||
if pwd_node is not None and not pwd_node.startswith('!&&&'):
|
||||
encoded = base64.b64encode(six.b(pwd_node)).decode()
|
||||
pwd_node = '!&&&' + encoded
|
||||
need_encode = True
|
||||
|
||||
if need_encode:
|
||||
self._rewrite_conf(name_node, pwd_node)
|
||||
|
||||
def _rewrite_conf(self, name_node, pwd_node):
|
||||
if os.path.exists(constants.CONF_PATH):
|
||||
utils.execute("chmod", "666", constants.CONF_PATH,
|
||||
run_as_root=True)
|
||||
conf = configparser.ConfigParser()
|
||||
conf.read(constants.CONF_PATH)
|
||||
if name_node:
|
||||
conf.set(self.host, constants.CONF_USER, name_node)
|
||||
if pwd_node:
|
||||
conf.set(self.host, constants.CONF_PWD, pwd_node)
|
||||
fh = open(constants.CONF_PATH, 'w')
|
||||
conf.write(fh)
|
||||
fh.close()
|
||||
utils.execute("chmod", "644", constants.CONF_PATH,
|
||||
run_as_root=True)
|
||||
|
||||
def _assert_text_result(self, text, mess):
|
||||
if not text:
|
||||
msg = _("%s is not configured.") % mess
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidInput(reason=msg)
|
||||
|
||||
def _san_address(self):
|
||||
address = self.configuration.safe_get(constants.CONF_ADDRESS)
|
||||
self._assert_text_result(address, mess=constants.CONF_ADDRESS)
|
||||
setattr(self.configuration, 'san_address', address)
|
||||
|
||||
def _decode_text(self, text):
|
||||
return (base64.b64decode(six.b(text[4:])).decode() if
|
||||
text.startswith('!&&&') else text)
|
||||
|
||||
def _san_user(self):
|
||||
user_text = self.configuration.safe_get(constants.CONF_USER)
|
||||
self._assert_text_result(user_text, mess=constants.CONF_USER)
|
||||
user = self._decode_text(user_text)
|
||||
setattr(self.configuration, 'san_user', user)
|
||||
|
||||
def _san_password(self):
|
||||
pwd_text = self.configuration.safe_get(constants.CONF_PWD)
|
||||
self._assert_text_result(pwd_text, mess=constants.CONF_PWD)
|
||||
pwd = self._decode_text(pwd_text)
|
||||
setattr(self.configuration, 'san_password', pwd)
|
||||
|
||||
def _pools_name(self):
|
||||
pools_name = self.configuration.safe_get(constants.CONF_POOLS)
|
||||
self._assert_text_result(pools_name, mess=constants.CONF_POOLS)
|
||||
pools = set(x.strip() for x in pools_name.split(';') if x.strip())
|
||||
if not pools:
|
||||
msg = _('No valid storage pool configured.')
|
||||
LOG.error(msg)
|
||||
raise exception.InvalidInput(msg)
|
||||
setattr(self.configuration, 'pools_name', list(pools))
|
||||
|
||||
def _manager_ip(self):
|
||||
manager_ips = self.configuration.safe_get(constants.CONF_MANAGER_IP)
|
||||
self._assert_text_result(manager_ips, mess=constants.CONF_MANAGER_IP)
|
||||
setattr(self.configuration, 'manager_ips', manager_ips)
|
@ -81,9 +81,6 @@ title=Huawei 18000 Series Driver (iSCSI, FC)
|
||||
[driver.huawei_dorado]
|
||||
title=Huawei Dorado V3 Series Driver (iSCSI, FC)
|
||||
|
||||
[driver.huawei_fusionstorage]
|
||||
title=Huawei FusionStorage Driver (dsware)
|
||||
|
||||
[driver.ibm_ds8k]
|
||||
title=IBM DS8k Storage Driver (FC)
|
||||
|
||||
@ -224,7 +221,6 @@ driver.huawei_v5=complete
|
||||
driver.huawei_f_v5=complete
|
||||
driver.huawei_18000=complete
|
||||
driver.huawei_dorado=complete
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=complete
|
||||
driver.ibm_ds8k=complete
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -289,7 +285,6 @@ driver.huawei_v5=complete
|
||||
driver.huawei_f_v5=complete
|
||||
driver.huawei_18000=complete
|
||||
driver.huawei_dorado=complete
|
||||
driver.huawei_fusionstorage=complete
|
||||
driver.infinidat=complete
|
||||
driver.ibm_ds8k=missing
|
||||
driver.ibm_flashsystem=complete
|
||||
@ -354,7 +349,6 @@ driver.huawei_v5=complete
|
||||
driver.huawei_f_v5=complete
|
||||
driver.huawei_18000=complete
|
||||
driver.huawei_dorado=complete
|
||||
driver.huawei_fusionstorage=complete
|
||||
driver.infinidat=missing
|
||||
driver.ibm_ds8k=missing
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -422,7 +416,6 @@ driver.huawei_v5=complete
|
||||
driver.huawei_f_v5=complete
|
||||
driver.huawei_18000=complete
|
||||
driver.huawei_dorado=complete
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=complete
|
||||
driver.ibm_ds8k=missing
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -489,7 +482,6 @@ driver.huawei_v5=complete
|
||||
driver.huawei_f_v5=complete
|
||||
driver.huawei_18000=complete
|
||||
driver.huawei_dorado=complete
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=missing
|
||||
driver.ibm_ds8k=complete
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -557,7 +549,6 @@ driver.huawei_v5=complete
|
||||
driver.huawei_f_v5=complete
|
||||
driver.huawei_18000=complete
|
||||
driver.huawei_dorado=complete
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=missing
|
||||
driver.ibm_ds8k=complete
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -624,7 +615,6 @@ driver.huawei_v5=complete
|
||||
driver.huawei_f_v5=complete
|
||||
driver.huawei_18000=complete
|
||||
driver.huawei_dorado=complete
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=complete
|
||||
driver.ibm_ds8k=missing
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -692,7 +682,6 @@ driver.huawei_v5=complete
|
||||
driver.huawei_f_v5=complete
|
||||
driver.huawei_18000=complete
|
||||
driver.huawei_dorado=complete
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=missing
|
||||
driver.ibm_ds8k=missing
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -760,7 +749,6 @@ driver.huawei_v5=missing
|
||||
driver.huawei_f_v5=missing
|
||||
driver.huawei_18000=missing
|
||||
driver.huawei_dorado=missing
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=complete
|
||||
driver.ibm_ds8k=complete
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -825,7 +813,6 @@ driver.huawei_f_v5=missing
|
||||
driver.huawei_v5=missing
|
||||
driver.huawei_18000=missing
|
||||
driver.huawei_dorado=missing
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=missing
|
||||
driver.ibm_ds8k=missing
|
||||
driver.ibm_flashsystem=missing
|
||||
@ -894,7 +881,6 @@ driver.huawei_f_v5=missing
|
||||
driver.huawei_v5=missing
|
||||
driver.huawei_18000=missing
|
||||
driver.huawei_dorado=missing
|
||||
driver.huawei_fusionstorage=missing
|
||||
driver.infinidat=missing
|
||||
driver.ibm_ds8k=missing
|
||||
driver.ibm_flashsystem=missing
|
||||
|
@ -85,3 +85,6 @@ release.
|
||||
* Tintri Storage Driver
|
||||
* Veritas HyperScale Storage Driver
|
||||
* Nexenta Edge Storage Driver
|
||||
|
||||
* Ussuri
|
||||
* Huawei FusionStorage Driver
|
||||
|
@ -0,0 +1,6 @@
|
||||
upgrade:
|
||||
- |
|
||||
The Huawei FusionStorage driver was marked unsupported in the
|
||||
Train release and has now been removed. All data on
|
||||
FusionStorage backends should be migrated to a supported
|
||||
storage backend before upgrading your Cinder installation.
|
Loading…
Reference in New Issue
Block a user