diff --git a/doc/source/user/dynamic-emulator.rst b/doc/source/user/dynamic-emulator.rst index e817ff7d..87fb0566 100644 --- a/doc/source/user/dynamic-emulator.rst +++ b/doc/source/user/dynamic-emulator.rst @@ -26,7 +26,8 @@ Systems resource For *Systems* resource, emulator maintains two drivers relying on a virtualization backend to emulate bare metal machines by means of -virtual machines. +virtual machines. In addition, there is a fake driver used to mock +bare metal machines. The following sections will explain how to configure and use each of these drivers. @@ -388,6 +389,39 @@ the ``fake`` system backend, all operations just return success. Any modifications are done purely in the local cache. This way, many Ironic operations can be tested at scale without access to a large computing pool. +System status notifications +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``fake`` driver may need to simulate components that run on the VMs to test +an end-to-end deployment. This requires a hook interface to integrate external +components. For instance, when testing Ironic scalability, Ironic needs to +communicate with the Ironic Python Agent (IPA). A fake IPA can be implemented +and synchronized with the VM status using this hook, which notifies the fake +IPA whenever the VM status changes. + +To enable notifications, set ``external_notifier`` to ``True`` in the fake system +object: + +.. code-block:: python + + { + "uuid": "7946b59-9e44-4fa7-8e91-f3527a1ef094", + "name": "fake", + "power_state": "Off", + "external_notifier": True, + "nics": [ + { + "mac": "00:5c:52:31:3a:9c", + "ip": "172.22.0.100" + } + ] + } + +After this, whenever the fake driver updates this system object, it will send +an HTTP ``PUT`` request with the new system object as ``JSON`` data. The +endpoint URL can be configured with the parameter +``EXTERNAL_NOTIFICATION_URL``. + Filtering by allowed instances ++++++++++++++++++++++++++++++ diff --git a/sushy_tools/emulator/resources/systems/fakedriver.py b/sushy_tools/emulator/resources/systems/fakedriver.py index 4d247db3..13b056c3 100644 --- a/sushy_tools/emulator/resources/systems/fakedriver.py +++ b/sushy_tools/emulator/resources/systems/fakedriver.py @@ -14,11 +14,12 @@ import copy import random import time +import requests + from sushy_tools.emulator import memoize from sushy_tools.emulator.resources.systems.base import AbstractSystemsDriver from sushy_tools import error - DEFAULT_UUID = '27946b59-9e44-4fa7-8e91-f3527a1ef094' @@ -32,11 +33,16 @@ class FakeDriver(AbstractSystemsDriver): 'uuid': DEFAULT_UUID, 'name': 'fake', 'power_state': 'Off', + 'external_notifier': False, 'nics': [ - {'address': '00:5c:52:31:3a:9c'} + { + 'mac': '00:5c:52:31:3a:9c', + 'ip': '172.22.0.100' + } ] } ]) + config.setdefault('EXTERNAL_NOTIFICATION_URL', 'http://localhost:9999') cls._config = config cls._logger = logger return cls @@ -61,6 +67,8 @@ class FakeDriver(AbstractSystemsDriver): def _update_if_needed(self, system): pending_power = system.get('pending_power') if pending_power and time.time() >= pending_power['apply_time']: + if 'Restart' in pending_power['power_state']: + pending_power['power_state'] = 'On' self._update(system, power_state=pending_power['power_state'], pending_power=None) @@ -87,6 +95,8 @@ class FakeDriver(AbstractSystemsDriver): system = self._get(system) system.update(changes) self._systems[system['uuid']] = system + if system.get('external_notifier'): + self._send_external_notification(system) @property def driver(self): @@ -123,7 +133,7 @@ class FakeDriver(AbstractSystemsDriver): pending_state = 'Off' elif 'Restart' in state: system['power_state'] = 'Off' - pending_state = 'On' + pending_state = state else: raise error.NotSupportedError( f'Power state {state} is not supported') @@ -165,5 +175,36 @@ class FakeDriver(AbstractSystemsDriver): def get_nics(self, identity): nics = self._get(identity)['nics'] - return [{'id': nic.get('address'), 'mac': nic.get('address')} + return [{'id': nic.get('mac'), 'mac': nic.get('mac')} for nic in nics] + + def _send_external_notification(self, system): + """Notify external API about a given system changes. + + Args: + system (dict): The system dictionary containing system details. + + Logs: + Info: Logs the start of the fake IPA boot process. + Error: Logs any errors encountered during the request. + """ + external_notification_url = self._config['EXTERNAL_NOTIFICATION_URL'] + self._logger.info( + 'External notification to (%s): node %s power state changes', + external_notification_url, system.get('name')) + resp = requests.put( + external_notification_url, json=system, + headers={'Content-type': 'application/json'}) + + # Check if the request was unsuccessful + if resp.status_code >= 400: + self._logger.error( + 'External notifcation to (%s) about system %s request' + 'error %d: %s', + external_notification_url, system.get('name'), + resp.status_code, resp.text) + return + + # Log successful notification + self._logger.info("External notifcation to (%s) sent about %s", + external_notification_url, system.get('name'))