main
py 316 lines 11.4 KB
Raw
1 from loguru import logger
2 from sqlalchemy.ext.asyncio import AsyncSession
3
4 from app.connectors.influxdb.schema.alerts import AlertStatus
5 from app.connectors.influxdb.schema.alerts import GetInfluxDBAlertQueryParams
6 from app.connectors.influxdb.schema.alerts import InfluxDBAlert
7 from app.connectors.influxdb.schema.alerts import InfluxDBAlertResponse
8 from app.connectors.influxdb.schema.alerts import InfluxDBCheckNamesResponse
9 from app.connectors.influxdb.utils.universal import create_influxdb_client
10 from app.connectors.influxdb.utils.universal import get_influxdb_organization
11 from app.connectors.utils import get_connector_info_from_db
12
13
14 async def get_influxdb_alerts(
15 query_params: GetInfluxDBAlertQueryParams,
16 session: AsyncSession,
17 ) -> InfluxDBAlertResponse:
18 """
19 Retrieve alerts from InfluxDB with advanced filtering
20
21 Args:
22 query_params: Query parameters for filtering
23 session: Database session
24
25 Returns:
26 InfluxDBAlertResponse with filtered alerts
27 """
28 logger.info("Fetching InfluxDB alerts")
29
30 # Get connector info to verify it exists
31 connector_info = await get_connector_info_from_db("InfluxDB", session)
32 if not connector_info:
33 logger.error("InfluxDB connector not found")
34 return InfluxDBAlertResponse(
35 success=False,
36 message="InfluxDB connector not found",
37 alerts=[],
38 total_count=0,
39 filtered_count=0,
40 )
41
42 # Get org and bucket info
43 try:
44 influxdb_org = await get_influxdb_organization()
45 # Use _monitoring bucket
46 influxdb_bucket = "_monitoring"
47 except Exception as e:
48 logger.error(f"Error getting InfluxDB configuration: {e}")
49 influxdb_org = "SOCFORTRESS"
50 influxdb_bucket = "_monitoring"
51
52 # Create InfluxDB client using existing function
53 try:
54 influxdb_client = await create_influxdb_client("InfluxDB")
55 except Exception as e:
56 logger.error(f"Failed to create InfluxDB client: {e}")
57 return InfluxDBAlertResponse(
58 success=False,
59 message=f"Failed to connect to InfluxDB: {str(e)}",
60 alerts=[],
61 total_count=0,
62 filtered_count=0,
63 )
64
65 query_api = influxdb_client.query_api()
66
67 # Build time range - use relative time for better performance
68 days_ago = f"-{query_params.days}d"
69
70 try:
71 # Build Flux query - matching the actual InfluxDB structure
72 flux_query = f"""
73 from(bucket: "{influxdb_bucket}")
74 |> range(start: {days_ago})
75 |> filter(fn: (r) => r._measurement == "statuses" and r._field == "_message")
76 |> filter(fn: (r) => exists r._check_id and exists r._check_name and exists r._level)
77 """
78
79 # Add severity/level filter
80 if query_params.exclude_ok or query_params.severity:
81 severity_filters = []
82
83 if query_params.severity:
84 # Map severity to InfluxDB levels (crit, warn, info, ok)
85 level_mapping = {"critical": "crit", "error": "crit", "warning": "warn", "ok": "ok"}
86 for sev in query_params.severity:
87 level = level_mapping.get(sev.value, sev.value)
88 severity_filters.append(f'r._level == "{level}"')
89 elif query_params.exclude_ok:
90 # Exclude 'ok' level
91 severity_filters = ['r._level == "warn"', 'r._level == "crit"', 'r._level == "info"']
92
93 if severity_filters:
94 severity_filter_str = " or ".join(severity_filters)
95 flux_query += f"\n |> filter(fn: (r) => {severity_filter_str})"
96
97 # Add check name filter
98 if query_params.check_name:
99 flux_query += f"""
100 |> filter(fn: (r) => r._check_name =~ /{query_params.check_name}/)
101 """
102
103 # Add sensor type filter (if applicable)
104 if query_params.sensor_type:
105 flux_query += f"""
106 |> filter(fn: (r) => r._check_name =~ /{query_params.sensor_type}/)
107 """
108
109 # Get only latest per check if requested
110 if query_params.latest_only:
111 flux_query += """
112 |> group(columns: ["_check_name"])
113 |> sort(columns: ["_time"], desc: true)
114 |> limit(n: 1)
115 |> group()
116 """
117 else:
118 flux_query += """
119 |> sort(columns: ["_time"], desc: true)
120 """
121
122 # Add limit to prevent overwhelming results
123 flux_query += f"""
124 |> limit(n: {query_params.limit})
125 """
126
127 logger.info(f"Executing Flux query:\n{flux_query}")
128
129 # Execute query
130 result = await query_api.query(flux_query, org=influxdb_org)
131
132 # Process results
133 alerts = []
134 check_states = {} # Track latest state per check for status calculation
135
136 for table in result:
137 for record in table.records:
138 alert_time = record.get_time()
139 check_name = record.values.get("_check_name", "unknown")
140 check_id = record.values.get("_check_id", "unknown")
141 level = record.values.get("_level", "unknown")
142 message = record.values.get("_value", "No message")
143
144 # Map InfluxDB levels to severity
145 severity_mapping = {"crit": "critical", "warn": "warning", "info": "info", "ok": "ok"}
146 severity = severity_mapping.get(level, level)
147
148 # Track the latest state for this check ID
149 if check_id not in check_states or alert_time > check_states[check_id]["time"]:
150 check_states[check_id] = {
151 "time": alert_time,
152 "level": level,
153 "check_name": check_name,
154 }
155
156 # Status: if level is 'ok', it's cleared; otherwise active
157 status = "cleared" if level == "ok" else "active"
158
159 alerts.append(
160 InfluxDBAlert(
161 time=alert_time,
162 check_name=check_name,
163 sensor_type=check_name.split()[0] if " " in check_name else check_name,
164 severity=severity,
165 message=message,
166 status=status,
167 check_id=str(check_id),
168 ),
169 )
170
171 # Apply status filter if requested
172 if query_params.status != AlertStatus.ALL:
173 if query_params.status == AlertStatus.ACTIVE:
174 # Only keep alerts from checks that are currently NOT in 'ok' state
175 active_check_ids = {check_id for check_id, state in check_states.items() if state["level"] != "ok"}
176
177 logger.info(f"Active check IDs: {active_check_ids}")
178
179 alerts = [alert for alert in alerts if alert.check_id in active_check_ids]
180 elif query_params.status == AlertStatus.CLEARED:
181 # Only keep alerts from checks that are currently in 'ok' state
182 cleared_check_ids = {check_id for check_id, state in check_states.items() if state["level"] == "ok"}
183
184 alerts = [alert for alert in alerts if alert.check_id in cleared_check_ids]
185
186 # Calculate counts
187 total_count = len(alerts)
188 active_count = sum(1 for a in alerts if a.status == "active")
189 cleared_count = sum(1 for a in alerts if a.status == "cleared")
190
191 logger.info(f"Retrieved {len(alerts)} alerts from InfluxDB (active: {active_count}, cleared: {cleared_count})")
192
193 return InfluxDBAlertResponse(
194 success=True,
195 message="Successfully retrieved InfluxDB alerts",
196 alerts=alerts,
197 total_count=total_count,
198 filtered_count=len(alerts),
199 active_alerts_count=active_count,
200 cleared_alerts_count=cleared_count,
201 )
202
203 except Exception as e:
204 logger.error(f"Error querying InfluxDB alerts: {e}")
205 import traceback
206
207 logger.error(f"Traceback: {traceback.format_exc()}")
208 return InfluxDBAlertResponse(
209 success=False,
210 message=f"Error querying InfluxDB: {str(e)}",
211 alerts=[],
212 total_count=0,
213 filtered_count=0,
214 )
215 finally:
216 await influxdb_client.close()
217
218
219 async def get_influxdb_check_names(
220 session: AsyncSession,
221 ) -> InfluxDBCheckNamesResponse:
222 """
223 Retrieve unique check names from InfluxDB
224
225 Args:
226 session: Database session
227
228 Returns:
229 InfluxDBCheckNamesResponse with list of available check names
230 """
231 logger.info("Fetching InfluxDB check names")
232
233 # Get connector info to verify it exists
234 connector_info = await get_connector_info_from_db("InfluxDB", session)
235 if not connector_info:
236 logger.error("InfluxDB connector not found")
237 return InfluxDBCheckNamesResponse(
238 success=False,
239 message="InfluxDB connector not found",
240 check_names=[],
241 total_count=0,
242 )
243
244 # Get org and bucket info
245 try:
246 influxdb_org = await get_influxdb_organization()
247 influxdb_bucket = "_monitoring"
248 except Exception as e:
249 logger.error(f"Error getting InfluxDB configuration: {e}")
250 influxdb_org = "SOCFORTRESS"
251 influxdb_bucket = "_monitoring"
252
253 # Create InfluxDB client
254 try:
255 influxdb_client = await create_influxdb_client("InfluxDB")
256 except Exception as e:
257 logger.error(f"Failed to create InfluxDB client: {e}")
258 return InfluxDBCheckNamesResponse(
259 success=False,
260 message=f"Failed to connect to InfluxDB: {str(e)}",
261 check_names=[],
262 total_count=0,
263 )
264
265 query_api = influxdb_client.query_api()
266
267 try:
268 # Query to get unique check names from the last 30 days
269 flux_query = f"""
270 from(bucket: "{influxdb_bucket}")
271 |> range(start: -30d)
272 |> filter(fn: (r) => r._measurement == "statuses" and r._field == "_message")
273 |> filter(fn: (r) => exists r._check_name)
274 |> group(columns: ["_check_name"])
275 |> distinct(column: "_check_name")
276 |> keep(columns: ["_check_name"])
277 """
278
279 logger.info(f"Executing Flux query:\n{flux_query}")
280
281 # Execute query
282 result = await query_api.query(flux_query, org=influxdb_org)
283
284 # Process results - collect unique check names
285 check_names = set()
286 for table in result:
287 for record in table.records:
288 check_name = record.values.get("_check_name")
289 if check_name:
290 check_names.add(check_name)
291
292 # Convert to sorted list
293 check_names_list = sorted(list(check_names))
294
295 logger.info(f"Retrieved {len(check_names_list)} unique check names")
296
297 return InfluxDBCheckNamesResponse(
298 success=True,
299 message="Successfully retrieved check names",
300 check_names=check_names_list,
301 total_count=len(check_names_list),
302 )
303
304 except Exception as e:
305 logger.error(f"Error querying InfluxDB check names: {e}")
306 import traceback
307
308 logger.error(f"Traceback: {traceback.format_exc()}")
309 return InfluxDBCheckNamesResponse(
310 success=False,
311 message=f"Error querying InfluxDB: {str(e)}",
312 check_names=[],
313 total_count=0,
314 )
315 finally:
316 await influxdb_client.close()