Skip to content

Commit acd46b5

Browse files
[AI-5554] Add support for submitting events for proxmox tasks (#20849)
* Add support for tasks as events * Add changelog * fix * test * fix timezon * Add event categories * address comments * Address comments * Update proxmox/tests/test_unit.py Co-authored-by: NouemanKHAL <noueman.khalikine@datadoghq.com> * Address * Update proxmox/tests/test_unit.py Co-authored-by: NouemanKHAL <noueman.khalikine@datadoghq.com> --------- Co-authored-by: NouemanKHAL <noueman.khalikine@datadoghq.com>
1 parent f0340f3 commit acd46b5

File tree

12 files changed

+772
-5
lines changed

12 files changed

+772
-5
lines changed

proxmox/assets/configuration/spec.yaml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,39 @@ files:
1616
example: http://localhost:8006/api2/json
1717
type: string
1818
required: true
19+
- name: collect_tasks
20+
display_priority: 5
21+
description: |
22+
Whether or not to collect Proxmox tasks as events.
23+
value:
24+
type: boolean
25+
example: true
26+
display_default: false
27+
required: false
28+
- name: collected_task_types
29+
display_priority: 5
30+
description: |
31+
Which Proxmox task types to collect. When `collect_tasks` is enabled, the integration will collect
32+
the following task types by default:
33+
- qmstart
34+
- qhstop
35+
- qmshutdown
36+
- qmreboot
37+
- qmigrate
38+
- qmsuspend
39+
- vzstart
40+
- vzshutdown
41+
- vzsuspend
42+
- startall
43+
- stopall
44+
- suspendall
45+
- aptupdate
46+
value:
47+
type: array
48+
items:
49+
type: string
50+
default: ['qmstart', 'qmstop', 'qmshutdown', 'qmreboot', 'qmigrate', 'qmsuspend', 'vzstart', 'vzshutdown', 'vzsuspend', 'startall', 'stopall', 'suspendall', 'aptupdate']
51+
required: false
1952
- template: instances/default
2053
overrides:
2154
empty_default_hostname.display_priority: 1

proxmox/changelog.d/20849.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for sending tasks as events.

proxmox/datadog_checks/proxmox/check.py

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
# (C) Datadog, Inc. 2025-present
22
# All rights reserved
33
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
45
from requests.exceptions import ConnectionError, HTTPError, InvalidURL, JSONDecodeError, Timeout
56

67
from datadog_checks.base import AgentCheck
8+
from datadog_checks.base.utils.time import get_current_datetime, get_timestamp
79
from datadog_checks.proxmox.config_models import ConfigMixin
810

911
from .constants import (
12+
EVENT_TYPE_TO_TITLE,
1013
NODE_RESOURCE,
1114
OK_STATUS,
1215
PERF_METRIC_NAME,
@@ -17,13 +20,22 @@
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+
2031
class 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()

proxmox/datadog_checks/proxmox/config_models/defaults.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,28 @@ def instance_auth_type():
2424
return 'basic'
2525

2626

27+
def instance_collect_tasks():
28+
return False
29+
30+
31+
def instance_collected_task_types():
32+
return [
33+
'qmstart',
34+
'qmstop',
35+
'qmshutdown',
36+
'qmreboot',
37+
'qmigrate',
38+
'qmsuspend',
39+
'vzstart',
40+
'vzshutdown',
41+
'vzsuspend',
42+
'startall',
43+
'stopall',
44+
'suspendall',
45+
'aptupdate',
46+
]
47+
48+
2749
def instance_disable_generic_tags():
2850
return False
2951

proxmox/datadog_checks/proxmox/config_models/instance.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ class InstanceConfig(BaseModel):
6060
aws_host: Optional[str] = None
6161
aws_region: Optional[str] = None
6262
aws_service: Optional[str] = None
63+
collect_tasks: Optional[bool] = None
64+
collected_task_types: Optional[tuple[str, ...]] = None
6365
connect_timeout: Optional[float] = None
6466
disable_generic_tags: Optional[bool] = None
6567
empty_default_hostname: Optional[bool] = None

proxmox/datadog_checks/proxmox/constants.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,20 @@
4646
}
4747

4848
RESOURCE_COUNT_METRICS = ['uptime']
49+
50+
EVENT_TYPE_TO_TITLE = {
51+
'vzstart': 'Container Started',
52+
'vzshutdown': 'Container Shutdown',
53+
'vzsuspend': 'Container Suspened',
54+
'qmstart': 'VM Started',
55+
'qhstop': 'VM Stopped',
56+
'qmshutdown': 'VM Shutdown',
57+
'qmreboot': 'VM Rebooted',
58+
'qmigrate': 'VM Migrated',
59+
'qmsuspend': 'VM Hibernated',
60+
'startall': 'Bulk start VMs and Containers',
61+
'stopall': 'Bulk stop VMs and Containers',
62+
'suspendall': 'Bulk suspend VMs and Containers',
63+
'aptupdate': 'Update package database',
64+
'vncproxy': 'Console started',
65+
}

proxmox/datadog_checks/proxmox/data/conf.yaml.example

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,30 @@ instances:
5050
#
5151
- proxmox_server: http://localhost:8006/api2/json
5252

53+
## @param collect_tasks - boolean - optional - default: false
54+
## Whether or not to collect Proxmox tasks as events.
55+
#
56+
# collect_tasks: true
57+
58+
## @param collected_task_types - list of strings - optional
59+
## Which Proxmox task types to collect. When `collect_tasks` is enabled, the integration will collect
60+
## the following task types by default:
61+
## - qmstart
62+
## - qhstop
63+
## - qmshutdown
64+
## - qmreboot
65+
## - qmigrate
66+
## - qmsuspend
67+
## - vzstart
68+
## - vzshutdown
69+
## - vzsuspend
70+
## - startall
71+
## - stopall
72+
## - suspendall
73+
## - aptupdate
74+
#
75+
# collected_task_types: []
76+
5377
## @param headers - mapping - optional
5478
## Headers to use for every request. An Authorization header including the Proxmox API token is required
5579
## for authentication for the REST API.

proxmox/manifest.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
"Category::OS & System",
1919
"Category::Cloud",
2020
"Category::Network",
21+
"Category::Event Management",
2122
"Category::Metrics",
2223
"Category::Log Collection",
2324
"Offering::Integration",
2425
"Submitted Data Type::Metrics",
25-
"Submitted Data Type::Logs"
26+
"Submitted Data Type::Logs",
27+
"Submitted Data Type::Events"
2628
]
2729
},
2830
"assets": {

0 commit comments

Comments
 (0)