Merge "Add 'openstack server evacuate' command"
This commit is contained in:
commit
4d3bad9f19
@ -10,6 +10,9 @@ Compute v2
|
|||||||
.. autoprogram-cliff:: openstack.compute.v2
|
.. autoprogram-cliff:: openstack.compute.v2
|
||||||
:command: server create
|
:command: server create
|
||||||
|
|
||||||
|
.. autoprogram-cliff:: openstack.compute.v2
|
||||||
|
:command: server evacuate
|
||||||
|
|
||||||
.. autoprogram-cliff:: openstack.compute.v2
|
.. autoprogram-cliff:: openstack.compute.v2
|
||||||
:command: server delete
|
:command: server delete
|
||||||
|
|
||||||
|
@ -2513,6 +2513,118 @@ class RebuildServer(command.ShowOne):
|
|||||||
return zip(*sorted(details.items()))
|
return zip(*sorted(details.items()))
|
||||||
|
|
||||||
|
|
||||||
|
class EvacuateServer(command.ShowOne):
|
||||||
|
_description = _("""Evacuate a server to a different host.
|
||||||
|
|
||||||
|
This command is used to recreate a server after the host it was on has failed.
|
||||||
|
It can only be used if the compute service that manages the server is down.
|
||||||
|
This command should only be used by an admin after they have confirmed that the
|
||||||
|
instance is not running on the failed host.
|
||||||
|
|
||||||
|
If the server instance was created with an ephemeral root disk on non-shared
|
||||||
|
storage the server will be rebuilt using the original glance image preserving
|
||||||
|
the ports and any attached data volumes.
|
||||||
|
|
||||||
|
If the server uses boot for volume or has its root disk on shared storage the
|
||||||
|
root disk will be preserved and reused for the evacuated instance on the new
|
||||||
|
host.""")
|
||||||
|
|
||||||
|
def get_parser(self, prog_name):
|
||||||
|
parser = super(EvacuateServer, self).get_parser(prog_name)
|
||||||
|
parser.add_argument(
|
||||||
|
'server',
|
||||||
|
metavar='<server>',
|
||||||
|
help=_('Server (name or ID)'),
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
'--wait', action='store_true',
|
||||||
|
help=_('Wait for evacuation to complete'),
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--host', metavar='<host>', default=None,
|
||||||
|
help=_(
|
||||||
|
'Set the preferred host on which to rebuild the evacuated '
|
||||||
|
'server. The host will be validated by the scheduler. '
|
||||||
|
'(supported by --os-compute-api-version 2.29 or above)'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
shared_storage_group = parser.add_mutually_exclusive_group()
|
||||||
|
shared_storage_group.add_argument(
|
||||||
|
'--password', metavar='<password>', default=None,
|
||||||
|
help=_(
|
||||||
|
'Set the password on the evacuated instance. This option is '
|
||||||
|
'mutually exclusive with the --shared-storage option'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
shared_storage_group.add_argument(
|
||||||
|
'--shared-storage', action='store_true', dest='shared_storage',
|
||||||
|
help=_(
|
||||||
|
'Indicate that the instance is on shared storage. '
|
||||||
|
'This will be auto-calculated with '
|
||||||
|
'--os-compute-api-version 2.14 and greater and should not '
|
||||||
|
'be used with later microversions. This option is mutually '
|
||||||
|
'exclusive with the --password option'
|
||||||
|
),
|
||||||
|
)
|
||||||
|
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
|
||||||
|
image_client = self.app.client_manager.image
|
||||||
|
|
||||||
|
if parsed_args.host:
|
||||||
|
if compute_client.api_version < api_versions.APIVersion('2.29'):
|
||||||
|
msg = _(
|
||||||
|
'--os-compute-api-version 2.29 or later is required '
|
||||||
|
'to specify a preferred host.'
|
||||||
|
)
|
||||||
|
raise exceptions.CommandError(msg)
|
||||||
|
|
||||||
|
if parsed_args.shared_storage:
|
||||||
|
if compute_client.api_version > api_versions.APIVersion('2.13'):
|
||||||
|
msg = _(
|
||||||
|
'--os-compute-api-version 2.13 or earlier is required '
|
||||||
|
'to specify shared-storage.'
|
||||||
|
)
|
||||||
|
raise exceptions.CommandError(msg)
|
||||||
|
|
||||||
|
kwargs = {
|
||||||
|
'host': parsed_args.host,
|
||||||
|
'password': parsed_args.password,
|
||||||
|
}
|
||||||
|
|
||||||
|
if compute_client.api_version <= api_versions.APIVersion('2.13'):
|
||||||
|
kwargs['on_shared_storage'] = parsed_args.shared_storage
|
||||||
|
|
||||||
|
server = utils.find_resource(
|
||||||
|
compute_client.servers, parsed_args.server)
|
||||||
|
|
||||||
|
server = server.evacuate(**kwargs)
|
||||||
|
|
||||||
|
if parsed_args.wait:
|
||||||
|
if utils.wait_for_status(
|
||||||
|
compute_client.servers.get,
|
||||||
|
server.id,
|
||||||
|
callback=_show_progress,
|
||||||
|
):
|
||||||
|
self.app.stdout.write(_('Complete\n'))
|
||||||
|
else:
|
||||||
|
LOG.error(_('Error evacuating server: %s'), server.id)
|
||||||
|
self.app.stdout.write(_('Error evacuating server\n'))
|
||||||
|
raise SystemExit
|
||||||
|
|
||||||
|
details = _prep_server_detail(
|
||||||
|
compute_client, image_client, server, refresh=False)
|
||||||
|
return zip(*sorted(details.items()))
|
||||||
|
|
||||||
|
|
||||||
class RemoveFixedIP(command.Command):
|
class RemoveFixedIP(command.Command):
|
||||||
_description = _("Remove fixed IP address from server")
|
_description = _("Remove fixed IP address from server")
|
||||||
|
|
||||||
|
@ -4984,6 +4984,167 @@ class TestServerRebuild(TestServer):
|
|||||||
self.cmd, arglist, verifylist)
|
self.cmd, arglist, verifylist)
|
||||||
|
|
||||||
|
|
||||||
|
class TestEvacuateServer(TestServer):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestEvacuateServer, self).setUp()
|
||||||
|
# Return value for utils.find_resource for image
|
||||||
|
self.image = image_fakes.FakeImage.create_one_image()
|
||||||
|
self.images_mock.get.return_value = self.image
|
||||||
|
|
||||||
|
# Fake the rebuilt new server.
|
||||||
|
attrs = {
|
||||||
|
'image': {
|
||||||
|
'id': self.image.id
|
||||||
|
},
|
||||||
|
'networks': {},
|
||||||
|
'adminPass': 'passw0rd',
|
||||||
|
}
|
||||||
|
new_server = compute_fakes.FakeServer.create_one_server(attrs=attrs)
|
||||||
|
|
||||||
|
# Fake the server to be rebuilt. The IDs of them should be the same.
|
||||||
|
attrs['id'] = new_server.id
|
||||||
|
methods = {
|
||||||
|
'evacuate': new_server,
|
||||||
|
}
|
||||||
|
self.server = compute_fakes.FakeServer.create_one_server(
|
||||||
|
attrs=attrs,
|
||||||
|
methods=methods
|
||||||
|
)
|
||||||
|
|
||||||
|
# Return value for utils.find_resource for server.
|
||||||
|
self.servers_mock.get.return_value = self.server
|
||||||
|
|
||||||
|
self.cmd = server.EvacuateServer(self.app, None)
|
||||||
|
|
||||||
|
def _test_evacuate(self, args, verify_args, evac_args):
|
||||||
|
parsed_args = self.check_parser(self.cmd, args, verify_args)
|
||||||
|
|
||||||
|
# Get the command object to test
|
||||||
|
self.cmd.take_action(parsed_args)
|
||||||
|
|
||||||
|
self.servers_mock.get.assert_called_with(self.server.id)
|
||||||
|
self.server.evacuate.assert_called_with(**evac_args)
|
||||||
|
|
||||||
|
def test_evacuate(self):
|
||||||
|
args = [
|
||||||
|
self.server.id,
|
||||||
|
]
|
||||||
|
verify_args = [
|
||||||
|
('server', self.server.id),
|
||||||
|
]
|
||||||
|
evac_args = {
|
||||||
|
'host': None, 'on_shared_storage': False, 'password': None,
|
||||||
|
}
|
||||||
|
self._test_evacuate(args, verify_args, evac_args)
|
||||||
|
|
||||||
|
def test_evacuate_with_password(self):
|
||||||
|
args = [
|
||||||
|
self.server.id,
|
||||||
|
'--password', 'password',
|
||||||
|
]
|
||||||
|
verify_args = [
|
||||||
|
('server', self.server.id),
|
||||||
|
('password', 'password'),
|
||||||
|
]
|
||||||
|
evac_args = {
|
||||||
|
'host': None, 'on_shared_storage': False, 'password': 'password',
|
||||||
|
}
|
||||||
|
self._test_evacuate(args, verify_args, evac_args)
|
||||||
|
|
||||||
|
def test_evacuate_with_host(self):
|
||||||
|
self.app.client_manager.compute.api_version = \
|
||||||
|
api_versions.APIVersion('2.29')
|
||||||
|
|
||||||
|
host = 'target-host'
|
||||||
|
args = [
|
||||||
|
self.server.id,
|
||||||
|
'--host', 'target-host',
|
||||||
|
]
|
||||||
|
verify_args = [
|
||||||
|
('server', self.server.id),
|
||||||
|
('host', 'target-host'),
|
||||||
|
]
|
||||||
|
evac_args = {'host': host, 'password': None}
|
||||||
|
|
||||||
|
self._test_evacuate(args, verify_args, evac_args)
|
||||||
|
|
||||||
|
def test_evacuate_with_host_pre_v229(self):
|
||||||
|
self.app.client_manager.compute.api_version = \
|
||||||
|
api_versions.APIVersion('2.28')
|
||||||
|
|
||||||
|
args = [
|
||||||
|
self.server.id,
|
||||||
|
'--host', 'target-host',
|
||||||
|
]
|
||||||
|
verify_args = [
|
||||||
|
('server', self.server.id),
|
||||||
|
('host', 'target-host'),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, args, verify_args)
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.CommandError,
|
||||||
|
self.cmd.take_action,
|
||||||
|
parsed_args)
|
||||||
|
|
||||||
|
def test_evacuate_without_share_storage(self):
|
||||||
|
self.app.client_manager.compute.api_version = \
|
||||||
|
api_versions.APIVersion('2.13')
|
||||||
|
|
||||||
|
args = [
|
||||||
|
self.server.id,
|
||||||
|
'--shared-storage'
|
||||||
|
]
|
||||||
|
verify_args = [
|
||||||
|
('server', self.server.id),
|
||||||
|
('shared_storage', True),
|
||||||
|
]
|
||||||
|
evac_args = {
|
||||||
|
'host': None, 'on_shared_storage': True, 'password': None,
|
||||||
|
}
|
||||||
|
self._test_evacuate(args, verify_args, evac_args)
|
||||||
|
|
||||||
|
def test_evacuate_without_share_storage_post_v213(self):
|
||||||
|
self.app.client_manager.compute.api_version = \
|
||||||
|
api_versions.APIVersion('2.14')
|
||||||
|
|
||||||
|
args = [
|
||||||
|
self.server.id,
|
||||||
|
'--shared-storage'
|
||||||
|
]
|
||||||
|
verify_args = [
|
||||||
|
('server', self.server.id),
|
||||||
|
('shared_storage', True),
|
||||||
|
]
|
||||||
|
parsed_args = self.check_parser(self.cmd, args, verify_args)
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exceptions.CommandError,
|
||||||
|
self.cmd.take_action,
|
||||||
|
parsed_args)
|
||||||
|
|
||||||
|
@mock.patch.object(common_utils, 'wait_for_status', return_value=True)
|
||||||
|
def test_evacuate_with_wait_ok(self, mock_wait_for_status):
|
||||||
|
args = [
|
||||||
|
self.server.id,
|
||||||
|
'--wait',
|
||||||
|
]
|
||||||
|
verify_args = [
|
||||||
|
('server', self.server.id),
|
||||||
|
('wait', True),
|
||||||
|
]
|
||||||
|
evac_args = {
|
||||||
|
'host': None, 'on_shared_storage': False, 'password': None,
|
||||||
|
}
|
||||||
|
self._test_evacuate(args, verify_args, evac_args)
|
||||||
|
mock_wait_for_status.assert_called_once_with(
|
||||||
|
self.servers_mock.get,
|
||||||
|
self.server.id,
|
||||||
|
callback=mock.ANY,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestServerRemoveFixedIP(TestServer):
|
class TestServerRemoveFixedIP(TestServer):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add ``server evacuate`` command. This command will recreate an instance
|
||||||
|
from scratch on a new host and is intended to be used when the original
|
||||||
|
host fails.
|
@ -103,6 +103,7 @@ openstack.compute.v2 =
|
|||||||
server_create = openstackclient.compute.v2.server:CreateServer
|
server_create = openstackclient.compute.v2.server:CreateServer
|
||||||
server_delete = openstackclient.compute.v2.server:DeleteServer
|
server_delete = openstackclient.compute.v2.server:DeleteServer
|
||||||
server_dump_create = openstackclient.compute.v2.server:CreateServerDump
|
server_dump_create = openstackclient.compute.v2.server:CreateServerDump
|
||||||
|
server_evacuate = openstackclient.compute.v2.server:EvacuateServer
|
||||||
server_list = openstackclient.compute.v2.server:ListServer
|
server_list = openstackclient.compute.v2.server:ListServer
|
||||||
server_lock = openstackclient.compute.v2.server:LockServer
|
server_lock = openstackclient.compute.v2.server:LockServer
|
||||||
server_migrate = openstackclient.compute.v2.server:MigrateServer
|
server_migrate = openstackclient.compute.v2.server:MigrateServer
|
||||||
|
Loading…
x
Reference in New Issue
Block a user