diff options
| author | Matt Riedemann <mriedem.os@gmail.com> | 2019-08-01 15:11:29 -0400 |
|---|---|---|
| committer | Matt Riedemann <mriedem.os@gmail.com> | 2019-08-09 16:44:46 +0000 |
| commit | b9d63105566c84db11a976846844ad7b3a0b331e (patch) | |
| tree | 66e9aeee501de4c87a33aafd6c088b9f11b3c0f8 /openstackclient | |
| parent | c28ed25e3a6f2d9cf5fc349c544354a9d07aa957 (diff) | |
| download | python-openstackclient-b9d63105566c84db11a976846844ad7b3a0b331e.tar.gz | |
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
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/compute/v2/server.py | 33 | ||||
| -rw-r--r-- | openstackclient/tests/functional/compute/v2/test_server.py | 76 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_server.py | 27 |
3 files changed, 134 insertions, 2 deletions
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index fee2b27d..95c2f28a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -568,6 +568,19 @@ class CreateServer(command.ShowOne): '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>', action=parseractions.KeyValueAction, @@ -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] diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 67e2a66e..2bca70d0 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -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 diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index cd12db06..ae6b6e40 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -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', |
