Skip to content

Commit b36af28

Browse files
committed
feat: add miss target support. Closes #31
1 parent b2376a7 commit b36af28

File tree

2 files changed

+186
-9
lines changed

2 files changed

+186
-9
lines changed

apps/backup2graph/app/backup2graph.py

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def load_objects(o, data):
5959
if o.ClassName not in dict(const.RelationshipToLocal | const.RelationshipFromLocal | const.RelationshipFromGlobal | const.RelationshipToGlobal ):
6060
# Create the Header for the #Class CSV File
6161
data['csv_nodes'][o.ClassName] = []
62-
# Entich the node CSV with a mainstat property for Grafana Visualization
62+
# Enrich the node CSV with a mainstat property for Grafana Visualization
6363
data['csv_nodes'][o.ClassName].append(o.NonEmptyPropertyNames + ['fabric', 'mainstat'])
6464
load_command = 'LOAD CSV FROM "' + csv_folder +'/{}.csv" WITH HEADER AS row CREATE (p:{}) SET p += row'.format(str(o.ClassName),str(o.ClassName))
6565
data['load_nodes'].append(load_command)
@@ -98,14 +98,15 @@ def load_objects(o, data):
9898
data['load_edges'].append(load_command)
9999

100100
data['parent-child-rel'][pc].append([data['fabric_id'],o.Parent.Dn,o.Dn])
101-
101+
102102
def create_non_pc_rel(data):
103103
_, fabric_backup_folder = folder_paths(data['fabric_id'])
104104
csv_folder = os.path.join(fabric_backup_folder, 'csv')
105105
def generate_rel(key,rel_target_class):
106-
106+
107+
# The backup MO properties are not 1:1 to what you have in the API so we need to ALWAYS look at a backup file to be sure how to code the logic.
107108
# Some Rel Classes have a tDn in the backup
108-
# Some put the name of the target object in a property called tn<ClassName>Nameso but the 1st letter is capitalized.
109+
# Some put the name of the target object in a property called tn<ClassName>Nameso but the 1st letter is capitalized. i.e. tnVzBrCPName
109110
npcr = []
110111
rel_target_prop = 'tn' + rel_target_class[0].upper() + rel_target_class[1:] + 'Name'
111112

@@ -145,28 +146,37 @@ def generate_rel(key,rel_target_class):
145146
target_name = getattr(rel, rel_target_prop).replace("'","")
146147
if target_name == '':
147148
target_name = 'default'
149+
edge_direction = '(s)-[r:{} {} ]->(t)'.format(rel.ClassName, ' {target: row[3] }')
148150

149-
edge_direction = '(s)-[r:{}]->(t)'.format(rel.ClassName)
150151

151152
key += rel.Parent.ClassName + "-" + rel_target_class
152153

153154
# Handle the contracts in a special way and override the default file name and relationship direction.
154155
if rel.ClassName == 'fvRsProv':
155156
key += '-fvRsProv'
156-
edge_direction = '(t)-[r:{}]->(s)'.format(rel.ClassName)
157+
edge_direction = '(s)-[r:{} {} ]->(t)'.format(rel.ClassName, ' {target: row[3] }')
157158
elif rel.ClassName == 'fvRsCons':
158159
key += '-fvRsCons'
159160

160161
key += rel.Parent.ClassName + "-" + rel_target_class
161162

