Skip to content

Commit b9d6310

Browse files
committed
Add openstack server create --boot-from-volume option
This adds a --boot-from-volume option to the server create command which is used with the --image or --image-property option and will create a volume-backed server from the specified image with the specified size. Similar to the --volume option, the created root volume will not be deleted when the server is deleted. The --boot-from-volume option is not allowed with the --volume option since they both create a block device mapping with boot_index=0. Change-Id: I88c590361cb232c1df7b5bb010dcea307080d34c Story: 2006302 Task: 36017
1 parent c28ed25 commit b9d6310

4 files changed

Lines changed: 142 additions & 2 deletions

File tree

openstackclient/compute/v2/server.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,19 @@ def get_parser(self, prog_name):
567567
'only by default. (supported by --os-compute-api-version '
568568
'2.74 or above)'),
569569
)
570+
parser.add_argument(
571+
'--boot-from-volume',
572+
metavar='<volume-size>',
573+
type=int,
574+
help=_('When used in conjunction with the ``--image`` or '
575+
'``--image-property`` option, this option automatically '
576+
'creates a block device mapping with a boot index of 0 '
577+
'and tells the compute service to create a volume of the '
578+
'given size (in GB) from the specified image and use it '
579+
'as the root disk of the server. The root volume will not '
580+
'be deleted when the server is deleted. This option is '
581+
'mutually exclusive with the ``--volume`` option.')
582+
)
570583
parser.add_argument(
571584
'--block-device-mapping',
572585
metavar='<dev-name=mapping>',
@@ -730,6 +743,10 @@ def _match_image(image_api, wanted_properties):
730743
# Lookup parsed_args.volume
731744
volume = None
732745
if parsed_args.volume:
746+
# --volume and --boot-from-volume are mutually exclusive.
747+
if parsed_args.boot_from_volume:
748+
raise exceptions.CommandError(
749+
_('--volume is not allowed with --boot-from-volume'))
733750
volume = utils.find_resource(
734751
volume_client.volumes,
735752
parsed_args.volume,
@@ -739,8 +756,6 @@ def _match_image(image_api, wanted_properties):
739756
flavor = utils.find_resource(compute_client.flavors,
740757
parsed_args.flavor)
741758

742-
boot_args = [parsed_args.server_name, image, flavor]
743-
744759
files = {}
745760
for f in parsed_args.file:
746761
dst, src = f.split('=', 1)
@@ -787,6 +802,20 @@ def _match_image(image_api, wanted_properties):
787802
'source_type': 'volume',
788803
'destination_type': 'volume'
789804
}]
805+
elif parsed_args.boot_from_volume:
806+
# Tell nova to create a root volume from the image provided.
807+
block_device_mapping_v2 = [{
808+
'uuid': image.id,
809+
'boot_index': '0',
810+
'source_type': 'image',
811+
'destination_type': 'volume',
812+
'volume_size': parsed_args.boot_from_volume
813+
}]
814+
# If booting from volume we do not pass an image to compute.
815+
image = None
816+
817+
boot_args = [parsed_args.server_name, image, flavor]
818+
790819
# Handle block device by device name order, like: vdb -> vdc -> vdd
791820
for dev_name in sorted(six.iterkeys(parsed_args.block_device_mapping)):
792821
dev_map = parsed_args.block_device_mapping[dev_name]

openstackclient/tests/functional/compute/v2/test_server.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,82 @@ def test_server_boot_with_bdm_image(self):
701701
# the attached volume had been deleted
702702
pass
703703

