Skip to content

Commit

Permalink
Release 0.4.2
Browse files Browse the repository at this point in the history
  • Loading branch information
wh1te909 committed Jan 29, 2021
2 parents bff0527 + e17d25c commit b5c28de
Show file tree
Hide file tree
Showing 17 changed files with 119 additions and 195 deletions.
6 changes: 6 additions & 0 deletions api/tacticalrmm/agents/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ class Meta:
]


class AgentOverdueActionSerializer(serializers.ModelSerializer):
class Meta:
model = Agent
fields = ["pk", "overdue_email_alert", "overdue_text_alert"]


class AgentTableSerializer(serializers.ModelSerializer):
patches_pending = serializers.ReadOnlyField(source="has_patches_pending")
pending_actions = serializers.SerializerMethodField()
Expand Down
8 changes: 7 additions & 1 deletion api/tacticalrmm/agents/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@
def _check_agent_service(pk: int) -> None:
agent = Agent.objects.get(pk=pk)
r = asyncio.run(agent.nats_cmd({"func": "ping"}, timeout=2))
# if the agent is respoding to pong from the rpc service but is not showing as online (handled by tacticalagent service)
# then tacticalagent service is hung. forcefully restart it
if r == "pong":
logger.info(
f"Detected crashed tacticalagent service on {agent.hostname}, attempting recovery"
f"Detected crashed tacticalagent service on {agent.hostname} v{agent.version}, attempting recovery"
)
data = {"func": "recover", "payload": {"mode": "tacagent"}}
asyncio.run(agent.nats_cmd(data, wait=False))
Expand Down Expand Up @@ -109,6 +111,10 @@ def agent_update(pk: int) -> str:
asyncio.run(agent.nats_cmd(nats_data, wait=False))

return "created"
else:
logger.warning(
f"{agent.hostname} v{agent.version} is running an unsupported version. Refusing to update."
)

return "not supported"

Expand Down
26 changes: 2 additions & 24 deletions api/tacticalrmm/agents/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -479,42 +479,20 @@ def test_by_site(self):
def test_overdue_action(self):
url = "/agents/overdueaction/"

payload = {"pk": self.agent.pk, "alertType": "email", "action": "enabled"}
payload = {"pk": self.agent.pk, "overdue_email_alert": True}
r = self.client.post(url, payload, format="json")
self.assertEqual(r.status_code, 200)
agent = Agent.objects.get(pk=self.agent.pk)
self.assertTrue(agent.overdue_email_alert)
self.assertEqual(self.agent.hostname, r.data)

payload.update({"alertType": "email", "action": "disabled"})
r = self.client.post(url, payload, format="json")
self.assertEqual(r.status_code, 200)
agent = Agent.objects.get(pk=self.agent.pk)
self.assertFalse(agent.overdue_email_alert)
self.assertEqual(self.agent.hostname, r.data)

payload.update({"alertType": "text", "action": "enabled"})
r = self.client.post(url, payload, format="json")
self.assertEqual(r.status_code, 200)
agent = Agent.objects.get(pk=self.agent.pk)
self.assertTrue(agent.overdue_text_alert)
self.assertEqual(self.agent.hostname, r.data)

payload.update({"alertType": "text", "action": "disabled"})
payload = {"pk": self.agent.pk, "overdue_text_alert": False}
r = self.client.post(url, payload, format="json")
self.assertEqual(r.status_code, 200)
agent = Agent.objects.get(pk=self.agent.pk)
self.assertFalse(agent.overdue_text_alert)
self.assertEqual(self.agent.hostname, r.data)

payload.update({"alertType": "email", "action": "523423"})
r = self.client.post(url, payload, format="json")
self.assertEqual(r.status_code, 400)

payload.update({"alertType": "text", "action": "asdasd3434asdasd"})
r = self.client.post(url, payload, format="json")
self.assertEqual(r.status_code, 400)

self.check_not_authenticated("post", url)

def test_list_agents_no_detail(self):
Expand Down
29 changes: 8 additions & 21 deletions api/tacticalrmm/agents/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
AgentEditSerializer,
NoteSerializer,
NotesSerializer,
AgentOverdueActionSerializer,
)
from winupdate.serializers import WinUpdatePolicySerializer

Expand Down Expand Up @@ -333,26 +334,12 @@ def by_site(request, sitepk):

