Add and modify options for "volume create" command
1.Add mutually exclusive options into a mutually exclusive group. 2.Add "--source-replicated", "--consistency-group", "--hint" and "multi-attach" options 3.Make --size option to be optional under some cases Closes-Bug: #1568005 Closes-Bug: #1627913 Implements: bp implement-cinder-features Co-Authored-By: Roman Vasilets <rvasilets@mirantis.com> Change-Id: I2c4c3073195d33774e477f4d7f22e383b14b41dd
This commit is contained in:
		@@ -13,21 +13,23 @@ Create new volume
 | 
				
			|||||||
.. code:: bash
 | 
					.. code:: bash
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    os volume create
 | 
					    os volume create
 | 
				
			||||||
        --size <size>
 | 
					        [--size <size>]
 | 
				
			||||||
        [--type <volume-type>]
 | 
					        [--type <volume-type>]
 | 
				
			||||||
        [--image <image>]
 | 
					        [--image <image> | --snapshot <snapshot> | --source <volume> | --source-replicated <replicated-volume>]
 | 
				
			||||||
        [--snapshot <snapshot>]
 | 
					 | 
				
			||||||
        [--source <volume>]
 | 
					 | 
				
			||||||
        [--description <description>]
 | 
					        [--description <description>]
 | 
				
			||||||
        [--user <user>]
 | 
					        [--user <user>]
 | 
				
			||||||
        [--project <project>]
 | 
					        [--project <project>]
 | 
				
			||||||
        [--availability-zone <availability-zone>]
 | 
					        [--availability-zone <availability-zone>]
 | 
				
			||||||
 | 
					        [--consistency-group <consistency-group>]
 | 
				
			||||||
        [--property <key=value> [...] ]
 | 
					        [--property <key=value> [...] ]
 | 
				
			||||||
 | 
					        [--hint <key=value> [...] ]
 | 
				
			||||||
 | 
					        [--multi-attach]
 | 
				
			||||||
        <name>
 | 
					        <name>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. option:: --size <size> (required)
 | 
					.. option:: --size <size>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Volume size in GB
 | 
					    Volume size in GB
 | 
				
			||||||
 | 
					    (Required unless --snapshot or --source or --source-replicated is specified)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. option:: --type <volume-type>
 | 
					.. option:: --type <volume-type>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,10 +48,14 @@ Create new volume
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Use :option:`\<snapshot\>` as source of volume (name or ID)
 | 
					    Use :option:`\<snapshot\>` as source of volume (name or ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. option:: --source <source>
 | 
					.. option:: --source <volume>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Volume to clone (name or ID)
 | 
					    Volume to clone (name or ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. option:: --source-replicated <replicated-volume>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Replicated volume to clone (name or ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. option:: --description <description>
 | 
					.. option:: --description <description>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Volume description
 | 
					    Volume description
 | 
				
			||||||
@@ -66,10 +72,23 @@ Create new volume
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    Create volume in :option:`\<availability-zone\>`
 | 
					    Create volume in :option:`\<availability-zone\>`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. option:: --consistency-group <consistency-group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Consistency group where the new volume belongs to
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. option:: --property <key=value>
 | 
					.. option:: --property <key=value>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Set a property on this volume (repeat option to set multiple properties)
 | 
					    Set a property on this volume (repeat option to set multiple properties)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. option:: --hint <key=value>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Arbitrary scheduler hint key-value pairs to help boot an instance
 | 
				
			||||||
 | 
					    (repeat option to set multiple hints)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.. option:: --multi-attach
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Allow volume to be attached more than once (default to False)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.. _volume_create-name:
 | 
					.. _volume_create-name:
 | 
				
			||||||
.. describe:: <name>
 | 
					.. describe:: <name>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,7 @@ from osc_lib import utils
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from openstackclient.tests.unit import fakes
 | 
					from openstackclient.tests.unit import fakes
 | 
				
			||||||
from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
 | 
					from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes
 | 
				
			||||||
 | 
					from openstackclient.tests.unit import utils as tests_utils
 | 
				
			||||||
from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
 | 
					from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes
 | 
				
			||||||
from openstackclient.volume.v1 import volume
 | 
					from openstackclient.volume.v1 import volume
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -411,6 +412,67 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
        self.assertEqual(self.datalist, data)
 | 
					        self.assertEqual(self.datalist, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_volume_create_with_source(self):
 | 
				
			||||||
 | 
					        self.volumes_mock.get.return_value = self.new_volume
 | 
				
			||||||
 | 
					        arglist = [
 | 
				
			||||||
 | 
					            '--source', self.new_volume.id,
 | 
				
			||||||
 | 
					            self.new_volume.display_name,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        verifylist = [
 | 
				
			||||||
 | 
					            ('source', self.new_volume.id),
 | 
				
			||||||
 | 
					            ('name', self.new_volume.display_name),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        columns, data = self.cmd.take_action(parsed_args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.volumes_mock.create.assert_called_with(
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            self.new_volume.id,
 | 
				
			||||||
 | 
					            self.new_volume.display_name,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
 | 
					        self.assertEqual(self.datalist, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_volume_create_without_size(self):
 | 
				
			||||||
 | 
					        arglist = [
 | 
				
			||||||
 | 
					            self.new_volume.display_name,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        verifylist = [
 | 
				
			||||||
 | 
					            ('name', self.new_volume.display_name),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.CommandError, self.cmd.take_action,
 | 
				
			||||||
 | 
					                          parsed_args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_volume_create_with_multi_source(self):
 | 
				
			||||||
 | 
					        arglist = [
 | 
				
			||||||
 | 
					            '--image', 'source_image',
 | 
				
			||||||
 | 
					            '--source', 'source_volume',
 | 
				
			||||||
 | 
					            '--snapshot', 'source_snapshot',
 | 
				
			||||||
 | 
					            '--size', str(self.new_volume.size),
 | 
				
			||||||
 | 
					            self.new_volume.display_name,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        verifylist = [
 | 
				
			||||||
 | 
					            ('image', 'source_image'),
 | 
				
			||||||
 | 
					            ('source', 'source_volume'),
 | 
				
			||||||
 | 
					            ('snapshot', 'source_snapshot'),
 | 
				
			||||||
 | 
					            ('size', self.new_volume.size),
 | 
				
			||||||
 | 
					            ('name', self.new_volume.display_name),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(tests_utils.ParserException, self.check_parser,
 | 
				
			||||||
 | 
					                          self.cmd, arglist, verifylist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestVolumeDelete(TestVolume):
 | 
					class TestVolumeDelete(TestVolume):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ from osc_lib import utils
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
 | 
					from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
 | 
				
			||||||
from openstackclient.tests.unit.image.v2 import fakes as image_fakes
 | 
					from openstackclient.tests.unit.image.v2 import fakes as image_fakes
 | 
				
			||||||
 | 
					from openstackclient.tests.unit import utils as tests_utils
 | 
				
			||||||
from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
 | 
					from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes
 | 
				
			||||||
from openstackclient.volume.v2 import volume
 | 
					from openstackclient.volume.v2 import volume
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,6 +46,10 @@ class TestVolume(volume_fakes.TestVolume):
 | 
				
			|||||||
        self.snapshots_mock = self.app.client_manager.volume.volume_snapshots
 | 
					        self.snapshots_mock = self.app.client_manager.volume.volume_snapshots
 | 
				
			||||||
        self.snapshots_mock.reset_mock()
 | 
					        self.snapshots_mock.reset_mock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.consistencygroups_mock = (
 | 
				
			||||||
 | 
					            self.app.client_manager.volume.consistencygroups)
 | 
				
			||||||
 | 
					        self.consistencygroups_mock.reset_mock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def setup_volumes_mock(self, count):
 | 
					    def setup_volumes_mock(self, count):
 | 
				
			||||||
        volumes = volume_fakes.FakeVolume.create_volumes(count=count)
 | 
					        volumes = volume_fakes.FakeVolume.create_volumes(count=count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -123,18 +128,28 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            availability_zone=None,
 | 
					            availability_zone=None,
 | 
				
			||||||
            metadata=None,
 | 
					            metadata=None,
 | 
				
			||||||
            imageRef=None,
 | 
					            imageRef=None,
 | 
				
			||||||
            source_volid=None
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=None,
 | 
				
			||||||
 | 
					            source_replica=None,
 | 
				
			||||||
 | 
					            multiattach=False,
 | 
				
			||||||
 | 
					            scheduler_hints=None,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
        self.assertEqual(self.datalist, data)
 | 
					        self.assertEqual(self.datalist, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_volume_create_options(self):
 | 
					    def test_volume_create_options(self):
 | 
				
			||||||
 | 
					        consistency_group = (
 | 
				
			||||||
 | 
					            volume_fakes.FakeConsistencyGroup.create_one_consistency_group())
 | 
				
			||||||
 | 
					        self.consistencygroups_mock.get.return_value = consistency_group
 | 
				
			||||||
        arglist = [
 | 
					        arglist = [
 | 
				
			||||||
            '--size', str(self.new_volume.size),
 | 
					            '--size', str(self.new_volume.size),
 | 
				
			||||||
            '--description', self.new_volume.description,
 | 
					            '--description', self.new_volume.description,
 | 
				
			||||||
            '--type', self.new_volume.volume_type,
 | 
					            '--type', self.new_volume.volume_type,
 | 
				
			||||||
            '--availability-zone', self.new_volume.availability_zone,
 | 
					            '--availability-zone', self.new_volume.availability_zone,
 | 
				
			||||||
 | 
					            '--consistency-group', consistency_group.id,
 | 
				
			||||||
 | 
					            '--hint', 'k=v',
 | 
				
			||||||
 | 
					            '--multi-attach',
 | 
				
			||||||
            self.new_volume.name,
 | 
					            self.new_volume.name,
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        verifylist = [
 | 
					        verifylist = [
 | 
				
			||||||
@@ -142,6 +157,9 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            ('description', self.new_volume.description),
 | 
					            ('description', self.new_volume.description),
 | 
				
			||||||
            ('type', self.new_volume.volume_type),
 | 
					            ('type', self.new_volume.volume_type),
 | 
				
			||||||
            ('availability_zone', self.new_volume.availability_zone),
 | 
					            ('availability_zone', self.new_volume.availability_zone),
 | 
				
			||||||
 | 
					            ('consistency_group', consistency_group.id),
 | 
				
			||||||
 | 
					            ('hint', {'k': 'v'}),
 | 
				
			||||||
 | 
					            ('multi_attach', True),
 | 
				
			||||||
            ('name', self.new_volume.name),
 | 
					            ('name', self.new_volume.name),
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
					        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
				
			||||||
@@ -162,7 +180,11 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            availability_zone=self.new_volume.availability_zone,
 | 
					            availability_zone=self.new_volume.availability_zone,
 | 
				
			||||||
            metadata=None,
 | 
					            metadata=None,
 | 
				
			||||||
            imageRef=None,
 | 
					            imageRef=None,
 | 
				
			||||||
            source_volid=None
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=consistency_group.id,
 | 
				
			||||||
 | 
					            source_replica=None,
 | 
				
			||||||
 | 
					            multiattach=True,
 | 
				
			||||||
 | 
					            scheduler_hints={'k': 'v'},
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
@@ -204,7 +226,11 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            availability_zone=None,
 | 
					            availability_zone=None,
 | 
				
			||||||
            metadata=None,
 | 
					            metadata=None,
 | 
				
			||||||
            imageRef=None,
 | 
					            imageRef=None,
 | 
				
			||||||
            source_volid=None
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=None,
 | 
				
			||||||
 | 
					            source_replica=None,
 | 
				
			||||||
 | 
					            multiattach=False,
 | 
				
			||||||
 | 
					            scheduler_hints=None,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
@@ -246,7 +272,11 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            availability_zone=None,
 | 
					            availability_zone=None,
 | 
				
			||||||
            metadata=None,
 | 
					            metadata=None,
 | 
				
			||||||
            imageRef=None,
 | 
					            imageRef=None,
 | 
				
			||||||
            source_volid=None
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=None,
 | 
				
			||||||
 | 
					            source_replica=None,
 | 
				
			||||||
 | 
					            multiattach=False,
 | 
				
			||||||
 | 
					            scheduler_hints=None,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
@@ -282,7 +312,11 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            availability_zone=None,
 | 
					            availability_zone=None,
 | 
				
			||||||
            metadata={'Alpha': 'a', 'Beta': 'b'},
 | 
					            metadata={'Alpha': 'a', 'Beta': 'b'},
 | 
				
			||||||
            imageRef=None,
 | 
					            imageRef=None,
 | 
				
			||||||
            source_volid=None
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=None,
 | 
				
			||||||
 | 
					            source_replica=None,
 | 
				
			||||||
 | 
					            multiattach=False,
 | 
				
			||||||
 | 
					            scheduler_hints=None,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
@@ -321,6 +355,10 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            metadata=None,
 | 
					            metadata=None,
 | 
				
			||||||
            imageRef=image.id,
 | 
					            imageRef=image.id,
 | 
				
			||||||
            source_volid=None,
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=None,
 | 
				
			||||||
 | 
					            source_replica=None,
 | 
				
			||||||
 | 
					            multiattach=False,
 | 
				
			||||||
 | 
					            scheduler_hints=None,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
@@ -358,7 +396,11 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            availability_zone=None,
 | 
					            availability_zone=None,
 | 
				
			||||||
            metadata=None,
 | 
					            metadata=None,
 | 
				
			||||||
            imageRef=image.id,
 | 
					            imageRef=image.id,
 | 
				
			||||||
            source_volid=None
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=None,
 | 
				
			||||||
 | 
					            source_replica=None,
 | 
				
			||||||
 | 
					            multiattach=False,
 | 
				
			||||||
 | 
					            scheduler_hints=None,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
@@ -368,12 +410,10 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
        snapshot = volume_fakes.FakeSnapshot.create_one_snapshot()
 | 
					        snapshot = volume_fakes.FakeSnapshot.create_one_snapshot()
 | 
				
			||||||
        self.new_volume.snapshot_id = snapshot.id
 | 
					        self.new_volume.snapshot_id = snapshot.id
 | 
				
			||||||
        arglist = [
 | 
					        arglist = [
 | 
				
			||||||
            '--size', str(self.new_volume.size),
 | 
					 | 
				
			||||||
            '--snapshot', self.new_volume.snapshot_id,
 | 
					            '--snapshot', self.new_volume.snapshot_id,
 | 
				
			||||||
            self.new_volume.name,
 | 
					            self.new_volume.name,
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
        verifylist = [
 | 
					        verifylist = [
 | 
				
			||||||
            ('size', self.new_volume.size),
 | 
					 | 
				
			||||||
            ('snapshot', self.new_volume.snapshot_id),
 | 
					            ('snapshot', self.new_volume.snapshot_id),
 | 
				
			||||||
            ('name', self.new_volume.name),
 | 
					            ('name', self.new_volume.name),
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
@@ -387,7 +427,7 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
        columns, data = self.cmd.take_action(parsed_args)
 | 
					        columns, data = self.cmd.take_action(parsed_args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.volumes_mock.create.assert_called_once_with(
 | 
					        self.volumes_mock.create.assert_called_once_with(
 | 
				
			||||||
            size=self.new_volume.size,
 | 
					            size=None,
 | 
				
			||||||
            snapshot_id=snapshot.id,
 | 
					            snapshot_id=snapshot.id,
 | 
				
			||||||
            name=self.new_volume.name,
 | 
					            name=self.new_volume.name,
 | 
				
			||||||
            description=None,
 | 
					            description=None,
 | 
				
			||||||
@@ -397,12 +437,83 @@ class TestVolumeCreate(TestVolume):
 | 
				
			|||||||
            availability_zone=None,
 | 
					            availability_zone=None,
 | 
				
			||||||
            metadata=None,
 | 
					            metadata=None,
 | 
				
			||||||
            imageRef=None,
 | 
					            imageRef=None,
 | 
				
			||||||
            source_volid=None
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=None,
 | 
				
			||||||
 | 
					            source_replica=None,
 | 
				
			||||||
 | 
					            multiattach=False,
 | 
				
			||||||
 | 
					            scheduler_hints=None,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertEqual(self.columns, columns)
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
        self.assertEqual(self.datalist, data)
 | 
					        self.assertEqual(self.datalist, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_volume_create_with_source_replicated(self):
 | 
				
			||||||
 | 
					        self.volumes_mock.get.return_value = self.new_volume
 | 
				
			||||||
 | 
					        arglist = [
 | 
				
			||||||
 | 
					            '--source-replicated', self.new_volume.id,
 | 
				
			||||||
 | 
					            self.new_volume.name,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        verifylist = [
 | 
				
			||||||
 | 
					            ('source_replicated', self.new_volume.id),
 | 
				
			||||||
 | 
					            ('name', self.new_volume.name),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        columns, data = self.cmd.take_action(parsed_args)
 | 
				
			||||||
 | 
					        self.volumes_mock.create.assert_called_once_with(
 | 
				
			||||||
 | 
					            size=None,
 | 
				
			||||||
 | 
					            snapshot_id=None,
 | 
				
			||||||
 | 
					            name=self.new_volume.name,
 | 
				
			||||||
 | 
					            description=None,
 | 
				
			||||||
 | 
					            volume_type=None,
 | 
				
			||||||
 | 
					            user_id=None,
 | 
				
			||||||
 | 
					            project_id=None,
 | 
				
			||||||
 | 
					            availability_zone=None,
 | 
				
			||||||
 | 
					            metadata=None,
 | 
				
			||||||
 | 
					            imageRef=None,
 | 
				
			||||||
 | 
					            source_volid=None,
 | 
				
			||||||
 | 
					            consistencygroup_id=None,
 | 
				
			||||||
 | 
					            source_replica=self.new_volume.id,
 | 
				
			||||||
 | 
					            multiattach=False,
 | 
				
			||||||
 | 
					            scheduler_hints=None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertEqual(self.columns, columns)
 | 
				
			||||||
 | 
					        self.assertEqual(self.datalist, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_volume_create_without_size(self):
 | 
				
			||||||
 | 
					        arglist = [
 | 
				
			||||||
 | 
					            self.new_volume.name,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        verifylist = [
 | 
				
			||||||
 | 
					            ('name', self.new_volume.name),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(exceptions.CommandError, self.cmd.take_action,
 | 
				
			||||||
 | 
					                          parsed_args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_volume_create_with_multi_source(self):
 | 
				
			||||||
 | 
					        arglist = [
 | 
				
			||||||
 | 
					            '--image', 'source_image',
 | 
				
			||||||
 | 
					            '--source', 'source_volume',
 | 
				
			||||||
 | 
					            '--snapshot', 'source_snapshot',
 | 
				
			||||||
 | 
					            '--source-replicated', 'source_replicated_volume',
 | 
				
			||||||
 | 
					            '--size', str(self.new_volume.size),
 | 
				
			||||||
 | 
					            self.new_volume.name,
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        verifylist = [
 | 
				
			||||||
 | 
					            ('image', 'source_image'),
 | 
				
			||||||
 | 
					            ('source', 'source_volume'),
 | 
				
			||||||
 | 
					            ('snapshot', 'source_snapshot'),
 | 
				
			||||||
 | 
					            ('source-replicated', 'source_replicated_volume'),
 | 
				
			||||||
 | 
					            ('size', self.new_volume.size),
 | 
				
			||||||
 | 
					            ('name', self.new_volume.name),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        self.assertRaises(tests_utils.ParserException, self.check_parser,
 | 
				
			||||||
 | 
					                          self.cmd, arglist, verifylist)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TestVolumeDelete(TestVolume):
 | 
					class TestVolumeDelete(TestVolume):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,20 @@ from openstackclient.i18n import _
 | 
				
			|||||||
LOG = logging.getLogger(__name__)
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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):
 | 
					class CreateVolume(command.ShowOne):
 | 
				
			||||||
    """Create new volume"""
 | 
					    """Create new volume"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,32 +57,32 @@ class CreateVolume(command.ShowOne):
 | 
				
			|||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
            '--size',
 | 
					            '--size',
 | 
				
			||||||
            metavar='<size>',
 | 
					            metavar='<size>',
 | 
				
			||||||
            required=True,
 | 
					 | 
				
			||||||
            type=int,
 | 
					            type=int,
 | 
				
			||||||
            help=_('Volume size in GB'),
 | 
					            help=_("Volume size in GB (Required unless --snapshot or "
 | 
				
			||||||
 | 
					                   "--source is specified)"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
            '--type',
 | 
					            '--type',
 | 
				
			||||||
            metavar='<volume-type>',
 | 
					            metavar='<volume-type>',
 | 
				
			||||||
            help=_("Set the type of volume"),
 | 
					            help=_("Set the type of volume"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        source_group = parser.add_mutually_exclusive_group()
 | 
				
			||||||
 | 
					        source_group.add_argument(
 | 
				
			||||||
            '--image',
 | 
					            '--image',
 | 
				
			||||||
            metavar='<image>',
 | 
					            metavar='<image>',
 | 
				
			||||||
            help=_('Use <image> as source of volume (name or ID)'),
 | 
					            help=_('Use <image> as source of volume (name or ID)'),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        snapshot_group = parser.add_mutually_exclusive_group()
 | 
					        source_group.add_argument(
 | 
				
			||||||
        snapshot_group.add_argument(
 | 
					 | 
				
			||||||
            '--snapshot',
 | 
					            '--snapshot',
 | 
				
			||||||
            metavar='<snapshot>',
 | 
					            metavar='<snapshot>',
 | 
				
			||||||
            help=_('Use <snapshot> as source of volume (name or ID)'),
 | 
					            help=_('Use <snapshot> as source of volume (name or ID)'),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        snapshot_group.add_argument(
 | 
					        source_group.add_argument(
 | 
				
			||||||
            '--snapshot-id',
 | 
					            '--snapshot-id',
 | 
				
			||||||
            metavar='<snapshot-id>',
 | 
					            metavar='<snapshot-id>',
 | 
				
			||||||
            help=argparse.SUPPRESS,
 | 
					            help=argparse.SUPPRESS,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        source_group.add_argument(
 | 
				
			||||||
            '--source',
 | 
					            '--source',
 | 
				
			||||||
            metavar='<volume>',
 | 
					            metavar='<volume>',
 | 
				
			||||||
            help=_('Volume to clone (name or ID)'),
 | 
					            help=_('Volume to clone (name or ID)'),
 | 
				
			||||||
@@ -104,7 +118,7 @@ class CreateVolume(command.ShowOne):
 | 
				
			|||||||
        return parser
 | 
					        return parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def take_action(self, parsed_args):
 | 
					    def take_action(self, parsed_args):
 | 
				
			||||||
 | 
					        _check_size_arg(parsed_args)
 | 
				
			||||||
        identity_client = self.app.client_manager.identity
 | 
					        identity_client = self.app.client_manager.identity
 | 
				
			||||||
        image_client = self.app.client_manager.image
 | 
					        image_client = self.app.client_manager.image
 | 
				
			||||||
        volume_client = self.app.client_manager.volume
 | 
					        volume_client = self.app.client_manager.volume
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,20 @@ from openstackclient.identity import common as identity_common
 | 
				
			|||||||
LOG = logging.getLogger(__name__)
 | 
					LOG = logging.getLogger(__name__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 or args.source_replicated)
 | 
				
			||||||
 | 
					            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):
 | 
					class CreateVolume(command.ShowOne):
 | 
				
			||||||
    """Create new volume"""
 | 
					    """Create new volume"""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -44,29 +58,35 @@ class CreateVolume(command.ShowOne):
 | 
				
			|||||||
            "--size",
 | 
					            "--size",
 | 
				
			||||||
            metavar="<size>",
 | 
					            metavar="<size>",
 | 
				
			||||||
            type=int,
 | 
					            type=int,
 | 
				
			||||||
            required=True,
 | 
					            help=_("Volume size in GB (Required unless --snapshot or "
 | 
				
			||||||
            help=_("Volume size in GB"),
 | 
					                   "--source or --source-replicated is specified)"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
            "--type",
 | 
					            "--type",
 | 
				
			||||||
            metavar="<volume-type>",
 | 
					            metavar="<volume-type>",
 | 
				
			||||||
            help=_("Set the type of volume"),
 | 
					            help=_("Set the type of volume"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        source_group = parser.add_mutually_exclusive_group()
 | 
				
			||||||
 | 
					        source_group.add_argument(
 | 
				
			||||||
            "--image",
 | 
					            "--image",
 | 
				
			||||||
            metavar="<image>",
 | 
					            metavar="<image>",
 | 
				
			||||||
            help=_("Use <image> as source of volume (name or ID)"),
 | 
					            help=_("Use <image> as source of volume (name or ID)"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        source_group.add_argument(
 | 
				
			||||||
            "--snapshot",
 | 
					            "--snapshot",
 | 
				
			||||||
            metavar="<snapshot>",
 | 
					            metavar="<snapshot>",
 | 
				
			||||||
            help=_("Use <snapshot> as source of volume (name or ID)"),
 | 
					            help=_("Use <snapshot> as source of volume (name or ID)"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        source_group.add_argument(
 | 
				
			||||||
            "--source",
 | 
					            "--source",
 | 
				
			||||||
            metavar="<volume>",
 | 
					            metavar="<volume>",
 | 
				
			||||||
            help=_("Volume to clone (name or ID)"),
 | 
					            help=_("Volume to clone (name or ID)"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        source_group.add_argument(
 | 
				
			||||||
 | 
					            "--source-replicated",
 | 
				
			||||||
 | 
					            metavar="<replicated-volume>",
 | 
				
			||||||
 | 
					            help=_("Replicated volume to clone (name or ID)"),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
            "--description",
 | 
					            "--description",
 | 
				
			||||||
            metavar="<description>",
 | 
					            metavar="<description>",
 | 
				
			||||||
@@ -87,6 +107,11 @@ class CreateVolume(command.ShowOne):
 | 
				
			|||||||
            metavar="<availability-zone>",
 | 
					            metavar="<availability-zone>",
 | 
				
			||||||
            help=_("Create volume in <availability-zone>"),
 | 
					            help=_("Create volume in <availability-zone>"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        parser.add_argument(
 | 
				
			||||||
 | 
					            "--consistency-group",
 | 
				
			||||||
 | 
					            metavar="consistency-group>",
 | 
				
			||||||
 | 
					            help=_("Consistency group where the new volume belongs to"),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        parser.add_argument(
 | 
					        parser.add_argument(
 | 
				
			||||||
            "--property",
 | 
					            "--property",
 | 
				
			||||||
            metavar="<key=value>",
 | 
					            metavar="<key=value>",
 | 
				
			||||||
@@ -94,9 +119,23 @@ class CreateVolume(command.ShowOne):
 | 
				
			|||||||
            help=_("Set a property to this volume "
 | 
					            help=_("Set a property to this volume "
 | 
				
			||||||
                   "(repeat option to set multiple properties)"),
 | 
					                   "(repeat option to set multiple properties)"),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					        parser.add_argument(
 | 
				
			||||||
 | 
					            "--hint",
 | 
				
			||||||
 | 
					            metavar="<key=value>",
 | 
				
			||||||
 | 
					            action=parseractions.KeyValueAction,
 | 
				
			||||||
 | 
					            help=_("Arbitrary scheduler hint key-value pairs to help boot "
 | 
				
			||||||
 | 
					                   "an instance (repeat option to set multiple hints)"),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        parser.add_argument(
 | 
				
			||||||
 | 
					            "--multi-attach",
 | 
				
			||||||
 | 
					            action="store_true",
 | 
				
			||||||
 | 
					            help=_("Allow volume to be attached more than once "
 | 
				
			||||||
 | 
					                   "(default to False)")
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        return parser
 | 
					        return parser
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def take_action(self, parsed_args):
 | 
					    def take_action(self, parsed_args):
 | 
				
			||||||
 | 
					        _check_size_arg(parsed_args)
 | 
				
			||||||
        identity_client = self.app.client_manager.identity
 | 
					        identity_client = self.app.client_manager.identity
 | 
				
			||||||
        volume_client = self.app.client_manager.volume
 | 
					        volume_client = self.app.client_manager.volume
 | 
				
			||||||
        image_client = self.app.client_manager.image
 | 
					        image_client = self.app.client_manager.image
 | 
				
			||||||
@@ -107,6 +146,18 @@ class CreateVolume(command.ShowOne):
 | 
				
			|||||||
                volume_client.volumes,
 | 
					                volume_client.volumes,
 | 
				
			||||||
                parsed_args.source).id
 | 
					                parsed_args.source).id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        replicated_source_volume = None
 | 
				
			||||||
 | 
					        if parsed_args.source_replicated:
 | 
				
			||||||
 | 
					            replicated_source_volume = utils.find_resource(
 | 
				
			||||||
 | 
					                volume_client.volumes,
 | 
				
			||||||
 | 
					                parsed_args.source_replicated).id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        consistency_group = None
 | 
				
			||||||
 | 
					        if parsed_args.consistency_group:
 | 
				
			||||||
 | 
					            consistency_group = utils.find_resource(
 | 
				
			||||||
 | 
					                volume_client.consistencygroups,
 | 
				
			||||||
 | 
					                parsed_args.consistency_group).id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        image = None
 | 
					        image = None
 | 
				
			||||||
        if parsed_args.image:
 | 
					        if parsed_args.image:
 | 
				
			||||||
            image = utils.find_resource(
 | 
					            image = utils.find_resource(
 | 
				
			||||||
@@ -142,7 +193,11 @@ class CreateVolume(command.ShowOne):
 | 
				
			|||||||
            availability_zone=parsed_args.availability_zone,
 | 
					            availability_zone=parsed_args.availability_zone,
 | 
				
			||||||
            metadata=parsed_args.property,
 | 
					            metadata=parsed_args.property,
 | 
				
			||||||
            imageRef=image,
 | 
					            imageRef=image,
 | 
				
			||||||
            source_volid=source_volume
 | 
					            source_volid=source_volume,
 | 
				
			||||||
 | 
					            consistencygroup_id=consistency_group,
 | 
				
			||||||
 | 
					            source_replica=replicated_source_volume,
 | 
				
			||||||
 | 
					            multiattach=parsed_args.multi_attach,
 | 
				
			||||||
 | 
					            scheduler_hints=parsed_args.hint,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        # Remove key links from being displayed
 | 
					        # Remove key links from being displayed
 | 
				
			||||||
        volume._info.update(
 | 
					        volume._info.update(
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								releasenotes/notes/bug-1627913-2adf4182977e5926.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								releasenotes/notes/bug-1627913-2adf4182977e5926.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					features:
 | 
				
			||||||
 | 
					  - Add ``--source-replicated``, ``--consistency-group``, ``--hint`` and
 | 
				
			||||||
 | 
					    ``--multi-attach`` options to ``volume create`` command in volume v2.
 | 
				
			||||||
 | 
					    [Bug `1627913 <https://bugs.launchpad.net/python-openstackclient/+bug/1627913>`_]
 | 
				
			||||||
		Reference in New Issue
	
	Block a user