162163
if key not in data['non-parent-child-rel'].keys():
163164
data['non-parent-child-rel'][key] = []
164-
load_command = 'LOAD CSV FROM "'+ csv_folder + '/{0}.csv" NO HEADER AS row OPTIONAL MATCH (s1:{1}),(t1:{2}) WHERE s1.dn=row[1] AND t1.name=row[3] AND t1.dn STARTS WITH row[2] AND s1.fabric=row[0] AND t1.fabric=row[0] WITH s1, t1, row OPTIONAL MATCH (s2:{1}),(t2:{2}) WHERE s2.dn=row[1] AND t2.name=row[3] AND t2.dn STARTS WITH "uni/tn-common" AND s2.fabric=row[0] AND t2.fabric=row[0] WITH COALESCE(s1, s2) as s, COALESCE(t1, t2) AS t WHERE s IS NOT NULL AND t IS NOT NULL CREATE {3}'.format(key,rel.Parent.ClassName,rel_target_class,edge_direction)
165+
load_command = 'LOAD CSV FROM "'+ csv_folder + '/{0}.csv" NO HEADER AS row \
166+
MATCH (s:{1}) WHERE s.dn=row[1] AND s.fabric=row[0] \
167+
OPTIONAL MATCH (t1:{2}) WHERE t1.name=row[3] AND t1.dn STARTS WITH row[2] AND t1.fabric=row[0] \
168+
OPTIONAL MATCH (t2:{2}) WHERE t2.name=row[3] AND t2.dn STARTS WITH "uni/tn-common" AND t2.fabric=row[0] \
169+
OPTIONAL MATCH (t3:{2}) WHERE t3.name=row[3] AND t3.dn STARTS WITH "uni/fabric" AND t3.fabric=row[0] \
170+
OPTIONAL MATCH (t4:{2}) WHERE t4.name=row[3] AND t4.dn STARTS WITH "uni/infra" AND t4.fabric=row[0] \
171+
OPTIONAL MATCH (t5:MissingTarget) WHERE t5.fabric=row[0] \
172+
WITH row, s, COALESCE(t1, t2, t3, t4, t5) AS t \
173+
WHERE s IS NOT NULL AND t IS NOT NULL \
174+
CREATE {3}'.format(key,rel.Parent.ClassName,rel_target_class,edge_direction)
165175
data['load_edges'].append(load_command)
166176

167177
npcr = [data['fabric_id'] , rel.Parent.Dn , "/".join(rel.Parent.Dn.split('/')[:2]) , target_name]
168178
data['non-parent-child-rel'][key].append(npcr)
169-
179+
170180
for rel in data['relationships']:
171181

172182
if rel.ClassName in const.RelationshipToLocal.keys():
@@ -193,6 +203,7 @@ def write_to_disk(data,fabric_backup_folder):
193203
f.write(index)
194204

195205
# Write the Nodes LOAD Queries
206+
196207
with open(csv_folder + '/nodes', 'w') as f:
197208
for i in data['load_nodes']:
198209
f.write(i + ";\n")
@@ -231,13 +242,23 @@ def process_backups(backups):
231242
'non-parent-child-rel': {},
232243
'load_nodes': [],
233244
'load_edges': [],
234-
'load_indexes': [],
235245
'csv_nodes': {},
236246
'csv_edges': [],
237247
'fabric_id': ""
238248
}
239249
data['fabric_id']=fabric
240250

