From 0d454089c9f507bf128673a07d780fbfdba981f0 Mon Sep 17 00:00:00 2001 From: Brian Elliott Date: Wed, 31 Jul 2013 15:53:26 -0500 Subject: [PATCH] Support programmatic use of disk config extension Allow servers to be created, resized, and rebuilt with the disk_config option: http://api.openstack.org/api-ref.html#ext-os-disk-config There is a separate extension that exists for disk config already, but it only works via the novaclient CLI interface, not via programmatic use of python-novaclient as a library. assert changes in tests/v1_1/fakes allow for other data in rebuild and resize requests. Change-Id: I8051ffb8747cf5c67b0199d22fbbe80306b01499 --- novaclient/base.py | 7 +++- novaclient/tests/v1_1/fakes.py | 5 +-- novaclient/tests/v1_1/test_servers.py | 52 +++++++++++++++++++++++++++ novaclient/v1_1/servers.py | 22 +++++++++--- 4 files changed, 79 insertions(+), 7 deletions(-) diff --git a/novaclient/base.py b/novaclient/base.py index bf309b3c9..5d4b583e4 100644 --- a/novaclient/base.py +++ b/novaclient/base.py @@ -231,7 +231,7 @@ class BootingManagerWithFind(ManagerWithFind): max_count=None, security_groups=None, key_name=None, availability_zone=None, block_device_mapping=None, nics=None, scheduler_hints=None, config_drive=None, admin_pass=None, - **kwargs): + disk_config=None, **kwargs): """ Create (boot) a new server. @@ -264,6 +264,8 @@ class BootingManagerWithFind(ManagerWithFind): :param config_drive: (optional extension) value for config drive either boolean, or volume-id :param admin_pass: admin password for the server. + :param disk_config: (optional extension) control how the disk is + partitioned when the server is created. """ body = {"server": { "name": name, @@ -360,6 +362,9 @@ class BootingManagerWithFind(ManagerWithFind): all_net_data.append(net_data) body['server']['networks'] = all_net_data + if disk_config is not None: + body['server']['OS-DCF:diskConfig'] = disk_config + return self._create(resource_url, body, response_key, return_raw=return_raw, **kwargs) diff --git a/novaclient/tests/v1_1/fakes.py b/novaclient/tests/v1_1/fakes.py index 9a9686c77..3e1e955a2 100644 --- a/novaclient/tests/v1_1/fakes.py +++ b/novaclient/tests/v1_1/fakes.py @@ -473,10 +473,11 @@ class FakeHTTPClient(base_client.HTTPClient): keys = body[action].keys() if 'adminPass' in keys: keys.remove('adminPass') - assert keys == ['imageRef'] + assert 'imageRef' in keys _body = self.get_servers_1234()[2] elif action == 'resize': - assert body[action].keys() == ['flavorRef'] + keys = body[action].keys() + assert 'flavorRef' in keys elif action == 'confirmResize': assert body[action] is None # This one method returns a different response code diff --git a/novaclient/tests/v1_1/test_servers.py b/novaclient/tests/v1_1/test_servers.py index e04e7887b..701c88126 100644 --- a/novaclient/tests/v1_1/test_servers.py +++ b/novaclient/tests/v1_1/test_servers.py @@ -135,6 +135,29 @@ class ServersTest(utils.TestCase): cs.assert_called('POST', '/servers') self.assertTrue(isinstance(s, servers.Server)) + def _create_disk_config(self, disk_config): + s = cs.servers.create( + name="My server", + image=1, + flavor=1, + disk_config=disk_config + ) + cs.assert_called('POST', '/servers') + self.assertTrue(isinstance(s, servers.Server)) + + # verify disk config param was used in the request: + last_request = cs.client.callstack[-1] + body = last_request[-1] + server = body['server'] + self.assertTrue('OS-DCF:diskConfig' in server) + self.assertEqual(disk_config, server['OS-DCF:diskConfig']) + + def test_create_server_disk_config_auto(self): + self._create_disk_config('AUTO') + + def test_create_server_disk_config_manual(self): + self._create_disk_config('MANUAL') + def test_update_server(self): s = cs.servers.get(1234) @@ -199,6 +222,29 @@ class ServersTest(utils.TestCase): cs.servers.rebuild(s, image=1, password='5678') cs.assert_called('POST', '/servers/1234/action') + def _rebuild_resize_disk_config(self, disk_config, operation="rebuild"): + s = cs.servers.get(1234) + + if operation == "rebuild": + s.rebuild(image=1, disk_config=disk_config) + elif operation == "resize": + s.resize(flavor=1, disk_config=disk_config) + cs.assert_called('POST', '/servers/1234/action') + + # verify disk config param was used in the request: + last_request = cs.client.callstack[-1] + body = last_request[-1] + + d = body[operation] + self.assertTrue('OS-DCF:diskConfig' in d) + self.assertEqual(disk_config, d['OS-DCF:diskConfig']) + + def test_rebuild_server_disk_config_auto(self): + self._rebuild_resize_disk_config('AUTO') + + def test_rebuild_server_disk_config_manual(self): + self._rebuild_resize_disk_config('MANUAL') + def test_resize_server(self): s = cs.servers.get(1234) s.resize(flavor=1) @@ -206,6 +252,12 @@ class ServersTest(utils.TestCase): cs.servers.resize(s, flavor=1) cs.assert_called('POST', '/servers/1234/action') + def test_resize_server_disk_config_auto(self): + self._rebuild_resize_disk_config('AUTO', 'resize') + + def test_resize_server_disk_config_manual(self): + self._rebuild_resize_disk_config('MANUAL', 'resize') + def test_confirm_resized_server(self): s = cs.servers.get(1234) s.confirm_resize() diff --git a/novaclient/v1_1/servers.py b/novaclient/v1_1/servers.py index fa44e9683..4f09d9df4 100644 --- a/novaclient/v1_1/servers.py +++ b/novaclient/v1_1/servers.py @@ -570,7 +570,7 @@ class ServerManager(base.BootingManagerWithFind): max_count=None, security_groups=None, userdata=None, key_name=None, availability_zone=None, block_device_mapping=None, nics=None, scheduler_hints=None, - config_drive=None, **kwargs): + config_drive=None, disk_config=None, **kwargs): # TODO(anthony): indicate in doc string if param is an extension # and/or optional """ @@ -604,6 +604,9 @@ class ServerManager(base.BootingManagerWithFind): specified by the client to help boot an instance :param config_drive: (optional extension) value for config drive either boolean, or volume-id + :param disk_config: (optional extension) control how the disk is + partitioned when the server is created. possible + values are 'AUTO' or 'MANUAL'. """ if not min_count: min_count = 1 @@ -620,7 +623,7 @@ class ServerManager(base.BootingManagerWithFind): max_count=max_count, security_groups=security_groups, key_name=key_name, availability_zone=availability_zone, scheduler_hints=scheduler_hints, config_drive=config_drive, - **kwargs) + disk_config=disk_config, **kwargs) if block_device_mapping: resource_url = "/os-volumes_boot" @@ -674,17 +677,23 @@ class ServerManager(base.BootingManagerWithFind): """ self._action('reboot', server, {'type': reboot_type}) - def rebuild(self, server, image, password=None, **kwargs): + def rebuild(self, server, image, password=None, disk_config=None, + **kwargs): """ Rebuild -- shut down and then re-image -- a server. :param server: The :class:`Server` (or its ID) to share onto. :param image: the :class:`Image` (or its ID) to re-image with. :param password: string to set as password on the rebuilt server. + :param disk_config: partitioning mode to use on the rebuilt server. + Valid values are 'AUTO' or 'MANUAL' """ body = {'imageRef': base.getid(image)} if password is not None: body['adminPass'] = password + if disk_config is not None: + body['OS-DCF:diskConfig'] = disk_config + _resp, body = self._action('rebuild', server, body, **kwargs) return Server(self, body['server']) @@ -696,12 +705,14 @@ class ServerManager(base.BootingManagerWithFind): """ self._action('migrate', server) - def resize(self, server, flavor, **kwargs): + def resize(self, server, flavor, disk_config=None, **kwargs): """ Resize a server's resources. :param server: The :class:`Server` (or its ID) to share onto. :param flavor: the :class:`Flavor` (or its ID) to resize to. + :param disk_config: partitioning mode to use on the rebuilt server. + Valid values are 'AUTO' or 'MANUAL' Until a resize event is confirmed with :meth:`confirm_resize`, the old server will be kept around and you'll be able to roll back to the old @@ -709,6 +720,9 @@ class ServerManager(base.BootingManagerWithFind): automatically confirmed after 24 hours. """ info = {'flavorRef': base.getid(flavor)} + if disk_config is not None: + info['OS-DCF:diskConfig'] = disk_config + self._action('resize', server, info=info, **kwargs) def confirm_resize(self, server):