Compute Add build_instance hook in compute manager
The build_instance hook provides a hook close to the actual spawn of an instance. It distinguishes itself from the create_instance hook because, in the case of a successful build, the instance will exist on the hypervisor when the build_instance post hook is called, instead of simply just being scheduled as is the case with the create_instance post hook. This is useful as some details may not be available until after scheduling. Co-Authored-By: Corey Wright <corey.wright@rackspace.com> Change-Id: Ic12abf6315bbc6b010b8222ef296ec65cb4b4f95
This commit is contained in:
26
nova/compute/build_results.py
Normal file
26
nova/compute/build_results.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Copyright 2014 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""Possible results from instance build
|
||||||
|
|
||||||
|
Results represent the ultimate result of an attempt to build an instance.
|
||||||
|
|
||||||
|
Results describe whether an instance was actually built, failed to build, or
|
||||||
|
was rescheduled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
ACTIVE = 'active' # Instance is running
|
||||||
|
FAILED = 'failed' # Instance failed to build and was not rescheduled
|
||||||
|
RESCHEDULED = 'rescheduled' # Instance failed to build, but was rescheduled
|
@@ -51,6 +51,7 @@ from nova import block_device
|
|||||||
from nova.cells import rpcapi as cells_rpcapi
|
from nova.cells import rpcapi as cells_rpcapi
|
||||||
from nova.cloudpipe import pipelib
|
from nova.cloudpipe import pipelib
|
||||||
from nova import compute
|
from nova import compute
|
||||||
|
from nova.compute import build_results
|
||||||
from nova.compute import power_state
|
from nova.compute import power_state
|
||||||
from nova.compute import resource_tracker
|
from nova.compute import resource_tracker
|
||||||
from nova.compute import rpcapi as compute_rpcapi
|
from nova.compute import rpcapi as compute_rpcapi
|
||||||
@@ -2008,6 +2009,7 @@ class ComputeManager(manager.Manager):
|
|||||||
requested_networks, security_groups,
|
requested_networks, security_groups,
|
||||||
block_device_mapping, node, limits)
|
block_device_mapping, node, limits)
|
||||||
|
|
||||||
|
@hooks.add_hook('build_instance')
|
||||||
@wrap_exception()
|
@wrap_exception()
|
||||||
@reverts_task_state
|
@reverts_task_state
|
||||||
@wrap_instance_event
|
@wrap_instance_event
|
||||||
@@ -2027,10 +2029,10 @@ class ComputeManager(manager.Manager):
|
|||||||
except exception.InstanceNotFound:
|
except exception.InstanceNotFound:
|
||||||
msg = 'Instance disappeared before build.'
|
msg = 'Instance disappeared before build.'
|
||||||
LOG.debug(msg, instance=instance)
|
LOG.debug(msg, instance=instance)
|
||||||
return
|
return build_results.FAILED
|
||||||
except exception.UnexpectedTaskStateError as e:
|
except exception.UnexpectedTaskStateError as e:
|
||||||
LOG.debug(e.format_message(), instance=instance)
|
LOG.debug(e.format_message(), instance=instance)
|
||||||
return
|
return build_results.FAILED
|
||||||
|
|
||||||
# b64 decode the files to inject:
|
# b64 decode the files to inject:
|
||||||
decoded_files = self._decode_files(injected_files)
|
decoded_files = self._decode_files(injected_files)
|
||||||
@@ -2048,6 +2050,7 @@ class ComputeManager(manager.Manager):
|
|||||||
decoded_files, admin_password, requested_networks,
|
decoded_files, admin_password, requested_networks,
|
||||||
security_groups, block_device_mapping, node, limits,
|
security_groups, block_device_mapping, node, limits,
|
||||||
filter_properties)
|
filter_properties)
|
||||||
|
return build_results.ACTIVE
|
||||||
except exception.RescheduledException as e:
|
except exception.RescheduledException as e:
|
||||||
LOG.debug(e.format_message(), instance=instance)
|
LOG.debug(e.format_message(), instance=instance)
|
||||||
retry = filter_properties.get('retry', None)
|
retry = filter_properties.get('retry', None)
|
||||||
@@ -2060,7 +2063,7 @@ class ComputeManager(manager.Manager):
|
|||||||
compute_utils.add_instance_fault_from_exc(context,
|
compute_utils.add_instance_fault_from_exc(context,
|
||||||
instance, e, sys.exc_info())
|
instance, e, sys.exc_info())
|
||||||
self._set_instance_error_state(context, instance)
|
self._set_instance_error_state(context, instance)
|
||||||
return
|
return build_results.FAILED
|
||||||
retry['exc'] = traceback.format_exception(*sys.exc_info())
|
retry['exc'] = traceback.format_exception(*sys.exc_info())
|
||||||
# NOTE(comstud): Deallocate networks if the driver wants
|
# NOTE(comstud): Deallocate networks if the driver wants
|
||||||
# us to do so.
|
# us to do so.
|
||||||
@@ -2075,12 +2078,14 @@ class ComputeManager(manager.Manager):
|
|||||||
image, filter_properties, admin_password,
|
image, filter_properties, admin_password,
|
||||||
injected_files, requested_networks, security_groups,
|
injected_files, requested_networks, security_groups,
|
||||||
block_device_mapping)
|
block_device_mapping)
|
||||||
|
return build_results.RESCHEDULED
|
||||||
except (exception.InstanceNotFound,
|
except (exception.InstanceNotFound,
|
||||||
exception.UnexpectedDeletingTaskStateError):
|
exception.UnexpectedDeletingTaskStateError):
|
||||||
msg = 'Instance disappeared during build.'
|
msg = 'Instance disappeared during build.'
|
||||||
LOG.debug(msg, instance=instance)
|
LOG.debug(msg, instance=instance)
|
||||||
self._cleanup_allocated_networks(context, instance,
|
self._cleanup_allocated_networks(context, instance,
|
||||||
requested_networks)
|
requested_networks)
|
||||||
|
return build_results.FAILED
|
||||||
except exception.BuildAbortException as e:
|
except exception.BuildAbortException as e:
|
||||||
LOG.exception(e.format_message(), instance=instance)
|
LOG.exception(e.format_message(), instance=instance)
|
||||||
self._cleanup_allocated_networks(context, instance,
|
self._cleanup_allocated_networks(context, instance,
|
||||||
@@ -2090,6 +2095,7 @@ class ComputeManager(manager.Manager):
|
|||||||
compute_utils.add_instance_fault_from_exc(context, instance,
|
compute_utils.add_instance_fault_from_exc(context, instance,
|
||||||
e, sys.exc_info())
|
e, sys.exc_info())
|
||||||
self._set_instance_error_state(context, instance)
|
self._set_instance_error_state(context, instance)
|
||||||
|
return build_results.FAILED
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Should not reach here.
|
# Should not reach here.
|
||||||
msg = _LE('Unexpected build failure, not rescheduling build.')
|
msg = _LE('Unexpected build failure, not rescheduling build.')
|
||||||
@@ -2101,6 +2107,7 @@ class ComputeManager(manager.Manager):
|
|||||||
compute_utils.add_instance_fault_from_exc(context, instance,
|
compute_utils.add_instance_fault_from_exc(context, instance,
|
||||||
e, sys.exc_info())
|
e, sys.exc_info())
|
||||||
self._set_instance_error_state(context, instance)
|
self._set_instance_error_state(context, instance)
|
||||||
|
return build_results.FAILED
|
||||||
|
|
||||||
def _build_and_run_instance(self, context, instance, image, injected_files,
|
def _build_and_run_instance(self, context, instance, image, injected_files,
|
||||||
admin_password, requested_networks, security_groups,
|
admin_password, requested_networks, security_groups,
|
||||||
|
@@ -11447,3 +11447,8 @@ class ComputeHooksTestCase(test.BaseHookTestCase):
|
|||||||
def test_create_instance_has_hook(self):
|
def test_create_instance_has_hook(self):
|
||||||
create_func = compute_api.API.create
|
create_func = compute_api.API.create
|
||||||
self.assert_has_hook('create_instance', create_func)
|
self.assert_has_hook('create_instance', create_func)
|
||||||
|
|
||||||
|
def test_build_instance_has_hook(self):
|
||||||
|
build_instance_func = (compute_manager.ComputeManager.
|
||||||
|
_do_build_and_run_instance)
|
||||||
|
self.assert_has_hook('build_instance', build_instance_func)
|
||||||
|
@@ -24,6 +24,7 @@ from oslo import messaging
|
|||||||
from oslo.utils import importutils
|
from oslo.utils import importutils
|
||||||
from oslo.utils import timeutils
|
from oslo.utils import timeutils
|
||||||
|
|
||||||
|
from nova.compute import build_results
|
||||||
from nova.compute import manager
|
from nova.compute import manager
|
||||||
from nova.compute import power_state
|
from nova.compute import power_state
|
||||||
from nova.compute import task_states
|
from nova.compute import task_states
|
||||||
@@ -2192,8 +2193,19 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
exc_val=mox.IgnoreArg(), exc_tb=mox.IgnoreArg(),
|
exc_val=mox.IgnoreArg(), exc_tb=mox.IgnoreArg(),
|
||||||
want_result=False)
|
want_result=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _assert_build_instance_hook_called(mock_hooks, result):
|
||||||
|
# NOTE(coreywright): we want to test the return value of
|
||||||
|
# _do_build_and_run_instance, but it doesn't bubble all the way up, so
|
||||||
|
# mock the hooking, which allows us to test that too, though a little
|
||||||
|
# too intimately
|
||||||
|
mock_hooks.setdefault().run_post.assert_called_once_with(
|
||||||
|
'build_instance', result, mock.ANY, mock.ANY, f=None)
|
||||||
|
|
||||||
|
@mock.patch('nova.hooks._HOOKS')
|
||||||
@mock.patch('nova.utils.spawn_n')
|
@mock.patch('nova.utils.spawn_n')
|
||||||
def test_build_and_run_instance_called_with_proper_args(self, mock_spawn):
|
def test_build_and_run_instance_called_with_proper_args(self, mock_spawn,
|
||||||
|
mock_hooks):
|
||||||
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
||||||
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
||||||
self._do_build_instance_update()
|
self._do_build_instance_update()
|
||||||
@@ -2214,6 +2226,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
security_groups=self.security_groups,
|
security_groups=self.security_groups,
|
||||||
block_device_mapping=self.block_device_mapping, node=self.node,
|
block_device_mapping=self.block_device_mapping, node=self.node,
|
||||||
limits=self.limits)
|
limits=self.limits)
|
||||||
|
self._assert_build_instance_hook_called(mock_hooks,
|
||||||
|
build_results.ACTIVE)
|
||||||
|
|
||||||
# This test when sending an icehouse compatible rpc call to juno compute
|
# This test when sending an icehouse compatible rpc call to juno compute
|
||||||
# node, NetworkRequest object can load from three items tuple.
|
# node, NetworkRequest object can load from three items tuple.
|
||||||
@@ -2242,8 +2256,9 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
self.assertEqual('10.0.0.1', str(requested_network.address))
|
self.assertEqual('10.0.0.1', str(requested_network.address))
|
||||||
self.assertEqual('fake_port_id', requested_network.port_id)
|
self.assertEqual('fake_port_id', requested_network.port_id)
|
||||||
|
|
||||||
|
@mock.patch('nova.hooks._HOOKS')
|
||||||
@mock.patch('nova.utils.spawn_n')
|
@mock.patch('nova.utils.spawn_n')
|
||||||
def test_build_abort_exception(self, mock_spawn):
|
def test_build_abort_exception(self, mock_spawn, mock_hooks):
|
||||||
def fake_spawn(f, *args, **kwargs):
|
def fake_spawn(f, *args, **kwargs):
|
||||||
# NOTE(danms): Simulate the detached nature of spawn so that
|
# NOTE(danms): Simulate the detached nature of spawn so that
|
||||||
# we confirm that the inner task has the fault logic
|
# we confirm that the inner task has the fault logic
|
||||||
@@ -2288,9 +2303,12 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
security_groups=self.security_groups,
|
security_groups=self.security_groups,
|
||||||
block_device_mapping=self.block_device_mapping, node=self.node,
|
block_device_mapping=self.block_device_mapping, node=self.node,
|
||||||
limits=self.limits)
|
limits=self.limits)
|
||||||
|
self._assert_build_instance_hook_called(mock_hooks,
|
||||||
|
build_results.FAILED)
|
||||||
|
|
||||||
|
@mock.patch('nova.hooks._HOOKS')
|
||||||
@mock.patch('nova.utils.spawn_n')
|
@mock.patch('nova.utils.spawn_n')
|
||||||
def test_rescheduled_exception(self, mock_spawn):
|
def test_rescheduled_exception(self, mock_spawn, mock_hooks):
|
||||||
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
||||||
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
||||||
self.mox.StubOutWithMock(self.compute, '_set_instance_error_state')
|
self.mox.StubOutWithMock(self.compute, '_set_instance_error_state')
|
||||||
@@ -2320,6 +2338,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
security_groups=self.security_groups,
|
security_groups=self.security_groups,
|
||||||
block_device_mapping=self.block_device_mapping, node=self.node,
|
block_device_mapping=self.block_device_mapping, node=self.node,
|
||||||
limits=self.limits)
|
limits=self.limits)
|
||||||
|
self._assert_build_instance_hook_called(mock_hooks,
|
||||||
|
build_results.RESCHEDULED)
|
||||||
|
|
||||||
def test_rescheduled_exception_with_non_ascii_exception(self):
|
def test_rescheduled_exception_with_non_ascii_exception(self):
|
||||||
exc = exception.NovaException(u's\xe9quence')
|
exc = exception.NovaException(u's\xe9quence')
|
||||||
@@ -2355,8 +2375,9 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
self.block_device_mapping, self.node,
|
self.block_device_mapping, self.node,
|
||||||
self.limits, self.filter_properties)
|
self.limits, self.filter_properties)
|
||||||
|
|
||||||
|
@mock.patch('nova.hooks._HOOKS')
|
||||||
@mock.patch('nova.utils.spawn_n')
|
@mock.patch('nova.utils.spawn_n')
|
||||||
def test_rescheduled_exception_without_retry(self, mock_spawn):
|
def test_rescheduled_exception_without_retry(self, mock_spawn, mock_hooks):
|
||||||
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
||||||
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
||||||
self.mox.StubOutWithMock(compute_utils, 'add_instance_fault_from_exc')
|
self.mox.StubOutWithMock(compute_utils, 'add_instance_fault_from_exc')
|
||||||
@@ -2389,9 +2410,13 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
security_groups=self.security_groups,
|
security_groups=self.security_groups,
|
||||||
block_device_mapping=self.block_device_mapping, node=self.node,
|
block_device_mapping=self.block_device_mapping, node=self.node,
|
||||||
limits=self.limits)
|
limits=self.limits)
|
||||||
|
self._assert_build_instance_hook_called(mock_hooks,
|
||||||
|
build_results.FAILED)
|
||||||
|
|
||||||
|
@mock.patch('nova.hooks._HOOKS')
|
||||||
@mock.patch('nova.utils.spawn_n')
|
@mock.patch('nova.utils.spawn_n')
|
||||||
def test_rescheduled_exception_do_not_deallocate_network(self, mock_spawn):
|
def test_rescheduled_exception_do_not_deallocate_network(self, mock_spawn,
|
||||||
|
mock_hooks):
|
||||||
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
||||||
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
||||||
self.mox.StubOutWithMock(self.compute.driver,
|
self.mox.StubOutWithMock(self.compute.driver,
|
||||||
@@ -2425,9 +2450,13 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
security_groups=self.security_groups,
|
security_groups=self.security_groups,
|
||||||
block_device_mapping=self.block_device_mapping, node=self.node,
|
block_device_mapping=self.block_device_mapping, node=self.node,
|
||||||
limits=self.limits)
|
limits=self.limits)
|
||||||
|
self._assert_build_instance_hook_called(mock_hooks,
|
||||||
|
build_results.RESCHEDULED)
|
||||||
|
|
||||||
|
@mock.patch('nova.hooks._HOOKS')
|
||||||
@mock.patch('nova.utils.spawn_n')
|
@mock.patch('nova.utils.spawn_n')
|
||||||
def test_rescheduled_exception_deallocate_network(self, mock_spawn):
|
def test_rescheduled_exception_deallocate_network(self, mock_spawn,
|
||||||
|
mock_hooks):
|
||||||
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
||||||
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
self.mox.StubOutWithMock(self.compute, '_build_and_run_instance')
|
||||||
self.mox.StubOutWithMock(self.compute.driver,
|
self.mox.StubOutWithMock(self.compute.driver,
|
||||||
@@ -2463,6 +2492,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
security_groups=self.security_groups,
|
security_groups=self.security_groups,
|
||||||
block_device_mapping=self.block_device_mapping, node=self.node,
|
block_device_mapping=self.block_device_mapping, node=self.node,
|
||||||
limits=self.limits)
|
limits=self.limits)
|
||||||
|
self._assert_build_instance_hook_called(mock_hooks,
|
||||||
|
build_results.RESCHEDULED)
|
||||||
|
|
||||||
def _test_build_and_run_exceptions(self, exc, set_error=False,
|
def _test_build_and_run_exceptions(self, exc, set_error=False,
|
||||||
cleanup_volumes=False):
|
cleanup_volumes=False):
|
||||||
@@ -2492,7 +2523,13 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
self._instance_action_events()
|
self._instance_action_events()
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
with mock.patch('nova.utils.spawn_n') as mock_spawn:
|
with contextlib.nested(
|
||||||
|
mock.patch('nova.utils.spawn_n'),
|
||||||
|
mock.patch('nova.hooks._HOOKS')
|
||||||
|
) as (
|
||||||
|
mock_spawn,
|
||||||
|
mock_hooks
|
||||||
|
):
|
||||||
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
mock_spawn.side_effect = lambda f, *a, **k: f(*a, **k)
|
||||||
self.compute.build_and_run_instance(self.context, self.instance,
|
self.compute.build_and_run_instance(self.context, self.instance,
|
||||||
self.image, request_spec={},
|
self.image, request_spec={},
|
||||||
@@ -2503,6 +2540,8 @@ class ComputeManagerBuildInstanceTestCase(test.NoDBTestCase):
|
|||||||
security_groups=self.security_groups,
|
security_groups=self.security_groups,
|
||||||
block_device_mapping=self.block_device_mapping, node=self.node,
|
block_device_mapping=self.block_device_mapping, node=self.node,
|
||||||
limits=self.limits)
|
limits=self.limits)
|
||||||
|
self._assert_build_instance_hook_called(mock_hooks,
|
||||||
|
build_results.FAILED)
|
||||||
|
|
||||||
def test_build_and_run_notfound_exception(self):
|
def test_build_and_run_notfound_exception(self):
|
||||||
self._test_build_and_run_exceptions(exception.InstanceNotFound(
|
self._test_build_and_run_exceptions(exception.InstanceNotFound(
|
||||||
|
Reference in New Issue
Block a user