Merge "Add openstack server create --boot-from-volume option"
This commit is contained in:
commit
8ef2602447
@ -567,6 +567,19 @@ class CreateServer(command.ShowOne):
|
||||
'only by default. (supported by --os-compute-api-version '
|
||||
'2.74 or above)'),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--boot-from-volume',
|
||||
metavar='<volume-size>',
|
||||
type=int,
|
||||
help=_('When used in conjunction with the ``--image`` or '
|
||||
'``--image-property`` option, this option automatically '
|
||||
'creates a block device mapping with a boot index of 0 '
|
||||
'and tells the compute service to create a volume of the '
|
||||
'given size (in GB) from the specified image and use it '
|
||||
'as the root disk of the server. The root volume will not '
|
||||
'be deleted when the server is deleted. This option is '
|
||||
'mutually exclusive with the ``--volume`` option.')
|
||||
)
|
||||
parser.add_argument(
|
||||
'--block-device-mapping',
|
||||
metavar='<dev-name=mapping>',
|
||||
@ -730,6 +743,10 @@ class CreateServer(command.ShowOne):
|
||||
# Lookup parsed_args.volume
|
||||
volume = None
|
||||
if parsed_args.volume:
|
||||
# --volume and --boot-from-volume are mutually exclusive.
|
||||
if parsed_args.boot_from_volume:
|
||||
raise exceptions.CommandError(
|
||||
_('--volume is not allowed with --boot-from-volume'))
|
||||
volume = utils.find_resource(
|
||||
volume_client.volumes,
|
||||
parsed_args.volume,
|
||||
@ -739,8 +756,6 @@ class CreateServer(command.ShowOne):
|
||||
flavor = utils.find_resource(compute_client.flavors,
|
||||
parsed_args.flavor)
|
||||
|
||||
boot_args = [parsed_args.server_name, image, flavor]
|
||||
|
||||
files = {}
|
||||
for f in parsed_args.file:
|
||||
dst, src = f.split('=', 1)
|
||||
@ -787,6 +802,20 @@ class CreateServer(command.ShowOne):
|
||||
'source_type': 'volume',
|
||||
'destination_type': 'volume'
|
||||
}]
|
||||
elif parsed_args.boot_from_volume:
|
||||
# Tell nova to create a root volume from the image provided.
|
||||
block_device_mapping_v2 = [{
|
||||
'uuid': image.id,
|
||||
'boot_index': '0',
|
||||
'source_type': 'image',
|
||||
'destination_type': 'volume',
|
||||
'volume_size': parsed_args.boot_from_volume
|
||||
}]
|
||||
# If booting from volume we do not pass an image to compute.
|
||||
image = None
|
||||
|
||||
boot_args = [parsed_args.server_name, image, flavor]
|
||||
|
||||
# Handle block device by device name order, like: vdb -> vdc -> vdd
|
||||
for dev_name in sorted(six.iterkeys(parsed_args.block_device_mapping)):
|
||||
dev_map = parsed_args.block_device_mapping[dev_name]
|
||||
|
@ -701,6 +701,82 @@ class ServerTests(common.ComputeTestCase):
|
||||
# the attached volume had been deleted
|
||||
pass
|
||||
|
||||
def test_boot_from_volume(self):
|
||||
# Tests creating a server using --image and --boot-from-volume where
|
||||
# the compute service will create a root volume of the specified size
|
||||
# using the provided image, attach it as the root disk for the server
|
||||
# and not delete the volume when the server is deleted.
|
||||
server_name = uuid.uuid4().hex
|
||||
server = json.loads(self.openstack(
|
||||
'server create -f json ' +
|
||||
'--flavor ' + self.flavor_name + ' ' +
|
||||
'--image ' + self.image_name + ' ' +
|
||||
'--boot-from-volume 1 ' + # create a 1GB volume from the image
|
||||
self.network_arg + ' ' +
|
||||
'--wait ' +
|
||||
server_name
|
||||
))
|
||||
self.assertIsNotNone(server["id"])
|
||||
self.assertEqual(
|
||||
server_name,
|
||||
server['name'],
|
||||
)
|
||||
self.wait_for_status(server_name, 'ACTIVE')
|
||||
|
||||
# check server volumes_attached, format is
|
||||
# {"volumes_attached": "id='2518bc76-bf0b-476e-ad6b-571973745bb5'",}
|
||||
cmd_output = json.loads(self.openstack(
|
||||
'server show -f json ' +
|
||||
server_name
|
||||
))
|
||||
volumes_attached = cmd_output['volumes_attached']
|
||||
self.assertTrue(volumes_attached.startswith('id='))
|
||||
attached_volume_id = volumes_attached.replace('id=', '')
|
||||
# Don't leak the volume when the test exits.
|
||||
self.addCleanup(self.openstack, 'volume delete ' + attached_volume_id)
|
||||
|
||||
# Since the server is volume-backed the GET /servers/{server_id}
|
||||
# response will have image=''.
|
||||
self.assertEqual('', cmd_output['image'])
|
||||
|
||||
# check the volume that attached on server
|
||||
cmd_output = json.loads(self.openstack(
|
||||
'volume show -f json ' +
|
||||
attached_volume_id
|
||||
))
|
||||
# The volume size should be what we specified on the command line.
|
||||
self.assertEqual(1, int(cmd_output['size']))
|
||||
attachments = cmd_output['attachments']
|
||||
self.assertEqual(
|
||||
1,
|
||||
len(attachments),
|
||||
)
|
||||
self.assertEqual(
|
||||
server['id'],
|
||||
attachments[0]['server_id'],
|
||||
)
|
||||
self.assertEqual(
|
||||
"in-use",
|
||||
cmd_output['status'],
|
||||
)
|
||||
# TODO(mriedem): If we can parse the volume_image_metadata field from
|
||||
# the volume show output we could assert the image_name is what we
|
||||
# specified. volume_image_metadata is something like this:
|
||||
# {u'container_format': u'bare', u'min_ram': u'0',
|
||||
# u'disk_format': u'qcow2', u'image_name': u'cirros-0.4.0-x86_64-disk',
|
||||
# u'image_id': u'05496c83-e2df-4c2f-9e48-453b6e49160d',
|
||||
# u'checksum': u'443b7623e27ecf03dc9e01ee93f67afe', u'min_disk': u'0',
|
||||
# u'size': u'12716032'}
|
||||
|
||||
# delete server, then check the attached volume was not deleted
|
||||
self.openstack('server delete --wait ' + server_name)
|
||||
cmd_output = json.loads(self.openstack(
|
||||
'volume show -f json ' +
|
||||
attached_volume_id
|
||||
))
|
||||
# check the volume is in 'available' status
|
||||
self.assertEqual('available', cmd_output['status'])
|
||||
|
||||
def test_server_create_with_none_network(self):
|
||||
"""Test server create with none network option."""
|
||||
server_name = uuid.uuid4().hex
|
||||
|
@ -1698,6 +1698,33 @@ class TestServerCreate(TestServer):
|
||||
self.cmd.take_action,
|
||||
parsed_args)
|
||||
|
||||
def test_server_create_volume_boot_from_volume_conflict(self):
|
||||
# Tests that specifying --volume and --boot-from-volume results in
|
||||
# an error. Since --boot-from-volume requires --image or
|
||||
# --image-property but those are in a mutex group with --volume, we
|
||||
# only specify --volume and --boot-from-volume for this test since
|
||||
# the validation is not handled with argparse.
|
||||
arglist = [
|
||||
'--flavor', self.flavor.id,
|
||||
'--volume', 'volume1',
|
||||
'--boot-from-volume', '1',
|
||||
self.new_server.name,
|
||||
]
|
||||
verifylist = [
|
||||
('flavor', self.flavor.id),
|
||||
('volume', 'volume1'),
|
||||
('boot_from_volume', 1),
|
||||
('config_drive', False),
|
||||
('server_name', self.new_server.name),
|
||||
]
|
||||
parsed_args = self.check_parser(self.cmd, arglist, verifylist)
|
||||
|
||||
ex = self.assertRaises(exceptions.CommandError,
|
||||
self.cmd.take_action, parsed_args)
|
||||
# Assert it is the error we expect.
|
||||
self.assertIn('--volume is not allowed with --boot-from-volume',
|
||||
six.text_type(ex))
|
||||
|
||||
def test_server_create_image_property(self):
|
||||
arglist = [
|
||||
'--image-property', 'hypervisor_type=qemu',
|
||||
|
@ -0,0 +1,8 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add ``--boot-from-volume`` option to the ``server create`` command
|
||||
to create a volume-backed server from the specified image with the
|
||||
specified size when used in conjunction with the ``--image`` or
|
||||
``--image-property`` options.
|
||||
[Story `2006302 <https://storyboard.openstack.org/#!/story/2006302>`_]
|
Loading…
Reference in New Issue
Block a user