Compare commits
	
		
			16 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 617738bb28 | ||
|  | f6ac15d790 | ||
|  | 144a3dedbb | ||
|  | f90d966f1a | ||
|  | b188e2ea97 | ||
|  | b63b2002a9 | ||
|  | 059edc36e4 | ||
|  | 902034ecf0 | ||
|  | 4d27f2b594 | ||
|  | f73ea8f9f4 | ||
|  | 596ec3eb2c | ||
|  | cb71319ff0 | ||
|  | 7b7164a9a2 | ||
|  | 721ce8f91a | ||
|  | 9fdbae986c | ||
|  | 9535a9fa3f | 
| @@ -86,27 +86,35 @@ class Agent(BaseAuditModel): | ||||
|     @property | ||||
|     def arch(self): | ||||
|         if self.operating_system is not None: | ||||
|             if "64bit" in self.operating_system: | ||||
|             if "64 bit" in self.operating_system or "64bit" in self.operating_system: | ||||
|                 return "64" | ||||
|             elif "32bit" in self.operating_system: | ||||
|             elif "32 bit" in self.operating_system or "32bit" in self.operating_system: | ||||
|                 return "32" | ||||
|         return "64" | ||||
|         return None | ||||
|  | ||||
|     @property | ||||
|     def winagent_dl(self): | ||||
|         return settings.DL_64 if self.arch == "64" else settings.DL_32 | ||||
|         if self.arch == "64": | ||||
|             return settings.DL_64 | ||||
|         elif self.arch == "32": | ||||
|             return settings.DL_32 | ||||
|         return None | ||||
|  | ||||
|     @property | ||||
|     def winsalt_dl(self): | ||||
|         return settings.SALT_64 if self.arch == "64" else settings.SALT_32 | ||||
|         if self.arch == "64": | ||||
|             return settings.SALT_64 | ||||
|         elif self.arch == "32": | ||||
|             return settings.SALT_32 | ||||
|         return None | ||||
|  | ||||
|     @property | ||||
|     def win_inno_exe(self): | ||||
|         return ( | ||||
|             f"winagent-v{settings.LATEST_AGENT_VER}.exe" | ||||
|             if self.arch == "64" | ||||
|             else f"winagent-v{settings.LATEST_AGENT_VER}-x86.exe" | ||||
|         ) | ||||
|         if self.arch == "64": | ||||
|             return f"winagent-v{settings.LATEST_AGENT_VER}.exe" | ||||
|         elif self.arch == "32": | ||||
|             return f"winagent-v{settings.LATEST_AGENT_VER}-x86.exe" | ||||
|         return None | ||||
|  | ||||
|     @property | ||||
|     def status(self): | ||||
|   | ||||
| @@ -30,6 +30,14 @@ def send_agent_update_task(pks, version): | ||||
|     for chunk in chunks: | ||||
|         for pk in chunk: | ||||
|             agent = Agent.objects.get(pk=pk) | ||||
|  | ||||
|             # skip if we can't determine the arch | ||||
|             if agent.arch is None: | ||||
|                 logger.warning( | ||||
|                     f"Unable to determine arch on {agent.salt_id}. Skipping." | ||||
|                 ) | ||||
|                 continue | ||||
|  | ||||
|             # golang agent only backwards compatible with py agent 0.11.2 | ||||
|             # force an upgrade to the latest python agent if version < 0.11.2 | ||||
|             if pyver.parse(agent.version) < pyver.parse("0.11.2"): | ||||
| @@ -42,6 +50,9 @@ def send_agent_update_task(pks, version): | ||||
|             else: | ||||
|                 url = agent.winagent_dl | ||||
|                 inno = agent.win_inno_exe | ||||
|             logger.info( | ||||
|                 f"Updating {agent.salt_id} current version {agent.version} using {inno}" | ||||
|             ) | ||||
|             r = agent.salt_api_async( | ||||
|                 func="win_agent.do_agent_update_v2", | ||||
|                 kwargs={ | ||||
| @@ -49,6 +60,7 @@ def send_agent_update_task(pks, version): | ||||
|                     "url": url, | ||||
|                 }, | ||||
|             ) | ||||
|             logger.info(f"{agent.salt_id}: {r}") | ||||
|         sleep(10) | ||||
|  | ||||
|  | ||||
| @@ -56,6 +68,7 @@ def send_agent_update_task(pks, version): | ||||
| def auto_self_agent_update_task(): | ||||
|     core = CoreSettings.objects.first() | ||||
|     if not core.agent_auto_update: | ||||
|         logger.info("Agent auto update is disabled. Skipping.") | ||||
|         return | ||||
|  | ||||
|     q = Agent.objects.only("pk", "version") | ||||
| @@ -64,12 +77,21 @@ def auto_self_agent_update_task(): | ||||
|         for i in q | ||||
|         if pyver.parse(i.version) < pyver.parse(settings.LATEST_AGENT_VER) | ||||
|     ] | ||||
|     logger.info(f"Updating {len(agents)}") | ||||
|  | ||||
|     chunks = (agents[i : i + 30] for i in range(0, len(agents), 30)) | ||||
|  | ||||
|     for chunk in chunks: | ||||
|         for pk in chunk: | ||||
|             agent = Agent.objects.get(pk=pk) | ||||
|  | ||||
|             # skip if we can't determine the arch | ||||
|             if agent.arch is None: | ||||
|                 logger.warning( | ||||
|                     f"Unable to determine arch on {agent.salt_id}. Skipping." | ||||
|                 ) | ||||
|                 continue | ||||
|  | ||||
|             # golang agent only backwards compatible with py agent 0.11.2 | ||||
|             # force an upgrade to the latest python agent if version < 0.11.2 | ||||
|             if pyver.parse(agent.version) < pyver.parse("0.11.2"): | ||||
| @@ -82,6 +104,9 @@ def auto_self_agent_update_task(): | ||||
|             else: | ||||
|                 url = agent.winagent_dl | ||||
|                 inno = agent.win_inno_exe | ||||
|             logger.info( | ||||
|                 f"Updating {agent.salt_id} current version {agent.version} using {inno}" | ||||
|             ) | ||||
|             r = agent.salt_api_async( | ||||
|                 func="win_agent.do_agent_update_v2", | ||||
|                 kwargs={ | ||||
| @@ -89,6 +114,7 @@ def auto_self_agent_update_task(): | ||||
|                     "url": url, | ||||
|                 }, | ||||
|             ) | ||||
|             logger.info(f"{agent.salt_id}: {r}") | ||||
|         sleep(10) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -6,11 +6,22 @@ from model_bakery import baker | ||||
| from itertools import cycle | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.utils import timezone as djangotime | ||||
|  | ||||
| from tacticalrmm.test import BaseTestCase, TacticalTestCase | ||||
| from .serializers import AgentSerializer | ||||
| from winupdate.serializers import WinUpdatePolicySerializer | ||||
| from .models import Agent | ||||
| from .tasks import ( | ||||
|     auto_self_agent_update_task, | ||||
|     update_salt_minion_task, | ||||
|     get_wmi_detail_task, | ||||
|     sync_salt_modules_task, | ||||
|     batch_sync_modules_task, | ||||
|     batch_sysinfo_task, | ||||
|     OLD_64_PY_AGENT, | ||||
|     OLD_32_PY_AGENT, | ||||
| ) | ||||
| from winupdate.models import WinUpdatePolicy | ||||
|  | ||||
|  | ||||
| @@ -779,3 +790,224 @@ class TestAgentViewsNew(TacticalTestCase): | ||||
|         self.assertEqual(r.status_code, 400) | ||||
|  | ||||
|         self.check_not_authenticated("post", url) | ||||
|  | ||||
|  | ||||
| class TestAgentTasks(TacticalTestCase): | ||||
|     def setUp(self): | ||||
|         self.authenticate() | ||||
|         self.setup_coresettings() | ||||
|  | ||||
|     @patch("agents.models.Agent.salt_api_async", return_value=None) | ||||
|     def test_get_wmi_detail_task(self, salt_api_async): | ||||
|         self.agent = baker.make_recipe("agents.agent") | ||||
|         ret = get_wmi_detail_task.s(self.agent.pk).apply() | ||||
|         salt_api_async.assert_called_with(timeout=30, func="win_agent.local_sys_info") | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|     @patch("agents.models.Agent.salt_api_cmd") | ||||
|     def test_sync_salt_modules_task(self, salt_api_cmd): | ||||
|         self.agent = baker.make_recipe("agents.agent") | ||||
|         salt_api_cmd.return_value = {"return": [{f"{self.agent.salt_id}": []}]} | ||||
|         ret = sync_salt_modules_task.s(self.agent.pk).apply() | ||||
|         salt_api_cmd.assert_called_with(timeout=35, func="saltutil.sync_modules") | ||||
|         self.assertEqual( | ||||
|             ret.result, f"Successfully synced salt modules on {self.agent.hostname}" | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|         salt_api_cmd.return_value = "timeout" | ||||
|         ret = sync_salt_modules_task.s(self.agent.pk).apply() | ||||
|         self.assertEqual(ret.result, f"Unable to sync modules {self.agent.salt_id}") | ||||
|  | ||||
|         salt_api_cmd.return_value = "error" | ||||
|         ret = sync_salt_modules_task.s(self.agent.pk).apply() | ||||
|         self.assertEqual(ret.result, f"Unable to sync modules {self.agent.salt_id}") | ||||
|  | ||||
|     @patch("agents.models.Agent.salt_batch_async", return_value=None) | ||||
|     @patch("agents.tasks.sleep", return_value=None) | ||||
|     def test_batch_sync_modules_task(self, mock_sleep, salt_batch_async): | ||||
|         # chunks of 50, 60 online should run only 2 times | ||||
|         baker.make_recipe( | ||||
|             "agents.online_agent", last_seen=djangotime.now(), _quantity=60 | ||||
|         ) | ||||
|         baker.make_recipe( | ||||
|             "agents.overdue_agent", | ||||
|             last_seen=djangotime.now() - djangotime.timedelta(minutes=9), | ||||
|             _quantity=115, | ||||
|         ) | ||||
|         ret = batch_sync_modules_task.s().apply() | ||||
|         self.assertEqual(salt_batch_async.call_count, 2) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|     @patch("agents.models.Agent.salt_batch_async", return_value=None) | ||||
|     @patch("agents.tasks.sleep", return_value=None) | ||||
|     def test_batch_sysinfo_task(self, mock_sleep, salt_batch_async): | ||||
|         # chunks of 30, 70 online should run only 3 times | ||||
|         self.online = baker.make_recipe( | ||||
|             "agents.online_agent", version=settings.LATEST_AGENT_VER, _quantity=70 | ||||
|         ) | ||||
|         self.overdue = baker.make_recipe( | ||||
|             "agents.overdue_agent", version=settings.LATEST_AGENT_VER, _quantity=115 | ||||
|         ) | ||||
|         ret = batch_sysinfo_task.s().apply() | ||||
|         self.assertEqual(salt_batch_async.call_count, 3) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         salt_batch_async.reset_mock() | ||||
|         [i.delete() for i in self.online] | ||||
|         [i.delete() for i in self.overdue] | ||||
|  | ||||
|         # test old agents, should not run | ||||
|         self.online_old = baker.make_recipe( | ||||
|             "agents.online_agent", version="0.10.2", _quantity=70 | ||||
|         ) | ||||
|         self.overdue_old = baker.make_recipe( | ||||
|             "agents.overdue_agent", version="0.10.2", _quantity=115 | ||||
|         ) | ||||
|         ret = batch_sysinfo_task.s().apply() | ||||
|         salt_batch_async.assert_not_called() | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|     @patch("agents.models.Agent.salt_api_async", return_value=None) | ||||
|     @patch("agents.tasks.sleep", return_value=None) | ||||
|     def test_update_salt_minion_task(self, mock_sleep, salt_api_async): | ||||
|         # test agents that need salt update | ||||
|         self.agents = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             version=settings.LATEST_AGENT_VER, | ||||
|             salt_ver="1.0.3", | ||||
|             _quantity=53, | ||||
|         ) | ||||
|         ret = update_salt_minion_task.s().apply() | ||||
|         self.assertEqual(salt_api_async.call_count, 53) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         [i.delete() for i in self.agents] | ||||
|         salt_api_async.reset_mock() | ||||
|  | ||||
|         # test agents that need salt update but agent version too low | ||||
|         self.agents = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             version="0.10.2", | ||||
|             salt_ver="1.0.3", | ||||
|             _quantity=53, | ||||
|         ) | ||||
|         ret = update_salt_minion_task.s().apply() | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         salt_api_async.assert_not_called() | ||||
|         [i.delete() for i in self.agents] | ||||
|         salt_api_async.reset_mock() | ||||
|  | ||||
|         # test agents already on latest salt ver | ||||
|         self.agents = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             version=settings.LATEST_AGENT_VER, | ||||
|             salt_ver=settings.LATEST_SALT_VER, | ||||
|             _quantity=53, | ||||
|         ) | ||||
|         ret = update_salt_minion_task.s().apply() | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         salt_api_async.assert_not_called() | ||||
|  | ||||
|     @patch("agents.models.Agent.salt_api_async") | ||||
|     @patch("agents.tasks.sleep", return_value=None) | ||||
|     def test_auto_self_agent_update_task(self, mock_sleep, salt_api_async): | ||||
|         # test 64bit golang agent | ||||
|         self.agent64 = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 10 Pro, 64 bit (build 19041.450)", | ||||
|             version="1.0.0", | ||||
|         ) | ||||
|         salt_api_async.return_value = True | ||||
|         ret = auto_self_agent_update_task.s().apply() | ||||
|         salt_api_async.assert_called_with( | ||||
|             func="win_agent.do_agent_update_v2", | ||||
|             kwargs={ | ||||
|                 "inno": f"winagent-v{settings.LATEST_AGENT_VER}.exe", | ||||
|                 "url": settings.DL_64, | ||||
|             }, | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         self.agent64.delete() | ||||
|         salt_api_async.reset_mock() | ||||
|  | ||||
|         # test 32bit golang agent | ||||
|         self.agent32 = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 7 Professional, 32 bit (build 7601.24544)", | ||||
|             version="1.0.0", | ||||
|         ) | ||||
|         salt_api_async.return_value = True | ||||
|         ret = auto_self_agent_update_task.s().apply() | ||||
|         salt_api_async.assert_called_with( | ||||
|             func="win_agent.do_agent_update_v2", | ||||
|             kwargs={ | ||||
|                 "inno": f"winagent-v{settings.LATEST_AGENT_VER}-x86.exe", | ||||
|                 "url": settings.DL_32, | ||||
|             }, | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         self.agent32.delete() | ||||
|         salt_api_async.reset_mock() | ||||
|  | ||||
|         # test agent that has a null os field | ||||
|         self.agentNone = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system=None, | ||||
|             version="1.0.0", | ||||
|         ) | ||||
|         ret = auto_self_agent_update_task.s().apply() | ||||
|         salt_api_async.assert_not_called() | ||||
|         self.agentNone.delete() | ||||
|         salt_api_async.reset_mock() | ||||
|  | ||||
|         # test auto update disabled in global settings | ||||
|         self.agent64 = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 10 Pro, 64 bit (build 19041.450)", | ||||
|             version="1.0.0", | ||||
|         ) | ||||
|         self.coresettings.agent_auto_update = False | ||||
|         self.coresettings.save(update_fields=["agent_auto_update"]) | ||||
|         ret = auto_self_agent_update_task.s().apply() | ||||
|         salt_api_async.assert_not_called() | ||||
|  | ||||
|         # reset core settings | ||||
|         self.agent64.delete() | ||||
|         salt_api_async.reset_mock() | ||||
|         self.coresettings.agent_auto_update = True | ||||
|         self.coresettings.save(update_fields=["agent_auto_update"]) | ||||
|  | ||||
|         # test 64bit python agent | ||||
|         self.agent64py = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 10 Pro, 64 bit (build 19041.450)", | ||||
|             version="0.11.1", | ||||
|         ) | ||||
|         salt_api_async.return_value = True | ||||
|         ret = auto_self_agent_update_task.s().apply() | ||||
|         salt_api_async.assert_called_with( | ||||
|             func="win_agent.do_agent_update_v2", | ||||
|             kwargs={ | ||||
|                 "inno": "winagent-v0.11.2.exe", | ||||
|                 "url": OLD_64_PY_AGENT, | ||||
|             }, | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         self.agent64py.delete() | ||||
|         salt_api_async.reset_mock() | ||||
|  | ||||
|         # test 32bit python agent | ||||
|         self.agent32py = baker.make_recipe( | ||||
|             "agents.agent", | ||||
|             operating_system="Windows 7 Professional, 32 bit (build 7601.24544)", | ||||
|             version="0.11.1", | ||||
|         ) | ||||
|         salt_api_async.return_value = True | ||||
|         ret = auto_self_agent_update_task.s().apply() | ||||
|         salt_api_async.assert_called_with( | ||||
|             func="win_agent.do_agent_update_v2", | ||||
|             kwargs={ | ||||
|                 "inno": "winagent-v0.11.2-x86.exe", | ||||
|                 "url": OLD_32_PY_AGENT, | ||||
|             }, | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
| @@ -888,18 +888,22 @@ class TestPolicyTasks(TacticalTestCase): | ||||
|         # setup data | ||||
|         policy = baker.make("automation.Policy", active=True) | ||||
|         self.create_checks(policy=policy) | ||||
|         clients = baker.make("clients.Client", client=seq("Default"), _quantity=2) | ||||
|         baker.make( | ||||
|             "clients.Site", client=cycle(clients), site=seq("Default"), _quantity=4 | ||||
|         clients = baker.make("clients.Client", client=seq("Client"), _quantity=2) | ||||
|         sites = baker.make( | ||||
|             "clients.Site", client=cycle(clients), site=seq("Site"), _quantity=4 | ||||
|         ) | ||||
|         server_agent = baker.make_recipe( | ||||
|             "agents.server_agent", client="Default1", site="Default1" | ||||
|             "agents.server_agent", client=clients[0].client, site=sites[0].site | ||||
|         ) | ||||
|         workstation_agent = baker.make_recipe( | ||||
|             "agents.workstation_agent", client="Default1", site="Default3" | ||||
|             "agents.workstation_agent", client=clients[0].client, site=sites[2].site | ||||
|         ) | ||||
|         agent1 = baker.make_recipe( | ||||
|             "agents.server_agent", client=clients[1].client, site=sites[1].site | ||||
|         ) | ||||
|         agent2 = baker.make_recipe( | ||||
|             "agents.workstation_agent", client=clients[1].client, site=sites[3].site | ||||
|         ) | ||||
|         agent1 = baker.make_recipe("agents.agent", client="Default2", site="Default2") | ||||
|         agent2 = baker.make_recipe("agents.agent", client="Default2", site="Default4") | ||||
|         core = CoreSettings.objects.first() | ||||
|         core.server_policy = policy | ||||
|         core.workstation_policy = policy | ||||
| @@ -1027,7 +1031,7 @@ class TestPolicyTasks(TacticalTestCase): | ||||
|  | ||||
|         # setup data | ||||
|         policy = baker.make("automation.Policy", active=True) | ||||
|         tasks = baker.make( | ||||
|         baker.make( | ||||
|             "autotasks.AutomatedTask", policy=policy, name=seq("Task"), _quantity=3 | ||||
|         ) | ||||
|         clients = baker.make( | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| from loguru import logger | ||||
| from tacticalrmm.celery import app | ||||
| from django.conf import settings | ||||
| from datetime import datetime as dt | ||||
| import pytz | ||||
| from django.utils import timezone as djangotime | ||||
|  | ||||
| from .models import AutomatedTask | ||||
| from logs.models import PendingAction | ||||
| @@ -46,6 +47,16 @@ def create_win_task_schedule(pk, pending_action=False): | ||||
|  | ||||
|     elif task.task_type == "runonce": | ||||
|  | ||||
|         # check if scheduled time is in the past | ||||
|         agent_tz = pytz.timezone(task.agent.timezone) | ||||
|         task_time_utc = task.run_time_date.replace(tzinfo=agent_tz).astimezone(pytz.utc) | ||||
|         now = djangotime.now() | ||||
|         if task_time_utc < now: | ||||
|             task.run_time_date = now.astimezone(agent_tz).replace( | ||||
|                 tzinfo=pytz.utc | ||||
|             ) + djangotime.timedelta(minutes=5) | ||||
|             task.save() | ||||
|  | ||||
|         r = task.agent.salt_api_cmd( | ||||
|             timeout=20, | ||||
|             func="task.create_task", | ||||
| @@ -61,6 +72,7 @@ def create_win_task_schedule(pk, pending_action=False): | ||||
|                 f'start_time="{task.run_time_date.strftime("%H:%M")}"', | ||||
|                 "ac_only=False", | ||||
|                 "stop_if_on_batteries=False", | ||||
|                 "start_when_available=True", | ||||
|             ], | ||||
|         ) | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| from unittest.mock import patch, call | ||||
| from model_bakery import baker | ||||
| from django.utils import timezone as djangotime | ||||
|  | ||||
| from tacticalrmm.test import TacticalTestCase | ||||
|  | ||||
| from .models import AutomatedTask | ||||
| from logs.models import PendingAction | ||||
| from .serializers import AutoTaskSerializer | ||||
| from .tasks import remove_orphaned_win_tasks, run_win_task | ||||
| from .tasks import remove_orphaned_win_tasks, run_win_task, create_win_task_schedule | ||||
|  | ||||
|  | ||||
| class TestAutotaskViews(TacticalTestCase): | ||||
| @@ -281,3 +283,201 @@ class TestAutoTaskCeleryTasks(TacticalTestCase): | ||||
|         salt_api_async.return_value = "Response 200" | ||||
|         ret = run_win_task.s(self.task1.pk).apply() | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|     @patch("agents.models.Agent.salt_api_cmd") | ||||
|     def test_create_win_task_schedule(self, salt_api_cmd): | ||||
|         self.agent = baker.make_recipe("agents.agent") | ||||
|  | ||||
|         task_name = AutomatedTask.generate_task_name() | ||||
|         # test scheduled task | ||||
|         self.task1 = AutomatedTask.objects.create( | ||||
|             agent=self.agent, | ||||
|             name="test task 1", | ||||
|             win_task_name=task_name, | ||||
|             task_type="scheduled", | ||||
|             run_time_days=[0, 1, 6], | ||||
|             run_time_minute="21:55", | ||||
|         ) | ||||
|         self.assertEqual(self.task1.sync_status, "notsynced") | ||||
|         salt_api_cmd.return_value = True | ||||
|         ret = create_win_task_schedule.s(pk=self.task1.pk, pending_action=False).apply() | ||||
|         self.assertEqual(salt_api_cmd.call_count, 1) | ||||
|         salt_api_cmd.assert_called_with( | ||||
|             timeout=20, | ||||
|             func="task.create_task", | ||||
|             arg=[ | ||||
|                 f"name={task_name}", | ||||
|                 "force=True", | ||||
|                 "action_type=Execute", | ||||
|                 'cmd="C:\\Program Files\\TacticalAgent\\tacticalrmm.exe"', | ||||
|                 f'arguments="-m taskrunner -p {self.task1.pk}"', | ||||
|                 "start_in=C:\\Program Files\\TacticalAgent", | ||||
|                 "trigger_type=Weekly", | ||||
|                 'start_time="21:55"', | ||||
|                 "ac_only=False", | ||||
|                 "stop_if_on_batteries=False", | ||||
|             ], | ||||
|             kwargs={"days_of_week": ["Monday", "Tuesday", "Sunday"]}, | ||||
|         ) | ||||
|         self.task1 = AutomatedTask.objects.get(pk=self.task1.pk) | ||||
|         self.assertEqual(self.task1.sync_status, "synced") | ||||
|  | ||||
|         salt_api_cmd.return_value = "timeout" | ||||
|         ret = create_win_task_schedule.s(pk=self.task1.pk, pending_action=False).apply() | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         self.task1 = AutomatedTask.objects.get(pk=self.task1.pk) | ||||
|         self.assertEqual(self.task1.sync_status, "notsynced") | ||||
|  | ||||
|         salt_api_cmd.return_value = "error" | ||||
|         ret = create_win_task_schedule.s(pk=self.task1.pk, pending_action=False).apply() | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         self.task1 = AutomatedTask.objects.get(pk=self.task1.pk) | ||||
|         self.assertEqual(self.task1.sync_status, "notsynced") | ||||
|  | ||||
|         salt_api_cmd.return_value = False | ||||
|         ret = create_win_task_schedule.s(pk=self.task1.pk, pending_action=False).apply() | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         self.task1 = AutomatedTask.objects.get(pk=self.task1.pk) | ||||
|         self.assertEqual(self.task1.sync_status, "notsynced") | ||||
|  | ||||
|         # test pending action | ||||
|         self.pending_action = PendingAction.objects.create( | ||||
|             agent=self.agent, action_type="taskaction" | ||||
|         ) | ||||
|         self.assertEqual(self.pending_action.status, "pending") | ||||
|         salt_api_cmd.return_value = True | ||||
|         ret = create_win_task_schedule.s( | ||||
|             pk=self.task1.pk, pending_action=self.pending_action.pk | ||||
|         ).apply() | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|         self.pending_action = PendingAction.objects.get(pk=self.pending_action.pk) | ||||
|         self.assertEqual(self.pending_action.status, "completed") | ||||
|  | ||||
|         # test runonce with future date | ||||
|         salt_api_cmd.reset_mock() | ||||
|         task_name = AutomatedTask.generate_task_name() | ||||
|         run_time_date = djangotime.now() + djangotime.timedelta(hours=22) | ||||
|         self.task2 = AutomatedTask.objects.create( | ||||
|             agent=self.agent, | ||||
|             name="test task 2", | ||||
|             win_task_name=task_name, | ||||
|             task_type="runonce", | ||||
|             run_time_date=run_time_date, | ||||
|         ) | ||||
|         salt_api_cmd.return_value = True | ||||
|         ret = create_win_task_schedule.s(pk=self.task2.pk, pending_action=False).apply() | ||||
|         salt_api_cmd.assert_called_with( | ||||
|             timeout=20, | ||||
|             func="task.create_task", | ||||
|             arg=[ | ||||
|                 f"name={task_name}", | ||||
|                 "force=True", | ||||
|                 "action_type=Execute", | ||||
|                 'cmd="C:\\Program Files\\TacticalAgent\\tacticalrmm.exe"', | ||||
|                 f'arguments="-m taskrunner -p {self.task2.pk}"', | ||||
|                 "start_in=C:\\Program Files\\TacticalAgent", | ||||
|                 "trigger_type=Once", | ||||
|                 f'start_date="{run_time_date.strftime("%Y-%m-%d")}"', | ||||
|                 f'start_time="{run_time_date.strftime("%H:%M")}"', | ||||
|                 "ac_only=False", | ||||
|                 "stop_if_on_batteries=False", | ||||
|                 "start_when_available=True", | ||||
|             ], | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|         # test runonce with date in the past | ||||
|         salt_api_cmd.reset_mock() | ||||
|         task_name = AutomatedTask.generate_task_name() | ||||
|         run_time_date = djangotime.now() - djangotime.timedelta(days=13) | ||||
|         self.task3 = AutomatedTask.objects.create( | ||||
|             agent=self.agent, | ||||
|             name="test task 3", | ||||
|             win_task_name=task_name, | ||||
|             task_type="runonce", | ||||
|             run_time_date=run_time_date, | ||||
|         ) | ||||
|         salt_api_cmd.return_value = True | ||||
|         ret = create_win_task_schedule.s(pk=self.task3.pk, pending_action=False).apply() | ||||
|         self.task3 = AutomatedTask.objects.get(pk=self.task3.pk) | ||||
|         salt_api_cmd.assert_called_with( | ||||
|             timeout=20, | ||||
|             func="task.create_task", | ||||
|             arg=[ | ||||
|                 f"name={task_name}", | ||||
|                 "force=True", | ||||
|                 "action_type=Execute", | ||||
|                 'cmd="C:\\Program Files\\TacticalAgent\\tacticalrmm.exe"', | ||||
|                 f'arguments="-m taskrunner -p {self.task3.pk}"', | ||||
|                 "start_in=C:\\Program Files\\TacticalAgent", | ||||
|                 "trigger_type=Once", | ||||
|                 f'start_date="{self.task3.run_time_date.strftime("%Y-%m-%d")}"', | ||||
|                 f'start_time="{self.task3.run_time_date.strftime("%H:%M")}"', | ||||
|                 "ac_only=False", | ||||
|                 "stop_if_on_batteries=False", | ||||
|                 "start_when_available=True", | ||||
|             ], | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|         # test checkfailure | ||||
|         salt_api_cmd.reset_mock() | ||||
|         self.check = baker.make_recipe("checks.diskspace_check", agent=self.agent) | ||||
|         task_name = AutomatedTask.generate_task_name() | ||||
|         self.task4 = AutomatedTask.objects.create( | ||||
|             agent=self.agent, | ||||
|             name="test task 4", | ||||
|             win_task_name=task_name, | ||||
|             task_type="checkfailure", | ||||
|             assigned_check=self.check, | ||||
|         ) | ||||
|         salt_api_cmd.return_value = True | ||||
|         ret = create_win_task_schedule.s(pk=self.task4.pk, pending_action=False).apply() | ||||
|         salt_api_cmd.assert_called_with( | ||||
|             timeout=20, | ||||
|             func="task.create_task", | ||||
|             arg=[ | ||||
|                 f"name={task_name}", | ||||
|                 "force=True", | ||||
|                 "action_type=Execute", | ||||
|                 'cmd="C:\\Program Files\\TacticalAgent\\tacticalrmm.exe"', | ||||
|                 f'arguments="-m taskrunner -p {self.task4.pk}"', | ||||
|                 "start_in=C:\\Program Files\\TacticalAgent", | ||||
|                 "trigger_type=Once", | ||||
|                 'start_date="1975-01-01"', | ||||
|                 'start_time="01:00"', | ||||
|                 "ac_only=False", | ||||
|                 "stop_if_on_batteries=False", | ||||
|             ], | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|  | ||||
|         # test manual | ||||
|         salt_api_cmd.reset_mock() | ||||
|         task_name = AutomatedTask.generate_task_name() | ||||
|         self.task5 = AutomatedTask.objects.create( | ||||
|             agent=self.agent, | ||||
|             name="test task 5", | ||||
|             win_task_name=task_name, | ||||
|             task_type="manual", | ||||
|         ) | ||||
|         salt_api_cmd.return_value = True | ||||
|         ret = create_win_task_schedule.s(pk=self.task5.pk, pending_action=False).apply() | ||||
|         salt_api_cmd.assert_called_with( | ||||
|             timeout=20, | ||||
|             func="task.create_task", | ||||
|             arg=[ | ||||
|                 f"name={task_name}", | ||||
|                 "force=True", | ||||
|                 "action_type=Execute", | ||||
|                 'cmd="C:\\Program Files\\TacticalAgent\\tacticalrmm.exe"', | ||||
|                 f'arguments="-m taskrunner -p {self.task5.pk}"', | ||||
|                 "start_in=C:\\Program Files\\TacticalAgent", | ||||
|                 "trigger_type=Once", | ||||
|                 'start_date="1975-01-01"', | ||||
|                 'start_time="01:00"', | ||||
|                 "ac_only=False", | ||||
|                 "stop_if_on_batteries=False", | ||||
|             ], | ||||
|         ) | ||||
|         self.assertEqual(ret.status, "SUCCESS") | ||||
|   | ||||
| @@ -25,8 +25,9 @@ def core_maintenance_tasks(): | ||||
|  | ||||
|     # cleanup expired runonce tasks | ||||
|     tasks = AutomatedTask.objects.filter( | ||||
|         task_type="runonce", remove_if_not_scheduled=True | ||||
|     ) | ||||
|         task_type="runonce", | ||||
|         remove_if_not_scheduled=True, | ||||
|     ).exclude(last_run=None) | ||||
|  | ||||
|     for task in tasks: | ||||
|         agent_tz = pytz.timezone(task.agent.timezone) | ||||
|   | ||||
| @@ -10,7 +10,7 @@ EXE_DIR = os.path.join(BASE_DIR, "tacticalrmm/private/exe") | ||||
| AUTH_USER_MODEL = "accounts.User" | ||||
|  | ||||
| # latest release | ||||
| TRMM_VERSION = "0.1.0" | ||||
| TRMM_VERSION = "0.1.3" | ||||
|  | ||||
| # bump this version everytime vue code is changed | ||||
| # to alert user they need to manually refresh their browser | ||||
|   | ||||
		Reference in New Issue
	
	Block a user