Merge "Add instance.share_attach_error notification"

This commit is contained in:
Zuul
2024-12-09 19:35:07 +00:00
committed by Gerrit Code Review
6 changed files with 285 additions and 23 deletions
@@ -0,0 +1,22 @@
{
"event_type": "instance.share_attach.error",
"payload": {
"$ref": "common_payloads/InstanceActionSharePayload.json#",
"nova_object.data": {
"fault": {
"nova_object.data": {
"exception": "ShareAccessGrantError",
"exception_message": "Share access could not be granted to share id 8db0037b-e98f-4bde-ae71-f96a077c19a4.\nReason: Connection timed out.",
"function_name": "_execute_mock_call",
"module_name": "unittest.mock",
"traceback": "Traceback (most recent call last):\n File \"nova/compute/manager.py\", line ..."
},
"nova_object.name": "ExceptionPayload",
"nova_object.namespace": "nova",
"nova_object.version": "1.1"
}
}
},
"priority": "ERROR",
"publisher_id": "nova-api:compute"
}
+45 -19
View File
@@ -4637,7 +4637,6 @@ class ComputeManager(manager.Manager):
@utils.synchronized(share_mapping.share_id)
def _allow_share(context, instance, share_mapping):
def _apply_policy():
# self.manila_api.lock(share_mapping.share_id)
# Explicitly locking the share is not needed as
@@ -4684,6 +4683,15 @@ class ComputeManager(manager.Manager):
)
try:
compute_utils.notify_about_share_attach_detach(
context,
instance,
instance.host,
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.START,
share_id=share_mapping.share_id
)
share_mapping.set_access_according_to_protocol()
if not self.manila_api.has_access(
@@ -4700,6 +4708,15 @@ class ComputeManager(manager.Manager):
share_mapping, fields.ShareMappingStatus.INACTIVE
)
compute_utils.notify_about_share_attach_detach(
context,
instance,
instance.host,
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.END,
share_id=share_mapping.share_id
)
except (
exception.ShareNotFound,
exception.ShareProtocolNotSupported,
@@ -4708,6 +4725,15 @@ class ComputeManager(manager.Manager):
self._set_share_mapping_status(
share_mapping, fields.ShareMappingStatus.ERROR
)
compute_utils.notify_about_share_attach_detach(
context,
instance,
instance.host,
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.ERROR,
share_id=share_mapping.share_id,
exception=e
)
LOG.error(e.format_message())
raise
except (
@@ -4716,6 +4742,15 @@ class ComputeManager(manager.Manager):
self._set_share_mapping_status(
share_mapping, fields.ShareMappingStatus.ERROR
)
compute_utils.notify_about_share_attach_detach(
context,
instance,
instance.host,
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.ERROR,
share_id=share_mapping.share_id,
exception=e
)
LOG.error(
"%s: %s error from url: %s, %s",
e.message,
@@ -4728,29 +4763,20 @@ class ComputeManager(manager.Manager):
self._set_share_mapping_status(
share_mapping, fields.ShareMappingStatus.ERROR
)
compute_utils.notify_about_share_attach_detach(
context,
instance,
instance.host,
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.ERROR,
share_id=share_mapping.share_id,
exception=e
)
LOG.error(e)
raise
compute_utils.notify_about_share_attach_detach(
context,
instance,
instance.host,
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.START,
share_id=share_mapping.share_id
)
_allow_share(context, instance, share_mapping)
compute_utils.notify_about_share_attach_detach(
context,
instance,
instance.host,
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.END,
share_id=share_mapping.share_id
)
@messaging.expected_exceptions(NotImplementedError)
@wrap_exception()
def deny_share(self, context, instance, share_mapping):
+1
View File
@@ -683,6 +683,7 @@ class InstanceActionVolumeNotification(base.NotificationBase):
@base.notification_sample('instance-share_attach-start.json')
@base.notification_sample('instance-share_attach-error.json')
@base.notification_sample('instance-share_attach-end.json')
@base.notification_sample('instance-share_detach-start.json')
@base.notification_sample('instance-share_detach-end.json')
@@ -565,6 +565,12 @@ class InstanceHelperMixin:
self.notifier.wait_for_versioned_notifications(
'instance.share_attach.end')
def _attach_share_with_error(self, server, share_id):
self.api.post_server_share(
server['id'], {"share": {"share_id": share_id}})
self.notifier.wait_for_versioned_notifications(
'instance.share_attach.error')
def _detach_share(self, server, share_id):
self.api.delete_server_share(server['id'], share_id)
self.notifier.wait_for_versioned_notifications(
@@ -20,6 +20,7 @@ from nova.tests.functional.api import client
from nova.tests.functional.notification_sample_tests \
import notification_sample_base
from nova.volume import cinder
from oslo_concurrency import processutils
class TestInstanceNotificationSampleWithMultipleCompute(
@@ -344,7 +345,7 @@ class TestInstanceNotificationSample(
self.useFixture(self.neutron)
self.cinder = fixtures.CinderFixture(self)
self.useFixture(self.cinder)
self.useFixture(fixtures.ManilaFixture())
self.manila_fixture = self.useFixture(fixtures.ManilaFixture())
def _wait_until_swap_volume(self, server, volume_id):
for i in range(50):
@@ -392,6 +393,7 @@ class TestInstanceNotificationSample(
self._test_lock_unlock_instance,
self._test_lock_unlock_instance_with_reason,
self._test_share_attach_detach,
self._test_share_attach_error,
]
for action in actions:
@@ -1786,7 +1788,6 @@ class TestInstanceNotificationSample(
},
actual=self.notifier.versioned_notifications[1])
def _test_share_detach(self, server):
# Shutdown server
self.notifier.reset()
self.api.post_server_action(server['id'], {'os-stop': {}})
@@ -1800,8 +1801,10 @@ class TestInstanceNotificationSample(
'uuid': server['id'],
'state': 'active',
'power_state': 'running',
'shares': expected_shares
},
actual=self.notifier.versioned_notifications[0])
expected_shares[0]['nova_object.data']['status'] = 'inactive'
self._verify_notification(
'instance-power_off-end',
replacements={
@@ -1809,6 +1812,7 @@ class TestInstanceNotificationSample(
'uuid': server['id'],
'state': 'stopped',
'power_state': 'shutdown',
'shares': expected_shares
},
actual=self.notifier.versioned_notifications[1])
@@ -1819,6 +1823,7 @@ class TestInstanceNotificationSample(
self.assertEqual(2, len(self.notifier.versioned_notifications),
self.notifier.versioned_notifications)
expected_shares[0]['nova_object.data']['status'] = 'detaching'
self._verify_notification(
'instance-share_detach-start',
replacements={
@@ -1826,6 +1831,7 @@ class TestInstanceNotificationSample(
'uuid': server['id'],
'state': 'stopped',
'power_state': 'shutdown',
'shares': expected_shares
},
actual=self.notifier.versioned_notifications[0])
self._verify_notification(
@@ -1863,6 +1869,141 @@ class TestInstanceNotificationSample(
},
actual=self.notifier.versioned_notifications[1])
def _test_share_attach_error(self, server):
expected_shares = [
{
"nova_object.name": "SharePayload",
"nova_object.namespace": "nova",
"nova_object.version": "1.0",
"nova_object.data": {
"share_mapping_uuid": (
"f7c1726d-7622-42b3-8b2c-4473239d60d1"),
"share_id": "e8debdc0-447a-4376-a10a-4cd9122d7986",
"status": "attaching",
"tag": "e8debdc0-447a-4376-a10a-4cd9122d7986",
# 'export_location': '10.0.0.50:/mnt/foo',
},
}
]
self.api.post_server_action(server['id'], {'os-stop': {}})
self._wait_for_state_change(server, expected_status='SHUTOFF')
self.notifier.reset()
with mock.patch(
'nova.virt.fake.FakeDriver.mount_share',
side_effect=exception.ShareMountError(
share_id='8db0037b-e98f-4bde-ae71-f96a077c19a4',
server_id=server['id'],
reason=processutils.ProcessExecutionError(
stdout='This is stdout',
stderr='This is stderror',
exit_code=1,
cmd="mount"
)
)
):
# Simulate an error attaching the share.
self.manila_fixture.mock_allow.side_effect = (
exception.ShareAccessGrantError(
share_id="8db0037b-e98f-4bde-ae71-f96a077c19a4",
reason="Connection timed out"
)
)
self._attach_share_with_error(
server, "e8debdc0-447a-4376-a10a-4cd9122d7986")
share_info = self._get_share(
server, "e8debdc0-447a-4376-a10a-4cd9122d7986", admin=True
)
expected_shares[0]["nova_object.data"][
"share_mapping_uuid"
] = share_info["uuid"]
# 0: instance-share_attach-start
# 1: instance-share_attach-error
# 2: compute.exception
self.assertEqual(3, len(self.notifier.versioned_notifications),
self.notifier.versioned_notifications)
self._verify_notification(
'instance-share_attach-start',
replacements={
'reservation_id': server['reservation_id'],
'uuid': server['id'],
'state': 'stopped',
'power_state': 'shutdown',
'shares': expected_shares,
},
actual=self.notifier.versioned_notifications[0])
expected_shares[0]["nova_object.data"]["status"] = "error"
self._verify_notification(
'instance-share_attach-error',
replacements={
'reservation_id': server['reservation_id'],
'uuid': server['id'],
'state': 'stopped',
'power_state': 'shutdown',
'shares': expected_shares,
'fault.traceback': self.ANY
},
actual=self.notifier.versioned_notifications[1])
self._wait_for_notification('compute.exception')
# Restart server
self.notifier.reset()
self.api.post_server_action(server['id'], {'os-start': {}})
self._wait_for_state_change(server, expected_status='ERROR')
self.assertTrue(
any(
[
item
for item in self.notifier.versioned_notifications
if item["event_type"] == "compute.exception"
]
)
)
# Detach share despite the share error
self.notifier.reset()
self._detach_share(
server, 'e8debdc0-447a-4376-a10a-4cd9122d7986')
expected_shares[0]["nova_object.data"]["status"] = "detaching"
self.assertEqual(2, len(self.notifier.versioned_notifications),
self.notifier.versioned_notifications)
self._verify_notification(
'instance-share_detach-start',
replacements={
'reservation_id': server['reservation_id'],
'uuid': server['id'],
'state': 'error',
'shares': expected_shares,
'power_state': 'shutdown'},
actual=self.notifier.versioned_notifications[0])
expected_shares[0]['nova_object.data']['status'] = 'detaching'
self._verify_notification(
'instance-share_detach-end',
replacements={
'reservation_id': server['reservation_id'],
'uuid': server['id'],
'state': 'error',
'power_state': 'shutdown',
},
actual=self.notifier.versioned_notifications[1])
# Reboot server
self.notifier.reset()
post = {'reboot': {'type': 'HARD'}}
self.api.post_server_action(server['id'], post)
self._wait_for_notification('instance.reboot.start')
self._wait_for_notification('instance.reboot.end')
def _test_rescue_unrescue_server(self, server):
# Both "rescue" and "unrescue" notification asserts are made here
# rescue notification asserts
+68 -2
View File
@@ -2493,7 +2493,7 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
"fake-host",
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.START,
share_id=share_mapping.share_id
share_id=share_mapping.share_id,
),
mock.call(
mock.ANY,
@@ -2501,7 +2501,73 @@ class ComputeManagerUnitTestCase(test.NoDBTestCase,
"fake-host",
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.END,
share_id=share_mapping.share_id
share_id=share_mapping.share_id,
),
])
@mock.patch(
'nova.compute.utils.notify_about_share_attach_detach',
return_value=None
)
@mock.patch('nova.share.manila.API.allow')
@mock.patch('nova.share.manila.API.get_access')
@mock.patch('nova.objects.share_mapping.ShareMapping.save')
def test_allow_share_fails_share_grant_failure(
self, mock_db, mock_get_access, mock_allow, mock_notifications
):
self.flags(shutdown_retry_interval=20, group='compute')
instance = fake_instance.fake_instance_obj(
self.context,
uuid=uuids.instance,
vm_state=vm_states.ACTIVE,
task_state=task_states.POWERING_OFF)
mock_get_access.side_effect = [None, self.get_fake_share_access()]
# Ensure CONF.my_shared_fs_storage_ip default is my_ip
self.flags(my_ip="10.0.0.2")
self.assertEqual(CONF.my_shared_fs_storage_ip, '10.0.0.2')
# Set CONF.my_shared_fs_storage_ip to ensure it is used by the code
self.flags(my_shared_fs_storage_ip="192.168.0.1")
compute_ip = CONF.my_shared_fs_storage_ip
self.assertEqual(compute_ip, '192.168.0.1')
share_mapping = self.get_fake_share_mapping()
exc = exception.ShareAccessGrantError(
share_id=share_mapping.share_id,
reason="fake_reason"
)
mock_allow.side_effect = exc
self.assertRaises(
exception.ShareAccessGrantError,
self.compute.allow_share,
self.context,
instance,
share_mapping,
)
self.assertEqual(share_mapping.status, 'error')
mock_get_access.assert_called_with(
self.context, share_mapping.share_id, 'ip', compute_ip)
mock_allow.assert_called_once_with(
mock.ANY, share_mapping.share_id, 'ip', compute_ip, 'rw')
mock_notifications.assert_has_calls([
mock.call(
mock.ANY,
instance,
"fake-host",
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.START,
share_id=share_mapping.share_id,
),
mock.call(
mock.ANY,
instance,
"fake-host",
action=fields.NotificationAction.SHARE_ATTACH,
phase=fields.NotificationPhase.ERROR,
share_id=share_mapping.share_id,
exception=exc
),
])