Notify vim on state change
USM has several async operations. This change allows software to notify VIM when those async operations complete. It does this by using the existing listener functionality to trigger a host-audit event in VIM via the sysinv API. TEST PLAN PASS: AIO-SX patch upgrade PASS: AIO-SX patch downgrade PASS: AIO-SX patch upgrade, pre-bootstrap PASS: AIO-DX patch upgrade Story: 2011045 Task: 52380 Depends-On: https://review.opendev.org/c/starlingx/config/+/952590 Change-Id: Id7b22f58040cbbf02a2e616a529a2b72a3970e80 Signed-off-by: Joshua Kraitberg <joshua.kraitberg@windriver.com>
This commit is contained in:
@@ -106,6 +106,7 @@ from software.sysinv_utils import are_all_hosts_unlocked_and_online
|
||||
from software.sysinv_utils import get_system_info
|
||||
from software.sysinv_utils import get_oot_drivers
|
||||
from software.sysinv_utils import trigger_evaluate_apps_reapply
|
||||
from software.sysinv_utils import trigger_vim_host_audit
|
||||
|
||||
from software.db.api import get_instance
|
||||
|
||||
@@ -1080,6 +1081,40 @@ class PatchController(PatchService):
|
||||
else:
|
||||
self._update_state_to_peer()
|
||||
|
||||
def _notify_vim_on_state_change(self, target_state):
|
||||
"""Notify VIM of state change.
|
||||
|
||||
This method will notify VIM when one of the following state changes is made:
|
||||
- start-done
|
||||
- start-failed
|
||||
- activate-done
|
||||
- activate-failed
|
||||
- activate-rollback-done
|
||||
- activate-rollback-failed
|
||||
|
||||
If new async states are added they should be added here.
|
||||
|
||||
Args:
|
||||
target_state: The new deployment state to notify VIM about
|
||||
"""
|
||||
|
||||
if self.pre_bootstrap:
|
||||
return
|
||||
|
||||
if target_state not in [
|
||||
DEPLOY_STATES.START_DONE,
|
||||
DEPLOY_STATES.START_FAILED,
|
||||
DEPLOY_STATES.ACTIVATE_DONE,
|
||||
DEPLOY_STATES.ACTIVATE_FAILED,
|
||||
DEPLOY_STATES.ACTIVATE_ROLLBACK_DONE,
|
||||
DEPLOY_STATES.ACTIVATE_ROLLBACK_FAILED,
|
||||
]:
|
||||
return
|
||||
|
||||
# Get local hostname
|
||||
LOG.info("Notifying VIM of state change: %s", target_state)
|
||||
trigger_vim_host_audit(socket.gethostname())
|
||||
|
||||
def register_deploy_state_change_listeners(self):
|
||||
# data sync listener
|
||||
DeployState.register_event_listener(self._state_changed_sync)
|
||||
@@ -1089,6 +1124,10 @@ class PatchController(PatchService):
|
||||
DeployState.register_event_listener(ReleaseState.deploy_updated)
|
||||
DeployState.register_event_listener(self.create_clean_up_deployment_alarm)
|
||||
|
||||
# VIM notifications
|
||||
DeployState.register_event_listener(self._notify_vim_on_state_change)
|
||||
# TODO(jkraitbe): Add host-deploy when that becomes async
|
||||
|
||||
@property
|
||||
def release_collection(self):
|
||||
swrc = get_SWReleaseCollection()
|
||||
|
||||
@@ -107,6 +107,18 @@ def update_host_sw_version(hostname, sw_version):
|
||||
raise
|
||||
|
||||
|
||||
def trigger_vim_host_audit(hostname):
|
||||
"""Trigger for the sysinv function vim_host_audit."""
|
||||
token, endpoint = utils.get_endpoints_token()
|
||||
sysinv_client = get_sysinv_client(token=token, endpoint=endpoint)
|
||||
try:
|
||||
host = sysinv_client.ihost.get(hostname)
|
||||
sysinv_client.ihost.vim_host_audit(host.uuid)
|
||||
except Exception as err:
|
||||
LOG.error("Failed to trigger VIM host audit for %s: %s", hostname, err)
|
||||
raise
|
||||
|
||||
|
||||
def get_service_parameter(service=None, section=None, name=None):
|
||||
"""return a list of dictionaries with keys
|
||||
uuid, service, section, name, personality, resource, value
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from software.software_controller import PatchController
|
||||
from software.states import DEPLOY_STATES
|
||||
|
||||
# This import has to be first
|
||||
from software.tests import base # pylint: disable=unused-import # noqa: F401
|
||||
|
||||
|
||||
class TestSoftwareControllerVimNotification(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
@patch("software.software_controller.PatchController.__init__", return_value=None)
|
||||
@patch("software.software_controller.trigger_vim_host_audit")
|
||||
@patch("socket.gethostname", return_value="controller-0")
|
||||
def test_notify_vim_on_state_change_supported_states(
|
||||
self, mock_gethostname, mock_trigger_vim_host_audit, mock_init
|
||||
): # pylint: disable=unused-argument
|
||||
"""Test that VIM is notified for supported states."""
|
||||
controller = PatchController()
|
||||
controller.pre_bootstrap = False
|
||||
|
||||
# Test all supported states
|
||||
supported_states = [
|
||||
DEPLOY_STATES.START_DONE,
|
||||
DEPLOY_STATES.START_FAILED,
|
||||
DEPLOY_STATES.ACTIVATE_DONE,
|
||||
DEPLOY_STATES.ACTIVATE_FAILED,
|
||||
DEPLOY_STATES.ACTIVATE_ROLLBACK_DONE,
|
||||
DEPLOY_STATES.ACTIVATE_ROLLBACK_FAILED,
|
||||
]
|
||||
|
||||
for state in supported_states:
|
||||
mock_trigger_vim_host_audit.reset_mock()
|
||||
# pylint: disable=protected-access
|
||||
controller._notify_vim_on_state_change(state)
|
||||
mock_gethostname.assert_called_with()
|
||||
mock_trigger_vim_host_audit.assert_called_once_with("controller-0")
|
||||
|
||||
@patch("software.software_controller.PatchController.__init__", return_value=None)
|
||||
@patch("software.software_controller.trigger_vim_host_audit")
|
||||
@patch("socket.gethostname")
|
||||
def test_notify_vim_on_state_change_unsupported_states(
|
||||
self, mock_gethostname, mock_trigger_vim_host_audit, mock_init
|
||||
): # pylint: disable=unused-argument
|
||||
"""Test that VIM is not notified for unsupported states."""
|
||||
controller = PatchController()
|
||||
controller.pre_bootstrap = False
|
||||
|
||||
# Test some unsupported states
|
||||
unsupported_states = [
|
||||
"HELLO?",
|
||||
DEPLOY_STATES.START,
|
||||
DEPLOY_STATES.ACTIVATE,
|
||||
]
|
||||
|
||||
for state in unsupported_states:
|
||||
# pylint: disable=protected-access
|
||||
controller._notify_vim_on_state_change(state)
|
||||
mock_gethostname.assert_not_called()
|
||||
mock_trigger_vim_host_audit.assert_not_called()
|
||||
|
||||
@patch("software.software_controller.PatchController.__init__", return_value=None)
|
||||
@patch("software.software_controller.DeployState")
|
||||
def test_register_deploy_state_change_listeners(
|
||||
self, mock_deploy_state, mock_init
|
||||
): # pylint: disable=unused-argument
|
||||
"""Test that the VIM notification listener is registered."""
|
||||
controller = PatchController()
|
||||
|
||||
# Mock other methods that are called during registration
|
||||
controller._state_changed_sync = MagicMock() # pylint: disable=protected-access
|
||||
# pylint: disable=protected-access
|
||||
controller._state_changed_notify = MagicMock()
|
||||
controller.create_clean_up_deployment_alarm = MagicMock()
|
||||
|
||||
# Call the method
|
||||
controller.register_deploy_state_change_listeners()
|
||||
|
||||
# Verify that _notify_vim_on_state_change is registered as a listener
|
||||
# pylint: disable=protected-access
|
||||
mock_deploy_state.register_event_listener.assert_any_call(
|
||||
controller._notify_vim_on_state_change
|
||||
)
|
||||
|
||||
@patch("software.software_controller.PatchController.__init__", return_value=None)
|
||||
@patch("software.software_controller.trigger_vim_host_audit")
|
||||
@patch("socket.gethostname")
|
||||
def test_notify_vim_on_state_change_prebootstrap(
|
||||
self, mock_gethostname, mock_trigger_vim_host_audit, mock_init
|
||||
): # pylint: disable=unused-argument
|
||||
"""Test that VIM is not notified during prebootstrap."""
|
||||
controller = PatchController()
|
||||
controller.pre_bootstrap = True
|
||||
|
||||
# pylint: disable=protected-access
|
||||
controller._notify_vim_on_state_change(DEPLOY_STATES.START_DONE)
|
||||
mock_gethostname.assert_not_called()
|
||||
mock_trigger_vim_host_audit.assert_not_called()
|
||||
63
software/software/tests/test_sysinv_utils.py
Normal file
63
software/software/tests/test_sysinv_utils.py
Normal file
@@ -0,0 +1,63 @@
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
# Copyright (c) 2025 Wind River Systems, Inc.
|
||||
#
|
||||
|
||||
import unittest
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from software.sysinv_utils import trigger_vim_host_audit
|
||||
|
||||
|
||||
class TestSysinvUtils(unittest.TestCase):
|
||||
HOSTNAME = "test-host"
|
||||
HOST_UUID = "test-uuid"
|
||||
|
||||
def setUp(self):
|
||||
# Create shared mock host
|
||||
self.mock_host = MagicMock()
|
||||
self.mock_host.hostname = self.HOSTNAME
|
||||
self.mock_host.uuid = self.HOST_UUID
|
||||
|
||||
@patch("software.sysinv_utils.utils.get_endpoints_token")
|
||||
@patch("software.sysinv_utils.get_sysinv_client")
|
||||
@patch("software.sysinv_utils.get_ihost_list")
|
||||
def test_trigger_vim_host_audit(
|
||||
self, mock_get_ihost_list, mock_get_sysinv_client, mock_get_endpoints_token
|
||||
):
|
||||
mock_sysinv_client = MagicMock()
|
||||
mock_get_sysinv_client.return_value = mock_sysinv_client
|
||||
mock_sysinv_client.ihost.get.return_value = self.mock_host
|
||||
mock_get_endpoints_token.return_value = ("fake_token", "fake_endpoint")
|
||||
mock_get_ihost_list.return_value = [self.mock_host]
|
||||
|
||||
trigger_vim_host_audit(self.HOSTNAME)
|
||||
|
||||
mock_get_endpoints_token.assert_called_once()
|
||||
mock_get_sysinv_client.assert_called_once_with(
|
||||
token="fake_token", endpoint="fake_endpoint"
|
||||
)
|
||||
mock_sysinv_client.ihost.get.assert_called_once_with(self.HOSTNAME)
|
||||
mock_sysinv_client.ihost.vim_host_audit.assert_called_once_with(self.HOST_UUID)
|
||||
|
||||
@patch("software.sysinv_utils.utils.get_endpoints_token")
|
||||
@patch("software.sysinv_utils.get_sysinv_client")
|
||||
@patch("software.sysinv_utils.get_ihost_list")
|
||||
def test_trigger_vim_host_audit_sysinv_call_fails(
|
||||
self, mock_get_ihost_list, mock_get_sysinv_client, mock_get_endpoints_token
|
||||
):
|
||||
mock_sysinv_client = MagicMock()
|
||||
mock_get_sysinv_client.return_value = mock_sysinv_client
|
||||
mock_get_endpoints_token.return_value = ("fake_token", "fake_endpoint")
|
||||
mock_get_ihost_list.return_value = [self.mock_host]
|
||||
|
||||
# Configure vim_host_audit to raise an exception
|
||||
mock_sysinv_client.ihost.vim_host_audit.side_effect = Exception(
|
||||
"VIM audit failed"
|
||||
)
|
||||
|
||||
msg = "Failed to trigger VIM host audit: VIM audit failed"
|
||||
with self.assertRaises(Exception, msg=msg): # noqa: H202
|
||||
trigger_vim_host_audit(self.HOSTNAME)
|
||||
@@ -37,6 +37,7 @@ deps =
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
-e{[tox]stxdir}/fault/fm-api/source
|
||||
-e{[tox]stxdir}/config/tsconfig/tsconfig
|
||||
-e{[tox]stxdir}/update/software
|
||||
-c{env:UPPER_CONSTRAINTS_FILE:https://opendev.org/starlingx/root/raw/branch/master/build-tools/requirements/debian/upper-constraints.txt}
|
||||
|
||||
passenv =
|
||||
|
||||
Reference in New Issue
Block a user