11# (C) Datadog, Inc. 2025-present
22# All rights reserved
33# Licensed under a 3-clause BSD style license (see LICENSE)
4+
45from requests .exceptions import ConnectionError , HTTPError , InvalidURL , JSONDecodeError , Timeout
56
67from datadog_checks .base import AgentCheck
8+ from datadog_checks .base .utils .time import get_current_datetime , get_timestamp
79from datadog_checks .proxmox .config_models import ConfigMixin
810
911from .constants import (
12+ EVENT_TYPE_TO_TITLE ,
1013 NODE_RESOURCE ,
1114 OK_STATUS ,
1215 PERF_METRIC_NAME ,
1720)
1821
1922
23+ def resource_type_for_event_type (event_type ):
24+ if event_type .startswith ('vz' ):
25+ return 'lxc'
26+ elif event_type .startswith ('qm' ) or event_type .startswith ('vnc' ):
27+ return 'qemu'
28+ return 'node'
29+
30+
2031class ProxmoxCheck (AgentCheck , ConfigMixin ):
2132 __NAMESPACE__ = 'proxmox'
2233
2334 def __init__ (self , name , init_config , instances ):
2435 super (ProxmoxCheck , self ).__init__ (name , init_config , instances )
25- self .check_initializations .append (self ._parse_config )
2636 self .all_resources = {}
37+ self .last_event_collect_time = get_current_datetime ()
38+ self .check_initializations .append (self ._parse_config )
2739
2840 def _parse_config (self ):
2941 self .base_tags = [f"proxmox_server:{ self .config .proxmox_server } " ]
@@ -54,6 +66,49 @@ def _get_vm_hostname(self, vm_id, vm_name, node):
5466 hostname = hostname_json .get ("data" , {}).get ("result" , {}).get ("host-name" , vm_name )
5567 return hostname
5668
69+ def _create_dd_event_for_task (self , task , node_name ):
70+ task_type = task .get ('type' )
71+ status = "success" if task .get ("status" ) == "OK" else "error"
72+ id = task .get ('id' ) if task .get ('id' ) else node_name
73+ user = task .get ('user' )
74+ event_title = EVENT_TYPE_TO_TITLE .get (task_type , task_type )
75+ resource_type = resource_type_for_event_type (task_type )
76+ resource_id = f'{ resource_type } /{ id } '
77+ self .log .debug (
78+ "Creating event for task type: %s ID: %s, resource id %s on node %s" ,
79+ task_type ,
80+ id ,
81+ resource_id ,
82+ node_name ,
83+ )
84+
85+ resource = self .all_resources .get (resource_id , {})
86+
87+ tags = list (resource .get ('tags' , []))
88+ tags .append (f'proxmox_event_type:{ task_type } ' )
89+ tags .append (f'proxmox_user:{ user } ' )
90+
91+ timestamp = task .get ('endtime' , get_timestamp (get_current_datetime ()))
92+ hostname = resource .get ('hostname' , None )
93+
94+ if resource_type != 'node' :
95+ resource_type_format = resource .get ('resource_type' , 'node' ).capitalize ()
96+ event_message = f"{ resource_type_format } { resource .get ('resource_name' )} : { event_title } on node { node_name } "
97+ else :
98+ event_message = f"{ event_title } on node { node_name } "
99+
100+ event = {
101+ 'timestamp' : timestamp ,
102+ 'event_type' : self .__NAMESPACE__ ,
103+ 'host' : hostname ,
104+ 'msg_text' : event_message ,
105+ 'msg_title' : event_title ,
106+ 'alert_type' : status ,
107+ 'source_type_name' : self .__NAMESPACE__ ,
108+ 'tags' : tags ,
109+ }
110+ return event
111+
57112 def _collect_ha_metrics (self ):
58113 ha_response = self .http .get (f"{ self .config .proxmox_server } /cluster/ha/status/current" )
59114 ha_response_json = ha_response .json ()
@@ -155,7 +210,12 @@ def _collect_resource_metrics(self):
155210 else :
156211 external_tags .append ((hostname , {self .__NAMESPACE__ : self .base_tags + list (resource_tags )}))
157212
158- all_resources [resource_id ] = {'resource_type' : resource_type_remapped , 'tags' : tags , 'hostname' : hostname }
213+ all_resources [resource_id ] = {
214+ 'resource_type' : resource_type_remapped ,
215+ 'resource_name' : resource_name ,
216+ 'tags' : tags ,
217+ 'hostname' : hostname ,
218+ }
159219
160220 if resource_type_remapped != "pool" :
161221 # pools don't have a status attribute
@@ -170,6 +230,33 @@ def _collect_resource_metrics(self):
170230 self .all_resources = all_resources
171231 self .set_external_tags (external_tags )
172232
233+ def _collect_tasks (self ):
234+ for resource in self .all_resources .values ():
235+ if resource .get ('resource_type' ) != 'node' :
236+ continue
237+
238+ node_name = resource .get ('hostname' )
239+ since = int (get_timestamp (self .last_event_collect_time ))
240+ self .log .debug ("Collecting events for node %s since %s" , node_name , since )
241+
242+ now = get_current_datetime ()
243+ params = {'since' : since }
244+ response = self .http .get (f"{ self .config .proxmox_server } /nodes/{ node_name } /tasks" , params = params )
245+ response .raise_for_status ()
246+
247+ response_json = response .json ().get ("data" , [])
248+ self .last_event_collect_time = now
249+
250+ for task in response_json :
251+ task_type = task .get ('type' )
252+
253+ if task_type not in self .config .collected_task_types :
254+ continue
255+
256+ event = self ._create_dd_event_for_task (task , node_name )
257+ self .log .debug ("Submitting event %s" , event )
258+ self .event (event )
259+
173260 def check (self , _ ):
174261 try :
175262 response = self .http .get (f"{ self .config .proxmox_server } /version" )
@@ -190,3 +277,5 @@ def check(self, _):
190277 self ._collect_resource_metrics ()
191278 self ._collect_performance_metrics ()
192279 self ._collect_ha_metrics ()
280+ if self .config .collect_tasks :
281+ self ._collect_tasks ()
0 commit comments