@api_view(["POST"])
def overdue_action(request):
pk = request.data["pk"]
alert_type = request.data["alertType"]
action = request.data["action"]
agent = get_object_or_404(Agent, pk=pk)
if alert_type == "email" and action == "enabled":
agent.overdue_email_alert = True
agent.save(update_fields=["overdue_email_alert"])
elif alert_type == "email" and action == "disabled":
agent.overdue_email_alert = False
agent.save(update_fields=["overdue_email_alert"])
elif alert_type == "text" and action == "enabled":
agent.overdue_text_alert = True
agent.save(update_fields=["overdue_text_alert"])
elif alert_type == "text" and action == "disabled":
agent.overdue_text_alert = False
agent.save(update_fields=["overdue_text_alert"])
else:
return Response(
{"error": "Something went wrong"}, status=status.HTTP_400_BAD_REQUEST
)
agent = get_object_or_404(Agent, pk=request.data["pk"])
serializer = AgentOverdueActionSerializer(
instance=agent, data=request.data, partial=True
)
serializer.is_valid(raise_exception=True)
serializer.save()
return Response(agent.hostname)


Expand Down Expand Up @@ -473,7 +460,7 @@ def install_agent(request):
f"GOARCH={goarch}",
go_bin,
"build",
f"-ldflags=\"-X 'main.Inno={inno}'",
f"-ldflags=\"-s -w -X 'main.Inno={inno}'",
f"-X 'main.Api={api}'",
f"-X 'main.Client={client_id}'",
f"-X 'main.Site={site_id}'",
Expand Down
6 changes: 4 additions & 2 deletions api/tacticalrmm/apiv3/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from accounts.models import User
from winupdate.models import WinUpdatePolicy
from software.models import InstalledSoftware
from checks.serializers import CheckRunnerGetSerializerV3
from checks.serializers import CheckRunnerGetSerializer
from agents.serializers import WinAgentSerializer
from autotasks.serializers import TaskGOGetSerializer, TaskRunnerPatchSerializer
from winupdate.serializers import ApprovedUpdateSerializer
Expand Down Expand Up @@ -232,7 +232,7 @@ def get(self, request, agentid):
ret = {
"agent": agent.pk,
"check_interval": agent.check_interval,
"checks": CheckRunnerGetSerializerV3(checks, many=True).data,
"checks": CheckRunnerGetSerializer(checks, many=True).data,
}
return Response(ret)

Expand Down Expand Up @@ -332,6 +332,7 @@ def patch(self, request):
update.installed = True
update.save(update_fields=["result", "downloaded", "installed"])

agent.delete_superseded_updates()
return Response("ok")

# agent calls this after it's finished installing all patches
Expand All @@ -357,6 +358,7 @@ def post(self, request):
f"{agent.hostname} is rebooting after updates were installed."
)

agent.delete_superseded_updates()
return Response("ok")


Expand Down
36 changes: 0 additions & 36 deletions api/tacticalrmm/checks/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,42 +445,6 @@ def handle_checkv2(self, data):

return self.status

def handle_check(self, data):
if self.check_type != "cpuload" and self.check_type != "memory":

if data["status"] == "passing" and self.fail_count != 0:
self.fail_count = 0
self.save(update_fields=["fail_count"])

elif data["status"] == "failing":
self.fail_count += 1
self.save(update_fields=["fail_count"])

else:
self.history.append(data["percent"])

if len(self.history) > 15:
self.history = self.history[-15:]

self.save(update_fields=["history"])

avg = int(mean(self.history))

if avg > self.threshold:
self.status = "failing"
self.fail_count += 1
self.save(update_fields=["status", "fail_count"])
else:
self.status = "passing"
if self.fail_count != 0:
self.fail_count = 0
self.save(update_fields=["status", "fail_count"])
else:
self.save(update_fields=["status"])

if self.email_alert and self.fail_count >= self.fails_b4_alert:
handle_check_email_alert_task.delay(self.pk)

@staticmethod
def serialize(check):
# serializes the check and returns json
Expand Down
94 changes: 0 additions & 94 deletions api/tacticalrmm/checks/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,101 +95,7 @@ class Meta:


class CheckRunnerGetSerializer(serializers.ModelSerializer):
# for the windows agent
# only send data needed for agent to run a check

assigned_task = serializers.SerializerMethodField()
script = ScriptSerializer(read_only=True)

def get_assigned_task(self, obj):
if obj.assignedtask.exists():
# this will not break agents on version 0.10.2 or lower
# newer agents once released will properly handle multiple tasks assigned to a check
task = obj.assignedtask.first()
return AssignedTaskCheckRunnerField(task).data

class Meta:
model = Check
exclude = [
"policy",
"managed_by_policy",
"overriden_by_policy",
"parent_check",
"name",
"more_info",
"last_run",
"email_alert",
"text_alert",
"fails_b4_alert",
"fail_count",
"email_sent",
"text_sent",
"outage_history",
"extra_details",
"stdout",
"stderr",
"retcode",
"execution_time",
"svc_display_name",
"svc_policy_mode",
"created_by",
"created_time",
"modified_by",
"modified_time",
"history",
]


