Merge "Add 'server shelve --offload', 'server shelve --wait' options"
This commit is contained in:
		@@ -114,7 +114,7 @@ service-force-down,compute service set --force,Force service to down.
 | 
			
		||||
service-list,compute service list,Show a list of all running services.
 | 
			
		||||
set-password,server set --root-password,Change the admin password for a server.
 | 
			
		||||
shelve,server shelve,Shelve a server.
 | 
			
		||||
shelve-offload,,Remove a shelved server from the compute node.
 | 
			
		||||
shelve-offload,shelve --offload,Remove a shelved server from the compute node.
 | 
			
		||||
show,server show,Show details about the given server.
 | 
			
		||||
ssh,server ssh,SSH into a server.
 | 
			
		||||
start,server start,Start the server(s).
 | 
			
		||||
 
 | 
			
		||||
		
		
			
  | 
@@ -3585,25 +3585,115 @@ class SetServer(command.Command):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShelveServer(command.Command):
 | 
			
		||||
    _description = _("Shelve server(s)")
 | 
			
		||||
    """Shelve and optionally offload server(s).
 | 
			
		||||
 | 
			
		||||
    Shelving a server creates a snapshot of the server and stores this
 | 
			
		||||
    snapshot before shutting down the server. This shelved server can then be
 | 
			
		||||
    offloaded or deleted from the host, freeing up remaining resources on the
 | 
			
		||||
    host, such as network interfaces. Shelved servers can be unshelved,
 | 
			
		||||
    restoring the server from the snapshot. Shelving is therefore useful where
 | 
			
		||||
    users wish to retain the UUID and IP of a server, without utilizing other
 | 
			
		||||
    resources or disks.
 | 
			
		||||
 | 
			
		||||
    Most clouds are configured to automatically offload shelved servers
 | 
			
		||||
    immediately or after a small delay. For clouds where this is not
 | 
			
		||||
    configured, or where the delay is larger, offloading can be manually
 | 
			
		||||
    specified. This is an admin-only operation by default.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def get_parser(self, prog_name):
 | 
			
		||||
        parser = super(ShelveServer, self).get_parser(prog_name)
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            'server',
 | 
			
		||||
            'servers',
 | 
			
		||||
            metavar='<server>',
 | 
			
		||||
            nargs='+',
 | 
			
		||||
            help=_('Server(s) to shelve (name or ID)'),
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--offload',
 | 
			
		||||
            action='store_true',
 | 
			
		||||
            default=False,
 | 
			
		||||
            help=_(
 | 
			
		||||
                'Remove the shelved server(s) from the host (admin only). '
 | 
			
		||||
                'Invoking this option on an unshelved server(s) will result '
 | 
			
		||||
                'in the server being shelved first'
 | 
			
		||||
            ),
 | 
			
		||||
        )
 | 
			
		||||
        parser.add_argument(
 | 
			
		||||
            '--wait',
 | 
			
		||||
            action='store_true',
 | 
			
		||||
            default=False,
 | 
			
		||||
            help=_('Wait for shelve and/or offload operation to complete'),
 | 
			
		||||
        )
 | 
			
		||||
        return parser
 | 
			
		||||
 | 
			
		||||
    def take_action(self, parsed_args):
 | 
			
		||||
 | 
			
		||||
        def _show_progress(progress):
 | 
			
		||||
            if progress:
 | 
			
		||||
                self.app.stdout.write('\rProgress: %s' % progress)
 | 
			
		||||
                self.app.stdout.flush()
 | 
			
		||||
 | 
			
		||||
        compute_client = self.app.client_manager.compute
 | 
			
		||||
        for server in parsed_args.server:
 | 
			
		||||
            utils.find_resource(
 | 
			
		||||
 | 
			
		||||
        for server in parsed_args.servers:
 | 
			
		||||
            server_obj = utils.find_resource(
 | 
			
		||||
                compute_client.servers,
 | 
			
		||||
                server,
 | 
			
		||||
            ).shelve()
 | 
			
		||||
            )
 | 
			
		||||
            if server_obj.status.lower() in ('shelved', 'shelved_offloaded'):
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            server_obj.shelve()
 | 
			
		||||
 | 
			
		||||
        # if we don't hav to wait, either because it was requested explicitly
 | 
			
		||||
        # or is required implicitly, then our job is done
 | 
			
		||||
        if not parsed_args.wait and not parsed_args.offload:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for server in parsed_args.servers:
 | 
			
		||||
            # TODO(stephenfin): We should wait for these in parallel using e.g.
 | 
			
		||||
            # https://review.opendev.org/c/openstack/osc-lib/+/762503/
 | 
			
		||||
            if not utils.wait_for_status(
 | 
			
		||||
                compute_client.servers.get, server_obj.id,
 | 
			
		||||
                success_status=('shelved', 'shelved_offloaded'),
 | 
			
		||||
                callback=_show_progress,
 | 
			
		||||
            ):
 | 
			
		||||
                LOG.error(_('Error shelving server: %s'), server_obj.id)
 | 
			
		||||
                self.app.stdout.write(
 | 
			
		||||
                    _('Error shelving server: %s\n') % server_obj.id)
 | 
			
		||||
                raise SystemExit
 | 
			
		||||
 | 
			
		||||
        if not parsed_args.offload:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for server in parsed_args.servers:
 | 
			
		||||
            server_obj = utils.find_resource(
 | 
			
		||||
                compute_client.servers,
 | 
			
		||||
                server,
 | 
			
		||||
            )
 | 
			
		||||
            if server_obj.status.lower() == 'shelved_offloaded':
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            server_obj.shelve_offload()
 | 
			
		||||
 | 
			
		||||
        if not parsed_args.wait:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for server in parsed_args.servers:
 | 
			
		||||
            # TODO(stephenfin): We should wait for these in parallel using e.g.
 | 
			
		||||
            # https://review.opendev.org/c/openstack/osc-lib/+/762503/
 | 
			
		||||
            if not utils.wait_for_status(
 | 
			
		||||
                compute_client.servers.get, server_obj.id,
 | 
			
		||||
                success_status=('shelved_offloaded',),
 | 
			
		||||
                callback=_show_progress,
 | 
			
		||||
            ):
 | 
			
		||||
                LOG.error(
 | 
			
		||||
                    _('Error offloading shelved server %s'), server_obj.id)
 | 
			
		||||
                self.app.stdout.write(
 | 
			
		||||
                    _('Error offloading shelved server: %s\n') % (
 | 
			
		||||
                        server_obj.id))
 | 
			
		||||
                raise SystemExit
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ShowServer(command.ShowOne):
 | 
			
		||||
 
 | 
			
		||||
@@ -43,7 +43,7 @@ class HelpTests(base.TestCase):
 | 
			
		||||
        ('server resize', 'Scale server to a new flavor'),
 | 
			
		||||
        ('server resume', 'Resume server(s)'),
 | 
			
		||||
        ('server set', 'Set server properties'),
 | 
			
		||||
        ('server shelve', 'Shelve server(s)'),
 | 
			
		||||
        ('server shelve', 'Shelve and optionally offload server(s)'),
 | 
			
		||||
        ('server show', 'Show server details'),
 | 
			
		||||
        ('server ssh', 'SSH to server'),
 | 
			
		||||
        ('server start', 'Start server(s).'),
 | 
			
		||||
 
 | 
			
		||||
@@ -6434,16 +6434,126 @@ class TestServerShelve(TestServer):
 | 
			
		||||
        # Get the command object to test
 | 
			
		||||
        self.cmd = server.ShelveServer(self.app, None)
 | 
			
		||||
 | 
			
		||||
        # Set shelve method to be tested.
 | 
			
		||||
        self.methods = {
 | 
			
		||||
    def test_shelve(self):
 | 
			
		||||
        server_info = {'status': 'ACTIVE'}
 | 
			
		||||
        server_methods = {
 | 
			
		||||
            'shelve': None,
 | 
			
		||||
            'shelve_offload': None,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    def test_shelve_one_server(self):
 | 
			
		||||
        self.run_method_with_servers('shelve', 1)
 | 
			
		||||
        server = compute_fakes.FakeServer.create_one_server(
 | 
			
		||||
            attrs=server_info, methods=server_methods)
 | 
			
		||||
        self.servers_mock.get.return_value = server
 | 
			
		||||
 | 
			
		||||
    def test_shelve_multi_servers(self):
 | 
			
		||||
        self.run_method_with_servers('shelve', 3)
 | 
			
		||||
        arglist = [server.name]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('servers', [server.name]),
 | 
			
		||||
            ('wait', False),
 | 
			
		||||
            ('offload', False),
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        result = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertIsNone(result)
 | 
			
		||||
 | 
			
		||||
        self.servers_mock.get.assert_called_once_with(server.name)
 | 
			
		||||
        server.shelve.assert_called_once_with()
 | 
			
		||||
        server.shelve_offload.assert_not_called()
 | 
			
		||||
 | 
			
		||||
    def test_shelve_already_shelved(self):
 | 
			
		||||
        server_info = {'status': 'SHELVED'}
 | 
			
		||||
        server_methods = {
 | 
			
		||||
            'shelve': None,
 | 
			
		||||
            'shelve_offload': None,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        server = compute_fakes.FakeServer.create_one_server(
 | 
			
		||||
            attrs=server_info, methods=server_methods)
 | 
			
		||||
        self.servers_mock.get.return_value = server
 | 
			
		||||
 | 
			
		||||
        arglist = [server.name]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('servers', [server.name]),
 | 
			
		||||
            ('wait', False),
 | 
			
		||||
            ('offload', False),
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        result = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertIsNone(result)
 | 
			
		||||
 | 
			
		||||
        self.servers_mock.get.assert_called_once_with(server.name)
 | 
			
		||||
        server.shelve.assert_not_called()
 | 
			
		||||
        server.shelve_offload.assert_not_called()
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(common_utils, 'wait_for_status', return_value=True)
 | 
			
		||||
    def test_shelve_with_wait(self, mock_wait_for_status):
 | 
			
		||||
        server_info = {'status': 'ACTIVE'}
 | 
			
		||||
        server_methods = {
 | 
			
		||||
            'shelve': None,
 | 
			
		||||
            'shelve_offload': None,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        server = compute_fakes.FakeServer.create_one_server(
 | 
			
		||||
            attrs=server_info, methods=server_methods)
 | 
			
		||||
        self.servers_mock.get.return_value = server
 | 
			
		||||
 | 
			
		||||
        arglist = ['--wait', server.name]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('servers', [server.name]),
 | 
			
		||||
            ('wait', True),
 | 
			
		||||
            ('offload', False),
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        result = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertIsNone(result)
 | 
			
		||||
 | 
			
		||||
        self.servers_mock.get.assert_called_once_with(server.name)
 | 
			
		||||
        server.shelve.assert_called_once_with()
 | 
			
		||||
        server.shelve_offload.assert_not_called()
 | 
			
		||||
        mock_wait_for_status.assert_called_once_with(
 | 
			
		||||
            self.servers_mock.get,
 | 
			
		||||
            server.id,
 | 
			
		||||
            callback=mock.ANY,
 | 
			
		||||
            success_status=('shelved', 'shelved_offloaded'),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    @mock.patch.object(common_utils, 'wait_for_status', return_value=True)
 | 
			
		||||
    def test_shelve_offload(self, mock_wait_for_status):
 | 
			
		||||
        server_info = {'status': 'ACTIVE'}
 | 
			
		||||
        server_methods = {
 | 
			
		||||
            'shelve': None,
 | 
			
		||||
            'shelve_offload': None,
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        server = compute_fakes.FakeServer.create_one_server(
 | 
			
		||||
            attrs=server_info, methods=server_methods)
 | 
			
		||||
        self.servers_mock.get.return_value = server
 | 
			
		||||
 | 
			
		||||
        arglist = ['--offload', server.name]
 | 
			
		||||
        verifylist = [
 | 
			
		||||
            ('servers', [server.name]),
 | 
			
		||||
            ('wait', False),
 | 
			
		||||
            ('offload', True),
 | 
			
		||||
        ]
 | 
			
		||||
        parsed_args = self.check_parser(self.cmd, arglist, verifylist)
 | 
			
		||||
 | 
			
		||||
        result = self.cmd.take_action(parsed_args)
 | 
			
		||||
        self.assertIsNone(result)
 | 
			
		||||
 | 
			
		||||
        self.servers_mock.get.assert_has_calls([
 | 
			
		||||
            mock.call(server.name),
 | 
			
		||||
            mock.call(server.name),
 | 
			
		||||
        ])
 | 
			
		||||
        server.shelve.assert_called_once_with()
 | 
			
		||||
        server.shelve_offload.assert_called_once_with()
 | 
			
		||||
        mock_wait_for_status.assert_called_once_with(
 | 
			
		||||
            self.servers_mock.get,
 | 
			
		||||
            server.id,
 | 
			
		||||
            callback=mock.ANY,
 | 
			
		||||
            success_status=('shelved', 'shelved_offloaded'),
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestServerShow(TestServer):
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,8 @@
 | 
			
		||||
---
 | 
			
		||||
features:
 | 
			
		||||
  - |
 | 
			
		||||
    Add support for ``--offload`` and ``--wait`` options for ``server shelve``.
 | 
			
		||||
    ``--offload`` allows users to explicitly request offloading of a shelved
 | 
			
		||||
    server in environments where automatic offloading is not configured, while
 | 
			
		||||
    ``--wait`` allows users to wait for the shelve and/or shelve offload
 | 
			
		||||
    operations to complete.
 | 
			
		||||
		Reference in New Issue
	
	Block a user