Reproducer for dangling bdms

- Added reproducer test for dangling BDMs

Related-Bug: 2019078
Change-Id: Ieb76b00600acc9c7610546f5a1afab5b65903825
This commit is contained in:
Amit Uniyal 2023-07-28 11:07:33 +00:00
parent 32ed205794
commit c33a9ccf4c
2 changed files with 185 additions and 0 deletions

View File

@ -0,0 +1,157 @@
# Copyright 2023, Red Hat, Inc. 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.
from nova import context
from nova import objects
from nova.tests.functional import integrated_helpers
from nova.tests.functional.libvirt import base
class TestAttachedVolumes(
base.ServersTestBase,
integrated_helpers.InstanceHelperMixin):
microversion = 'latest'
def setUp(self):
super(TestAttachedVolumes, self).setUp()
self.admin_api = self.api_fixture.admin_api
self.start_compute()
def _get_bdm_list(self, server):
ctxt = context.get_admin_context()
bdms = objects.BlockDeviceMappingList.get_by_instance_uuid(
ctxt, server['id'])
return [(
bdm.volume_id, bdm.attachment_id) for bdm in bdms if bdm.volume_id]
def test_delete_stale_attachment_from_nova(self):
volume_id = 'aeb9b5f4-1fe9-4964-ab65-5e168be4de8e'
# create a server
server = self._create_server(networks=[])
server = self._attach_volumes(server, [volume_id])
# verify if volume attachment created at cinder
attached_volume_ids = self.cinder.attachment_ids_for_instance(
server['id'])
self.assertEqual(1, len(attached_volume_ids))
# verify if volume attachment created at nova
bdm_list = self._get_bdm_list(server)
self.assertEqual(1, len(bdm_list))
self.assertEqual(volume_id, bdm_list[0][0])
# delete volume attachment using cinder api
self.cinder.delete_vol_attachment(bdm_list[0][0])
# rebooting server should remove stale attachments
server = self._reboot_server(server, hard=True)
# verify if volume attachment is present at cinder
attached_volume_ids = self.cinder.attachment_ids_for_instance(
server['id'])
self.assertEqual(0, len(attached_volume_ids))
# verify if volume attachment is present at nova
bdm_list = self._get_bdm_list(server)
# TODO(auniyal): Reboot should remove stale bdms
# after fix bdm should not have any volume
self.assertEqual(1, len(bdm_list))
self.assertEqual(volume_id, bdm_list[0][0])
def test_delete_multiple_stale_attachment_from_nova(self):
volumes = [
'543c6517-e1f7-4327-968d-7be8246b798a',
'f37d7501-f1dd-428f-a1f8-6a9eb951f247',
'45990290-ef3d-410c-b8cc-45f7830b5a8f',
'6addafaf-9dc8-470e-88d2-288d0daa616c',
]
# create a server
server = self._create_server(networks='none')
server = self._attach_volumes(server, volumes)
# verify if volume attachment created at cinder
attached_volume_ids = self.cinder.attachment_ids_for_instance(
server['id'])
self.assertEqual(4, len(attached_volume_ids))
# verify if volume attachment created at nova
bdm_list = self._get_bdm_list(server)
bdm_attc_vols = set([bdm[0] for bdm in bdm_list])
self.assertEqual(4, len(bdm_list))
self.assertEqual(set(volumes), set(bdm_attc_vols))
# delete 2 volume attachment using cinder api
self.cinder.delete_vol_attachment(bdm_list[1][0])
self.cinder.delete_vol_attachment(bdm_list[3][0])
server = self._reboot_server(server, hard=True)
# verify if volume attachment is present at cinder
attached_volume_ids = self.cinder.attachment_ids_for_instance(
server['id'])
self.assertEqual(2, len(attached_volume_ids))
# verify if volume attachment is present at nova
bdm_list_2 = self._get_bdm_list(server)
bdm_attc_vols = [bdm[0] for bdm in bdm_list_2]
# after fix only 2 volumes should be attached instead 4
self.assertEqual(4, len(bdm_list_2))
self.assertIn(bdm_list[0][0], bdm_attc_vols)
self.assertIn(bdm_list[1][0], bdm_attc_vols)
self.assertIn(bdm_list[2][0], bdm_attc_vols)
self.assertIn(bdm_list[3][0], bdm_attc_vols)
def test_delete_multiple_stale_attachment_from_cinder(self):
volume_id = 'aeb9b5f4-1fe9-4964-ab65-5e168be4de8e'
server = self._create_server(networks=[])
# this will create a valid attachments
server = self._attach_volumes(server, [volume_id])
attachments = 4
# create stale attachments at cinder
self._create_vol_attachments_by_cinder(volume_id, server, attachments)
# verify if volume attachment created at cinder
# get attachment id by server from cinder
attached_volume_ids = self.cinder.attachment_ids_for_instance(
server['id'])
# here we are checking with +1 because we had attached
# a valid attachment at L145.
# this is to differentiate that reboot should not remove
# a valid attachment and only remove dangling one.
self.assertEqual(attachments + 1, len(attached_volume_ids))
for _id in attached_volume_ids:
_ = self.cinder.get_vol_attachment(_id)
self.assertEqual(_['instance_uuid'], server['id'])
# verify if nova aware of new attachments
bdm_list = self._get_bdm_list(server)
self.assertEqual(1, len(bdm_list))
# rebooting server should remove stale attachments
server = self._reboot_server(server, hard=True)
# TODO(auniyal): Reboot should remove only stale attachments
# from cinder too
# verify if how many volume attachments are present at cinder
attached_volume_ids = self.cinder.attachment_ids_for_instance(
server['id'])
self.assertEqual(attachments + 1, len(attached_volume_ids))

View File

@ -42,6 +42,7 @@ 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 import utils
import typing as ty
CONF = nova.conf.CONF
@ -640,6 +641,33 @@ class InstanceHelperMixin:
{'createImage': {'name': snapshot_name}}
)
def _attach_volumes(self, server, vol_ids: ty.List[str]):
# attach volumes to server
# these attachments are done by nova api, that means
# nova know about these attachments and so they are valid ones.
for vol_id in vol_ids:
self.api.post_server_volume(
server['id'], {'volumeAttachment': {'volumeId': vol_id}})
for vol_id in vol_ids:
# wait for each vol to get attached
self._wait_for_volume_attach(server['id'], vol_id)
# here server is already in an active state
# this is just to refresh server object
# so attached volumes can be shown in server
return self._wait_for_state_change(server, expected_status='ACTIVE')
def _create_vol_attachments_by_cinder(
self, volume_id, server, new_attachments=1):
# these attachments are done by cinder api,
# and nova is not aware of them.
for _ in range(new_attachments):
self.cinder.create_vol_attachment(
volume_id, server['id'])
class PlacementHelperMixin:
"""A helper mixin for interacting with placement."""