Add functional tests for [cinder]/cross_az_attach=False

This adds some simple functional tests for the API validation
behavior during server create when [cinder]/cross_az_attach=False
meaning the server being created and any volumes attached to it
must be in the same AZ.

As part of this, bug 1694844 is recreated where the server is
created without an AZ (or default_schedule_zone AZ) which results
in a 400 response because None != whatever the volume's AZ is
(which defaults to "nova" in Cinder). This is important for testing
fixes for that bug later since the API interaction code is pretty
hairy and unit tests are insufficient for validating a fix.

Change-Id: I1b724f7ad3e2f6baa9fd865a8e22d87bf909b488
Related-Bug: #1694844
This commit is contained in:
Matt Riedemann 2019-09-23 17:43:33 -04:00
parent dc0911d30c
commit f212b85583
2 changed files with 149 additions and 1 deletions

View File

@ -1661,12 +1661,20 @@ class CinderFixture(fixtures.Fixture):
# as part of volume image metadata
IMAGE_WITH_TRAITS_BACKED_VOL = '6194fc02-c60e-4a01-a8e5-600798208b5f'
def __init__(self, test):
def __init__(self, test, az='nova'):
"""Initialize this instance of the CinderFixture.
:param test: The TestCase using this fixture.
:param az: The availability zone to return in volume GET responses.
Defaults to "nova" since that is the default we would see
from Cinder's storage_availability_zone config option.
"""
super(CinderFixture, self).__init__()
self.test = test
self.swap_volume_instance_uuid = None
self.swap_volume_instance_error_uuid = None
self.attachment_error_id = None
self.az = az
# A dict, keyed by volume id, to a dict, keyed by attachment id,
# with keys:
# - id: the attachment id
@ -1756,6 +1764,9 @@ class CinderFixture(fixtures.Fixture):
'size': 1
}
if 'availability_zone' not in volume:
volume['availability_zone'] = self.az
# Check for our special image-backed volume.
if volume_id in (self.IMAGE_BACKED_VOL,
self.IMAGE_WITH_TRAITS_BACKED_VOL):

View File

@ -0,0 +1,137 @@
# 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 six
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.functional.api import client as api_client
from nova.tests.functional import fixtures as func_fixtures
from nova.tests.functional import integrated_helpers
from nova.tests.unit.image import fake as fake_image
from nova.tests.unit import policy_fixture
class CrossAZAttachTestCase(test.TestCase,
integrated_helpers.InstanceHelperMixin):
"""Contains various scenarios for the [cinder]/cross_az_attach option
and how it affects interactions between nova and cinder in the API and
compute service.
"""
az = 'us-central-1'
def setUp(self):
super(CrossAZAttachTestCase, self).setUp()
# Use the standard fixtures.
self.useFixture(policy_fixture.RealPolicyFixture())
self.useFixture(nova_fixtures.CinderFixture(self, az=self.az))
self.useFixture(nova_fixtures.NeutronFixture(self))
self.useFixture(func_fixtures.PlacementFixture())
fake_image.stub_out_image_service(self)
self.addCleanup(fake_image.FakeImageService_reset)
# Start nova controller services.
self.api = self.useFixture(nova_fixtures.OSAPIFixture(
api_version='v2.1')).admin_api
self.start_service('conductor')
self.start_service('scheduler')
# Start one compute service.
self.start_service('compute', host='host1')
def test_cross_az_attach_false_boot_from_volume_no_az_specified(self):
"""Tests the scenario where [cinder]/cross_az_attach=False and the
server is created with a pre-existing volume but the server create
request does not specify an AZ nor is [DEFAULT]/default_schedule_zone
set.
"""
self.flags(cross_az_attach=False, group='cinder')
server = self._build_minimal_create_server_request(
self.api,
'test_cross_az_attach_false_boot_from_volume_no_az_specified')
del server['imageRef'] # Do not need imageRef for boot from volume.
server['block_device_mapping_v2'] = [{
'source_type': 'volume',
'destination_type': 'volume',
'boot_index': 0,
'uuid': nova_fixtures.CinderFixture.IMAGE_BACKED_VOL
}]
# FIXME(mriedem): This is bug 1694844 where the user creates the server
# without specifying an AZ and there is no default_schedule_zone set
# and the cross_az_attach check fails because the volume's availability
# zone shows up as "us-central-1" and None != "us-central-1" so the API
# thinks the cross_az_attach=False setting was violated.
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.post_server, {'server': server})
self.assertEqual(400, ex.response.status_code)
self.assertIn('are not in the same availability_zone',
six.text_type(ex))
def test_cross_az_attach_false_data_volume_no_az_specified(self):
"""Tests the scenario where [cinder]/cross_az_attach=False and the
server is created with a pre-existing volume as a non-boot data volume
but the server create request does not specify an AZ nor is
[DEFAULT]/default_schedule_zone set.
"""
self.flags(cross_az_attach=False, group='cinder')
server = self._build_minimal_create_server_request(
self.api,
'test_cross_az_attach_false_data_volume_no_az_specified')
# Note that we use the legacy block_device_mapping parameter rather
# than block_device_mapping_v2 because that will create an implicit
# source_type=image, destination_type=local, boot_index=0,
# uuid=$imageRef which is used as the root BDM and allows our
# non-boot data volume to be attached during server create. Otherwise
# we get InvalidBDMBootSequence.
server['block_device_mapping'] = [{
# This is a non-bootable volume in the CinderFixture.
'volume_id': nova_fixtures.CinderFixture.SWAP_OLD_VOL
}]
# FIXME(mriedem): This is bug 1694844 where the user creates the server
# without specifying an AZ and there is no default_schedule_zone set
# and the cross_az_attach check fails because the volume's availability
# zone shows up as "us-central-1" and None != "us-central-1" so the API
# thinks the cross_az_attach=False setting was violated.
ex = self.assertRaises(api_client.OpenStackApiException,
self.api.post_server, {'server': server})
self.assertEqual(400, ex.response.status_code)
self.assertIn('are not in the same availability_zone',
six.text_type(ex))
def test_cross_az_attach_false_boot_from_volume_default_zone_match(self):
"""Tests the scenario where [cinder]/cross_az_attach=False and the
server is created with a pre-existing volume and the
[DEFAULT]/default_schedule_zone matches the volume's AZ.
"""
self.flags(cross_az_attach=False, group='cinder')
self.flags(default_schedule_zone=self.az)
# For this test we have to put the compute host in an aggregate with
# the AZ we want to match.
agg_id = self.api.post_aggregate({
'aggregate': {
'name': self.az,
'availability_zone': self.az
}
})['id']
self.api.add_host_to_aggregate(agg_id, 'host1')
server = self._build_minimal_create_server_request(
self.api,
'test_cross_az_attach_false_boot_from_volume_default_zone_match')
del server['imageRef'] # Do not need imageRef for boot from volume.
server['block_device_mapping_v2'] = [{
'source_type': 'volume',
'destination_type': 'volume',
'boot_index': 0,
'uuid': nova_fixtures.CinderFixture.IMAGE_BACKED_VOL
}]
server = self.api.post_server({'server': server})
server = self._wait_for_state_change(self.api, server, 'ACTIVE')
self.assertEqual(self.az, server['OS-EXT-AZ:availability_zone'])