diff --git a/cinder/db/sqlalchemy/migrate_repo/versions/064_add_restore_volume_id_to_backups.py b/cinder/db/sqlalchemy/migrate_repo/versions/064_add_restore_volume_id_to_backups.py index 370a5351279..8de90c6e2dc 100644 --- a/cinder/db/sqlalchemy/migrate_repo/versions/064_add_restore_volume_id_to_backups.py +++ b/cinder/db/sqlalchemy/migrate_repo/versions/064_add_restore_volume_id_to_backups.py @@ -1,26 +1,26 @@ -# Copyright (c) 2015 Intel Corporation -# 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. - -from sqlalchemy import Column, MetaData, String, Table - - -def upgrade(migrate_engine): - meta = MetaData() - meta.bind = migrate_engine - - backups = Table('backups', meta, autoload=True) - restore_volume_id = Column('restore_volume_id', String(length=36)) - - backups.create_column(restore_volume_id) +# Copyright (c) 2015 Intel Corporation +# 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. + +from sqlalchemy import Column, MetaData, String, Table + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + + backups = Table('backups', meta, autoload=True) + restore_volume_id = Column('restore_volume_id', String(length=36)) + + backups.create_column(restore_volume_id) diff --git a/cinder/tests/unit/volume/drivers/emc/scaleio/test_consistencygroups.py b/cinder/tests/unit/volume/drivers/emc/scaleio/test_consistencygroups.py index bb1cc118ad7..44179369b68 100644 --- a/cinder/tests/unit/volume/drivers/emc/scaleio/test_consistencygroups.py +++ b/cinder/tests/unit/volume/drivers/emc/scaleio/test_consistencygroups.py @@ -1,211 +1,211 @@ -# Copyright (c) 2013 - 2016 EMC Corporation. -# 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 - -from cinder import context -from cinder.tests.unit.consistencygroup import fake_consistencygroup -from cinder.tests.unit import fake_constants as fake -from cinder.tests.unit import fake_snapshot -from cinder.tests.unit import fake_volume -from cinder.tests.unit.volume.drivers.emc import scaleio -from cinder.tests.unit.volume.drivers.emc.scaleio import mocks - - -class TestConsistencyGroups(scaleio.TestScaleIODriver): - """Test cases for ``ScaleIODriver consistency groups support``""" - - def setUp(self): - """Setup a test case environment. - - Creates a fake volume object and sets up the required API responses. - """ - super(TestConsistencyGroups, self).setUp() - self.ctx = context.RequestContext('fake', 'fake', auth_token=True) - self.consistency_group = ( - fake_consistencygroup.fake_consistencyobject_obj( - self.ctx, **{'id': fake.CONSISTENCY_GROUP_ID})) - fake_volume1 = fake_volume.fake_volume_obj( - self.ctx, - **{'id': fake.VOLUME_ID, 'provider_id': fake.PROVIDER_ID}) - fake_volume2 = fake_volume.fake_volume_obj( - self.ctx, - **{'id': fake.VOLUME2_ID, 'provider_id': fake.PROVIDER2_ID}) - fake_volume3 = fake_volume.fake_volume_obj( - self.ctx, - **{'id': fake.VOLUME3_ID, 'provider_id': fake.PROVIDER3_ID}) - fake_volume4 = fake_volume.fake_volume_obj( - self.ctx, - **{'id': fake.VOLUME4_ID, 'provider_id': fake.PROVIDER4_ID}) - self.volumes = [fake_volume1, fake_volume2] - self.volumes2 = [fake_volume3, fake_volume4] - fake_snapshot1 = fake_snapshot.fake_snapshot_obj( - self.ctx, - **{'id': fake.SNAPSHOT_ID, 'volume_id': fake.VOLUME_ID, - 'volume': fake_volume1}) - fake_snapshot2 = fake_snapshot.fake_snapshot_obj( - self.ctx, - **{'id': fake.SNAPSHOT2_ID, 'volume_id': fake.VOLUME2_ID, 'volume': - fake_volume2}) - self.snapshots = [fake_snapshot1, fake_snapshot2] - self.snapshot_reply = json.dumps({ - 'volumeIdList': ['sid1', 'sid2'], - 'snapshotGroupId': 'sgid1'}) - self.HTTPS_MOCK_RESPONSES = { - self.RESPONSE_MODE.Valid: { - 'instances/Volume::{}/action/removeVolume'.format( - fake_volume1['provider_id'] - ): fake_volume1['provider_id'], - 'instances/Volume::{}/action/removeVolume'.format( - fake_volume2['provider_id'] - ): fake_volume2['provider_id'], - 'instances/Volume::{}/action/removeMappedSdc'.format( - fake_volume1['provider_id'] - ): fake_volume1['provider_id'], - 'instances/Volume::{}/action/removeMappedSdc'.format( - fake_volume2['provider_id'] - ): fake_volume2['provider_id'], - 'instances/System/action/snapshotVolumes': - self.snapshot_reply, - }, - self.RESPONSE_MODE.BadStatus: { - 'instances/Volume::{}/action/removeVolume'.format( - fake_volume1['provider_id'] - ): mocks.MockHTTPSResponse( - { - 'errorCode': 401, - 'message': 'BadStatus Volume Test', - }, 401 - ), - 'instances/Volume::{}/action/removeVolume'.format( - fake_volume2['provider_id'] - ): mocks.MockHTTPSResponse( - { - 'errorCode': 401, - 'message': 'BadStatus Volume Test', - }, 401 - ), - 'instances/System/action/snapshotVolumes': - self.BAD_STATUS_RESPONSE - }, - } - - def _fake_cgsnapshot(self): - cgsnap = {'id': 'cgsid', 'name': 'testsnap', - 'consistencygroup_id': fake.CONSISTENCY_GROUP_ID, - 'status': 'available'} - return cgsnap - - def test_create_consistencygroup(self): - result = self.driver.create_consistencygroup(self.ctx, - self.consistency_group) - self.assertEqual('available', result['status']) - - def test_delete_consistencygroup_valid(self): - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - self.driver.configuration.set_override( - 'sio_unmap_volume_before_deletion', - override=True) - result_model_update, result_volumes_update = ( - self.driver.delete_consistencygroup(self.ctx, - self.consistency_group, - self.volumes)) - self.assertTrue(all(volume['status'] == 'deleted' for volume in - result_volumes_update)) - self.assertEqual('deleted', result_model_update['status']) - - def test_delete_consistency_group_fail(self): - self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) - result_model_update, result_volumes_update = ( - self.driver.delete_consistencygroup(self.ctx, - self.consistency_group, - self.volumes)) - self.assertTrue(any(volume['status'] == 'error_deleting' for volume in - result_volumes_update)) - self.assertIn(result_model_update['status'], - ['error_deleting', 'error']) - - def test_create_consistencygroup_from_cg(self): - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result_model_update, result_volumes_model_update = ( - self.driver.create_consistencygroup_from_src( - self.ctx, self.consistency_group, self.volumes2, - source_cg=self.consistency_group, source_vols=self.volumes)) - self.assertEqual('available', result_model_update['status']) - get_pid = lambda snapshot: snapshot['provider_id'] - volume_provider_list = list(map(get_pid, result_volumes_model_update)) - self.assertListEqual(volume_provider_list, ['sid1', 'sid2']) - - def test_create_consistencygroup_from_cgs(self): - self.snapshots[0]['provider_id'] = fake.PROVIDER_ID - self.snapshots[1]['provider_id'] = fake.PROVIDER2_ID - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result_model_update, result_volumes_model_update = ( - self.driver.create_consistencygroup_from_src( - self.ctx, self.consistency_group, self.volumes2, - cgsnapshot=self._fake_cgsnapshot(), - snapshots=self.snapshots)) - self.assertEqual('available', result_model_update['status']) - get_pid = lambda snapshot: snapshot['provider_id'] - volume_provider_list = list(map(get_pid, result_volumes_model_update)) - self.assertListEqual(['sid1', 'sid2'], volume_provider_list) - - @mock.patch('cinder.objects.snapshot') - @mock.patch('cinder.objects.snapshot') - def test_create_cgsnapshots(self, snapshot1, snapshot2): - type(snapshot1).volume = mock.PropertyMock( - return_value=self.volumes[0]) - type(snapshot2).volume = mock.PropertyMock( - return_value=self.volumes[1]) - snapshots = [snapshot1, snapshot2] - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result_model_update, result_snapshot_model_update = ( - self.driver.create_cgsnapshot( - self.ctx, - self._fake_cgsnapshot(), - snapshots - )) - self.assertEqual('available', result_model_update['status']) - self.assertTrue(all(snapshot['status'] == 'available' for snapshot in - result_snapshot_model_update)) - get_pid = lambda snapshot: snapshot['provider_id'] - snapshot_provider_list = list(map(get_pid, - result_snapshot_model_update)) - self.assertListEqual(['sid1', 'sid2'], snapshot_provider_list) - - @mock.patch('cinder.objects.snapshot') - @mock.patch('cinder.objects.snapshot') - def test_delete_cgsnapshots(self, snapshot1, snapshot2): - type(snapshot1).volume = mock.PropertyMock( - return_value=self.volumes[0]) - type(snapshot2).volume = mock.PropertyMock( - return_value=self.volumes[1]) - type(snapshot1).provider_id = mock.PropertyMock( - return_value=fake.PROVIDER_ID) - type(snapshot2).provider_id = mock.PropertyMock( - return_value=fake.PROVIDER2_ID) - snapshots = [snapshot1, snapshot2] - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result_model_update, result_snapshot_model_update = ( - self.driver.delete_cgsnapshot( - self.ctx, - self._fake_cgsnapshot(), - snapshots - )) - self.assertEqual('deleted', result_model_update['status']) - self.assertTrue(all(snapshot['status'] == 'deleted' for snapshot in - result_snapshot_model_update)) +# Copyright (c) 2013 - 2016 EMC Corporation. +# 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 + +from cinder import context +from cinder.tests.unit.consistencygroup import fake_consistencygroup +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_snapshot +from cinder.tests.unit import fake_volume +from cinder.tests.unit.volume.drivers.emc import scaleio +from cinder.tests.unit.volume.drivers.emc.scaleio import mocks + + +class TestConsistencyGroups(scaleio.TestScaleIODriver): + """Test cases for ``ScaleIODriver consistency groups support``""" + + def setUp(self): + """Setup a test case environment. + + Creates a fake volume object and sets up the required API responses. + """ + super(TestConsistencyGroups, self).setUp() + self.ctx = context.RequestContext('fake', 'fake', auth_token=True) + self.consistency_group = ( + fake_consistencygroup.fake_consistencyobject_obj( + self.ctx, **{'id': fake.CONSISTENCY_GROUP_ID})) + fake_volume1 = fake_volume.fake_volume_obj( + self.ctx, + **{'id': fake.VOLUME_ID, 'provider_id': fake.PROVIDER_ID}) + fake_volume2 = fake_volume.fake_volume_obj( + self.ctx, + **{'id': fake.VOLUME2_ID, 'provider_id': fake.PROVIDER2_ID}) + fake_volume3 = fake_volume.fake_volume_obj( + self.ctx, + **{'id': fake.VOLUME3_ID, 'provider_id': fake.PROVIDER3_ID}) + fake_volume4 = fake_volume.fake_volume_obj( + self.ctx, + **{'id': fake.VOLUME4_ID, 'provider_id': fake.PROVIDER4_ID}) + self.volumes = [fake_volume1, fake_volume2] + self.volumes2 = [fake_volume3, fake_volume4] + fake_snapshot1 = fake_snapshot.fake_snapshot_obj( + self.ctx, + **{'id': fake.SNAPSHOT_ID, 'volume_id': fake.VOLUME_ID, + 'volume': fake_volume1}) + fake_snapshot2 = fake_snapshot.fake_snapshot_obj( + self.ctx, + **{'id': fake.SNAPSHOT2_ID, 'volume_id': fake.VOLUME2_ID, 'volume': + fake_volume2}) + self.snapshots = [fake_snapshot1, fake_snapshot2] + self.snapshot_reply = json.dumps({ + 'volumeIdList': ['sid1', 'sid2'], + 'snapshotGroupId': 'sgid1'}) + self.HTTPS_MOCK_RESPONSES = { + self.RESPONSE_MODE.Valid: { + 'instances/Volume::{}/action/removeVolume'.format( + fake_volume1['provider_id'] + ): fake_volume1['provider_id'], + 'instances/Volume::{}/action/removeVolume'.format( + fake_volume2['provider_id'] + ): fake_volume2['provider_id'], + 'instances/Volume::{}/action/removeMappedSdc'.format( + fake_volume1['provider_id'] + ): fake_volume1['provider_id'], + 'instances/Volume::{}/action/removeMappedSdc'.format( + fake_volume2['provider_id'] + ): fake_volume2['provider_id'], + 'instances/System/action/snapshotVolumes': + self.snapshot_reply, + }, + self.RESPONSE_MODE.BadStatus: { + 'instances/Volume::{}/action/removeVolume'.format( + fake_volume1['provider_id'] + ): mocks.MockHTTPSResponse( + { + 'errorCode': 401, + 'message': 'BadStatus Volume Test', + }, 401 + ), + 'instances/Volume::{}/action/removeVolume'.format( + fake_volume2['provider_id'] + ): mocks.MockHTTPSResponse( + { + 'errorCode': 401, + 'message': 'BadStatus Volume Test', + }, 401 + ), + 'instances/System/action/snapshotVolumes': + self.BAD_STATUS_RESPONSE + }, + } + + def _fake_cgsnapshot(self): + cgsnap = {'id': 'cgsid', 'name': 'testsnap', + 'consistencygroup_id': fake.CONSISTENCY_GROUP_ID, + 'status': 'available'} + return cgsnap + + def test_create_consistencygroup(self): + result = self.driver.create_consistencygroup(self.ctx, + self.consistency_group) + self.assertEqual('available', result['status']) + + def test_delete_consistencygroup_valid(self): + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + self.driver.configuration.set_override( + 'sio_unmap_volume_before_deletion', + override=True) + result_model_update, result_volumes_update = ( + self.driver.delete_consistencygroup(self.ctx, + self.consistency_group, + self.volumes)) + self.assertTrue(all(volume['status'] == 'deleted' for volume in + result_volumes_update)) + self.assertEqual('deleted', result_model_update['status']) + + def test_delete_consistency_group_fail(self): + self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) + result_model_update, result_volumes_update = ( + self.driver.delete_consistencygroup(self.ctx, + self.consistency_group, + self.volumes)) + self.assertTrue(any(volume['status'] == 'error_deleting' for volume in + result_volumes_update)) + self.assertIn(result_model_update['status'], + ['error_deleting', 'error']) + + def test_create_consistencygroup_from_cg(self): + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + result_model_update, result_volumes_model_update = ( + self.driver.create_consistencygroup_from_src( + self.ctx, self.consistency_group, self.volumes2, + source_cg=self.consistency_group, source_vols=self.volumes)) + self.assertEqual('available', result_model_update['status']) + get_pid = lambda snapshot: snapshot['provider_id'] + volume_provider_list = list(map(get_pid, result_volumes_model_update)) + self.assertListEqual(volume_provider_list, ['sid1', 'sid2']) + + def test_create_consistencygroup_from_cgs(self): + self.snapshots[0]['provider_id'] = fake.PROVIDER_ID + self.snapshots[1]['provider_id'] = fake.PROVIDER2_ID + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + result_model_update, result_volumes_model_update = ( + self.driver.create_consistencygroup_from_src( + self.ctx, self.consistency_group, self.volumes2, + cgsnapshot=self._fake_cgsnapshot(), + snapshots=self.snapshots)) + self.assertEqual('available', result_model_update['status']) + get_pid = lambda snapshot: snapshot['provider_id'] + volume_provider_list = list(map(get_pid, result_volumes_model_update)) + self.assertListEqual(['sid1', 'sid2'], volume_provider_list) + + @mock.patch('cinder.objects.snapshot') + @mock.patch('cinder.objects.snapshot') + def test_create_cgsnapshots(self, snapshot1, snapshot2): + type(snapshot1).volume = mock.PropertyMock( + return_value=self.volumes[0]) + type(snapshot2).volume = mock.PropertyMock( + return_value=self.volumes[1]) + snapshots = [snapshot1, snapshot2] + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + result_model_update, result_snapshot_model_update = ( + self.driver.create_cgsnapshot( + self.ctx, + self._fake_cgsnapshot(), + snapshots + )) + self.assertEqual('available', result_model_update['status']) + self.assertTrue(all(snapshot['status'] == 'available' for snapshot in + result_snapshot_model_update)) + get_pid = lambda snapshot: snapshot['provider_id'] + snapshot_provider_list = list(map(get_pid, + result_snapshot_model_update)) + self.assertListEqual(['sid1', 'sid2'], snapshot_provider_list) + + @mock.patch('cinder.objects.snapshot') + @mock.patch('cinder.objects.snapshot') + def test_delete_cgsnapshots(self, snapshot1, snapshot2): + type(snapshot1).volume = mock.PropertyMock( + return_value=self.volumes[0]) + type(snapshot2).volume = mock.PropertyMock( + return_value=self.volumes[1]) + type(snapshot1).provider_id = mock.PropertyMock( + return_value=fake.PROVIDER_ID) + type(snapshot2).provider_id = mock.PropertyMock( + return_value=fake.PROVIDER2_ID) + snapshots = [snapshot1, snapshot2] + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + result_model_update, result_snapshot_model_update = ( + self.driver.delete_cgsnapshot( + self.ctx, + self._fake_cgsnapshot(), + snapshots + )) + self.assertEqual('deleted', result_model_update['status']) + self.assertTrue(all(snapshot['status'] == 'deleted' for snapshot in + result_snapshot_model_update)) diff --git a/cinder/tests/unit/volume/drivers/emc/scaleio/test_initialize_connection.py b/cinder/tests/unit/volume/drivers/emc/scaleio/test_initialize_connection.py index 7e8d58238c9..a23e3872ee4 100644 --- a/cinder/tests/unit/volume/drivers/emc/scaleio/test_initialize_connection.py +++ b/cinder/tests/unit/volume/drivers/emc/scaleio/test_initialize_connection.py @@ -1,116 +1,116 @@ -# Copyright (c) 2015 EMC Corporation. -# 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 mock - -from cinder import context -from cinder.tests.unit import fake_constants as fake -from cinder.tests.unit import fake_volume -from cinder.tests.unit.volume.drivers.emc import scaleio - - -class TestInitializeConnection(scaleio.TestScaleIODriver): - def setUp(self): - """Setup a test case environment.""" - - super(TestInitializeConnection, self).setUp() - self.connector = {} - self.ctx = ( - context.RequestContext('fake', 'fake', True, auth_token=True)) - self.volume = fake_volume.fake_volume_obj( - self.ctx, **{'provider_id': fake.PROVIDER_ID}) - - def test_only_qos(self): - qos = {'maxIOPS': 1000, 'maxBWS': 2048} - extraspecs = {} - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertEqual(1000, int(connection_properties['iopsLimit'])) - self.assertEqual(2048, int(connection_properties['bandwidthLimit'])) - - def test_no_qos(self): - qos = {} - extraspecs = {} - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertIsNone(connection_properties['iopsLimit']) - self.assertIsNone(connection_properties['bandwidthLimit']) - - def test_only_extraspecs(self): - qos = {} - extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4096} - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertEqual(2000, int(connection_properties['iopsLimit'])) - self.assertEqual(4096, int(connection_properties['bandwidthLimit'])) - - def test_qos_and_extraspecs(self): - qos = {'maxIOPS': 1000, 'maxBWS': 3072} - extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4000} - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertEqual(1000, int(connection_properties['iopsLimit'])) - self.assertEqual(3072, int(connection_properties['bandwidthLimit'])) - - def test_qos_scaling_and_max(self): - qos = {'maxIOPS': 100, 'maxBWS': 2048, 'maxIOPSperGB': 10, - 'maxBWSperGB': 128} - extraspecs = {} - self.volume.size = 8 - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertEqual(80, int(connection_properties['iopsLimit'])) - self.assertEqual(1024, int(connection_properties['bandwidthLimit'])) - - self.volume.size = 24 - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertEqual(100, int(connection_properties['iopsLimit'])) - self.assertEqual(2048, int(connection_properties['bandwidthLimit'])) - - def test_qos_scaling_no_max(self): - qos = {'maxIOPSperGB': 10, 'maxBWSperGB': 128} - extraspecs = {} - self.volume.size = 8 - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertEqual(80, int(connection_properties['iopsLimit'])) - self.assertEqual(1024, int(connection_properties['bandwidthLimit'])) - - def test_qos_round_up(self): - qos = {'maxBWS': 2000, 'maxBWSperGB': 100} - extraspecs = {} - self.volume.size = 8 - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertEqual(1024, int(connection_properties['bandwidthLimit'])) - - self.volume.size = 24 - connection_properties = ( - self._initialize_connection(qos, extraspecs)['data']) - self.assertEqual(2048, int(connection_properties['bandwidthLimit'])) - - def test_vol_id(self): - extraspecs = qos = {} - connection_properties = ( - self._initialize_connection(extraspecs, qos)['data']) - self.assertEqual(fake.PROVIDER_ID, - connection_properties['scaleIO_volume_id']) - - def _initialize_connection(self, qos, extraspecs): - self.driver._get_volumetype_qos = mock.MagicMock() - self.driver._get_volumetype_qos.return_value = qos - self.driver._get_volumetype_extraspecs = mock.MagicMock() - self.driver._get_volumetype_extraspecs.return_value = extraspecs - return self.driver.initialize_connection(self.volume, self.connector) +# Copyright (c) 2015 EMC Corporation. +# 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 mock + +from cinder import context +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_volume +from cinder.tests.unit.volume.drivers.emc import scaleio + + +class TestInitializeConnection(scaleio.TestScaleIODriver): + def setUp(self): + """Setup a test case environment.""" + + super(TestInitializeConnection, self).setUp() + self.connector = {} + self.ctx = ( + context.RequestContext('fake', 'fake', True, auth_token=True)) + self.volume = fake_volume.fake_volume_obj( + self.ctx, **{'provider_id': fake.PROVIDER_ID}) + + def test_only_qos(self): + qos = {'maxIOPS': 1000, 'maxBWS': 2048} + extraspecs = {} + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertEqual(1000, int(connection_properties['iopsLimit'])) + self.assertEqual(2048, int(connection_properties['bandwidthLimit'])) + + def test_no_qos(self): + qos = {} + extraspecs = {} + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertIsNone(connection_properties['iopsLimit']) + self.assertIsNone(connection_properties['bandwidthLimit']) + + def test_only_extraspecs(self): + qos = {} + extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4096} + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertEqual(2000, int(connection_properties['iopsLimit'])) + self.assertEqual(4096, int(connection_properties['bandwidthLimit'])) + + def test_qos_and_extraspecs(self): + qos = {'maxIOPS': 1000, 'maxBWS': 3072} + extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4000} + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertEqual(1000, int(connection_properties['iopsLimit'])) + self.assertEqual(3072, int(connection_properties['bandwidthLimit'])) + + def test_qos_scaling_and_max(self): + qos = {'maxIOPS': 100, 'maxBWS': 2048, 'maxIOPSperGB': 10, + 'maxBWSperGB': 128} + extraspecs = {} + self.volume.size = 8 + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertEqual(80, int(connection_properties['iopsLimit'])) + self.assertEqual(1024, int(connection_properties['bandwidthLimit'])) + + self.volume.size = 24 + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertEqual(100, int(connection_properties['iopsLimit'])) + self.assertEqual(2048, int(connection_properties['bandwidthLimit'])) + + def test_qos_scaling_no_max(self): + qos = {'maxIOPSperGB': 10, 'maxBWSperGB': 128} + extraspecs = {} + self.volume.size = 8 + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertEqual(80, int(connection_properties['iopsLimit'])) + self.assertEqual(1024, int(connection_properties['bandwidthLimit'])) + + def test_qos_round_up(self): + qos = {'maxBWS': 2000, 'maxBWSperGB': 100} + extraspecs = {} + self.volume.size = 8 + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertEqual(1024, int(connection_properties['bandwidthLimit'])) + + self.volume.size = 24 + connection_properties = ( + self._initialize_connection(qos, extraspecs)['data']) + self.assertEqual(2048, int(connection_properties['bandwidthLimit'])) + + def test_vol_id(self): + extraspecs = qos = {} + connection_properties = ( + self._initialize_connection(extraspecs, qos)['data']) + self.assertEqual(fake.PROVIDER_ID, + connection_properties['scaleIO_volume_id']) + + def _initialize_connection(self, qos, extraspecs): + self.driver._get_volumetype_qos = mock.MagicMock() + self.driver._get_volumetype_qos.return_value = qos + self.driver._get_volumetype_extraspecs = mock.MagicMock() + self.driver._get_volumetype_extraspecs.return_value = extraspecs + return self.driver.initialize_connection(self.volume, self.connector) diff --git a/cinder/tests/unit/volume/drivers/emc/scaleio/test_manage_existing.py b/cinder/tests/unit/volume/drivers/emc/scaleio/test_manage_existing.py index bf00cc49ed6..6db5f28fb48 100644 --- a/cinder/tests/unit/volume/drivers/emc/scaleio/test_manage_existing.py +++ b/cinder/tests/unit/volume/drivers/emc/scaleio/test_manage_existing.py @@ -1,127 +1,127 @@ -# Copyright (c) 2016 EMC Corporation. -# 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. - -from cinder import context -from cinder import exception -from cinder.tests.unit import fake_constants as fake -from cinder.tests.unit import fake_volume -from cinder.tests.unit.volume.drivers.emc import scaleio -from cinder.tests.unit.volume.drivers.emc.scaleio import mocks -from cinder.volume import volume_types -from mock import patch -from six.moves import urllib - - -class TestManageExisting(scaleio.TestScaleIODriver): - """Test cases for ``ScaleIODriver.manage_existing()``""" - - def setUp(self): - """Setup a test case environment. - - Creates a fake volume object and sets up the required API responses. - """ - super(TestManageExisting, self).setUp() - ctx = context.RequestContext('fake', 'fake', auth_token=True) - self.volume = fake_volume.fake_volume_obj( - ctx, **{'provider_id': fake.PROVIDER_ID}) - self.volume_attached = fake_volume.fake_volume_obj( - ctx, **{'provider_id': fake.PROVIDER2_ID}) - self.volume_no_provider_id = fake_volume.fake_volume_obj(ctx) - self.volume_name_2x_enc = urllib.parse.quote( - urllib.parse.quote(self.driver._id_to_base64(self.volume.id)) - ) - - self.HTTPS_MOCK_RESPONSES = { - self.RESPONSE_MODE.Valid: { - 'instances/Volume::' + self.volume['provider_id']: - mocks.MockHTTPSResponse({ - 'id': fake.PROVIDER_ID, - 'sizeInKb': 8000000, - 'mappedSdcInfo': None - }, 200) - }, - self.RESPONSE_MODE.BadStatus: { - 'instances/Volume::' + self.volume['provider_id']: - mocks.MockHTTPSResponse({ - 'errorCode': 401, - 'message': 'BadStatus Volume Test', - }, 401), - 'instances/Volume::' + self.volume_attached['provider_id']: - mocks.MockHTTPSResponse({ - 'id': fake.PROVIDER2_ID, - 'sizeInKb': 8388608, - 'mappedSdcInfo': 'Mapped' - }, 200) - } - } - - def test_no_source_id(self): - existing_ref = {'source-name': 'scaleioVolName'} - self.assertRaises(exception.ManageExistingInvalidReference, - self.driver.manage_existing, self.volume, - existing_ref) - - def test_no_type_id(self): - self.volume['volume_type_id'] = None - existing_ref = {'source-id': fake.PROVIDER_ID} - self.assertRaises(exception.ManageExistingVolumeTypeMismatch, - self.driver.manage_existing, self.volume, - existing_ref) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_volume_not_found(self, _mock_volume_type): - self.volume['volume_type_id'] = fake.VOLUME_TYPE_ID - existing_ref = {'source-id': fake.PROVIDER_ID} - self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) - self.assertRaises(exception.ManageExistingInvalidReference, - self.driver.manage_existing, self.volume, - existing_ref) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_volume_attached(self, _mock_volume_type): - self.volume_attached['volume_type_id'] = fake.VOLUME_TYPE_ID - existing_ref = {'source-id': fake.PROVIDER2_ID} - self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) - self.assertRaises(exception.ManageExistingInvalidReference, - self.driver.manage_existing, self.volume_attached, - existing_ref) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_manage_get_size_calc(self, _mock_volume_type): - self.volume['volume_type_id'] = fake.VOLUME_TYPE_ID - existing_ref = {'source-id': fake.PROVIDER_ID} - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result = self.driver.manage_existing_get_size(self.volume, - existing_ref) - self.assertEqual(8, result) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_manage_existing_valid(self, _mock_volume_type): - self.volume['volume_type_id'] = fake.VOLUME_TYPE_ID - existing_ref = {'source-id': fake.PROVIDER_ID} - result = self.driver.manage_existing(self.volume, existing_ref) - self.assertEqual(fake.PROVIDER_ID, result['provider_id']) +# Copyright (c) 2016 EMC Corporation. +# 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. + +from cinder import context +from cinder import exception +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_volume +from cinder.tests.unit.volume.drivers.emc import scaleio +from cinder.tests.unit.volume.drivers.emc.scaleio import mocks +from cinder.volume import volume_types +from mock import patch +from six.moves import urllib + + +class TestManageExisting(scaleio.TestScaleIODriver): + """Test cases for ``ScaleIODriver.manage_existing()``""" + + def setUp(self): + """Setup a test case environment. + + Creates a fake volume object and sets up the required API responses. + """ + super(TestManageExisting, self).setUp() + ctx = context.RequestContext('fake', 'fake', auth_token=True) + self.volume = fake_volume.fake_volume_obj( + ctx, **{'provider_id': fake.PROVIDER_ID}) + self.volume_attached = fake_volume.fake_volume_obj( + ctx, **{'provider_id': fake.PROVIDER2_ID}) + self.volume_no_provider_id = fake_volume.fake_volume_obj(ctx) + self.volume_name_2x_enc = urllib.parse.quote( + urllib.parse.quote(self.driver._id_to_base64(self.volume.id)) + ) + + self.HTTPS_MOCK_RESPONSES = { + self.RESPONSE_MODE.Valid: { + 'instances/Volume::' + self.volume['provider_id']: + mocks.MockHTTPSResponse({ + 'id': fake.PROVIDER_ID, + 'sizeInKb': 8000000, + 'mappedSdcInfo': None + }, 200) + }, + self.RESPONSE_MODE.BadStatus: { + 'instances/Volume::' + self.volume['provider_id']: + mocks.MockHTTPSResponse({ + 'errorCode': 401, + 'message': 'BadStatus Volume Test', + }, 401), + 'instances/Volume::' + self.volume_attached['provider_id']: + mocks.MockHTTPSResponse({ + 'id': fake.PROVIDER2_ID, + 'sizeInKb': 8388608, + 'mappedSdcInfo': 'Mapped' + }, 200) + } + } + + def test_no_source_id(self): + existing_ref = {'source-name': 'scaleioVolName'} + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing, self.volume, + existing_ref) + + def test_no_type_id(self): + self.volume['volume_type_id'] = None + existing_ref = {'source-id': fake.PROVIDER_ID} + self.assertRaises(exception.ManageExistingVolumeTypeMismatch, + self.driver.manage_existing, self.volume, + existing_ref) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_volume_not_found(self, _mock_volume_type): + self.volume['volume_type_id'] = fake.VOLUME_TYPE_ID + existing_ref = {'source-id': fake.PROVIDER_ID} + self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing, self.volume, + existing_ref) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_volume_attached(self, _mock_volume_type): + self.volume_attached['volume_type_id'] = fake.VOLUME_TYPE_ID + existing_ref = {'source-id': fake.PROVIDER2_ID} + self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing, self.volume_attached, + existing_ref) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_manage_get_size_calc(self, _mock_volume_type): + self.volume['volume_type_id'] = fake.VOLUME_TYPE_ID + existing_ref = {'source-id': fake.PROVIDER_ID} + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + result = self.driver.manage_existing_get_size(self.volume, + existing_ref) + self.assertEqual(8, result) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_manage_existing_valid(self, _mock_volume_type): + self.volume['volume_type_id'] = fake.VOLUME_TYPE_ID + existing_ref = {'source-id': fake.PROVIDER_ID} + result = self.driver.manage_existing(self.volume, existing_ref) + self.assertEqual(fake.PROVIDER_ID, result['provider_id']) diff --git a/cinder/tests/unit/volume/drivers/emc/scaleio/test_manage_existing_snapshot.py b/cinder/tests/unit/volume/drivers/emc/scaleio/test_manage_existing_snapshot.py index 26adc8d41fa..bde22004974 100644 --- a/cinder/tests/unit/volume/drivers/emc/scaleio/test_manage_existing_snapshot.py +++ b/cinder/tests/unit/volume/drivers/emc/scaleio/test_manage_existing_snapshot.py @@ -1,154 +1,154 @@ -# Copyright (c) 2016 EMC Corporation. -# 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. -from mock import patch - -from cinder import context -from cinder import exception -from cinder.tests.unit import fake_constants as fake -from cinder.tests.unit import fake_snapshot -from cinder.tests.unit import fake_volume -from cinder.tests.unit.volume.drivers.emc import scaleio -from cinder.tests.unit.volume.drivers.emc.scaleio import mocks -from cinder.volume import volume_types - - -class TestManageExistingSnapshot(scaleio.TestScaleIODriver): - """Test cases for ``ScaleIODriver.manage_existing_snapshot()``""" - - def setUp(self): - """Setup a test case environment. - - Creates a fake volume object and sets up the required API responses. - """ - super(TestManageExistingSnapshot, self).setUp() - ctx = context.RequestContext('fake', 'fake', auth_token=True) - self.volume = fake_volume.fake_volume_obj( - ctx, **{'provider_id': fake.PROVIDER_ID}) - self.snapshot = fake_snapshot.fake_snapshot_obj( - ctx, **{'provider_id': fake.PROVIDER2_ID}) - self.snapshot2 = fake_snapshot.fake_snapshot_obj( - ctx, **{'provider_id': fake.PROVIDER3_ID}) - self.snapshot.volume = self.snapshot2.volume = self.volume - self.snapshot['volume_type_id'] = fake.VOLUME_TYPE_ID - self.snapshot2['volume_type_id'] = fake.VOLUME_TYPE_ID - self.snapshot_attached = fake_snapshot.fake_snapshot_obj( - ctx, **{'provider_id': fake.PROVIDER3_ID}) - - self.HTTPS_MOCK_RESPONSES = { - self.RESPONSE_MODE.Valid: { - 'instances/Volume::' + self.volume['provider_id']: - mocks.MockHTTPSResponse({ - 'id': fake.PROVIDER_ID, - 'sizeInKb': 8388608, - 'mappedSdcInfo': None, - 'ancestorVolumeId': None - }, 200), - 'instances/Volume::' + self.snapshot['provider_id']: - mocks.MockHTTPSResponse({ - 'id': fake.PROVIDER2_ID, - 'sizeInKb': 8000000, - 'mappedSdcInfo': None, - 'ancestorVolumeId': fake.PROVIDER_ID - }, 200), - 'instances/Volume::' + self.snapshot2['provider_id']: - mocks.MockHTTPSResponse({ - 'id': fake.PROVIDER3_ID, - 'sizeInKb': 8388608, - 'mappedSdcInfo': None, - 'ancestorVolumeId': fake.PROVIDER2_ID - }, 200) - }, - self.RESPONSE_MODE.BadStatus: { - 'instances/Volume::' + self.snapshot['provider_id']: - mocks.MockHTTPSResponse({ - 'errorCode': 401, - 'message': 'BadStatus Volume Test', - }, 401), - 'instances/Volume::' + self.snapshot2['provider_id']: - mocks.MockHTTPSResponse({ - 'id': fake.PROVIDER3_ID, - 'sizeInKb': 8388608, - 'ancestorVolumeId': fake.PROVIDER2_ID - }, 200), - 'instances/Volume::' + self.snapshot_attached['provider_id']: - mocks.MockHTTPSResponse({ - 'id': fake.PROVIDER3_ID, - 'sizeInKb': 8388608, - 'mappedSdcInfo': 'Mapped', - 'ancestorVolumeId': fake.PROVIDER_ID - }, 200) - } - } - - def test_no_source_id(self): - existing_ref = {'source-name': 'scaleioSnapName'} - self.assertRaises(exception.ManageExistingInvalidReference, - self.driver.manage_existing_snapshot, self.snapshot, - existing_ref) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_snapshot_not_found(self, _mock_volume_type): - existing_ref = {'source-id': fake.PROVIDER2_ID} - self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) - self.assertRaises(exception.ManageExistingInvalidReference, - self.driver.manage_existing_snapshot, self.snapshot, - existing_ref) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_snapshot_attached(self, _mock_volume_type): - self.snapshot_attached['volume_type_id'] = fake.VOLUME_TYPE_ID - existing_ref = {'source-id': fake.PROVIDER2_ID} - self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) - self.assertRaises(exception.ManageExistingInvalidReference, - self.driver.manage_existing_snapshot, - self.snapshot_attached, existing_ref) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_different_ancestor(self, _mock_volume_type): - existing_ref = {'source-id': fake.PROVIDER3_ID} - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - self.assertRaises(exception.ManageExistingInvalidReference, - self.driver.manage_existing_snapshot, - self.snapshot2, existing_ref) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_manage_snapshot_get_size_calc(self, _mock_volume_type): - existing_ref = {'source-id': fake.PROVIDER2_ID} - self.set_https_response_mode(self.RESPONSE_MODE.Valid) - result = self.driver.manage_existing_snapshot_get_size( - self.snapshot, existing_ref) - self.assertEqual(8, result) - - @patch.object( - volume_types, - 'get_volume_type', - return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) - def test_manage_existing_snapshot_valid(self, _mock_volume_type): - existing_ref = {'source-id': fake.PROVIDER2_ID} - result = self.driver.manage_existing_snapshot( - self.snapshot, existing_ref) - self.assertEqual(fake.PROVIDER2_ID, result['provider_id']) +# Copyright (c) 2016 EMC Corporation. +# 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. +from mock import patch + +from cinder import context +from cinder import exception +from cinder.tests.unit import fake_constants as fake +from cinder.tests.unit import fake_snapshot +from cinder.tests.unit import fake_volume +from cinder.tests.unit.volume.drivers.emc import scaleio +from cinder.tests.unit.volume.drivers.emc.scaleio import mocks +from cinder.volume import volume_types + + +class TestManageExistingSnapshot(scaleio.TestScaleIODriver): + """Test cases for ``ScaleIODriver.manage_existing_snapshot()``""" + + def setUp(self): + """Setup a test case environment. + + Creates a fake volume object and sets up the required API responses. + """ + super(TestManageExistingSnapshot, self).setUp() + ctx = context.RequestContext('fake', 'fake', auth_token=True) + self.volume = fake_volume.fake_volume_obj( + ctx, **{'provider_id': fake.PROVIDER_ID}) + self.snapshot = fake_snapshot.fake_snapshot_obj( + ctx, **{'provider_id': fake.PROVIDER2_ID}) + self.snapshot2 = fake_snapshot.fake_snapshot_obj( + ctx, **{'provider_id': fake.PROVIDER3_ID}) + self.snapshot.volume = self.snapshot2.volume = self.volume + self.snapshot['volume_type_id'] = fake.VOLUME_TYPE_ID + self.snapshot2['volume_type_id'] = fake.VOLUME_TYPE_ID + self.snapshot_attached = fake_snapshot.fake_snapshot_obj( + ctx, **{'provider_id': fake.PROVIDER3_ID}) + + self.HTTPS_MOCK_RESPONSES = { + self.RESPONSE_MODE.Valid: { + 'instances/Volume::' + self.volume['provider_id']: + mocks.MockHTTPSResponse({ + 'id': fake.PROVIDER_ID, + 'sizeInKb': 8388608, + 'mappedSdcInfo': None, + 'ancestorVolumeId': None + }, 200), + 'instances/Volume::' + self.snapshot['provider_id']: + mocks.MockHTTPSResponse({ + 'id': fake.PROVIDER2_ID, + 'sizeInKb': 8000000, + 'mappedSdcInfo': None, + 'ancestorVolumeId': fake.PROVIDER_ID + }, 200), + 'instances/Volume::' + self.snapshot2['provider_id']: + mocks.MockHTTPSResponse({ + 'id': fake.PROVIDER3_ID, + 'sizeInKb': 8388608, + 'mappedSdcInfo': None, + 'ancestorVolumeId': fake.PROVIDER2_ID + }, 200) + }, + self.RESPONSE_MODE.BadStatus: { + 'instances/Volume::' + self.snapshot['provider_id']: + mocks.MockHTTPSResponse({ + 'errorCode': 401, + 'message': 'BadStatus Volume Test', + }, 401), + 'instances/Volume::' + self.snapshot2['provider_id']: + mocks.MockHTTPSResponse({ + 'id': fake.PROVIDER3_ID, + 'sizeInKb': 8388608, + 'ancestorVolumeId': fake.PROVIDER2_ID + }, 200), + 'instances/Volume::' + self.snapshot_attached['provider_id']: + mocks.MockHTTPSResponse({ + 'id': fake.PROVIDER3_ID, + 'sizeInKb': 8388608, + 'mappedSdcInfo': 'Mapped', + 'ancestorVolumeId': fake.PROVIDER_ID + }, 200) + } + } + + def test_no_source_id(self): + existing_ref = {'source-name': 'scaleioSnapName'} + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing_snapshot, self.snapshot, + existing_ref) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_snapshot_not_found(self, _mock_volume_type): + existing_ref = {'source-id': fake.PROVIDER2_ID} + self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing_snapshot, self.snapshot, + existing_ref) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_snapshot_attached(self, _mock_volume_type): + self.snapshot_attached['volume_type_id'] = fake.VOLUME_TYPE_ID + existing_ref = {'source-id': fake.PROVIDER2_ID} + self.set_https_response_mode(self.RESPONSE_MODE.BadStatus) + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing_snapshot, + self.snapshot_attached, existing_ref) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_different_ancestor(self, _mock_volume_type): + existing_ref = {'source-id': fake.PROVIDER3_ID} + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + self.assertRaises(exception.ManageExistingInvalidReference, + self.driver.manage_existing_snapshot, + self.snapshot2, existing_ref) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_manage_snapshot_get_size_calc(self, _mock_volume_type): + existing_ref = {'source-id': fake.PROVIDER2_ID} + self.set_https_response_mode(self.RESPONSE_MODE.Valid) + result = self.driver.manage_existing_snapshot_get_size( + self.snapshot, existing_ref) + self.assertEqual(8, result) + + @patch.object( + volume_types, + 'get_volume_type', + return_value={'extra_specs': {'volume_backend_name': 'ScaleIO'}}) + def test_manage_existing_snapshot_valid(self, _mock_volume_type): + existing_ref = {'source-id': fake.PROVIDER2_ID} + result = self.driver.manage_existing_snapshot( + self.snapshot, existing_ref) + self.assertEqual(fake.PROVIDER2_ID, result['provider_id']) diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py index 43d9712976a..2761669c59d 100644 --- a/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py +++ b/cinder/tests/unit/volume/drivers/fusionstorage/test_dsware.py @@ -1,771 +1,771 @@ -# Copyright (c) 2013 - 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. -""" -Unit Tests for Huawei FusionStorage drivers. -""" - -import mock -from oslo_config import cfg -from oslo_service import loopingcall - -from cinder import exception -from cinder.image import image_utils -from cinder import test -from cinder.volume import configuration as conf -from cinder.volume.drivers.fusionstorage import dsware -from cinder.volume.drivers.fusionstorage import fspythonapi - - -test_volume = {'name': 'test_vol1', - 'size': 4, - 'volume_metadata': '', - 'host': 'host01@dsware', - 'instance_uuid': None, - 'provider_id': '127.0.0.1'} - -test_src_volume = {'name': 'test_vol2', - 'size': 4, - 'status': 'available'} - -test_snapshot = { - 'name': 'test_snapshot1', - 'volume_id': 'vol1', - 'volume_size': '2'} - - -class FakeDSWAREDriver(dsware.DSWAREDriver): - def __init__(self): - configuration = conf.Configuration( - [ - cfg.StrOpt('fake'), - ], - None - ) - super(FakeDSWAREDriver, self).__init__(configuration=configuration) - self.dsware_client = fspythonapi.FSPythonApi() - self.manage_ip = '127.0.0.1' - self.pool_type = '1' - - -class DSwareDriverTestCase(test.TestCase): - def setUp(self): - super(DSwareDriverTestCase, self).setUp() - self.driver = FakeDSWAREDriver() - - def test_private_get_dsware_manage_ip(self): - retval = self.driver._get_dsware_manage_ip(test_volume) - self.assertEqual('127.0.0.1', retval) - - test_volume_fail = {'name': 'test_vol', - 'size': 4, - 'volume_metadata': '', - 'host': 'host01@dsware', - 'provider_id': None} - self.assertRaises(exception.CinderException, - self.driver._get_dsware_manage_ip, - test_volume_fail) - - def test_private_get_poolid_from_host(self): - retval = self.driver._get_poolid_from_host( - 'abc@fusionstorage_sas2copy#0') - self.assertEqual('0', retval) - - retval = self.driver._get_poolid_from_host( - 'abc@fusionstorage_sas2copy@0') - self.assertEqual(self.driver.pool_type, retval) - - retval = self.driver._get_poolid_from_host(None) - self.assertEqual(self.driver.pool_type, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') - def test_private_create_volume_old_version(self, mock_get_poolid, - mock_query_dsware, - mock_create_volume): - # query_dsware_version return 1, old version - mock_query_dsware.return_value = 1 - mock_create_volume.return_value = 0 - self.driver._create_volume(test_volume['name'], - test_volume['size'], - True, - 'abc@fusionstorage_sas2copy') - mock_create_volume.assert_called_with(test_volume['name'], 0, - test_volume['size'], 1) - - self.driver._create_volume(test_volume['name'], - test_volume['size'], - False, - 'abc@fusionstorage_sas2copy') - mock_create_volume.assert_called_with(test_volume['name'], 0, - test_volume['size'], 0) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') - def test_private_create_volume_new_version(self, mock_get_poolid, - mock_query_dsware, - mock_create_volume): - # query_dsware_version return 0, new version - mock_query_dsware.return_value = 0 - mock_get_poolid.return_value = 0 - mock_create_volume.return_value = 0 - self.driver._create_volume(test_volume['name'], - test_volume['size'], - True, - 'abcE@fusionstorage_sas2copy#0') - mock_create_volume.assert_called_with(test_volume['name'], 0, - test_volume['size'], 1) - - self.driver._create_volume(test_volume['name'], - test_volume['size'], - False, - 'abc@fusionstorage_sas2copy#0') - mock_create_volume.assert_called_with(test_volume['name'], 0, - test_volume['size'], 0) - - mock_query_dsware.return_value = 0 - mock_get_poolid.return_value = 1 - mock_create_volume.return_value = 0 - self.driver._create_volume(test_volume['name'], - test_volume['size'], - True, - 'abc@fusionstorage_sas2copy#1') - mock_create_volume.assert_called_with(test_volume['name'], 1, - test_volume['size'], 1) - - self.driver._create_volume(test_volume['name'], - test_volume['size'], - False, - 'abc@fusionstorage_sas2copy#1') - mock_create_volume.assert_called_with(test_volume['name'], 1, - test_volume['size'], 0) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') - def test_private_create_volume_query_version_fail(self, mock_get_poolid, - mock_query_dsware, - mock_create_volume): - # query_dsware_version return 500015, query dsware version failed! - mock_query_dsware.return_value = 500015 - self.assertRaises(exception.CinderException, - self.driver._create_volume, - test_volume['name'], - test_volume['size'], - True, - 'abc@fusionstorage_sas2copy#0') - self.assertRaises(exception.CinderException, - self.driver._create_volume, - test_volume['name'], - test_volume['size'], - False, - 'abc@fusionstorage_sas2copy#0') - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') - def test_private_create_volume_fail(self, mock_get_poolid, - mock_query_dsware, - mock_create_volume): - mock_query_dsware.return_value = 1 - # create_volume return 1, create volume failed - mock_create_volume.return_value = 1 - self.assertRaises(exception.CinderException, - self.driver._create_volume, - test_volume['name'], - test_volume['size'], - True, - 'abc@fusionstorage_sas2copy#0') - self.assertRaises(exception.CinderException, - self.driver._create_volume, - test_volume['name'], - test_volume['size'], - False, - 'abc@fusionstorage_sas2copy#0') - - @mock.patch.object(dsware.DSWAREDriver, '_create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') - def test_create_volume(self, mock_get_manage_ip, mock_create_volume): - # success - mock_get_manage_ip.return_value = self.driver.manage_ip - retval = self.driver.create_volume(test_volume) - self.assertEqual({"provider_id": self.driver.manage_ip}, - retval) - - # failure - mock_create_volume.side_effect = exception.CinderException( - 'DSWARE Create Volume failed!') - - self.assertRaises(exception.CinderException, - self.driver.create_volume, - test_volume) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_snap') - def test_private_create_volume_from_snap(self, mock_create_volume): - mock_create_volume.side_effect = [0, 1] - self.driver._create_volume_from_snap(test_volume['name'], - test_volume['size'], - test_snapshot['name']) - # failure - self.assertRaises(exception.CinderException, - self.driver._create_volume_from_snap, - test_volume['name'], test_volume['size'], - test_snapshot['name']) - - @mock.patch.object(fspythonapi.FSPythonApi, 'extend_volume') - def test_extend_volume(self, mock_extend_volume): - mock_extend_volume.return_value = 0 - self.driver.extend_volume(test_volume, 5) - - mock_extend_volume.return_value = 0 - self.assertRaises(exception.CinderException, - self.driver.extend_volume, - test_volume, - 3) - - mock_extend_volume.return_value = 1 - self.assertRaises(exception.CinderException, - self.driver.extend_volume, - test_volume, - 5) - - @mock.patch.object(dsware.DSWAREDriver, '_create_volume_from_snap') - @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') - def test_create_volume_from_snap(self, mock_manage_ip, mock_create_vol): - # success - mock_manage_ip.return_value = self.driver.manage_ip - retval = self.driver.create_volume_from_snapshot(test_volume, - test_snapshot) - self.assertEqual({"provider_id": self.driver.manage_ip}, - retval) - - # failure - mock_create_vol.side_effect = exception.CinderException( - 'DSWARE:create volume from snap failed') - self.assertRaises(exception.CinderException, - self.driver.create_volume_from_snapshot, - test_volume, test_snapshot) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, - '_wait_for_create_cloned_volume_finish_timer') - def test_create_cloned_volume(self, mock_wait_finish, - mock_get_manage_ip, mock_create_volume): - # success - mock_create_volume.return_value = None - mock_get_manage_ip.return_value = self.driver.manage_ip - mock_wait_finish.return_value = True - retval = self.driver.create_cloned_volume(test_volume, test_src_volume) - self.assertEqual({"provider_id": "127.0.0.1"}, retval) - - # failure:create exception - mock_create_volume.return_value = 500015 - self.assertRaises(exception.CinderException, - self.driver.create_cloned_volume, - test_volume, test_src_volume) - # failure:wait exception - mock_create_volume.return_value = None - mock_wait_finish.return_value = False - self.assertRaises(exception.CinderException, - self.driver.create_cloned_volume, - test_volume, test_src_volume) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_volume') - def test_private_check_create_cloned_volume_finish(self, - mock_query_volume): - query_result_done = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', 'status': '0', - 'vol_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'create_time': '01/01/2015'} - - query_result_doing = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', 'status': '6', - 'vol_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'create_time': '01/01/2015'} - - mock_query_volume.side_effect = [ - query_result_done, query_result_doing, query_result_doing] - - # success - self.assertRaises(loopingcall.LoopingCallDone, - self.driver._check_create_cloned_volume_finish, - test_volume['name']) - - # in the process of creating volume - self.driver.count = self.driver.configuration.clone_volume_timeout - 1 - self.driver._check_create_cloned_volume_finish(test_volume['name']) - self.assertEqual(self.driver.configuration.clone_volume_timeout, - self.driver.count) - - # timeout - self.driver.count = self.driver.configuration.clone_volume_timeout - self.assertRaises(loopingcall.LoopingCallDone, - self.driver._check_create_cloned_volume_finish, - test_volume['name']) - - @mock.patch.object(dsware.DSWAREDriver, - '_check_create_cloned_volume_finish') - def test_private_wait_for_create_cloned_volume_finish_timer(self, - mock_check): - mock_check.side_effect = [loopingcall.LoopingCallDone(retvalue=True), - loopingcall.LoopingCallDone(retvalue=False)] - retval = self.driver._wait_for_create_cloned_volume_finish_timer( - test_volume['name']) - self.assertTrue(retval) - - retval = self.driver._wait_for_create_cloned_volume_finish_timer( - test_volume['name']) - self.assertFalse(retval) - - def test_private_analyse_output(self): - out = 'ret_code=10\nret_desc=test\ndev_addr=/sda\n' - retval = self.driver._analyse_output(out) - self.assertEqual({'dev_addr': '/sda', - 'ret_desc': 'test', 'ret_code': '10'}, - retval) - - out = 'abcdefg' - retval = self.driver._analyse_output(out) - self.assertEqual({}, retval) - - def test_private_attach_volume(self): - success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] - failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', - ''] - mock_execute = self.mock_object(self.driver, '_execute') - mock_execute.side_effect = [success, failure] - # attached successful - retval = self.driver._attach_volume(test_volume['name'], - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'success', 'ret_code': '0'}, - retval) - # attached failure - retval = self.driver._attach_volume(test_volume['name'], - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'failed', 'ret_code': '50510011'}, - retval) - - def test_private_detach_volume(self): - success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] - failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', - ''] - mock_execute = self.mock_object(self.driver, '_execute') - mock_execute.side_effect = [success, failure] - # detached successful - retval = self.driver._detach_volume(test_volume['name'], - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'success', 'ret_code': '0'}, - retval) - # detached failure - retval = self.driver._detach_volume(test_volume['name'], - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'failed', - 'ret_code': '50510011'}, - retval) - - def test_private_query_volume_attach(self): - success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] - failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', - ''] - mock_execute = self.mock_object(self.driver, '_execute') - mock_execute.side_effect = [success, failure] - # query successful - retval = self.driver._query_volume_attach(test_volume['name'], - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'success', - 'ret_code': '0'}, - retval) - # query failure - retval = self.driver._query_volume_attach(test_volume['name'], - self.driver.manage_ip) - self.assertEqual({'dev_addr': '/dev/sdb', - 'ret_desc': 'failed', - 'ret_code': '50510011'}, - retval) - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(image_utils, 'fetch_to_raw') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_image_to_volume(self, mock_detach, mock_fetch, - mock_attach, mock_get_manage_ip): - success = {'ret_code': '0', - 'ret_desc': 'success', - 'dev_addr': '/dev/sdb'} - failure = {'ret_code': '50510011', - 'ret_desc': 'failed', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_id = '' - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.side_effect = [success, failure, success] - mock_detach.side_effect = [success, failure, failure] - - # success - self.driver.copy_image_to_volume(context, test_volume, image_service, - image_id) - - # failure - attach failure - self.assertRaises(exception.CinderException, - self.driver.copy_image_to_volume, - context, test_volume, image_service, image_id) - - # failure - detach failure - self.assertRaises(exception.CinderException, - self.driver.copy_image_to_volume, - context, test_volume, image_service, image_id) - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') - @mock.patch.object(image_utils, 'upload_volume') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_volume_to_image_success(self, mock_detach, mock_upload, - mock_query, mock_attach, - mock_get_manage_ip): - success = {'ret_code': '0', - 'ret_desc': 'success', - 'dev_addr': '/dev/sdb'} - already_attached = {'ret_code': '50151401', - 'ret_desc': 'already_attached', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_meta = '' - - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.return_value = success - mock_detach.return_value = success - self.driver.copy_volume_to_image(context, test_volume, image_service, - image_meta) - mock_upload.assert_called_with('', '', '', '/dev/sdb') - - mock_attach.return_value = already_attached - mock_query.return_value = success - mock_detach.return_value = success - self.driver.copy_volume_to_image(context, test_volume, image_service, - image_meta) - mock_upload.assert_called_with('', '', '', '/dev/sdb') - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') - @mock.patch.object(image_utils, 'upload_volume') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_volume_to_image_attach_fail(self, mock_detach, mock_upload, - mock_query, mock_attach, - mock_get_manage_ip): - failure = {'ret_code': '50510011', - 'ret_desc': 'failed', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_meta = '' - - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.return_value = failure - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, test_volume, image_service, image_meta) - mock_attach.return_value = None - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, test_volume, image_service, image_meta) - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') - @mock.patch.object(image_utils, 'upload_volume') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_volume_to_image_query_attach_fail(self, mock_detach, - mock_upload, mock_query, - mock_attach, - mock_get_manage_ip): - already_attached = {'ret_code': '50151401', - 'ret_desc': 'already_attached', - 'dev_addr': '/dev/sdb'} - failure = {'ret_code': '50510011', - 'ret_desc': 'failed', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_meta = '' - - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.return_value = already_attached - mock_query.return_value = failure - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, test_volume, image_service, image_meta) - - mock_query.return_value = None - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, test_volume, image_service, image_meta) - - @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') - @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') - @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') - @mock.patch.object(image_utils, 'upload_volume') - @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') - def test_copy_volume_to_image_upload_fail(self, mock_detach, mock_upload, - mock_query, mock_attach, - mock_get_manage_ip): - success = {'ret_code': '0', - 'ret_desc': 'success', - 'dev_addr': '/dev/sdb'} - already_attached = {'ret_code': '50151401', - 'ret_desc': 'already_attached', - 'dev_addr': '/dev/sdb'} - context = '' - image_service = '' - image_meta = '' - - mock_get_manage_ip.return_value = '127.0.0.1' - mock_attach.return_value = already_attached - mock_query.return_value = success - mock_upload.side_effect = exception.CinderException( - 'upload_volume error') - self.assertRaises(exception.CinderException, - self.driver.copy_volume_to_image, - context, test_volume, image_service, image_meta) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_volume') - def test_private_get_volume(self, mock_query): - result_success = {'result': 0} - result_not_exist = {'result': "50150005\n"} - result_exception = {'result': "50510006\n"} - - mock_query.side_effect = [ - result_success, result_not_exist, result_exception] - - retval = self.driver._get_volume(test_volume['name']) - self.assertTrue(retval) - - retval = self.driver._get_volume(test_volume['name']) - self.assertFalse(retval) - - self.assertRaises(exception.CinderException, - self.driver._get_volume, - test_volume['name']) - - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_volume') - def test_private_delete_volume(self, mock_delete): - result_success = 0 - result_not_exist = '50150005\n' - result_being_deleted = '50151002\n' - result_exception = '51050006\n' - - mock_delete.side_effect = [result_success, result_not_exist, - result_being_deleted, result_exception] - - retval = self.driver._delete_volume(test_volume['name']) - self.assertTrue(retval) - - retval = self.driver._delete_volume(test_volume['name']) - self.assertTrue(retval) - - retval = self.driver._delete_volume(test_volume['name']) - self.assertTrue(retval) - - self.assertRaises(exception.CinderException, - self.driver._delete_volume, test_volume['name']) - - @mock.patch.object(dsware.DSWAREDriver, '_get_volume') - @mock.patch.object(dsware.DSWAREDriver, '_delete_volume') - def test_delete_volume(self, mock_delete, mock_get): - mock_get.return_value = False - retval = self.driver.delete_volume(test_volume) - self.assertTrue(retval) - - mock_get.return_value = True - mock_delete.return_value = True - retval = self.driver.delete_volume(test_volume) - self.assertTrue(retval) - - mock_get.return_value = True - mock_delete.side_effect = exception.CinderException( - 'delete volume exception') - self.assertRaises(exception.CinderException, - self.driver.delete_volume, - test_volume) - - mock_get.side_effect = exception.CinderException( - 'get volume exception') - self.assertRaises(exception.CinderException, - self.driver.delete_volume, - test_volume) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_snap') - def test_private_get_snapshot(self, mock_query): - result_success = {'result': 0} - result_not_found = {'result': "50150006\n"} - result_exception = {'result': "51050007\n"} - mock_query.side_effect = [result_success, result_not_found, - result_exception] - - retval = self.driver._get_snapshot(test_snapshot['name']) - self.assertTrue(retval) - - retval = self.driver._get_snapshot(test_snapshot['name']) - self.assertFalse(retval) - - self.assertRaises(exception.CinderException, - self.driver._get_snapshot, - test_snapshot['name']) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') - def test_private_create_snapshot(self, mock_create): - mock_create.side_effect = [0, 1] - - self.driver._create_snapshot(test_snapshot['name'], - test_volume['name']) - - self.assertRaises(exception.CinderException, - self.driver._create_snapshot, - test_snapshot['name'], test_volume['name']) - - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') - def test_private_delete_snapshot(self, mock_delete): - mock_delete.side_effect = [0, 1] - - self.driver._delete_snapshot(test_snapshot['name']) - - self.assertRaises(exception.CinderException, - self.driver._delete_snapshot, test_snapshot['name']) - - @mock.patch.object(dsware.DSWAREDriver, '_get_volume') - @mock.patch.object(dsware.DSWAREDriver, '_create_snapshot') - def test_create_snapshot(self, mock_create, mock_get): - mock_get.return_value = True - self.driver.create_snapshot(test_snapshot) - - mock_create.side_effect = exception.CinderException( - 'create snapshot failed') - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, test_snapshot) - - mock_get.side_effect = [ - False, exception.CinderException('get volume failed')] - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, - test_snapshot) - self.assertRaises(exception.CinderException, - self.driver.create_snapshot, - test_snapshot) - - @mock.patch.object(dsware.DSWAREDriver, '_get_snapshot') - @mock.patch.object(dsware.DSWAREDriver, '_delete_snapshot') - def test_delete_snapshot(self, mock_delete, mock_get): - mock_get.side_effect = [True, False, exception.CinderException, True] - self.driver.delete_snapshot(test_snapshot) - self.driver.delete_snapshot(test_snapshot) - - self.assertRaises(exception.CinderException, - self.driver.delete_snapshot, - test_snapshot) - mock_delete.side_effect = exception.CinderException( - 'delete snapshot exception') - self.assertRaises(exception.CinderException, - self.driver.delete_snapshot, - test_snapshot) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_pool_info') - def test_private_update_single_pool_info_status(self, mock_query): - pool_info = {'result': 0, - 'pool_id': 10, - 'total_capacity': 10240, - 'used_capacity': 5120, - 'alloc_capacity': 7168} - pool_info_none = {'result': 1} - - mock_query.side_effect = [pool_info, pool_info_none] - - self.driver._update_single_pool_info_status() - self.assertEqual({'total_capacity_gb': 10.0, - 'free_capacity_gb': 5.0, - 'volume_backend_name': None, - 'vendor_name': 'Open Source', - 'driver_version': '1.0', - 'storage_protocol': 'dsware', - 'reserved_percentage': 0, - 'QoS_support': False}, - self.driver._stats) - - self.driver._update_single_pool_info_status() - self.assertIsNone(self.driver._stats) - - @mock.patch.object(fspythonapi.FSPythonApi, 'query_pool_type') - def test_private_update_multi_pool_of_same_type_status(self, mock_query): - query_result = (0, [{'result': 0, - 'pool_id': '0', - 'total_capacity': '10240', - 'used_capacity': '5120', - 'alloc_capacity': '7168'}]) - query_result_none = (0, []) - - mock_query.side_effect = [query_result, query_result_none] - - self.driver._update_multi_pool_of_same_type_status() - self.assertEqual({'volume_backend_name': None, - 'vendor_name': 'Open Source', - 'driver_version': '1.0', - 'storage_protocol': 'dsware', - 'pools': [{'pool_name': '0', - 'total_capacity_gb': 10.0, - 'allocated_capacity_gb': 5.0, - 'free_capacity_gb': 5.0, - 'QoS_support': False, - 'reserved_percentage': 0}]}, - self.driver._stats) - - self.driver._update_multi_pool_of_same_type_status() - self.assertIsNone(self.driver._stats) - - def test_private_calculate_pool_info(self): - pool_sets = [{'pool_id': 0, - 'total_capacity': 10240, - 'used_capacity': 5120, - 'QoS_support': False, - 'reserved_percentage': 0}] - retval = self.driver._calculate_pool_info(pool_sets) - self.assertEqual([{'pool_name': 0, - 'total_capacity_gb': 10.0, - 'allocated_capacity_gb': 5.0, - 'free_capacity_gb': 5.0, - 'QoS_support': False, - 'reserved_percentage': 0}], - retval) - - @mock.patch.object(dsware.DSWAREDriver, '_update_single_pool_info_status') - @mock.patch.object(dsware.DSWAREDriver, - '_update_multi_pool_of_same_type_status') - @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') - def test_get_volume_stats(self, mock_query, mock_type, mock_info): - mock_query.return_value = 1 - - self.driver.get_volume_stats(False) - mock_query.assert_not_called() - - self.driver.get_volume_stats(True) - mock_query.assert_called_once_with() +# Copyright (c) 2013 - 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. +""" +Unit Tests for Huawei FusionStorage drivers. +""" + +import mock +from oslo_config import cfg +from oslo_service import loopingcall + +from cinder import exception +from cinder.image import image_utils +from cinder import test +from cinder.volume import configuration as conf +from cinder.volume.drivers.fusionstorage import dsware +from cinder.volume.drivers.fusionstorage import fspythonapi + + +test_volume = {'name': 'test_vol1', + 'size': 4, + 'volume_metadata': '', + 'host': 'host01@dsware', + 'instance_uuid': None, + 'provider_id': '127.0.0.1'} + +test_src_volume = {'name': 'test_vol2', + 'size': 4, + 'status': 'available'} + +test_snapshot = { + 'name': 'test_snapshot1', + 'volume_id': 'vol1', + 'volume_size': '2'} + + +class FakeDSWAREDriver(dsware.DSWAREDriver): + def __init__(self): + configuration = conf.Configuration( + [ + cfg.StrOpt('fake'), + ], + None + ) + super(FakeDSWAREDriver, self).__init__(configuration=configuration) + self.dsware_client = fspythonapi.FSPythonApi() + self.manage_ip = '127.0.0.1' + self.pool_type = '1' + + +class DSwareDriverTestCase(test.TestCase): + def setUp(self): + super(DSwareDriverTestCase, self).setUp() + self.driver = FakeDSWAREDriver() + + def test_private_get_dsware_manage_ip(self): + retval = self.driver._get_dsware_manage_ip(test_volume) + self.assertEqual('127.0.0.1', retval) + + test_volume_fail = {'name': 'test_vol', + 'size': 4, + 'volume_metadata': '', + 'host': 'host01@dsware', + 'provider_id': None} + self.assertRaises(exception.CinderException, + self.driver._get_dsware_manage_ip, + test_volume_fail) + + def test_private_get_poolid_from_host(self): + retval = self.driver._get_poolid_from_host( + 'abc@fusionstorage_sas2copy#0') + self.assertEqual('0', retval) + + retval = self.driver._get_poolid_from_host( + 'abc@fusionstorage_sas2copy@0') + self.assertEqual(self.driver.pool_type, retval) + + retval = self.driver._get_poolid_from_host(None) + self.assertEqual(self.driver.pool_type, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') + @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') + @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') + def test_private_create_volume_old_version(self, mock_get_poolid, + mock_query_dsware, + mock_create_volume): + # query_dsware_version return 1, old version + mock_query_dsware.return_value = 1 + mock_create_volume.return_value = 0 + self.driver._create_volume(test_volume['name'], + test_volume['size'], + True, + 'abc@fusionstorage_sas2copy') + mock_create_volume.assert_called_with(test_volume['name'], 0, + test_volume['size'], 1) + + self.driver._create_volume(test_volume['name'], + test_volume['size'], + False, + 'abc@fusionstorage_sas2copy') + mock_create_volume.assert_called_with(test_volume['name'], 0, + test_volume['size'], 0) + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') + @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') + @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') + def test_private_create_volume_new_version(self, mock_get_poolid, + mock_query_dsware, + mock_create_volume): + # query_dsware_version return 0, new version + mock_query_dsware.return_value = 0 + mock_get_poolid.return_value = 0 + mock_create_volume.return_value = 0 + self.driver._create_volume(test_volume['name'], + test_volume['size'], + True, + 'abcE@fusionstorage_sas2copy#0') + mock_create_volume.assert_called_with(test_volume['name'], 0, + test_volume['size'], 1) + + self.driver._create_volume(test_volume['name'], + test_volume['size'], + False, + 'abc@fusionstorage_sas2copy#0') + mock_create_volume.assert_called_with(test_volume['name'], 0, + test_volume['size'], 0) + + mock_query_dsware.return_value = 0 + mock_get_poolid.return_value = 1 + mock_create_volume.return_value = 0 + self.driver._create_volume(test_volume['name'], + test_volume['size'], + True, + 'abc@fusionstorage_sas2copy#1') + mock_create_volume.assert_called_with(test_volume['name'], 1, + test_volume['size'], 1) + + self.driver._create_volume(test_volume['name'], + test_volume['size'], + False, + 'abc@fusionstorage_sas2copy#1') + mock_create_volume.assert_called_with(test_volume['name'], 1, + test_volume['size'], 0) + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') + @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') + @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') + def test_private_create_volume_query_version_fail(self, mock_get_poolid, + mock_query_dsware, + mock_create_volume): + # query_dsware_version return 500015, query dsware version failed! + mock_query_dsware.return_value = 500015 + self.assertRaises(exception.CinderException, + self.driver._create_volume, + test_volume['name'], + test_volume['size'], + True, + 'abc@fusionstorage_sas2copy#0') + self.assertRaises(exception.CinderException, + self.driver._create_volume, + test_volume['name'], + test_volume['size'], + False, + 'abc@fusionstorage_sas2copy#0') + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') + @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') + @mock.patch.object(dsware.DSWAREDriver, '_get_poolid_from_host') + def test_private_create_volume_fail(self, mock_get_poolid, + mock_query_dsware, + mock_create_volume): + mock_query_dsware.return_value = 1 + # create_volume return 1, create volume failed + mock_create_volume.return_value = 1 + self.assertRaises(exception.CinderException, + self.driver._create_volume, + test_volume['name'], + test_volume['size'], + True, + 'abc@fusionstorage_sas2copy#0') + self.assertRaises(exception.CinderException, + self.driver._create_volume, + test_volume['name'], + test_volume['size'], + False, + 'abc@fusionstorage_sas2copy#0') + + @mock.patch.object(dsware.DSWAREDriver, '_create_volume') + @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') + def test_create_volume(self, mock_get_manage_ip, mock_create_volume): + # success + mock_get_manage_ip.return_value = self.driver.manage_ip + retval = self.driver.create_volume(test_volume) + self.assertEqual({"provider_id": self.driver.manage_ip}, + retval) + + # failure + mock_create_volume.side_effect = exception.CinderException( + 'DSWARE Create Volume failed!') + + self.assertRaises(exception.CinderException, + self.driver.create_volume, + test_volume) + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_snap') + def test_private_create_volume_from_snap(self, mock_create_volume): + mock_create_volume.side_effect = [0, 1] + self.driver._create_volume_from_snap(test_volume['name'], + test_volume['size'], + test_snapshot['name']) + # failure + self.assertRaises(exception.CinderException, + self.driver._create_volume_from_snap, + test_volume['name'], test_volume['size'], + test_snapshot['name']) + + @mock.patch.object(fspythonapi.FSPythonApi, 'extend_volume') + def test_extend_volume(self, mock_extend_volume): + mock_extend_volume.return_value = 0 + self.driver.extend_volume(test_volume, 5) + + mock_extend_volume.return_value = 0 + self.assertRaises(exception.CinderException, + self.driver.extend_volume, + test_volume, + 3) + + mock_extend_volume.return_value = 1 + self.assertRaises(exception.CinderException, + self.driver.extend_volume, + test_volume, + 5) + + @mock.patch.object(dsware.DSWAREDriver, '_create_volume_from_snap') + @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') + def test_create_volume_from_snap(self, mock_manage_ip, mock_create_vol): + # success + mock_manage_ip.return_value = self.driver.manage_ip + retval = self.driver.create_volume_from_snapshot(test_volume, + test_snapshot) + self.assertEqual({"provider_id": self.driver.manage_ip}, + retval) + + # failure + mock_create_vol.side_effect = exception.CinderException( + 'DSWARE:create volume from snap failed') + self.assertRaises(exception.CinderException, + self.driver.create_volume_from_snapshot, + test_volume, test_snapshot) + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_volume') + @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') + @mock.patch.object(dsware.DSWAREDriver, + '_wait_for_create_cloned_volume_finish_timer') + def test_create_cloned_volume(self, mock_wait_finish, + mock_get_manage_ip, mock_create_volume): + # success + mock_create_volume.return_value = None + mock_get_manage_ip.return_value = self.driver.manage_ip + mock_wait_finish.return_value = True + retval = self.driver.create_cloned_volume(test_volume, test_src_volume) + self.assertEqual({"provider_id": "127.0.0.1"}, retval) + + # failure:create exception + mock_create_volume.return_value = 500015 + self.assertRaises(exception.CinderException, + self.driver.create_cloned_volume, + test_volume, test_src_volume) + # failure:wait exception + mock_create_volume.return_value = None + mock_wait_finish.return_value = False + self.assertRaises(exception.CinderException, + self.driver.create_cloned_volume, + test_volume, test_src_volume) + + @mock.patch.object(fspythonapi.FSPythonApi, 'query_volume') + def test_private_check_create_cloned_volume_finish(self, + mock_query_volume): + query_result_done = {'result': 0, 'vol_name': 'vol1', + 'father_name': 'vol1_father', 'status': '0', + 'vol_size': '1024', 'real_size': '1024', + 'pool_id': 'pool1', 'create_time': '01/01/2015'} + + query_result_doing = {'result': 0, 'vol_name': 'vol1', + 'father_name': 'vol1_father', 'status': '6', + 'vol_size': '1024', 'real_size': '1024', + 'pool_id': 'pool1', 'create_time': '01/01/2015'} + + mock_query_volume.side_effect = [ + query_result_done, query_result_doing, query_result_doing] + + # success + self.assertRaises(loopingcall.LoopingCallDone, + self.driver._check_create_cloned_volume_finish, + test_volume['name']) + + # in the process of creating volume + self.driver.count = self.driver.configuration.clone_volume_timeout - 1 + self.driver._check_create_cloned_volume_finish(test_volume['name']) + self.assertEqual(self.driver.configuration.clone_volume_timeout, + self.driver.count) + + # timeout + self.driver.count = self.driver.configuration.clone_volume_timeout + self.assertRaises(loopingcall.LoopingCallDone, + self.driver._check_create_cloned_volume_finish, + test_volume['name']) + + @mock.patch.object(dsware.DSWAREDriver, + '_check_create_cloned_volume_finish') + def test_private_wait_for_create_cloned_volume_finish_timer(self, + mock_check): + mock_check.side_effect = [loopingcall.LoopingCallDone(retvalue=True), + loopingcall.LoopingCallDone(retvalue=False)] + retval = self.driver._wait_for_create_cloned_volume_finish_timer( + test_volume['name']) + self.assertTrue(retval) + + retval = self.driver._wait_for_create_cloned_volume_finish_timer( + test_volume['name']) + self.assertFalse(retval) + + def test_private_analyse_output(self): + out = 'ret_code=10\nret_desc=test\ndev_addr=/sda\n' + retval = self.driver._analyse_output(out) + self.assertEqual({'dev_addr': '/sda', + 'ret_desc': 'test', 'ret_code': '10'}, + retval) + + out = 'abcdefg' + retval = self.driver._analyse_output(out) + self.assertEqual({}, retval) + + def test_private_attach_volume(self): + success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] + failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', + ''] + mock_execute = self.mock_object(self.driver, '_execute') + mock_execute.side_effect = [success, failure] + # attached successful + retval = self.driver._attach_volume(test_volume['name'], + self.driver.manage_ip) + self.assertEqual({'dev_addr': '/dev/sdb', + 'ret_desc': 'success', 'ret_code': '0'}, + retval) + # attached failure + retval = self.driver._attach_volume(test_volume['name'], + self.driver.manage_ip) + self.assertEqual({'dev_addr': '/dev/sdb', + 'ret_desc': 'failed', 'ret_code': '50510011'}, + retval) + + def test_private_detach_volume(self): + success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] + failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', + ''] + mock_execute = self.mock_object(self.driver, '_execute') + mock_execute.side_effect = [success, failure] + # detached successful + retval = self.driver._detach_volume(test_volume['name'], + self.driver.manage_ip) + self.assertEqual({'dev_addr': '/dev/sdb', + 'ret_desc': 'success', 'ret_code': '0'}, + retval) + # detached failure + retval = self.driver._detach_volume(test_volume['name'], + self.driver.manage_ip) + self.assertEqual({'dev_addr': '/dev/sdb', + 'ret_desc': 'failed', + 'ret_code': '50510011'}, + retval) + + def test_private_query_volume_attach(self): + success = ['ret_code=0\nret_desc=success\ndev_addr=/dev/sdb\n', ''] + failure = ['ret_code=50510011\nret_desc=failed\ndev_addr=/dev/sdb\n', + ''] + mock_execute = self.mock_object(self.driver, '_execute') + mock_execute.side_effect = [success, failure] + # query successful + retval = self.driver._query_volume_attach(test_volume['name'], + self.driver.manage_ip) + self.assertEqual({'dev_addr': '/dev/sdb', + 'ret_desc': 'success', + 'ret_code': '0'}, + retval) + # query failure + retval = self.driver._query_volume_attach(test_volume['name'], + self.driver.manage_ip) + self.assertEqual({'dev_addr': '/dev/sdb', + 'ret_desc': 'failed', + 'ret_code': '50510011'}, + retval) + + @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') + @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') + @mock.patch.object(image_utils, 'fetch_to_raw') + @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') + def test_copy_image_to_volume(self, mock_detach, mock_fetch, + mock_attach, mock_get_manage_ip): + success = {'ret_code': '0', + 'ret_desc': 'success', + 'dev_addr': '/dev/sdb'} + failure = {'ret_code': '50510011', + 'ret_desc': 'failed', + 'dev_addr': '/dev/sdb'} + context = '' + image_service = '' + image_id = '' + mock_get_manage_ip.return_value = '127.0.0.1' + mock_attach.side_effect = [success, failure, success] + mock_detach.side_effect = [success, failure, failure] + + # success + self.driver.copy_image_to_volume(context, test_volume, image_service, + image_id) + + # failure - attach failure + self.assertRaises(exception.CinderException, + self.driver.copy_image_to_volume, + context, test_volume, image_service, image_id) + + # failure - detach failure + self.assertRaises(exception.CinderException, + self.driver.copy_image_to_volume, + context, test_volume, image_service, image_id) + + @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') + @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') + @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') + @mock.patch.object(image_utils, 'upload_volume') + @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') + def test_copy_volume_to_image_success(self, mock_detach, mock_upload, + mock_query, mock_attach, + mock_get_manage_ip): + success = {'ret_code': '0', + 'ret_desc': 'success', + 'dev_addr': '/dev/sdb'} + already_attached = {'ret_code': '50151401', + 'ret_desc': 'already_attached', + 'dev_addr': '/dev/sdb'} + context = '' + image_service = '' + image_meta = '' + + mock_get_manage_ip.return_value = '127.0.0.1' + mock_attach.return_value = success + mock_detach.return_value = success + self.driver.copy_volume_to_image(context, test_volume, image_service, + image_meta) + mock_upload.assert_called_with('', '', '', '/dev/sdb') + + mock_attach.return_value = already_attached + mock_query.return_value = success + mock_detach.return_value = success + self.driver.copy_volume_to_image(context, test_volume, image_service, + image_meta) + mock_upload.assert_called_with('', '', '', '/dev/sdb') + + @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') + @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') + @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') + @mock.patch.object(image_utils, 'upload_volume') + @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') + def test_copy_volume_to_image_attach_fail(self, mock_detach, mock_upload, + mock_query, mock_attach, + mock_get_manage_ip): + failure = {'ret_code': '50510011', + 'ret_desc': 'failed', + 'dev_addr': '/dev/sdb'} + context = '' + image_service = '' + image_meta = '' + + mock_get_manage_ip.return_value = '127.0.0.1' + mock_attach.return_value = failure + self.assertRaises(exception.CinderException, + self.driver.copy_volume_to_image, + context, test_volume, image_service, image_meta) + mock_attach.return_value = None + self.assertRaises(exception.CinderException, + self.driver.copy_volume_to_image, + context, test_volume, image_service, image_meta) + + @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') + @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') + @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') + @mock.patch.object(image_utils, 'upload_volume') + @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') + def test_copy_volume_to_image_query_attach_fail(self, mock_detach, + mock_upload, mock_query, + mock_attach, + mock_get_manage_ip): + already_attached = {'ret_code': '50151401', + 'ret_desc': 'already_attached', + 'dev_addr': '/dev/sdb'} + failure = {'ret_code': '50510011', + 'ret_desc': 'failed', + 'dev_addr': '/dev/sdb'} + context = '' + image_service = '' + image_meta = '' + + mock_get_manage_ip.return_value = '127.0.0.1' + mock_attach.return_value = already_attached + mock_query.return_value = failure + self.assertRaises(exception.CinderException, + self.driver.copy_volume_to_image, + context, test_volume, image_service, image_meta) + + mock_query.return_value = None + self.assertRaises(exception.CinderException, + self.driver.copy_volume_to_image, + context, test_volume, image_service, image_meta) + + @mock.patch.object(dsware.DSWAREDriver, '_get_dsware_manage_ip') + @mock.patch.object(dsware.DSWAREDriver, '_attach_volume') + @mock.patch.object(dsware.DSWAREDriver, '_query_volume_attach') + @mock.patch.object(image_utils, 'upload_volume') + @mock.patch.object(dsware.DSWAREDriver, '_detach_volume') + def test_copy_volume_to_image_upload_fail(self, mock_detach, mock_upload, + mock_query, mock_attach, + mock_get_manage_ip): + success = {'ret_code': '0', + 'ret_desc': 'success', + 'dev_addr': '/dev/sdb'} + already_attached = {'ret_code': '50151401', + 'ret_desc': 'already_attached', + 'dev_addr': '/dev/sdb'} + context = '' + image_service = '' + image_meta = '' + + mock_get_manage_ip.return_value = '127.0.0.1' + mock_attach.return_value = already_attached + mock_query.return_value = success + mock_upload.side_effect = exception.CinderException( + 'upload_volume error') + self.assertRaises(exception.CinderException, + self.driver.copy_volume_to_image, + context, test_volume, image_service, image_meta) + + @mock.patch.object(fspythonapi.FSPythonApi, 'query_volume') + def test_private_get_volume(self, mock_query): + result_success = {'result': 0} + result_not_exist = {'result': "50150005\n"} + result_exception = {'result': "50510006\n"} + + mock_query.side_effect = [ + result_success, result_not_exist, result_exception] + + retval = self.driver._get_volume(test_volume['name']) + self.assertTrue(retval) + + retval = self.driver._get_volume(test_volume['name']) + self.assertFalse(retval) + + self.assertRaises(exception.CinderException, + self.driver._get_volume, + test_volume['name']) + + @mock.patch.object(fspythonapi.FSPythonApi, 'delete_volume') + def test_private_delete_volume(self, mock_delete): + result_success = 0 + result_not_exist = '50150005\n' + result_being_deleted = '50151002\n' + result_exception = '51050006\n' + + mock_delete.side_effect = [result_success, result_not_exist, + result_being_deleted, result_exception] + + retval = self.driver._delete_volume(test_volume['name']) + self.assertTrue(retval) + + retval = self.driver._delete_volume(test_volume['name']) + self.assertTrue(retval) + + retval = self.driver._delete_volume(test_volume['name']) + self.assertTrue(retval) + + self.assertRaises(exception.CinderException, + self.driver._delete_volume, test_volume['name']) + + @mock.patch.object(dsware.DSWAREDriver, '_get_volume') + @mock.patch.object(dsware.DSWAREDriver, '_delete_volume') + def test_delete_volume(self, mock_delete, mock_get): + mock_get.return_value = False + retval = self.driver.delete_volume(test_volume) + self.assertTrue(retval) + + mock_get.return_value = True + mock_delete.return_value = True + retval = self.driver.delete_volume(test_volume) + self.assertTrue(retval) + + mock_get.return_value = True + mock_delete.side_effect = exception.CinderException( + 'delete volume exception') + self.assertRaises(exception.CinderException, + self.driver.delete_volume, + test_volume) + + mock_get.side_effect = exception.CinderException( + 'get volume exception') + self.assertRaises(exception.CinderException, + self.driver.delete_volume, + test_volume) + + @mock.patch.object(fspythonapi.FSPythonApi, 'query_snap') + def test_private_get_snapshot(self, mock_query): + result_success = {'result': 0} + result_not_found = {'result': "50150006\n"} + result_exception = {'result': "51050007\n"} + mock_query.side_effect = [result_success, result_not_found, + result_exception] + + retval = self.driver._get_snapshot(test_snapshot['name']) + self.assertTrue(retval) + + retval = self.driver._get_snapshot(test_snapshot['name']) + self.assertFalse(retval) + + self.assertRaises(exception.CinderException, + self.driver._get_snapshot, + test_snapshot['name']) + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') + def test_private_create_snapshot(self, mock_create): + mock_create.side_effect = [0, 1] + + self.driver._create_snapshot(test_snapshot['name'], + test_volume['name']) + + self.assertRaises(exception.CinderException, + self.driver._create_snapshot, + test_snapshot['name'], test_volume['name']) + + @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') + def test_private_delete_snapshot(self, mock_delete): + mock_delete.side_effect = [0, 1] + + self.driver._delete_snapshot(test_snapshot['name']) + + self.assertRaises(exception.CinderException, + self.driver._delete_snapshot, test_snapshot['name']) + + @mock.patch.object(dsware.DSWAREDriver, '_get_volume') + @mock.patch.object(dsware.DSWAREDriver, '_create_snapshot') + def test_create_snapshot(self, mock_create, mock_get): + mock_get.return_value = True + self.driver.create_snapshot(test_snapshot) + + mock_create.side_effect = exception.CinderException( + 'create snapshot failed') + self.assertRaises(exception.CinderException, + self.driver.create_snapshot, test_snapshot) + + mock_get.side_effect = [ + False, exception.CinderException('get volume failed')] + self.assertRaises(exception.CinderException, + self.driver.create_snapshot, + test_snapshot) + self.assertRaises(exception.CinderException, + self.driver.create_snapshot, + test_snapshot) + + @mock.patch.object(dsware.DSWAREDriver, '_get_snapshot') + @mock.patch.object(dsware.DSWAREDriver, '_delete_snapshot') + def test_delete_snapshot(self, mock_delete, mock_get): + mock_get.side_effect = [True, False, exception.CinderException, True] + self.driver.delete_snapshot(test_snapshot) + self.driver.delete_snapshot(test_snapshot) + + self.assertRaises(exception.CinderException, + self.driver.delete_snapshot, + test_snapshot) + mock_delete.side_effect = exception.CinderException( + 'delete snapshot exception') + self.assertRaises(exception.CinderException, + self.driver.delete_snapshot, + test_snapshot) + + @mock.patch.object(fspythonapi.FSPythonApi, 'query_pool_info') + def test_private_update_single_pool_info_status(self, mock_query): + pool_info = {'result': 0, + 'pool_id': 10, + 'total_capacity': 10240, + 'used_capacity': 5120, + 'alloc_capacity': 7168} + pool_info_none = {'result': 1} + + mock_query.side_effect = [pool_info, pool_info_none] + + self.driver._update_single_pool_info_status() + self.assertEqual({'total_capacity_gb': 10.0, + 'free_capacity_gb': 5.0, + 'volume_backend_name': None, + 'vendor_name': 'Open Source', + 'driver_version': '1.0', + 'storage_protocol': 'dsware', + 'reserved_percentage': 0, + 'QoS_support': False}, + self.driver._stats) + + self.driver._update_single_pool_info_status() + self.assertIsNone(self.driver._stats) + + @mock.patch.object(fspythonapi.FSPythonApi, 'query_pool_type') + def test_private_update_multi_pool_of_same_type_status(self, mock_query): + query_result = (0, [{'result': 0, + 'pool_id': '0', + 'total_capacity': '10240', + 'used_capacity': '5120', + 'alloc_capacity': '7168'}]) + query_result_none = (0, []) + + mock_query.side_effect = [query_result, query_result_none] + + self.driver._update_multi_pool_of_same_type_status() + self.assertEqual({'volume_backend_name': None, + 'vendor_name': 'Open Source', + 'driver_version': '1.0', + 'storage_protocol': 'dsware', + 'pools': [{'pool_name': '0', + 'total_capacity_gb': 10.0, + 'allocated_capacity_gb': 5.0, + 'free_capacity_gb': 5.0, + 'QoS_support': False, + 'reserved_percentage': 0}]}, + self.driver._stats) + + self.driver._update_multi_pool_of_same_type_status() + self.assertIsNone(self.driver._stats) + + def test_private_calculate_pool_info(self): + pool_sets = [{'pool_id': 0, + 'total_capacity': 10240, + 'used_capacity': 5120, + 'QoS_support': False, + 'reserved_percentage': 0}] + retval = self.driver._calculate_pool_info(pool_sets) + self.assertEqual([{'pool_name': 0, + 'total_capacity_gb': 10.0, + 'allocated_capacity_gb': 5.0, + 'free_capacity_gb': 5.0, + 'QoS_support': False, + 'reserved_percentage': 0}], + retval) + + @mock.patch.object(dsware.DSWAREDriver, '_update_single_pool_info_status') + @mock.patch.object(dsware.DSWAREDriver, + '_update_multi_pool_of_same_type_status') + @mock.patch.object(fspythonapi.FSPythonApi, 'query_dsware_version') + def test_get_volume_stats(self, mock_query, mock_type, mock_info): + mock_query.return_value = 1 + + self.driver.get_volume_stats(False) + mock_query.assert_not_called() + + self.driver.get_volume_stats(True) + mock_query.assert_called_once_with() diff --git a/cinder/tests/unit/volume/drivers/fusionstorage/test_fspythonapi.py b/cinder/tests/unit/volume/drivers/fusionstorage/test_fspythonapi.py index ddf1fac2d17..785334e5e15 100644 --- a/cinder/tests/unit/volume/drivers/fusionstorage/test_fspythonapi.py +++ b/cinder/tests/unit/volume/drivers/fusionstorage/test_fspythonapi.py @@ -1,447 +1,447 @@ -# Copyright (c) 2013 - 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. -""" -Unit Tests for Huawei FusionStorage drivers. -""" - -import mock - -from cinder import test -from cinder import utils -from cinder.volume.drivers.fusionstorage import fspythonapi - - -class FSPythonApiTestCase(test.TestCase): - - def setUp(self): - super(FSPythonApiTestCase, self).setUp() - self.api = fspythonapi.FSPythonApi() - - @mock.patch.object(fspythonapi.FSPythonApi, 'get_ip_port') - @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') - @mock.patch.object(utils, 'execute') - def test_start_execute_cmd(self, mock_execute, - mock_get_manage_ip, mock_get_ip_port): - result1 = ['result=0\ndesc=success\n', ''] - result2 = ['result=50150007\ndesc=volume does not exist\n', ''] - result3 = ['result=50150008\ndesc=volume is being deleted\n', ''] - result4 = ['result=50\ndesc=exception\n', ''] - cmd = 'abcdef' - - mock_get_ip_port.return_value = ['127.0.0.1', '128.0.0.1'] - mock_get_manage_ip.return_value = '127.0.0.1' - - mock_execute.return_value = result1 - retval = self.api.start_execute_cmd(cmd, 0) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result2 - retval = self.api.start_execute_cmd(cmd, 0) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result3 - retval = self.api.start_execute_cmd(cmd, 0) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result4 - retval = self.api.start_execute_cmd(cmd, 0) - self.assertEqual('result=50', retval) - - mock_execute.return_value = result1 - retval = self.api.start_execute_cmd(cmd, 1) - self.assertEqual(['result=0', 'desc=success', ''], retval) - - mock_execute.return_value = result2 - retval = self.api.start_execute_cmd(cmd, 1) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result3 - retval = self.api.start_execute_cmd(cmd, 1) - self.assertEqual('result=0', retval) - - mock_execute.return_value = result4 - retval = self.api.start_execute_cmd(cmd, 1) - self.assertEqual(['result=50', 'desc=exception', ''], retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_create_volume(self, mock_start_execute): - mock_start_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) - self.assertEqual(0, retval) - - retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) - self.assertEqual('50150007\n', retval) - - retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_extend_volume(self, mock_start_execute): - mock_start_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.extend_volume('volume_name', 1024) - self.assertEqual(0, retval) - - retval = self.api.extend_volume('volume_name', 1024) - self.assertEqual('50150007\n', retval) - - retval = self.api.extend_volume('volume_name', 1024) - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_create_volume_from_snap(self, mock_start_execute): - mock_start_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.create_volume_from_snap('volume_name', 1024, - 'snap_name') - self.assertEqual(0, retval) - - retval = self.api.create_volume_from_snap('volume_name', 1024, - 'snap_name') - self.assertEqual('50150007\n', retval) - - retval = self.api.create_volume_from_snap('volume_name', 1024, - 'snap_name') - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_create_fullvol_from_snap(self, mock_start_execute): - mock_start_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') - self.assertEqual(0, retval) - - retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') - self.assertEqual('50150007\n', retval) - - retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_volume') - @mock.patch.object(fspythonapi.FSPythonApi, 'create_fullvol_from_snap') - def test_create_volume_from_volume(self, mock_create_fullvol, - mock_delete_volume, mock_delete_snap, - mock_create_volume, mock_create_snap): - mock_create_snap.return_value = 0 - mock_create_volume.return_value = 0 - mock_create_fullvol.return_value = 0 - - retval = self.api.create_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(0, retval) - - mock_create_snap.return_value = 1 - retval = self.api.create_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(1, retval) - - mock_create_snap.return_value = 0 - mock_create_volume.return_value = 1 - retval = self.api.create_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(1, retval) - - mock_create_volume.return_value = 0 - self.api.create_fullvol_from_snap.return_value = 1 - retval = self.api.create_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') - @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_snap') - def test_create_clone_volume_from_volume(self, mock_volume, mock_snap): - mock_snap.side_effect = [0, 1] - mock_volume.side_effect = [0, 1] - retval = self.api.create_clone_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(0, retval) - retval = self.api.create_clone_volume_from_volume('vol_name', 1024, - 'src_vol_name') - self.assertEqual(1, retval) - - def test_volume_info_analyze_success(self): - vol_info = ('vol_name=vol1,father_name=vol1_father,' - 'status=available,vol_size=1024,real_size=1024,' - 'pool_id=pool1,create_time=01/01/2015') - vol_info_res = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', - 'status': 'available', 'vol_size': '1024', - 'real_size': '1024', 'pool_id': 'pool1', - 'create_time': '01/01/2015'} - - retval = self.api.volume_info_analyze(vol_info) - self.assertEqual(vol_info_res, retval) - - def test_volume_info_analyze_fail(self): - vol_info = '' - vol_info_res = {'result': 1, 'vol_name': '', 'father_name': '', - 'status': '', 'vol_size': '', 'real_size': '', - 'pool_id': '', 'create_time': ''} - retval = self.api.volume_info_analyze(vol_info) - self.assertEqual(vol_info_res, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - @mock.patch.object(fspythonapi.FSPythonApi, 'volume_info_analyze') - @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') - def test_query_volume(self, mock_delete, mock_analyze, mock_execute): - exec_result = ['result=0\n', - 'vol_name=vol1,father_name=vol1_father,status=0,' + - 'vol_size=1024,real_size=1024,pool_id=pool1,' + - 'create_time=01/01/2015'] - query_result = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', 'status': '0', - 'vol_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'create_time': '01/01/2015'} - mock_delete.return_value = 0 - mock_execute.return_value = exec_result - mock_analyze.return_value = query_result - retval = self.api.query_volume('vol1') - self.assertEqual(query_result, retval) - - exec_result = ['result=0\n', - 'vol_name=vol1,father_name=vol1_father,status=1,' + - 'vol_size=1024,real_size=1024,pool_id=pool1,' + - 'create_time=01/01/2015'] - query_result = {'result': 0, 'vol_name': 'vol1', - 'father_name': 'vol1_father', 'status': '1', - 'vol_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'create_time': '01/01/2015'} - mock_delete.return_value = 0 - mock_execute.return_value = exec_result - mock_analyze.return_value = query_result - retval = self.api.query_volume('vol1') - self.assertEqual(query_result, retval) - - vol_info_failure = 'result=32500000\n' - failure_res = {'result': 1, 'vol_name': '', 'father_name': '', - 'status': '', 'vol_size': '', 'real_size': '', - 'pool_id': '', 'create_time': ''} - mock_execute.return_value = vol_info_failure - retval = self.api.query_volume('vol1') - self.assertEqual(failure_res, retval) - - vol_info_failure = None - failure_res = {'result': 1, 'vol_name': '', 'father_name': '', - 'status': '', 'vol_size': '', 'real_size': '', - 'pool_id': '', 'create_time': ''} - - mock_execute.return_value = vol_info_failure - retval = self.api.query_volume('vol1') - self.assertEqual(failure_res, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_delete_volume(self, mock_execute): - mock_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.delete_volume('volume_name') - self.assertEqual(0, retval) - - retval = self.api.delete_volume('volume_name') - self.assertEqual('50150007\n', retval) - - retval = self.api.delete_volume('volume_name') - self.assertEqual(1, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_create_snapshot(self, mock_execute): - mock_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.create_snapshot('snap_name', 'vol_name', 0) - self.assertEqual(0, retval) - - retval = self.api.create_snapshot('snap_name', 'vol_name', 0) - self.assertEqual('50150007\n', retval) - - retval = self.api.create_snapshot('snap_name', 'vol_name', 0) - self.assertEqual(1, retval) - - def test_snap_info_analyze_success(self): - snap_info = ('snap_name=snap1,father_name=snap1_father,status=0,' - 'snap_size=1024,real_size=1024,pool_id=pool1,' - 'delete_priority=1,create_time=01/01/2015') - snap_info_res = {'result': 0, 'snap_name': 'snap1', - 'father_name': 'snap1_father', 'status': '0', - 'snap_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'delete_priority': '1', - 'create_time': '01/01/2015'} - - retval = self.api.snap_info_analyze(snap_info) - self.assertEqual(snap_info_res, retval) - - def test_snap_info_analyze_fail(self): - snap_info = '' - snap_info_res = {'result': 1, 'snap_name': '', 'father_name': '', - 'status': '', 'snap_size': '', 'real_size': '', - 'pool_id': '', 'delete_priority': '', - 'create_time': ''} - retval = self.api.snap_info_analyze(snap_info) - self.assertEqual(snap_info_res, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_query_snap(self, mock_execute): - exec_result = ['result=0\n', - 'snap_name=snap1,father_name=snap1_father,status=0,' + - 'snap_size=1024,real_size=1024,pool_id=pool1,' + - 'delete_priority=1,create_time=01/01/2015'] - query_result = {'result': 0, 'snap_name': 'snap1', - 'father_name': 'snap1_father', 'status': '0', - 'snap_size': '1024', 'real_size': '1024', - 'pool_id': 'pool1', 'delete_priority': '1', - 'create_time': '01/01/2015'} - mock_execute.return_value = exec_result - retval = self.api.query_snap('snap1') - self.assertEqual(query_result, retval) - - exec_result = ['result=50150007\n'] - qurey_result = {'result': '50150007\n', 'snap_name': '', - 'father_name': '', 'status': '', 'snap_size': '', - 'real_size': '', 'pool_id': '', - 'delete_priority': '', 'create_time': ''} - mock_execute.return_value = exec_result - retval = self.api.query_snap('snap1') - self.assertEqual(qurey_result, retval) - - exec_result = '' - query_result = {'result': 1, 'snap_name': '', 'father_name': '', - 'status': '', 'snap_size': '', 'real_size': '', - 'pool_id': '', 'delete_priority': '', - 'create_time': ''} - mock_execute.return_value = exec_result - retval = self.api.query_snap('snap1') - self.assertEqual(query_result, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_delete_snapshot(self, mock_execute): - mock_execute.side_effect = ['result=0\n', - 'result=50150007\n', None] - - retval = self.api.delete_snapshot('snap_name') - self.assertEqual(0, retval) - - retval = self.api.delete_snapshot('snap_name') - self.assertEqual('50150007\n', retval) - - retval = self.api.delete_snapshot('snap_name') - self.assertEqual(1, retval) - - def test_pool_info_analyze(self): - pool_info = 'pool_id=pool100,total_capacity=1024,' + \ - 'used_capacity=500,alloc_capacity=500' - analyze_res = {'result': 0, 'pool_id': 'pool100', - 'total_capacity': '1024', 'used_capacity': '500', - 'alloc_capacity': '500'} - - retval = self.api.pool_info_analyze(pool_info) - self.assertEqual(analyze_res, retval) - - pool_info = '' - analyze_res = {'result': 1, 'pool_id': '', 'total_capacity': '', - 'used_capacity': '', 'alloc_capacity': ''} - retval = self.api.pool_info_analyze(pool_info) - self.assertEqual(analyze_res, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_query_pool_info(self, mock_execute): - exec_result = ['result=0\n', - 'pool_id=0,total_capacity=1024,' + - 'used_capacity=500,alloc_capacity=500\n'] - query_result = {'result': 0, 'pool_id': '0', - 'total_capacity': '1024', 'used_capacity': '500', - 'alloc_capacity': '500'} - mock_execute.return_value = exec_result - retval = self.api.query_pool_info('0') - self.assertEqual(query_result, retval) - - exec_result = ['result=51050008\n'] - query_result = {'result': '51050008\n', 'pool_id': '', - 'total_capacity': '', 'used_capacity': '', - 'alloc_capacity': ''} - mock_execute.return_value = exec_result - retval = self.api.query_pool_info('0') - self.assertEqual(query_result, retval) - - exec_result = '' - query_result = {'result': 1, 'pool_id': '', 'total_capacity': '', - 'used_capacity': '', 'alloc_capacity': ''} - mock_execute.return_value = exec_result - retval = self.api.query_pool_info('0') - self.assertEqual(query_result, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_query_pool_type(self, mock_execute): - exec_result = ['result=0\n', - 'pool_id=0,total_capacity=1024,' + - 'used_capacity=500,alloc_capacity=500\n'] - query_result = (0, [{'result': 0, - 'pool_id': '0', 'total_capacity': '1024', - 'used_capacity': '500', 'alloc_capacity': '500'}]) - - mock_execute.return_value = exec_result - retval = self.api.query_pool_type('sata2copy') - self.assertEqual(query_result, retval) - - exec_result = ['result=0\n', - 'pool_id=0,total_capacity=1024,' + - 'used_capacity=500,alloc_capacity=500\n', - 'pool_id=1,total_capacity=2048,' + - 'used_capacity=500,alloc_capacity=500\n'] - query_result = (0, [{'result': 0, 'pool_id': '0', - 'total_capacity': '1024', 'used_capacity': '500', - 'alloc_capacity': '500'}, - {'result': 0, 'pool_id': '1', - 'total_capacity': '2048', 'used_capacity': '500', - 'alloc_capacity': '500'}]) - mock_execute.return_value = exec_result - retval = self.api.query_pool_type('sata2copy') - self.assertEqual(query_result, retval) - - exec_result = ['result=51010015\n'] - query_result = (51010015, []) - mock_execute.return_value = exec_result - retval = self.api.query_pool_type('sata2copy') - self.assertEqual(query_result, retval) - - exec_result = '' - query_result = (0, []) - mock_execute.return_value = exec_result - retval = self.api.query_pool_type('sata2copy') - self.assertEqual(query_result, retval) - - @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') - def test_query_dsware_version(self, mock_execute): - mock_execute.side_effect = ['result=0\n', 'result=50500001\n', - 'result=50150007\n', None] - - retval = self.api.query_dsware_version() - self.assertEqual(0, retval) - - retval = self.api.query_dsware_version() - self.assertEqual(1, retval) - - retval = self.api.query_dsware_version() - self.assertEqual('50150007\n', retval) - - retval = self.api.query_dsware_version() - self.assertEqual(2, retval) +# Copyright (c) 2013 - 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. +""" +Unit Tests for Huawei FusionStorage drivers. +""" + +import mock + +from cinder import test +from cinder import utils +from cinder.volume.drivers.fusionstorage import fspythonapi + + +class FSPythonApiTestCase(test.TestCase): + + def setUp(self): + super(FSPythonApiTestCase, self).setUp() + self.api = fspythonapi.FSPythonApi() + + @mock.patch.object(fspythonapi.FSPythonApi, 'get_ip_port') + @mock.patch.object(fspythonapi.FSPythonApi, 'get_manage_ip') + @mock.patch.object(utils, 'execute') + def test_start_execute_cmd(self, mock_execute, + mock_get_manage_ip, mock_get_ip_port): + result1 = ['result=0\ndesc=success\n', ''] + result2 = ['result=50150007\ndesc=volume does not exist\n', ''] + result3 = ['result=50150008\ndesc=volume is being deleted\n', ''] + result4 = ['result=50\ndesc=exception\n', ''] + cmd = 'abcdef' + + mock_get_ip_port.return_value = ['127.0.0.1', '128.0.0.1'] + mock_get_manage_ip.return_value = '127.0.0.1' + + mock_execute.return_value = result1 + retval = self.api.start_execute_cmd(cmd, 0) + self.assertEqual('result=0', retval) + + mock_execute.return_value = result2 + retval = self.api.start_execute_cmd(cmd, 0) + self.assertEqual('result=0', retval) + + mock_execute.return_value = result3 + retval = self.api.start_execute_cmd(cmd, 0) + self.assertEqual('result=0', retval) + + mock_execute.return_value = result4 + retval = self.api.start_execute_cmd(cmd, 0) + self.assertEqual('result=50', retval) + + mock_execute.return_value = result1 + retval = self.api.start_execute_cmd(cmd, 1) + self.assertEqual(['result=0', 'desc=success', ''], retval) + + mock_execute.return_value = result2 + retval = self.api.start_execute_cmd(cmd, 1) + self.assertEqual('result=0', retval) + + mock_execute.return_value = result3 + retval = self.api.start_execute_cmd(cmd, 1) + self.assertEqual('result=0', retval) + + mock_execute.return_value = result4 + retval = self.api.start_execute_cmd(cmd, 1) + self.assertEqual(['result=50', 'desc=exception', ''], retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_create_volume(self, mock_start_execute): + mock_start_execute.side_effect = ['result=0\n', + 'result=50150007\n', None] + + retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) + self.assertEqual(0, retval) + + retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) + self.assertEqual('50150007\n', retval) + + retval = self.api.create_volume('volume_name', 'pool_id-123', 1024, 0) + self.assertEqual(1, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_extend_volume(self, mock_start_execute): + mock_start_execute.side_effect = ['result=0\n', + 'result=50150007\n', None] + + retval = self.api.extend_volume('volume_name', 1024) + self.assertEqual(0, retval) + + retval = self.api.extend_volume('volume_name', 1024) + self.assertEqual('50150007\n', retval) + + retval = self.api.extend_volume('volume_name', 1024) + self.assertEqual(1, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_create_volume_from_snap(self, mock_start_execute): + mock_start_execute.side_effect = ['result=0\n', + 'result=50150007\n', None] + + retval = self.api.create_volume_from_snap('volume_name', 1024, + 'snap_name') + self.assertEqual(0, retval) + + retval = self.api.create_volume_from_snap('volume_name', 1024, + 'snap_name') + self.assertEqual('50150007\n', retval) + + retval = self.api.create_volume_from_snap('volume_name', 1024, + 'snap_name') + self.assertEqual(1, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_create_fullvol_from_snap(self, mock_start_execute): + mock_start_execute.side_effect = ['result=0\n', + 'result=50150007\n', None] + + retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') + self.assertEqual(0, retval) + + retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') + self.assertEqual('50150007\n', retval) + + retval = self.api.create_fullvol_from_snap('volume_name', 'snap_name') + self.assertEqual(1, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') + @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume') + @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') + @mock.patch.object(fspythonapi.FSPythonApi, 'delete_volume') + @mock.patch.object(fspythonapi.FSPythonApi, 'create_fullvol_from_snap') + def test_create_volume_from_volume(self, mock_create_fullvol, + mock_delete_volume, mock_delete_snap, + mock_create_volume, mock_create_snap): + mock_create_snap.return_value = 0 + mock_create_volume.return_value = 0 + mock_create_fullvol.return_value = 0 + + retval = self.api.create_volume_from_volume('vol_name', 1024, + 'src_vol_name') + self.assertEqual(0, retval) + + mock_create_snap.return_value = 1 + retval = self.api.create_volume_from_volume('vol_name', 1024, + 'src_vol_name') + self.assertEqual(1, retval) + + mock_create_snap.return_value = 0 + mock_create_volume.return_value = 1 + retval = self.api.create_volume_from_volume('vol_name', 1024, + 'src_vol_name') + self.assertEqual(1, retval) + + mock_create_volume.return_value = 0 + self.api.create_fullvol_from_snap.return_value = 1 + retval = self.api.create_volume_from_volume('vol_name', 1024, + 'src_vol_name') + self.assertEqual(1, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'create_snapshot') + @mock.patch.object(fspythonapi.FSPythonApi, 'create_volume_from_snap') + def test_create_clone_volume_from_volume(self, mock_volume, mock_snap): + mock_snap.side_effect = [0, 1] + mock_volume.side_effect = [0, 1] + retval = self.api.create_clone_volume_from_volume('vol_name', 1024, + 'src_vol_name') + self.assertEqual(0, retval) + retval = self.api.create_clone_volume_from_volume('vol_name', 1024, + 'src_vol_name') + self.assertEqual(1, retval) + + def test_volume_info_analyze_success(self): + vol_info = ('vol_name=vol1,father_name=vol1_father,' + 'status=available,vol_size=1024,real_size=1024,' + 'pool_id=pool1,create_time=01/01/2015') + vol_info_res = {'result': 0, 'vol_name': 'vol1', + 'father_name': 'vol1_father', + 'status': 'available', 'vol_size': '1024', + 'real_size': '1024', 'pool_id': 'pool1', + 'create_time': '01/01/2015'} + + retval = self.api.volume_info_analyze(vol_info) + self.assertEqual(vol_info_res, retval) + + def test_volume_info_analyze_fail(self): + vol_info = '' + vol_info_res = {'result': 1, 'vol_name': '', 'father_name': '', + 'status': '', 'vol_size': '', 'real_size': '', + 'pool_id': '', 'create_time': ''} + retval = self.api.volume_info_analyze(vol_info) + self.assertEqual(vol_info_res, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + @mock.patch.object(fspythonapi.FSPythonApi, 'volume_info_analyze') + @mock.patch.object(fspythonapi.FSPythonApi, 'delete_snapshot') + def test_query_volume(self, mock_delete, mock_analyze, mock_execute): + exec_result = ['result=0\n', + 'vol_name=vol1,father_name=vol1_father,status=0,' + + 'vol_size=1024,real_size=1024,pool_id=pool1,' + + 'create_time=01/01/2015'] + query_result = {'result': 0, 'vol_name': 'vol1', + 'father_name': 'vol1_father', 'status': '0', + 'vol_size': '1024', 'real_size': '1024', + 'pool_id': 'pool1', 'create_time': '01/01/2015'} + mock_delete.return_value = 0 + mock_execute.return_value = exec_result + mock_analyze.return_value = query_result + retval = self.api.query_volume('vol1') + self.assertEqual(query_result, retval) + + exec_result = ['result=0\n', + 'vol_name=vol1,father_name=vol1_father,status=1,' + + 'vol_size=1024,real_size=1024,pool_id=pool1,' + + 'create_time=01/01/2015'] + query_result = {'result': 0, 'vol_name': 'vol1', + 'father_name': 'vol1_father', 'status': '1', + 'vol_size': '1024', 'real_size': '1024', + 'pool_id': 'pool1', 'create_time': '01/01/2015'} + mock_delete.return_value = 0 + mock_execute.return_value = exec_result + mock_analyze.return_value = query_result + retval = self.api.query_volume('vol1') + self.assertEqual(query_result, retval) + + vol_info_failure = 'result=32500000\n' + failure_res = {'result': 1, 'vol_name': '', 'father_name': '', + 'status': '', 'vol_size': '', 'real_size': '', + 'pool_id': '', 'create_time': ''} + mock_execute.return_value = vol_info_failure + retval = self.api.query_volume('vol1') + self.assertEqual(failure_res, retval) + + vol_info_failure = None + failure_res = {'result': 1, 'vol_name': '', 'father_name': '', + 'status': '', 'vol_size': '', 'real_size': '', + 'pool_id': '', 'create_time': ''} + + mock_execute.return_value = vol_info_failure + retval = self.api.query_volume('vol1') + self.assertEqual(failure_res, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_delete_volume(self, mock_execute): + mock_execute.side_effect = ['result=0\n', + 'result=50150007\n', None] + + retval = self.api.delete_volume('volume_name') + self.assertEqual(0, retval) + + retval = self.api.delete_volume('volume_name') + self.assertEqual('50150007\n', retval) + + retval = self.api.delete_volume('volume_name') + self.assertEqual(1, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_create_snapshot(self, mock_execute): + mock_execute.side_effect = ['result=0\n', + 'result=50150007\n', None] + + retval = self.api.create_snapshot('snap_name', 'vol_name', 0) + self.assertEqual(0, retval) + + retval = self.api.create_snapshot('snap_name', 'vol_name', 0) + self.assertEqual('50150007\n', retval) + + retval = self.api.create_snapshot('snap_name', 'vol_name', 0) + self.assertEqual(1, retval) + + def test_snap_info_analyze_success(self): + snap_info = ('snap_name=snap1,father_name=snap1_father,status=0,' + 'snap_size=1024,real_size=1024,pool_id=pool1,' + 'delete_priority=1,create_time=01/01/2015') + snap_info_res = {'result': 0, 'snap_name': 'snap1', + 'father_name': 'snap1_father', 'status': '0', + 'snap_size': '1024', 'real_size': '1024', + 'pool_id': 'pool1', 'delete_priority': '1', + 'create_time': '01/01/2015'} + + retval = self.api.snap_info_analyze(snap_info) + self.assertEqual(snap_info_res, retval) + + def test_snap_info_analyze_fail(self): + snap_info = '' + snap_info_res = {'result': 1, 'snap_name': '', 'father_name': '', + 'status': '', 'snap_size': '', 'real_size': '', + 'pool_id': '', 'delete_priority': '', + 'create_time': ''} + retval = self.api.snap_info_analyze(snap_info) + self.assertEqual(snap_info_res, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_query_snap(self, mock_execute): + exec_result = ['result=0\n', + 'snap_name=snap1,father_name=snap1_father,status=0,' + + 'snap_size=1024,real_size=1024,pool_id=pool1,' + + 'delete_priority=1,create_time=01/01/2015'] + query_result = {'result': 0, 'snap_name': 'snap1', + 'father_name': 'snap1_father', 'status': '0', + 'snap_size': '1024', 'real_size': '1024', + 'pool_id': 'pool1', 'delete_priority': '1', + 'create_time': '01/01/2015'} + mock_execute.return_value = exec_result + retval = self.api.query_snap('snap1') + self.assertEqual(query_result, retval) + + exec_result = ['result=50150007\n'] + qurey_result = {'result': '50150007\n', 'snap_name': '', + 'father_name': '', 'status': '', 'snap_size': '', + 'real_size': '', 'pool_id': '', + 'delete_priority': '', 'create_time': ''} + mock_execute.return_value = exec_result + retval = self.api.query_snap('snap1') + self.assertEqual(qurey_result, retval) + + exec_result = '' + query_result = {'result': 1, 'snap_name': '', 'father_name': '', + 'status': '', 'snap_size': '', 'real_size': '', + 'pool_id': '', 'delete_priority': '', + 'create_time': ''} + mock_execute.return_value = exec_result + retval = self.api.query_snap('snap1') + self.assertEqual(query_result, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_delete_snapshot(self, mock_execute): + mock_execute.side_effect = ['result=0\n', + 'result=50150007\n', None] + + retval = self.api.delete_snapshot('snap_name') + self.assertEqual(0, retval) + + retval = self.api.delete_snapshot('snap_name') + self.assertEqual('50150007\n', retval) + + retval = self.api.delete_snapshot('snap_name') + self.assertEqual(1, retval) + + def test_pool_info_analyze(self): + pool_info = 'pool_id=pool100,total_capacity=1024,' + \ + 'used_capacity=500,alloc_capacity=500' + analyze_res = {'result': 0, 'pool_id': 'pool100', + 'total_capacity': '1024', 'used_capacity': '500', + 'alloc_capacity': '500'} + + retval = self.api.pool_info_analyze(pool_info) + self.assertEqual(analyze_res, retval) + + pool_info = '' + analyze_res = {'result': 1, 'pool_id': '', 'total_capacity': '', + 'used_capacity': '', 'alloc_capacity': ''} + retval = self.api.pool_info_analyze(pool_info) + self.assertEqual(analyze_res, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_query_pool_info(self, mock_execute): + exec_result = ['result=0\n', + 'pool_id=0,total_capacity=1024,' + + 'used_capacity=500,alloc_capacity=500\n'] + query_result = {'result': 0, 'pool_id': '0', + 'total_capacity': '1024', 'used_capacity': '500', + 'alloc_capacity': '500'} + mock_execute.return_value = exec_result + retval = self.api.query_pool_info('0') + self.assertEqual(query_result, retval) + + exec_result = ['result=51050008\n'] + query_result = {'result': '51050008\n', 'pool_id': '', + 'total_capacity': '', 'used_capacity': '', + 'alloc_capacity': ''} + mock_execute.return_value = exec_result + retval = self.api.query_pool_info('0') + self.assertEqual(query_result, retval) + + exec_result = '' + query_result = {'result': 1, 'pool_id': '', 'total_capacity': '', + 'used_capacity': '', 'alloc_capacity': ''} + mock_execute.return_value = exec_result + retval = self.api.query_pool_info('0') + self.assertEqual(query_result, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_query_pool_type(self, mock_execute): + exec_result = ['result=0\n', + 'pool_id=0,total_capacity=1024,' + + 'used_capacity=500,alloc_capacity=500\n'] + query_result = (0, [{'result': 0, + 'pool_id': '0', 'total_capacity': '1024', + 'used_capacity': '500', 'alloc_capacity': '500'}]) + + mock_execute.return_value = exec_result + retval = self.api.query_pool_type('sata2copy') + self.assertEqual(query_result, retval) + + exec_result = ['result=0\n', + 'pool_id=0,total_capacity=1024,' + + 'used_capacity=500,alloc_capacity=500\n', + 'pool_id=1,total_capacity=2048,' + + 'used_capacity=500,alloc_capacity=500\n'] + query_result = (0, [{'result': 0, 'pool_id': '0', + 'total_capacity': '1024', 'used_capacity': '500', + 'alloc_capacity': '500'}, + {'result': 0, 'pool_id': '1', + 'total_capacity': '2048', 'used_capacity': '500', + 'alloc_capacity': '500'}]) + mock_execute.return_value = exec_result + retval = self.api.query_pool_type('sata2copy') + self.assertEqual(query_result, retval) + + exec_result = ['result=51010015\n'] + query_result = (51010015, []) + mock_execute.return_value = exec_result + retval = self.api.query_pool_type('sata2copy') + self.assertEqual(query_result, retval) + + exec_result = '' + query_result = (0, []) + mock_execute.return_value = exec_result + retval = self.api.query_pool_type('sata2copy') + self.assertEqual(query_result, retval) + + @mock.patch.object(fspythonapi.FSPythonApi, 'start_execute_cmd') + def test_query_dsware_version(self, mock_execute): + mock_execute.side_effect = ['result=0\n', 'result=50500001\n', + 'result=50150007\n', None] + + retval = self.api.query_dsware_version() + self.assertEqual(0, retval) + + retval = self.api.query_dsware_version() + self.assertEqual(1, retval) + + retval = self.api.query_dsware_version() + self.assertEqual('50150007\n', retval) + + retval = self.api.query_dsware_version() + self.assertEqual(2, retval) diff --git a/cinder/tests/unit/volume/drivers/nexenta/test_nexenta_edge_nbd.py b/cinder/tests/unit/volume/drivers/nexenta/test_nexenta_edge_nbd.py index ddcd165cc9a..ae76f6cab6e 100644 --- a/cinder/tests/unit/volume/drivers/nexenta/test_nexenta_edge_nbd.py +++ b/cinder/tests/unit/volume/drivers/nexenta/test_nexenta_edge_nbd.py @@ -1,503 +1,503 @@ -# Copyright 2016 Nexenta Systems, 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 socket - -import mock -from mock import patch -from oslo_serialization import jsonutils -from oslo_utils import units - -from cinder import context -from cinder import exception -from cinder import test -from cinder.volume import configuration as conf -from cinder.volume.drivers.nexenta.nexentaedge import jsonrpc -from cinder.volume.drivers.nexenta.nexentaedge import nbd - - -class FakeResponse(object): - - def __init__(self, response): - self.response = response - super(FakeResponse, self).__init__() - - def json(self): - return self.response - - def close(self): - pass - - -class RequestParams(object): - def __init__(self, scheme, host, port, user, password): - self.scheme = scheme.lower() - self.host = host - self.port = port - self.user = user - self.password = password - - def url(self, path=''): - return '%s://%s:%s/%s' % ( - self.scheme, self.host, self.port, path) - - def build_post_args(self, args): - return jsonutils.dumps(args) - - -class TestNexentaEdgeNBDDriver(test.TestCase): - - def setUp(self): - def _safe_get(opt): - return getattr(self.cfg, opt) - super(TestNexentaEdgeNBDDriver, self).setUp() - self.cfg = mock.Mock(spec=conf.Configuration) - self.cfg.safe_get = mock.Mock(side_effect=_safe_get) - self.cfg.trace_flags = 'fake_trace_flags' - self.cfg.driver_data_namespace = 'fake_driver_data_namespace' - self.cfg.nexenta_rest_protocol = 'http' - self.cfg.nexenta_rest_address = '127.0.0.1' - self.cfg.nexenta_rest_port = 8080 - self.cfg.nexenta_rest_user = 'admin' - self.cfg.nexenta_rest_password = '0' - self.cfg.nexenta_lun_container = 'cluster/tenant/bucket' - self.cfg.nexenta_nbd_symlinks_dir = '/dev/disk/by-path' - self.cfg.volume_dd_blocksize = 512 - self.cfg.nexenta_blocksize = 512 - self.cfg.nexenta_chunksize = 4096 - self.cfg.reserved_percentage = 0 - - self.ctx = context.get_admin_context() - self.drv = nbd.NexentaEdgeNBDDriver(configuration=self.cfg) - - session = mock.Mock() - session.get = mock.Mock() - session.post = mock.Mock() - session.put = mock.Mock() - session.delete = mock.Mock() - self.drv.do_setup(self.ctx) - self.drv.restapi.session = session - self.mock_api = session - - self.request_params = RequestParams( - 'http', self.cfg.nexenta_rest_address, self.cfg.nexenta_rest_port, - self.cfg.nexenta_rest_user, self.cfg.nexenta_rest_password) - - def test_check_do_setup__symlinks_dir_not_specified(self): - self.drv.symlinks_dir = None - self.assertRaises( - exception.NexentaException, self.drv.check_for_setup_error) - - def test_check_do_setup__symlinks_dir_doesnt_exist(self): - self.drv.symlinks_dir = '/some/random/path' - self.assertRaises( - exception.NexentaException, self.drv.check_for_setup_error) - - @patch('os.path.exists') - def test_check_do_setup__empty_response(self, exists): - self.mock_api.get.return_value = FakeResponse({}) - exists.return_value = True - self.assertRaises(exception.VolumeBackendAPIException, - self.drv.check_for_setup_error) - - @patch('os.path.exists') - def test_check_do_setup(self, exists): - self.mock_api.get.return_value = FakeResponse({'response': 'OK'}) - exists.return_value = True - self.drv.check_for_setup_error() - self.mock_api.get.assert_any_call( - self.request_params.url(self.drv.bucket_url + '/objects/'), - timeout=jsonrpc.TIMEOUT) - - def test_local_path__error(self): - self.drv._get_nbd_number = lambda volume_: -1 - volume = {'name': 'volume'} - self.assertRaises(exception.VolumeBackendAPIException, - self.drv.local_path, volume) - - def test_local_path(self): - volume = { - 'name': 'volume', - 'host': 'myhost@backend#pool' - } - _get_host_info__response = { - 'stats': { - 'servers': { - 'host1': { - 'hostname': 'host1', - 'ipv6addr': 'fe80::fc16:3eff:fedb:bd69'}, - 'host2': { - 'hostname': 'myhost', - 'ipv6addr': 'fe80::fc16:3eff:fedb:bd68'} - } - } - } - _get_nbd_devices__response = { - 'value': jsonutils.dumps([ - { - 'objectPath': '/'.join( - (self.cfg.nexenta_lun_container, 'some_volume')), - 'number': 1 - }, - { - 'objectPath': '/'.join( - (self.cfg.nexenta_lun_container, volume['name'])), - 'number': 2 - } - ]) - } - - def my_side_effect(*args, **kwargs): - if args[0] == self.request_params.url('system/stats'): - return FakeResponse({'response': _get_host_info__response}) - elif args[0].startswith( - self.request_params.url('sysconfig/nbd/devices')): - return FakeResponse({'response': _get_nbd_devices__response}) - else: - raise Exception('Unexpected request') - - self.mock_api.get.side_effect = my_side_effect - self.drv.local_path(volume) - - def test_local_path__host_not_found(self): - volume = { - 'name': 'volume', - 'host': 'unknown-host@backend#pool' - } - _get_host_info__response = { - 'stats': { - 'servers': { - 'host1': { - 'hostname': 'host1', - 'ipv6addr': 'fe80::fc16:3eff:fedb:bd69'}, - 'host2': { - 'hostname': 'myhost', - 'ipv6addr': 'fe80::fc16:3eff:fedb:bd68'} - } - } - } - _get_nbd_devices__response = { - 'value': jsonutils.dumps([ - { - 'objectPath': '/'.join( - (self.cfg.nexenta_lun_container, 'some_volume')), - 'number': 1 - }, - { - 'objectPath': '/'.join( - (self.cfg.nexenta_lun_container, volume['name'])), - 'number': 2 - } - ]) - } - - def my_side_effect(*args, **kwargs): - if args[0] == self.request_params.url('system/stats'): - return FakeResponse({'response': _get_host_info__response}) - elif args[0].startswith( - self.request_params.url('sysconfig/nbd/devices')): - return FakeResponse({'response': _get_nbd_devices__response}) - else: - raise Exception('Unexpected request') - - self.mock_api.get.side_effect = my_side_effect - self.assertRaises(exception.VolumeBackendAPIException, - self.drv.local_path, volume) - - @patch('cinder.utils.execute') - def test_create_volume(self, execute): - self.mock_api.post.returning_value = FakeResponse({}) - volume = { - 'host': 'host@backend#pool info', - 'size': 1, - 'name': 'volume' - } - number = 5 - remote_url = '' - self.drv._get_remote_url = lambda host_: remote_url - self.drv._get_nbd_number = lambda volume_: number - self.drv.create_volume(volume) - self.mock_api.post.assert_called_with( - self.request_params.url('nbd' + remote_url), - data=self.request_params.build_post_args({ - 'objectPath': '/'.join((self.cfg.nexenta_lun_container, - volume['name'])), - 'volSizeMB': volume['size'] * units.Ki, - 'blockSize': self.cfg.nexenta_blocksize, - 'chunkSize': self.cfg.nexenta_chunksize}), - timeout=jsonrpc.TIMEOUT) - - def test_delete_volume(self): - self.mock_api.delete.returning_value = FakeResponse({}) - volume = { - 'host': 'host@backend#pool info', - 'size': 1, - 'name': 'volume' - } - number = 5 - remote_url = '' - self.drv._get_remote_url = lambda host_: remote_url - self.drv._get_nbd_number = lambda volume_: number - self.drv.delete_volume(volume) - self.mock_api.delete.assert_called_with( - self.request_params.url('nbd' + remote_url), - data=self.request_params.build_post_args({ - 'objectPath': '/'.join((self.cfg.nexenta_lun_container, - volume['name'])), - 'number': number}), - timeout=jsonrpc.TIMEOUT) - - def test_delete_volume__not_found(self): - self.mock_api.delete.returning_value = FakeResponse({}) - volume = { - 'host': 'host@backend#pool info', - 'size': 1, - 'name': 'volume' - } - remote_url = '' - self.drv._get_remote_url = lambda host_: remote_url - self.drv._get_nbd_number = lambda volume_: -1 - self.drv.delete_volume(volume) - self.mock_api.delete.assert_not_called() - - def test_extend_volume(self): - self.mock_api.put.returning_value = FakeResponse({}) - volume = { - 'host': 'host@backend#pool info', - 'size': 1, - 'name': 'volume' - } - new_size = 5 - remote_url = '' - self.drv._get_remote_url = lambda host_: remote_url - self.drv.extend_volume(volume, new_size) - self.mock_api.put.assert_called_with( - self.request_params.url('nbd/resize' + remote_url), - data=self.request_params.build_post_args({ - 'objectPath': '/'.join((self.cfg.nexenta_lun_container, - volume['name'])), - 'newSizeMB': new_size * units.Ki}), - timeout=jsonrpc.TIMEOUT) - - def test_create_snapshot(self): - self.mock_api.post.returning_value = FakeResponse({}) - snapshot = { - 'name': 'dsfsdsdgfdf', - 'volume_name': 'volume' - } - self.drv.create_snapshot(snapshot) - self.mock_api.post.assert_called_with( - self.request_params.url('nbd/snapshot'), - data=self.request_params.build_post_args({ - 'objectPath': '/'.join((self.cfg.nexenta_lun_container, - snapshot['volume_name'])), - 'snapName': snapshot['name']}), - timeout=jsonrpc.TIMEOUT) - - def test_delete_snapshot(self): - self.mock_api.delete.returning_value = FakeResponse({}) - snapshot = { - 'name': 'dsfsdsdgfdf', - 'volume_name': 'volume' - } - self.drv.delete_snapshot(snapshot) - self.mock_api.delete.assert_called_with( - self.request_params.url('nbd/snapshot'), - data=self.request_params.build_post_args({ - 'objectPath': '/'.join((self.cfg.nexenta_lun_container, - snapshot['volume_name'])), - 'snapName': snapshot['name']}), - timeout=jsonrpc.TIMEOUT) - - def test_create_volume_from_snapshot(self): - self.mock_api.put.returning_value = FakeResponse({}) - snapshot = { - 'name': 'dsfsdsdgfdf', - 'volume_size': 1, - 'volume_name': 'volume' - } - volume = { - 'host': 'host@backend#pool info', - 'size': 2, - 'name': 'volume' - } - remote_url = '' - self.drv._get_remote_url = lambda host_: remote_url - self.drv.extend_volume = lambda v, s: None - self.drv.create_volume_from_snapshot(volume, snapshot) - self.mock_api.put.assert_called_with( - self.request_params.url('nbd/snapshot/clone' + remote_url), - data=self.request_params.build_post_args({ - 'objectPath': '/'.join((self.cfg.nexenta_lun_container, - snapshot['volume_name'])), - 'snapName': snapshot['name'], - 'clonePath': '/'.join((self.cfg.nexenta_lun_container, - volume['name'])) - }), - timeout=jsonrpc.TIMEOUT) - - def test_create_cloned_volume(self): - self.mock_api.post.returning_value = FakeResponse({}) - volume = { - 'host': 'host@backend#pool info', - 'size': 1, - 'name': 'volume' - } - src_vref = { - 'size': 1, - 'name': 'qwerty' - } - container = self.cfg.nexenta_lun_container - remote_url = '' - self.drv._get_remote_url = lambda host_: remote_url - self.drv.create_cloned_volume(volume, src_vref) - self.mock_api.post.assert_called_with( - self.request_params.url('nbd' + remote_url), - data=self.request_params.build_post_args({ - 'objectPath': '/'.join((container, volume['name'])), - 'volSizeMB': src_vref['size'] * units.Ki, - 'blockSize': self.cfg.nexenta_blocksize, - 'chunkSize': self.cfg.nexenta_chunksize - }), - timeout=jsonrpc.TIMEOUT) - - def test_create_cloned_volume_gt_src(self): - self.mock_api.post.returning_value = FakeResponse({}) - volume = { - 'host': 'host@backend#pool info', - 'size': 2, - 'name': 'volume' - } - src_vref = { - 'size': 1, - 'name': 'qwerty' - } - container = self.cfg.nexenta_lun_container - remote_url = '' - self.drv._get_remote_url = lambda host_: remote_url - self.drv.create_cloned_volume(volume, src_vref) - self.mock_api.post.assert_called_with( - self.request_params.url('nbd' + remote_url), - data=self.request_params.build_post_args({ - 'objectPath': '/'.join((container, volume['name'])), - 'volSizeMB': volume['size'] * units.Ki, - 'blockSize': self.cfg.nexenta_blocksize, - 'chunkSize': self.cfg.nexenta_chunksize - }), - timeout=jsonrpc.TIMEOUT) - - def test_get_volume_stats(self): - self.cfg.volume_backend_name = None - self.mock_api.get.return_value = FakeResponse({ - 'response': { - 'stats': { - 'summary': { - 'total_capacity': units.Gi, - 'total_available': units.Gi - } - } - } - }) - location_info = '%(driver)s:%(host)s:%(bucket)s' % { - 'driver': self.drv.__class__.__name__, - 'host': socket.gethostname(), - 'bucket': self.cfg.nexenta_lun_container - } - expected = { - 'vendor_name': 'Nexenta', - 'driver_version': self.drv.VERSION, - 'storage_protocol': 'NBD', - 'reserved_percentage': self.cfg.reserved_percentage, - 'total_capacity_gb': 1, - 'free_capacity_gb': 1, - 'QoS_support': False, - 'volume_backend_name': self.drv.__class__.__name__, - 'location_info': location_info, - 'restapi_url': '%s://%s:%s/' % ( - 'http', self.cfg.nexenta_rest_address, - self.cfg.nexenta_rest_port) - } - - self.assertEqual(expected, self.drv.get_volume_stats()) - - @patch('cinder.image.image_utils.fetch_to_raw') - def test_copy_image_to_volume(self, fetch_to_raw): - volume = { - 'host': 'host@backend#pool info', - 'size': 1, - 'name': 'volume' - } - self.drv.local_path = lambda host: 'local_path' - self.drv.copy_image_to_volume(self.ctx, volume, 'image_service', - 'image_id') - fetch_to_raw.assert_called_with( - self.ctx, 'image_service', 'image_id', 'local_path', - self.cfg.volume_dd_blocksize, size=volume['size']) - - @patch('cinder.image.image_utils.upload_volume') - def test_copy_volume_to_image(self, upload_volume): - volume = { - 'host': 'host@backend#pool info', - 'size': 1, - 'name': 'volume' - } - self.drv.local_path = lambda host: 'local_path' - self.drv.copy_volume_to_image(self.ctx, volume, 'image_service', - 'image_meta') - upload_volume.assert_called_with( - self.ctx, 'image_service', 'image_meta', 'local_path') - - def test_validate_connector(self): - connector = {'host': 'host2'} - r = { - 'stats': { - 'servers': { - 'host1': {'hostname': 'host1'}, - 'host2': {'hostname': 'host2'} - } - } - } - self.mock_api.get.return_value = FakeResponse({'response': r}) - self.drv.validate_connector(connector) - self.mock_api.get.assert_called_with( - self.request_params.url('system/stats'), - timeout=jsonrpc.TIMEOUT) - - def test_validate_connector__host_not_found(self): - connector = {'host': 'host3'} - r = { - 'stats': { - 'servers': { - 'host1': {'hostname': 'host1'}, - 'host2': {'hostname': 'host2'} - } - } - } - self.mock_api.get.return_value = FakeResponse({'response': r}) - self.assertRaises(exception.VolumeBackendAPIException, - self.drv.validate_connector, connector) - - def test_initialize_connection(self): - connector = {'host': 'host'} - volume = { - 'host': 'host@backend#pool info', - 'size': 1, - 'name': 'volume' - } - self.drv.local_path = lambda host: 'local_path' - self.assertEqual({ - 'driver_volume_type': 'local', - 'data': {'device_path': 'local_path'}}, - self.drv.initialize_connection(volume, connector)) +# Copyright 2016 Nexenta Systems, 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 socket + +import mock +from mock import patch +from oslo_serialization import jsonutils +from oslo_utils import units + +from cinder import context +from cinder import exception +from cinder import test +from cinder.volume import configuration as conf +from cinder.volume.drivers.nexenta.nexentaedge import jsonrpc +from cinder.volume.drivers.nexenta.nexentaedge import nbd + + +class FakeResponse(object): + + def __init__(self, response): + self.response = response + super(FakeResponse, self).__init__() + + def json(self): + return self.response + + def close(self): + pass + + +class RequestParams(object): + def __init__(self, scheme, host, port, user, password): + self.scheme = scheme.lower() + self.host = host + self.port = port + self.user = user + self.password = password + + def url(self, path=''): + return '%s://%s:%s/%s' % ( + self.scheme, self.host, self.port, path) + + def build_post_args(self, args): + return jsonutils.dumps(args) + + +class TestNexentaEdgeNBDDriver(test.TestCase): + + def setUp(self): + def _safe_get(opt): + return getattr(self.cfg, opt) + super(TestNexentaEdgeNBDDriver, self).setUp() + self.cfg = mock.Mock(spec=conf.Configuration) + self.cfg.safe_get = mock.Mock(side_effect=_safe_get) + self.cfg.trace_flags = 'fake_trace_flags' + self.cfg.driver_data_namespace = 'fake_driver_data_namespace' + self.cfg.nexenta_rest_protocol = 'http' + self.cfg.nexenta_rest_address = '127.0.0.1' + self.cfg.nexenta_rest_port = 8080 + self.cfg.nexenta_rest_user = 'admin' + self.cfg.nexenta_rest_password = '0' + self.cfg.nexenta_lun_container = 'cluster/tenant/bucket' + self.cfg.nexenta_nbd_symlinks_dir = '/dev/disk/by-path' + self.cfg.volume_dd_blocksize = 512 + self.cfg.nexenta_blocksize = 512 + self.cfg.nexenta_chunksize = 4096 + self.cfg.reserved_percentage = 0 + + self.ctx = context.get_admin_context() + self.drv = nbd.NexentaEdgeNBDDriver(configuration=self.cfg) + + session = mock.Mock() + session.get = mock.Mock() + session.post = mock.Mock() + session.put = mock.Mock() + session.delete = mock.Mock() + self.drv.do_setup(self.ctx) + self.drv.restapi.session = session + self.mock_api = session + + self.request_params = RequestParams( + 'http', self.cfg.nexenta_rest_address, self.cfg.nexenta_rest_port, + self.cfg.nexenta_rest_user, self.cfg.nexenta_rest_password) + + def test_check_do_setup__symlinks_dir_not_specified(self): + self.drv.symlinks_dir = None + self.assertRaises( + exception.NexentaException, self.drv.check_for_setup_error) + + def test_check_do_setup__symlinks_dir_doesnt_exist(self): + self.drv.symlinks_dir = '/some/random/path' + self.assertRaises( + exception.NexentaException, self.drv.check_for_setup_error) + + @patch('os.path.exists') + def test_check_do_setup__empty_response(self, exists): + self.mock_api.get.return_value = FakeResponse({}) + exists.return_value = True + self.assertRaises(exception.VolumeBackendAPIException, + self.drv.check_for_setup_error) + + @patch('os.path.exists') + def test_check_do_setup(self, exists): + self.mock_api.get.return_value = FakeResponse({'response': 'OK'}) + exists.return_value = True + self.drv.check_for_setup_error() + self.mock_api.get.assert_any_call( + self.request_params.url(self.drv.bucket_url + '/objects/'), + timeout=jsonrpc.TIMEOUT) + + def test_local_path__error(self): + self.drv._get_nbd_number = lambda volume_: -1 + volume = {'name': 'volume'} + self.assertRaises(exception.VolumeBackendAPIException, + self.drv.local_path, volume) + + def test_local_path(self): + volume = { + 'name': 'volume', + 'host': 'myhost@backend#pool' + } + _get_host_info__response = { + 'stats': { + 'servers': { + 'host1': { + 'hostname': 'host1', + 'ipv6addr': 'fe80::fc16:3eff:fedb:bd69'}, + 'host2': { + 'hostname': 'myhost', + 'ipv6addr': 'fe80::fc16:3eff:fedb:bd68'} + } + } + } + _get_nbd_devices__response = { + 'value': jsonutils.dumps([ + { + 'objectPath': '/'.join( + (self.cfg.nexenta_lun_container, 'some_volume')), + 'number': 1 + }, + { + 'objectPath': '/'.join( + (self.cfg.nexenta_lun_container, volume['name'])), + 'number': 2 + } + ]) + } + + def my_side_effect(*args, **kwargs): + if args[0] == self.request_params.url('system/stats'): + return FakeResponse({'response': _get_host_info__response}) + elif args[0].startswith( + self.request_params.url('sysconfig/nbd/devices')): + return FakeResponse({'response': _get_nbd_devices__response}) + else: + raise Exception('Unexpected request') + + self.mock_api.get.side_effect = my_side_effect + self.drv.local_path(volume) + + def test_local_path__host_not_found(self): + volume = { + 'name': 'volume', + 'host': 'unknown-host@backend#pool' + } + _get_host_info__response = { + 'stats': { + 'servers': { + 'host1': { + 'hostname': 'host1', + 'ipv6addr': 'fe80::fc16:3eff:fedb:bd69'}, + 'host2': { + 'hostname': 'myhost', + 'ipv6addr': 'fe80::fc16:3eff:fedb:bd68'} + } + } + } + _get_nbd_devices__response = { + 'value': jsonutils.dumps([ + { + 'objectPath': '/'.join( + (self.cfg.nexenta_lun_container, 'some_volume')), + 'number': 1 + }, + { + 'objectPath': '/'.join( + (self.cfg.nexenta_lun_container, volume['name'])), + 'number': 2 + } + ]) + } + + def my_side_effect(*args, **kwargs): + if args[0] == self.request_params.url('system/stats'): + return FakeResponse({'response': _get_host_info__response}) + elif args[0].startswith( + self.request_params.url('sysconfig/nbd/devices')): + return FakeResponse({'response': _get_nbd_devices__response}) + else: + raise Exception('Unexpected request') + + self.mock_api.get.side_effect = my_side_effect + self.assertRaises(exception.VolumeBackendAPIException, + self.drv.local_path, volume) + + @patch('cinder.utils.execute') + def test_create_volume(self, execute): + self.mock_api.post.returning_value = FakeResponse({}) + volume = { + 'host': 'host@backend#pool info', + 'size': 1, + 'name': 'volume' + } + number = 5 + remote_url = '' + self.drv._get_remote_url = lambda host_: remote_url + self.drv._get_nbd_number = lambda volume_: number + self.drv.create_volume(volume) + self.mock_api.post.assert_called_with( + self.request_params.url('nbd' + remote_url), + data=self.request_params.build_post_args({ + 'objectPath': '/'.join((self.cfg.nexenta_lun_container, + volume['name'])), + 'volSizeMB': volume['size'] * units.Ki, + 'blockSize': self.cfg.nexenta_blocksize, + 'chunkSize': self.cfg.nexenta_chunksize}), + timeout=jsonrpc.TIMEOUT) + + def test_delete_volume(self): + self.mock_api.delete.returning_value = FakeResponse({}) + volume = { + 'host': 'host@backend#pool info', + 'size': 1, + 'name': 'volume' + } + number = 5 + remote_url = '' + self.drv._get_remote_url = lambda host_: remote_url + self.drv._get_nbd_number = lambda volume_: number + self.drv.delete_volume(volume) + self.mock_api.delete.assert_called_with( + self.request_params.url('nbd' + remote_url), + data=self.request_params.build_post_args({ + 'objectPath': '/'.join((self.cfg.nexenta_lun_container, + volume['name'])), + 'number': number}), + timeout=jsonrpc.TIMEOUT) + + def test_delete_volume__not_found(self): + self.mock_api.delete.returning_value = FakeResponse({}) + volume = { + 'host': 'host@backend#pool info', + 'size': 1, + 'name': 'volume' + } + remote_url = '' + self.drv._get_remote_url = lambda host_: remote_url + self.drv._get_nbd_number = lambda volume_: -1 + self.drv.delete_volume(volume) + self.mock_api.delete.assert_not_called() + + def test_extend_volume(self): + self.mock_api.put.returning_value = FakeResponse({}) + volume = { + 'host': 'host@backend#pool info', + 'size': 1, + 'name': 'volume' + } + new_size = 5 + remote_url = '' + self.drv._get_remote_url = lambda host_: remote_url + self.drv.extend_volume(volume, new_size) + self.mock_api.put.assert_called_with( + self.request_params.url('nbd/resize' + remote_url), + data=self.request_params.build_post_args({ + 'objectPath': '/'.join((self.cfg.nexenta_lun_container, + volume['name'])), + 'newSizeMB': new_size * units.Ki}), + timeout=jsonrpc.TIMEOUT) + + def test_create_snapshot(self): + self.mock_api.post.returning_value = FakeResponse({}) + snapshot = { + 'name': 'dsfsdsdgfdf', + 'volume_name': 'volume' + } + self.drv.create_snapshot(snapshot) + self.mock_api.post.assert_called_with( + self.request_params.url('nbd/snapshot'), + data=self.request_params.build_post_args({ + 'objectPath': '/'.join((self.cfg.nexenta_lun_container, + snapshot['volume_name'])), + 'snapName': snapshot['name']}), + timeout=jsonrpc.TIMEOUT) + + def test_delete_snapshot(self): + self.mock_api.delete.returning_value = FakeResponse({}) + snapshot = { + 'name': 'dsfsdsdgfdf', + 'volume_name': 'volume' + } + self.drv.delete_snapshot(snapshot) + self.mock_api.delete.assert_called_with( + self.request_params.url('nbd/snapshot'), + data=self.request_params.build_post_args({ + 'objectPath': '/'.join((self.cfg.nexenta_lun_container, + snapshot['volume_name'])), + 'snapName': snapshot['name']}), + timeout=jsonrpc.TIMEOUT) + + def test_create_volume_from_snapshot(self): + self.mock_api.put.returning_value = FakeResponse({}) + snapshot = { + 'name': 'dsfsdsdgfdf', + 'volume_size': 1, + 'volume_name': 'volume' + } + volume = { + 'host': 'host@backend#pool info', + 'size': 2, + 'name': 'volume' + } + remote_url = '' + self.drv._get_remote_url = lambda host_: remote_url + self.drv.extend_volume = lambda v, s: None + self.drv.create_volume_from_snapshot(volume, snapshot) + self.mock_api.put.assert_called_with( + self.request_params.url('nbd/snapshot/clone' + remote_url), + data=self.request_params.build_post_args({ + 'objectPath': '/'.join((self.cfg.nexenta_lun_container, + snapshot['volume_name'])), + 'snapName': snapshot['name'], + 'clonePath': '/'.join((self.cfg.nexenta_lun_container, + volume['name'])) + }), + timeout=jsonrpc.TIMEOUT) + + def test_create_cloned_volume(self): + self.mock_api.post.returning_value = FakeResponse({}) + volume = { + 'host': 'host@backend#pool info', + 'size': 1, + 'name': 'volume' + } + src_vref = { + 'size': 1, + 'name': 'qwerty' + } + container = self.cfg.nexenta_lun_container + remote_url = '' + self.drv._get_remote_url = lambda host_: remote_url + self.drv.create_cloned_volume(volume, src_vref) + self.mock_api.post.assert_called_with( + self.request_params.url('nbd' + remote_url), + data=self.request_params.build_post_args({ + 'objectPath': '/'.join((container, volume['name'])), + 'volSizeMB': src_vref['size'] * units.Ki, + 'blockSize': self.cfg.nexenta_blocksize, + 'chunkSize': self.cfg.nexenta_chunksize + }), + timeout=jsonrpc.TIMEOUT) + + def test_create_cloned_volume_gt_src(self): + self.mock_api.post.returning_value = FakeResponse({}) + volume = { + 'host': 'host@backend#pool info', + 'size': 2, + 'name': 'volume' + } + src_vref = { + 'size': 1, + 'name': 'qwerty' + } + container = self.cfg.nexenta_lun_container + remote_url = '' + self.drv._get_remote_url = lambda host_: remote_url + self.drv.create_cloned_volume(volume, src_vref) + self.mock_api.post.assert_called_with( + self.request_params.url('nbd' + remote_url), + data=self.request_params.build_post_args({ + 'objectPath': '/'.join((container, volume['name'])), + 'volSizeMB': volume['size'] * units.Ki, + 'blockSize': self.cfg.nexenta_blocksize, + 'chunkSize': self.cfg.nexenta_chunksize + }), + timeout=jsonrpc.TIMEOUT) + + def test_get_volume_stats(self): + self.cfg.volume_backend_name = None + self.mock_api.get.return_value = FakeResponse({ + 'response': { + 'stats': { + 'summary': { + 'total_capacity': units.Gi, + 'total_available': units.Gi + } + } + } + }) + location_info = '%(driver)s:%(host)s:%(bucket)s' % { + 'driver': self.drv.__class__.__name__, + 'host': socket.gethostname(), + 'bucket': self.cfg.nexenta_lun_container + } + expected = { + 'vendor_name': 'Nexenta', + 'driver_version': self.drv.VERSION, + 'storage_protocol': 'NBD', + 'reserved_percentage': self.cfg.reserved_percentage, + 'total_capacity_gb': 1, + 'free_capacity_gb': 1, + 'QoS_support': False, + 'volume_backend_name': self.drv.__class__.__name__, + 'location_info': location_info, + 'restapi_url': '%s://%s:%s/' % ( + 'http', self.cfg.nexenta_rest_address, + self.cfg.nexenta_rest_port) + } + + self.assertEqual(expected, self.drv.get_volume_stats()) + + @patch('cinder.image.image_utils.fetch_to_raw') + def test_copy_image_to_volume(self, fetch_to_raw): + volume = { + 'host': 'host@backend#pool info', + 'size': 1, + 'name': 'volume' + } + self.drv.local_path = lambda host: 'local_path' + self.drv.copy_image_to_volume(self.ctx, volume, 'image_service', + 'image_id') + fetch_to_raw.assert_called_with( + self.ctx, 'image_service', 'image_id', 'local_path', + self.cfg.volume_dd_blocksize, size=volume['size']) + + @patch('cinder.image.image_utils.upload_volume') + def test_copy_volume_to_image(self, upload_volume): + volume = { + 'host': 'host@backend#pool info', + 'size': 1, + 'name': 'volume' + } + self.drv.local_path = lambda host: 'local_path' + self.drv.copy_volume_to_image(self.ctx, volume, 'image_service', + 'image_meta') + upload_volume.assert_called_with( + self.ctx, 'image_service', 'image_meta', 'local_path') + + def test_validate_connector(self): + connector = {'host': 'host2'} + r = { + 'stats': { + 'servers': { + 'host1': {'hostname': 'host1'}, + 'host2': {'hostname': 'host2'} + } + } + } + self.mock_api.get.return_value = FakeResponse({'response': r}) + self.drv.validate_connector(connector) + self.mock_api.get.assert_called_with( + self.request_params.url('system/stats'), + timeout=jsonrpc.TIMEOUT) + + def test_validate_connector__host_not_found(self): + connector = {'host': 'host3'} + r = { + 'stats': { + 'servers': { + 'host1': {'hostname': 'host1'}, + 'host2': {'hostname': 'host2'} + } + } + } + self.mock_api.get.return_value = FakeResponse({'response': r}) + self.assertRaises(exception.VolumeBackendAPIException, + self.drv.validate_connector, connector) + + def test_initialize_connection(self): + connector = {'host': 'host'} + volume = { + 'host': 'host@backend#pool info', + 'size': 1, + 'name': 'volume' + } + self.drv.local_path = lambda host: 'local_path' + self.assertEqual({ + 'driver_volume_type': 'local', + 'data': {'device_path': 'local_path'}}, + self.drv.initialize_connection(volume, connector)) diff --git a/cinder/volume/drivers/fusionstorage/dsware.py b/cinder/volume/drivers/fusionstorage/dsware.py index bd08f67230d..c23172d6565 100644 --- a/cinder/volume/drivers/fusionstorage/dsware.py +++ b/cinder/volume/drivers/fusionstorage/dsware.py @@ -1,627 +1,627 @@ -# Copyright (c) 2013 - 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. -""" -Driver for Huawei FusionStorage. -""" - -import os -import re - -from oslo_config import cfg -from oslo_log import log as logging -from oslo_service import loopingcall - -from cinder import exception -from cinder.i18n import _, _LE, _LW -from cinder.image import image_utils -from cinder import interface -from cinder.volume import driver -from cinder.volume.drivers.fusionstorage import fspythonapi - -LOG = logging.getLogger(__name__) - -volume_opts = [ - cfg.BoolOpt('dsware_isthin', - default = False, - help = 'The flag of thin storage allocation.'), - cfg.StrOpt('dsware_manager', - default = '', - help = 'Fusionstorage manager ip addr for cinder-volume.'), - cfg.StrOpt('fusionstorageagent', - default = '', - help = 'Fusionstorage agent ip addr range.'), - cfg.StrOpt('pool_type', - default = 'default', - help = 'Pool type, like sata-2copy.'), - cfg.ListOpt('pool_id_filter', - default = [], - help = 'Pool id permit to use.'), - cfg.IntOpt('clone_volume_timeout', - default = 680, - help = 'Create clone volume timeout.'), -] - -CONF = cfg.CONF -CONF.register_opts(volume_opts) - -OLD_VERSION = 1 -NEW_VERSION = 0 -VOLUME_ALREADY_ATTACHED = 50151401 -VOLUME_NOT_EXIST = '50150005\n' -VOLUME_BEING_DELETED = '50151002\n' -SNAP_NOT_EXIST = '50150006\n' - - -@interface.volumedriver -class DSWAREDriver(driver.VolumeDriver): - """Huawei FusionStorage Driver.""" - VERSION = '1.0' - - # ThirdPartySystems wiki page - CI_WIKI_NAME = "Huawei_volume_CI" - - DSWARE_VOLUME_CREATE_SUCCESS_STATUS = 0 - DSWARE_VOLUME_DUPLICATE_VOLUME = 6 - DSWARE_VOLUME_CREATING_STATUS = 7 - - def __init__(self, *args, **kwargs): - super(DSWAREDriver, self).__init__(*args, **kwargs) - self.dsware_client = fspythonapi.FSPythonApi() - self.check_cloned_interval = 2 - self.configuration.append_config_values(volume_opts) - - def check_for_setup_error(self): - # lrk: check config file here. - if not os.path.exists(fspythonapi.fsc_conf_file): - msg = _("Dsware config file not exists!") - LOG.error(_LE("Dsware config file: %s not exists!"), - fspythonapi.fsc_conf_file) - raise exception.VolumeBackendAPIException(data=msg) - - def do_setup(self, context): - # lrk: create fsc_conf_file here. - conf_info = ["manage_ip=%s" % self.configuration.dsware_manager, - "\n", - "vbs_url=%s" % self.configuration.fusionstorageagent] - - fsc_dir = os.path.dirname(fspythonapi.fsc_conf_file) - if not os.path.exists(fsc_dir): - os.makedirs(fsc_dir) - - with open(fspythonapi.fsc_conf_file, 'w') as f: - f.writelines(conf_info) - - # Get pool type. - self.pool_type = self.configuration.pool_type - LOG.debug("Dsware Driver do_setup finish.") - - def _get_dsware_manage_ip(self, volume): - dsw_manager_ip = volume['provider_id'] - if dsw_manager_ip is not None: - return dsw_manager_ip - else: - msg = _("Dsware get manager ip failed, " - "volume provider_id is None!") - raise exception.VolumeBackendAPIException(data=msg) - - def _get_poolid_from_host(self, host): - # Host format: 'hostid@backend#poolid'. - # Other formats: return 'default', and the pool id would be zero. - if host: - if len(host.split('#', 1)) == 2: - return host.split('#')[1] - return self.pool_type - - def _create_volume(self, volume_id, volume_size, isThin, volume_host): - pool_id = 0 - result = 1 - - # Query Dsware version. - retcode = self.dsware_client.query_dsware_version() - # Old version. - if retcode == OLD_VERSION: - pool_id = 0 - # New version. - elif retcode == NEW_VERSION: - pool_info = self._get_poolid_from_host(volume_host) - if pool_info != self.pool_type: - pool_id = int(pool_info) - # Query Dsware version failed! - else: - LOG.error(_LE("Query Dsware version fail!")) - msg = (_("Query Dsware version failed! Retcode is %s.") % - retcode) - raise exception.VolumeBackendAPIException(data=msg) - - try: - result = self.dsware_client.create_volume( - volume_id, pool_id, volume_size, int(isThin)) - except Exception as e: - LOG.exception(_LE("Create volume error, details is: %s."), e) - raise - - if result != 0: - msg = _("Dsware create volume failed! Result is: %s.") % result - raise exception.VolumeBackendAPIException(data=msg) - - def create_volume(self, volume): - # Creates a volume in Dsware. - LOG.debug("Begin to create volume %s in Dsware.", volume['name']) - volume_id = volume['name'] - volume_size = volume['size'] - volume_host = volume['host'] - is_thin = self.configuration.dsware_isthin - # Change GB to MB. - volume_size = volume_size * 1024 - self._create_volume(volume_id, volume_size, is_thin, volume_host) - - dsw_manager_ip = self.dsware_client.get_manage_ip() - return {"provider_id": dsw_manager_ip} - - def _create_volume_from_snap(self, volume_id, volume_size, snapshot_name): - result = self.dsware_client.create_volume_from_snap( - volume_id, volume_size, snapshot_name) - if result != 0: - msg = (_("Dsware: create volume from snap failed. Result: %s.") % - result) - raise exception.VolumeBackendAPIException(data=msg) - - def create_volume_from_snapshot(self, volume, snapshot): - # Creates a volume from snapshot. - volume_id = volume['name'] - volume_size = volume['size'] - snapshot_name = snapshot['name'] - if volume_size < int(snapshot['volume_size']): - msg = _("Dsware: volume size can not be less than snapshot size.") - raise exception.VolumeBackendAPIException(data=msg) - - volume_size = volume_size * 1024 - self._create_volume_from_snap(volume_id, volume_size, snapshot_name) - - dsw_manager_ip = self.dsware_client.get_manage_ip() - return {"provider_id": dsw_manager_ip} - - def create_cloned_volume(self, volume, src_volume): - """Dispatcher to Dsware client to create volume from volume. - - Wait volume create finished. - """ - volume_name = volume['name'] - volume_size = volume['size'] - src_volume_name = src_volume['name'] - - volume_size = volume_size * 1024 - result = self.dsware_client.create_volume_from_volume( - volume_name, volume_size, src_volume_name) - if result: - msg = _('Dsware fails to start cloning volume %s.') % volume_name - raise exception.VolumeBackendAPIException(data=msg) - - LOG.debug('Dsware create volume %(volume_name)s of size ' - '%(volume_size)s from src volume %(src_volume_name)s start.', - {"volume_name": volume_name, - "volume_size": volume_size, - "src_volume_name": src_volume_name}) - - ret = self._wait_for_create_cloned_volume_finish_timer(volume_name) - if not ret: - msg = (_('Clone volume %s failed while waiting for success.') % - volume_name) - raise exception.VolumeBackendAPIException(data=msg) - - LOG.debug('Dsware create volume from volume ends.') - - dsw_manager_ip = self.dsware_client.get_manage_ip() - return {"provider_id": dsw_manager_ip} - - def _check_create_cloned_volume_finish(self, new_volume_name): - LOG.debug('Loopcall: _check_create_cloned_volume_finish(), ' - 'volume-name: %s.', new_volume_name) - current_volume = self.dsware_client.query_volume(new_volume_name) - - if current_volume: - status = current_volume['status'] - LOG.debug('Wait clone volume %(volume_name)s, status: %(status)s.', - {"volume_name": new_volume_name, - "status": status}) - if int(status) == self.DSWARE_VOLUME_CREATING_STATUS or int( - status) == self.DSWARE_VOLUME_DUPLICATE_VOLUME: - self.count += 1 - elif int(status) == self.DSWARE_VOLUME_CREATE_SUCCESS_STATUS: - raise loopingcall.LoopingCallDone(retvalue=True) - else: - msg = _('Clone volume %(new_volume_name)s failed, ' - 'volume status is: %(status)s.') - LOG.error(msg, {'new_volume_name': new_volume_name, - 'status': status}) - raise loopingcall.LoopingCallDone(retvalue=False) - if self.count > self.configuration.clone_volume_timeout: - msg = _('Dsware clone volume time out. ' - 'Volume: %(new_volume_name)s, status: %(status)s') - LOG.error(msg, {'new_volume_name': new_volume_name, - 'status': current_volume['status']}) - raise loopingcall.LoopingCallDone(retvalue=False) - else: - LOG.warning(_LW('Can not find volume %s from Dsware.'), - new_volume_name) - self.count += 1 - if self.count > 10: - msg = _("Dsware clone volume failed: volume " - "can not be found from Dsware.") - LOG.error(msg) - raise loopingcall.LoopingCallDone(retvalue=False) - - def _wait_for_create_cloned_volume_finish_timer(self, new_volume_name): - timer = loopingcall.FixedIntervalLoopingCall( - self._check_create_cloned_volume_finish, new_volume_name) - LOG.debug('Call _check_create_cloned_volume_finish: volume-name %s.', - new_volume_name) - self.count = 0 - ret = timer.start(interval=self.check_cloned_interval).wait() - timer.stop() - return ret - - def _analyse_output(self, out): - if out is not None: - analyse_result = {} - out_temp = out.split('\n') - for line in out_temp: - if re.search('^ret_code=', line): - analyse_result['ret_code'] = line[9:] - elif re.search('^ret_desc=', line): - analyse_result['ret_desc'] = line[9:] - elif re.search('^dev_addr=', line): - analyse_result['dev_addr'] = line[9:] - return analyse_result - else: - return None - - def _attach_volume(self, volume_name, dsw_manager_ip): - cmd = ['vbs_cli', '-c', 'attachwithip', '-v', volume_name, '-i', - dsw_manager_ip.replace('\n', ''), '-p', 0] - out, err = self._execute(*cmd, run_as_root=True) - analyse_result = self._analyse_output(out) - LOG.debug("Attach volume result is %s.", analyse_result) - return analyse_result - - def _detach_volume(self, volume_name, dsw_manager_ip): - cmd = ['vbs_cli', '-c', 'detachwithip', '-v', volume_name, '-i', - dsw_manager_ip.replace('\n', ''), '-p', 0] - out, err = self._execute(*cmd, run_as_root=True) - analyse_result = self._analyse_output(out) - LOG.debug("Detach volume result is %s.", analyse_result) - return analyse_result - - def _query_volume_attach(self, volume_name, dsw_manager_ip): - cmd = ['vbs_cli', '-c', 'querydevwithip', '-v', volume_name, '-i', - dsw_manager_ip.replace('\n', ''), '-p', 0] - out, err = self._execute(*cmd, run_as_root=True) - analyse_result = self._analyse_output(out) - LOG.debug("Query volume attach result is %s.", analyse_result) - return analyse_result - - def copy_image_to_volume(self, context, volume, image_service, image_id): - # Copy image to volume. - # Step1: attach volume to host. - LOG.debug("Begin to copy image to volume.") - dsw_manager_ip = self._get_dsware_manage_ip(volume) - volume_attach_result = self._attach_volume(volume['name'], - dsw_manager_ip) - volume_attach_path = '' - if volume_attach_result is not None and int( - volume_attach_result['ret_code']) == 0: - volume_attach_path = volume_attach_result['dev_addr'] - LOG.debug("Volume attach path is %s.", volume_attach_path) - if volume_attach_path == '': - msg = _("Host attach volume failed!") - raise exception.VolumeBackendAPIException(data=msg) - # Step2: fetch the image from image_service and write it to the - # volume. - try: - image_utils.fetch_to_raw(context, - image_service, - image_id, - volume_attach_path, - self.configuration.volume_dd_blocksize) - finally: - # Step3: detach volume from host. - dsw_manager_ip = self._get_dsware_manage_ip(volume) - volume_detach_result = self._detach_volume(volume['name'], - dsw_manager_ip) - if volume_detach_result is not None and int( - volume_detach_result['ret_code']) != 0: - msg = (_("Dsware detach volume from host failed: %s!") % - volume_detach_result) - raise exception.VolumeBackendAPIException(data=msg) - - def copy_volume_to_image(self, context, volume, image_service, image_meta): - # Copy volume to image. - # If volume was not attached, then attach it. - - dsw_manager_ip = self._get_dsware_manage_ip(volume) - - already_attached = False - _attach_result = self._attach_volume(volume['name'], dsw_manager_ip) - if _attach_result: - retcode = _attach_result['ret_code'] - if int(retcode) == VOLUME_ALREADY_ATTACHED: - already_attached = True - result = self._query_volume_attach(volume['name'], - dsw_manager_ip) - if not result or int(result['ret_code']) != 0: - msg = (_("Query volume attach failed, result=%s.") % - result) - raise exception.VolumeBackendAPIException(data=msg) - - elif int(retcode) == 0: - result = _attach_result - else: - msg = (_("Attach volume to host failed " - "in copy volume to image, retcode: %s.") % - retcode) - raise exception.VolumeBackendAPIException(data=msg) - - volume_attach_path = result['dev_addr'] - - else: - msg = _("Attach_volume failed.") - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - try: - image_utils.upload_volume(context, - image_service, - image_meta, - volume_attach_path) - except Exception as e: - LOG.error(_LE("Upload volume error, details: %s."), e) - raise e - finally: - if not already_attached: - self._detach_volume(volume['name'], dsw_manager_ip) - - def _get_volume(self, volume_name): - result = self.dsware_client.query_volume(volume_name) - LOG.debug("Dsware query volume result is %s.", result['result']) - if result['result'] == VOLUME_NOT_EXIST: - LOG.debug("Dsware volume %s does not exist.", volume_name) - return False - elif result['result'] == 0: - return True - else: - msg = _("Dsware query volume %s failed!") % volume_name - raise exception.VolumeBackendAPIException(data=msg) - - def _delete_volume(self, volume_name): - # Delete volume in Dsware. - result = self.dsware_client.delete_volume(volume_name) - LOG.debug("Dsware delete volume, result is %s.", result) - if result == VOLUME_NOT_EXIST: - LOG.debug("Dsware delete volume, volume does not exist.") - return True - elif result == VOLUME_BEING_DELETED: - LOG.debug("Dsware delete volume, volume is being deleted.") - return True - elif result == 0: - return True - else: - msg = _("Dsware delete volume failed: %s!") % result - raise exception.VolumeBackendAPIException(data=msg) - - def delete_volume(self, volume): - # Delete volume. - # If volume does not exist, then return. - LOG.debug("Begin to delete volume in Dsware: %s.", volume['name']) - if not self._get_volume(volume['name']): - return True - - return self._delete_volume(volume['name']) - - def _get_snapshot(self, snapshot_name): - snapshot_info = self.dsware_client.query_snap(snapshot_name) - LOG.debug("Get snapshot, snapshot_info is : %s.", snapshot_info) - if snapshot_info['result'] == SNAP_NOT_EXIST: - LOG.error(_LE('Snapshot: %s not found!'), snapshot_name) - return False - elif snapshot_info['result'] == 0: - return True - else: - msg = _("Dsware get snapshot failed!") - raise exception.VolumeBackendAPIException(data=msg) - - def _create_snapshot(self, snapshot_id, volume_id): - LOG.debug("Create snapshot %s to Dsware.", snapshot_id) - smart_flag = 0 - res = self.dsware_client.create_snapshot(snapshot_id, - volume_id, - smart_flag) - if res != 0: - msg = _("Dsware Create Snapshot failed! Result: %s.") % res - raise exception.VolumeBackendAPIException(data=msg) - - def _delete_snapshot(self, snapshot_id): - LOG.debug("Delete snapshot %s to Dsware.", snapshot_id) - res = self.dsware_client.delete_snapshot(snapshot_id) - LOG.debug("Ddelete snapshot result is: %s.", res) - if res != 0: - raise exception.SnapshotIsBusy(snapshot_name=snapshot_id) - - def create_snapshot(self, snapshot): - vol_id = 'volume-%s' % snapshot['volume_id'] - snapshot_id = snapshot['name'] - if not self._get_volume(vol_id): - msg = _LE('Create Snapshot, but volume: %s not found!') - LOG.error(msg, vol_id) - raise exception.VolumeNotFound(volume_id=vol_id) - else: - self._create_snapshot(snapshot_id, vol_id) - - def delete_snapshot(self, snapshot): - LOG.debug("Delete snapshot %s.", snapshot['name']) - snapshot_id = snapshot['name'] - if self._get_snapshot(snapshot_id): - self._delete_snapshot(snapshot_id) - - def _calculate_pool_info(self, pool_sets): - filter = False - pools_status = [] - reserved_percentage = self.configuration.reserved_percentage - pool_id_filter = self.configuration.pool_id_filter - LOG.debug("Filtered pool id is %s.", pool_id_filter) - if pool_id_filter == []: - for pool_info in pool_sets: - pool = {} - pool['pool_name'] = pool_info['pool_id'] - pool['total_capacity_gb'] = float( - pool_info['total_capacity']) / 1024 - pool['allocated_capacity_gb'] = float( - pool_info['used_capacity']) / 1024 - pool['free_capacity_gb'] = pool['total_capacity_gb'] - pool[ - 'allocated_capacity_gb'] - pool['QoS_support'] = False - pool['reserved_percentage'] = reserved_percentage - pools_status.append(pool) - else: - for pool_info in pool_sets: - for pool_id in pool_id_filter: - if pool_id == pool_info['pool_id']: - filter = True - break - - if filter: - pool = {} - pool['pool_name'] = pool_info['pool_id'] - pool['total_capacity_gb'] = float( - pool_info['total_capacity']) / 1024 - pool['allocated_capacity_gb'] = float( - pool_info['used_capacity']) / 1024 - pool['free_capacity_gb'] = float( - pool['total_capacity_gb'] - pool[ - 'allocated_capacity_gb']) - pool['QoS_support'] = False - pool['reserved_percentage'] = reserved_percentage - pools_status.append(pool) - - filter = False - - return pools_status - - def _update_single_pool_info_status(self): - """Query pool info when Dsware is single-pool version.""" - status = {} - status['volume_backend_name'] = self.configuration.volume_backend_name - status['vendor_name'] = 'Open Source' - status['driver_version'] = self.VERSION - status['storage_protocol'] = 'dsware' - - status['total_capacity_gb'] = 0 - status['free_capacity_gb'] = 0 - status['reserved_percentage'] = self.configuration.reserved_percentage - status['QoS_support'] = False - pool_id = 0 - pool_info = self.dsware_client.query_pool_info(pool_id) - result = pool_info['result'] - if result == 0: - status['total_capacity_gb'] = float( - pool_info['total_capacity']) / 1024 - status['free_capacity_gb'] = (float( - pool_info['total_capacity']) - float( - pool_info['used_capacity'])) / 1024 - LOG.debug("total_capacity_gb is %s, free_capacity_gb is %s.", - status['total_capacity_gb'], - status['free_capacity_gb']) - self._stats = status - else: - self._stats = None - - def _update_multi_pool_of_same_type_status(self): - """Query info of multiple pools when Dsware is multi-pool version. - - These pools have the same pool type. - """ - status = {} - status['volume_backend_name'] = self.configuration.volume_backend_name - status['vendor_name'] = 'Open Source' - status['driver_version'] = self.VERSION - status['storage_protocol'] = 'dsware' - - (result, pool_sets) = self.dsware_client.query_pool_type( - self.pool_type) - if pool_sets == []: - self._stats = None - else: - pools_status = self._calculate_pool_info(pool_sets) - status['pools'] = pools_status - self._stats = status - - def get_volume_stats(self, refresh=False): - if refresh: - dsware_version = self.dsware_client.query_dsware_version() - # Old version. - if dsware_version == OLD_VERSION: - self._update_single_pool_info_status() - # New version. - elif dsware_version == NEW_VERSION: - self._update_multi_pool_of_same_type_status() - else: - msg = _("Dsware query Dsware version failed!") - LOG.error(msg) - raise exception.VolumeBackendAPIException(data=msg) - - return self._stats - - def extend_volume(self, volume, new_size): - # Extend volume in Dsware. - LOG.debug("Begin to extend volume in Dsware: %s.", volume['name']) - volume_id = volume['name'] - if volume['size'] > new_size: - msg = (_("Dsware extend Volume failed! " - "New size %(new_size)s should be greater than " - "old size %(old_size)s!") - % {'new_size': new_size, - 'old_size': volume['size']}) - raise exception.VolumeBackendAPIException(data=msg) - # Change GB to MB. - volume_size = new_size * 1024 - result = self.dsware_client.extend_volume(volume_id, volume_size) - if result != 0: - msg = _("Dsware extend Volume failed! Result:%s.") % result - raise exception.VolumeBackendAPIException(data=msg) - - def initialize_connection(self, volume, connector): - """Initializes the connection and returns connection info.""" - LOG.debug("Begin initialize connection.") - - properties = {} - properties['volume_name'] = volume['name'] - properties['volume'] = volume - properties['dsw_manager_ip'] = self._get_dsware_manage_ip(volume) - - LOG.debug("End initialize connection with properties:%s.", properties) - - return {'driver_volume_type': 'dsware', - 'data': properties} - - def terminate_connection(self, volume, connector, force=False, **kwargs): - pass - - def create_export(self, context, volume, connector): - pass - - def ensure_export(self, context, volume): - pass - - def remove_export(self, context, volume): - pass +# Copyright (c) 2013 - 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. +""" +Driver for Huawei FusionStorage. +""" + +import os +import re + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_service import loopingcall + +from cinder import exception +from cinder.i18n import _, _LE, _LW +from cinder.image import image_utils +from cinder import interface +from cinder.volume import driver +from cinder.volume.drivers.fusionstorage import fspythonapi + +LOG = logging.getLogger(__name__) + +volume_opts = [ + cfg.BoolOpt('dsware_isthin', + default = False, + help = 'The flag of thin storage allocation.'), + cfg.StrOpt('dsware_manager', + default = '', + help = 'Fusionstorage manager ip addr for cinder-volume.'), + cfg.StrOpt('fusionstorageagent', + default = '', + help = 'Fusionstorage agent ip addr range.'), + cfg.StrOpt('pool_type', + default = 'default', + help = 'Pool type, like sata-2copy.'), + cfg.ListOpt('pool_id_filter', + default = [], + help = 'Pool id permit to use.'), + cfg.IntOpt('clone_volume_timeout', + default = 680, + help = 'Create clone volume timeout.'), +] + +CONF = cfg.CONF +CONF.register_opts(volume_opts) + +OLD_VERSION = 1 +NEW_VERSION = 0 +VOLUME_ALREADY_ATTACHED = 50151401 +VOLUME_NOT_EXIST = '50150005\n' +VOLUME_BEING_DELETED = '50151002\n' +SNAP_NOT_EXIST = '50150006\n' + + +@interface.volumedriver +class DSWAREDriver(driver.VolumeDriver): + """Huawei FusionStorage Driver.""" + VERSION = '1.0' + + # ThirdPartySystems wiki page + CI_WIKI_NAME = "Huawei_volume_CI" + + DSWARE_VOLUME_CREATE_SUCCESS_STATUS = 0 + DSWARE_VOLUME_DUPLICATE_VOLUME = 6 + DSWARE_VOLUME_CREATING_STATUS = 7 + + def __init__(self, *args, **kwargs): + super(DSWAREDriver, self).__init__(*args, **kwargs) + self.dsware_client = fspythonapi.FSPythonApi() + self.check_cloned_interval = 2 + self.configuration.append_config_values(volume_opts) + + def check_for_setup_error(self): + # lrk: check config file here. + if not os.path.exists(fspythonapi.fsc_conf_file): + msg = _("Dsware config file not exists!") + LOG.error(_LE("Dsware config file: %s not exists!"), + fspythonapi.fsc_conf_file) + raise exception.VolumeBackendAPIException(data=msg) + + def do_setup(self, context): + # lrk: create fsc_conf_file here. + conf_info = ["manage_ip=%s" % self.configuration.dsware_manager, + "\n", + "vbs_url=%s" % self.configuration.fusionstorageagent] + + fsc_dir = os.path.dirname(fspythonapi.fsc_conf_file) + if not os.path.exists(fsc_dir): + os.makedirs(fsc_dir) + + with open(fspythonapi.fsc_conf_file, 'w') as f: + f.writelines(conf_info) + + # Get pool type. + self.pool_type = self.configuration.pool_type + LOG.debug("Dsware Driver do_setup finish.") + + def _get_dsware_manage_ip(self, volume): + dsw_manager_ip = volume['provider_id'] + if dsw_manager_ip is not None: + return dsw_manager_ip + else: + msg = _("Dsware get manager ip failed, " + "volume provider_id is None!") + raise exception.VolumeBackendAPIException(data=msg) + + def _get_poolid_from_host(self, host): + # Host format: 'hostid@backend#poolid'. + # Other formats: return 'default', and the pool id would be zero. + if host: + if len(host.split('#', 1)) == 2: + return host.split('#')[1] + return self.pool_type + + def _create_volume(self, volume_id, volume_size, isThin, volume_host): + pool_id = 0 + result = 1 + + # Query Dsware version. + retcode = self.dsware_client.query_dsware_version() + # Old version. + if retcode == OLD_VERSION: + pool_id = 0 + # New version. + elif retcode == NEW_VERSION: + pool_info = self._get_poolid_from_host(volume_host) + if pool_info != self.pool_type: + pool_id = int(pool_info) + # Query Dsware version failed! + else: + LOG.error(_LE("Query Dsware version fail!")) + msg = (_("Query Dsware version failed! Retcode is %s.") % + retcode) + raise exception.VolumeBackendAPIException(data=msg) + + try: + result = self.dsware_client.create_volume( + volume_id, pool_id, volume_size, int(isThin)) + except Exception as e: + LOG.exception(_LE("Create volume error, details is: %s."), e) + raise + + if result != 0: + msg = _("Dsware create volume failed! Result is: %s.") % result + raise exception.VolumeBackendAPIException(data=msg) + + def create_volume(self, volume): + # Creates a volume in Dsware. + LOG.debug("Begin to create volume %s in Dsware.", volume['name']) + volume_id = volume['name'] + volume_size = volume['size'] + volume_host = volume['host'] + is_thin = self.configuration.dsware_isthin + # Change GB to MB. + volume_size = volume_size * 1024 + self._create_volume(volume_id, volume_size, is_thin, volume_host) + + dsw_manager_ip = self.dsware_client.get_manage_ip() + return {"provider_id": dsw_manager_ip} + + def _create_volume_from_snap(self, volume_id, volume_size, snapshot_name): + result = self.dsware_client.create_volume_from_snap( + volume_id, volume_size, snapshot_name) + if result != 0: + msg = (_("Dsware: create volume from snap failed. Result: %s.") % + result) + raise exception.VolumeBackendAPIException(data=msg) + + def create_volume_from_snapshot(self, volume, snapshot): + # Creates a volume from snapshot. + volume_id = volume['name'] + volume_size = volume['size'] + snapshot_name = snapshot['name'] + if volume_size < int(snapshot['volume_size']): + msg = _("Dsware: volume size can not be less than snapshot size.") + raise exception.VolumeBackendAPIException(data=msg) + + volume_size = volume_size * 1024 + self._create_volume_from_snap(volume_id, volume_size, snapshot_name) + + dsw_manager_ip = self.dsware_client.get_manage_ip() + return {"provider_id": dsw_manager_ip} + + def create_cloned_volume(self, volume, src_volume): + """Dispatcher to Dsware client to create volume from volume. + + Wait volume create finished. + """ + volume_name = volume['name'] + volume_size = volume['size'] + src_volume_name = src_volume['name'] + + volume_size = volume_size * 1024 + result = self.dsware_client.create_volume_from_volume( + volume_name, volume_size, src_volume_name) + if result: + msg = _('Dsware fails to start cloning volume %s.') % volume_name + raise exception.VolumeBackendAPIException(data=msg) + + LOG.debug('Dsware create volume %(volume_name)s of size ' + '%(volume_size)s from src volume %(src_volume_name)s start.', + {"volume_name": volume_name, + "volume_size": volume_size, + "src_volume_name": src_volume_name}) + + ret = self._wait_for_create_cloned_volume_finish_timer(volume_name) + if not ret: + msg = (_('Clone volume %s failed while waiting for success.') % + volume_name) + raise exception.VolumeBackendAPIException(data=msg) + + LOG.debug('Dsware create volume from volume ends.') + + dsw_manager_ip = self.dsware_client.get_manage_ip() + return {"provider_id": dsw_manager_ip} + + def _check_create_cloned_volume_finish(self, new_volume_name): + LOG.debug('Loopcall: _check_create_cloned_volume_finish(), ' + 'volume-name: %s.', new_volume_name) + current_volume = self.dsware_client.query_volume(new_volume_name) + + if current_volume: + status = current_volume['status'] + LOG.debug('Wait clone volume %(volume_name)s, status: %(status)s.', + {"volume_name": new_volume_name, + "status": status}) + if int(status) == self.DSWARE_VOLUME_CREATING_STATUS or int( + status) == self.DSWARE_VOLUME_DUPLICATE_VOLUME: + self.count += 1 + elif int(status) == self.DSWARE_VOLUME_CREATE_SUCCESS_STATUS: + raise loopingcall.LoopingCallDone(retvalue=True) + else: + msg = _('Clone volume %(new_volume_name)s failed, ' + 'volume status is: %(status)s.') + LOG.error(msg, {'new_volume_name': new_volume_name, + 'status': status}) + raise loopingcall.LoopingCallDone(retvalue=False) + if self.count > self.configuration.clone_volume_timeout: + msg = _('Dsware clone volume time out. ' + 'Volume: %(new_volume_name)s, status: %(status)s') + LOG.error(msg, {'new_volume_name': new_volume_name, + 'status': current_volume['status']}) + raise loopingcall.LoopingCallDone(retvalue=False) + else: + LOG.warning(_LW('Can not find volume %s from Dsware.'), + new_volume_name) + self.count += 1 + if self.count > 10: + msg = _("Dsware clone volume failed: volume " + "can not be found from Dsware.") + LOG.error(msg) + raise loopingcall.LoopingCallDone(retvalue=False) + + def _wait_for_create_cloned_volume_finish_timer(self, new_volume_name): + timer = loopingcall.FixedIntervalLoopingCall( + self._check_create_cloned_volume_finish, new_volume_name) + LOG.debug('Call _check_create_cloned_volume_finish: volume-name %s.', + new_volume_name) + self.count = 0 + ret = timer.start(interval=self.check_cloned_interval).wait() + timer.stop() + return ret + + def _analyse_output(self, out): + if out is not None: + analyse_result = {} + out_temp = out.split('\n') + for line in out_temp: + if re.search('^ret_code=', line): + analyse_result['ret_code'] = line[9:] + elif re.search('^ret_desc=', line): + analyse_result['ret_desc'] = line[9:] + elif re.search('^dev_addr=', line): + analyse_result['dev_addr'] = line[9:] + return analyse_result + else: + return None + + def _attach_volume(self, volume_name, dsw_manager_ip): + cmd = ['vbs_cli', '-c', 'attachwithip', '-v', volume_name, '-i', + dsw_manager_ip.replace('\n', ''), '-p', 0] + out, err = self._execute(*cmd, run_as_root=True) + analyse_result = self._analyse_output(out) + LOG.debug("Attach volume result is %s.", analyse_result) + return analyse_result + + def _detach_volume(self, volume_name, dsw_manager_ip): + cmd = ['vbs_cli', '-c', 'detachwithip', '-v', volume_name, '-i', + dsw_manager_ip.replace('\n', ''), '-p', 0] + out, err = self._execute(*cmd, run_as_root=True) + analyse_result = self._analyse_output(out) + LOG.debug("Detach volume result is %s.", analyse_result) + return analyse_result + + def _query_volume_attach(self, volume_name, dsw_manager_ip): + cmd = ['vbs_cli', '-c', 'querydevwithip', '-v', volume_name, '-i', + dsw_manager_ip.replace('\n', ''), '-p', 0] + out, err = self._execute(*cmd, run_as_root=True) + analyse_result = self._analyse_output(out) + LOG.debug("Query volume attach result is %s.", analyse_result) + return analyse_result + + def copy_image_to_volume(self, context, volume, image_service, image_id): + # Copy image to volume. + # Step1: attach volume to host. + LOG.debug("Begin to copy image to volume.") + dsw_manager_ip = self._get_dsware_manage_ip(volume) + volume_attach_result = self._attach_volume(volume['name'], + dsw_manager_ip) + volume_attach_path = '' + if volume_attach_result is not None and int( + volume_attach_result['ret_code']) == 0: + volume_attach_path = volume_attach_result['dev_addr'] + LOG.debug("Volume attach path is %s.", volume_attach_path) + if volume_attach_path == '': + msg = _("Host attach volume failed!") + raise exception.VolumeBackendAPIException(data=msg) + # Step2: fetch the image from image_service and write it to the + # volume. + try: + image_utils.fetch_to_raw(context, + image_service, + image_id, + volume_attach_path, + self.configuration.volume_dd_blocksize) + finally: + # Step3: detach volume from host. + dsw_manager_ip = self._get_dsware_manage_ip(volume) + volume_detach_result = self._detach_volume(volume['name'], + dsw_manager_ip) + if volume_detach_result is not None and int( + volume_detach_result['ret_code']) != 0: + msg = (_("Dsware detach volume from host failed: %s!") % + volume_detach_result) + raise exception.VolumeBackendAPIException(data=msg) + + def copy_volume_to_image(self, context, volume, image_service, image_meta): + # Copy volume to image. + # If volume was not attached, then attach it. + + dsw_manager_ip = self._get_dsware_manage_ip(volume) + + already_attached = False + _attach_result = self._attach_volume(volume['name'], dsw_manager_ip) + if _attach_result: + retcode = _attach_result['ret_code'] + if int(retcode) == VOLUME_ALREADY_ATTACHED: + already_attached = True + result = self._query_volume_attach(volume['name'], + dsw_manager_ip) + if not result or int(result['ret_code']) != 0: + msg = (_("Query volume attach failed, result=%s.") % + result) + raise exception.VolumeBackendAPIException(data=msg) + + elif int(retcode) == 0: + result = _attach_result + else: + msg = (_("Attach volume to host failed " + "in copy volume to image, retcode: %s.") % + retcode) + raise exception.VolumeBackendAPIException(data=msg) + + volume_attach_path = result['dev_addr'] + + else: + msg = _("Attach_volume failed.") + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + try: + image_utils.upload_volume(context, + image_service, + image_meta, + volume_attach_path) + except Exception as e: + LOG.error(_LE("Upload volume error, details: %s."), e) + raise e + finally: + if not already_attached: + self._detach_volume(volume['name'], dsw_manager_ip) + + def _get_volume(self, volume_name): + result = self.dsware_client.query_volume(volume_name) + LOG.debug("Dsware query volume result is %s.", result['result']) + if result['result'] == VOLUME_NOT_EXIST: + LOG.debug("Dsware volume %s does not exist.", volume_name) + return False + elif result['result'] == 0: + return True + else: + msg = _("Dsware query volume %s failed!") % volume_name + raise exception.VolumeBackendAPIException(data=msg) + + def _delete_volume(self, volume_name): + # Delete volume in Dsware. + result = self.dsware_client.delete_volume(volume_name) + LOG.debug("Dsware delete volume, result is %s.", result) + if result == VOLUME_NOT_EXIST: + LOG.debug("Dsware delete volume, volume does not exist.") + return True + elif result == VOLUME_BEING_DELETED: + LOG.debug("Dsware delete volume, volume is being deleted.") + return True + elif result == 0: + return True + else: + msg = _("Dsware delete volume failed: %s!") % result + raise exception.VolumeBackendAPIException(data=msg) + + def delete_volume(self, volume): + # Delete volume. + # If volume does not exist, then return. + LOG.debug("Begin to delete volume in Dsware: %s.", volume['name']) + if not self._get_volume(volume['name']): + return True + + return self._delete_volume(volume['name']) + + def _get_snapshot(self, snapshot_name): + snapshot_info = self.dsware_client.query_snap(snapshot_name) + LOG.debug("Get snapshot, snapshot_info is : %s.", snapshot_info) + if snapshot_info['result'] == SNAP_NOT_EXIST: + LOG.error(_LE('Snapshot: %s not found!'), snapshot_name) + return False + elif snapshot_info['result'] == 0: + return True + else: + msg = _("Dsware get snapshot failed!") + raise exception.VolumeBackendAPIException(data=msg) + + def _create_snapshot(self, snapshot_id, volume_id): + LOG.debug("Create snapshot %s to Dsware.", snapshot_id) + smart_flag = 0 + res = self.dsware_client.create_snapshot(snapshot_id, + volume_id, + smart_flag) + if res != 0: + msg = _("Dsware Create Snapshot failed! Result: %s.") % res + raise exception.VolumeBackendAPIException(data=msg) + + def _delete_snapshot(self, snapshot_id): + LOG.debug("Delete snapshot %s to Dsware.", snapshot_id) + res = self.dsware_client.delete_snapshot(snapshot_id) + LOG.debug("Ddelete snapshot result is: %s.", res) + if res != 0: + raise exception.SnapshotIsBusy(snapshot_name=snapshot_id) + + def create_snapshot(self, snapshot): + vol_id = 'volume-%s' % snapshot['volume_id'] + snapshot_id = snapshot['name'] + if not self._get_volume(vol_id): + msg = _LE('Create Snapshot, but volume: %s not found!') + LOG.error(msg, vol_id) + raise exception.VolumeNotFound(volume_id=vol_id) + else: + self._create_snapshot(snapshot_id, vol_id) + + def delete_snapshot(self, snapshot): + LOG.debug("Delete snapshot %s.", snapshot['name']) + snapshot_id = snapshot['name'] + if self._get_snapshot(snapshot_id): + self._delete_snapshot(snapshot_id) + + def _calculate_pool_info(self, pool_sets): + filter = False + pools_status = [] + reserved_percentage = self.configuration.reserved_percentage + pool_id_filter = self.configuration.pool_id_filter + LOG.debug("Filtered pool id is %s.", pool_id_filter) + if pool_id_filter == []: + for pool_info in pool_sets: + pool = {} + pool['pool_name'] = pool_info['pool_id'] + pool['total_capacity_gb'] = float( + pool_info['total_capacity']) / 1024 + pool['allocated_capacity_gb'] = float( + pool_info['used_capacity']) / 1024 + pool['free_capacity_gb'] = pool['total_capacity_gb'] - pool[ + 'allocated_capacity_gb'] + pool['QoS_support'] = False + pool['reserved_percentage'] = reserved_percentage + pools_status.append(pool) + else: + for pool_info in pool_sets: + for pool_id in pool_id_filter: + if pool_id == pool_info['pool_id']: + filter = True + break + + if filter: + pool = {} + pool['pool_name'] = pool_info['pool_id'] + pool['total_capacity_gb'] = float( + pool_info['total_capacity']) / 1024 + pool['allocated_capacity_gb'] = float( + pool_info['used_capacity']) / 1024 + pool['free_capacity_gb'] = float( + pool['total_capacity_gb'] - pool[ + 'allocated_capacity_gb']) + pool['QoS_support'] = False + pool['reserved_percentage'] = reserved_percentage + pools_status.append(pool) + + filter = False + + return pools_status + + def _update_single_pool_info_status(self): + """Query pool info when Dsware is single-pool version.""" + status = {} + status['volume_backend_name'] = self.configuration.volume_backend_name + status['vendor_name'] = 'Open Source' + status['driver_version'] = self.VERSION + status['storage_protocol'] = 'dsware' + + status['total_capacity_gb'] = 0 + status['free_capacity_gb'] = 0 + status['reserved_percentage'] = self.configuration.reserved_percentage + status['QoS_support'] = False + pool_id = 0 + pool_info = self.dsware_client.query_pool_info(pool_id) + result = pool_info['result'] + if result == 0: + status['total_capacity_gb'] = float( + pool_info['total_capacity']) / 1024 + status['free_capacity_gb'] = (float( + pool_info['total_capacity']) - float( + pool_info['used_capacity'])) / 1024 + LOG.debug("total_capacity_gb is %s, free_capacity_gb is %s.", + status['total_capacity_gb'], + status['free_capacity_gb']) + self._stats = status + else: + self._stats = None + + def _update_multi_pool_of_same_type_status(self): + """Query info of multiple pools when Dsware is multi-pool version. + + These pools have the same pool type. + """ + status = {} + status['volume_backend_name'] = self.configuration.volume_backend_name + status['vendor_name'] = 'Open Source' + status['driver_version'] = self.VERSION + status['storage_protocol'] = 'dsware' + + (result, pool_sets) = self.dsware_client.query_pool_type( + self.pool_type) + if pool_sets == []: + self._stats = None + else: + pools_status = self._calculate_pool_info(pool_sets) + status['pools'] = pools_status + self._stats = status + + def get_volume_stats(self, refresh=False): + if refresh: + dsware_version = self.dsware_client.query_dsware_version() + # Old version. + if dsware_version == OLD_VERSION: + self._update_single_pool_info_status() + # New version. + elif dsware_version == NEW_VERSION: + self._update_multi_pool_of_same_type_status() + else: + msg = _("Dsware query Dsware version failed!") + LOG.error(msg) + raise exception.VolumeBackendAPIException(data=msg) + + return self._stats + + def extend_volume(self, volume, new_size): + # Extend volume in Dsware. + LOG.debug("Begin to extend volume in Dsware: %s.", volume['name']) + volume_id = volume['name'] + if volume['size'] > new_size: + msg = (_("Dsware extend Volume failed! " + "New size %(new_size)s should be greater than " + "old size %(old_size)s!") + % {'new_size': new_size, + 'old_size': volume['size']}) + raise exception.VolumeBackendAPIException(data=msg) + # Change GB to MB. + volume_size = new_size * 1024 + result = self.dsware_client.extend_volume(volume_id, volume_size) + if result != 0: + msg = _("Dsware extend Volume failed! Result:%s.") % result + raise exception.VolumeBackendAPIException(data=msg) + + def initialize_connection(self, volume, connector): + """Initializes the connection and returns connection info.""" + LOG.debug("Begin initialize connection.") + + properties = {} + properties['volume_name'] = volume['name'] + properties['volume'] = volume + properties['dsw_manager_ip'] = self._get_dsware_manage_ip(volume) + + LOG.debug("End initialize connection with properties:%s.", properties) + + return {'driver_volume_type': 'dsware', + 'data': properties} + + def terminate_connection(self, volume, connector, force=False, **kwargs): + pass + + def create_export(self, context, volume, connector): + pass + + def ensure_export(self, context, volume): + pass + + def remove_export(self, context, volume): + pass