251+
#Add a special case for the MissingTarget Objects
252+
data['csv_nodes']["MissingTarget"] = []
253+
# Enrich the node CSV with a mainstat property for Grafana Visualization
254+
data['csv_nodes']["MissingTarget"].append(['fabric', 'mainstat'])
255+
# I really only need 1 MissingTarget node per fabric no other info is needed.
256+
data['csv_nodes']["MissingTarget"].append([data['fabric_id'],data['fabric_id']])
257+
csv_folder = os.path.join(fabric_backup_folder, 'csv')
258+
load_command = 'LOAD CSV FROM "' + csv_folder +'/MissingTarget.csv" WITH HEADER AS row CREATE (p:MissingTarget) SET p += row'
259+
data['load_nodes'].append(load_command)
260+
261+
241262
logger.info("PyACI: Loading Backup in Memory for Fabric %s",fabric)
242263
b = Backup(backup_file, aciMetaFilePath=fabric_meta).load()
243264
logger.info("PyACI: Completed Loading Backup in Memory for Fabric %s",fabric)
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
{
2+
"annotations": {
3+
"list": [
4+
{
5+
"builtIn": 1,
6+
"datasource": {
7+
"type": "grafana",
8+
"uid": "-- Grafana --"
9+
},
10+
"enable": true,
11+
"hide": true,
12+
"iconColor": "rgba(0, 211, 255, 1)",
13+
"name": "Annotations & Alerts",
14+
"type": "dashboard"
15+
}
16+
]
17+
},
18+
"description": "Show a list of missing Targets",
19+
"editable": true,
20+
"fiscalYearStartMonth": 0,
21+
"graphTooltip": 0,
22+
"id": 29,
23+
"links": [],
24+
"panels": [
25+
{
26+
"datasource": {
27+
"type": "kniepdennis-neo4j-datasource",
28+
"uid": "memgraph"
29+
},
30+
"fieldConfig": {
31+
"defaults": {
32+
"color": {
33+
"mode": "thresholds"
34+
},
35+
"custom": {
36+
"align": "auto",
37+
"cellOptions": {
38+
"type": "auto"
39+
},
40+
"inspect": false
41+
},
42+
"mappings": [],
43+
"thresholds": {
44+
"mode": "absolute",
45+
"steps": [
46+
{
47+
"color": "green",
48+
"value": null
49+
},
50+
{
51+
"color": "red",
52+
"value": 80
53+
}
54+
]
55+
}
56+
},
57+
"overrides": [
58+
{
59+
"matcher": {
60+
"id": "byName",
61+
"options": "LeafProfile.name"
62+
},
63+
"properties": [
64+
{
65+
"id": "custom.width",
66+
"value": 220
67+
}
68+
]
69+
}
70+
]
71+
},
72+
"gridPos": {
73+
"h": 17,
74+
"w": 24,
75+
"x": 0,
76+
"y": 0
77+
},
78+
"id": 1,
79+
"options": {
80+
"cellHeight": "sm",
81+
"footer": {
82+
"countRows": false,
83+
"fields": "",
84+
"reducer": [
85+
"sum"
86+
],
87+
"show": false
88+
},
89+
"frameIndex": 1,
90+
"showHeader": true,
91+
"sortBy": [
92+
{
93+
"desc": false,
94+
"displayName": "TargetName"
95+
}
96+
]
97+
},
98+
"pluginVersion": "11.5.1",
99+
"targets": [
100+
{
101+
"Format": "table",
102+
"cypherQuery": "// I am not loading commRsKeyRing class in the DB so I just ignore it here\nMATCH (p:MissingTarget)-[r]-(t) WHERE TYPE(r) != \"commRsKeyRing\" AND p.fabric='$fabric'\nRETURN t.dn AS Parent ,r.target AS TargetName, TYPE(r) AS RelationshipClass",
103+
"datasource": {
104+
"type": "kniepdennis-neo4j-datasource",
105+
"uid": "memgraph"
106+
},
107+
"refId": "A"
108+
}
109+
],
110+
"title": "Missing Targets",
111+
"type": "table"
112+
}
113+
],
114+
"preload": false,
115+
"schemaVersion": 40,
116+
"tags": [
117+
"cisco-aci",
118+
"cisco-aci-config"
119+
],
120+
"templating": {
121+
"list": [
122+
{
123+
"current": {
124+
"text": "fab2",
125+
"value": "fab2"
126+
},
127+
"datasource": {
128+
"type": "prometheus",
129+
"uid": "prometheus"
130+
},
131+
"definition": "label_values(fabric)",
132+
"label": "Fabric",
133+
"name": "fabric",
134+
"options": [],
135+
"query": {
136+
"qryType": 1,
137+
"query": "label_values(fabric)",
138+
"refId": "PrometheusVariableQueryEditor-VariableQuery"
139+
},
140+
"refresh": 1,
141+
"regex": "",
142+
"type": "query"
143+
}
144+
]
145+
},
146+
"time": {
147+
"from": "now-6h",
148+
"to": "now"
149+
},
150+
"timepicker": {},
151+
"timezone": "browser",
152+
"title": "Missing Targets",
153+
"uid": "beezkx0398u80f",
154+
"version": 6,
155+
"weekStart": ""
156+
}

0 commit comments

Comments
 (0)