diff --git a/gvm/protocols/gmp/_gmpnext.py b/gvm/protocols/gmp/_gmpnext.py index 8ce40027..e9a1ccd3 100644 --- a/gvm/protocols/gmp/_gmpnext.py +++ b/gvm/protocols/gmp/_gmpnext.py @@ -218,6 +218,7 @@ def create_agent_group( agent_ids: list[str], scheduler_cron_time: str, *, + max_delay_seconds: int | None = None, comment: str | None = None, ) -> T: """Create a new agent group. @@ -226,6 +227,7 @@ def create_agent_group( name: Name of the new agent group. agent_ids: List of agent UUIDs to include in the group (required). scheduler_cron_time: Scheduler cron to use. + max_delay_seconds: Max delay seconds to use. comment: Optional comment for the group. Raises: @@ -237,6 +239,7 @@ def create_agent_group( comment=comment, agent_ids=agent_ids, scheduler_cron_time=scheduler_cron_time, + max_delay_seconds=max_delay_seconds, ) ) @@ -248,6 +251,7 @@ def modify_agent_group( name: str | None = None, comment: str | None = None, agent_ids: list[str] | None = None, + max_delay_seconds: int | None = None, ) -> T: """Modify an existing agent group. @@ -257,6 +261,7 @@ def modify_agent_group( scheduler_cron_time: Scheduler cron to use. comment: Optional comment for the group. agent_ids: Optional list of agent UUIDs to set for the group. + max_delay_seconds: Optional maximum delay for agent groups. Raises: RequiredArgument: If agent_group_id is not provided. @@ -268,6 +273,7 @@ def modify_agent_group( comment=comment, agent_ids=agent_ids, scheduler_cron_time=scheduler_cron_time, + max_delay_seconds=max_delay_seconds, ) ) diff --git a/gvm/protocols/gmp/requests/next/_agent_groups.py b/gvm/protocols/gmp/requests/next/_agent_groups.py index c817364e..fb9eca5b 100644 --- a/gvm/protocols/gmp/requests/next/_agent_groups.py +++ b/gvm/protocols/gmp/requests/next/_agent_groups.py @@ -18,6 +18,7 @@ def create_agent_group( agent_ids: list[str], scheduler_cron_time: str, *, + max_delay_seconds: int | None = None, comment: str | None = None, ) -> Request: """Create a new agent group. @@ -26,6 +27,7 @@ def create_agent_group( name: Name of the new agent group. agent_ids: List of agent UUIDs to include in the group (required). scheduler_cron_time: Cron-like time to schedule new agent groups (required). + max_delay_seconds: Max delay seconds to schedule the new agent group (optional). comment: Optional comment for the group. Raises: @@ -50,6 +52,9 @@ def create_agent_group( cmd.add_element("name", name) cmd.add_element("scheduler_cron_time", scheduler_cron_time) + if max_delay_seconds is not None: + cmd.add_element("max_delay_seconds", str(max_delay_seconds)) + if comment: cmd.add_element("comment", comment) @@ -89,6 +94,7 @@ def modify_agent_group( name: str | None = None, comment: str | None = None, agent_ids: list[str] | None = None, + max_delay_seconds: int | None = None, ) -> Request: """Modify an existing agent group. @@ -98,6 +104,7 @@ def modify_agent_group( name: Optional new name for the group. comment: Optional comment for the group. agent_ids: Optional list of agent UUIDs to set for the group. + max_delay_seconds: Max delay seconds to schedule the new agent group. Raises: RequiredArgument: If agent_group_id is not provided. @@ -127,6 +134,8 @@ def modify_agent_group( agents_element = cmd.add_element("agents") for agent_id in agent_ids: agents_element.add_element("agent", attrs={"id": agent_id}) + if max_delay_seconds is not None: + cmd.add_element("max_delay_seconds", str(max_delay_seconds)) return cmd diff --git a/gvm/protocols/gmp/requests/next/_agents.py b/gvm/protocols/gmp/requests/next/_agents.py index e0571567..c8434a76 100644 --- a/gvm/protocols/gmp/requests/next/_agents.py +++ b/gvm/protocols/gmp/requests/next/_agents.py @@ -73,20 +73,40 @@ def valid_value(d: Mapping[str, Any], key: str, path: str) -> Any: valid_value(se, "indexer_dir_depth", "agent_script_executor.") sched = se.get("scheduler_cron_time") - if isinstance(sched, str): - items = [sched] - elif isinstance(sched, Sequence) and not isinstance( - sched, (str, bytes) + + if ( + not isinstance(sched, Sequence) + or isinstance(sched, (str, bytes)) + or not sched ): - items = [str(x) for x in sched] - else: - items = [] - if not items or any(not str(x).strip() for x in items): raise RequiredArgument( function=caller, argument="config.agent_script_executor.scheduler_cron_time", ) + for item in sched: + if not isinstance(item, Mapping): + raise InvalidArgumentType( + function=caller, + argument="config.agent_script_executor.scheduler_cron_time", + arg_type="Mapping", + ) + + cron = item.get("cron") + max_delay_seconds = item.get("max_delay_seconds") + + if not isinstance(cron, str) or not cron.strip(): + raise RequiredArgument( + function=caller, + argument="config.agent_script_executor.scheduler_cron_time.cron", + ) + + if type(max_delay_seconds) is not int or max_delay_seconds < 0: + raise InvalidArgumentType( + function=caller, + argument="config.agent_script_executor.scheduler_cron_time.max_delay_seconds", + arg_type="non-negative integer", + ) # heartbeat hb = valid_map(config, "heartbeat", "") valid_value(hb, "interval_in_seconds", "heartbeat.") @@ -162,7 +182,12 @@ def _append_agent_config( "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + { + "cron": "0 */12 * * *", + "max_delay_seconds": 30, + }, + ], }, "heartbeat": { "interval_in_seconds": 300, @@ -212,18 +237,21 @@ def _append_agent_config( xml_se, "indexer_dir_depth", se.get("indexer_dir_depth") ) sched = se.get("scheduler_cron_time") - if isinstance(sched, str): - sched_items = [sched] - else: - sched_items = list(sched or []) - if sched_items: + if sched: xml_sched = xml_se.add_element( "scheduler_cron_time", attrs={"is_list": "1"}, ) - for item in sched_items: - xml_sched.add_element("item", str(item)) + + for item in sched: + xml_item = xml_sched.add_element("item") + cls._add_element(xml_item, "cron", item["cron"]) + cls._add_element( + xml_item, + "max_delay_seconds", + item["max_delay_seconds"], + ) # heartbeat hb = config["heartbeat"] @@ -311,7 +339,12 @@ def modify_agents( "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], # str or list[str] + "scheduler_cron_time": [ + { + "cron": "0 */12 * * *", + "max_delay_seconds": 30, + }, + ], }, "heartbeat": { "interval_in_seconds": 300, @@ -393,7 +426,16 @@ def modify_agent_control_scan_config( "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + { + "cron": "0 */12 * * *", + "max_delay_seconds": 30, + }, + { + "cron": "0 23 * * *", + "max_delay_seconds": 60, + }, + ], }, "heartbeat": { "interval_in_seconds": 300, @@ -410,6 +452,7 @@ def modify_agent_control_scan_config( function=cls.modify_agent_control_scan_config.__name__, argument="agent_control_id", ) + if not config_defaults: raise RequiredArgument( function=cls.modify_agent_control_scan_config.__name__, diff --git a/tests/protocols/gmpnext/entities/agent_groups/test_create_agent_group.py b/tests/protocols/gmpnext/entities/agent_groups/test_create_agent_group.py index 80bf2c74..95a0291a 100644 --- a/tests/protocols/gmpnext/entities/agent_groups/test_create_agent_group.py +++ b/tests/protocols/gmpnext/entities/agent_groups/test_create_agent_group.py @@ -12,12 +12,14 @@ def test_create_agent_group(self): agent_ids=["agent-1", "agent-2"], comment="Sample comment", scheduler_cron_time="0 */5 * * *", + max_delay_seconds=30, ) self.connection.send.has_been_called_with( b"" b"ExampleGroup" b"0 */5 * * *" + b"30" b"Sample comment" b'' b"" @@ -57,3 +59,35 @@ def test_create_agent_group_without_comment(self): b'' b"" ) + + def test_create_agent_group_with_zero_max_delay(self): + self.gmp.create_agent_group( + name="GroupX", + agent_ids=["a1"], + scheduler_cron_time="0 */5 * * *", + max_delay_seconds=0, + ) + + self.connection.send.has_been_called_with( + b"" + b"GroupX" + b"0 */5 * * *" + b"0" + b'' + b"" + ) + + def test_create_agent_group_without_max_delay(self): + self.gmp.create_agent_group( + name="GroupX", + agent_ids=["a1"], + scheduler_cron_time="0 */5 * * *", + ) + + self.connection.send.has_been_called_with( + b"" + b"GroupX" + b"0 */5 * * *" + b'' + b"" + ) diff --git a/tests/protocols/gmpnext/entities/agent_groups/test_modify_agent_group.py b/tests/protocols/gmpnext/entities/agent_groups/test_modify_agent_group.py index c7f809ea..e179ba14 100644 --- a/tests/protocols/gmpnext/entities/agent_groups/test_modify_agent_group.py +++ b/tests/protocols/gmpnext/entities/agent_groups/test_modify_agent_group.py @@ -12,6 +12,7 @@ def test_modify_agent_group(self): name="NewName", comment="Updated comment", scheduler_cron_time="0 */5 * * *", + max_delay_seconds=30, agent_ids=["agent-1", "agent-2"], ) @@ -21,6 +22,7 @@ def test_modify_agent_group(self): b"NewName" b"Updated comment" b'' + b"30" b"" ) @@ -83,3 +85,29 @@ def test_modify_agent_group_without_agent_ids(self): b"Updated comment" b"" ) + + def test_modify_agent_group_with_zero_max_delay(self): + self.gmp.modify_agent_group( + agent_group_id="group-1", + scheduler_cron_time="0 */5 * * *", + max_delay_seconds=0, + ) + + self.connection.send.has_been_called_with( + b'' + b"0 */5 * * *" + b"0" + b"" + ) + + def test_modify_agent_group_without_max_delay(self): + self.gmp.modify_agent_group( + agent_group_id="group-1", + scheduler_cron_time="0 */5 * * *", + ) + + self.connection.send.has_been_called_with( + b'' + b"0 */5 * * *" + b"" + ) diff --git a/tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py b/tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py index 5e293bae..0d9d3b7a 100644 --- a/tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py +++ b/tests/protocols/gmpnext/entities/agents/test_modify_agent_controller_scan_config.py @@ -21,8 +21,8 @@ def test_modify_agent_control_scan_config_full(self): "bulk_throttle_time_in_ms": 100, "indexer_dir_depth": 10, "scheduler_cron_time": [ - "0 23 * * *", - "0 22 * * *", + {"cron": "0 23 * * *", "max_delay_seconds": 30}, + {"cron": "0 22 * * *", "max_delay_seconds": 60}, ], }, "heartbeat": { @@ -56,8 +56,10 @@ def test_modify_agent_control_scan_config_full(self): b"100" b"10" b'' - b"0 23 * * *" - b"0 22 * * *" + b"0 23 * * *" + b"30" + b"0 22 * * *" + b"60" b"" b"" b"" @@ -72,32 +74,85 @@ def test_modify_agent_control_scan_config_full(self): b"" ) + def test_modify_agent_control_scan_config_zero_max_delay(self): + config = { + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 1, + "bulk_throttle_time_in_ms": 100, + "indexer_dir_depth": 10, + "scheduler_cron_time": [ + {"cron": "0 23 * * *", "max_delay_seconds": 0}, + ], + }, + "heartbeat": { + "interval_in_seconds": 600, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": {"update_to_latest": True}, + } + + self.gmp.modify_agent_control_scan_config( + "3b4be213-281f-49ee-b457-5a5f34f71510", + config=config, + ) + + self.connection.send.has_been_called_with( + b'' + b"" + b"6" + b"60" + b"10" + b"" + b"1" + b"100" + b"10" + b'' + b"0 23 * * *" + b"0" + b"" + b"600" + b"1" + b"" + b"1" + b"" + b"" + ) + def test_modify_agent_control_scan_config_with_missing_element_raises(self): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - # max_jitter_in_seconds is missing - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + # max_jitter_in_seconds is missing + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - "agent_control_defaults": { - "update_to_latest": True, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): @@ -111,30 +166,33 @@ def test_modify_agent_control_scan_config_missing_id_raises(self): self.gmp.modify_agent_control_scan_config( "", # missing id config={ - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + { + "cron": "0 */12 * * *", + "max_delay_seconds": 30, } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + ], }, - "agent_control_defaults": { - "update_to_latest": True, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, }, ) @@ -155,24 +213,24 @@ def test_modify_agent_control_scan_config_agent_control_not_mapping_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": "oops-not-a-mapping", - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": "oops-not-a-mapping", + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - "agent_control_defaults": { - "update_to_latest": True, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -181,30 +239,28 @@ def test_modify_agent_control_scan_config_agent_control_not_mapping_raises( def test_modify_agent_control_scan_config_scheduler_empty_list_raises(self): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": [], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [], }, - } + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -215,30 +271,31 @@ def test_modify_agent_control_scan_config_scheduler_with_empty_item_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["", " "], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "", "max_delay_seconds": 30}, + {"cron": " ", "max_delay_seconds": 30}, + ], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -249,24 +306,24 @@ def test_modify_agent_control_scan_config_missing_agent_control_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - # "agent_control": missing - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + # "agent_control": missing + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - "agent_control_defaults": { - "update_to_latest": True, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -275,26 +332,26 @@ def test_modify_agent_control_scan_config_missing_agent_control_raises( def test_modify_agent_control_scan_config_missing_retry_block_raises(self): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - # "retry": {} - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + # "retry": {} }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - } + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -305,30 +362,30 @@ def test_modify_agent_control_scan_config_missing_retry_attempts_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - # "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + # "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -337,30 +394,30 @@ def test_modify_agent_control_scan_config_missing_retry_attempts_raises( def test_modify_agent_control_scan_config_missing_retry_delay_raises(self): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - # "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + # "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - } + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -371,30 +428,30 @@ def test_modify_agent_control_scan_config_missing_retry_max_jitter_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - # "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + # "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - "agent_control_defaults": { - "update_to_latest": True, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -405,25 +462,23 @@ def test_modify_agent_control_scan_config_missing_agent_script_executor_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - # "agent_script_executor": missing, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + # "agent_script_executor": missing, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -432,30 +487,30 @@ def test_modify_agent_control_scan_config_missing_agent_script_executor_raises( def test_modify_agent_control_scan_config_missing_bulk_size_raises(self): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - # "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + # "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - } + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -466,30 +521,30 @@ def test_modify_agent_control_scan_config_missing_bulk_throttle_time_in_ms_raise self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - # "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + # "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - } + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -500,30 +555,30 @@ def test_modify_agent_control_scan_config_missing_indexer_dir_depth_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - # "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + # "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -534,27 +589,27 @@ def test_modify_agent_control_scan_config_missing_heartbeat_block_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - # "heartbeat": missing, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - } + # "heartbeat": missing, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -565,30 +620,30 @@ def test_modify_agent_control_scan_config_missing_heartbeat_interval_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - # "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], + }, + "heartbeat": { + # "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -599,30 +654,30 @@ def test_modify_agent_control_scan_config_missing_heartbeat_miss_until_inactive_ self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - # "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, - } + "heartbeat": { + "interval_in_seconds": 300, + # "miss_until_inactive": 1, + }, + }, + "agent_control_defaults": { + "update_to_latest": True, + }, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -633,28 +688,28 @@ def test_modify_agent_control_scan_config_agent_control_defaults_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - # "agent_control_defaults": missing, - } + }, + # "agent_control_defaults": missing, } with self.assertRaises(RequiredArgument): self.gmp.modify_agent_control_scan_config( @@ -665,32 +720,32 @@ def test_modify_agent_control_scan_config_missing_update_to_latest_raises( self, ): cfg = { - "config_defaults": { - "agent_defaults": { - "agent_control": { - "retry": { - "attempts": 6, - "delay_in_seconds": 60, - "max_jitter_in_seconds": 10, - } - }, - "agent_script_executor": { - "bulk_size": 2, - "bulk_throttle_time_in_ms": 300, - "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], - }, - "heartbeat": { - "interval_in_seconds": 300, - "miss_until_inactive": 1, - }, + "agent_defaults": { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } }, - "agent_control_defaults": { - # "update_to_latest": True, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, }, - } + }, + "agent_control_defaults": { + # "update_to_latest": True, + }, } - with self.assertRaises(RequiredArgument): + with self.assertRaises(InvalidArgumentType): self.gmp.modify_agent_control_scan_config( "fb6451bf-ec5a-45a8-8bab-5cf4b862e51b", config=cfg ) @@ -712,8 +767,8 @@ def test_modify_agent_control_scan_config_wrong_update_to_latest_raises( "bulk_throttle_time_in_ms": 100, "indexer_dir_depth": 10, "scheduler_cron_time": [ - "0 23 * * *", - "0 22 * * *", + {"cron": "0 23 * * *", "max_delay_seconds": 30}, + {"cron": "0 22 * * *", "max_delay_seconds": 60}, ], }, "heartbeat": { diff --git a/tests/protocols/gmpnext/entities/agents/test_modify_agents.py b/tests/protocols/gmpnext/entities/agents/test_modify_agents.py index d2db4d00..544282ba 100644 --- a/tests/protocols/gmpnext/entities/agents/test_modify_agents.py +++ b/tests/protocols/gmpnext/entities/agents/test_modify_agents.py @@ -2,7 +2,7 @@ # # SPDX-License-Identifier: GPL-3.0-or-later -from gvm.errors import RequiredArgument +from gvm.errors import InvalidArgumentType, RequiredArgument class GmpModifyAgentsTestMixin: @@ -52,7 +52,9 @@ def test_modify_agents_with_full_config_and_comment(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -83,7 +85,10 @@ def test_modify_agents_with_full_config_and_comment(self): b"300" b"100" b'' - b"0 */12 * * *" + b"" + b"0 */12 * * *" + b"30" + b"" b"" b"" b"" @@ -108,7 +113,9 @@ def test_modify_agents_omits_optional_elements_when_none(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": { "interval_in_seconds": 300, @@ -140,7 +147,10 @@ def test_modify_agents_omits_optional_elements_when_none(self): b"300" b"100" b'' - b"0 */12 * * *" + b"" + b"0 */12 * * *" + b"30" + b"" b"" b"" b"" @@ -214,7 +224,10 @@ def test_modify_agents_scheduler_with_empty_item_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["", " "], + "scheduler_cron_time": [ + {"cron": "", "max_delay_seconds": 30}, + {"cron": " ", "max_delay_seconds": 30}, + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -228,7 +241,9 @@ def test_modify_agents_missing_agent_control_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -244,7 +259,9 @@ def test_modify_agents_missing_retry_block_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -264,7 +281,9 @@ def test_modify_agents_missing_retry_attempts_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -284,7 +303,9 @@ def test_modify_agents_missing_retry_delay_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -304,7 +325,9 @@ def test_modify_agents_missing_retry_max_jitter_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -339,7 +362,9 @@ def test_modify_agents_missing_bulk_size_raises(self): # "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -359,7 +384,9 @@ def test_modify_agents_missing_bulk_throttle_time_in_ms_raises(self): "bulk_size": 2, # "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -379,7 +406,9 @@ def test_modify_agents_missing_indexer_dir_depth_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, # "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } @@ -399,7 +428,9 @@ def test_modify_agents_missing_heartbeat_block_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, # "heartbeat": missing } @@ -419,7 +450,9 @@ def test_modify_agents_missing_heartbeat_interval_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": { # "interval_in_seconds": 300, @@ -442,7 +475,9 @@ def test_modify_agents_missing_heartbeat_miss_until_inactive_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": { "interval_in_seconds": 300, @@ -465,9 +500,132 @@ def test_modify_agents_agent_control_not_mapping_raises(self): "bulk_size": 2, "bulk_throttle_time_in_ms": 300, "indexer_dir_depth": 100, - "scheduler_cron_time": ["0 */12 * * *"], + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 30} + ], }, "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, } with self.assertRaises(RequiredArgument): self.gmp.modify_agents(agent_ids=["agent-123"], config=cfg) + + def test_modify_agents_scheduler_zero_max_delay(self): + cfg = { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": 0} + ], + }, + "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + } + + self.gmp.modify_agents(agent_ids=["agent-123"], config=cfg) + + self.connection.send.has_been_called_with( + b"" + b'' + b"" + b"" + b"" + b"6" + b"60" + b"10" + b"" + b"" + b"" + b"2" + b"300" + b"100" + b'' + b"0 */12 * * *" + b"0" + b"" + b"" + b"" + b"300" + b"1" + b"" + b"" + b"" + ) + + def test_modify_agents_scheduler_missing_max_delay_raises(self): + cfg = { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [{"cron": "0 */12 * * *"}], + }, + "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + } + + with self.assertRaises(InvalidArgumentType): + self.gmp.modify_agents(agent_ids=["agent-123"], config=cfg) + + def test_modify_agents_scheduler_negative_max_delay_raises(self): + cfg = { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 */12 * * *", "max_delay_seconds": -1} + ], + }, + "heartbeat": {"interval_in_seconds": 300, "miss_until_inactive": 1}, + } + + with self.assertRaises(InvalidArgumentType): + self.gmp.modify_agents(agent_ids=["agent-123"], config=cfg) + + def test_modify_agents_scheduler_item_not_mapping_raises(self): + cfg = { + "agent_control": { + "retry": { + "attempts": 6, + "delay_in_seconds": 60, + "max_jitter_in_seconds": 10, + } + }, + "agent_script_executor": { + "bulk_size": 2, + "bulk_throttle_time_in_ms": 300, + "indexer_dir_depth": 100, + "scheduler_cron_time": [ + {"cron": "0 23 * * *", "max_delay_seconds": 30}, + "0 22 * * *", + ], + }, + "heartbeat": { + "interval_in_seconds": 300, + "miss_until_inactive": 1, + }, + } + + with self.assertRaises(InvalidArgumentType): + self.gmp.modify_agents(agent_ids=["agent-123"], config=cfg)