2
2
# All rights reserved
3
3
# Licensed under a 3-clause BSD style license (see LICENSE)
4
4
5
- from six import iteritems
5
+ from six import PY3 , iteritems
6
+
7
+ from datadog_checks .base .utils .serialization import json
8
+
9
+ if PY3 :
10
+ import time
11
+
12
+ from datadog_checks .cisco_aci .models import DeviceMetadata , InterfaceMetadata , NetworkDevicesMetadata , Node , PhysIf
13
+
14
+ else :
15
+ DeviceMetadata = None
16
+ Eth = None
17
+ InterfaceMetadata = None
18
+ Node = None
6
19
7
20
from . import aci_metrics , exceptions , helpers
8
21
22
+ VENDOR_CISCO = 'cisco'
23
+ PAYLOAD_METADATA_BATCH_SIZE = 100
24
+
9
25
10
26
class Fabric :
11
27
"""
12
28
Collect fabric metrics from the APIC
13
29
"""
14
30
15
- def __init__ (self , check , api , instance ):
31
+ def __init__ (self , check , api , instance , namespace ):
16
32
self .check = check
17
33
self .api = api
18
34
self .instance = instance
19
35
self .check_tags = check .check_tags
36
+ self .namespace = namespace
20
37
21
38
# grab some functions from the check
22
39
self .gauge = check .gauge
@@ -25,13 +42,19 @@ def __init__(self, check, api, instance):
25
42
self .submit_metrics = check .submit_metrics
26
43
self .tagger = self .check .tagger
27
44
self .external_host_tags = self .check .external_host_tags
45
+ self .event_platform_event = check .event_platform_event
28
46
29
47
def collect (self ):
30
48
fabric_pods = self .api .get_fabric_pods ()
31
49
fabric_nodes = self .api .get_fabric_nodes ()
32
50
self .log .info ("%s pods and %s nodes computed" , len (fabric_nodes ), len (fabric_pods ))
33
51
pods = self .submit_pod_health (fabric_pods )
34
- self .submit_nodes_health (fabric_nodes , pods )
52
+ devices , interfaces = self .submit_nodes_health_and_metadata (fabric_nodes , pods )
53
+ if PY3 :
54
+ collect_timestamp = int (time .time ())
55
+ batches = self .batch_payloads (devices , interfaces , collect_timestamp )
56
+ for batch in batches :
57
+ self .event_platform_event (json .dumps (batch .model_dump (exclude_none = True )), "network-devices-metadata" )
35
58
36
59
def submit_pod_health (self , pods ):
37
60
pods_dict = {}
@@ -53,7 +76,9 @@ def submit_pod_health(self, pods):
53
76
54
77
return pods_dict
55
78
56
- def submit_nodes_health (self , nodes , pods ):
79
+ def submit_nodes_health_and_metadata (self , nodes , pods ):
80
+ device_metadata = []
81
+ interface_metadata = []
57
82
for n in nodes :
58
83
hostname = helpers .get_fabric_hostname (n )
59
84
@@ -70,17 +95,22 @@ def submit_nodes_health(self, nodes, pods):
70
95
continue
71
96
self .log .info ("processing node %s on pod %s" , node_id , pod_id )
72
97
try :
98
+ if PY3 :
99
+ device_metadata .append (self .submit_node_metadata (node_attrs , tags ))
73
100
self .submit_process_metric (n , tags + self .check_tags + user_tags , hostname = hostname )
74
101
except (exceptions .APIConnectionException , exceptions .APIParsingException ):
75
102
pass
76
103
if node_attrs .get ('role' ) != "controller" :
77
104
try :
78
105
stats = self .api .get_node_stats (pod_id , node_id )
79
106
self .submit_fabric_metric (stats , tags , 'fabricNode' , hostname = hostname )
80
- self .process_eth (node_attrs )
107
+ eth_metadata = self .process_eth (node_attrs )
108
+ if PY3 :
109
+ interface_metadata .extend (eth_metadata )
81
110
except (exceptions .APIConnectionException , exceptions .APIParsingException ):
82
111
pass
83
112
self .log .info ("finished processing node %s" , node_id )
113
+ return device_metadata , interface_metadata
84
114
85
115
def process_eth (self , node ):
86
116
self .log .info ("processing ethernet ports for %s" , node .get ('id' ))
@@ -90,16 +120,20 @@ def process_eth(self, node):
90
120
eth_list = self .api .get_eth_list (pod_id , node ['id' ])
91
121
except (exceptions .APIConnectionException , exceptions .APIParsingException ):
92
122
pass
123
+ interfaces = []
93
124
for e in eth_list :
94
125
eth_attrs = helpers .get_attributes (e )
95
126
eth_id = eth_attrs ['id' ]
96
127
tags = self .tagger .get_fabric_tags (e , 'l1PhysIf' )
128
+ if PY3 :
129
+ interfaces .append (self .create_interface_metadata (e , node ['address' ], tags , hostname ))
97
130
try :
98
131
stats = self .api .get_eth_stats (pod_id , node ['id' ], eth_id )
99
132
self .submit_fabric_metric (stats , tags , 'l1PhysIf' , hostname = hostname )
100
133
except (exceptions .APIConnectionException , exceptions .APIParsingException ):
101
134
pass
102
135
self .log .info ("finished processing ethernet ports for %s" , node ['id' ])
136
+ return interfaces
103
137
104
138
def submit_fabric_metric (self , stats , tags , obj_type , hostname = None ):
105
139
for s in stats :
@@ -209,3 +243,70 @@ def get_fabric_type(self, obj_type):
209
243
return 'pod'
210
244
if obj_type == 'l1PhysIf' :
211
245
return 'port'
246
+
247
+ def batch_payloads (self , devices , interfaces , collect_ts ):
248
+ for device in devices :
249
+ yield NetworkDevicesMetadata (namespace = self .namespace , devices = [device ], collect_timestamp = collect_ts )
250
+
251
+ payloads = []
252
+ for interface in interfaces :
253
+ if len (payloads ) == PAYLOAD_METADATA_BATCH_SIZE :
254
+ yield NetworkDevicesMetadata (
255
+ namespace = self .namespace , interfaces = payloads , collect_timestamp = collect_ts
256
+ )
257
+ payloads = []
258
+ payloads .append (interface )
259
+ if payloads :
260
+ yield NetworkDevicesMetadata (namespace = self .namespace , interfaces = payloads , collect_timestamp = collect_ts )
261
+
262
+ def submit_node_metadata (self , node_attrs , tags ):
263
+ node = Node (attributes = node_attrs )
264
+ id_tags = ['namespace:{}' .format (self .namespace )]
265
+ device_tags = [
266
+ 'device_vendor:{}' .format (VENDOR_CISCO ),
267
+ 'device_namespace:{}' .format (self .namespace ),
268
+ 'device_hostname:{}' .format (node .attributes .dn ),
269
+ 'hostname:{}' .format (node .attributes .dn ),
270
+ 'device_ip:{}' .format (node .attributes .address ),
271
+ 'device_id:{}:{}' .format (self .namespace , node .attributes .address ),
272
+ "source:cisco-aci" ,
273
+ ]
274
+ device = DeviceMetadata (
275
+ id = '{}:{}' .format (self .namespace , node .attributes .address ),
276
+ id_tags = id_tags ,
277
+ tags = device_tags + tags ,
278
+ name = node .attributes .dn ,
279
+ ip_address = node .attributes .address ,
280
+ model = node .attributes .model ,
281
+ fabric_st = node .attributes .fabric_st ,
282
+ vendor = VENDOR_CISCO ,
283
+ version = node .attributes .version ,
284
+ serial_number = node .attributes .serial ,
285
+ device_type = node .attributes .device_type ,
286
+ )
287
+ return device .model_dump (exclude_none = True )
288
+
289
+ def create_interface_metadata (self , phys_if , address , tags , hostname ):
290
+ eth = PhysIf (** phys_if .get ('l1PhysIf' , {}))
291
+ interface = InterfaceMetadata (
292
+ device_id = '{}:{}' .format (self .namespace , address ),
293
+ id_tags = ['interface:{}' .format (eth .attributes .name )],
294
+ index = eth .attributes .id ,
295
+ name = eth .attributes .name ,
296
+ description = eth .attributes .desc ,
297
+ mac_address = eth .attributes .router_mac ,
298
+ admin_status = eth .attributes .admin_st ,
299
+ )
300
+ if eth .ethpm_phys_if :
301
+ interface .oper_status = eth .ethpm_phys_if .attributes .oper_st
302
+ if interface .status :
303
+ new_tags = tags .copy ()
304
+ new_tags .extend (
305
+ [
306
+ "device_ip:{}" .format (address ),
307
+ "device_namespace:{}" .format (self .namespace ),
308
+ "interface.status:{}" .format (interface .status ),
309
+ ]
310
+ )
311
+ self .gauge ('cisco_aci.fabric.node.interface.status' , 1 , tags = new_tags , hostname = hostname )
312
+ return interface .model_dump (exclude_none = True )
0 commit comments