Merge "volume: Remove Cinder v1 support"
This commit is contained in:
		| @@ -3,7 +3,7 @@ image | ||||
| ===== | ||||
|  | ||||
| .. NOTE(efried): This page is hidden from the main TOC; it's here so links in | ||||
|    the wild redirect somewhere sane, because previously identity v2 and v3 were | ||||
|    the wild redirect somewhere sane, because previously image v2 and v3 were | ||||
|    combined in a single page. | ||||
|  | ||||
| .. toctree:: | ||||
|   | ||||
| @@ -4,7 +4,7 @@ limits | ||||
|  | ||||
| The Compute and Block Storage APIs have resource usage limits. | ||||
|  | ||||
| Compute v2, Block Storage v1 | ||||
| Block Storage v2, v3; Compute v2 | ||||
|  | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.common | ||||
|   | ||||
| @@ -5,7 +5,7 @@ quota | ||||
| Resource quotas appear in multiple APIs, OpenStackClient presents them as a | ||||
| single object with multiple properties. | ||||
|  | ||||
| Block Storage v1, v2, Compute v2, Network v2 | ||||
| Block Storage v1, v3; Compute v2; Network v2 | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.common | ||||
|    :command: quota * | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| volume backup | ||||
| ============= | ||||
|  | ||||
| Block Storage v1, v2, v3 | ||||
| Block Storage v2, v3 | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.volume.v3 | ||||
|    :command: volume backup * | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| volume qos | ||||
| ========== | ||||
|  | ||||
| Block Storage v1, v2, v3 | ||||
| Block Storage v2, v3 | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.volume.v3 | ||||
|    :command: volume qos * | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| volume service | ||||
| ============== | ||||
|  | ||||
| Block Storage v1, v2, v3 | ||||
| Block Storage v2, v3 | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.volume.v3 | ||||
|    :command: volume service * | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| volume snapshot | ||||
| =============== | ||||
|  | ||||
| Block Storage v1, v2, v3 | ||||
| Block Storage v2, v3 | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.volume.v3 | ||||
|    :command: volume snapshot * | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| volume transfer request | ||||
| ======================= | ||||
|  | ||||
| Block Storage v1, v2, v3 | ||||
| Block Storage v2, v3 | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.volume.v3 | ||||
|    :command: volume transfer request * | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| volume type | ||||
| =========== | ||||
|  | ||||
| Block Storage v1, v2, v3 | ||||
| Block Storage v2, v3 | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.volume.v3 | ||||
|    :command: volume type * | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| volume | ||||
| ====== | ||||
|  | ||||
| Block Storage v1, v2 | ||||
| Block Storage v2, v3 | ||||
|  | ||||
| .. autoprogram-cliff:: openstack.volume.v3 | ||||
|    :command: volume create | ||||
|   | ||||
| @@ -1,35 +0,0 @@ | ||||
| #    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 fixtures | ||||
|  | ||||
| from openstackclient.tests.functional.volume import base as volume_base | ||||
|  | ||||
|  | ||||
| class BaseVolumeTests(volume_base.BaseVolumeTests): | ||||
|     """Base class for Volume functional tests""" | ||||
|  | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         super().setUpClass() | ||||
|         cls.haz_volume_v1 = cls.is_service_enabled('block-storage', '1.0') | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         if not self.haz_volume_v1: | ||||
|             self.skipTest("No Volume v1 service present") | ||||
|  | ||||
|         ver_fixture = fixtures.EnvironmentVariable( | ||||
|             'OS_VOLUME_API_VERSION', '1' | ||||
|         ) | ||||
|         self.useFixture(ver_fixture) | ||||
| @@ -1,100 +0,0 @@ | ||||
| #    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 uuid | ||||
|  | ||||
| from openstackclient.tests.functional.volume.v1 import common | ||||
|  | ||||
|  | ||||
| class QosTests(common.BaseVolumeTests): | ||||
|     """Functional tests for volume qos.""" | ||||
|  | ||||
|     def test_volume_qos_create_list(self): | ||||
|         """Test create, list, delete multiple""" | ||||
|         name1 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume qos create ' + name1, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(name1, cmd_output['name']) | ||||
|  | ||||
|         name2 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume qos create ' + name2, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(name2, cmd_output['name']) | ||||
|  | ||||
|         # Test list | ||||
|         cmd_output = self.openstack( | ||||
|             'volume qos list', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         names = [x["Name"] for x in cmd_output] | ||||
|         self.assertIn(name1, names) | ||||
|         self.assertIn(name2, names) | ||||
|  | ||||
|         # Test delete multiple | ||||
|         del_output = self.openstack('volume qos delete ' + name1 + ' ' + name2) | ||||
|         self.assertOutput('', del_output) | ||||
|  | ||||
|     def test_volume_qos_set_show_unset(self): | ||||
|         """Tests create volume qos, set, unset, show, delete""" | ||||
|  | ||||
|         name = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume qos create ' | ||||
|             + '--consumer front-end ' | ||||
|             + '--property Alpha=a ' | ||||
|             + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup(self.openstack, 'volume qos delete ' + name) | ||||
|         self.assertEqual(name, cmd_output['name']) | ||||
|  | ||||
|         self.assertEqual("front-end", cmd_output['consumer']) | ||||
|  | ||||
|         # Test volume qos set | ||||
|         raw_output = self.openstack( | ||||
|             'volume qos set ' | ||||
|             + '--no-property ' | ||||
|             + '--property Beta=b ' | ||||
|             + '--property Charlie=c ' | ||||
|             + name, | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|         # Test volume qos show | ||||
|         cmd_output = self.openstack( | ||||
|             'volume qos show ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(name, cmd_output['name']) | ||||
|         self.assertEqual( | ||||
|             {'Beta': 'b', 'Charlie': 'c'}, | ||||
|             cmd_output['properties'], | ||||
|         ) | ||||
|  | ||||
|         # Test volume qos unset | ||||
|         raw_output = self.openstack( | ||||
|             'volume qos unset ' + '--property Charlie ' + name, | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume qos show ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(name, cmd_output['name']) | ||||
|         self.assertEqual({'Beta': 'b'}, cmd_output['properties']) | ||||
|  | ||||
|     # TODO(qiangjiahui): Add tests for associate and disassociate volume type | ||||
| @@ -1,76 +0,0 @@ | ||||
| #    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 openstackclient.tests.functional.volume.v1 import common | ||||
|  | ||||
|  | ||||
| class VolumeServiceTests(common.BaseVolumeTests): | ||||
|     """Functional tests for volume service.""" | ||||
|  | ||||
|     def test_volume_service_list(self): | ||||
|         cmd_output = self.openstack('volume service list', parse_output=True) | ||||
|  | ||||
|         # Get the nonredundant services and hosts | ||||
|         services = list({x['Binary'] for x in cmd_output}) | ||||
|  | ||||
|         # Test volume service list --service | ||||
|         cmd_output = self.openstack( | ||||
|             'volume service list ' + '--service ' + services[0], | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         for x in cmd_output: | ||||
|             self.assertEqual(services[0], x['Binary']) | ||||
|  | ||||
|         # TODO(zhiyong.dai): test volume service list --host after solving | ||||
|         # https://bugs.launchpad.net/python-openstackclient/+bug/1664451 | ||||
|  | ||||
|     def test_volume_service_set(self): | ||||
|         # Get a service and host | ||||
|         cmd_output = self.openstack( | ||||
|             'volume service list', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         service_1 = cmd_output[0]['Binary'] | ||||
|         host_1 = cmd_output[0]['Host'] | ||||
|  | ||||
|         # Test volume service set --enable | ||||
|         raw_output = self.openstack( | ||||
|             'volume service set --enable ' + host_1 + ' ' + service_1 | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume service list --long', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual('enabled', cmd_output[0]['Status']) | ||||
|         self.assertIsNone(cmd_output[0]['Disabled Reason']) | ||||
|  | ||||
|         # Test volume service set --disable and --disable-reason | ||||
|         disable_reason = 'disable_reason' | ||||
|         raw_output = self.openstack( | ||||
|             'volume service set --disable ' | ||||
|             + '--disable-reason ' | ||||
|             + disable_reason | ||||
|             + ' ' | ||||
|             + host_1 | ||||
|             + ' ' | ||||
|             + service_1 | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume service list --long', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual('disabled', cmd_output[0]['Status']) | ||||
|         self.assertEqual(disable_reason, cmd_output[0]['Disabled Reason']) | ||||
| @@ -1,232 +0,0 @@ | ||||
| #    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 uuid | ||||
|  | ||||
| from openstackclient.tests.functional.volume.v1 import common | ||||
|  | ||||
|  | ||||
| class VolumeSnapshotTests(common.BaseVolumeTests): | ||||
|     """Functional tests for volume snapshot.""" | ||||
|  | ||||
|     VOLLY = uuid.uuid4().hex | ||||
|  | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         super().setUpClass() | ||||
|         # create a volume for all tests to create snapshot | ||||
|         cmd_output = cls.openstack( | ||||
|             'volume create ' + '--size 1 ' + cls.VOLLY, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         cls.wait_for_status('volume', cls.VOLLY, 'available') | ||||
|         cls.VOLUME_ID = cmd_output['id'] | ||||
|  | ||||
|     @classmethod | ||||
|     def tearDownClass(cls): | ||||
|         try: | ||||
|             cls.wait_for_status('volume', cls.VOLLY, 'available') | ||||
|             raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) | ||||
|             cls.assertOutput('', raw_output) | ||||
|         finally: | ||||
|             super().tearDownClass() | ||||
|  | ||||
|     def test_volume_snapshot_delete(self): | ||||
|         """Test create, delete multiple""" | ||||
|         name1 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot create ' + name1 + ' --volume ' + self.VOLLY, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             name1, | ||||
|             cmd_output["display_name"], | ||||
|         ) | ||||
|  | ||||
|         name2 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot create ' + name2 + ' --volume ' + self.VOLLY, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             name2, | ||||
|             cmd_output["display_name"], | ||||
|         ) | ||||
|  | ||||
|         self.wait_for_status('volume snapshot', name1, 'available') | ||||
|         self.wait_for_status('volume snapshot', name2, 'available') | ||||
|  | ||||
|         del_output = self.openstack( | ||||
|             'volume snapshot delete ' + name1 + ' ' + name2 | ||||
|         ) | ||||
|         self.assertOutput('', del_output) | ||||
|         self.wait_for_delete('volume snapshot', name1) | ||||
|         self.wait_for_delete('volume snapshot', name2) | ||||
|  | ||||
|     def test_volume_snapshot_list(self): | ||||
|         """Test create, list filter""" | ||||
|         name1 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot create ' + name1 + ' --volume ' + self.VOLLY, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup(self.wait_for_delete, 'volume snapshot', name1) | ||||
|         self.addCleanup(self.openstack, 'volume snapshot delete ' + name1) | ||||
|         self.assertEqual( | ||||
|             name1, | ||||
|             cmd_output["display_name"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             self.VOLUME_ID, | ||||
|             cmd_output["volume_id"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             1, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|         self.wait_for_status('volume snapshot', name1, 'available') | ||||
|  | ||||
|         name2 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot create ' + name2 + ' --volume ' + self.VOLLY, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup(self.wait_for_delete, 'volume snapshot', name2) | ||||
|         self.addCleanup(self.openstack, 'volume snapshot delete ' + name2) | ||||
|         self.assertEqual( | ||||
|             name2, | ||||
|             cmd_output["display_name"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             self.VOLUME_ID, | ||||
|             cmd_output["volume_id"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             1, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|         self.wait_for_status('volume snapshot', name2, 'available') | ||||
|  | ||||
|         # Test list --long, --status | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot list ' + '--long ' + '--status error', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         names = [x["Name"] for x in cmd_output] | ||||
|         self.assertNotIn(name1, names) | ||||
|         self.assertNotIn(name2, names) | ||||
|  | ||||
|         # Test list --volume | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot list ' + '--volume ' + self.VOLLY, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         names = [x["Name"] for x in cmd_output] | ||||
|         self.assertIn(name1, names) | ||||
|         self.assertIn(name2, names) | ||||
|  | ||||
|         # Test list --name | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot list ' + '--name ' + name1, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         names = [x["Name"] for x in cmd_output] | ||||
|         self.assertIn(name1, names) | ||||
|         self.assertNotIn(name2, names) | ||||
|  | ||||
|     def test_snapshot_set(self): | ||||
|         """Test create, set, unset, show, delete volume snapshot""" | ||||
|         name = uuid.uuid4().hex | ||||
|         new_name = name + "_" | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot create ' | ||||
|             + '--volume ' | ||||
|             + self.VOLLY | ||||
|             + ' --description aaaa ' | ||||
|             + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup(self.wait_for_delete, 'volume snapshot', new_name) | ||||
|         self.addCleanup(self.openstack, 'volume snapshot delete ' + new_name) | ||||
|         self.assertEqual( | ||||
|             name, | ||||
|             cmd_output["display_name"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             1, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             'aaaa', | ||||
|             cmd_output["display_description"], | ||||
|         ) | ||||
|         self.wait_for_status('volume snapshot', name, 'available') | ||||
|  | ||||
|         # Test volume snapshot set | ||||
|         raw_output = self.openstack( | ||||
|             'volume snapshot set ' | ||||
|             + '--name ' | ||||
|             + new_name | ||||
|             + ' --description bbbb ' | ||||
|             + '--property Alpha=a ' | ||||
|             + '--property Beta=b ' | ||||
|             + name, | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|         # Show snapshot set result | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot show ' + new_name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             new_name, | ||||
|             cmd_output["display_name"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             1, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             'bbbb', | ||||
|             cmd_output["display_description"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             {'Alpha': 'a', 'Beta': 'b'}, | ||||
|             cmd_output["properties"], | ||||
|         ) | ||||
|  | ||||
|         # Test volume unset | ||||
|         raw_output = self.openstack( | ||||
|             'volume snapshot unset ' + '--property Alpha ' + new_name, | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot show ' + new_name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             {'Beta': 'b'}, | ||||
|             cmd_output["properties"], | ||||
|         ) | ||||
|  | ||||
|         # Test volume snapshot set --no-property | ||||
|         raw_output = self.openstack( | ||||
|             'volume snapshot set ' + '--no-property ' + new_name, | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|         cmd_output = self.openstack( | ||||
|             'volume snapshot show ' + new_name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual({}, cmd_output["properties"]) | ||||
| @@ -1,111 +0,0 @@ | ||||
| #    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 uuid | ||||
|  | ||||
| from openstackclient.tests.functional.volume.v1 import common | ||||
|  | ||||
|  | ||||
| class TransferRequestTests(common.BaseVolumeTests): | ||||
|     """Functional tests for transfer request.""" | ||||
|  | ||||
|     NAME = uuid.uuid4().hex | ||||
|     VOLUME_NAME = uuid.uuid4().hex | ||||
|  | ||||
|     @classmethod | ||||
|     def setUpClass(cls): | ||||
|         super().setUpClass() | ||||
|         cmd_output = cls.openstack( | ||||
|             'volume create --size 1 ' + cls.VOLUME_NAME, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         cls.assertOutput(cls.VOLUME_NAME, cmd_output['name']) | ||||
|  | ||||
|         cls.wait_for_status("volume", cls.VOLUME_NAME, "available") | ||||
|  | ||||
|     @classmethod | ||||
|     def tearDownClass(cls): | ||||
|         try: | ||||
|             raw_output_volume = cls.openstack( | ||||
|                 'volume delete ' + cls.VOLUME_NAME | ||||
|             ) | ||||
|             cls.assertOutput('', raw_output_volume) | ||||
|         finally: | ||||
|             super().tearDownClass() | ||||
|  | ||||
|     def test_volume_transfer_request_accept(self): | ||||
|         volume_name = uuid.uuid4().hex | ||||
|         name = uuid.uuid4().hex | ||||
|  | ||||
|         # create a volume | ||||
|         cmd_output = self.openstack( | ||||
|             'volume create --size 1 ' + volume_name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(volume_name, cmd_output['name']) | ||||
|  | ||||
|         # create volume transfer request for the volume | ||||
|         # and get the auth_key of the new transfer request | ||||
|         cmd_output = self.openstack( | ||||
|             'volume transfer request create ' | ||||
|             + volume_name | ||||
|             + ' --name ' | ||||
|             + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         auth_key = cmd_output['auth_key'] | ||||
|         self.assertTrue(auth_key) | ||||
|  | ||||
|         # accept the volume transfer request | ||||
|         output = self.openstack( | ||||
|             'volume transfer request accept ' | ||||
|             + name | ||||
|             + ' ' | ||||
|             + '--auth-key ' | ||||
|             + auth_key, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(name, output.get('name')) | ||||
|  | ||||
|         # the volume transfer will be removed by default after accepted | ||||
|         # so just need to delete the volume here | ||||
|         raw_output = self.openstack('volume delete ' + volume_name) | ||||
|         self.assertEqual('', raw_output) | ||||
|  | ||||
|     def test_volume_transfer_request_list_show(self): | ||||
|         name = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume transfer request create ' | ||||
|             + ' --name ' | ||||
|             + name | ||||
|             + ' ' | ||||
|             + self.VOLUME_NAME, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup( | ||||
|             self.openstack, 'volume transfer request delete ' + name | ||||
|         ) | ||||
|         self.assertOutput(name, cmd_output['name']) | ||||
|         auth_key = cmd_output['auth_key'] | ||||
|         self.assertTrue(auth_key) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume transfer request list', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertIn(name, [req['Name'] for req in cmd_output]) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume transfer request show ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(name, cmd_output['name']) | ||||
| @@ -1,228 +0,0 @@ | ||||
| #    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 uuid | ||||
|  | ||||
| from openstackclient.tests.functional.volume.v1 import common | ||||
|  | ||||
|  | ||||
| class VolumeTests(common.BaseVolumeTests): | ||||
|     """Functional tests for volume.""" | ||||
|  | ||||
|     def test_volume_create_and_delete(self): | ||||
|         """Test create, delete multiple""" | ||||
|         name1 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume create ' + '--size 1 ' + name1, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             1, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|  | ||||
|         name2 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume create ' + '--size 2 ' + name2, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             2, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|  | ||||
|         self.wait_for_status("volume", name1, "available") | ||||
|         self.wait_for_status("volume", name2, "available") | ||||
|         del_output = self.openstack('volume delete ' + name1 + ' ' + name2) | ||||
|         self.assertOutput('', del_output) | ||||
|  | ||||
|     def test_volume_list(self): | ||||
|         """Test create, list filter""" | ||||
|         name1 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume create ' + '--size 1 ' + name1, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup(self.openstack, 'volume delete ' + name1) | ||||
|         self.assertEqual( | ||||
|             1, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|         self.wait_for_status("volume", name1, "available") | ||||
|  | ||||
|         name2 = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume create ' + '--size 2 ' + name2, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup(self.openstack, 'volume delete ' + name2) | ||||
|         self.assertEqual( | ||||
|             2, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|         self.wait_for_status("volume", name2, "available") | ||||
|  | ||||
|         # Test list | ||||
|         cmd_output = self.openstack( | ||||
|             'volume list ', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         names = [x["Name"] for x in cmd_output] | ||||
|         self.assertIn(name1, names) | ||||
|         self.assertIn(name2, names) | ||||
|  | ||||
|         # Test list --long | ||||
|         cmd_output = self.openstack( | ||||
|             'volume list --long', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         bootable = [x["Bootable"] for x in cmd_output] | ||||
|         self.assertIn('false', bootable) | ||||
|  | ||||
|         # Test list --name | ||||
|         cmd_output = self.openstack( | ||||
|             'volume list ' + '--name ' + name1, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         names = [x["Name"] for x in cmd_output] | ||||
|         self.assertIn(name1, names) | ||||
|         self.assertNotIn(name2, names) | ||||
|  | ||||
|     def test_volume_set_and_unset(self): | ||||
|         """Tests create volume, set, unset, show, delete""" | ||||
|         name = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume create ' | ||||
|             + '--size 1 ' | ||||
|             + '--description aaaa ' | ||||
|             + '--property Alpha=a ' | ||||
|             + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             name, | ||||
|             cmd_output["name"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             1, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             'aaaa', | ||||
|             cmd_output["display_description"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             {'Alpha': 'a'}, | ||||
|             cmd_output["properties"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             'false', | ||||
|             cmd_output["bootable"], | ||||
|         ) | ||||
|         self.wait_for_status("volume", name, "available") | ||||
|  | ||||
|         # Test volume set | ||||
|         new_name = uuid.uuid4().hex | ||||
|         self.addCleanup(self.openstack, 'volume delete ' + new_name) | ||||
|         raw_output = self.openstack( | ||||
|             'volume set ' | ||||
|             + '--name ' | ||||
|             + new_name | ||||
|             + ' --size 2 ' | ||||
|             + '--description bbbb ' | ||||
|             + '--no-property ' | ||||
|             + '--property Beta=b ' | ||||
|             + '--property Gamma=c ' | ||||
|             + '--bootable ' | ||||
|             + name, | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume show ' + new_name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             new_name, | ||||
|             cmd_output["name"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             2, | ||||
|             cmd_output["size"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             'bbbb', | ||||
|             cmd_output["display_description"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             {'Beta': 'b', 'Gamma': 'c'}, | ||||
|             cmd_output["properties"], | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             'true', | ||||
|             cmd_output["bootable"], | ||||
|         ) | ||||
|  | ||||
|         # Test volume unset | ||||
|         raw_output = self.openstack( | ||||
|             'volume unset ' + '--property Beta ' + new_name, | ||||
|         ) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume show ' + new_name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             {'Gamma': 'c'}, | ||||
|             cmd_output["properties"], | ||||
|         ) | ||||
|  | ||||
|     def test_volume_create_and_list_and_show_backward_compatibility(self): | ||||
|         """Test backward compatibility of create, list, show""" | ||||
|         name1 = uuid.uuid4().hex | ||||
|         output = self.openstack( | ||||
|             'volume create ' + '-c display_name -c id ' + '--size 1 ' + name1, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertIn('display_name', output) | ||||
|         self.assertEqual(name1, output['display_name']) | ||||
|         self.assertIn('id', output) | ||||
|         volume_id = output['id'] | ||||
|         self.assertIsNotNone(volume_id) | ||||
|         self.assertNotIn('name', output) | ||||
|         self.addCleanup(self.openstack, 'volume delete ' + volume_id) | ||||
|  | ||||
|         self.wait_for_status("volume", name1, "available") | ||||
|  | ||||
|         output = self.openstack( | ||||
|             'volume list ' + '-c "Display Name"', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         for each_volume in output: | ||||
|             self.assertIn('Display Name', each_volume) | ||||
|  | ||||
|         output = self.openstack( | ||||
|             'volume list ' + '-c "Name"', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         for each_volume in output: | ||||
|             self.assertIn('Name', each_volume) | ||||
|  | ||||
|         output = self.openstack( | ||||
|             'volume show ' + '-c display_name -c id ' + name1, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertIn('display_name', output) | ||||
|         self.assertEqual(name1, output['display_name']) | ||||
|         self.assertIn('id', output) | ||||
|         self.assertNotIn('name', output) | ||||
| @@ -1,213 +0,0 @@ | ||||
| #    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 time | ||||
| import uuid | ||||
|  | ||||
| from openstackclient.tests.functional.volume.v1 import common | ||||
|  | ||||
|  | ||||
| class VolumeTypeTests(common.BaseVolumeTests): | ||||
|     """Functional tests for volume type.""" | ||||
|  | ||||
|     def test_volume_type_create_list(self): | ||||
|         name = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type create --private ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup( | ||||
|             self.openstack, | ||||
|             'volume type delete ' + name, | ||||
|         ) | ||||
|         self.assertEqual(name, cmd_output['name']) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             f'volume type show {name}', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(self.NAME, cmd_output['name']) | ||||
|  | ||||
|         cmd_output = self.openstack('volume type list', parse_output=True) | ||||
|         self.assertIn(self.NAME, [t['Name'] for t in cmd_output]) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type list --default', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual(1, len(cmd_output)) | ||||
|         self.assertEqual('lvmdriver-1', cmd_output[0]['Name']) | ||||
|  | ||||
|     def test_volume_type_set_unset_properties(self): | ||||
|         name = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type create --private ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup(self.openstack, 'volume type delete ' + name) | ||||
|         self.assertEqual(name, cmd_output['name']) | ||||
|  | ||||
|         raw_output = self.openstack( | ||||
|             f'volume type set --property a=b --property c=d {name}' | ||||
|         ) | ||||
|         self.assertEqual("", raw_output) | ||||
|         cmd_output = self.openstack( | ||||
|             f'volume type show {name}', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) | ||||
|  | ||||
|         raw_output = self.openstack(f'volume type unset --property a {name}') | ||||
|         self.assertEqual("", raw_output) | ||||
|         cmd_output = self.openstack( | ||||
|             f'volume type show {name}', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual({'c': 'd'}, cmd_output['properties']) | ||||
|  | ||||
|     def test_volume_type_set_unset_multiple_properties(self): | ||||
|         name = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type create --private ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup(self.openstack, 'volume type delete ' + name) | ||||
|         self.assertEqual(name, cmd_output['name']) | ||||
|  | ||||
|         raw_output = self.openstack( | ||||
|             f'volume type set --property a=b --property c=d {name}' | ||||
|         ) | ||||
|         self.assertEqual("", raw_output) | ||||
|         cmd_output = self.openstack( | ||||
|             f'volume type show {name}', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual({'a': 'b', 'c': 'd'}, cmd_output['properties']) | ||||
|  | ||||
|         raw_output = self.openstack( | ||||
|             f'volume type unset --property a --property c {name}' | ||||
|         ) | ||||
|         self.assertEqual("", raw_output) | ||||
|         cmd_output = self.openstack( | ||||
|             f'volume type show {name}', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual({}, cmd_output['properties']) | ||||
|  | ||||
|     def test_multi_delete(self): | ||||
|         vol_type1 = uuid.uuid4().hex | ||||
|         vol_type2 = uuid.uuid4().hex | ||||
|         self.openstack(f'volume type create {vol_type1}') | ||||
|         time.sleep(5) | ||||
|         self.openstack(f'volume type create {vol_type2}') | ||||
|         time.sleep(5) | ||||
|         cmd = f'volume type delete {vol_type1} {vol_type2}' | ||||
|         raw_output = self.openstack(cmd) | ||||
|         self.assertOutput('', raw_output) | ||||
|  | ||||
|     # NOTE: Add some basic functional tests with the old format to | ||||
|     #       make sure the command works properly, need to change | ||||
|     #       these to new test format when beef up all tests for | ||||
|     #       volume type commands. | ||||
|     def test_encryption_type(self): | ||||
|         encryption_type = uuid.uuid4().hex | ||||
|         # test create new encryption type | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type create ' | ||||
|             '--encryption-provider LuksEncryptor ' | ||||
|             '--encryption-cipher aes-xts-plain64 ' | ||||
|             '--encryption-key-size 128 ' | ||||
|             '--encryption-control-location front-end ' + encryption_type | ||||
|         ) | ||||
|         expected = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': 'aes-xts-plain64', | ||||
|             'key_size': 128, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         for attr, value in expected.items(): | ||||
|             self.assertEqual(value, cmd_output['encryption'][attr]) | ||||
|         # test show encryption type | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type show --encryption-type ' + encryption_type, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         expected = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': 'aes-xts-plain64', | ||||
|             'key_size': 128, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         for attr, value in expected.items(): | ||||
|             self.assertEqual(value, cmd_output['encryption'][attr]) | ||||
|         # test list encryption type | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type list --encryption-type', | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         encryption_output = [ | ||||
|             t['Encryption'] for t in cmd_output if t['Name'] == encryption_type | ||||
|         ][0] | ||||
|         expected = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': 'aes-xts-plain64', | ||||
|             'key_size': 128, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         for attr, value in expected.items(): | ||||
|             self.assertEqual(value, encryption_output[attr]) | ||||
|         # test set new encryption type | ||||
|         raw_output = self.openstack( | ||||
|             'volume type set ' | ||||
|             '--encryption-provider LuksEncryptor ' | ||||
|             '--encryption-cipher aes-xts-plain64 ' | ||||
|             '--encryption-key-size 128 ' | ||||
|             '--encryption-control-location front-end ' + self.NAME | ||||
|         ) | ||||
|         self.assertEqual('', raw_output) | ||||
|  | ||||
|         name = uuid.uuid4().hex | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type create --private ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.addCleanup( | ||||
|             self.openstack, | ||||
|             'volume type delete ' + name, | ||||
|         ) | ||||
|         self.assertEqual(name, cmd_output['name']) | ||||
|  | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type show --encryption-type ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         expected = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': 'aes-xts-plain64', | ||||
|             'key_size': 128, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         for attr, value in expected.items(): | ||||
|             self.assertEqual(value, cmd_output['encryption'][attr]) | ||||
|         # test unset encryption type | ||||
|         raw_output = self.openstack( | ||||
|             'volume type unset --encryption-type ' + name | ||||
|         ) | ||||
|         self.assertEqual('', raw_output) | ||||
|         cmd_output = self.openstack( | ||||
|             'volume type show --encryption-type ' + name, | ||||
|             parse_output=True, | ||||
|         ) | ||||
|         self.assertEqual({}, cmd_output['encryption']) | ||||
|         # test delete encryption type | ||||
|         raw_output = self.openstack('volume type delete ' + encryption_type) | ||||
|         self.assertEqual('', raw_output) | ||||
| @@ -20,7 +20,7 @@ from openstack.image.v1 import image | ||||
|  | ||||
| from openstackclient.tests.unit import fakes | ||||
| from openstackclient.tests.unit import utils | ||||
| from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes | ||||
| from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes | ||||
|  | ||||
|  | ||||
| class FakeClientMixin: | ||||
| @@ -35,7 +35,7 @@ class TestImagev1(FakeClientMixin, utils.TestCommand): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.app.client_manager.volume = volume_fakes.FakeVolumev1Client( | ||||
|         self.app.client_manager.volume = volume_fakes.FakeVolumeClient( | ||||
|             endpoint=fakes.AUTH_URL, | ||||
|             token=fakes.AUTH_TOKEN, | ||||
|         ) | ||||
|   | ||||
| @@ -1,615 +0,0 @@ | ||||
| #   Copyright 2013 Nebula Inc. | ||||
| # | ||||
| #   Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||
| #   not use this file except in compliance with the License. You may obtain | ||||
| #   a copy of the License at | ||||
| # | ||||
| #        http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| #   Unless required by applicable law or agreed to in writing, software | ||||
| #   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| #   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| #   License for the specific language governing permissions and limitations | ||||
| #   under the License. | ||||
| # | ||||
|  | ||||
| import copy | ||||
| import random | ||||
| from unittest import mock | ||||
| import uuid | ||||
|  | ||||
| from openstack.image.v1 import _proxy as image_v1_proxy | ||||
|  | ||||
| from openstackclient.tests.unit import fakes | ||||
| from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes | ||||
| from openstackclient.tests.unit import utils | ||||
|  | ||||
|  | ||||
| class FakeVolumev1Client: | ||||
|     def __init__(self, **kwargs): | ||||
|         self.volumes = mock.Mock() | ||||
|         self.volumes.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.services = mock.Mock() | ||||
|         self.services.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.extensions = mock.Mock() | ||||
|         self.extensions.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.qos_specs = mock.Mock() | ||||
|         self.qos_specs.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.volume_types = mock.Mock() | ||||
|         self.volume_types.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.volume_encryption_types = mock.Mock() | ||||
|         self.volume_encryption_types.resource_class = fakes.FakeResource( | ||||
|             None, {} | ||||
|         ) | ||||
|         self.transfers = mock.Mock() | ||||
|         self.transfers.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.volume_snapshots = mock.Mock() | ||||
|         self.volume_snapshots.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.backups = mock.Mock() | ||||
|         self.backups.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.restores = mock.Mock() | ||||
|         self.restores.resource_class = fakes.FakeResource(None, {}) | ||||
|         self.auth_token = kwargs['token'] | ||||
|         self.management_url = kwargs['endpoint'] | ||||
|  | ||||
|  | ||||
| class FakeClientMixin: | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.app.client_manager.volume = FakeVolumev1Client( | ||||
|             endpoint=fakes.AUTH_URL, | ||||
|             token=fakes.AUTH_TOKEN, | ||||
|         ) | ||||
|         self.volume_client = self.app.client_manager.volume | ||||
|  | ||||
|  | ||||
| class TestVolumev1( | ||||
|     identity_fakes.FakeClientMixin, | ||||
|     FakeClientMixin, | ||||
|     utils.TestCommand, | ||||
| ): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         # avoid circular imports by defining this manually rather than using | ||||
|         # openstackclient.tests.unit.image.v1.fakes.FakeClientMixin | ||||
|         self.app.client_manager.image = mock.Mock(spec=image_v1_proxy.Proxy) | ||||
|         self.image_client = self.app.client_manager.image | ||||
|  | ||||
|  | ||||
| def create_one_transfer(attrs=None): | ||||
|     """Create a fake transfer. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes of Transfer Request | ||||
|     :return: | ||||
|         A FakeResource object with volume_id, name, id. | ||||
|     """ | ||||
|     # Set default attribute | ||||
|     transfer_info = { | ||||
|         'volume_id': 'volume-id-' + uuid.uuid4().hex, | ||||
|         'name': 'fake_transfer_name', | ||||
|         'id': 'id-' + uuid.uuid4().hex, | ||||
|         'links': 'links-' + uuid.uuid4().hex, | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes if there are some attributes set | ||||
|     attrs = attrs or {} | ||||
|  | ||||
|     transfer_info.update(attrs) | ||||
|  | ||||
|     transfer = fakes.FakeResource(None, transfer_info, loaded=True) | ||||
|  | ||||
|     return transfer | ||||
|  | ||||
|  | ||||
| def create_transfers(attrs=None, count=2): | ||||
|     """Create multiple fake transfers. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes of transfer | ||||
|     :param Integer count: | ||||
|         The number of transfers to be faked | ||||
|     :return: | ||||
|         A list of FakeResource objects | ||||
|     """ | ||||
|     transfers = [] | ||||
|     for n in range(0, count): | ||||
|         transfers.append(create_one_transfer(attrs)) | ||||
|  | ||||
|     return transfers | ||||
|  | ||||
|  | ||||
| def get_transfers(transfers=None, count=2): | ||||
|     """Get an iterable MagicMock object with a list of faked transfers. | ||||
|  | ||||
|     If transfers list is provided, then initialize the Mock object with the | ||||
|     list. Otherwise create one. | ||||
|  | ||||
|     :param List transfers: | ||||
|         A list of FakeResource objects faking transfers | ||||
|     :param Integer count: | ||||
|         The number of transfers to be faked | ||||
|     :return | ||||
|         An iterable Mock object with side_effect set to a list of faked | ||||
|         transfers | ||||
|     """ | ||||
|     if transfers is None: | ||||
|         transfers = create_transfers(count) | ||||
|  | ||||
|     return mock.Mock(side_effect=transfers) | ||||
|  | ||||
|  | ||||
| def create_one_service(attrs=None): | ||||
|     """Create a fake service. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes of service | ||||
|     :return: | ||||
|         A FakeResource object with host, status, etc. | ||||
|     """ | ||||
|     # Set default attribute | ||||
|     service_info = { | ||||
|         'host': 'host_test', | ||||
|         'binary': 'cinder_test', | ||||
|         'status': 'enabled', | ||||
|         'disabled_reason': 'LongHoliday-GoldenWeek', | ||||
|         'zone': 'fake_zone', | ||||
|         'updated_at': 'fake_date', | ||||
|         'state': 'fake_state', | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes if there are some attributes set | ||||
|     attrs = attrs or {} | ||||
|  | ||||
|     service_info.update(attrs) | ||||
|  | ||||
|     service = fakes.FakeResource(None, service_info, loaded=True) | ||||
|  | ||||
|     return service | ||||
|  | ||||
|  | ||||
| def create_services(attrs=None, count=2): | ||||
|     """Create multiple fake services. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes of service | ||||
|     :param Integer count: | ||||
|         The number of services to be faked | ||||
|     :return: | ||||
|         A list of FakeResource objects | ||||
|     """ | ||||
|     services = [] | ||||
|     for n in range(0, count): | ||||
|         services.append(create_one_service(attrs)) | ||||
|  | ||||
|     return services | ||||
|  | ||||
|  | ||||
| def get_services(services=None, count=2): | ||||
|     """Get an iterable MagicMock object with a list of faked services. | ||||
|  | ||||
|     If services list is provided, then initialize the Mock object with the | ||||
|     list. Otherwise create one. | ||||
|  | ||||
|     :param List services: | ||||
|         A list of FakeResource objects faking services | ||||
|     :param Integer count: | ||||
|         The number of services to be faked | ||||
|     :return | ||||
|         An iterable Mock object with side_effect set to a list of faked | ||||
|         services | ||||
|     """ | ||||
|     if services is None: | ||||
|         services = create_services(count) | ||||
|  | ||||
|     return mock.Mock(side_effect=services) | ||||
|  | ||||
|  | ||||
| def create_one_qos(attrs=None): | ||||
|     """Create a fake Qos specification. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :return: | ||||
|         A FakeResource object with id, name, consumer, etc. | ||||
|     """ | ||||
|     attrs = attrs or {} | ||||
|  | ||||
|     # Set default attributes. | ||||
|     qos_info = { | ||||
|         "id": 'qos-id-' + uuid.uuid4().hex, | ||||
|         "name": 'qos-name-' + uuid.uuid4().hex, | ||||
|         "consumer": 'front-end', | ||||
|         "specs": {"foo": "bar", "iops": "9001"}, | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes. | ||||
|     qos_info.update(attrs) | ||||
|  | ||||
|     qos = fakes.FakeResource(info=copy.deepcopy(qos_info), loaded=True) | ||||
|     return qos | ||||
|  | ||||
|  | ||||
| def create_one_qos_association(attrs=None): | ||||
|     """Create a fake Qos specification association. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :return: | ||||
|         A FakeResource object with id, name, association_type, etc. | ||||
|     """ | ||||
|     attrs = attrs or {} | ||||
|  | ||||
|     # Set default attributes. | ||||
|     qos_association_info = { | ||||
|         "id": 'type-id-' + uuid.uuid4().hex, | ||||
|         "name": 'type-name-' + uuid.uuid4().hex, | ||||
|         "association_type": 'volume_type', | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes. | ||||
|     qos_association_info.update(attrs) | ||||
|  | ||||
|     qos_association = fakes.FakeResource( | ||||
|         info=copy.deepcopy(qos_association_info), loaded=True | ||||
|     ) | ||||
|     return qos_association | ||||
|  | ||||
|  | ||||
| def create_qoses(attrs=None, count=2): | ||||
|     """Create multiple fake Qos specifications. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :param int count: | ||||
|         The number of Qos specifications to fake | ||||
|     :return: | ||||
|         A list of FakeResource objects faking the Qos specifications | ||||
|     """ | ||||
|     qoses = [] | ||||
|     for i in range(0, count): | ||||
|         qos = create_one_qos(attrs) | ||||
|         qoses.append(qos) | ||||
|  | ||||
|     return qoses | ||||
|  | ||||
|  | ||||
| def get_qoses(qoses=None, count=2): | ||||
|     """Get an iterable MagicMock object with a list of faked qoses. | ||||
|  | ||||
|     If qoses list is provided, then initialize the Mock object with the | ||||
|     list. Otherwise create one. | ||||
|  | ||||
|     :param List volumes: | ||||
|         A list of FakeResource objects faking qoses | ||||
|     :param Integer count: | ||||
|         The number of qoses to be faked | ||||
|     :return | ||||
|         An iterable Mock object with side_effect set to a list of faked | ||||
|         qoses | ||||
|     """ | ||||
|     if qoses is None: | ||||
|         qoses = create_qoses(count) | ||||
|  | ||||
|     return mock.Mock(side_effect=qoses) | ||||
|  | ||||
|  | ||||
| def create_one_volume(attrs=None): | ||||
|     """Create a fake volume. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes of volume | ||||
|     :return: | ||||
|         A FakeResource object with id, name, status, etc. | ||||
|     """ | ||||
|     attrs = attrs or {} | ||||
|  | ||||
|     # Set default attribute | ||||
|     volume_info = { | ||||
|         'id': 'volume-id' + uuid.uuid4().hex, | ||||
|         'display_name': 'volume-name' + uuid.uuid4().hex, | ||||
|         'display_description': 'description' + uuid.uuid4().hex, | ||||
|         'status': 'available', | ||||
|         'size': 10, | ||||
|         'volume_type': random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), | ||||
|         'bootable': 'true', | ||||
|         'metadata': { | ||||
|             'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, | ||||
|             'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, | ||||
|             'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, | ||||
|         }, | ||||
|         'snapshot_id': 'snapshot-id-' + uuid.uuid4().hex, | ||||
|         'availability_zone': 'zone' + uuid.uuid4().hex, | ||||
|         'attachments': [ | ||||
|             { | ||||
|                 'device': '/dev/' + uuid.uuid4().hex, | ||||
|                 'server_id': uuid.uuid4().hex, | ||||
|             }, | ||||
|         ], | ||||
|         'created_at': 'time-' + uuid.uuid4().hex, | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes if there are some attributes set | ||||
|     volume_info.update(attrs) | ||||
|  | ||||
|     volume = fakes.FakeResource(None, volume_info, loaded=True) | ||||
|     return volume | ||||
|  | ||||
|  | ||||
| def create_volumes(attrs=None, count=2): | ||||
|     """Create multiple fake volumes. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes of volume | ||||
|     :param Integer count: | ||||
|         The number of volumes to be faked | ||||
|     :return: | ||||
|         A list of FakeResource objects | ||||
|     """ | ||||
|     volumes = [] | ||||
|     for n in range(0, count): | ||||
|         volumes.append(create_one_volume(attrs)) | ||||
|  | ||||
|     return volumes | ||||
|  | ||||
|  | ||||
| def get_volumes(volumes=None, count=2): | ||||
|     """Get an iterable MagicMock object with a list of faked volumes. | ||||
|  | ||||
|     If volumes list is provided, then initialize the Mock object with the | ||||
|     list. Otherwise create one. | ||||
|  | ||||
|     :param List volumes: | ||||
|         A list of FakeResource objects faking volumes | ||||
|     :param Integer count: | ||||
|         The number of volumes to be faked | ||||
|     :return | ||||
|         An iterable Mock object with side_effect set to a list of faked | ||||
|         volumes | ||||
|     """ | ||||
|     if volumes is None: | ||||
|         volumes = create_volumes(count) | ||||
|  | ||||
|     return mock.Mock(side_effect=volumes) | ||||
|  | ||||
|  | ||||
| def create_one_volume_type(attrs=None, methods=None): | ||||
|     """Create a fake volume type. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :param Dictionary methods: | ||||
|         A dictionary with all methods | ||||
|     :return: | ||||
|         A FakeResource object with id, name, description, etc. | ||||
|     """ | ||||
|     attrs = attrs or {} | ||||
|     methods = methods or {} | ||||
|  | ||||
|     # Set default attributes. | ||||
|     volume_type_info = { | ||||
|         "id": 'type-id-' + uuid.uuid4().hex, | ||||
|         "name": 'type-name-' + uuid.uuid4().hex, | ||||
|         "description": 'type-description-' + uuid.uuid4().hex, | ||||
|         "extra_specs": {"foo": "bar"}, | ||||
|         "is_public": True, | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes. | ||||
|     volume_type_info.update(attrs) | ||||
|  | ||||
|     volume_type = fakes.FakeResource( | ||||
|         info=copy.deepcopy(volume_type_info), methods=methods, loaded=True | ||||
|     ) | ||||
|     return volume_type | ||||
|  | ||||
|  | ||||
| def create_volume_types(attrs=None, count=2): | ||||
|     """Create multiple fake types. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :param int count: | ||||
|         The number of types to fake | ||||
|     :return: | ||||
|         A list of FakeResource objects faking the types | ||||
|     """ | ||||
|     volume_types = [] | ||||
|     for i in range(0, count): | ||||
|         volume_type = create_one_volume_type(attrs) | ||||
|         volume_types.append(volume_type) | ||||
|  | ||||
|     return volume_types | ||||
|  | ||||
|  | ||||
| def get_volume_types(volume_types=None, count=2): | ||||
|     """Get an iterable MagicMock object with a list of faked types. | ||||
|  | ||||
|     If types list is provided, then initialize the Mock object with the | ||||
|     list. Otherwise create one. | ||||
|  | ||||
|     :param List volume_types: | ||||
|         A list of FakeResource objects faking types | ||||
|     :param Integer count: | ||||
|         The number of types to be faked | ||||
|     :return | ||||
|         An iterable Mock object with side_effect set to a list of faked | ||||
|         types | ||||
|     """ | ||||
|     if volume_types is None: | ||||
|         volume_types = create_volume_types(count) | ||||
|  | ||||
|     return mock.Mock(side_effect=volume_types) | ||||
|  | ||||
|  | ||||
| def create_one_encryption_volume_type(attrs=None): | ||||
|     """Create a fake encryption volume type. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :return: | ||||
|         A FakeResource object with volume_type_id etc. | ||||
|     """ | ||||
|     attrs = attrs or {} | ||||
|  | ||||
|     # Set default attributes. | ||||
|     encryption_info = { | ||||
|         "volume_type_id": 'type-id-' + uuid.uuid4().hex, | ||||
|         'provider': 'LuksEncryptor', | ||||
|         'cipher': None, | ||||
|         'key_size': None, | ||||
|         'control_location': 'front-end', | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes. | ||||
|     encryption_info.update(attrs) | ||||
|  | ||||
|     encryption_type = fakes.FakeResource( | ||||
|         info=copy.deepcopy(encryption_info), loaded=True | ||||
|     ) | ||||
|     return encryption_type | ||||
|  | ||||
|  | ||||
| def create_one_snapshot(attrs=None): | ||||
|     """Create a fake snapshot. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :return: | ||||
|         A FakeResource object with id, name, description, etc. | ||||
|     """ | ||||
|     attrs = attrs or {} | ||||
|  | ||||
|     # Set default attributes. | ||||
|     snapshot_info = { | ||||
|         "id": 'snapshot-id-' + uuid.uuid4().hex, | ||||
|         "display_name": 'snapshot-name-' + uuid.uuid4().hex, | ||||
|         "display_description": 'snapshot-description-' + uuid.uuid4().hex, | ||||
|         "size": 10, | ||||
|         "status": "available", | ||||
|         "metadata": {"foo": "bar"}, | ||||
|         "created_at": "2015-06-03T18:49:19.000000", | ||||
|         "volume_id": 'vloume-id-' + uuid.uuid4().hex, | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes. | ||||
|     snapshot_info.update(attrs) | ||||
|  | ||||
|     snapshot_method = {'update': None} | ||||
|  | ||||
|     snapshot = fakes.FakeResource( | ||||
|         info=copy.deepcopy(snapshot_info), | ||||
|         methods=copy.deepcopy(snapshot_method), | ||||
|         loaded=True, | ||||
|     ) | ||||
|     return snapshot | ||||
|  | ||||
|  | ||||
| def create_snapshots(attrs=None, count=2): | ||||
|     """Create multiple fake snapshots. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :param int count: | ||||
|         The number of snapshots to fake | ||||
|     :return: | ||||
|         A list of FakeResource objects faking the snapshots | ||||
|     """ | ||||
|     snapshots = [] | ||||
|     for i in range(0, count): | ||||
|         snapshot = create_one_snapshot(attrs) | ||||
|         snapshots.append(snapshot) | ||||
|  | ||||
|     return snapshots | ||||
|  | ||||
|  | ||||
| def get_snapshots(snapshots=None, count=2): | ||||
|     """Get an iterable MagicMock object with a list of faked snapshots. | ||||
|  | ||||
|     If snapshots list is provided, then initialize the Mock object with the | ||||
|     list. Otherwise create one. | ||||
|  | ||||
|     :param List volumes: | ||||
|         A list of FakeResource objects faking snapshots | ||||
|     :param Integer count: | ||||
|         The number of snapshots to be faked | ||||
|     :return | ||||
|         An iterable Mock object with side_effect set to a list of faked | ||||
|         snapshots | ||||
|     """ | ||||
|     if snapshots is None: | ||||
|         snapshots = create_snapshots(count) | ||||
|  | ||||
|     return mock.Mock(side_effect=snapshots) | ||||
|  | ||||
|  | ||||
| def create_one_backup(attrs=None): | ||||
|     """Create a fake backup. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :return: | ||||
|         A FakeResource object with id, name, volume_id, etc. | ||||
|     """ | ||||
|     attrs = attrs or {} | ||||
|  | ||||
|     # Set default attributes. | ||||
|     backup_info = { | ||||
|         "id": 'backup-id-' + uuid.uuid4().hex, | ||||
|         "name": 'backup-name-' + uuid.uuid4().hex, | ||||
|         "volume_id": 'volume-id-' + uuid.uuid4().hex, | ||||
|         "snapshot_id": 'snapshot-id' + uuid.uuid4().hex, | ||||
|         "description": 'description-' + uuid.uuid4().hex, | ||||
|         "object_count": None, | ||||
|         "container": 'container-' + uuid.uuid4().hex, | ||||
|         "size": random.randint(1, 20), | ||||
|         "status": "error", | ||||
|         "availability_zone": 'zone' + uuid.uuid4().hex, | ||||
|         "links": 'links-' + uuid.uuid4().hex, | ||||
|     } | ||||
|  | ||||
|     # Overwrite default attributes. | ||||
|     backup_info.update(attrs) | ||||
|  | ||||
|     backup = fakes.FakeResource(info=copy.deepcopy(backup_info), loaded=True) | ||||
|     return backup | ||||
|  | ||||
|  | ||||
| def create_backups(attrs=None, count=2): | ||||
|     """Create multiple fake backups. | ||||
|  | ||||
|     :param Dictionary attrs: | ||||
|         A dictionary with all attributes | ||||
|     :param int count: | ||||
|         The number of backups to fake | ||||
|     :return: | ||||
|         A list of FakeResource objects faking the backups | ||||
|     """ | ||||
|     backups = [] | ||||
|     for i in range(0, count): | ||||
|         backup = create_one_backup(attrs) | ||||
|         backups.append(backup) | ||||
|  | ||||
|     return backups | ||||
|  | ||||
|  | ||||
| def get_backups(backups=None, count=2): | ||||
|     """Get an iterable MagicMock object with a list of faked backups. | ||||
|  | ||||
|     If backups list is provided, then initialize the Mock object with the | ||||
|     list. Otherwise create one. | ||||
|  | ||||
|     :param List volumes: | ||||
|         A list of FakeResource objects faking backups | ||||
|     :param Integer count: | ||||
|         The number of backups to be faked | ||||
|     :return | ||||
|         An iterable Mock object with side_effect set to a list of faked | ||||
|         backups | ||||
|     """ | ||||
|     if backups is None: | ||||
|         backups = create_backups(count) | ||||
|  | ||||
|     return mock.Mock(side_effect=backups) | ||||
| @@ -1,471 +0,0 @@ | ||||
| #   Copyright 2015 iWeb Technologies Inc. | ||||
| # | ||||
| #   Licensed under the Apache License, Version 2.0 (the "License"); you may | ||||
| #   not use this file except in compliance with the License. You may obtain | ||||
| #   a copy of the License at | ||||
| # | ||||
| #        http://www.apache.org/licenses/LICENSE-2.0 | ||||
| # | ||||
| #   Unless required by applicable law or agreed to in writing, software | ||||
| #   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||
| #   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||
| #   License for the specific language governing permissions and limitations | ||||
| #   under the License. | ||||
| # | ||||
|  | ||||
| import copy | ||||
| from unittest import mock | ||||
| from unittest.mock import call | ||||
|  | ||||
| from osc_lib.cli import format_columns | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes | ||||
| from openstackclient.volume.v1 import qos_specs | ||||
|  | ||||
|  | ||||
| class TestQos(volume_fakes.TestVolumev1): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.qos_mock = self.volume_client.qos_specs | ||||
|         self.qos_mock.reset_mock() | ||||
|  | ||||
|         self.types_mock = self.volume_client.volume_types | ||||
|         self.types_mock.reset_mock() | ||||
|  | ||||
|  | ||||
| class TestQosAssociate(TestQos): | ||||
|     volume_type = volume_fakes.create_one_volume_type() | ||||
|     qos_spec = volume_fakes.create_one_qos() | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.qos_mock.get.return_value = self.qos_spec | ||||
|         self.types_mock.get.return_value = self.volume_type | ||||
|         # Get the command object to test | ||||
|         self.cmd = qos_specs.AssociateQos(self.app, None) | ||||
|  | ||||
|     def test_qos_associate(self): | ||||
|         arglist = [self.qos_spec.id, self.volume_type.id] | ||||
|         verifylist = [ | ||||
|             ('qos_spec', self.qos_spec.id), | ||||
|             ('volume_type', self.volume_type.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.associate.assert_called_with( | ||||
|             self.qos_spec.id, self.volume_type.id | ||||
|         ) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|  | ||||
| class TestQosCreate(TestQos): | ||||
|     columns = ('consumer', 'id', 'name', 'properties') | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.new_qos_spec = volume_fakes.create_one_qos() | ||||
|         self.datalist = ( | ||||
|             self.new_qos_spec.consumer, | ||||
|             self.new_qos_spec.id, | ||||
|             self.new_qos_spec.name, | ||||
|             format_columns.DictColumn(self.new_qos_spec.specs), | ||||
|         ) | ||||
|         self.qos_mock.create.return_value = self.new_qos_spec | ||||
|         # Get the command object to test | ||||
|         self.cmd = qos_specs.CreateQos(self.app, None) | ||||
|  | ||||
|     def test_qos_create_without_properties(self): | ||||
|         arglist = [ | ||||
|             self.new_qos_spec.name, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('name', self.new_qos_spec.name), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.create.assert_called_with( | ||||
|             self.new_qos_spec.name, {'consumer': 'both'} | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.datalist, data) | ||||
|  | ||||
|     def test_qos_create_with_consumer(self): | ||||
|         arglist = [ | ||||
|             '--consumer', | ||||
|             self.new_qos_spec.consumer, | ||||
|             self.new_qos_spec.name, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('consumer', self.new_qos_spec.consumer), | ||||
|             ('name', self.new_qos_spec.name), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.create.assert_called_with( | ||||
|             self.new_qos_spec.name, {'consumer': self.new_qos_spec.consumer} | ||||
|         ) | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.datalist, data) | ||||
|  | ||||
|     def test_qos_create_with_properties(self): | ||||
|         arglist = [ | ||||
|             '--consumer', | ||||
|             self.new_qos_spec.consumer, | ||||
|             '--property', | ||||
|             'foo=bar', | ||||
|             '--property', | ||||
|             'iops=9001', | ||||
|             self.new_qos_spec.name, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('consumer', self.new_qos_spec.consumer), | ||||
|             ('property', self.new_qos_spec.specs), | ||||
|             ('name', self.new_qos_spec.name), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.new_qos_spec.specs.update( | ||||
|             {'consumer': self.new_qos_spec.consumer} | ||||
|         ) | ||||
|         self.qos_mock.create.assert_called_with( | ||||
|             self.new_qos_spec.name, self.new_qos_spec.specs | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.datalist, data) | ||||
|  | ||||
|  | ||||
| class TestQosDelete(TestQos): | ||||
|     qos_specs = volume_fakes.create_qoses(count=2) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.qos_mock.get = volume_fakes.get_qoses(self.qos_specs) | ||||
|         # Get the command object to test | ||||
|         self.cmd = qos_specs.DeleteQos(self.app, None) | ||||
|  | ||||
|     def test_qos_delete_with_id(self): | ||||
|         arglist = [self.qos_specs[0].id] | ||||
|         verifylist = [('qos_specs', [self.qos_specs[0].id])] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_qos_delete_with_name(self): | ||||
|         arglist = [self.qos_specs[0].name] | ||||
|         verifylist = [('qos_specs', [self.qos_specs[0].name])] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_qos_delete_with_force(self): | ||||
|         arglist = ['--force', self.qos_specs[0].id] | ||||
|         verifylist = [('force', True), ('qos_specs', [self.qos_specs[0].id])] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, True) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_delete_multiple_qoses(self): | ||||
|         arglist = [] | ||||
|         for q in self.qos_specs: | ||||
|             arglist.append(q.id) | ||||
|         verifylist = [ | ||||
|             ('qos_specs', arglist), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         calls = [] | ||||
|         for q in self.qos_specs: | ||||
|             calls.append(call(q.id, False)) | ||||
|         self.qos_mock.delete.assert_has_calls(calls) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_delete_multiple_qoses_with_exception(self): | ||||
|         arglist = [ | ||||
|             self.qos_specs[0].id, | ||||
|             'unexist_qos', | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('qos_specs', arglist), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         find_mock_result = [self.qos_specs[0], exceptions.CommandError] | ||||
|         with mock.patch.object( | ||||
|             utils, 'find_resource', side_effect=find_mock_result | ||||
|         ) as find_mock: | ||||
|             try: | ||||
|                 self.cmd.take_action(parsed_args) | ||||
|                 self.fail('CommandError should be raised.') | ||||
|             except exceptions.CommandError as e: | ||||
|                 self.assertEqual( | ||||
|                     '1 of 2 QoS specifications failed to delete.', str(e) | ||||
|                 ) | ||||
|  | ||||
|             find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id) | ||||
|             find_mock.assert_any_call(self.qos_mock, 'unexist_qos') | ||||
|  | ||||
|             self.assertEqual(2, find_mock.call_count) | ||||
|             self.qos_mock.delete.assert_called_once_with( | ||||
|                 self.qos_specs[0].id, False | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class TestQosDisassociate(TestQos): | ||||
|     volume_type = volume_fakes.create_one_volume_type() | ||||
|     qos_spec = volume_fakes.create_one_qos() | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.qos_mock.get.return_value = self.qos_spec | ||||
|         self.types_mock.get.return_value = self.volume_type | ||||
|         # Get the command object to test | ||||
|         self.cmd = qos_specs.DisassociateQos(self.app, None) | ||||
|  | ||||
|     def test_qos_disassociate_with_volume_type(self): | ||||
|         arglist = [ | ||||
|             '--volume-type', | ||||
|             self.volume_type.id, | ||||
|             self.qos_spec.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('volume_type', self.volume_type.id), | ||||
|             ('qos_spec', self.qos_spec.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.disassociate.assert_called_with( | ||||
|             self.qos_spec.id, self.volume_type.id | ||||
|         ) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_qos_disassociate_with_all_volume_types(self): | ||||
|         arglist = [ | ||||
|             '--all', | ||||
|             self.qos_spec.id, | ||||
|         ] | ||||
|         verifylist = [('qos_spec', self.qos_spec.id)] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.disassociate_all.assert_called_with(self.qos_spec.id) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|  | ||||
| class TestQosList(TestQos): | ||||
|     qos_specs = volume_fakes.create_qoses(count=2) | ||||
|     qos_association = volume_fakes.create_one_qos_association() | ||||
|  | ||||
|     columns = ( | ||||
|         'ID', | ||||
|         'Name', | ||||
|         'Consumer', | ||||
|         'Associations', | ||||
|         'Properties', | ||||
|     ) | ||||
|     data = [] | ||||
|     for q in qos_specs: | ||||
|         data.append( | ||||
|             ( | ||||
|                 q.id, | ||||
|                 q.name, | ||||
|                 q.consumer, | ||||
|                 format_columns.ListColumn([qos_association.name]), | ||||
|                 format_columns.DictColumn(q.specs), | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.qos_mock.list.return_value = self.qos_specs | ||||
|         self.qos_mock.get_associations.return_value = [self.qos_association] | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = qos_specs.ListQos(self.app, None) | ||||
|  | ||||
|     def test_qos_list(self): | ||||
|         arglist = [] | ||||
|         verifylist = [] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.qos_mock.list.assert_called_with() | ||||
|  | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.data, list(data)) | ||||
|  | ||||
|     def test_qos_list_no_association(self): | ||||
|         self.qos_mock.reset_mock() | ||||
|         self.qos_mock.get_associations.side_effect = [ | ||||
|             [self.qos_association], | ||||
|             exceptions.NotFound("NotFound"), | ||||
|         ] | ||||
|  | ||||
|         arglist = [] | ||||
|         verifylist = [] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.qos_mock.list.assert_called_with() | ||||
|  | ||||
|         self.assertEqual(self.columns, columns) | ||||
|  | ||||
|         ex_data = copy.deepcopy(self.data) | ||||
|         ex_data[1] = ( | ||||
|             self.qos_specs[1].id, | ||||
|             self.qos_specs[1].name, | ||||
|             self.qos_specs[1].consumer, | ||||
|             format_columns.ListColumn(None), | ||||
|             format_columns.DictColumn(self.qos_specs[1].specs), | ||||
|         ) | ||||
|         self.assertCountEqual(ex_data, list(data)) | ||||
|  | ||||
|  | ||||
| class TestQosSet(TestQos): | ||||
|     qos_spec = volume_fakes.create_one_qos() | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.qos_mock.get.return_value = self.qos_spec | ||||
|         # Get the command object to test | ||||
|         self.cmd = qos_specs.SetQos(self.app, None) | ||||
|  | ||||
|     def test_qos_set_with_properties_with_id(self): | ||||
|         arglist = [ | ||||
|             '--no-property', | ||||
|             '--property', | ||||
|             'a=b', | ||||
|             '--property', | ||||
|             'c=d', | ||||
|             self.qos_spec.id, | ||||
|         ] | ||||
|         new_property = {"a": "b", "c": "d"} | ||||
|         verifylist = [ | ||||
|             ('no_property', True), | ||||
|             ('property', new_property), | ||||
|             ('qos_spec', self.qos_spec.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.unset_keys.assert_called_with( | ||||
|             self.qos_spec.id, | ||||
|             list(self.qos_spec.specs.keys()), | ||||
|         ) | ||||
|         self.qos_mock.set_keys.assert_called_with( | ||||
|             self.qos_spec.id, | ||||
|             {"a": "b", "c": "d"}, | ||||
|         ) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|  | ||||
| class TestQosShow(TestQos): | ||||
|     qos_spec = volume_fakes.create_one_qos() | ||||
|     qos_association = volume_fakes.create_one_qos_association() | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.qos_mock.get.return_value = self.qos_spec | ||||
|         self.qos_mock.get_associations.return_value = [self.qos_association] | ||||
|         # Get the command object to test | ||||
|         self.cmd = qos_specs.ShowQos(self.app, None) | ||||
|  | ||||
|     def test_qos_show(self): | ||||
|         arglist = [self.qos_spec.id] | ||||
|         verifylist = [('qos_spec', self.qos_spec.id)] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.qos_mock.get.assert_called_with(self.qos_spec.id) | ||||
|  | ||||
|         collist = ('associations', 'consumer', 'id', 'name', 'properties') | ||||
|         self.assertEqual(collist, columns) | ||||
|         datalist = ( | ||||
|             format_columns.ListColumn([self.qos_association.name]), | ||||
|             self.qos_spec.consumer, | ||||
|             self.qos_spec.id, | ||||
|             self.qos_spec.name, | ||||
|             format_columns.DictColumn(self.qos_spec.specs), | ||||
|         ) | ||||
|         self.assertCountEqual(datalist, tuple(data)) | ||||
|  | ||||
|  | ||||
| class TestQosUnset(TestQos): | ||||
|     qos_spec = volume_fakes.create_one_qos() | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.qos_mock.get.return_value = self.qos_spec | ||||
|         # Get the command object to test | ||||
|         self.cmd = qos_specs.UnsetQos(self.app, None) | ||||
|  | ||||
|     def test_qos_unset_with_properties(self): | ||||
|         arglist = [ | ||||
|             '--property', | ||||
|             'iops', | ||||
|             '--property', | ||||
|             'foo', | ||||
|             self.qos_spec.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('property', ['iops', 'foo']), | ||||
|             ('qos_spec', self.qos_spec.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.qos_mock.unset_keys.assert_called_with( | ||||
|             self.qos_spec.id, ['iops', 'foo'] | ||||
|         ) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_qos_unset_nothing(self): | ||||
|         arglist = [ | ||||
|             self.qos_spec.id, | ||||
|         ] | ||||
|  | ||||
|         verifylist = [ | ||||
|             ('qos_spec', self.qos_spec.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|         self.assertIsNone(result) | ||||
| @@ -1,295 +0,0 @@ | ||||
| # | ||||
| #   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 osc_lib import exceptions | ||||
|  | ||||
| from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes | ||||
| from openstackclient.volume.v1 import service | ||||
|  | ||||
|  | ||||
| class TestService(volume_fakes.TestVolumev1): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         # Get a shortcut to the ServiceManager Mock | ||||
|         self.service_mock = self.volume_client.services | ||||
|         self.service_mock.reset_mock() | ||||
|  | ||||
|  | ||||
| class TestServiceList(TestService): | ||||
|     # The service to be listed | ||||
|     services = volume_fakes.create_one_service() | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.service_mock.list.return_value = [self.services] | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = service.ListService(self.app, None) | ||||
|  | ||||
|     def test_service_list(self): | ||||
|         arglist = [ | ||||
|             '--host', | ||||
|             self.services.host, | ||||
|             '--service', | ||||
|             self.services.binary, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('host', self.services.host), | ||||
|             ('service', self.services.binary), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         # In base command class Lister in cliff, abstract method take_action() | ||||
|         # returns a tuple containing the column names and an iterable | ||||
|         # containing the data to be listed. | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         expected_columns = [ | ||||
|             'Binary', | ||||
|             'Host', | ||||
|             'Zone', | ||||
|             'Status', | ||||
|             'State', | ||||
|             'Updated At', | ||||
|         ] | ||||
|  | ||||
|         # confirming if all expected columns are present in the result. | ||||
|         self.assertEqual(expected_columns, columns) | ||||
|  | ||||
|         datalist = ( | ||||
|             ( | ||||
|                 self.services.binary, | ||||
|                 self.services.host, | ||||
|                 self.services.zone, | ||||
|                 self.services.status, | ||||
|                 self.services.state, | ||||
|                 self.services.updated_at, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|         # confirming if all expected values are present in the result. | ||||
|         self.assertEqual(datalist, tuple(data)) | ||||
|  | ||||
|         # checking if proper call was made to list services | ||||
|         self.service_mock.list.assert_called_with( | ||||
|             self.services.host, | ||||
|             self.services.binary, | ||||
|         ) | ||||
|  | ||||
|         # checking if prohibited columns are present in output | ||||
|         self.assertNotIn("Disabled Reason", columns) | ||||
|         self.assertNotIn(self.services.disabled_reason, tuple(data)) | ||||
|  | ||||
|     def test_service_list_with_long_option(self): | ||||
|         arglist = [ | ||||
|             '--host', | ||||
|             self.services.host, | ||||
|             '--service', | ||||
|             self.services.binary, | ||||
|             '--long', | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('host', self.services.host), | ||||
|             ('service', self.services.binary), | ||||
|             ('long', True), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         # In base command class Lister in cliff, abstract method take_action() | ||||
|         # returns a tuple containing the column names and an iterable | ||||
|         # containing the data to be listed. | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         expected_columns = [ | ||||
|             'Binary', | ||||
|             'Host', | ||||
|             'Zone', | ||||
|             'Status', | ||||
|             'State', | ||||
|             'Updated At', | ||||
|             'Disabled Reason', | ||||
|         ] | ||||
|  | ||||
|         # confirming if all expected columns are present in the result. | ||||
|         self.assertEqual(expected_columns, columns) | ||||
|  | ||||
|         datalist = ( | ||||
|             ( | ||||
|                 self.services.binary, | ||||
|                 self.services.host, | ||||
|                 self.services.zone, | ||||
|                 self.services.status, | ||||
|                 self.services.state, | ||||
|                 self.services.updated_at, | ||||
|                 self.services.disabled_reason, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|         # confirming if all expected values are present in the result. | ||||
|         self.assertEqual(datalist, tuple(data)) | ||||
|  | ||||
|         self.service_mock.list.assert_called_with( | ||||
|             self.services.host, | ||||
|             self.services.binary, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class TestServiceSet(TestService): | ||||
|     service = volume_fakes.create_one_service() | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.service_mock.enable.return_value = self.service | ||||
|         self.service_mock.disable.return_value = self.service | ||||
|         self.service_mock.disable_log_reason.return_value = self.service | ||||
|  | ||||
|         self.cmd = service.SetService(self.app, None) | ||||
|  | ||||
|     def test_service_set_nothing(self): | ||||
|         arglist = [ | ||||
|             self.service.host, | ||||
|             self.service.binary, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('host', self.service.host), | ||||
|             ('service', self.service.binary), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.service_mock.enable.assert_not_called() | ||||
|         self.service_mock.disable.assert_not_called() | ||||
|         self.service_mock.disable_log_reason.assert_not_called() | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_service_set_enable(self): | ||||
|         arglist = [ | ||||
|             '--enable', | ||||
|             self.service.host, | ||||
|             self.service.binary, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('enable', True), | ||||
|             ('host', self.service.host), | ||||
|             ('service', self.service.binary), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.service_mock.enable.assert_called_with( | ||||
|             self.service.host, self.service.binary | ||||
|         ) | ||||
|         self.service_mock.disable.assert_not_called() | ||||
|         self.service_mock.disable_log_reason.assert_not_called() | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_service_set_disable(self): | ||||
|         arglist = [ | ||||
|             '--disable', | ||||
|             self.service.host, | ||||
|             self.service.binary, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('disable', True), | ||||
|             ('host', self.service.host), | ||||
|             ('service', self.service.binary), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.service_mock.disable.assert_called_with( | ||||
|             self.service.host, self.service.binary | ||||
|         ) | ||||
|         self.service_mock.enable.assert_not_called() | ||||
|         self.service_mock.disable_log_reason.assert_not_called() | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_service_set_disable_with_reason(self): | ||||
|         reason = 'earthquake' | ||||
|         arglist = [ | ||||
|             '--disable', | ||||
|             '--disable-reason', | ||||
|             reason, | ||||
|             self.service.host, | ||||
|             self.service.binary, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('disable', True), | ||||
|             ('disable_reason', reason), | ||||
|             ('host', self.service.host), | ||||
|             ('service', self.service.binary), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.service_mock.disable_log_reason.assert_called_with( | ||||
|             self.service.host, self.service.binary, reason | ||||
|         ) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_service_set_only_with_disable_reason(self): | ||||
|         reason = 'earthquake' | ||||
|         arglist = [ | ||||
|             '--disable-reason', | ||||
|             reason, | ||||
|             self.service.host, | ||||
|             self.service.binary, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('disable_reason', reason), | ||||
|             ('host', self.service.host), | ||||
|             ('service', self.service.binary), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         try: | ||||
|             self.cmd.take_action(parsed_args) | ||||
|             self.fail("CommandError should be raised.") | ||||
|         except exceptions.CommandError as e: | ||||
|             self.assertEqual( | ||||
|                 "Cannot specify option --disable-reason without " | ||||
|                 "--disable specified.", | ||||
|                 str(e), | ||||
|             ) | ||||
|  | ||||
|     def test_service_set_enable_with_disable_reason(self): | ||||
|         reason = 'earthquake' | ||||
|         arglist = [ | ||||
|             '--enable', | ||||
|             '--disable-reason', | ||||
|             reason, | ||||
|             self.service.host, | ||||
|             self.service.binary, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('enable', True), | ||||
|             ('disable_reason', reason), | ||||
|             ('host', self.service.host), | ||||
|             ('service', self.service.binary), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         try: | ||||
|             self.cmd.take_action(parsed_args) | ||||
|             self.fail("CommandError should be raised.") | ||||
|         except exceptions.CommandError as e: | ||||
|             self.assertEqual( | ||||
|                 "Cannot specify option --disable-reason without " | ||||
|                 "--disable specified.", | ||||
|                 str(e), | ||||
|             ) | ||||
| @@ -1,380 +0,0 @@ | ||||
| # | ||||
| #   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 unittest import mock | ||||
| from unittest.mock import call | ||||
|  | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes | ||||
| from openstackclient.volume.v1 import volume_transfer_request | ||||
|  | ||||
|  | ||||
| class TestTransfer(volume_fakes.TestVolumev1): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         # Get a shortcut to the TransferManager Mock | ||||
|         self.transfer_mock = self.volume_client.transfers | ||||
|         self.transfer_mock.reset_mock() | ||||
|  | ||||
|         # Get a shortcut to the VolumeManager Mock | ||||
|         self.volumes_mock = self.volume_client.volumes | ||||
|         self.volumes_mock.reset_mock() | ||||
|  | ||||
|  | ||||
| class TestTransferAccept(TestTransfer): | ||||
|     columns = ( | ||||
|         'id', | ||||
|         'name', | ||||
|         'volume_id', | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.volume_transfer = volume_fakes.create_one_transfer() | ||||
|         self.data = ( | ||||
|             self.volume_transfer.id, | ||||
|             self.volume_transfer.name, | ||||
|             self.volume_transfer.volume_id, | ||||
|         ) | ||||
|  | ||||
|         self.transfer_mock.get.return_value = self.volume_transfer | ||||
|         self.transfer_mock.accept.return_value = self.volume_transfer | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_transfer_request.AcceptTransferRequest( | ||||
|             self.app, None | ||||
|         ) | ||||
|  | ||||
|     def test_transfer_accept(self): | ||||
|         arglist = [ | ||||
|             '--auth-key', | ||||
|             'key_value', | ||||
|             self.volume_transfer.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('transfer_request', self.volume_transfer.id), | ||||
|             ('auth_key', 'key_value'), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.transfer_mock.get.assert_called_once_with( | ||||
|             self.volume_transfer.id, | ||||
|         ) | ||||
|         self.transfer_mock.accept.assert_called_once_with( | ||||
|             self.volume_transfer.id, | ||||
|             'key_value', | ||||
|         ) | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertEqual(self.data, data) | ||||
|  | ||||
|     def test_transfer_accept_no_option(self): | ||||
|         arglist = [ | ||||
|             self.volume_transfer.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('transfer_request', self.volume_transfer.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         self.assertRaises( | ||||
|             exceptions.CommandError, | ||||
|             self.cmd.take_action, | ||||
|             parsed_args, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class TestTransferCreate(TestTransfer): | ||||
|     volume = volume_fakes.create_one_volume() | ||||
|  | ||||
|     columns = ( | ||||
|         'auth_key', | ||||
|         'created_at', | ||||
|         'id', | ||||
|         'name', | ||||
|         'volume_id', | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.volume_transfer = volume_fakes.create_one_transfer( | ||||
|             attrs={ | ||||
|                 'volume_id': self.volume.id, | ||||
|                 'auth_key': 'key', | ||||
|                 'created_at': 'time', | ||||
|             }, | ||||
|         ) | ||||
|         self.data = ( | ||||
|             self.volume_transfer.auth_key, | ||||
|             self.volume_transfer.created_at, | ||||
|             self.volume_transfer.id, | ||||
|             self.volume_transfer.name, | ||||
|             self.volume_transfer.volume_id, | ||||
|         ) | ||||
|  | ||||
|         self.transfer_mock.create.return_value = self.volume_transfer | ||||
|         self.volumes_mock.get.return_value = self.volume | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_transfer_request.CreateTransferRequest( | ||||
|             self.app, None | ||||
|         ) | ||||
|  | ||||
|     def test_transfer_create_without_name(self): | ||||
|         arglist = [ | ||||
|             self.volume.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('volume', self.volume.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.transfer_mock.create.assert_called_once_with(self.volume.id, None) | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertEqual(self.data, data) | ||||
|  | ||||
|     def test_transfer_create_with_name(self): | ||||
|         arglist = [ | ||||
|             '--name', | ||||
|             self.volume_transfer.name, | ||||
|             self.volume.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('name', self.volume_transfer.name), | ||||
|             ('volume', self.volume.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.transfer_mock.create.assert_called_once_with( | ||||
|             self.volume.id, | ||||
|             self.volume_transfer.name, | ||||
|         ) | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertEqual(self.data, data) | ||||
|  | ||||
|  | ||||
| class TestTransferDelete(TestTransfer): | ||||
|     volume_transfers = volume_fakes.create_transfers(count=2) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.transfer_mock.get = volume_fakes.get_transfers( | ||||
|             self.volume_transfers, | ||||
|         ) | ||||
|         self.transfer_mock.delete.return_value = None | ||||
|  | ||||
|         # Get the command object to mock | ||||
|         self.cmd = volume_transfer_request.DeleteTransferRequest( | ||||
|             self.app, None | ||||
|         ) | ||||
|  | ||||
|     def test_transfer_delete(self): | ||||
|         arglist = [self.volume_transfers[0].id] | ||||
|         verifylist = [("transfer_request", [self.volume_transfers[0].id])] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.transfer_mock.delete.assert_called_with( | ||||
|             self.volume_transfers[0].id | ||||
|         ) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_delete_multiple_transfers(self): | ||||
|         arglist = [] | ||||
|         for v in self.volume_transfers: | ||||
|             arglist.append(v.id) | ||||
|         verifylist = [ | ||||
|             ('transfer_request', arglist), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         calls = [] | ||||
|         for v in self.volume_transfers: | ||||
|             calls.append(call(v.id)) | ||||
|         self.transfer_mock.delete.assert_has_calls(calls) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_delete_multiple_transfers_with_exception(self): | ||||
|         arglist = [ | ||||
|             self.volume_transfers[0].id, | ||||
|             'unexist_transfer', | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('transfer_request', arglist), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         find_mock_result = [self.volume_transfers[0], exceptions.CommandError] | ||||
|         with mock.patch.object( | ||||
|             utils, 'find_resource', side_effect=find_mock_result | ||||
|         ) as find_mock: | ||||
|             try: | ||||
|                 self.cmd.take_action(parsed_args) | ||||
|                 self.fail('CommandError should be raised.') | ||||
|             except exceptions.CommandError as e: | ||||
|                 self.assertEqual( | ||||
|                     '1 of 2 volume transfer requests failed to delete', | ||||
|                     str(e), | ||||
|                 ) | ||||
|  | ||||
|             find_mock.assert_any_call( | ||||
|                 self.transfer_mock, self.volume_transfers[0].id | ||||
|             ) | ||||
|             find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') | ||||
|  | ||||
|             self.assertEqual(2, find_mock.call_count) | ||||
|             self.transfer_mock.delete.assert_called_once_with( | ||||
|                 self.volume_transfers[0].id, | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class TestTransferList(TestTransfer): | ||||
|     # The Transfers to be listed | ||||
|     volume_transfers = volume_fakes.create_one_transfer() | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.transfer_mock.list.return_value = [self.volume_transfers] | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_transfer_request.ListTransferRequest(self.app, None) | ||||
|  | ||||
|     def test_transfer_list_without_argument(self): | ||||
|         arglist = [] | ||||
|         verifylist = [] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         # In base command class Lister in cliff, abstract method take_action() | ||||
|         # returns a tuple containing the column names and an iterable | ||||
|         # containing the data to be listed. | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         expected_columns = [ | ||||
|             'ID', | ||||
|             'Name', | ||||
|             'Volume', | ||||
|         ] | ||||
|  | ||||
|         # confirming if all expected columns are present in the result. | ||||
|         self.assertEqual(expected_columns, columns) | ||||
|  | ||||
|         datalist = ( | ||||
|             ( | ||||
|                 self.volume_transfers.id, | ||||
|                 self.volume_transfers.name, | ||||
|                 self.volume_transfers.volume_id, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|         # confirming if all expected values are present in the result. | ||||
|         self.assertEqual(datalist, tuple(data)) | ||||
|  | ||||
|         # checking if proper call was made to list volume_transfers | ||||
|         self.transfer_mock.list.assert_called_with( | ||||
|             detailed=True, search_opts={'all_tenants': 0} | ||||
|         ) | ||||
|  | ||||
|     def test_transfer_list_with_argument(self): | ||||
|         arglist = ["--all-projects"] | ||||
|         verifylist = [("all_projects", True)] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         # In base command class Lister in cliff, abstract method take_action() | ||||
|         # returns a tuple containing the column names and an iterable | ||||
|         # containing the data to be listed. | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         expected_columns = [ | ||||
|             'ID', | ||||
|             'Name', | ||||
|             'Volume', | ||||
|         ] | ||||
|  | ||||
|         # confirming if all expected columns are present in the result. | ||||
|         self.assertEqual(expected_columns, columns) | ||||
|  | ||||
|         datalist = ( | ||||
|             ( | ||||
|                 self.volume_transfers.id, | ||||
|                 self.volume_transfers.name, | ||||
|                 self.volume_transfers.volume_id, | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|         # confirming if all expected values are present in the result. | ||||
|         self.assertEqual(datalist, tuple(data)) | ||||
|  | ||||
|         # checking if proper call was made to list volume_transfers | ||||
|         self.transfer_mock.list.assert_called_with( | ||||
|             detailed=True, search_opts={'all_tenants': 1} | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class TestTransferShow(TestTransfer): | ||||
|     columns = ( | ||||
|         'created_at', | ||||
|         'id', | ||||
|         'name', | ||||
|         'volume_id', | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.volume_transfer = volume_fakes.create_one_transfer( | ||||
|             attrs={'created_at': 'time'} | ||||
|         ) | ||||
|         self.data = ( | ||||
|             self.volume_transfer.created_at, | ||||
|             self.volume_transfer.id, | ||||
|             self.volume_transfer.name, | ||||
|             self.volume_transfer.volume_id, | ||||
|         ) | ||||
|  | ||||
|         self.transfer_mock.get.return_value = self.volume_transfer | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_transfer_request.ShowTransferRequest(self.app, None) | ||||
|  | ||||
|     def test_transfer_show(self): | ||||
|         arglist = [ | ||||
|             self.volume_transfer.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('transfer_request', self.volume_transfer.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.transfer_mock.get.assert_called_once_with(self.volume_transfer.id) | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertEqual(self.data, data) | ||||
| @@ -1,633 +0,0 @@ | ||||
| # | ||||
| #   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 unittest import mock | ||||
| from unittest.mock import call | ||||
|  | ||||
| from osc_lib.cli import format_columns | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.tests.unit import utils as tests_utils | ||||
| from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes | ||||
| from openstackclient.volume.v1 import volume_type | ||||
|  | ||||
|  | ||||
| class TestType(volume_fakes.TestVolumev1): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.types_mock = self.volume_client.volume_types | ||||
|         self.types_mock.reset_mock() | ||||
|  | ||||
|         self.encryption_types_mock = self.volume_client.volume_encryption_types | ||||
|         self.encryption_types_mock.reset_mock() | ||||
|  | ||||
|  | ||||
| class TestTypeCreate(TestType): | ||||
|     columns = ( | ||||
|         'description', | ||||
|         'id', | ||||
|         'is_public', | ||||
|         'name', | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.new_volume_type = volume_fakes.create_one_volume_type( | ||||
|             methods={'set_keys': {'myprop': 'myvalue'}}, | ||||
|         ) | ||||
|         self.data = ( | ||||
|             self.new_volume_type.description, | ||||
|             self.new_volume_type.id, | ||||
|             True, | ||||
|             self.new_volume_type.name, | ||||
|         ) | ||||
|  | ||||
|         self.types_mock.create.return_value = self.new_volume_type | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_type.CreateVolumeType(self.app, None) | ||||
|  | ||||
|     def test_type_create(self): | ||||
|         arglist = [ | ||||
|             self.new_volume_type.name, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("name", self.new_volume_type.name), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.types_mock.create.assert_called_with( | ||||
|             self.new_volume_type.name, | ||||
|         ) | ||||
|  | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.data, data) | ||||
|  | ||||
|     def test_type_create_with_encryption(self): | ||||
|         encryption_info = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': 'aes-xts-plain64', | ||||
|             'key_size': '128', | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         encryption_type = volume_fakes.create_one_encryption_volume_type( | ||||
|             attrs=encryption_info, | ||||
|         ) | ||||
|         self.new_volume_type = volume_fakes.create_one_volume_type( | ||||
|             attrs={'encryption': encryption_info}, | ||||
|         ) | ||||
|         self.types_mock.create.return_value = self.new_volume_type | ||||
|         self.encryption_types_mock.create.return_value = encryption_type | ||||
|         encryption_columns = ( | ||||
|             'description', | ||||
|             'encryption', | ||||
|             'id', | ||||
|             'is_public', | ||||
|             'name', | ||||
|         ) | ||||
|         encryption_data = ( | ||||
|             self.new_volume_type.description, | ||||
|             format_columns.DictColumn(encryption_info), | ||||
|             self.new_volume_type.id, | ||||
|             True, | ||||
|             self.new_volume_type.name, | ||||
|         ) | ||||
|         arglist = [ | ||||
|             '--encryption-provider', | ||||
|             'LuksEncryptor', | ||||
|             '--encryption-cipher', | ||||
|             'aes-xts-plain64', | ||||
|             '--encryption-key-size', | ||||
|             '128', | ||||
|             '--encryption-control-location', | ||||
|             'front-end', | ||||
|             self.new_volume_type.name, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('encryption_provider', 'LuksEncryptor'), | ||||
|             ('encryption_cipher', 'aes-xts-plain64'), | ||||
|             ('encryption_key_size', 128), | ||||
|             ('encryption_control_location', 'front-end'), | ||||
|             ('name', self.new_volume_type.name), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.types_mock.create.assert_called_with( | ||||
|             self.new_volume_type.name, | ||||
|         ) | ||||
|         body = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': 'aes-xts-plain64', | ||||
|             'key_size': 128, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         self.encryption_types_mock.create.assert_called_with( | ||||
|             self.new_volume_type, | ||||
|             body, | ||||
|         ) | ||||
|         self.assertEqual(encryption_columns, columns) | ||||
|         self.assertCountEqual(encryption_data, data) | ||||
|  | ||||
|  | ||||
| class TestTypeDelete(TestType): | ||||
|     volume_types = volume_fakes.create_volume_types(count=2) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.types_mock.get = volume_fakes.get_volume_types(self.volume_types) | ||||
|         self.types_mock.delete.return_value = None | ||||
|  | ||||
|         # Get the command object to mock | ||||
|         self.cmd = volume_type.DeleteVolumeType(self.app, None) | ||||
|  | ||||
|     def test_type_delete(self): | ||||
|         arglist = [self.volume_types[0].id] | ||||
|         verifylist = [("volume_types", [self.volume_types[0].id])] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.types_mock.delete.assert_called_with(self.volume_types[0]) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_delete_multiple_types(self): | ||||
|         arglist = [] | ||||
|         for t in self.volume_types: | ||||
|             arglist.append(t.id) | ||||
|         verifylist = [ | ||||
|             ('volume_types', arglist), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         calls = [] | ||||
|         for t in self.volume_types: | ||||
|             calls.append(call(t)) | ||||
|         self.types_mock.delete.assert_has_calls(calls) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_delete_multiple_types_with_exception(self): | ||||
|         arglist = [ | ||||
|             self.volume_types[0].id, | ||||
|             'unexist_type', | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('volume_types', arglist), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         find_mock_result = [self.volume_types[0], exceptions.CommandError] | ||||
|         with mock.patch.object( | ||||
|             utils, 'find_resource', side_effect=find_mock_result | ||||
|         ) as find_mock: | ||||
|             try: | ||||
|                 self.cmd.take_action(parsed_args) | ||||
|                 self.fail('CommandError should be raised.') | ||||
|             except exceptions.CommandError as e: | ||||
|                 self.assertEqual( | ||||
|                     '1 of 2 volume types failed to delete.', str(e) | ||||
|                 ) | ||||
|  | ||||
|             find_mock.assert_any_call(self.types_mock, self.volume_types[0].id) | ||||
|             find_mock.assert_any_call(self.types_mock, 'unexist_type') | ||||
|  | ||||
|             self.assertEqual(2, find_mock.call_count) | ||||
|             self.types_mock.delete.assert_called_once_with( | ||||
|                 self.volume_types[0] | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class TestTypeList(TestType): | ||||
|     volume_types = volume_fakes.create_volume_types() | ||||
|  | ||||
|     columns = [ | ||||
|         "ID", | ||||
|         "Name", | ||||
|         "Is Public", | ||||
|     ] | ||||
|     columns_long = ["ID", "Name", "Is Public", "Properties"] | ||||
|  | ||||
|     data = [] | ||||
|     for t in volume_types: | ||||
|         data.append( | ||||
|             ( | ||||
|                 t.id, | ||||
|                 t.name, | ||||
|                 t.is_public, | ||||
|             ) | ||||
|         ) | ||||
|     data_long = [] | ||||
|     for t in volume_types: | ||||
|         data_long.append( | ||||
|             ( | ||||
|                 t.id, | ||||
|                 t.name, | ||||
|                 t.is_public, | ||||
|                 format_columns.DictColumn(t.extra_specs), | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.types_mock.list.return_value = self.volume_types | ||||
|         self.encryption_types_mock.create.return_value = None | ||||
|         self.encryption_types_mock.update.return_value = None | ||||
|         # get the command to test | ||||
|         self.cmd = volume_type.ListVolumeType(self.app, None) | ||||
|  | ||||
|     def test_type_list_without_options(self): | ||||
|         arglist = [] | ||||
|         verifylist = [ | ||||
|             ("long", False), | ||||
|             ("encryption_type", False), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.types_mock.list.assert_called_once_with() | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.data, list(data)) | ||||
|  | ||||
|     def test_type_list_with_options(self): | ||||
|         arglist = [ | ||||
|             "--long", | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("long", True), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.types_mock.list.assert_called_once_with() | ||||
|         self.assertEqual(self.columns_long, columns) | ||||
|         self.assertCountEqual(self.data_long, list(data)) | ||||
|  | ||||
|     def test_type_list_with_encryption(self): | ||||
|         encryption_type = volume_fakes.create_one_encryption_volume_type( | ||||
|             attrs={'volume_type_id': self.volume_types[0].id}, | ||||
|         ) | ||||
|         encryption_info = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': None, | ||||
|             'key_size': None, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         encryption_columns = self.columns + [ | ||||
|             "Encryption", | ||||
|         ] | ||||
|         encryption_data = [] | ||||
|         encryption_data.append( | ||||
|             ( | ||||
|                 self.volume_types[0].id, | ||||
|                 self.volume_types[0].name, | ||||
|                 self.volume_types[0].is_public, | ||||
|                 volume_type.EncryptionInfoColumn( | ||||
|                     self.volume_types[0].id, | ||||
|                     {self.volume_types[0].id: encryption_info}, | ||||
|                 ), | ||||
|             ) | ||||
|         ) | ||||
|         encryption_data.append( | ||||
|             ( | ||||
|                 self.volume_types[1].id, | ||||
|                 self.volume_types[1].name, | ||||
|                 self.volume_types[1].is_public, | ||||
|                 volume_type.EncryptionInfoColumn(self.volume_types[1].id, {}), | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|         self.encryption_types_mock.list.return_value = [encryption_type] | ||||
|         arglist = [ | ||||
|             "--encryption-type", | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("encryption_type", True), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.encryption_types_mock.list.assert_called_once_with() | ||||
|         self.types_mock.list.assert_called_once_with() | ||||
|         self.assertEqual(encryption_columns, columns) | ||||
|         self.assertCountEqual(encryption_data, list(data)) | ||||
|  | ||||
|  | ||||
| class TestTypeSet(TestType): | ||||
|     volume_type = volume_fakes.create_one_volume_type( | ||||
|         methods={'set_keys': None}, | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.types_mock.get.return_value = self.volume_type | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_type.SetVolumeType(self.app, None) | ||||
|  | ||||
|     def test_type_set_nothing(self): | ||||
|         arglist = [ | ||||
|             self.volume_type.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('volume_type', self.volume_type.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_type_set_property(self): | ||||
|         arglist = [ | ||||
|             '--property', | ||||
|             'myprop=myvalue', | ||||
|             self.volume_type.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('property', {'myprop': 'myvalue'}), | ||||
|             ('volume_type', self.volume_type.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|         self.volume_type.set_keys.assert_called_once_with( | ||||
|             {'myprop': 'myvalue'} | ||||
|         ) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_type_set_new_encryption(self): | ||||
|         arglist = [ | ||||
|             '--encryption-provider', | ||||
|             'LuksEncryptor', | ||||
|             '--encryption-cipher', | ||||
|             'aes-xts-plain64', | ||||
|             '--encryption-key-size', | ||||
|             '128', | ||||
|             '--encryption-control-location', | ||||
|             'front-end', | ||||
|             self.volume_type.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('encryption_provider', 'LuksEncryptor'), | ||||
|             ('encryption_cipher', 'aes-xts-plain64'), | ||||
|             ('encryption_key_size', 128), | ||||
|             ('encryption_control_location', 'front-end'), | ||||
|             ('volume_type', self.volume_type.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|         body = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': 'aes-xts-plain64', | ||||
|             'key_size': 128, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         self.encryption_types_mock.create.assert_called_with( | ||||
|             self.volume_type, | ||||
|             body, | ||||
|         ) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_type_set_new_encryption_without_provider(self): | ||||
|         arglist = [ | ||||
|             '--encryption-cipher', | ||||
|             'aes-xts-plain64', | ||||
|             '--encryption-key-size', | ||||
|             '128', | ||||
|             '--encryption-control-location', | ||||
|             'front-end', | ||||
|             self.volume_type.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('encryption_cipher', 'aes-xts-plain64'), | ||||
|             ('encryption_key_size', 128), | ||||
|             ('encryption_control_location', 'front-end'), | ||||
|             ('volume_type', self.volume_type.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         try: | ||||
|             self.cmd.take_action(parsed_args) | ||||
|             self.fail('CommandError should be raised.') | ||||
|         except exceptions.CommandError as e: | ||||
|             self.assertEqual( | ||||
|                 "Command Failed: One or more of the operations failed", | ||||
|                 str(e), | ||||
|             ) | ||||
|         self.encryption_types_mock.create.assert_not_called() | ||||
|         self.encryption_types_mock.update.assert_not_called() | ||||
|  | ||||
|  | ||||
| class TestTypeShow(TestType): | ||||
|     columns = ( | ||||
|         'description', | ||||
|         'id', | ||||
|         'is_public', | ||||
|         'name', | ||||
|         'properties', | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.volume_type = volume_fakes.create_one_volume_type() | ||||
|         self.data = ( | ||||
|             self.volume_type.description, | ||||
|             self.volume_type.id, | ||||
|             True, | ||||
|             self.volume_type.name, | ||||
|             format_columns.DictColumn(self.volume_type.extra_specs), | ||||
|         ) | ||||
|  | ||||
|         self.types_mock.get.return_value = self.volume_type | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_type.ShowVolumeType(self.app, None) | ||||
|  | ||||
|     def test_type_show(self): | ||||
|         arglist = [self.volume_type.id] | ||||
|         verifylist = [ | ||||
|             ("volume_type", self.volume_type.id), | ||||
|             ("encryption_type", False), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.types_mock.get.assert_called_with(self.volume_type.id) | ||||
|  | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.data, data) | ||||
|  | ||||
|     def test_type_show_with_encryption(self): | ||||
|         encryption_type = volume_fakes.create_one_encryption_volume_type() | ||||
|         encryption_info = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': None, | ||||
|             'key_size': None, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         self.volume_type = volume_fakes.create_one_volume_type( | ||||
|             attrs={'encryption': encryption_info}, | ||||
|         ) | ||||
|         self.types_mock.get.return_value = self.volume_type | ||||
|         self.encryption_types_mock.get.return_value = encryption_type | ||||
|         encryption_columns = ( | ||||
|             'description', | ||||
|             'encryption', | ||||
|             'id', | ||||
|             'is_public', | ||||
|             'name', | ||||
|             'properties', | ||||
|         ) | ||||
|         encryption_data = ( | ||||
|             self.volume_type.description, | ||||
|             format_columns.DictColumn(encryption_info), | ||||
|             self.volume_type.id, | ||||
|             True, | ||||
|             self.volume_type.name, | ||||
|             format_columns.DictColumn(self.volume_type.extra_specs), | ||||
|         ) | ||||
|         arglist = ['--encryption-type', self.volume_type.id] | ||||
|         verifylist = [ | ||||
|             ('encryption_type', True), | ||||
|             ("volume_type", self.volume_type.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.types_mock.get.assert_called_with(self.volume_type.id) | ||||
|         self.encryption_types_mock.get.assert_called_with(self.volume_type.id) | ||||
|         self.assertEqual(encryption_columns, columns) | ||||
|         self.assertCountEqual(encryption_data, data) | ||||
|  | ||||
|  | ||||
| class TestTypeUnset(TestType): | ||||
|     volume_type = volume_fakes.create_one_volume_type( | ||||
|         methods={'unset_keys': None}, | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.types_mock.get.return_value = self.volume_type | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_type.UnsetVolumeType(self.app, None) | ||||
|  | ||||
|     def test_type_unset_property(self): | ||||
|         arglist = [ | ||||
|             '--property', | ||||
|             'property', | ||||
|             '--property', | ||||
|             'multi_property', | ||||
|             self.volume_type.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('encryption_type', False), | ||||
|             ('property', ['property', 'multi_property']), | ||||
|             ('volume_type', self.volume_type.id), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|         self.volume_type.unset_keys.assert_called_once_with( | ||||
|             ['property', 'multi_property'] | ||||
|         ) | ||||
|         self.encryption_types_mock.delete.assert_not_called() | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_type_unset_failed_with_missing_volume_type_argument(self): | ||||
|         arglist = [ | ||||
|             '--property', | ||||
|             'property', | ||||
|             '--property', | ||||
|             'multi_property', | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('property', ['property', 'multi_property']), | ||||
|         ] | ||||
|  | ||||
|         self.assertRaises( | ||||
|             tests_utils.ParserException, | ||||
|             self.check_parser, | ||||
|             self.cmd, | ||||
|             arglist, | ||||
|             verifylist, | ||||
|         ) | ||||
|  | ||||
|     def test_type_unset_nothing(self): | ||||
|         arglist = [ | ||||
|             self.volume_type.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('volume_type', self.volume_type.id), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_type_unset_encryption_type(self): | ||||
|         arglist = [ | ||||
|             '--encryption-type', | ||||
|             self.volume_type.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('encryption_type', True), | ||||
|             ('volume_type', self.volume_type.id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|         self.encryption_types_mock.delete.assert_called_with(self.volume_type) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|  | ||||
| class TestColumns(TestType): | ||||
|     def test_encryption_info_column_with_info(self): | ||||
|         fake_volume_type = volume_fakes.create_one_volume_type() | ||||
|         type_id = fake_volume_type.id | ||||
|  | ||||
|         encryption_info = { | ||||
|             'provider': 'LuksEncryptor', | ||||
|             'cipher': None, | ||||
|             'key_size': None, | ||||
|             'control_location': 'front-end', | ||||
|         } | ||||
|         col = volume_type.EncryptionInfoColumn( | ||||
|             type_id, {type_id: encryption_info} | ||||
|         ) | ||||
|         self.assertEqual( | ||||
|             utils.format_dict(encryption_info), col.human_readable() | ||||
|         ) | ||||
|         self.assertEqual(encryption_info, col.machine_readable()) | ||||
|  | ||||
|     def test_encryption_info_column_without_info(self): | ||||
|         fake_volume_type = volume_fakes.create_one_volume_type() | ||||
|         type_id = fake_volume_type.id | ||||
|  | ||||
|         col = volume_type.EncryptionInfoColumn(type_id, {}) | ||||
|         self.assertEqual('-', col.human_readable()) | ||||
|         self.assertIsNone(col.machine_readable()) | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,435 +0,0 @@ | ||||
| # | ||||
| #   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 unittest import mock | ||||
| from unittest.mock import call | ||||
|  | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes | ||||
| from openstackclient.volume.v1 import volume_backup | ||||
|  | ||||
|  | ||||
| class TestBackup(volume_fakes.TestVolumev1): | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.backups_mock = self.volume_client.backups | ||||
|         self.backups_mock.reset_mock() | ||||
|         self.volumes_mock = self.volume_client.volumes | ||||
|         self.volumes_mock.reset_mock() | ||||
|         self.snapshots_mock = self.volume_client.volume_snapshots | ||||
|         self.snapshots_mock.reset_mock() | ||||
|         self.restores_mock = self.volume_client.restores | ||||
|         self.restores_mock.reset_mock() | ||||
|  | ||||
|  | ||||
| class TestBackupCreate(TestBackup): | ||||
|     volume = volume_fakes.create_one_volume() | ||||
|  | ||||
|     columns = ( | ||||
|         'availability_zone', | ||||
|         'container', | ||||
|         'description', | ||||
|         'id', | ||||
|         'name', | ||||
|         'object_count', | ||||
|         'size', | ||||
|         'snapshot_id', | ||||
|         'status', | ||||
|         'volume_id', | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.new_backup = volume_fakes.create_one_backup( | ||||
|             attrs={'volume_id': self.volume.id}, | ||||
|         ) | ||||
|         self.data = ( | ||||
|             self.new_backup.availability_zone, | ||||
|             self.new_backup.container, | ||||
|             self.new_backup.description, | ||||
|             self.new_backup.id, | ||||
|             self.new_backup.name, | ||||
|             self.new_backup.object_count, | ||||
|             self.new_backup.size, | ||||
|             self.new_backup.snapshot_id, | ||||
|             self.new_backup.status, | ||||
|             self.new_backup.volume_id, | ||||
|         ) | ||||
|         self.volumes_mock.get.return_value = self.volume | ||||
|         self.backups_mock.create.return_value = self.new_backup | ||||
|  | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_backup.CreateVolumeBackup(self.app, None) | ||||
|  | ||||
|     def test_backup_create(self): | ||||
|         arglist = [ | ||||
|             "--name", | ||||
|             self.new_backup.name, | ||||
|             "--description", | ||||
|             self.new_backup.description, | ||||
|             "--container", | ||||
|             self.new_backup.container, | ||||
|             self.new_backup.volume_id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("name", self.new_backup.name), | ||||
|             ("description", self.new_backup.description), | ||||
|             ("container", self.new_backup.container), | ||||
|             ("volume", self.new_backup.volume_id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.backups_mock.create.assert_called_with( | ||||
|             self.new_backup.volume_id, | ||||
|             self.new_backup.container, | ||||
|             self.new_backup.name, | ||||
|             self.new_backup.description, | ||||
|         ) | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.data, data) | ||||
|  | ||||
|     def test_backup_create_without_name(self): | ||||
|         arglist = [ | ||||
|             "--description", | ||||
|             self.new_backup.description, | ||||
|             "--container", | ||||
|             self.new_backup.container, | ||||
|             self.new_backup.volume_id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("description", self.new_backup.description), | ||||
|             ("container", self.new_backup.container), | ||||
|             ("volume", self.new_backup.volume_id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.backups_mock.create.assert_called_with( | ||||
|             self.new_backup.volume_id, | ||||
|             self.new_backup.container, | ||||
|             None, | ||||
|             self.new_backup.description, | ||||
|         ) | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.data, data) | ||||
|  | ||||
|  | ||||
| class TestBackupDelete(TestBackup): | ||||
|     backups = volume_fakes.create_backups(count=2) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.backups_mock.get = volume_fakes.get_backups(self.backups) | ||||
|         self.backups_mock.delete.return_value = None | ||||
|  | ||||
|         # Get the command object to mock | ||||
|         self.cmd = volume_backup.DeleteVolumeBackup(self.app, None) | ||||
|  | ||||
|     def test_backup_delete(self): | ||||
|         arglist = [self.backups[0].id] | ||||
|         verifylist = [("backups", [self.backups[0].id])] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         self.backups_mock.delete.assert_called_with(self.backups[0].id) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_delete_multiple_backups(self): | ||||
|         arglist = [] | ||||
|         for b in self.backups: | ||||
|             arglist.append(b.id) | ||||
|         verifylist = [ | ||||
|             ('backups', arglist), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         calls = [] | ||||
|         for b in self.backups: | ||||
|             calls.append(call(b.id)) | ||||
|         self.backups_mock.delete.assert_has_calls(calls) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_delete_multiple_backups_with_exception(self): | ||||
|         arglist = [ | ||||
|             self.backups[0].id, | ||||
|             'unexist_backup', | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('backups', arglist), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         find_mock_result = [self.backups[0], exceptions.CommandError] | ||||
|         with mock.patch.object( | ||||
|             utils, 'find_resource', side_effect=find_mock_result | ||||
|         ) as find_mock: | ||||
|             try: | ||||
|                 self.cmd.take_action(parsed_args) | ||||
|                 self.fail('CommandError should be raised.') | ||||
|             except exceptions.CommandError as e: | ||||
|                 self.assertEqual('1 of 2 backups failed to delete.', str(e)) | ||||
|  | ||||
|             find_mock.assert_any_call(self.backups_mock, self.backups[0].id) | ||||
|             find_mock.assert_any_call(self.backups_mock, 'unexist_backup') | ||||
|  | ||||
|             self.assertEqual(2, find_mock.call_count) | ||||
|             self.backups_mock.delete.assert_called_once_with( | ||||
|                 self.backups[0].id, | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class TestBackupList(TestBackup): | ||||
|     volume = volume_fakes.create_one_volume() | ||||
|     backups = volume_fakes.create_backups( | ||||
|         attrs={'volume_id': volume.display_name}, | ||||
|         count=3, | ||||
|     ) | ||||
|  | ||||
|     columns = [ | ||||
|         'ID', | ||||
|         'Name', | ||||
|         'Description', | ||||
|         'Status', | ||||
|         'Size', | ||||
|     ] | ||||
|     columns_long = columns + [ | ||||
|         'Availability Zone', | ||||
|         'Volume', | ||||
|         'Container', | ||||
|     ] | ||||
|  | ||||
|     data = [] | ||||
|     for b in backups: | ||||
|         data.append( | ||||
|             ( | ||||
|                 b.id, | ||||
|                 b.name, | ||||
|                 b.description, | ||||
|                 b.status, | ||||
|                 b.size, | ||||
|             ) | ||||
|         ) | ||||
|     data_long = [] | ||||
|     for b in backups: | ||||
|         data_long.append( | ||||
|             ( | ||||
|                 b.id, | ||||
|                 b.name, | ||||
|                 b.description, | ||||
|                 b.status, | ||||
|                 b.size, | ||||
|                 b.availability_zone, | ||||
|                 volume_backup.VolumeIdColumn(b.volume_id), | ||||
|                 b.container, | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.volumes_mock.list.return_value = [self.volume] | ||||
|         self.backups_mock.list.return_value = self.backups | ||||
|         self.volumes_mock.get.return_value = self.volume | ||||
|         # Get the command to test | ||||
|         self.cmd = volume_backup.ListVolumeBackup(self.app, None) | ||||
|  | ||||
|     def test_backup_list_without_options(self): | ||||
|         arglist = [] | ||||
|         verifylist = [ | ||||
|             ("long", False), | ||||
|             ("name", None), | ||||
|             ("status", None), | ||||
|             ("volume", None), | ||||
|             ('all_projects', False), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         search_opts = { | ||||
|             "name": None, | ||||
|             "status": None, | ||||
|             "volume_id": None, | ||||
|             "all_tenants": False, | ||||
|         } | ||||
|         self.volumes_mock.get.assert_not_called() | ||||
|         self.backups_mock.list.assert_called_with( | ||||
|             search_opts=search_opts, | ||||
|         ) | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.data, list(data)) | ||||
|  | ||||
|     def test_backup_list_with_options(self): | ||||
|         arglist = [ | ||||
|             "--long", | ||||
|             "--name", | ||||
|             self.backups[0].name, | ||||
|             "--status", | ||||
|             "error", | ||||
|             "--volume", | ||||
|             self.volume.id, | ||||
|             "--all-projects", | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("long", True), | ||||
|             ("name", self.backups[0].name), | ||||
|             ("status", "error"), | ||||
|             ("volume", self.volume.id), | ||||
|             ('all_projects', True), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         search_opts = { | ||||
|             "name": self.backups[0].name, | ||||
|             "status": "error", | ||||
|             "volume_id": self.volume.id, | ||||
|             "all_tenants": True, | ||||
|         } | ||||
|         self.volumes_mock.get.assert_called_once_with(self.volume.id) | ||||
|         self.backups_mock.list.assert_called_with( | ||||
|             search_opts=search_opts, | ||||
|         ) | ||||
|         self.assertEqual(self.columns_long, columns) | ||||
|         self.assertCountEqual(self.data_long, list(data)) | ||||
|  | ||||
|  | ||||
| class TestBackupRestore(TestBackup): | ||||
|     volume = volume_fakes.create_one_volume() | ||||
|     backup = volume_fakes.create_one_backup( | ||||
|         attrs={'volume_id': volume.id}, | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|  | ||||
|         self.backups_mock.get.return_value = self.backup | ||||
|         self.volumes_mock.get.return_value = self.volume | ||||
|         self.restores_mock.restore.return_value = ( | ||||
|             volume_fakes.create_one_volume( | ||||
|                 {'id': self.volume['id']}, | ||||
|             ) | ||||
|         ) | ||||
|         # Get the command object to mock | ||||
|         self.cmd = volume_backup.RestoreVolumeBackup(self.app, None) | ||||
|  | ||||
|     def test_backup_restore(self): | ||||
|         arglist = [ | ||||
|             self.backup.id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("backup", self.backup.id), | ||||
|             ("volume", None), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|         self.restores_mock.restore.assert_called_with(self.backup.id, None) | ||||
|         self.assertIsNotNone(result) | ||||
|  | ||||
|     def test_backup_restore_with_existing_volume(self): | ||||
|         arglist = [ | ||||
|             self.backup.id, | ||||
|             self.backup.volume_id, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("backup", self.backup.id), | ||||
|             ("volume", self.backup.volume_id), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|         self.restores_mock.restore.assert_called_with( | ||||
|             self.backup.id, | ||||
|             self.backup.volume_id, | ||||
|         ) | ||||
|         self.assertIsNotNone(result) | ||||
|  | ||||
|     def test_backup_restore_with_invalid_volume(self): | ||||
|         arglist = [ | ||||
|             self.backup.id, | ||||
|             "unexist_volume", | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ("backup", self.backup.id), | ||||
|             ("volume", "unexist_volume"), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         with mock.patch.object( | ||||
|             utils, | ||||
|             'find_resource', | ||||
|             side_effect=exceptions.CommandError(), | ||||
|         ): | ||||
|             self.assertRaises( | ||||
|                 exceptions.CommandError, | ||||
|                 self.cmd.take_action, | ||||
|                 parsed_args, | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class TestBackupShow(TestBackup): | ||||
|     columns = ( | ||||
|         'availability_zone', | ||||
|         'container', | ||||
|         'description', | ||||
|         'id', | ||||
|         'name', | ||||
|         'object_count', | ||||
|         'size', | ||||
|         'snapshot_id', | ||||
|         'status', | ||||
|         'volume_id', | ||||
|     ) | ||||
|  | ||||
|     def setUp(self): | ||||
|         super().setUp() | ||||
|         self.backup = volume_fakes.create_one_backup() | ||||
|         self.data = ( | ||||
|             self.backup.availability_zone, | ||||
|             self.backup.container, | ||||
|             self.backup.description, | ||||
|             self.backup.id, | ||||
|             self.backup.name, | ||||
|             self.backup.object_count, | ||||
|             self.backup.size, | ||||
|             self.backup.snapshot_id, | ||||
|             self.backup.status, | ||||
|             self.backup.volume_id, | ||||
|         ) | ||||
|         self.backups_mock.get.return_value = self.backup | ||||
|         # Get the command object to test | ||||
|         self.cmd = volume_backup.ShowVolumeBackup(self.app, None) | ||||
|  | ||||
|     def test_backup_show(self): | ||||
|         arglist = [self.backup.id] | ||||
|         verifylist = [("backup", self.backup.id)] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = self.cmd.take_action(parsed_args) | ||||
|         self.backups_mock.get.assert_called_with(self.backup.id) | ||||
|  | ||||
|         self.assertEqual(self.columns, columns) | ||||
|         self.assertCountEqual(self.data, data) | ||||
| @@ -1,376 +0,0 @@ | ||||
| #   Copyright 2015 iWeb Technologies Inc. | ||||
| # | ||||
| #   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. | ||||
| # | ||||
|  | ||||
| """Volume v1 QoS action implementations""" | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from osc_lib.cli import format_columns | ||||
| from osc_lib.cli import parseractions | ||||
| from osc_lib.command import command | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.i18n import _ | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class AssociateQos(command.Command): | ||||
|     _description = _("Associate a QoS specification to a volume type") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'qos_spec', | ||||
|             metavar='<qos-spec>', | ||||
|             help=_('QoS specification to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             'volume_type', | ||||
|             metavar='<volume-type>', | ||||
|             help=_('Volume type to associate the QoS (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         qos_spec = utils.find_resource( | ||||
|             volume_client.qos_specs, parsed_args.qos_spec | ||||
|         ) | ||||
|         volume_type = utils.find_resource( | ||||
|             volume_client.volume_types, parsed_args.volume_type | ||||
|         ) | ||||
|  | ||||
|         volume_client.qos_specs.associate(qos_spec.id, volume_type.id) | ||||
|  | ||||
|  | ||||
| class CreateQos(command.ShowOne): | ||||
|     _description = _("Create new QoS specification") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'name', | ||||
|             metavar='<name>', | ||||
|             help=_('New QoS specification name'), | ||||
|         ) | ||||
|         consumer_choices = ['front-end', 'back-end', 'both'] | ||||
|         parser.add_argument( | ||||
|             '--consumer', | ||||
|             metavar='<consumer>', | ||||
|             choices=consumer_choices, | ||||
|             default='both', | ||||
|             help=( | ||||
|                 _( | ||||
|                     'Consumer of the QoS. Valid consumers: %s ' | ||||
|                     "(defaults to 'both')" | ||||
|                 ) | ||||
|                 % utils.format_list(consumer_choices) | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key=value>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_( | ||||
|                 'Set a QoS specification property ' | ||||
|                 '(repeat option to set multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         specs = {} | ||||
|         specs.update({'consumer': parsed_args.consumer}) | ||||
|  | ||||
|         if parsed_args.property: | ||||
|             specs.update(parsed_args.property) | ||||
|  | ||||
|         qos_spec = volume_client.qos_specs.create(parsed_args.name, specs) | ||||
|         qos_spec._info.update( | ||||
|             { | ||||
|                 'properties': format_columns.DictColumn( | ||||
|                     qos_spec._info.pop('specs') | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|         return zip(*sorted(qos_spec._info.items())) | ||||
|  | ||||
|  | ||||
| class DeleteQos(command.Command): | ||||
|     _description = _("Delete QoS specification") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'qos_specs', | ||||
|             metavar='<qos-spec>', | ||||
|             nargs="+", | ||||
|             help=_('QoS specification(s) to delete (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--force', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_("Allow to delete in-use QoS specification(s)"), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         result = 0 | ||||
|  | ||||
|         for i in parsed_args.qos_specs: | ||||
|             try: | ||||
|                 qos_spec = utils.find_resource(volume_client.qos_specs, i) | ||||
|                 volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) | ||||
|             except Exception as e: | ||||
|                 result += 1 | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to delete QoS specification with " | ||||
|                         "name or ID '%(qos)s': %(e)s" | ||||
|                     ), | ||||
|                     {'qos': i, 'e': e}, | ||||
|                 ) | ||||
|  | ||||
|         if result > 0: | ||||
|             total = len(parsed_args.qos_specs) | ||||
|             msg = _( | ||||
|                 "%(result)s of %(total)s QoS specifications failed to delete." | ||||
|             ) % {'result': result, 'total': total} | ||||
|             raise exceptions.CommandError(msg) | ||||
|  | ||||
|  | ||||
| class DisassociateQos(command.Command): | ||||
|     _description = _("Disassociate a QoS specification from a volume type") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'qos_spec', | ||||
|             metavar='<qos-spec>', | ||||
|             help=_('QoS specification to modify (name or ID)'), | ||||
|         ) | ||||
|         volume_type_group = parser.add_mutually_exclusive_group() | ||||
|         volume_type_group.add_argument( | ||||
|             '--volume-type', | ||||
|             metavar='<volume-type>', | ||||
|             help=_('Volume type to disassociate the QoS from (name or ID)'), | ||||
|         ) | ||||
|         volume_type_group.add_argument( | ||||
|             '--all', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_('Disassociate the QoS from every volume type'), | ||||
|         ) | ||||
|  | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         qos_spec = utils.find_resource( | ||||
|             volume_client.qos_specs, parsed_args.qos_spec | ||||
|         ) | ||||
|  | ||||
|         if parsed_args.volume_type: | ||||
|             volume_type = utils.find_resource( | ||||
|                 volume_client.volume_types, parsed_args.volume_type | ||||
|             ) | ||||
|             volume_client.qos_specs.disassociate(qos_spec.id, volume_type.id) | ||||
|         elif parsed_args.all: | ||||
|             volume_client.qos_specs.disassociate_all(qos_spec.id) | ||||
|  | ||||
|  | ||||
| class ListQos(command.Lister): | ||||
|     _description = _("List QoS specifications") | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         qos_specs_list = volume_client.qos_specs.list() | ||||
|  | ||||
|         for qos in qos_specs_list: | ||||
|             try: | ||||
|                 qos_associations = volume_client.qos_specs.get_associations( | ||||
|                     qos, | ||||
|                 ) | ||||
|                 if qos_associations: | ||||
|                     associations = [ | ||||
|                         association.name for association in qos_associations | ||||
|                     ] | ||||
|                     qos._info.update({'associations': associations}) | ||||
|             except Exception as ex: | ||||
|                 if type(ex).__name__ == 'NotFound': | ||||
|                     qos._info.update({'associations': None}) | ||||
|                 else: | ||||
|                     raise | ||||
|  | ||||
|         display_columns = ( | ||||
|             'ID', | ||||
|             'Name', | ||||
|             'Consumer', | ||||
|             'Associations', | ||||
|             'Properties', | ||||
|         ) | ||||
|         columns = ('ID', 'Name', 'Consumer', 'Associations', 'Specs') | ||||
|         return ( | ||||
|             display_columns, | ||||
|             ( | ||||
|                 utils.get_dict_properties( | ||||
|                     s._info, | ||||
|                     columns, | ||||
|                     formatters={ | ||||
|                         'Specs': format_columns.DictColumn, | ||||
|                         'Associations': format_columns.ListColumn, | ||||
|                     }, | ||||
|                 ) | ||||
|                 for s in qos_specs_list | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class SetQos(command.Command): | ||||
|     _description = _("Set QoS specification properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'qos_spec', | ||||
|             metavar='<qos-spec>', | ||||
|             help=_('QoS specification to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--no-property', | ||||
|             dest='no_property', | ||||
|             action='store_true', | ||||
|             help=_( | ||||
|                 'Remove all properties from <qos-spec> ' | ||||
|                 '(specify both --no-property and --property to remove the ' | ||||
|                 'current properties before setting new properties)' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key=value>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_( | ||||
|                 'Property to add or modify for this QoS specification ' | ||||
|                 '(repeat option to set multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         qos_spec = utils.find_resource( | ||||
|             volume_client.qos_specs, parsed_args.qos_spec | ||||
|         ) | ||||
|  | ||||
|         result = 0 | ||||
|         if parsed_args.no_property: | ||||
|             try: | ||||
|                 key_list = list(qos_spec._info['specs'].keys()) | ||||
|                 volume_client.qos_specs.unset_keys(qos_spec.id, key_list) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to clean qos properties: %s"), e) | ||||
|                 result += 1 | ||||
|  | ||||
|         if parsed_args.property: | ||||
|             try: | ||||
|                 volume_client.qos_specs.set_keys( | ||||
|                     qos_spec.id, | ||||
|                     parsed_args.property, | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to set qos property: %s"), e) | ||||
|                 result += 1 | ||||
|  | ||||
|         if result > 0: | ||||
|             raise exceptions.CommandError( | ||||
|                 _("One or more of the set operations failed") | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class ShowQos(command.ShowOne): | ||||
|     _description = _("Display QoS specification details") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'qos_spec', | ||||
|             metavar='<qos-spec>', | ||||
|             help=_('QoS specification to display (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         qos_spec = utils.find_resource( | ||||
|             volume_client.qos_specs, parsed_args.qos_spec | ||||
|         ) | ||||
|  | ||||
|         qos_associations = volume_client.qos_specs.get_associations(qos_spec) | ||||
|         if qos_associations: | ||||
|             associations = [ | ||||
|                 association.name for association in qos_associations | ||||
|             ] | ||||
|             qos_spec._info.update( | ||||
|                 {'associations': format_columns.ListColumn(associations)} | ||||
|             ) | ||||
|         qos_spec._info.update( | ||||
|             { | ||||
|                 'properties': format_columns.DictColumn( | ||||
|                     qos_spec._info.pop('specs') | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         return zip(*sorted(qos_spec._info.items())) | ||||
|  | ||||
|  | ||||
| class UnsetQos(command.Command): | ||||
|     _description = _("Unset QoS specification properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'qos_spec', | ||||
|             metavar='<qos-spec>', | ||||
|             help=_('QoS specification to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key>', | ||||
|             action='append', | ||||
|             help=_( | ||||
|                 'Property to remove from the QoS specification. ' | ||||
|                 '(repeat option to unset multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         qos_spec = utils.find_resource( | ||||
|             volume_client.qos_specs, parsed_args.qos_spec | ||||
|         ) | ||||
|  | ||||
|         if parsed_args.property: | ||||
|             volume_client.qos_specs.unset_keys( | ||||
|                 qos_spec.id, parsed_args.property | ||||
|             ) | ||||
| @@ -1,136 +0,0 @@ | ||||
| # | ||||
| #   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. | ||||
| # | ||||
|  | ||||
| """Service action implementations""" | ||||
|  | ||||
| from osc_lib.command import command | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.i18n import _ | ||||
|  | ||||
|  | ||||
| class ListService(command.Lister): | ||||
|     _description = _("List service command") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             "--host", | ||||
|             metavar="<host>", | ||||
|             help=_("List services on specified host (name only)"), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--service", | ||||
|             metavar="<service>", | ||||
|             help=_("List only specified service (name only)"), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--long", | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help=_("List additional fields in output"), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         service_client = self.app.client_manager.volume | ||||
|  | ||||
|         if parsed_args.long: | ||||
|             columns = [ | ||||
|                 "Binary", | ||||
|                 "Host", | ||||
|                 "Zone", | ||||
|                 "Status", | ||||
|                 "State", | ||||
|                 "Updated At", | ||||
|                 "Disabled Reason", | ||||
|             ] | ||||
|         else: | ||||
|             columns = [ | ||||
|                 "Binary", | ||||
|                 "Host", | ||||
|                 "Zone", | ||||
|                 "Status", | ||||
|                 "State", | ||||
|                 "Updated At", | ||||
|             ] | ||||
|  | ||||
|         data = service_client.services.list( | ||||
|             parsed_args.host, parsed_args.service | ||||
|         ) | ||||
|         return ( | ||||
|             columns, | ||||
|             ( | ||||
|                 utils.get_item_properties( | ||||
|                     s, | ||||
|                     columns, | ||||
|                 ) | ||||
|                 for s in data | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class SetService(command.Command): | ||||
|     _description = _("Set volume service properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument("host", metavar="<host>", help=_("Name of host")) | ||||
|         parser.add_argument( | ||||
|             "service", | ||||
|             metavar="<service>", | ||||
|             help=_("Name of service (Binary name)"), | ||||
|         ) | ||||
|         enabled_group = parser.add_mutually_exclusive_group() | ||||
|         enabled_group.add_argument( | ||||
|             "--enable", action="store_true", help=_("Enable volume service") | ||||
|         ) | ||||
|         enabled_group.add_argument( | ||||
|             "--disable", action="store_true", help=_("Disable volume service") | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--disable-reason", | ||||
|             metavar="<reason>", | ||||
|             help=_( | ||||
|                 "Reason for disabling the service " | ||||
|                 "(should be used with --disable option)" | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         if parsed_args.disable_reason and not parsed_args.disable: | ||||
|             msg = _( | ||||
|                 "Cannot specify option --disable-reason without " | ||||
|                 "--disable specified." | ||||
|             ) | ||||
|             raise exceptions.CommandError(msg) | ||||
|  | ||||
|         service_client = self.app.client_manager.volume | ||||
|         if parsed_args.enable: | ||||
|             service_client.services.enable( | ||||
|                 parsed_args.host, parsed_args.service | ||||
|             ) | ||||
|         if parsed_args.disable: | ||||
|             if parsed_args.disable_reason: | ||||
|                 service_client.services.disable_log_reason( | ||||
|                     parsed_args.host, | ||||
|                     parsed_args.service, | ||||
|                     parsed_args.disable_reason, | ||||
|                 ) | ||||
|             else: | ||||
|                 service_client.services.disable( | ||||
|                     parsed_args.host, parsed_args.service | ||||
|                 ) | ||||
| @@ -1,727 +0,0 @@ | ||||
| #   Copyright 2012-2013 OpenStack Foundation | ||||
| # | ||||
| #   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. | ||||
| # | ||||
|  | ||||
| """Volume v1 Volume action implementations""" | ||||
|  | ||||
| import argparse | ||||
| import functools | ||||
| import logging | ||||
|  | ||||
| from cliff import columns as cliff_columns | ||||
| from osc_lib.cli import format_columns | ||||
| from osc_lib.cli import parseractions | ||||
| from osc_lib.command import command | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.common import pagination | ||||
| from openstackclient.i18n import _ | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class AttachmentsColumn(cliff_columns.FormattableColumn): | ||||
|     """Formattable column for attachments column. | ||||
|  | ||||
|     Unlike the parent FormattableColumn class, the initializer of the | ||||
|     class takes server_cache as the second argument. | ||||
|     osc_lib.utils.get_item_properties instantiate cliff FormattableColumn | ||||
|     object with a single parameter "column value", so you need to pass | ||||
|     a partially initialized class like | ||||
|     ``functools.partial(AttachmentsColumn, server_cache)``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, value, server_cache=None): | ||||
|         super().__init__(value) | ||||
|         self._server_cache = server_cache or {} | ||||
|  | ||||
|     def human_readable(self): | ||||
|         """Return a formatted string of a volume's attached instances | ||||
|  | ||||
|         :rtype: a string of formatted instances | ||||
|         """ | ||||
|  | ||||
|         msg = '' | ||||
|         for attachment in self._value: | ||||
|             server = attachment['server_id'] | ||||
|             if server in self._server_cache.keys(): | ||||
|                 server = self._server_cache[server].name | ||||
|             device = attachment['device'] | ||||
|             msg += f'Attached to {server} on {device} ' | ||||
|         return msg | ||||
|  | ||||
|  | ||||
| def _check_size_arg(args): | ||||
|     """Check whether --size option is required or not. | ||||
|  | ||||
|     Require size parameter only in case when snapshot or source | ||||
|     volume is not specified. | ||||
|     """ | ||||
|  | ||||
|     if (args.snapshot or args.source) is None and args.size is None: | ||||
|         msg = _( | ||||
|             "--size is a required option if snapshot " | ||||
|             "or source volume is not specified." | ||||
|         ) | ||||
|         raise exceptions.CommandError(msg) | ||||
|  | ||||
|  | ||||
| class CreateVolume(command.ShowOne): | ||||
|     _description = _("Create new volume") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'name', | ||||
|             metavar='<name>', | ||||
|             help=_('Volume name'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--size', | ||||
|             metavar='<size>', | ||||
|             type=int, | ||||
|             help=_( | ||||
|                 "Volume size in GB (Required unless --snapshot or " | ||||
|                 "--source is specified)" | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--type', | ||||
|             metavar='<volume-type>', | ||||
|             help=_("Set the type of volume"), | ||||
|         ) | ||||
|         source_group = parser.add_mutually_exclusive_group() | ||||
|         source_group.add_argument( | ||||
|             '--image', | ||||
|             metavar='<image>', | ||||
|             help=_('Use <image> as source of volume (name or ID)'), | ||||
|         ) | ||||
|         source_group.add_argument( | ||||
|             '--snapshot', | ||||
|             metavar='<snapshot>', | ||||
|             help=_('Use <snapshot> as source of volume (name or ID)'), | ||||
|         ) | ||||
|         source_group.add_argument( | ||||
|             '--snapshot-id', | ||||
|             metavar='<snapshot-id>', | ||||
|             help=argparse.SUPPRESS, | ||||
|         ) | ||||
|         source_group.add_argument( | ||||
|             '--source', | ||||
|             metavar='<volume>', | ||||
|             help=_('Volume to clone (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--description', | ||||
|             metavar='<description>', | ||||
|             help=_('Volume description'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--user', | ||||
|             metavar='<user>', | ||||
|             help=_('Specify an alternate user (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--project', | ||||
|             metavar='<project>', | ||||
|             help=_('Specify an alternate project (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--availability-zone', | ||||
|             metavar='<availability-zone>', | ||||
|             help=_('Create volume in <availability-zone>'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key=value>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_( | ||||
|                 'Set a property on this volume ' | ||||
|                 '(repeat option to set multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         bootable_group = parser.add_mutually_exclusive_group() | ||||
|         bootable_group.add_argument( | ||||
|             "--bootable", | ||||
|             action="store_true", | ||||
|             help=_("Mark volume as bootable"), | ||||
|         ) | ||||
|         bootable_group.add_argument( | ||||
|             "--non-bootable", | ||||
|             action="store_true", | ||||
|             help=_("Mark volume as non-bootable (default)"), | ||||
|         ) | ||||
|         readonly_group = parser.add_mutually_exclusive_group() | ||||
|         readonly_group.add_argument( | ||||
|             "--read-only", | ||||
|             action="store_true", | ||||
|             help=_("Set volume to read-only access mode"), | ||||
|         ) | ||||
|         readonly_group.add_argument( | ||||
|             "--read-write", | ||||
|             action="store_true", | ||||
|             help=_("Set volume to read-write access mode (default)"), | ||||
|         ) | ||||
|  | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         _check_size_arg(parsed_args) | ||||
|         identity_client = self.app.client_manager.identity | ||||
|         image_client = self.app.client_manager.image | ||||
|         volume_client = self.app.client_manager.volume | ||||
|  | ||||
|         source_volume = None | ||||
|         if parsed_args.source: | ||||
|             source_volume = utils.find_resource( | ||||
|                 volume_client.volumes, | ||||
|                 parsed_args.source, | ||||
|             ).id | ||||
|  | ||||
|         project = None | ||||
|         if parsed_args.project: | ||||
|             project = utils.find_resource( | ||||
|                 identity_client.tenants, | ||||
|                 parsed_args.project, | ||||
|             ).id | ||||
|  | ||||
|         user = None | ||||
|         if parsed_args.user: | ||||
|             user = utils.find_resource( | ||||
|                 identity_client.users, | ||||
|                 parsed_args.user, | ||||
|             ).id | ||||
|  | ||||
|         image = None | ||||
|         if parsed_args.image: | ||||
|             image = image_client.find_image( | ||||
|                 parsed_args.image, | ||||
|                 ignore_missing=False, | ||||
|             ).id | ||||
|  | ||||
|         snapshot = parsed_args.snapshot or parsed_args.snapshot_id | ||||
|  | ||||
|         volume = volume_client.volumes.create( | ||||
|             parsed_args.size, | ||||
|             snapshot, | ||||
|             source_volume, | ||||
|             parsed_args.name, | ||||
|             parsed_args.description, | ||||
|             parsed_args.type, | ||||
|             user, | ||||
|             project, | ||||
|             parsed_args.availability_zone, | ||||
|             parsed_args.property, | ||||
|             image, | ||||
|         ) | ||||
|  | ||||
|         if parsed_args.bootable or parsed_args.non_bootable: | ||||
|             try: | ||||
|                 if utils.wait_for_status( | ||||
|                     volume_client.volumes.get, | ||||
|                     volume.id, | ||||
|                     success_status=['available'], | ||||
|                     error_status=['error'], | ||||
|                     sleep_time=1, | ||||
|                 ): | ||||
|                     volume_client.volumes.set_bootable( | ||||
|                         volume.id, parsed_args.bootable | ||||
|                     ) | ||||
|                 else: | ||||
|                     msg = _( | ||||
|                         "Volume status is not available for setting boot state" | ||||
|                     ) | ||||
|                     raise exceptions.CommandError(msg) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to set volume bootable property: %s"), e) | ||||
|         if parsed_args.read_only or parsed_args.read_write: | ||||
|             try: | ||||
|                 if utils.wait_for_status( | ||||
|                     volume_client.volumes.get, | ||||
|                     volume.id, | ||||
|                     success_status=['available'], | ||||
|                     error_status=['error'], | ||||
|                     sleep_time=1, | ||||
|                 ): | ||||
|                     volume_client.volumes.update_readonly_flag( | ||||
|                         volume.id, parsed_args.read_only | ||||
|                     ) | ||||
|                 else: | ||||
|                     msg = _( | ||||
|                         "Volume status is not available for setting it" | ||||
|                         "read only." | ||||
|                     ) | ||||
|                     raise exceptions.CommandError(msg) | ||||
|             except Exception as e: | ||||
|                 LOG.error( | ||||
|                     _("Failed to set volume read-only access mode flag: %s"), | ||||
|                     e, | ||||
|                 ) | ||||
|  | ||||
|         # Map 'metadata' column to 'properties' | ||||
|         volume._info.update( | ||||
|             { | ||||
|                 'properties': format_columns.DictColumn( | ||||
|                     volume._info.pop('metadata') | ||||
|                 ), | ||||
|                 'type': volume._info.pop('volume_type'), | ||||
|             }, | ||||
|         ) | ||||
|         # Replace "display_name" by "name", keep consistent in v1 and v2 | ||||
|         if 'display_name' in volume._info: | ||||
|             volume._info.update({'name': volume._info.pop('display_name')}) | ||||
|         volume_info = utils.backward_compat_col_showone( | ||||
|             volume._info, parsed_args.columns, {'display_name': 'name'} | ||||
|         ) | ||||
|  | ||||
|         return zip(*sorted(volume_info.items())) | ||||
|  | ||||
|  | ||||
| class DeleteVolume(command.Command): | ||||
|     _description = _("Delete volume(s)") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volumes', | ||||
|             metavar='<volume>', | ||||
|             nargs="+", | ||||
|             help=_('Volume(s) to delete (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--force', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_( | ||||
|                 'Attempt forced removal of volume(s), regardless of state ' | ||||
|                 '(defaults to False)' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         result = 0 | ||||
|  | ||||
|         for i in parsed_args.volumes: | ||||
|             try: | ||||
|                 volume_obj = utils.find_resource(volume_client.volumes, i) | ||||
|                 if parsed_args.force: | ||||
|                     volume_client.volumes.force_delete(volume_obj.id) | ||||
|                 else: | ||||
|                     volume_client.volumes.delete(volume_obj.id) | ||||
|             except Exception as e: | ||||
|                 result += 1 | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to delete volume with " | ||||
|                         "name or ID '%(volume)s': %(e)s" | ||||
|                     ), | ||||
|                     {'volume': i, 'e': e}, | ||||
|                 ) | ||||
|  | ||||
|         if result > 0: | ||||
|             total = len(parsed_args.volumes) | ||||
|             msg = _("%(result)s of %(total)s volumes failed to delete.") % { | ||||
|                 'result': result, | ||||
|                 'total': total, | ||||
|             } | ||||
|             raise exceptions.CommandError(msg) | ||||
|  | ||||
|  | ||||
| class ListVolume(command.Lister): | ||||
|     _description = _("List volumes") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--name', | ||||
|             metavar='<name>', | ||||
|             help=_('Filter results by volume name'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--status', | ||||
|             metavar='<status>', | ||||
|             help=_('Filter results by status'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--all-projects', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_('Include all projects (admin only)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--long', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_('List additional fields in output'), | ||||
|         ) | ||||
|         pagination.add_offset_pagination_option_to_parser(parser) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|  | ||||
|         if parsed_args.long: | ||||
|             columns: tuple[str, ...] = ( | ||||
|                 'ID', | ||||
|                 'Display Name', | ||||
|                 'Status', | ||||
|                 'Size', | ||||
|                 'Volume Type', | ||||
|                 'Bootable', | ||||
|                 'Attachments', | ||||
|                 'Metadata', | ||||
|             ) | ||||
|             column_headers: tuple[str, ...] = ( | ||||
|                 'ID', | ||||
|                 'Name', | ||||
|                 'Status', | ||||
|                 'Size', | ||||
|                 'Type', | ||||
|                 'Bootable', | ||||
|                 'Attached to', | ||||
|                 'Properties', | ||||
|             ) | ||||
|         else: | ||||
|             columns = ( | ||||
|                 'ID', | ||||
|                 'Display Name', | ||||
|                 'Status', | ||||
|                 'Size', | ||||
|                 'Attachments', | ||||
|             ) | ||||
|             column_headers = ( | ||||
|                 'ID', | ||||
|                 'Name', | ||||
|                 'Status', | ||||
|                 'Size', | ||||
|                 'Attached to', | ||||
|             ) | ||||
|  | ||||
|         # Cache the server list | ||||
|         server_cache = {} | ||||
|         try: | ||||
|             compute_client = self.app.client_manager.sdk_connection.compute | ||||
|             for s in compute_client.servers(): | ||||
|                 server_cache[s.id] = s | ||||
|         except Exception:  # noqa: S110 | ||||
|             # Just forget it if there's any trouble | ||||
|             pass | ||||
|         AttachmentsColumnWithCache = functools.partial( | ||||
|             AttachmentsColumn, server_cache=server_cache | ||||
|         ) | ||||
|  | ||||
|         search_opts = { | ||||
|             'all_tenants': parsed_args.all_projects, | ||||
|             'display_name': parsed_args.name, | ||||
|             'status': parsed_args.status, | ||||
|         } | ||||
|  | ||||
|         if parsed_args.offset: | ||||
|             search_opts['offset'] = parsed_args.offset | ||||
|  | ||||
|         data = volume_client.volumes.list( | ||||
|             search_opts=search_opts, | ||||
|             limit=parsed_args.limit, | ||||
|         ) | ||||
|         column_headers = utils.backward_compat_col_lister( | ||||
|             column_headers, parsed_args.columns, {'Display Name': 'Name'} | ||||
|         ) | ||||
|  | ||||
|         return ( | ||||
|             column_headers, | ||||
|             ( | ||||
|                 utils.get_item_properties( | ||||
|                     s, | ||||
|                     columns, | ||||
|                     formatters={ | ||||
|                         'Metadata': format_columns.DictColumn, | ||||
|                         'Attachments': AttachmentsColumnWithCache, | ||||
|                     }, | ||||
|                 ) | ||||
|                 for s in data | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class MigrateVolume(command.Command): | ||||
|     _description = _("Migrate volume to a new host") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volume', | ||||
|             metavar="<volume>", | ||||
|             help=_("Volume to migrate (name or ID)"), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--host', | ||||
|             metavar="<host>", | ||||
|             required=True, | ||||
|             help=_( | ||||
|                 "Destination host (takes the form: host@backend-name#pool)" | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--force-host-copy', | ||||
|             action="store_true", | ||||
|             help=_( | ||||
|                 "Enable generic host-based force-migration, " | ||||
|                 "which bypasses driver optimizations" | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume = utils.find_resource(volume_client.volumes, parsed_args.volume) | ||||
|         volume_client.volumes.migrate_volume( | ||||
|             volume.id, | ||||
|             parsed_args.host, | ||||
|             parsed_args.force_host_copy, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class SetVolume(command.Command): | ||||
|     _description = _("Set volume properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volume', | ||||
|             metavar='<volume>', | ||||
|             help=_('Volume to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--name', | ||||
|             metavar='<name>', | ||||
|             help=_('New volume name'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--description', | ||||
|             metavar='<description>', | ||||
|             help=_('New volume description'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--size', | ||||
|             metavar='<size>', | ||||
|             type=int, | ||||
|             help=_('Extend volume size in GB'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--no-property", | ||||
|             dest="no_property", | ||||
|             action="store_true", | ||||
|             help=_( | ||||
|                 "Remove all properties from <volume> " | ||||
|                 "(specify both --no-property and --property to " | ||||
|                 "remove the current properties before setting " | ||||
|                 "new properties.)" | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key=value>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_( | ||||
|                 'Set a property on this volume ' | ||||
|                 '(repeat option to set multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         bootable_group = parser.add_mutually_exclusive_group() | ||||
|         bootable_group.add_argument( | ||||
|             "--bootable", | ||||
|             action="store_true", | ||||
|             help=_("Mark volume as bootable"), | ||||
|         ) | ||||
|         bootable_group.add_argument( | ||||
|             "--non-bootable", | ||||
|             action="store_true", | ||||
|             help=_("Mark volume as non-bootable"), | ||||
|         ) | ||||
|         readonly_group = parser.add_mutually_exclusive_group() | ||||
|         readonly_group.add_argument( | ||||
|             "--read-only", | ||||
|             action="store_true", | ||||
|             help=_("Set volume to read-only access mode"), | ||||
|         ) | ||||
|         readonly_group.add_argument( | ||||
|             "--read-write", | ||||
|             action="store_true", | ||||
|             help=_("Set volume to read-write access mode"), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume = utils.find_resource(volume_client.volumes, parsed_args.volume) | ||||
|  | ||||
|         result = 0 | ||||
|         if parsed_args.size: | ||||
|             try: | ||||
|                 if volume.status != 'available': | ||||
|                     msg = ( | ||||
|                         _( | ||||
|                             "Volume is in %s state, it must be available " | ||||
|                             "before size can be extended" | ||||
|                         ) | ||||
|                         % volume.status | ||||
|                     ) | ||||
|                     raise exceptions.CommandError(msg) | ||||
|                 if parsed_args.size <= volume.size: | ||||
|                     msg = ( | ||||
|                         _("New size must be greater than %s GB") % volume.size | ||||
|                     ) | ||||
|                     raise exceptions.CommandError(msg) | ||||
|                 volume_client.volumes.extend(volume.id, parsed_args.size) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to set volume size: %s"), e) | ||||
|                 result += 1 | ||||
|  | ||||
|         if parsed_args.no_property: | ||||
|             try: | ||||
|                 volume_client.volumes.delete_metadata( | ||||
|                     volume.id, volume.metadata.keys() | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to clean volume properties: %s"), e) | ||||
|                 result += 1 | ||||
|  | ||||
|         if parsed_args.property: | ||||
|             try: | ||||
|                 volume_client.volumes.set_metadata( | ||||
|                     volume.id, parsed_args.property | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to set volume property: %s"), e) | ||||
|                 result += 1 | ||||
|         if parsed_args.bootable or parsed_args.non_bootable: | ||||
|             try: | ||||
|                 volume_client.volumes.set_bootable( | ||||
|                     volume.id, parsed_args.bootable | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to set volume bootable property: %s"), e) | ||||
|                 result += 1 | ||||
|         if parsed_args.read_only or parsed_args.read_write: | ||||
|             try: | ||||
|                 volume_client.volumes.update_readonly_flag( | ||||
|                     volume.id, parsed_args.read_only | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error( | ||||
|                     _("Failed to set volume read-only access mode flag: %s"), | ||||
|                     e, | ||||
|                 ) | ||||
|                 result += 1 | ||||
|         kwargs = {} | ||||
|         if parsed_args.name: | ||||
|             kwargs['display_name'] = parsed_args.name | ||||
|         if parsed_args.description: | ||||
|             kwargs['display_description'] = parsed_args.description | ||||
|         if kwargs: | ||||
|             try: | ||||
|                 volume_client.volumes.update(volume.id, **kwargs) | ||||
|             except Exception as e: | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to update volume display name " | ||||
|                         "or display description: %s" | ||||
|                     ), | ||||
|                     e, | ||||
|                 ) | ||||
|                 result += 1 | ||||
|  | ||||
|         if result > 0: | ||||
|             raise exceptions.CommandError( | ||||
|                 _("One or more of the set operations failed") | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class ShowVolume(command.ShowOne): | ||||
|     _description = _("Show volume details") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volume', | ||||
|             metavar='<volume>', | ||||
|             help=_('Volume to display (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume = utils.find_resource(volume_client.volumes, parsed_args.volume) | ||||
|         # Map 'metadata' column to 'properties' | ||||
|         volume._info.update( | ||||
|             { | ||||
|                 'properties': format_columns.DictColumn( | ||||
|                     volume._info.pop('metadata') | ||||
|                 ), | ||||
|                 'type': volume._info.pop('volume_type'), | ||||
|             }, | ||||
|         ) | ||||
|         if 'os-vol-tenant-attr:tenant_id' in volume._info: | ||||
|             volume._info.update( | ||||
|                 { | ||||
|                     'project_id': volume._info.pop( | ||||
|                         'os-vol-tenant-attr:tenant_id' | ||||
|                     ) | ||||
|                 } | ||||
|             ) | ||||
|         # Replace "display_name" by "name", keep consistent in v1 and v2 | ||||
|         if 'display_name' in volume._info: | ||||
|             volume._info.update({'name': volume._info.pop('display_name')}) | ||||
|  | ||||
|         volume_info = utils.backward_compat_col_showone( | ||||
|             volume._info, parsed_args.columns, {'display_name': 'name'} | ||||
|         ) | ||||
|  | ||||
|         return zip(*sorted(volume_info.items())) | ||||
|  | ||||
|  | ||||
| class UnsetVolume(command.Command): | ||||
|     _description = _("Unset volume properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volume', | ||||
|             metavar='<volume>', | ||||
|             help=_('Volume to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key>', | ||||
|             action='append', | ||||
|             help=_( | ||||
|                 'Remove a property from volume ' | ||||
|                 '(repeat option to remove multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume = utils.find_resource(volume_client.volumes, parsed_args.volume) | ||||
|  | ||||
|         if parsed_args.property: | ||||
|             volume_client.volumes.delete_metadata( | ||||
|                 volume.id, | ||||
|                 parsed_args.property, | ||||
|             ) | ||||
| @@ -1,301 +0,0 @@ | ||||
| #   Copyright 2012-2013 OpenStack Foundation | ||||
| # | ||||
| #   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. | ||||
| # | ||||
|  | ||||
| """Volume v1 Backup action implementations""" | ||||
|  | ||||
| import copy | ||||
| import functools | ||||
| import logging | ||||
|  | ||||
| from cliff import columns as cliff_columns | ||||
| from osc_lib.command import command | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.i18n import _ | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class VolumeIdColumn(cliff_columns.FormattableColumn): | ||||
|     """Formattable column for volume ID column. | ||||
|  | ||||
|     Unlike the parent FormattableColumn class, the initializer of the | ||||
|     class takes volume_cache as the second argument. | ||||
|     osc_lib.utils.get_item_properties instantiate cliff FormattableColumn | ||||
|     object with a single parameter "column value", so you need to pass | ||||
|     a partially initialized class like | ||||
|     ``functools.partial(VolumeIdColumn, volume_cache)``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, value, volume_cache=None): | ||||
|         super().__init__(value) | ||||
|         self._volume_cache = volume_cache or {} | ||||
|  | ||||
|     def human_readable(self): | ||||
|         """Return a volume name if available | ||||
|  | ||||
|         :rtype: either the volume ID or name | ||||
|         """ | ||||
|         volume_id = self._value | ||||
|         volume = volume_id | ||||
|         if volume_id in self._volume_cache.keys(): | ||||
|             volume = self._volume_cache[volume_id].display_name | ||||
|         return volume | ||||
|  | ||||
|  | ||||
| class CreateVolumeBackup(command.ShowOne): | ||||
|     _description = _("Create new volume backup") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volume', | ||||
|             metavar='<volume>', | ||||
|             help=_('Volume to backup (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--container', | ||||
|             metavar='<container>', | ||||
|             required=False, | ||||
|             help=_('Optional backup container name'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--name', | ||||
|             metavar='<name>', | ||||
|             help=_('Name of the backup'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--description', | ||||
|             metavar='<description>', | ||||
|             help=_('Description of the backup'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume_id = utils.find_resource( | ||||
|             volume_client.volumes, parsed_args.volume | ||||
|         ).id | ||||
|         backup = volume_client.backups.create( | ||||
|             volume_id, | ||||
|             parsed_args.container, | ||||
|             parsed_args.name, | ||||
|             parsed_args.description, | ||||
|         ) | ||||
|  | ||||
|         backup._info.pop('links') | ||||
|         return zip(*sorted(backup._info.items())) | ||||
|  | ||||
|  | ||||
| class DeleteVolumeBackup(command.Command): | ||||
|     _description = _("Delete volume backup(s)") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'backups', | ||||
|             metavar='<backup>', | ||||
|             nargs="+", | ||||
|             help=_('Backup(s) to delete (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         result = 0 | ||||
|  | ||||
|         for i in parsed_args.backups: | ||||
|             try: | ||||
|                 backup_id = utils.find_resource(volume_client.backups, i).id | ||||
|                 volume_client.backups.delete(backup_id) | ||||
|             except Exception as e: | ||||
|                 result += 1 | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to delete backup with " | ||||
|                         "name or ID '%(backup)s': %(e)s" | ||||
|                     ), | ||||
|                     {'backup': i, 'e': e}, | ||||
|                 ) | ||||
|  | ||||
|         if result > 0: | ||||
|             total = len(parsed_args.backups) | ||||
|             msg = _("%(result)s of %(total)s backups failed to delete.") % { | ||||
|                 'result': result, | ||||
|                 'total': total, | ||||
|             } | ||||
|             raise exceptions.CommandError(msg) | ||||
|  | ||||
|  | ||||
| class ListVolumeBackup(command.Lister): | ||||
|     _description = _("List volume backups") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--long', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_('List additional fields in output'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--name", | ||||
|             metavar="<name>", | ||||
|             help=_("Filters results by the backup name"), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--status", | ||||
|             metavar="<status>", | ||||
|             choices=[ | ||||
|                 'creating', | ||||
|                 'available', | ||||
|                 'deleting', | ||||
|                 'error', | ||||
|                 'restoring', | ||||
|                 'error_restoring', | ||||
|             ], | ||||
|             help=_( | ||||
|                 "Filters results by the backup status " | ||||
|                 "('creating', 'available', 'deleting', " | ||||
|                 "'error', 'restoring' or 'error_restoring')" | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--volume", | ||||
|             metavar="<volume>", | ||||
|             help=_( | ||||
|                 "Filters results by the volume which they backup (name or ID)" | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--all-projects', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_('Include all projects (admin only)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|  | ||||
|         if parsed_args.long: | ||||
|             columns = [ | ||||
|                 'ID', | ||||
|                 'Name', | ||||
|                 'Description', | ||||
|                 'Status', | ||||
|                 'Size', | ||||
|                 'Availability Zone', | ||||
|                 'Volume ID', | ||||
|                 'Container', | ||||
|             ] | ||||
|             column_headers = copy.deepcopy(columns) | ||||
|             column_headers[6] = 'Volume' | ||||
|         else: | ||||
|             columns = ['ID', 'Name', 'Description', 'Status', 'Size'] | ||||
|             column_headers = columns | ||||
|  | ||||
|         # Cache the volume list | ||||
|         volume_cache = {} | ||||
|         try: | ||||
|             for s in volume_client.volumes.list(): | ||||
|                 volume_cache[s.id] = s | ||||
|         except Exception:  # noqa: S110 | ||||
|             # Just forget it if there's any trouble | ||||
|             pass | ||||
|         VolumeIdColumnWithCache = functools.partial( | ||||
|             VolumeIdColumn, volume_cache=volume_cache | ||||
|         ) | ||||
|  | ||||
|         filter_volume_id = None | ||||
|         if parsed_args.volume: | ||||
|             filter_volume_id = utils.find_resource( | ||||
|                 volume_client.volumes, parsed_args.volume | ||||
|             ).id | ||||
|         search_opts = { | ||||
|             'name': parsed_args.name, | ||||
|             'status': parsed_args.status, | ||||
|             'volume_id': filter_volume_id, | ||||
|             'all_tenants': parsed_args.all_projects, | ||||
|         } | ||||
|         data = volume_client.backups.list( | ||||
|             search_opts=search_opts, | ||||
|         ) | ||||
|  | ||||
|         return ( | ||||
|             column_headers, | ||||
|             ( | ||||
|                 utils.get_item_properties( | ||||
|                     s, | ||||
|                     columns, | ||||
|                     formatters={'Volume ID': VolumeIdColumnWithCache}, | ||||
|                 ) | ||||
|                 for s in data | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class RestoreVolumeBackup(command.Command): | ||||
|     _description = _("Restore volume backup") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'backup', | ||||
|             metavar='<backup>', | ||||
|             help=_('Backup to restore (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             'volume', | ||||
|             metavar='<volume>', | ||||
|             nargs='?', | ||||
|             help=_('Volume to restore to (name or ID) (default to None)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         backup = utils.find_resource( | ||||
|             volume_client.backups, | ||||
|             parsed_args.backup, | ||||
|         ) | ||||
|         volume_id = None | ||||
|         if parsed_args.volume is not None: | ||||
|             volume_id = utils.find_resource( | ||||
|                 volume_client.volumes, | ||||
|                 parsed_args.volume, | ||||
|             ).id | ||||
|         return volume_client.restores.restore(backup.id, volume_id) | ||||
|  | ||||
|  | ||||
| class ShowVolumeBackup(command.ShowOne): | ||||
|     _description = _("Display volume backup details") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'backup', | ||||
|             metavar='<backup>', | ||||
|             help=_('Backup to display (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         backup = utils.find_resource(volume_client.backups, parsed_args.backup) | ||||
|         backup._info.pop('links') | ||||
|         return zip(*sorted(backup._info.items())) | ||||
| @@ -1,432 +0,0 @@ | ||||
| #   Copyright 2012-2013 OpenStack Foundation | ||||
| # | ||||
| #   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. | ||||
| # | ||||
|  | ||||
| """Volume v1 Snapshot action implementations""" | ||||
|  | ||||
| import copy | ||||
| import functools | ||||
| import logging | ||||
|  | ||||
| from cliff import columns as cliff_columns | ||||
| from osc_lib.cli import format_columns | ||||
| from osc_lib.cli import parseractions | ||||
| from osc_lib.command import command | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.i18n import _ | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class VolumeIdColumn(cliff_columns.FormattableColumn): | ||||
|     """Formattable column for volume ID column. | ||||
|  | ||||
|     Unlike the parent FormattableColumn class, the initializer of the | ||||
|     class takes volume_cache as the second argument. | ||||
|     osc_lib.utils.get_item_properties instantiate cliff FormattableColumn | ||||
|     object with a single parameter "column value", so you need to pass | ||||
|     a partially initialized class like | ||||
|     ``functools.partial(VolumeIdColumn, volume_cache)``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, value, volume_cache=None): | ||||
|         super().__init__(value) | ||||
|         self._volume_cache = volume_cache or {} | ||||
|  | ||||
|     def human_readable(self): | ||||
|         """Return a volume name if available | ||||
|  | ||||
|         :rtype: either the volume ID or name | ||||
|         """ | ||||
|         volume_id = self._value | ||||
|         volume = volume_id | ||||
|         if volume_id in self._volume_cache.keys(): | ||||
|             volume = self._volume_cache[volume_id].display_name | ||||
|         return volume | ||||
|  | ||||
|  | ||||
| class CreateVolumeSnapshot(command.ShowOne): | ||||
|     _description = _("Create new volume snapshot") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'snapshot_name', | ||||
|             metavar='<snapshot-name>', | ||||
|             help=_('Name of the new snapshot'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--volume', | ||||
|             metavar='<volume>', | ||||
|             help=_( | ||||
|                 'Volume to snapshot (name or ID) (default is <snapshot-name>)' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--description', | ||||
|             metavar='<description>', | ||||
|             help=_('Description of the snapshot'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--force', | ||||
|             dest='force', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_( | ||||
|                 'Create a snapshot attached to an instance. Default is False' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume = parsed_args.volume | ||||
|         if not parsed_args.volume: | ||||
|             volume = parsed_args.snapshot_name | ||||
|         volume_id = utils.find_resource(volume_client.volumes, volume).id | ||||
|         snapshot = volume_client.volume_snapshots.create( | ||||
|             volume_id, | ||||
|             parsed_args.force, | ||||
|             parsed_args.snapshot_name, | ||||
|             parsed_args.description, | ||||
|         ) | ||||
|  | ||||
|         snapshot._info.update( | ||||
|             { | ||||
|                 'properties': format_columns.DictColumn( | ||||
|                     snapshot._info.pop('metadata') | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         return zip(*sorted(snapshot._info.items())) | ||||
|  | ||||
|  | ||||
| class DeleteVolumeSnapshot(command.Command): | ||||
|     _description = _("Delete volume snapshot(s)") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'snapshots', | ||||
|             metavar='<snapshot>', | ||||
|             nargs="+", | ||||
|             help=_('Snapshot(s) to delete (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         result = 0 | ||||
|  | ||||
|         for i in parsed_args.snapshots: | ||||
|             try: | ||||
|                 snapshot_id = utils.find_resource( | ||||
|                     volume_client.volume_snapshots, i | ||||
|                 ).id | ||||
|                 volume_client.volume_snapshots.delete(snapshot_id) | ||||
|             except Exception as e: | ||||
|                 result += 1 | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to delete snapshot with " | ||||
|                         "name or ID '%(snapshot)s': %(e)s" | ||||
|                     ), | ||||
|                     {'snapshot': i, 'e': e}, | ||||
|                 ) | ||||
|  | ||||
|         if result > 0: | ||||
|             total = len(parsed_args.snapshots) | ||||
|             msg = _("%(result)s of %(total)s snapshots failed to delete.") % { | ||||
|                 'result': result, | ||||
|                 'total': total, | ||||
|             } | ||||
|             raise exceptions.CommandError(msg) | ||||
|  | ||||
|  | ||||
| class ListVolumeSnapshot(command.Lister): | ||||
|     _description = _("List volume snapshots") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--all-projects', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_('Include all projects (admin only)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--long', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_('List additional fields in output'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--name', | ||||
|             metavar='<name>', | ||||
|             default=None, | ||||
|             help=_('Filters results by a name.'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--status', | ||||
|             metavar='<status>', | ||||
|             choices=[ | ||||
|                 'available', | ||||
|                 'error', | ||||
|                 'creating', | ||||
|                 'deleting', | ||||
|                 'error_deleting', | ||||
|             ], | ||||
|             help=_( | ||||
|                 "Filters results by a status. " | ||||
|                 "('available', 'error', 'creating', 'deleting'" | ||||
|                 " or 'error_deleting')" | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--volume', | ||||
|             metavar='<volume>', | ||||
|             default=None, | ||||
|             help=_('Filters results by a volume (name or ID).'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|  | ||||
|         if parsed_args.long: | ||||
|             columns = [ | ||||
|                 'ID', | ||||
|                 'Display Name', | ||||
|                 'Display Description', | ||||
|                 'Status', | ||||
|                 'Size', | ||||
|                 'Created At', | ||||
|                 'Volume ID', | ||||
|                 'Metadata', | ||||
|             ] | ||||
|             column_headers = copy.deepcopy(columns) | ||||
|             column_headers[6] = 'Volume' | ||||
|             column_headers[7] = 'Properties' | ||||
|         else: | ||||
|             columns = [ | ||||
|                 'ID', | ||||
|                 'Display Name', | ||||
|                 'Display Description', | ||||
|                 'Status', | ||||
|                 'Size', | ||||
|             ] | ||||
|             column_headers = copy.deepcopy(columns) | ||||
|  | ||||
|         # Always update Name and Description | ||||
|         column_headers[1] = 'Name' | ||||
|         column_headers[2] = 'Description' | ||||
|  | ||||
|         # Cache the volume list | ||||
|         volume_cache = {} | ||||
|         try: | ||||
|             for s in volume_client.volumes.list(): | ||||
|                 volume_cache[s.id] = s | ||||
|         except Exception:  # noqa: S110 | ||||
|             # Just forget it if there's any trouble | ||||
|             pass | ||||
|         VolumeIdColumnWithCache = functools.partial( | ||||
|             VolumeIdColumn, volume_cache=volume_cache | ||||
|         ) | ||||
|  | ||||
|         volume_id = None | ||||
|         if parsed_args.volume: | ||||
|             volume_id = utils.find_resource( | ||||
|                 volume_client.volumes, parsed_args.volume | ||||
|             ).id | ||||
|  | ||||
|         search_opts = { | ||||
|             'all_tenants': parsed_args.all_projects, | ||||
|             'display_name': parsed_args.name, | ||||
|             'status': parsed_args.status, | ||||
|             'volume_id': volume_id, | ||||
|         } | ||||
|  | ||||
|         data = volume_client.volume_snapshots.list(search_opts=search_opts) | ||||
|         return ( | ||||
|             column_headers, | ||||
|             ( | ||||
|                 utils.get_item_properties( | ||||
|                     s, | ||||
|                     columns, | ||||
|                     formatters={ | ||||
|                         'Metadata': format_columns.DictColumn, | ||||
|                         'Volume ID': VolumeIdColumnWithCache, | ||||
|                     }, | ||||
|                 ) | ||||
|                 for s in data | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class SetVolumeSnapshot(command.Command): | ||||
|     _description = _("Set volume snapshot properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'snapshot', | ||||
|             metavar='<snapshot>', | ||||
|             help=_('Snapshot to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--name', metavar='<name>', help=_('New snapshot name') | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--description', | ||||
|             metavar='<description>', | ||||
|             help=_('New snapshot description'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--no-property", | ||||
|             dest="no_property", | ||||
|             action="store_true", | ||||
|             help=_( | ||||
|                 "Remove all properties from <snapshot> " | ||||
|                 "(specify both --no-property and --property to " | ||||
|                 "remove the current properties before setting " | ||||
|                 "new properties.)" | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key=value>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_( | ||||
|                 'Property to add/change for this snapshot ' | ||||
|                 '(repeat option to set multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         snapshot = utils.find_resource( | ||||
|             volume_client.volume_snapshots, parsed_args.snapshot | ||||
|         ) | ||||
|  | ||||
|         result = 0 | ||||
|         if parsed_args.no_property: | ||||
|             try: | ||||
|                 key_list = snapshot.metadata.keys() | ||||
|                 volume_client.volume_snapshots.delete_metadata( | ||||
|                     snapshot.id, | ||||
|                     list(key_list), | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to clean snapshot properties: %s"), e) | ||||
|                 result += 1 | ||||
|  | ||||
|         if parsed_args.property: | ||||
|             try: | ||||
|                 volume_client.volume_snapshots.set_metadata( | ||||
|                     snapshot.id, parsed_args.property | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to set snapshot property: %s"), e) | ||||
|                 result += 1 | ||||
|  | ||||
|         kwargs = {} | ||||
|         if parsed_args.name: | ||||
|             kwargs['display_name'] = parsed_args.name | ||||
|         if parsed_args.description: | ||||
|             kwargs['display_description'] = parsed_args.description | ||||
|         if kwargs: | ||||
|             try: | ||||
|                 snapshot.update(**kwargs) | ||||
|             except Exception as e: | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to update snapshot display name " | ||||
|                         "or display description: %s" | ||||
|                     ), | ||||
|                     e, | ||||
|                 ) | ||||
|                 result += 1 | ||||
|  | ||||
|         if result > 0: | ||||
|             raise exceptions.CommandError( | ||||
|                 _("One or more of the set operations failed") | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class ShowVolumeSnapshot(command.ShowOne): | ||||
|     _description = _("Display volume snapshot details") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'snapshot', | ||||
|             metavar='<snapshot>', | ||||
|             help=_('Snapshot to display (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         snapshot = utils.find_resource( | ||||
|             volume_client.volume_snapshots, parsed_args.snapshot | ||||
|         ) | ||||
|  | ||||
|         snapshot._info.update( | ||||
|             { | ||||
|                 'properties': format_columns.DictColumn( | ||||
|                     snapshot._info.pop('metadata') | ||||
|                 ) | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|         return zip(*sorted(snapshot._info.items())) | ||||
|  | ||||
|  | ||||
| class UnsetVolumeSnapshot(command.Command): | ||||
|     _description = _("Unset volume snapshot properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'snapshot', | ||||
|             metavar='<snapshot>', | ||||
|             help=_('Snapshot to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key>', | ||||
|             action='append', | ||||
|             help=_( | ||||
|                 'Property to remove from snapshot ' | ||||
|                 '(repeat option to remove multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         snapshot = utils.find_resource( | ||||
|             volume_client.volume_snapshots, parsed_args.snapshot | ||||
|         ) | ||||
|  | ||||
|         if parsed_args.property: | ||||
|             volume_client.volume_snapshots.delete_metadata( | ||||
|                 snapshot.id, | ||||
|                 parsed_args.property, | ||||
|             ) | ||||
| @@ -1,200 +0,0 @@ | ||||
| # | ||||
| #   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. | ||||
| # | ||||
|  | ||||
| """Volume v1 transfer action implementations""" | ||||
|  | ||||
| import logging | ||||
|  | ||||
| from osc_lib.command import command | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.i18n import _ | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class AcceptTransferRequest(command.ShowOne): | ||||
|     _description = _("Accept volume transfer request.") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'transfer_request', | ||||
|             metavar="<transfer-request-id>", | ||||
|             help=_('Volume transfer request to accept (ID only)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--auth-key', | ||||
|             metavar="<key>", | ||||
|             help=_('Volume transfer request authentication key'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|  | ||||
|         try: | ||||
|             transfer_request_id = utils.find_resource( | ||||
|                 volume_client.transfers, parsed_args.transfer_request | ||||
|             ).id | ||||
|         except exceptions.CommandError: | ||||
|             # Non-admin users will fail to lookup name -> ID so we just | ||||
|             # move on and attempt with the user-supplied information | ||||
|             transfer_request_id = parsed_args.transfer_request | ||||
|  | ||||
|         if not parsed_args.auth_key: | ||||
|             msg = _("argument --auth-key is required") | ||||
|             raise exceptions.CommandError(msg) | ||||
|  | ||||
|         transfer_accept = volume_client.transfers.accept( | ||||
|             transfer_request_id, | ||||
|             parsed_args.auth_key, | ||||
|         ) | ||||
|         transfer_accept._info.pop("links", None) | ||||
|  | ||||
|         return zip(*sorted(transfer_accept._info.items())) | ||||
|  | ||||
|  | ||||
| class CreateTransferRequest(command.ShowOne): | ||||
|     _description = _("Create volume transfer request.") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--name', | ||||
|             metavar="<name>", | ||||
|             help=_('New transfer request name (default to None)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             'volume', | ||||
|             metavar="<volume>", | ||||
|             help=_('Volume to transfer (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume_id = utils.find_resource( | ||||
|             volume_client.volumes, | ||||
|             parsed_args.volume, | ||||
|         ).id | ||||
|         volume_transfer_request = volume_client.transfers.create( | ||||
|             volume_id, | ||||
|             parsed_args.name, | ||||
|         ) | ||||
|         volume_transfer_request._info.pop("links", None) | ||||
|  | ||||
|         return zip(*sorted(volume_transfer_request._info.items())) | ||||
|  | ||||
|  | ||||
| class DeleteTransferRequest(command.Command): | ||||
|     _description = _("Delete volume transfer request(s).") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'transfer_request', | ||||
|             metavar="<transfer-request>", | ||||
|             nargs="+", | ||||
|             help=_('Volume transfer request(s) to delete (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         result = 0 | ||||
|  | ||||
|         for t in parsed_args.transfer_request: | ||||
|             try: | ||||
|                 transfer_request_id = utils.find_resource( | ||||
|                     volume_client.transfers, | ||||
|                     t, | ||||
|                 ).id | ||||
|                 volume_client.transfers.delete(transfer_request_id) | ||||
|             except Exception as e: | ||||
|                 result += 1 | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to delete volume transfer request " | ||||
|                         "with name or ID '%(transfer)s': %(e)s" | ||||
|                     ) | ||||
|                     % {'transfer': t, 'e': e} | ||||
|                 ) | ||||
|  | ||||
|         if result > 0: | ||||
|             total = len(parsed_args.transfer_request) | ||||
|             msg = _( | ||||
|                 "%(result)s of %(total)s volume transfer requests failed" | ||||
|                 " to delete" | ||||
|             ) % {'result': result, 'total': total} | ||||
|             raise exceptions.CommandError(msg) | ||||
|  | ||||
|  | ||||
| class ListTransferRequest(command.Lister): | ||||
|     _description = _("Lists all volume transfer requests.") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--all-projects', | ||||
|             dest='all_projects', | ||||
|             action="store_true", | ||||
|             default=False, | ||||
|             help=_('Include all projects (admin only)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         columns = ['ID', 'Name', 'Volume ID'] | ||||
|         column_headers = ['ID', 'Name', 'Volume'] | ||||
|  | ||||
|         volume_client = self.app.client_manager.volume | ||||
|  | ||||
|         volume_transfer_result = volume_client.transfers.list( | ||||
|             detailed=True, | ||||
|             search_opts={'all_tenants': parsed_args.all_projects}, | ||||
|         ) | ||||
|  | ||||
|         return ( | ||||
|             column_headers, | ||||
|             ( | ||||
|                 utils.get_item_properties(s, columns) | ||||
|                 for s in volume_transfer_result | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class ShowTransferRequest(command.ShowOne): | ||||
|     _description = _("Show volume transfer request details.") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'transfer_request', | ||||
|             metavar="<transfer-request>", | ||||
|             help=_('Volume transfer request to display (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume_transfer_request = utils.find_resource( | ||||
|             volume_client.transfers, | ||||
|             parsed_args.transfer_request, | ||||
|         ) | ||||
|         volume_transfer_request._info.pop("links", None) | ||||
|  | ||||
|         return zip(*sorted(volume_transfer_request._info.items())) | ||||
| @@ -1,519 +0,0 @@ | ||||
| #   Copyright 2012-2013 OpenStack Foundation | ||||
| # | ||||
| #   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. | ||||
| # | ||||
|  | ||||
| """Volume v1 Type action implementations""" | ||||
|  | ||||
| import functools | ||||
| import logging | ||||
|  | ||||
| from cliff import columns as cliff_columns | ||||
| from osc_lib.cli import format_columns | ||||
| from osc_lib.cli import parseractions | ||||
| from osc_lib.command import command | ||||
| from osc_lib import exceptions | ||||
| from osc_lib import utils | ||||
|  | ||||
| from openstackclient.i18n import _ | ||||
|  | ||||
|  | ||||
| LOG = logging.getLogger(__name__) | ||||
|  | ||||
|  | ||||
| class EncryptionInfoColumn(cliff_columns.FormattableColumn): | ||||
|     """Formattable column for encryption info column. | ||||
|  | ||||
|     Unlike the parent FormattableColumn class, the initializer of the | ||||
|     class takes encryption_data as the second argument. | ||||
|     osc_lib.utils.get_item_properties instantiate cliff FormattableColumn | ||||
|     object with a single parameter "column value", so you need to pass | ||||
|     a partially initialized class like | ||||
|     ``functools.partial(EncryptionInfoColumn encryption_data)``. | ||||
|     """ | ||||
|  | ||||
|     def __init__(self, value, encryption_data=None): | ||||
|         super().__init__(value) | ||||
|         self._encryption_data = encryption_data or {} | ||||
|  | ||||
|     def _get_encryption_info(self): | ||||
|         type_id = self._value | ||||
|         return self._encryption_data.get(type_id) | ||||
|  | ||||
|     def human_readable(self): | ||||
|         encryption_info = self._get_encryption_info() | ||||
|         if encryption_info: | ||||
|             return utils.format_dict(encryption_info) | ||||
|         else: | ||||
|             return '-' | ||||
|  | ||||
|     def machine_readable(self): | ||||
|         return self._get_encryption_info() | ||||
|  | ||||
|  | ||||
| def _create_encryption_type(volume_client, volume_type, parsed_args): | ||||
|     if not parsed_args.encryption_provider: | ||||
|         msg = _( | ||||
|             "'--encryption-provider' should be specified while " | ||||
|             "creating a new encryption type" | ||||
|         ) | ||||
|         raise exceptions.CommandError(msg) | ||||
|     # set the default of control location while creating | ||||
|     control_location = 'front-end' | ||||
|     if parsed_args.encryption_control_location: | ||||
|         control_location = parsed_args.encryption_control_location | ||||
|     body = { | ||||
|         'provider': parsed_args.encryption_provider, | ||||
|         'cipher': parsed_args.encryption_cipher, | ||||
|         'key_size': parsed_args.encryption_key_size, | ||||
|         'control_location': control_location, | ||||
|     } | ||||
|     encryption = volume_client.volume_encryption_types.create( | ||||
|         volume_type, body | ||||
|     ) | ||||
|     return encryption | ||||
|  | ||||
|  | ||||
| class CreateVolumeType(command.ShowOne): | ||||
|     _description = _("Create new volume type") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'name', | ||||
|             metavar='<name>', | ||||
|             help=_('Volume type name'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key=value>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_( | ||||
|                 'Set a property on this volume type ' | ||||
|                 '(repeat option to set multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. | ||||
|         parser.add_argument( | ||||
|             '--encryption-provider', | ||||
|             metavar='<provider>', | ||||
|             help=_( | ||||
|                 'Set the encryption provider format for ' | ||||
|                 'this volume type (e.g "luks" or "plain") (admin only) ' | ||||
|                 '(This option is required when setting encryption type ' | ||||
|                 'of a volume. Consider using other encryption options ' | ||||
|                 'such as: "--encryption-cipher", "--encryption-key-size" ' | ||||
|                 'and "--encryption-control-location")' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--encryption-cipher', | ||||
|             metavar='<cipher>', | ||||
|             help=_( | ||||
|                 'Set the encryption algorithm or mode for this ' | ||||
|                 'volume type (e.g "aes-xts-plain64") (admin only)' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--encryption-key-size', | ||||
|             metavar='<key-size>', | ||||
|             type=int, | ||||
|             help=_( | ||||
|                 'Set the size of the encryption key of this ' | ||||
|                 'volume type (e.g "128" or "256") (admin only)' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--encryption-control-location', | ||||
|             metavar='<control-location>', | ||||
|             choices=['front-end', 'back-end'], | ||||
|             help=_( | ||||
|                 'Set the notional service where the encryption is ' | ||||
|                 'performed ("front-end" or "back-end") (admin only) ' | ||||
|                 '(The default value for this option is "front-end" ' | ||||
|                 'when setting encryption type of a volume. Consider ' | ||||
|                 'using other encryption options such as: ' | ||||
|                 '"--encryption-cipher", "--encryption-key-size" and ' | ||||
|                 '"--encryption-provider")' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume_type = volume_client.volume_types.create(parsed_args.name) | ||||
|         volume_type._info.pop('extra_specs') | ||||
|         if parsed_args.property: | ||||
|             result = volume_type.set_keys(parsed_args.property) | ||||
|             volume_type._info.update( | ||||
|                 {'properties': format_columns.DictColumn(result)} | ||||
|             ) | ||||
|         if ( | ||||
|             parsed_args.encryption_provider | ||||
|             or parsed_args.encryption_cipher | ||||
|             or parsed_args.encryption_key_size | ||||
|             or parsed_args.encryption_control_location | ||||
|         ): | ||||
|             try: | ||||
|                 # create new encryption | ||||
|                 encryption = _create_encryption_type( | ||||
|                     volume_client, volume_type, parsed_args | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to set encryption information for this " | ||||
|                         "volume type: %s" | ||||
|                     ), | ||||
|                     e, | ||||
|                 ) | ||||
|             # add encryption info in result | ||||
|             encryption._info.pop("volume_type_id", None) | ||||
|             volume_type._info.update( | ||||
|                 {'encryption': format_columns.DictColumn(encryption._info)} | ||||
|             ) | ||||
|         volume_type._info.pop("os-volume-type-access:is_public", None) | ||||
|  | ||||
|         return zip(*sorted(volume_type._info.items())) | ||||
|  | ||||
|  | ||||
| class DeleteVolumeType(command.Command): | ||||
|     _description = _("Delete volume type(s)") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volume_types', | ||||
|             metavar='<volume-type>', | ||||
|             nargs='+', | ||||
|             help=_('Volume type(s) to delete (name or ID)'), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         result = 0 | ||||
|  | ||||
|         for volume_type in parsed_args.volume_types: | ||||
|             try: | ||||
|                 vol_type = utils.find_resource( | ||||
|                     volume_client.volume_types, volume_type | ||||
|                 ) | ||||
|  | ||||
|                 volume_client.volume_types.delete(vol_type) | ||||
|             except Exception as e: | ||||
|                 result += 1 | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to delete volume type with " | ||||
|                         "name or ID '%(volume_type)s': %(e)s" | ||||
|                     ) | ||||
|                     % {'volume_type': volume_type, 'e': e} | ||||
|                 ) | ||||
|  | ||||
|         if result > 0: | ||||
|             total = len(parsed_args.volume_types) | ||||
|             msg = _( | ||||
|                 "%(result)s of %(total)s volume types failed to delete." | ||||
|             ) % {'result': result, 'total': total} | ||||
|             raise exceptions.CommandError(msg) | ||||
|  | ||||
|  | ||||
| class ListVolumeType(command.Lister): | ||||
|     _description = _("List volume types") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             '--long', | ||||
|             action='store_true', | ||||
|             default=False, | ||||
|             help=_('List additional fields in output'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--encryption-type", | ||||
|             action="store_true", | ||||
|             help=_( | ||||
|                 "Display encryption information for each volume type " | ||||
|                 "(admin only)" | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         if parsed_args.long: | ||||
|             columns = ['ID', 'Name', 'Is Public', 'Extra Specs'] | ||||
|             column_headers = ['ID', 'Name', 'Is Public', 'Properties'] | ||||
|         else: | ||||
|             columns = ['ID', 'Name', 'Is Public'] | ||||
|             column_headers = ['ID', 'Name', 'Is Public'] | ||||
|         data = volume_client.volume_types.list() | ||||
|  | ||||
|         formatters = {'Extra Specs': format_columns.DictColumn} | ||||
|  | ||||
|         if parsed_args.encryption_type: | ||||
|             encryption = {} | ||||
|             for d in volume_client.volume_encryption_types.list(): | ||||
|                 volume_type_id = d._info['volume_type_id'] | ||||
|                 # remove some redundant information | ||||
|                 del_key = [ | ||||
|                     'deleted', | ||||
|                     'created_at', | ||||
|                     'updated_at', | ||||
|                     'deleted_at', | ||||
|                     'volume_type_id', | ||||
|                 ] | ||||
|                 for key in del_key: | ||||
|                     d._info.pop(key, None) | ||||
|                 # save the encryption information with their volume type ID | ||||
|                 encryption[volume_type_id] = d._info | ||||
|             # We need to get volume type ID, then show encryption | ||||
|             # information according to the ID, so use "id" to keep | ||||
|             # difference to the real "ID" column. | ||||
|             columns += ['id'] | ||||
|             column_headers += ['Encryption'] | ||||
|  | ||||
|             _EncryptionInfoColumn = functools.partial( | ||||
|                 EncryptionInfoColumn, encryption_data=encryption | ||||
|             ) | ||||
|             formatters['id'] = _EncryptionInfoColumn | ||||
|  | ||||
|         return ( | ||||
|             column_headers, | ||||
|             ( | ||||
|                 utils.get_item_properties( | ||||
|                     s, | ||||
|                     columns, | ||||
|                     formatters=formatters, | ||||
|                 ) | ||||
|                 for s in data | ||||
|             ), | ||||
|         ) | ||||
|  | ||||
|  | ||||
| class SetVolumeType(command.Command): | ||||
|     _description = _("Set volume type properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volume_type', | ||||
|             metavar='<volume-type>', | ||||
|             help=_('Volume type to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key=value>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_( | ||||
|                 'Set a property on this volume type ' | ||||
|                 '(repeat option to set multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         # TODO(Huanxuan Ao): Add choices for each "--encryption-*" option. | ||||
|         parser.add_argument( | ||||
|             '--encryption-provider', | ||||
|             metavar='<provider>', | ||||
|             help=_( | ||||
|                 'Set the encryption provider format for ' | ||||
|                 'this volume type (e.g "luks" or "plain") (admin only) ' | ||||
|                 '(This option is required when setting encryption type ' | ||||
|                 'of a volume. Consider using other encryption options ' | ||||
|                 'such as: "--encryption-cipher", "--encryption-key-size" ' | ||||
|                 'and "--encryption-control-location")' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--encryption-cipher', | ||||
|             metavar='<cipher>', | ||||
|             help=_( | ||||
|                 'Set the encryption algorithm or mode for this ' | ||||
|                 'volume type (e.g "aes-xts-plain64") (admin only)' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--encryption-key-size', | ||||
|             metavar='<key-size>', | ||||
|             type=int, | ||||
|             help=_( | ||||
|                 'Set the size of the encryption key of this ' | ||||
|                 'volume type (e.g "128" or "256") (admin only)' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--encryption-control-location', | ||||
|             metavar='<control-location>', | ||||
|             choices=['front-end', 'back-end'], | ||||
|             help=_( | ||||
|                 'Set the notional service where the encryption is ' | ||||
|                 'performed ("front-end" or "back-end") (admin only) ' | ||||
|                 '(The default value for this option is "front-end" ' | ||||
|                 'when setting encryption type of a volume. Consider ' | ||||
|                 'using other encryption options such as: ' | ||||
|                 '"--encryption-cipher", "--encryption-key-size" and ' | ||||
|                 '"--encryption-provider")' | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume_type = utils.find_resource( | ||||
|             volume_client.volume_types, parsed_args.volume_type | ||||
|         ) | ||||
|  | ||||
|         result = 0 | ||||
|         if parsed_args.property: | ||||
|             try: | ||||
|                 volume_type.set_keys(parsed_args.property) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to set volume type property: %s"), e) | ||||
|                 result += 1 | ||||
|  | ||||
|         if ( | ||||
|             parsed_args.encryption_provider | ||||
|             or parsed_args.encryption_cipher | ||||
|             or parsed_args.encryption_key_size | ||||
|             or parsed_args.encryption_control_location | ||||
|         ): | ||||
|             try: | ||||
|                 _create_encryption_type( | ||||
|                     volume_client, volume_type, parsed_args | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to set encryption information for this " | ||||
|                         "volume type: %s" | ||||
|                     ), | ||||
|                     e, | ||||
|                 ) | ||||
|                 result += 1 | ||||
|  | ||||
|         if result > 0: | ||||
|             raise exceptions.CommandError( | ||||
|                 _("Command Failed: One or more of the operations failed") | ||||
|             ) | ||||
|  | ||||
|  | ||||
| class ShowVolumeType(command.ShowOne): | ||||
|     _description = _("Display volume type details") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             "volume_type", | ||||
|             metavar="<volume-type>", | ||||
|             help=_("Volume type to display (name or ID)"), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--encryption-type", | ||||
|             action="store_true", | ||||
|             help=_( | ||||
|                 "Display encryption information of this volume type " | ||||
|                 "(admin only)" | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume_type = utils.find_resource( | ||||
|             volume_client.volume_types, parsed_args.volume_type | ||||
|         ) | ||||
|         properties = format_columns.DictColumn( | ||||
|             volume_type._info.pop('extra_specs') | ||||
|         ) | ||||
|         volume_type._info.update({'properties': properties}) | ||||
|         if parsed_args.encryption_type: | ||||
|             # show encryption type information for this volume type | ||||
|             try: | ||||
|                 encryption = volume_client.volume_encryption_types.get( | ||||
|                     volume_type.id | ||||
|                 ) | ||||
|                 encryption._info.pop("volume_type_id", None) | ||||
|                 volume_type._info.update( | ||||
|                     {'encryption': format_columns.DictColumn(encryption._info)} | ||||
|                 ) | ||||
|             except Exception as e: | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to display the encryption information " | ||||
|                         "of this volume type: %s" | ||||
|                     ), | ||||
|                     e, | ||||
|                 ) | ||||
|         volume_type._info.pop("os-volume-type-access:is_public", None) | ||||
|         return zip(*sorted(volume_type._info.items())) | ||||
|  | ||||
|  | ||||
| class UnsetVolumeType(command.Command): | ||||
|     _description = _("Unset volume type properties") | ||||
|  | ||||
|     def get_parser(self, prog_name): | ||||
|         parser = super().get_parser(prog_name) | ||||
|         parser.add_argument( | ||||
|             'volume_type', | ||||
|             metavar='<volume-type>', | ||||
|             help=_('Volume type to modify (name or ID)'), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             '--property', | ||||
|             metavar='<key>', | ||||
|             action='append', | ||||
|             help=_( | ||||
|                 'Remove a property from this volume type ' | ||||
|                 '(repeat option to remove multiple properties)' | ||||
|             ), | ||||
|         ) | ||||
|         parser.add_argument( | ||||
|             "--encryption-type", | ||||
|             action="store_true", | ||||
|             help=_( | ||||
|                 "Remove the encryption type for this volume type (admin only)" | ||||
|             ), | ||||
|         ) | ||||
|         return parser | ||||
|  | ||||
|     def take_action(self, parsed_args): | ||||
|         volume_client = self.app.client_manager.volume | ||||
|         volume_type = utils.find_resource( | ||||
|             volume_client.volume_types, | ||||
|             parsed_args.volume_type, | ||||
|         ) | ||||
|  | ||||
|         result = 0 | ||||
|         if parsed_args.property: | ||||
|             try: | ||||
|                 volume_type.unset_keys(parsed_args.property) | ||||
|             except Exception as e: | ||||
|                 LOG.error(_("Failed to unset volume type property: %s"), e) | ||||
|                 result += 1 | ||||
|         if parsed_args.encryption_type: | ||||
|             try: | ||||
|                 volume_client.volume_encryption_types.delete(volume_type) | ||||
|             except Exception as e: | ||||
|                 LOG.error( | ||||
|                     _( | ||||
|                         "Failed to remove the encryption type for this " | ||||
|                         "volume type: %s" | ||||
|                     ), | ||||
|                     e, | ||||
|                 ) | ||||
|                 result += 1 | ||||
|  | ||||
|         if result > 0: | ||||
|             raise exceptions.CommandError( | ||||
|                 _("Command Failed: One or more of the operations failed") | ||||
|             ) | ||||
| @@ -0,0 +1,35 @@ | ||||
| --- | ||||
| upgrade: | ||||
|   - | | ||||
|     Support for the Block Storage (Cinder) v1 API has been officially removed | ||||
|     as it had been broken for some time. If you haven't noticed then you likely | ||||
|     don't need to do anything. However, in the unlikely event that your cloud | ||||
|     is using the Block Storage v1 API - or incorrectly advertises the Block | ||||
|     Storage v1 API - consider overriding the API version to use v2 as this | ||||
|     behaves very similarly. It may also be necessary to set an endpoint | ||||
|     override for the Block Storage API if your clouds service catalog is not | ||||
|     configured correctly. For example: | ||||
|  | ||||
|     .. code-block:: yaml | ||||
|  | ||||
|         example: | ||||
|           regions: | ||||
|             - name: regionOne | ||||
|               values: | ||||
|                 block_storage_endpoint_override: 'https://blockstorage.api.cloud.example/' | ||||
|           volume_api_version: 2 | ||||
|  | ||||
|     If using a public cloud provider, there may also be a profile already | ||||
|     published that sets these. These are listed in the `Vendor Support`__ | ||||
|     doc. For example: | ||||
|  | ||||
|     .. code-block:: yaml | ||||
|  | ||||
|         example: | ||||
|           profile: rackspace | ||||
|  | ||||
|     Alternatively, consider use versions of OSC < 3.19 and python-cinderclient | ||||
|     < 5.0 (both Stein), since these were the last versions to fully support | ||||
|     Cinder v1. | ||||
|  | ||||
|     .. __: https://docs.openstack.org/openstacksdk/latest/user/config/vendor-support.html | ||||
							
								
								
									
										47
									
								
								setup.cfg
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								setup.cfg
									
									
									
									
									
								
							| @@ -635,53 +635,6 @@ openstack.object_store.v1 = | ||||
|     object_show = openstackclient.object.v1.object:ShowObject | ||||
|     object_unset = openstackclient.object.v1.object:UnsetObject | ||||
|  | ||||
| openstack.volume.v1 = | ||||
|     volume_create = openstackclient.volume.v1.volume:CreateVolume | ||||
|     volume_delete = openstackclient.volume.v1.volume:DeleteVolume | ||||
|     volume_list = openstackclient.volume.v1.volume:ListVolume | ||||
|     volume_migrate = openstackclient.volume.v1.volume:MigrateVolume | ||||
|     volume_set = openstackclient.volume.v1.volume:SetVolume | ||||
|     volume_show = openstackclient.volume.v1.volume:ShowVolume | ||||
|     volume_unset = openstackclient.volume.v1.volume:UnsetVolume | ||||
|  | ||||
|     volume_backup_create = openstackclient.volume.v1.volume_backup:CreateVolumeBackup | ||||
|     volume_backup_delete = openstackclient.volume.v1.volume_backup:DeleteVolumeBackup | ||||
|     volume_backup_list = openstackclient.volume.v1.volume_backup:ListVolumeBackup | ||||
|     volume_backup_restore = openstackclient.volume.v1.volume_backup:RestoreVolumeBackup | ||||
|     volume_backup_show = openstackclient.volume.v1.volume_backup:ShowVolumeBackup | ||||
|  | ||||
|     volume_snapshot_create = openstackclient.volume.v1.volume_snapshot:CreateVolumeSnapshot | ||||
|     volume_snapshot_delete = openstackclient.volume.v1.volume_snapshot:DeleteVolumeSnapshot | ||||
|     volume_snapshot_list = openstackclient.volume.v1.volume_snapshot:ListVolumeSnapshot | ||||
|     volume_snapshot_set = openstackclient.volume.v1.volume_snapshot:SetVolumeSnapshot | ||||
|     volume_snapshot_show = openstackclient.volume.v1.volume_snapshot:ShowVolumeSnapshot | ||||
|     volume_snapshot_unset = openstackclient.volume.v1.volume_snapshot:UnsetVolumeSnapshot | ||||
|  | ||||
|     volume_type_create = openstackclient.volume.v1.volume_type:CreateVolumeType | ||||
|     volume_type_delete = openstackclient.volume.v1.volume_type:DeleteVolumeType | ||||
|     volume_type_list = openstackclient.volume.v1.volume_type:ListVolumeType | ||||
|     volume_type_set = openstackclient.volume.v1.volume_type:SetVolumeType | ||||
|     volume_type_show = openstackclient.volume.v1.volume_type:ShowVolumeType | ||||
|     volume_type_unset = openstackclient.volume.v1.volume_type:UnsetVolumeType | ||||
|  | ||||
|     volume_qos_associate = openstackclient.volume.v1.qos_specs:AssociateQos | ||||
|     volume_qos_create = openstackclient.volume.v1.qos_specs:CreateQos | ||||
|     volume_qos_delete = openstackclient.volume.v1.qos_specs:DeleteQos | ||||
|     volume_qos_disassociate = openstackclient.volume.v1.qos_specs:DisassociateQos | ||||
|     volume_qos_list = openstackclient.volume.v1.qos_specs:ListQos | ||||
|     volume_qos_set = openstackclient.volume.v1.qos_specs:SetQos | ||||
|     volume_qos_show = openstackclient.volume.v1.qos_specs:ShowQos | ||||
|     volume_qos_unset = openstackclient.volume.v1.qos_specs:UnsetQos | ||||
|  | ||||
|     volume_service_list = openstackclient.volume.v1.service:ListService | ||||
|     volume_service_set = openstackclient.volume.v1.service:SetService | ||||
|  | ||||
|     volume_transfer_request_accept = openstackclient.volume.v1.volume_transfer_request:AcceptTransferRequest | ||||
|     volume_transfer_request_create = openstackclient.volume.v1.volume_transfer_request:CreateTransferRequest | ||||
|     volume_transfer_request_delete = openstackclient.volume.v1.volume_transfer_request:DeleteTransferRequest | ||||
|     volume_transfer_request_list = openstackclient.volume.v1.volume_transfer_request:ListTransferRequest | ||||
|     volume_transfer_request_show = openstackclient.volume.v1.volume_transfer_request:ShowTransferRequest | ||||
|  | ||||
| openstack.volume.v2 = | ||||
|     consistency_group_add_volume = openstackclient.volume.v2.consistency_group:AddVolumeToConsistencyGroup | ||||
|     consistency_group_create = openstackclient.volume.v2.consistency_group:CreateConsistencyGroup | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Zuul
					Zuul