Skip to content

Commit 3ff713f

Browse files
Jenkinsopenstack-gerrit
authored andcommitted
Merge "Add some options to "volume create" command"
2 parents 60370b4 + 7e5a98b commit 3ff713f

6 files changed

Lines changed: 383 additions & 0 deletions

File tree

doc/source/command-objects/volume.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Create new volume
2424
[--property <key=value> [...] ]
2525
[--hint <key=value> [...] ]
2626
[--multi-attach]
27+
[--bootable | --non-bootable]
28+
[--read-only | --read-write]
2729
<name>
2830
2931
.. option:: --size <size>
@@ -89,6 +91,22 @@ Create new volume
8991

9092
Allow volume to be attached more than once (default to False)
9193

94+
.. option:: --bootable
95+
96+
Mark volume as bootable
97+
98+
.. option:: --non-bootable
99+
100+
Mark volume as non-bootable (default)
101+
102+
.. option:: --read-only
103+
104+
Set volume to read-only access mode
105+
106+
.. option:: --read-write
107+
108+
Set volume to read-write access mode (default)
109+
92110
.. _volume_create-name:
93111
.. describe:: <name>
94112

openstackclient/tests/unit/volume/v1/test_volume.py

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,142 @@ def test_volume_create_with_source(self):
431431
self.assertEqual(self.columns, columns)
432432
self.assertEqual(self.datalist, data)
433433

