Skip to content

Commit 6027ae1

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Create request spec, build request and mappings in one transaction"
2 parents 50c5fa3 + 85f8d03 commit 6027ae1

File tree

3 files changed

+110
-17
lines changed

3 files changed

+110
-17
lines changed

nova/compute/api.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
from nova import context as nova_context
5757
from nova import crypto
5858
from nova.db import base
59+
from nova.db.sqlalchemy import api as db_api
5960
from nova import exception
6061
from nova import exception_wrapper
6162
from nova import hooks
@@ -927,6 +928,20 @@ def _validate_and_build_base_options(self, context, instance_type,
927928
return (base_options, max_network_count, key_pair, security_groups,
928929
network_metadata)
929930

931+
@staticmethod
932+
@db_api.api_context_manager.writer
933+
def _create_reqspec_buildreq_instmapping(context, rs, br, im):
934+
"""Create the request spec, build request, and instance mapping in a
935+
single database transaction.
936+
937+
The RequestContext must be passed in to this method so that the
938+
database transaction context manager decorator will nest properly and
939+
include each create() into the same transaction context.
940+
"""
941+
rs.create()
942+
br.create()
943+
im.create()
944+
930945
def _provision_instances(self, context, instance_type, min_count,
931946
max_count, base_options, boot_meta, security_groups,
932947
block_device_mapping, shutdown_terminate,
@@ -968,7 +983,6 @@ def _provision_instances(self, context, instance_type, min_count,
968983
# spec as this is how the conductor knows how many were in this
969984
# batch.
970985
req_spec.num_instances = num_instances
971-
req_spec.create()
972986

973987
# NOTE(stephenfin): The network_metadata field is not persisted
974988
# and is therefore set after 'create' is called.
@@ -1001,7 +1015,6 @@ def _provision_instances(self, context, instance_type, min_count,
10011015
project_id=instance.project_id,
10021016
block_device_mappings=block_device_mapping,
10031017
tags=instance_tags)
1004-
build_request.create()
10051018

10061019
# Create an instance_mapping. The null cell_mapping indicates
10071020
# that the instance doesn't yet exist in a cell, and lookups
@@ -1014,7 +1027,14 @@ def _provision_instances(self, context, instance_type, min_count,
10141027
inst_mapping.project_id = context.project_id
10151028
inst_mapping.user_id = context.user_id
10161029
inst_mapping.cell_mapping = None
1017-
inst_mapping.create()
1030+
1031+
# Create the request spec, build request, and instance mapping
1032+
# records in a single transaction so that if a DBError is
1033+
# raised from any of them, all INSERTs will be rolled back and
1034+
# no orphaned records will be left behind.
1035+
self._create_reqspec_buildreq_instmapping(context, req_spec,
1036+
build_request,
1037+
inst_mapping)
10181038

10191039
instances_to_build.append(
10201040
(req_spec, build_request, inst_mapping))
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
import mock
14+
from oslo_utils.fixture import uuidsentinel as uuids
15+
16+
from nova.compute import api as compute_api
17+
from nova import context as nova_context
18+
from nova import exception
19+
from nova import objects
20+
from nova import test
21+
from nova.tests import fixtures as nova_fixtures
22+
23+
24+
class ComputeAPITestCase(test.NoDBTestCase):
25+
USES_DB_SELF = True
26+
27+
def setUp(self):
28+
super(ComputeAPITestCase, self).setUp()
29+
self.useFixture(nova_fixtures.Database(database='api'))
30+
31+
@mock.patch('nova.objects.instance_mapping.InstanceMapping.create')
32+
def test_reqspec_buildreq_instmapping_single_transaction(self,
33+
mock_create):
34+
# Simulate a DBError during an INSERT by raising an exception from the
35+
# InstanceMapping.create method.
36+
mock_create.side_effect = test.TestingException('oops')
37+
38+
ctxt = nova_context.RequestContext('fake-user', 'fake-project')
39+
rs = objects.RequestSpec(context=ctxt, instance_uuid=uuids.inst)
40+
# project_id and instance cannot be None
41+
br = objects.BuildRequest(context=ctxt, instance_uuid=uuids.inst,
42+
project_id=ctxt.project_id,
43+
instance=objects.Instance())
44+
im = objects.InstanceMapping(context=ctxt, instance_uuid=uuids.inst)
45+
46+
self.assertRaises(
47+
test.TestingException,
48+
compute_api.API._create_reqspec_buildreq_instmapping, ctxt, rs, br,
49+
im)
50+
51+
# Since the instance mapping failed to INSERT, we should not have
52+
# written a request spec record or a build request record.
53+
self.assertRaises(
54+
exception.RequestSpecNotFound,
55+
objects.RequestSpec.get_by_instance_uuid, ctxt, uuids.inst)
56+
self.assertRaises(
57+
exception.BuildRequestNotFound,
58+
objects.BuildRequest.get_by_instance_uuid, ctxt, uuids.inst)

nova/tests/unit/compute/test_compute_api.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4714,6 +4714,9 @@ def test_provision_instances_with_keypair(self, mock_im, mock_instance,
47144714
mock_br, mock_rs):
47154715
fake_keypair = objects.KeyPair(name='test')
47164716

4717+
@mock.patch.object(self.compute_api,
4718+
'_create_reqspec_buildreq_instmapping',
4719+
new=mock.MagicMock())
47174720
@mock.patch('nova.compute.utils.check_num_instances_quota')
47184721
@mock.patch.object(self.compute_api, 'security_group_api')
47194722
@mock.patch.object(self.compute_api,
@@ -4748,16 +4751,16 @@ def do_test(mock_cbdm, mock_bdm_v, mock_cdb, mock_sg, mock_cniq):
47484751
do_test()
47494752

47504753
def test_provision_instances_creates_build_request(self):
4754+
@mock.patch.object(self.compute_api,
4755+
'_create_reqspec_buildreq_instmapping')
47514756
@mock.patch.object(objects.Instance, 'create')
47524757
@mock.patch('nova.compute.utils.check_num_instances_quota')
47534758
@mock.patch.object(self.compute_api.security_group_api,
47544759
'ensure_default')
47554760
@mock.patch.object(objects.RequestSpec, 'from_components')
4756-
@mock.patch.object(objects.BuildRequest, 'create')
4757-
@mock.patch.object(objects.InstanceMapping, 'create')
4758-
def do_test(_mock_inst_mapping_create, mock_build_req,
4759-
mock_req_spec_from_components, _mock_ensure_default,
4760-
mock_check_num_inst_quota, mock_inst_create):
4761+
def do_test(mock_req_spec_from_components, _mock_ensure_default,
4762+
mock_check_num_inst_quota, mock_inst_create,
4763+
mock_create_rs_br_im):
47614764

47624765
min_count = 1
47634766
max_count = 2
@@ -4829,11 +4832,14 @@ def do_test(_mock_inst_mapping_create, mock_build_req,
48294832
br.instance.project_id)
48304833
self.assertEqual(1, br.block_device_mappings[0].id)
48314834
self.assertEqual(br.instance.uuid, br.tags[0].resource_id)
4832-
br.create.assert_called_with()
4835+
mock_create_rs_br_im.assert_any_call(ctxt, rs, br, im)
48334836

48344837
do_test()
48354838

48364839
def test_provision_instances_creates_instance_mapping(self):
4840+
@mock.patch.object(self.compute_api,
4841+
'_create_reqspec_buildreq_instmapping',
4842+
new=mock.MagicMock())
48374843
@mock.patch('nova.compute.utils.check_num_instances_quota')
48384844
@mock.patch.object(objects.Instance, 'create', new=mock.MagicMock())
48394845
@mock.patch.object(self.compute_api.security_group_api,
@@ -4844,8 +4850,6 @@ def test_provision_instances_creates_instance_mapping(self):
48444850
new=mock.MagicMock())
48454851
@mock.patch.object(objects.RequestSpec, 'from_components',
48464852
mock.MagicMock())
4847-
@mock.patch.object(objects.BuildRequest, 'create',
4848-
new=mock.MagicMock())
48494853
@mock.patch('nova.objects.InstanceMapping')
48504854
def do_test(mock_inst_mapping, mock_check_num_inst_quota):
48514855
inst_mapping_mock = mock.MagicMock()
@@ -4928,6 +4932,8 @@ def test_provision_instances_cleans_up_when_volume_invalid(self,
49284932
_mock_cinder_reserve_volume,
49294933
_mock_cinder_check_availability_zone, _mock_cinder_get,
49304934
_mock_get_min_ver_cells):
4935+
@mock.patch.object(self.compute_api,
4936+
'_create_reqspec_buildreq_instmapping')
49314937
@mock.patch('nova.compute.utils.check_num_instances_quota')
49324938
@mock.patch.object(objects, 'Instance')
49334939
@mock.patch.object(self.compute_api.security_group_api,
@@ -4938,7 +4944,8 @@ def test_provision_instances_cleans_up_when_volume_invalid(self,
49384944
@mock.patch.object(objects, 'InstanceMapping')
49394945
def do_test(mock_inst_mapping, mock_build_req,
49404946
mock_req_spec_from_components, _mock_create_bdm,
4941-
_mock_ensure_default, mock_inst, mock_check_num_inst_quota):
4947+
_mock_ensure_default, mock_inst, mock_check_num_inst_quota,
4948+
mock_create_rs_br_im):
49424949

49434950
min_count = 1
49444951
max_count = 2
@@ -5005,9 +5012,10 @@ def do_test(mock_inst_mapping, mock_build_req,
50055012
check_server_group_quota, filter_properties,
50065013
None, tags, trusted_certs, False)
50075014
# First instance, build_req, mapping is created and destroyed
5008-
self.assertTrue(build_req_mocks[0].create.called)
5015+
mock_create_rs_br_im.assert_called_once_with(ctxt, req_spec_mock,
5016+
build_req_mocks[0],
5017+
inst_map_mocks[0])
50095018
self.assertTrue(build_req_mocks[0].destroy.called)
5010-
self.assertTrue(inst_map_mocks[0].create.called)
50115019
self.assertTrue(inst_map_mocks[0].destroy.called)
50125020
# Second instance, build_req, mapping is not created nor destroyed
50135021
self.assertFalse(inst_mocks[1].create.called)
@@ -5032,6 +5040,8 @@ def test_provision_instances_cleans_up_when_volume_invalid_new_flow(self,
50325040
_mock_bdm, _mock_cinder_attach_create,
50335041
_mock_cinder_check_availability_zone, _mock_cinder_get,
50345042
_mock_get_min_ver_cells, _mock_get_min_ver):
5043+
@mock.patch.object(self.compute_api,
5044+
'_create_reqspec_buildreq_instmapping')
50355045
@mock.patch('nova.compute.utils.check_num_instances_quota')
50365046
@mock.patch.object(objects, 'Instance')
50375047
@mock.patch.object(self.compute_api.security_group_api,
@@ -5042,7 +5052,8 @@ def test_provision_instances_cleans_up_when_volume_invalid_new_flow(self,
50425052
@mock.patch.object(objects, 'InstanceMapping')
50435053
def do_test(mock_inst_mapping, mock_build_req,
50445054
mock_req_spec_from_components, _mock_create_bdm,
5045-
_mock_ensure_default, mock_inst, mock_check_num_inst_quota):
5055+
_mock_ensure_default, mock_inst, mock_check_num_inst_quota,
5056+
mock_create_rs_br_im):
50465057

50475058
min_count = 1
50485059
max_count = 2
@@ -5109,9 +5120,10 @@ def do_test(mock_inst_mapping, mock_build_req,
51095120
check_server_group_quota, filter_properties,
51105121
None, tags, trusted_certs, False)
51115122
# First instance, build_req, mapping is created and destroyed
5112-
self.assertTrue(build_req_mocks[0].create.called)
5123+
mock_create_rs_br_im.assert_called_once_with(ctxt, req_spec_mock,
5124+
build_req_mocks[0],
5125+
inst_map_mocks[0])
51135126
self.assertTrue(build_req_mocks[0].destroy.called)
5114-
self.assertTrue(inst_map_mocks[0].create.called)
51155127
self.assertTrue(inst_map_mocks[0].destroy.called)
51165128
# Second instance, build_req, mapping is not created nor destroyed
51175129
self.assertFalse(inst_mocks[1].create.called)
@@ -5122,6 +5134,9 @@ def do_test(mock_inst_mapping, mock_build_req,
51225134
do_test()
51235135

51245136
def test_provision_instances_creates_reqspec_with_secgroups(self):
5137+
@mock.patch.object(self.compute_api,
5138+
'_create_reqspec_buildreq_instmapping',
5139+
new=mock.MagicMock())
51255140
@mock.patch('nova.compute.utils.check_num_instances_quota')
51265141
@mock.patch.object(self.compute_api, 'security_group_api')
51275142
@mock.patch.object(compute_api, 'objects')

0 commit comments

Comments
 (0)