Merge "Add api-call action for ironic inspection rule"

This commit is contained in:
Zuul
2025-06-19 17:15:30 +00:00
committed by Gerrit Code Review
3 changed files with 104 additions and 0 deletions

View File

@ -11,6 +11,9 @@
# under the License.
import abc
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
from oslo_log import log
@ -39,6 +42,7 @@ ACTIONS = {
"set-port-attribute": "SetPortAttributeAction",
"extend-port-attribute": "ExtendPortAttributeAction",
"del-port-attribute": "DelPortAttributeAction",
"api-call": "CallAPIHookAction",
}
@ -416,3 +420,46 @@ class DelPortAttributeAction(ActionBase):
'path': path, 'port_id': port_id, 'exc': str(exc)}
LOG.error(msg)
raise exception.RuleActionExecutionFailure(reason=msg)
class CallAPIHookAction(ActionBase):
FORMATTED_ARGS = ['url']
OPTIONAL_PARAMS = [
'headers', 'proxies', 'timeout', 'retries', 'backoff_factor'
]
def __call__(self, task, url, headers=None, proxies=None,
timeout=5, retries=3, backoff_factor=0.3):
try:
timeout = float(timeout)
if timeout <= 0:
raise ValueError("timeout must be greater than zero")
retries = int(retries)
backoff_factor = float(backoff_factor)
retry_strategy = Retry(
total=retries,
backoff_factor=backoff_factor,
status_forcelist=[429, 500, 502, 503, 504],
allowed_methods=["GET"],
raise_on_status=False
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session = requests.Session()
session.mount("http://", adapter)
session.mount("https://", adapter)
request_kwargs = {}
if headers:
request_kwargs['headers'] = headers
if proxies:
request_kwargs['proxies'] = proxies
response = session.get(url, timeout=timeout, **request_kwargs)
response.raise_for_status()
except ValueError as exc:
msg = _("Invalid parameter: %s") % exc
LOG.error(msg)
raise exception.RuleActionExecutionFailure(reason=msg)
except requests.exceptions.RequestException as exc:
msg = _("Request to %(url)s failed: %(exc)s") % {
'url': url, 'exc': exc}
LOG.error(msg)
raise exception.RuleActionExecutionFailure(reason=msg)

View File

@ -776,6 +776,27 @@ class TestActions(TestInspectionRules):
self.assertEqual('192.168.1.100',
task.node.driver_info['ipmi_address'])
@mock.patch(
'ironic.common.inspection_rules.actions.requests.Session',
autospec=True)
def test_call_api_hook_action_success(self, mock_session):
"""Test CallAPIHookAction successfully calls an API."""
mock_session_instance = mock_session.return_value
mock_response = mock.Mock()
mock_response.status_code = 200
mock_response.raise_for_status.return_value = None
mock_session_instance.get.return_value = mock_response
with task_manager.acquire(self.context, self.node.uuid) as task:
action = inspection_rules.actions.CallAPIHookAction()
test_url = 'http://example.com/simple_hook'
action(task, url=test_url)
mock_session_instance.mount.assert_any_call("http://", mock.ANY)
mock_session_instance.mount.assert_any_call("https://", mock.ANY)
mock_session_instance.get.assert_called_once_with(
test_url, timeout=5)
mock_response.raise_for_status.assert_called_once()
class TestShallowMask(TestInspectionRules):
def setUp(self):

View File

@ -0,0 +1,36 @@
---
features:
- |
Added a new 'api-call' action plugin for Ironic inspection rules.
This action allows triggering an HTTP GET request to a given URL when a
rule matches successfully during node inspection. It is useful for
integrating with external systems such as webhooks, alerting, or
automation tools.
The following options are supported:
* url (required): The HTTP endpoint to call
* timeout (optional, default: 5): Timeout in seconds
* retries (optional, default: 3): Number of retries on failure
* backoff_factor (optional, default: 0.3): Delay factor for retry attempts
* headers, proxies (optional): Additional request configuration
Retry applies to status codes 429, 500, 502, 503, and 504.
Example rule::
[
{
"description": "Trigger webhook after node inspection",
"actions": [
{
"action": "api-call",
"url": "http://example.com/hook",
"timeout": 10,
"retries": 5,
"backoff_factor": 1
}
]
}
]