704+
def test_boot_from_volume(self):
705+
# Tests creating a server using --image and --boot-from-volume where
706+
# the compute service will create a root volume of the specified size
707+
# using the provided image, attach it as the root disk for the server
708+
# and not delete the volume when the server is deleted.
709+
server_name = uuid.uuid4().hex
710+
server = json.loads(self.openstack(
711+
'server create -f json ' +
712+
'--flavor ' + self.flavor_name + ' ' +
713+
'--image ' + self.image_name + ' ' +
714+
'--boot-from-volume 1 ' + # create a 1GB volume from the image
715+
self.network_arg + ' ' +
716+
'--wait ' +
717+
server_name
718+
))
719+
self.assertIsNotNone(server["id"])
720+
self.assertEqual(
721+
server_name,
722+
server['name'],
723+
)
724+
self.wait_for_status(server_name, 'ACTIVE')
725+
726+
# check server volumes_attached, format is
727+
# {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",}
728+
cmd_output = json.loads(self.openstack(
729+
'server show -f json ' +
730+
server_name
731+
))
732+
volumes_attached = cmd_output['volumes_attached']
733+
self.assertTrue(volumes_attached.startswith('id='))
734+
attached_volume_id = volumes_attached.replace('id=', '')
735+
# Don't leak the volume when the test exits.
736+
self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id)
737+
738+
# Since the server is volume-backed the GET /servers/{server_id}
739+
# response will have image=''.
740+
self.assertEqual('', cmd_output['image'])
741+
742+
# check the volume that attached on server
743+
cmd_output = json.loads(self.openstack(
744+
'volume show -f json ' +
745+
attached_volume_id
746+
))
747+
# The volume size should be what we specified on the command line.
748+
self.assertEqual(1, int(cmd_output['size']))
749+
attachments = cmd_output['attachments']
750+
self.assertEqual(
751+
1,
752+
len(attachments),
753+
)
754+
self.assertEqual(
755+
server['id'],
756+
attachments[0]['server_id'],
757+
)
758+
self.assertEqual(
759+
"in-use",
760+
cmd_output['status'],
761+
)
762+
# TODO(mriedem): If we can parse the volume_image_metadata field from
763+
# the volume show output we could assert the image_name is what we
764+
# specified. volume_image_metadata is something like this:
765+
# {u'container_format': u'bare', u'min_ram': u'0',
766+
# u'disk_format': u'qcow2', u'image_name': u'cirros-0.4.0-x86_64-disk',
767+
# u'image_id': u'05496c83-e2df-4c2f-9e48-453b6e49160d',
768+
# u'checksum': u'443b7623e27ecf03dc9e01ee93f67afe', u'min_disk': u'0',
769+
# u'size': u'12716032'}
770+
771+
# delete server, then check the attached volume was not deleted
772+
self.openstack('server delete --wait ' + server_name)
773+
cmd_output = json.loads(self.openstack(
774+
'volume show -f json ' +
775+
attached_volume_id
776+
))
777+
# check the volume is in 'available' status
778+
self.assertEqual('available', cmd_output['status'])
779+
704780
def test_server_create_with_none_network(self):
705781
"""Test server create with none network option."""
706782
server_name = uuid.uuid4().hex

openstackclient/tests/unit/compute/v2/test_server.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1698,6 +1698,33 @@ def test_server_create_with_block_device_mapping_no_uuid(self):
16981698
self.cmd.take_action,
16991699
parsed_args)
17001700

1701+
def test_server_create_volume_boot_from_volume_conflict(self):
1702+
# Tests that specifying --volume and --boot-from-volume results in
1703+
# an error. Since --boot-from-volume requires --image or
1704+
# --image-property but those are in a mutex group with --volume, we
1705+
# only specify --volume and --boot-from-volume for this test since
1706+
# the validation is not handled with argparse.
1707+
arglist = [
1708+
'--flavor', self.flavor.id,
1709+
'--volume', 'volume1',
1710+
'--boot-from-volume', '1',
1711+
self.new_server.name,
1712+
]
1713+
verifylist = [
1714+
('flavor', self.flavor.id),
1715+
('volume', 'volume1'),
1716+
('boot_from_volume', 1),
1717+
('config_drive', False),
1718+
('server_name', self.new_server.name),
1719+
]
1720+
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
1721+
1722+
ex = self.assertRaises(exceptions.CommandError,
1723+
self.cmd.take_action, parsed_args)
1724+
# Assert it is the error we expect.
1725+
self.assertIn('--volume is not allowed with --boot-from-volume',
1726+
six.text_type(ex))
1727+
17011728
def test_server_create_image_property(self):
17021729
arglist = [
17031730
'--image-property', 'hypervisor_type=qemu',
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
features:
3+
- |
4+
Add ``--boot-from-volume`` option to the ``server create`` command
5+
to create a volume-backed server from the specified image with the
6+
specified size when used in conjunction with the ``--image`` or
7+
``--image-property`` options.
8+
[Story `2006302 <https://storyboard.openstack.org/#!/story/2006302>`_]

0 commit comments

Comments
 (0)