diff --git a/changelog/67746.added.md b/changelog/67746.added.md new file mode 100644 index 000000000000..7f117aa6ddac --- /dev/null +++ b/changelog/67746.added.md @@ -0,0 +1 @@ +Add `_auth` calls to the master stats diff --git a/salt/channel/server.py b/salt/channel/server.py index 4c43f7a29641..bb35c45a67c5 100644 --- a/salt/channel/server.py +++ b/salt/channel/server.py @@ -268,7 +268,13 @@ async def handle_message(self, payload): payload["enc"] == "clear" and payload.get("load", {}).get("cmd") == "_auth" ): - return self._auth(payload["load"], sign_messages, version) + # Store time at the beginning of serving _auth call + # to calculate duration of the call with master_stats + start = time.time() + ret = self._auth(payload["load"], sign_messages, version) + if self.opts.get("master_stats", False): + await self.payload_handler({"cmd": "_auth", "_start": start}) + return ret # Take the payload_handler function that was registered when we created the channel # and call it, returning control to the caller until it completes diff --git a/salt/master.py b/salt/master.py index c49b34e0f3a9..8e10152dc7ce 100644 --- a/salt/master.py +++ b/salt/master.py @@ -1203,6 +1203,11 @@ async def _handle_payload(self, payload): :param dict payload: The payload route to the appropriate handler """ + if payload.get("cmd") == "_auth": + if self.opts["master_stats"]: + self.stats["_auth"]["runs"] += 1 + self._post_stats(payload["_start"], "_auth") + return key = payload["enc"] load = payload["load"] if key == "clear": diff --git a/tests/pytests/unit/channel/test_server.py b/tests/pytests/unit/channel/test_server.py index 8f6852deb519..152d49bf5898 100644 --- a/tests/pytests/unit/channel/test_server.py +++ b/tests/pytests/unit/channel/test_server.py @@ -1,5 +1,6 @@ import ctypes import multiprocessing +import time import uuid import pytest @@ -553,3 +554,41 @@ async def test_handle_message_exceptions(temp_salt_master): {"version": 3, "enc": "clear", "load": {}} ) assert ret == "Server-side exception handling payload" + + +async def test__auth_cmd_stats_passing(auth_master_opts): + opts = auth_master_opts.copy() + opts.update( + { + "master_stats": True, + } + ) + req = server.ReqServerChannel(opts, None) + + fake_ret = {"enc": "clear", "load": b"FAKELOAD"} + + def _auth_mock(*_, **__): + time.sleep(0.03) + return fake_ret + + with patch.object(req, "_auth", _auth_mock), patch( + "salt.channel.server.ReqServerChannel.payload_handler", + AsyncMock(return_value=(None, {"fun": "send"})), + create=True, + ) as payload_handler: + ret = await req.handle_message( + { + "enc": "clear", + "version": 3, + "load": { + "cmd": "_auth", + "id": "minion", + }, + } + ) + cur_time = time.time() + payload_handler.assert_called_once() + assert payload_handler.call_args[0][0]["cmd"] == "_auth" + auth_call_duration = cur_time - payload_handler.call_args[0][0]["_start"] + assert auth_call_duration >= 0.03 + assert auth_call_duration < 0.05 diff --git a/tests/pytests/unit/test_master.py b/tests/pytests/unit/test_master.py index 768f78b65e2f..8732c3c90d98 100644 --- a/tests/pytests/unit/test_master.py +++ b/tests/pytests/unit/test_master.py @@ -1259,3 +1259,28 @@ def test_on_demand_not_allowed(not_allowed_funcs, tmp_path, caplog): "The following ext_pillar modules are not allowed for on-demand pillar data: git." in caplog.text ) + + +async def test_collect__auth_to_master_stats(): + """ + Check if master stats is collecting _auth calls while not calling neither _handle_aes nor _handle_clear + """ + opts = { + "master_stats": True, + "master_stats_event_iter": 10, + } + req_channel_mock = MagicMock() + mworker = salt.master.MWorker(opts, {}, {}, [req_channel_mock]) + with patch.object(mworker, "_handle_aes") as handle_aes_mock, patch.object( + mworker, "_handle_clear" + ) as handle_clear_mock: + await mworker._handle_payload({"cmd": "_auth", "_start": time.time() - 0.02}) + assert mworker.stats["_auth"]["runs"] == 1 + assert mworker.stats["_auth"]["mean"] >= 0.02 + assert mworker.stats["_auth"]["mean"] < 0.04 + await mworker._handle_payload({"cmd": "_auth", "_start": time.time() - 0.02}) + assert mworker.stats["_auth"]["runs"] == 2 + assert mworker.stats["_auth"]["mean"] >= 0.02 + assert mworker.stats["_auth"]["mean"] < 0.04 + handle_aes_mock.assert_not_called() + handle_clear_mock.assert_not_called()