placement/placement/tests/fixtures.py
Chris Dent 2fc321aea3 Move rc_cache onto RequestContext
Experimentation led to the discovery (as described in the story
noted below) that the global RC_CACHE is not safe in a multi-process
environment, which is common for a web service designed to scale
horizontally.

In rare circumstances it is possible for a custom resource class
to be deleted in one process but still appear to exist in another.
For many situations this wouldn't really matter, but there are
cases, even more rare, where it would be possible to write allocations
or resource provider inventory using the wrong resource class
id.

On the related story, a variety of options were discussed to fix
this. Reading through the code this one (which is option 2) was
the only one that proved workable in a relatively straightforward
fashion: Have a per request cache.

To that end, when a RequestContext is created (per request) the
resource class table is scanned to create a cache. Because the
context is local to this request, we no longer need to do any
locking around the cache, either when we create it or when we clear
it: The caller is linear.

The cost of this is that now every single request starts with a
scan of the resource class table. This isn't horrible: if we
had no cache at all we'd be reading rows from that table multiple
times throughout any request (read or write).

We should probably do some performance analysis to see what the
impact of this might be. The perfload jobs may be able to give
a limited sense of what the impact is, but profiling will be
required for accuracy.

It is the case that the functional tests seem a bit slower because
of that additional db query.

Change-Id: I409a5e819a72d64e66ee390e4528da0c503d8d05
Story: 2006232
Task: 35833
2019-07-18 11:04:48 +01:00

79 lines
3.1 KiB
Python

# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# 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.
"""Fixtures for Placement tests."""
from __future__ import absolute_import
from oslo_config import cfg
from oslo_db.sqlalchemy import test_fixtures
from placement.db.sqlalchemy import migration
from placement import db_api as placement_db
from placement import deploy
from placement.objects import resource_class
from placement.objects import trait
class Database(test_fixtures.GeneratesSchema, test_fixtures.AdHocDbFixture):
def __init__(self, conf_fixture, set_config=False):
"""Create a database fixture."""
super(Database, self).__init__()
if set_config:
try:
conf_fixture.register_opt(
cfg.StrOpt('connection'), group='placement_database')
except cfg.DuplicateOptError:
# already registered
pass
conf_fixture.config(connection='sqlite://',
group='placement_database')
self.conf_fixture = conf_fixture
self.get_engine = placement_db.get_placement_engine
placement_db.configure(self.conf_fixture.conf)
def get_enginefacade(self):
return placement_db.placement_context_manager
def generate_schema_create_all(self, engine):
# note: at this point in oslo_db's fixtures, the incoming
# Engine has **not** been associated with the global
# context manager yet.
migration.create_schema(engine)
# so, to work around that placement's setup code really wants to
# use the enginefacade, we will patch the engine into it early.
# oslo_db is going to patch it anyway later. So the bug in oslo.db
# is that code these days really wants the facade to be set up fully
# when it's time to create the database. When oslo_db's fixtures
# were written, enginefacade was not in use yet so it was not
# anticipated that everyone would be doing things this way
_reset_facade = placement_db.placement_context_manager.patch_engine(
engine)
self.addCleanup(_reset_facade)
# Make sure db flags are correct at both the start and finish
# of the test.
self.addCleanup(self.cleanup)
self.cleanup()
# Sync traits and resource classes.
deploy.update_database(self.conf_fixture.conf)
def cleanup(self):
trait._TRAITS_SYNCED = False
resource_class._RESOURCE_CLASSES_SYNCED = False