class CheckRunnerGetSerializerV2(serializers.ModelSerializer):
# for the windows __python__ agent
# only send data needed for agent to run a check

assigned_tasks = serializers.SerializerMethodField()
script = ScriptSerializer(read_only=True)

def get_assigned_tasks(self, obj):
if obj.assignedtask.exists():
tasks = obj.assignedtask.all()
return AssignedTaskCheckRunnerField(tasks, many=True).data

class Meta:
model = Check
exclude = [
"policy",
"managed_by_policy",
"overriden_by_policy",
"parent_check",
"name",
"more_info",
"last_run",
"email_alert",
"text_alert",
"fails_b4_alert",
"fail_count",
"email_sent",
"text_sent",
"outage_history",
"extra_details",
"stdout",
"stderr",
"retcode",
"execution_time",
"svc_display_name",
"svc_policy_mode",
"created_by",
"created_time",
"modified_by",
"modified_time",
"history",
]


class CheckRunnerGetSerializerV3(serializers.ModelSerializer):
# for the windows __golang__ agent
# only send data needed for agent to run a check
# the difference here is in the script serializer
# script checks no longer rely on salt and are executed directly by the go agent

assigned_tasks = serializers.SerializerMethodField()
script = ScriptCheckSerializer(read_only=True)

Expand Down
44 changes: 43 additions & 1 deletion api/tacticalrmm/checks/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
from tacticalrmm.test import TacticalTestCase
from .serializers import CheckSerializer
from django.utils import timezone as djangotime
from unittest.mock import patch

from model_bakery import baker
from itertools import cycle


class TestCheckViews(TacticalTestCase):
Expand Down Expand Up @@ -184,6 +184,48 @@ def test_edit_check_alert(self):

self.check_not_authenticated("patch", url_a)

@patch("agents.models.Agent.nats_cmd")
def test_run_checks(self, nats_cmd):
agent = baker.make_recipe("agents.agent", version="1.4.1")
agent_old = baker.make_recipe("agents.agent", version="1.0.2")
agent_b4_141 = baker.make_recipe("agents.agent", version="1.4.0")

url = f"/checks/runchecks/{agent_old.pk}/"
r = self.client.get(url)
self.assertEqual(r.status_code, 400)
self.assertEqual(r.json(), "Requires agent version 1.1.0 or greater")

url = f"/checks/runchecks/{agent_b4_141.pk}/"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
nats_cmd.assert_called_with({"func": "runchecks"}, wait=False)

nats_cmd.reset_mock()
nats_cmd.return_value = "busy"
url = f"/checks/runchecks/{agent.pk}/"
r = self.client.get(url)
self.assertEqual(r.status_code, 400)
nats_cmd.assert_called_with({"func": "runchecks"}, timeout=15)
self.assertEqual(r.json(), f"Checks are already running on {agent.hostname}")

nats_cmd.reset_mock()
nats_cmd.return_value = "ok"
url = f"/checks/runchecks/{agent.pk}/"
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
nats_cmd.assert_called_with({"func": "runchecks"}, timeout=15)
self.assertEqual(r.json(), f"Checks will now be re-run on {agent.hostname}")

nats_cmd.reset_mock()
nats_cmd.return_value = "timeout"
url = f"/checks/runchecks/{agent.pk}/"
r = self.client.get(url)
self.assertEqual(r.status_code, 400)
nats_cmd.assert_called_with({"func": "runchecks"}, timeout=15)
self.assertEqual(r.json(), "Unable to contact the agent")

self.check_not_authenticated("get", url)

def test_get_check_history(self):
# setup data
agent = baker.make_recipe("agents.agent")
Expand Down
14 changes: 12 additions & 2 deletions api/tacticalrmm/checks/views.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
from packaging import version as pyver

from django.shortcuts import get_object_or_404
from django.db.models import Q
Expand Down Expand Up @@ -168,8 +169,17 @@ def run_checks(request, pk):
if not agent.has_nats:
return notify_error("Requires agent version 1.1.0 or greater")

asyncio.run(agent.nats_cmd({"func": "runchecks"}, wait=False))
return Response(agent.hostname)
if pyver.parse(agent.version) >= pyver.parse("1.4.1"):
r = asyncio.run(agent.nats_cmd({"func": "runchecks"}, timeout=15))
if r == "busy":
return notify_error(f"Checks are already running on {agent.hostname}")
elif r == "ok":
return Response(f"Checks will now be re-run on {agent.hostname}")
else:
return notify_error("Unable to contact the agent")
else:
asyncio.run(agent.nats_cmd({"func": "runchecks"}, wait=False))
return Response(f"Checks will now be re-run on {agent.hostname}")


@api_view()
Expand Down
Loading

0 comments on commit b5c28de

Please sign in to comment.