434+
def test_volume_create_with_bootable_and_readonly(self):
435+
arglist = [
436+
'--bootable',
437+
'--read-only',
438+
'--size', str(self.new_volume.size),
439+
self.new_volume.display_name,
440+
]
441+
verifylist = [
442+
('bootable', True),
443+
('non_bootable', False),
444+
('read_only', True),
445+
('read_write', False),
446+
('size', self.new_volume.size),
447+
('name', self.new_volume.display_name),
448+
]
449+
450+
parsed_args = self.check_parser(
451+
self.cmd, arglist, verifylist)
452+
453+
columns, data = self.cmd.take_action(parsed_args)
454+
455+
self.volumes_mock.create.assert_called_with(
456+
self.new_volume.size,
457+
None,
458+
None,
459+
self.new_volume.display_name,
460+
None,
461+
None,
462+
None,
463+
None,
464+
None,
465+
None,
466+
None,
467+
)
468+
469+
self.assertEqual(self.columns, columns)
470+
self.assertEqual(self.datalist, data)
471+
self.volumes_mock.set_bootable.assert_called_with(
472+
self.new_volume.id, True)
473+
self.volumes_mock.update_readonly_flag.assert_called_with(
474+
self.new_volume.id, True)
475+
476+
def test_volume_create_with_nonbootable_and_readwrite(self):
477+
arglist = [
478+
'--non-bootable',
479+
'--read-write',
480+
'--size', str(self.new_volume.size),
481+
self.new_volume.display_name,
482+
]
483+
verifylist = [
484+
('bootable', False),
485+
('non_bootable', True),
486+
('read_only', False),
487+
('read_write', True),
488+
('size', self.new_volume.size),
489+
('name', self.new_volume.display_name),
490+
]
491+
492+
parsed_args = self.check_parser(
493+
self.cmd, arglist, verifylist)
494+
495+
columns, data = self.cmd.take_action(parsed_args)
496+
497+
self.volumes_mock.create.assert_called_with(
498+
self.new_volume.size,
499+
None,
500+
None,
501+
self.new_volume.display_name,
502+
None,
503+
None,
504+
None,
505+
None,
506+
None,
507+
None,
508+
None,
509+
)
510+
511+
self.assertEqual(self.columns, columns)
512+
self.assertEqual(self.datalist, data)
513+
self.volumes_mock.set_bootable.assert_called_with(
514+
self.new_volume.id, False)
515+
self.volumes_mock.update_readonly_flag.assert_called_with(
516+
self.new_volume.id, False)
517+
518+
@mock.patch.object(volume.LOG, 'error')
519+
def test_volume_create_with_bootable_and_readonly_fail(
520+
self, mock_error):
521+
522+
self.volumes_mock.set_bootable.side_effect = (
523+
exceptions.CommandError())
524+
525+
self.volumes_mock.update_readonly_flag.side_effect = (
526+
exceptions.CommandError())
527+
528+
arglist = [
529+
'--bootable',
530+
'--read-only',
531+
'--size', str(self.new_volume.size),
532+
self.new_volume.display_name,
533+
]
534+
verifylist = [
535+
('bootable', True),
536+
('non_bootable', False),
537+
('read_only', True),
538+
('read_write', False),
539+
('size', self.new_volume.size),
540+
('name', self.new_volume.display_name),
541+
]
542+
543+
parsed_args = self.check_parser(
544+
self.cmd, arglist, verifylist)
545+
546+
columns, data = self.cmd.take_action(parsed_args)
547+
548+
self.volumes_mock.create.assert_called_with(
549+
self.new_volume.size,
550+
None,
551+
None,
552+
self.new_volume.display_name,
553+
None,
554+
None,
555+
None,
556+
None,
557+
None,
558+
None,
559+
None,
560+
)
561+
562+
self.assertEqual(2, mock_error.call_count)
563+
self.assertEqual(self.columns, columns)
564+
self.assertEqual(self.datalist, data)
565+
self.volumes_mock.set_bootable.assert_called_with(
566+
self.new_volume.id, True)
567+
self.volumes_mock.update_readonly_flag.assert_called_with(
568+
self.new_volume.id, True)
569+
434570
def test_volume_create_without_size(self):
435571
arglist = [
436572
self.new_volume.display_name,

openstackclient/tests/unit/volume/v2/test_volume.py

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,154 @@ def test_volume_create_with_snapshot(self):
450450
self.assertEqual(self.columns, columns)
451451
self.assertEqual(self.datalist, data)
452452

453+
def test_volume_create_with_bootable_and_readonly(self):
454+
arglist = [
455+
'--bootable',
456+
'--read-only',
457+
'--size', str(self.new_volume.size),
458+
self.new_volume.name,
459+
]
460+
verifylist = [
461+
('bootable', True),
462+
('non_bootable', False),
463+
('read_only', True),
464+
('read_write', False),
465+
('size', self.new_volume.size),
466+
('name', self.new_volume.name),
467+
]
468+
469+
parsed_args = self.check_parser(
470+
self.cmd, arglist, verifylist)
471+
472+
columns, data = self.cmd.take_action(parsed_args)
473+
474+
self.volumes_mock.create.assert_called_with(
475+
size=self.new_volume.size,
476+
snapshot_id=None,
477+
name=self.new_volume.name,
478+
description=None,
479+
volume_type=None,
480+
user_id=None,
481+
project_id=None,
482+
availability_zone=None,
483+
metadata=None,
484+
imageRef=None,
485+
source_volid=None,
486+
consistencygroup_id=None,
487+
source_replica=None,
488+
multiattach=False,
489+
scheduler_hints=None,
490+
)
491+
492+
self.assertEqual(self.columns, columns)
493+
self.assertEqual(self.datalist, data)
494+
self.volumes_mock.set_bootable.assert_called_with(
495+
self.new_volume.id, True)
496+
self.volumes_mock.update_readonly_flag.assert_called_with(
497+
self.new_volume.id, True)
498+
499+
def test_volume_create_with_nonbootable_and_readwrite(self):
500+
arglist = [
501+
'--non-bootable',
502+
'--read-write',
503+
'--size', str(self.new_volume.size),
504+
self.new_volume.name,
505+
]
506+
verifylist = [
507+
('bootable', False),
508+
('non_bootable', True),
509+
('read_only', False),
510+
('read_write', True),
511+
('size', self.new_volume.size),
512+
('name', self.new_volume.name),
513+
]
514+
515+
parsed_args = self.check_parser(
516+
self.cmd, arglist, verifylist)
517+
518+
columns, data = self.cmd.take_action(parsed_args)
519+
520+
self.volumes_mock.create.assert_called_with(
521+
size=self.new_volume.size,
522+
snapshot_id=None,
523+
name=self.new_volume.name,
524+
description=None,
525+
volume_type=None,
526+
user_id=None,
527+
project_id=None,
528+
availability_zone=None,
529+
metadata=None,
530+
imageRef=None,
531+
source_volid=None,
532+
consistencygroup_id=None,
533+
source_replica=None,
534+
multiattach=False,
535+
scheduler_hints=None,
536+
)
537+
538+
self.assertEqual(self.columns, columns)
539+
self.assertEqual(self.datalist, data)
540+
self.volumes_mock.set_bootable.assert_called_with(
541+
self.new_volume.id, False)
542+
self.volumes_mock.update_readonly_flag.assert_called_with(
543+
self.new_volume.id, False)
544+
545+
@mock.patch.object(volume.LOG, 'error')
546+
def test_volume_create_with_bootable_and_readonly_fail(
547+
self, mock_error):
548+
549+
self.volumes_mock.set_bootable.side_effect = (
550+
exceptions.CommandError())
551+
552+
self.volumes_mock.update_readonly_flag.side_effect = (
553+
exceptions.CommandError())
554+
555+
arglist = [
556+
'--bootable',
557+
'--read-only',
558+
'--size', str(self.new_volume.size),
559+
self.new_volume.name,
560+
]
561+
verifylist = [
562+
('bootable', True),
563+
('non_bootable', False),
564+
('read_only', True),
565+
('read_write', False),
566+
('size', self.new_volume.size),
567+
('name', self.new_volume.name),
568+
]
569+
570+
parsed_args = self.check_parser(
571+
self.cmd, arglist, verifylist)
572+
573+
columns, data = self.cmd.take_action(parsed_args)
574+
575+
self.volumes_mock.create.assert_called_with(
576+
size=self.new_volume.size,
577+
snapshot_id=None,
578+
name=self.new_volume.name,
579+
description=None,
580+
volume_type=None,
581+
user_id=None,
582+
project_id=None,
583+
availability_zone=None,
584+
metadata=None,
585+
imageRef=None,
586+
source_volid=None,
587+
consistencygroup_id=None,
588+
source_replica=None,
589+
multiattach=False,
590+
scheduler_hints=None,
591+
)
592+
593+
self.assertEqual(2, mock_error.call_count)
594+
self.assertEqual(self.columns, columns)
595+
self.assertEqual(self.datalist, data)
596+
self.volumes_mock.set_bootable.assert_called_with(
597+
self.new_volume.id, True)
598+
self.volumes_mock.update_readonly_flag.assert_called_with(
599+
self.new_volume.id, True)
600+
453601
def test_volume_create_with_source_replicated(self):
454602
self.volumes_mock.get.return_value = self.new_volume
455603
arglist = [

openstackclient/volume/v1/volume.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,28 @@ def get_parser(self, prog_name):
114114
help=_('Set a property on this volume '
115115
'(repeat option to set multiple properties)'),
116116
)
117+
bootable_group = parser.add_mutually_exclusive_group()
118+
bootable_group.add_argument(
119+
"--bootable",
120+
action="store_true",
121+
help=_("Mark volume as bootable")
122+
)
123+
bootable_group.add_argument(
124+
"--non-bootable",
125+
action="store_true",
126+
help=_("Mark volume as non-bootable (default)")
127+
)
128+
readonly_group = parser.add_mutually_exclusive_group()
129+
readonly_group.add_argument(
130+
"--read-only",
131+
action="store_true",
132+
help=_("Set volume to read-only access mode")
133+
)
134+
readonly_group.add_argument(
135+
"--read-write",
136+
action="store_true",
137+
help=_("Set volume to read-write access mode (default)")
138+
)
117139

118140
return parser
119141

@@ -166,6 +188,22 @@ def take_action(self, parsed_args):
166188
parsed_args.property,
167189
image,
168190
)
191+
192+
if parsed_args.bootable or parsed_args.non_bootable:
193+
try:
194+
volume_client.volumes.set_bootable(
195+
volume.id, parsed_args.bootable)
196+
except Exception as e:
197+
LOG.error(_("Failed to set volume bootable property: %s"), e)
198+
if parsed_args.read_only or parsed_args.read_write:
199+
try:
200+
volume_client.volumes.update_readonly_flag(
201+
volume.id,
202+
parsed_args.read_only)
203+
except Exception as e:
204+
LOG.error(_("Failed to set volume read-only access "
205+
"mode flag: %s"), e)
206+
169207
# Map 'metadata' column to 'properties'
170208
volume._info.update(
171209
{

0 commit comments

Comments
 (0)