Files
nova/nova/tests/unit/api/openstack/test_wsgi_app.py
T
Balazs Gibizer ae064caf16 Validate [pci]alias at service startup
Both nova-api and nova-compute depends on the [pci]alias configuration.
These services loaded and validated the config lazily when it was
needed. This can late and hard to troubleshoot failures during instance
lifecycle operations due to simple config errors.

So this patch adds an early load of this config to nova-api and
nova-compute.

Related-Bug: #2102038
Related-Bug: #2111440
Change-Id: I5d5dc912ca24979067984c7cb53ceaded7daf236
2025-06-11 07:23:01 -07:00

153 lines
6.6 KiB
Python

# 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.
import tempfile
from unittest import mock
import fixtures
from oslo_config import fixture as config_fixture
from oslo_serialization import jsonutils
from oslotest import base
from nova.api.openstack import wsgi_app
from nova import exception
from nova import test
from nova.tests import fixtures as nova_fixtures
class WSGIAppTest(base.BaseTestCase):
_paste_config = """
[app:nova-api]
use = egg:Paste#static
document_root = /tmp
"""
def setUp(self):
# Ensure BaseTestCase's ConfigureLogging fixture is disabled since
# we're using our own (StandardLogging).
with fixtures.EnvironmentVariable('OS_LOG_CAPTURE', '0'):
super(WSGIAppTest, self).setUp()
self.stdlog = self.useFixture(nova_fixtures.StandardLogging())
self.conf = tempfile.NamedTemporaryFile(mode='w+t')
self.conf.write(self._paste_config.lstrip())
self.conf.seek(0)
self.conf.flush()
self.addCleanup(self.conf.close)
# Use of this fixture takes care of cleaning up config settings for
# subsequent tests.
self.useFixture(config_fixture.Config())
@mock.patch('sys.argv', return_value=mock.sentinel.argv)
@mock.patch('nova.db.api.api.configure')
@mock.patch('nova.db.main.api.configure')
@mock.patch('nova.api.openstack.wsgi_app._setup_service')
@mock.patch('nova.api.openstack.wsgi_app._get_config_files')
def test_init_application_called_twice(
self, mock_get_files, mock_setup, mock_main_db_configure,
mock_api_db_configure, mock_argv,
):
"""Test that init_application can tolerate being called twice in a
single python interpreter instance.
When nova-api is run via mod_wsgi, if any exception is raised during
init_application, mod_wsgi will re-run the WSGI script without
restarting the daemon process even when configured for Daemon Mode.
We access the database as part of init_application, so if nova-api
starts up before the database is up, we'll get, for example, a
DBConnectionError raised during init_application and our WSGI script
will get reloaded/re-run by mod_wsgi.
"""
mock_get_files.return_value = [self.conf.name]
mock_setup.side_effect = [test.TestingException, None]
# We need to mock the global database configure() methods, else we will
# be affected by global database state altered by other tests that ran
# before this test, causing this test to fail with
# oslo_db.sqlalchemy.enginefacade.AlreadyStartedError. We can instead
# mock the method to raise an exception if it's called a second time in
# this test to simulate the fact that the database does not tolerate
# re-init [after a database query has been made].
mock_main_db_configure.side_effect = [None, test.TestingException]
mock_api_db_configure.side_effect = [None, test.TestingException]
# Run init_application the first time, simulating an exception being
# raised during it.
self.assertRaises(test.TestingException, wsgi_app.init_application,
'nova-api')
# reset the latch_error_on_raise decorator
wsgi_app.init_application.reset()
# Now run init_application a second time, it should succeed since no
# exception is being raised (the init of global data should not be
# re-attempted).
wsgi_app.init_application('nova-api')
self.assertIn('Global data already initialized, not re-initializing.',
self.stdlog.logger.output)
@mock.patch(
'sys.argv', new=mock.MagicMock(return_value=mock.sentinel.argv))
@mock.patch('nova.api.openstack.wsgi_app._get_config_files')
def test_init_application_called_unrecoverable(self, mock_get_files):
"""Test that init_application can tolerate being called more than once
in a single python interpreter instance and raises the same exception
forever if its unrecoverable.
"""
error = ValueError("unrecoverable config error")
excepted_type = type(error)
mock_get_files.side_effect = [
error, test.TestingException, test.TestingException]
for i in range(3):
e = self.assertRaises(
excepted_type, wsgi_app.init_application, 'nova-api')
self.assertIs(e, error)
# since the expction is latched on the first raise mock_get_files
# should not be called again on each iteration
mock_get_files.assert_called_once()
@mock.patch('nova.objects.Service.get_by_host_and_binary')
@mock.patch('nova.utils.raise_if_old_compute')
def test_setup_service_version_workaround(self, mock_check_old, mock_get):
mock_check_old.side_effect = exception.TooOldComputeService(
oldest_supported_version='2',
scope='scope',
min_service_level=2,
oldest_supported_service=1)
self.assertRaises(exception.TooOldComputeService,
wsgi_app._setup_service, 'myhost', 'api')
wsgi_app.CONF.set_override(
'disable_compute_service_check_for_ffu', True,
group='workarounds')
wsgi_app._setup_service('myhost', 'api')
def test_setup_service_pci_alias_validation(self):
wsgi_app.CONF.set_override(
'alias', jsonutils.dumps({'name': 'foo'}),
group='pci')
self.assertRaises(
exception.PciInvalidAlias,
wsgi_app._setup_service, 'myhost', 'api')
def test__get_config_files_empty_env(self):
env = {}
result = wsgi_app._get_config_files(env)
expected = ['/etc/nova/api-paste.ini', '/etc/nova/nova.conf']
self.assertEqual(result, expected)
def test__get_config_files_with_env(self):
env = {
"OS_NOVA_CONFIG_DIR": "/nova",
"OS_NOVA_CONFIG_FILES": "api.conf",
}
result = wsgi_app._get_config_files(env)
expected = ['/nova/api.conf']
self.assertEqual(result, expected)