From 08dd2c0aed8fac833779d74b0a8e80bcb6e7d491 Mon Sep 17 00:00:00 2001 From: Sunami Dasgupta Date: Sun, 20 Oct 2024 21:53:03 -0700 Subject: [PATCH 01/13] Diagram class related to #127 --- autogole-api/packaging/Dockerfile | 8 +- autogole-api/packaging/icons/host.png | Bin 0 -> 15239 bytes autogole-api/packaging/icons/switch.png | Bin 0 -> 2532 bytes autogole-api/requirements.txt | 1 + autogole-api/src/python/RTMon/worker.py | 6 + .../src/python/RTMonLibs/DiagramWorker.py | 146 ++++++++++++++++++ autogole-api/src/python/RTMonLibs/Template.py | 8 + 7 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 autogole-api/packaging/icons/host.png create mode 100644 autogole-api/packaging/icons/switch.png create mode 100644 autogole-api/src/python/RTMonLibs/DiagramWorker.py diff --git a/autogole-api/packaging/Dockerfile b/autogole-api/packaging/Dockerfile index 7997dc1..bf2534b 100644 --- a/autogole-api/packaging/Dockerfile +++ b/autogole-api/packaging/Dockerfile @@ -1,11 +1,12 @@ FROM opensciencegrid/software-base:23-al8-release RUN yum -y install wget epel-release && \ - yum -y install git python3 python3-pyyaml python3-devel python3-pip gcc openssl-devel cronie python3-pyOpenSSL fetch-crl && \ + yum -y install git python3 python3-pyyaml python3-devel python3-pip gcc openssl-devel cronie python3-pyOpenSSL fetch-crl graphviz && \ yum clean all RUN mkdir -p /opt/ && \ mkdir -p /srv/ && \ + mkdir -p /srv/icons/ && \ mkdir -p /etc/rtmon/templates/ && \ mkdir -p /var/log/rtmon/ && \ mkdir -p /etc/grid-security/certificates/ @@ -20,12 +21,13 @@ RUN git clone https://github.com/sdn-sense/sense-o-py-client.git /opt/sense-o-py # Install RTMON (TODO, replace to ESnet repo once merged) RUN git clone https://github.com/esnet/sense-rtmon.git /opt/sense-rtmon && \ - cd /opt/sense-rtmon/autogole-api/ && pip3 install -r requirements.txt && pip3 install . && \ + cd /opt/sense-rtmon/autogole-api/ && pip3 install -r requirements.txt && pip3 install diagrams && pip3 install . && \ cp src/templates/* /etc/rtmon/templates/ RUN wget https://raw.githubusercontent.com/sdn-sense/rm-configs/master/CAs/SiteRM.pem -O /etc/grid-security/certificates/e52ac827.0 ADD files/etc/supervisord.d/10-server.conf /etc/supervisord.d/10-server.conf - +COPY icons/host.png /srv/icons/host.png +COPY icons/switch.png /srv/icons/switch.png # Get latest CA's RUN fetch-crl || echo "Supress warnings." diff --git a/autogole-api/packaging/icons/host.png b/autogole-api/packaging/icons/host.png new file mode 100644 index 0000000000000000000000000000000000000000..71fcd4a81d70a01235d09042649cf9b069aed1e2 GIT binary patch literal 15239 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%0Ad!ey(vJaye5M;>90SJ-pk{@iQkL73qP}B z%l8lD`?tAC#18KFi#b>tTS(imOLX|6g6jNrOL{u&0@KF~dnlC$sBm#Qa)j$QUGFws zLGR_kjM2j!K6|8tA+^f+DDEl*9IvY#3b5mcb66T%9!kN1Jspn~uAuiX@Vy(B#uj!Q zKJW0E!$GjJrk?k==CXR;v$(s7HQJPO+~KBX2NEMgnKLo6Gd(gS5Us}D;oRR9JT)3d z^f!rY8GBE&=X9~8&$x(SyFu{QZ5!`z&6PZSmFxm%E>}3*%bSxBg-;JH*Q+fK{|*&i z5#{fp)sP%LM3S@=Qewlnsi@{+xmjeJn;1o)pX)(NKYw3-|4gLFR#aBS)yisz`HgxnR?eP; z$324WJaV4w{0m){<#Z5C-+heyi>3DzsN;>)cuAkH4;6^g-kw@kzcN{J?Ql2x`TO$W zyd+UP0PxkJvywhbQsOb{#c@ae#ZuCDA9GrsQvqx?fe*IrqvS@7$Ud~6Nx}p67hK}> zrE-z2kI|R(MbGhKTsWQ@jh&CDM#D>EB3PgHoSjf7ipw}qaK+B217UsrMd^|oHGHsb zA2yrw9Tk_N!G`8mR%Gnq(*-G==lc84Ngr4U0<7A8fb|Pf2{7DwGv1zB7EXJbSCXPR zd$oe{n-<(q)7z+LM1PZApNF;q`S05gbjk1Vf3J}mI~0JLhGte|>~Sd)1c{ZP6ifym zlA=PXtgdJ8nM+R3@Pm69_-Ni_dig%s0cT}RJ%8SuW7qobg_~N<#E}CD(Cf(kvDl^S zHXSR$`x4+mVD$0g?HS{V4h_05zx<13tl4pZn{`cc(CQRG_B}y!T_f*&v6~6QLwI>? z1cQV8yUbryR>fC`&UV@7A0`yLBD4SOW%i%FOn;LfUDfFKbeeur|j~MiwI^;LX=%dD;0J>EXXg1>FDqmgS%-%DX<)kS8J;BB4S(N;*>tMza%FMOAwdvnf{hIFy zLfa|NqV!CTUbyD6&xztPUjHnUpQ~zRWYz{nrokp_D}VVimu&~7cXc|DoqvJl|N35$ zo8q8gFl4g{{4*z?aD(EZi u`|E`OrGlXPMyqN4IPa6U$ literal 0 HcmV?d00001 diff --git a/autogole-api/requirements.txt b/autogole-api/requirements.txt index 31f34b8..a6ec8ad 100644 --- a/autogole-api/requirements.txt +++ b/autogole-api/requirements.txt @@ -2,3 +2,4 @@ psutil requests pyyaml grafana-client +diagrams diff --git a/autogole-api/src/python/RTMon/worker.py b/autogole-api/src/python/RTMon/worker.py index e796121..dd27bc3 100644 --- a/autogole-api/src/python/RTMon/worker.py +++ b/autogole-api/src/python/RTMon/worker.py @@ -84,8 +84,14 @@ def submit_exe(self, filename, fout): self._updateState(filename, fout) def delete_exe(self, filename, fout): + """Delete Action Execution""" self.logger.info('Delete Execution: %s, %s', filename, fout) + #Deleting the diagram image + diagram_filename = f"{self.config.get('image_dir', '/srv/images')}/diagram_{fout['referenceUUID']}.png" + if os.path.exists(diagram_filename): + os.remove(diagram_filename) + self.logger.info(f"Removed diagram image {diagram_filename}") # Delete the dashboard and template from Grafana for grafDir, dirVals in self.dashboards.items(): for dashbName, dashbVals in dirVals.items(): diff --git a/autogole-api/src/python/RTMonLibs/DiagramWorker.py b/autogole-api/src/python/RTMonLibs/DiagramWorker.py new file mode 100644 index 0000000..2ad27c2 --- /dev/null +++ b/autogole-api/src/python/RTMonLibs/DiagramWorker.py @@ -0,0 +1,146 @@ + +import os +from diagrams import Diagram, Cluster, Edge +from diagrams.custom import Custom + +def _processName(name): + """Process Name for Diagram and replace all special chars with _""" + for repl in [[" ", "_"], [":", "_"], ["/", "_"], ["-", "_"], [".", "_"], ["?", "_"]]: + name = name.replace(repl[0], repl[1]) + return name + +class DiagramWorker: + HOST_ICON_PATH = '/srv/icons/host.png' + SWITCH_ICON_PATH = '/srv/icons/switch.png' + + def __init__(self, indata): + self.indata = indata + self.objects = {} + self.added = {} + self.linksadded = set() + self.popreverse = None + + def _d_findItem(self, fval, fkey): + """Find Item where fkey == fval""" + for key, vals in self.objects.items(): + if vals.get('data', {}).get(fkey, '') == fval: + return key, vals + return None, None + + @staticmethod + def d_LinkLabel(vals1, vals2): + """Get Link Label""" + label = "" + if vals1.get('data', {}).get('Type', '') == "Host": + label = f"Port1: {vals1['data']['Interface']}" + elif vals1.get('data', {}).get('Type', '') == "Switch": + label = f"Port1: {vals1['data']['Name']}" + # Get second side info: + if vals2.get('data', {}).get('Type', '') == "Host": + label += f"\nPort2: {vals2['data']['Interface']}" + elif vals2.get('data', {}).get('Type', '') == "Switch": + label += f"\nPort2: {vals2['data']['Name']}" + if vals1.get('data', {}).get('Vlan', None): + label += f"\nVlan: {vals1['data']['Vlan']}" + elif vals2.get('data', {}).get('Vlan', None): + label += f"\nVlan: {vals2['data']['Vlan']}" + return label + + def d_addLink(self, val1, val2, key, fkey): + """Add Link between 2 objects""" + if val1 and val2 and key and fkey: + if key == fkey: + return + + link_keys = tuple(sorted([key, fkey])) + if link_keys in self.linksadded: + return + self.linksadded.add(link_keys) + + val1["obj"] >> Edge(label=self.d_LinkLabel(val1, val2)) << val2["obj"] + + def d_addLinks(self): + """Identify Links between items""" + for key, vals in self.objects.items(): + data_type = vals.get('data', {}).get('Type', '') + if data_type == "Host": + fKey, fItem = self._d_findItem(key, 'PeerHost') + if fKey and fItem: + self.d_addLink(self.objects[key], fItem, key, fKey) + elif data_type == "Switch": + if 'Peer' in vals.get('data', {}) and vals['data']['Peer'] != "?peer?": + fKey, fItem = self._d_findItem(vals['data']['Peer'], "Port") + if fKey and fItem: + self.d_addLink(self.objects[key], fItem, key, fKey) + elif 'PeerHost' in vals.get('data', {}): + fKey = vals['data']['PeerHost'] + fItem = self.objects.get(fKey) + if fItem: + self.d_addLink(self.objects[key], fItem, key, fKey) + + def d_addHost(self, item): + name = f"Host: {item['Name'].split(':')[1]}" + name += f"\nInterface: {item['Interface']}" + name += f"\nVlan: {item['Vlan']}" + if 'IPv4' in item and item['IPv4'] != "?ipv4?": + name += f"\nIPv4: {item['IPv4']}" + if 'IPv6' in item and item['IPv6'] != "?ipv6?": + name += f"\nIPv6: {item['IPv6']}" + + worker = Custom(name, self.HOST_ICON_PATH) + self.objects[item['Name']] = {"obj": worker, "data": item} + return worker + + def d_addSwitch(self, item): + if item['Node'] in self.added: + self.objects[item['Port']] = {"obj": self.objects[self.added[item['Node']]]["obj"], "data": item} + return + switch1 = Custom(item['Node'].split(":")[1], self.SWITCH_ICON_PATH) + if 'Peer' in item and item['Peer'] != "?peer?": + self.added[item['Node']] = item['Port'] + self.objects[item['Port']] = {"obj": switch1, "data": item} + elif 'PeerHost' in item: + uniqname = _processName(f'{item["Node"]}_{item["Name"]}') + self.added[item['Node']] = uniqname + self.objects[uniqname] = {"obj": switch1, "data": item} + return switch1 + + def addItem(self, item): + site = self.identifySite(item) + if item['Type'] == 'Host': + with Cluster(site): + return self.d_addHost(item) + elif item['Type'] == 'Switch': + with Cluster(site): + return self.d_addSwitch(item) + + def identifySite(self, item): + site = None + if item['Type'] == 'Host': + site = item['Name'].split(':')[0] + elif item['Type'] == 'Switch': + site = item['Node'].split(':')[0] + return site + + def setreverse(self, item): + if item['Type'] == 'Host' and self.popreverse == None: + self.popreverse = False + elif item['Type'] == 'Host' and self.popreverse == False: + self.popreverse = True + elif item['Type'] == 'Host' and self.popreverse == True: + self.popreverse = False + + def createGraph(self, output_filename): + output_dir = os.path.dirname(output_filename) + if not os.path.exists(output_dir): + os.makedirs(output_dir) + + with Diagram("Network Topology", show=False, filename=output_filename): + while len(self.indata) > 0: + if self.popreverse == None or self.popreverse == False: + item = self.indata.pop(0) + elif self.popreverse == True: + item = self.indata.pop() + self.addItem(item) + self.setreverse(item) + self.d_addLinks() diff --git a/autogole-api/src/python/RTMonLibs/Template.py b/autogole-api/src/python/RTMonLibs/Template.py index 984db43..e940865 100644 --- a/autogole-api/src/python/RTMonLibs/Template.py +++ b/autogole-api/src/python/RTMonLibs/Template.py @@ -4,6 +4,7 @@ import copy import os.path from RTMonLibs.GeneralLibs import loadJson, dumpJson, dumpYaml, escape, getUUID +from RTMonLibs.DiagramWorker import DiagramWorker def _processName(name): """Process Name for Mermaid and replace all special chars with _""" @@ -628,6 +629,13 @@ def t_createTemplate(self, *args, **kwargs): # Add Mermaid (Send copy of args, as t_createMermaid will modify it by del items) orig_args = copy.deepcopy(args) self.generated['panels'] += self.t_createMermaid(*orig_args) + #Generate Diagrams + try: + diagram_filename = f"{self.config.get('image_dir', '/srv/images')}/diagram_{kwargs['referenceUUID']}" + DiagramWorker(self.orderlist).createGraph(diagram_filename) + self.logger.info(f"Diagram saved at {diagram_filename}.png") + except IOError as ex: + self.logger.error('Failed to create diagram: %s', ex) # Add Links on top of the page self.generated['links'] = self.t_addLinks(*args, **kwargs) # Add Debug Info (manifest, instance) From 0b94a701e51b505a66acc52f53f38fe25295dfe2 Mon Sep 17 00:00:00 2001 From: Sunami Dasgupta Date: Sun, 20 Oct 2024 22:04:08 -0700 Subject: [PATCH 02/13] Lint Dockerfile fixed --- autogole-api/packaging/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autogole-api/packaging/Dockerfile b/autogole-api/packaging/Dockerfile index bf2534b..b28bd19 100644 --- a/autogole-api/packaging/Dockerfile +++ b/autogole-api/packaging/Dockerfile @@ -27,7 +27,7 @@ RUN git clone https://github.com/esnet/sense-rtmon.git /opt/sense-rtmon && \ RUN wget https://raw.githubusercontent.com/sdn-sense/rm-configs/master/CAs/SiteRM.pem -O /etc/grid-security/certificates/e52ac827.0 ADD files/etc/supervisord.d/10-server.conf /etc/supervisord.d/10-server.conf -COPY icons/host.png /srv/icons/host.png -COPY icons/switch.png /srv/icons/switch.png +ADD icons/host.png /srv/icons/host.png +ADD icons/switch.png /srv/icons/switch.png # Get latest CA's RUN fetch-crl || echo "Supress warnings." From e588a79d4573e802f9387e9fe7c0b27903867e1d Mon Sep 17 00:00:00 2001 From: Sunami Dasgupta Date: Sun, 20 Oct 2024 22:08:34 -0700 Subject: [PATCH 03/13] Lint Dockerfile fixed --- autogole-api/packaging/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/autogole-api/packaging/Dockerfile b/autogole-api/packaging/Dockerfile index b28bd19..6e77ffd 100644 --- a/autogole-api/packaging/Dockerfile +++ b/autogole-api/packaging/Dockerfile @@ -26,8 +26,8 @@ RUN git clone https://github.com/esnet/sense-rtmon.git /opt/sense-rtmon && \ RUN wget https://raw.githubusercontent.com/sdn-sense/rm-configs/master/CAs/SiteRM.pem -O /etc/grid-security/certificates/e52ac827.0 -ADD files/etc/supervisord.d/10-server.conf /etc/supervisord.d/10-server.conf -ADD icons/host.png /srv/icons/host.png -ADD icons/switch.png /srv/icons/switch.png +COPY files/etc/supervisord.d/10-server.conf /etc/supervisord.d/10-server.conf +COPY icons/host.png /srv/icons/host.png +COPY icons/switch.png /srv/icons/switch.png # Get latest CA's RUN fetch-crl || echo "Supress warnings." From 096b20e1ab0c497ff12de6401e1ac749b2c766a7 Mon Sep 17 00:00:00 2001 From: Sunami Dasgupta Date: Mon, 21 Oct 2024 18:49:12 -0700 Subject: [PATCH 04/13] PR fixed --- autogole-api/packaging/Dockerfile | 2 +- .../src/python/RTMonLibs/DiagramWorker.py | 57 +++++++++++++++++-- .../src/python/RTMonLibs/GeneralLibs.py | 6 ++ autogole-api/src/python/RTMonLibs/Template.py | 8 +-- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/autogole-api/packaging/Dockerfile b/autogole-api/packaging/Dockerfile index 6e77ffd..c3d051d 100644 --- a/autogole-api/packaging/Dockerfile +++ b/autogole-api/packaging/Dockerfile @@ -21,7 +21,7 @@ RUN git clone https://github.com/sdn-sense/sense-o-py-client.git /opt/sense-o-py # Install RTMON (TODO, replace to ESnet repo once merged) RUN git clone https://github.com/esnet/sense-rtmon.git /opt/sense-rtmon && \ - cd /opt/sense-rtmon/autogole-api/ && pip3 install -r requirements.txt && pip3 install diagrams && pip3 install . && \ + cd /opt/sense-rtmon/autogole-api/ && pip3 install -r requirements.txt && pip3 install . && \ cp src/templates/* /etc/rtmon/templates/ RUN wget https://raw.githubusercontent.com/sdn-sense/rm-configs/master/CAs/SiteRM.pem -O /etc/grid-security/certificates/e52ac827.0 diff --git a/autogole-api/src/python/RTMonLibs/DiagramWorker.py b/autogole-api/src/python/RTMonLibs/DiagramWorker.py index 2ad27c2..85739d3 100644 --- a/autogole-api/src/python/RTMonLibs/DiagramWorker.py +++ b/autogole-api/src/python/RTMonLibs/DiagramWorker.py @@ -1,19 +1,30 @@ +""" +DiagramWorker Class for Network Topology Visualization +This module contains the DiagramWorker class, which generates network topology diagrams +by processing input data that includes hosts and switches. It uses the 'diagrams' library +to visualize network components and their interconnections. +""" import os from diagrams import Diagram, Cluster, Edge from diagrams.custom import Custom - -def _processName(name): - """Process Name for Diagram and replace all special chars with _""" - for repl in [[" ", "_"], [":", "_"], ["/", "_"], ["-", "_"], [".", "_"], ["?", "_"]]: - name = name.replace(repl[0], repl[1]) - return name +from RTMonLibs.GeneralLibs import _processName class DiagramWorker: + """ + DiagramWorker class is responsible for generating network topology diagrams + using the input data that contains host and switch information. The class + identifies and visualizes links between network components. + """ HOST_ICON_PATH = '/srv/icons/host.png' SWITCH_ICON_PATH = '/srv/icons/switch.png' def __init__(self, indata): + """ + Initialize the DiagramWorker with input data. + + :param indata: List of dictionaries containing host and switch details. + """ self.indata = indata self.objects = {} self.added = {} @@ -79,6 +90,12 @@ def d_addLinks(self): self.d_addLink(self.objects[key], fItem, key, fKey) def d_addHost(self, item): + """ + Add a host to the network diagram. + + :param item: Dictionary containing host details. + :return: Diagram object representing the host. + """ name = f"Host: {item['Name'].split(':')[1]}" name += f"\nInterface: {item['Interface']}" name += f"\nVlan: {item['Vlan']}" @@ -92,6 +109,12 @@ def d_addHost(self, item): return worker def d_addSwitch(self, item): + """ + Add a switch to the network diagram. + + :param item: Dictionary containing switch details. + :return: Diagram object representing the switch. + """ if item['Node'] in self.added: self.objects[item['Port']] = {"obj": self.objects[self.added[item['Node']]]["obj"], "data": item} return @@ -106,6 +129,12 @@ def d_addSwitch(self, item): return switch1 def addItem(self, item): + """ + Add an item (host or switch) to the diagram by identifying its type and location (cluster). + + :param item: Dictionary containing item details. + :return: Diagram object representing the item. + """ site = self.identifySite(item) if item['Type'] == 'Host': with Cluster(site): @@ -115,6 +144,12 @@ def addItem(self, item): return self.d_addSwitch(item) def identifySite(self, item): + """ + Identify the site or cluster to which the item (host or switch) belongs. + + :param item: Dictionary containing item details. + :return: The name of the site or cluster. + """ site = None if item['Type'] == 'Host': site = item['Name'].split(':')[0] @@ -123,6 +158,11 @@ def identifySite(self, item): return site def setreverse(self, item): + """ + Set the reverse flag for alternating between the first and last items in the input list. + + :param item: Dictionary containing item details. + """ if item['Type'] == 'Host' and self.popreverse == None: self.popreverse = False elif item['Type'] == 'Host' and self.popreverse == False: @@ -131,6 +171,11 @@ def setreverse(self, item): self.popreverse = False def createGraph(self, output_filename): + """ + Create the network topology diagram and save it to a file. + + :param output_filename: Path where the output diagram will be saved. + """ output_dir = os.path.dirname(output_filename) if not os.path.exists(output_dir): os.makedirs(output_dir) diff --git a/autogole-api/src/python/RTMonLibs/GeneralLibs.py b/autogole-api/src/python/RTMonLibs/GeneralLibs.py index b7f400c..6e9d4c9 100644 --- a/autogole-api/src/python/RTMonLibs/GeneralLibs.py +++ b/autogole-api/src/python/RTMonLibs/GeneralLibs.py @@ -9,6 +9,12 @@ from yaml import safe_load as yload from yaml import safe_dump as ydump +def _processName(name): + """Process Name for Mermaid and replace all special chars with _""" + for repl in [[" ", "_"], [":", "_"], ["/", "_"], ["-", "_"], [".", "_"], ["?", "_"]]: + name = name.replace(repl[0], repl[1]) + return name + def getUUID(inputstr): """Generate UUID from Input Str""" hashObject = hashlib.sha256(inputstr.encode('utf-8')) diff --git a/autogole-api/src/python/RTMonLibs/Template.py b/autogole-api/src/python/RTMonLibs/Template.py index e940865..7585b49 100644 --- a/autogole-api/src/python/RTMonLibs/Template.py +++ b/autogole-api/src/python/RTMonLibs/Template.py @@ -3,15 +3,9 @@ """Grafana Template Generation""" import copy import os.path -from RTMonLibs.GeneralLibs import loadJson, dumpJson, dumpYaml, escape, getUUID +from RTMonLibs.GeneralLibs import loadJson, dumpJson, dumpYaml, escape, getUUID, _processName from RTMonLibs.DiagramWorker import DiagramWorker -def _processName(name): - """Process Name for Mermaid and replace all special chars with _""" - for repl in [[" ", "_"], [":", "_"], ["/", "_"], ["-", "_"], [".", "_"], ["?", "_"]]: - name = name.replace(repl[0], repl[1]) - return name - def clamp(n, minn, maxn): """Clamp the value between min and max""" return max(min(maxn, n), minn) From f1fcc5d2dc609c8ed56387ab7f6e3f42894136ac Mon Sep 17 00:00:00 2001 From: Sunami Dasgupta Date: Sun, 27 Oct 2024 20:25:22 -0700 Subject: [PATCH 05/13] Lint Edit --- autogole-api/src/python/RTMonLibs/DiagramWorker.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/autogole-api/src/python/RTMonLibs/DiagramWorker.py b/autogole-api/src/python/RTMonLibs/DiagramWorker.py index 85739d3..6330be6 100644 --- a/autogole-api/src/python/RTMonLibs/DiagramWorker.py +++ b/autogole-api/src/python/RTMonLibs/DiagramWorker.py @@ -116,7 +116,10 @@ def d_addSwitch(self, item): :return: Diagram object representing the switch. """ if item['Node'] in self.added: - self.objects[item['Port']] = {"obj": self.objects[self.added[item['Node']]]["obj"], "data": item} + self.objects[item['Port']] = { + "obj": self.objects[self.added[item['Node']]]["obj"], + "data": item + } return switch1 = Custom(item['Node'].split(":")[1], self.SWITCH_ICON_PATH) if 'Peer' in item and item['Peer'] != "?peer?": @@ -163,11 +166,11 @@ def setreverse(self, item): :param item: Dictionary containing item details. """ - if item['Type'] == 'Host' and self.popreverse == None: + if item['Type'] == 'Host' and self.popreverse is None: self.popreverse = False - elif item['Type'] == 'Host' and self.popreverse == False: + elif item['Type'] == 'Host' and self.popreverse is False: self.popreverse = True - elif item['Type'] == 'Host' and self.popreverse == True: + elif item['Type'] == 'Host' and self.popreverse is True: self.popreverse = False def createGraph(self, output_filename): From 0c0728511f55a678ad9f31f0c8d6ca8daa43034c Mon Sep 17 00:00:00 2001 From: Sunami Dasgupta Date: Fri, 8 Nov 2024 10:02:52 -0800 Subject: [PATCH 06/13] Added BGP --- .../src/python/RTMonLibs/DiagramWorker.py | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/autogole-api/src/python/RTMonLibs/DiagramWorker.py b/autogole-api/src/python/RTMonLibs/DiagramWorker.py index 6330be6..4379448 100644 --- a/autogole-api/src/python/RTMonLibs/DiagramWorker.py +++ b/autogole-api/src/python/RTMonLibs/DiagramWorker.py @@ -31,7 +31,7 @@ def __init__(self, indata): self.linksadded = set() self.popreverse = None - def _d_findItem(self, fval, fkey): + def d_find_item(self, fval, fkey): """Find Item where fkey == fval""" for key, vals in self.objects.items(): if vals.get('data', {}).get(fkey, '') == fval: @@ -75,19 +75,19 @@ def d_addLinks(self): for key, vals in self.objects.items(): data_type = vals.get('data', {}).get('Type', '') if data_type == "Host": - fKey, fItem = self._d_findItem(key, 'PeerHost') + fKey, fItem = self.d_find_item(key, 'PeerHost') if fKey and fItem: - self.d_addLink(self.objects[key], fItem, key, fKey) + self.d_addLink(vals, fItem, key, fKey) elif data_type == "Switch": if 'Peer' in vals.get('data', {}) and vals['data']['Peer'] != "?peer?": - fKey, fItem = self._d_findItem(vals['data']['Peer'], "Port") + fKey, fItem = self.d_find_item(vals['data']['Peer'], "Port") if fKey and fItem: - self.d_addLink(self.objects[key], fItem, key, fKey) + self.d_addLink(vals, fItem, key, fKey) elif 'PeerHost' in vals.get('data', {}): fKey = vals['data']['PeerHost'] fItem = self.objects.get(fKey) if fItem: - self.d_addLink(self.objects[key], fItem, key, fKey) + self.d_addLink(vals, fItem, key, fKey) def d_addHost(self, item): """ @@ -129,7 +129,51 @@ def d_addSwitch(self, item): uniqname = _processName(f'{item["Node"]}_{item["Name"]}') self.added[item['Node']] = uniqname self.objects[uniqname] = {"obj": switch1, "data": item} + # Add IPv4/IPv6 on the switch + for ipkey, ipdef in {'IPv4': '?port_ipv4?', 'IPv6': '?port_ipv6?'}.items(): + if ipkey in item and item[ipkey] != ipdef: + ip_node_name = f"{item['Node']}_{ipkey}" + ip_label = item[ipkey] + ip_node = Custom(ip_label, self.HOST_ICON_PATH) + self.objects[ip_node_name] = {"obj": ip_node, "data": {}} + # Add edge between switch and IP node + self.d_addLink(self.objects[item['Port']], self.objects[ip_node_name], item['Port'], ip_node_name) + if item.get('Vlan'): + vlan_node_name = f"{item['Node']}_vlan{item['Vlan']}" + vlan_label = f"vlan.{item['Vlan']}" + vlan_node = Custom(vlan_label, self.SWITCH_ICON_PATH) + self.objects[vlan_node_name] = {"obj": vlan_node, "data": {}} + self.d_addLink(self.objects[item['Port']], self.objects[vlan_node_name], item['Port'], vlan_node_name) + self.d_addLink(self.objects[vlan_node_name], self.objects[ip_node_name], vlan_node_name, ip_node_name) + # Add BGP Peering information + bgppeer = ip_node_name + self.d_addBGP(item, ipkey, bgppeer) return switch1 + + def d_addBGP(self, item, ipkey, bgppeer): + """Add BGP into the network diagram""" + if not item.get('Site', None): + return + if not self.instance: + return + for intitem in self.instance.get('intents', []): + for connections in intitem.get('json', {}).get('data', {}).get('connections', []): + for terminal in connections.get('terminals', []): + if 'uri' not in terminal: + continue + if item['Site'] == terminal['uri'] and terminal.get(f'{ipkey.lower()}_prefix_list', None): + val = terminal[f'{ipkey.lower()}_prefix_list'] + bgp_node_name = f"{bgppeer}_bgp{ipkey}" + bgp_node_label = f"BGP_{ipkey}" + bgp_node = Custom(bgp_node_label, self.SWITCH_ICON_PATH) + self.objects[bgp_node_name] = {"obj": bgp_node, "data": {}} + self.d_addLink(self.objects[bgppeer], self.objects[bgp_node_name], bgppeer, bgp_node_name) + peer_node_name = f"{bgppeer}_bgp{ipkey}_peer" + peer_node_label = val + peer_node = Custom(peer_node_label, self.SWITCH_ICON_PATH) + self.objects[peer_node_name] = {"obj": peer_node, "data": {}} + self.d_addLink(self.objects[bgp_node_name], self.objects[peer_node_name], bgp_node_name, peer_node_name) + def addItem(self, item): """ @@ -185,7 +229,7 @@ def createGraph(self, output_filename): with Diagram("Network Topology", show=False, filename=output_filename): while len(self.indata) > 0: - if self.popreverse == None or self.popreverse == False: + if self.popreverse in (None, False): item = self.indata.pop(0) elif self.popreverse == True: item = self.indata.pop() From ee2526f77b16f4a6febeabbb10bb2b50128f2312 Mon Sep 17 00:00:00 2001 From: Sunami Dasgupta Date: Mon, 11 Nov 2024 08:59:50 -0800 Subject: [PATCH 07/13] BGP Added --- autogole-api/src/python/RTMonLibs/DiagramWorker.py | 4 +++- autogole-api/src/python/RTMonLibs/Template.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/autogole-api/src/python/RTMonLibs/DiagramWorker.py b/autogole-api/src/python/RTMonLibs/DiagramWorker.py index 4379448..d8279b4 100644 --- a/autogole-api/src/python/RTMonLibs/DiagramWorker.py +++ b/autogole-api/src/python/RTMonLibs/DiagramWorker.py @@ -19,7 +19,7 @@ class DiagramWorker: HOST_ICON_PATH = '/srv/icons/host.png' SWITCH_ICON_PATH = '/srv/icons/switch.png' - def __init__(self, indata): + def __init__(self, indata, instance, manifest): """ Initialize the DiagramWorker with input data. @@ -30,6 +30,8 @@ def __init__(self, indata): self.added = {} self.linksadded = set() self.popreverse = None + self.instance = instance + def d_find_item(self, fval, fkey): """Find Item where fkey == fval""" diff --git a/autogole-api/src/python/RTMonLibs/Template.py b/autogole-api/src/python/RTMonLibs/Template.py index 7585b49..8b20f1e 100644 --- a/autogole-api/src/python/RTMonLibs/Template.py +++ b/autogole-api/src/python/RTMonLibs/Template.py @@ -626,7 +626,7 @@ def t_createTemplate(self, *args, **kwargs): #Generate Diagrams try: diagram_filename = f"{self.config.get('image_dir', '/srv/images')}/diagram_{kwargs['referenceUUID']}" - DiagramWorker(self.orderlist).createGraph(diagram_filename) + DiagramWorker(self.orderlist, *orig_args).createGraph(diagram_filename) self.logger.info(f"Diagram saved at {diagram_filename}.png") except IOError as ex: self.logger.error('Failed to create diagram: %s', ex) From 8859737d2ee82c4a539bdf0e8661afda5d4af439 Mon Sep 17 00:00:00 2001 From: "sunamidasgupta@gmail.com" Date: Wed, 13 Nov 2024 10:45:15 -0800 Subject: [PATCH 08/13] ImageServer --- autogole-api/packaging/imageserver.py | 36 +++++++++++++++++++ autogole-api/packaging/start-dev.sh | 2 +- autogole-api/requirements.txt | 1 + autogole-api/src/python/RTMonLibs/Template.py | 16 +++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 autogole-api/packaging/imageserver.py diff --git a/autogole-api/packaging/imageserver.py b/autogole-api/packaging/imageserver.py new file mode 100644 index 0000000..22e8571 --- /dev/null +++ b/autogole-api/packaging/imageserver.py @@ -0,0 +1,36 @@ +from flask import Flask, send_from_directory, render_template_string, abort +import os + +app = Flask(__name__) + +IMAGE_DIRECTORY = '/srv/images' +@app.route('/') +def home(): + return "

Hello, this is the root page!

" + +@app.route('/images') +def list_images(): + if not os.path.exists(IMAGE_DIRECTORY): + return "

Image directory not found.

", 404 + + images = [f for f in os.listdir(IMAGE_DIRECTORY) if os.path.isfile(os.path.join(IMAGE_DIRECTORY, f))] + + html = """ +

Network Topology

+
    + {% for image in images %} +
  • {{ image }}
  • + {% endfor %} +
+ """ + return render_template_string(html, images=images) + +@app.route('/images/') +def serve_image(filename): + if not os.path.isfile(os.path.join(IMAGE_DIRECTORY, filename)): + abort(404) + return send_from_directory(IMAGE_DIRECTORY, filename) + +if __name__ == "__main__": + port = 8000 + app.run(host='0.0.0.0', port=port, debug=True) \ No newline at end of file diff --git a/autogole-api/packaging/start-dev.sh b/autogole-api/packaging/start-dev.sh index 506d9d9..75f95d2 100755 --- a/autogole-api/packaging/start-dev.sh +++ b/autogole-api/packaging/start-dev.sh @@ -14,5 +14,5 @@ docker run \ -v $(pwd)/files/etc/grid-security/hostcert.pem:/etc/grid-security/hostcert.pem:ro \ -v $(pwd)/files/etc/grid-security/hostcert.pem:/etc/grid-security/hostkey.pem:ro \ --restart always \ - --net=host \ + -p 8000:8000 \ rtmon diff --git a/autogole-api/requirements.txt b/autogole-api/requirements.txt index a6ec8ad..e04bd43 100644 --- a/autogole-api/requirements.txt +++ b/autogole-api/requirements.txt @@ -3,3 +3,4 @@ requests pyyaml grafana-client diagrams +flask diff --git a/autogole-api/src/python/RTMonLibs/Template.py b/autogole-api/src/python/RTMonLibs/Template.py index 8b20f1e..cf060b2 100644 --- a/autogole-api/src/python/RTMonLibs/Template.py +++ b/autogole-api/src/python/RTMonLibs/Template.py @@ -341,6 +341,19 @@ def _t_loadTemplate(self, templateName): template = loadJson(fd.read(), self.logger) return template + def t_addImagePanel(self, image_url, title="Image Panel"): + """Add an Image Panel to the Dashboard""" + panel = { + "type": "text", + "title": title, + "options": { + "content": f"
", + }, + "gridPos": {"x": 0, "y": 0, "w": 24, "h": 10}, + "id": self._getNextID() + } + return panel + def t_addRow(self, *_args, **kwargs): """Add Row to the Dashboard""" out = self._t_loadTemplate("row.json") @@ -640,4 +653,7 @@ def t_createTemplate(self, *args, **kwargs): self.generated['panels'] += self.t_createSwitchFlow(*args) # Add L2 Debugging self.generated['panels'] += self.t_addL2Debugging(*args) + base_image_url = self.config.get('baseImageURL', 'http://localhost:8080/images') + image_url = f"{base_image_url}/diagram_{kwargs['referenceUUID']}.png" + self.generated['panels'].append(self.t_addImagePanel(image_url, title="Network Topology Image")) return {"dashboard": self.generated}, {"uid": self.generated['uid'], "annotation_panels": self.annotationids} From a4681de9e09fdf982e049ae3592179dc923202b2 Mon Sep 17 00:00:00 2001 From: "sunamidasgupta@gmail.com" Date: Wed, 13 Nov 2024 10:54:06 -0800 Subject: [PATCH 09/13] rtmon.yaml --- autogole-api/packaging/files/etc/rtmon.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/autogole-api/packaging/files/etc/rtmon.yaml b/autogole-api/packaging/files/etc/rtmon.yaml index 4f4f06c..cc1d31f 100644 --- a/autogole-api/packaging/files/etc/rtmon.yaml +++ b/autogole-api/packaging/files/etc/rtmon.yaml @@ -2,11 +2,13 @@ sleep_timer: 30 # Work dir for temp files (api directory). Default /srv workdir: '/srv/' - +image_dir: '/srv/images' # Grafana settings (for the API) grafana_host: 'https://autogole-grafana.nrp-nautilus.io' -grafana_api_key: 'REPLACE_ME' +grafana_api_key: '' +baseImageURL: 'http://localhost:8080/images' grafana_folder: 'Real Time Mon' +grafana_dev: 'Sunami-Image-Server' # FOR DEVELOPMEENT ONLY # Enable grafana_dev parameter (can be any string, and will be used as name to create directory inside Grafana) # Additionally - all dashboards will have this added to name. @@ -25,7 +27,7 @@ data_sources: # Sense endpoints and their auth files. sense_endpoints: - "sense-o.es.net": "/etc/sense-o-auth-prod.yaml" + #"sense-o.es.net": "/etc/sense-o-auth-prod.yaml" "sense-o-dev.es.net": "/etc/sense-o-auth.yaml" # Additional links for the templates From f4898acb59b22bf146aa42f2978cd88f042d20fb Mon Sep 17 00:00:00 2001 From: sunami09 Date: Mon, 2 Dec 2024 21:32:28 -0800 Subject: [PATCH 10/13] BGP Added --- .../src/python/RTMonLibs/DiagramWorker.py | 62 ++++++++----------- autogole-api/src/python/RTMonLibs/Template.py | 7 ++- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/autogole-api/src/python/RTMonLibs/DiagramWorker.py b/autogole-api/src/python/RTMonLibs/DiagramWorker.py index d8279b4..fae172b 100644 --- a/autogole-api/src/python/RTMonLibs/DiagramWorker.py +++ b/autogole-api/src/python/RTMonLibs/DiagramWorker.py @@ -9,6 +9,13 @@ from diagrams import Diagram, Cluster, Edge from diagrams.custom import Custom from RTMonLibs.GeneralLibs import _processName +# # change later: +# def _processName(name): +# """Process Name for Mermaid and replace all special chars with _""" +# for repl in [[" ", "_"], [":", "_"], ["/", "_"], ["-", "_"], [".", "_"], ["?", "_"]]: +# name = name.replace(repl[0], repl[1]) +# return name +# ######### class DiagramWorker: """ @@ -18,6 +25,9 @@ class DiagramWorker: """ HOST_ICON_PATH = '/srv/icons/host.png' SWITCH_ICON_PATH = '/srv/icons/switch.png' + # # TEmp + # HOST_ICON_PATH = '/Users/sunami/Desktop/publish/sense-rtmon/autogole-api/packaging/icons/host.png' + # SWITCH_ICON_PATH = '/Users/sunami/Desktop/publish/sense-rtmon/autogole-api/packaging/icons/switch.png' def __init__(self, indata, instance, manifest): """ @@ -136,45 +146,23 @@ def d_addSwitch(self, item): if ipkey in item and item[ipkey] != ipdef: ip_node_name = f"{item['Node']}_{ipkey}" ip_label = item[ipkey] - ip_node = Custom(ip_label, self.HOST_ICON_PATH) - self.objects[ip_node_name] = {"obj": ip_node, "data": {}} - # Add edge between switch and IP node - self.d_addLink(self.objects[item['Port']], self.objects[ip_node_name], item['Port'], ip_node_name) - if item.get('Vlan'): - vlan_node_name = f"{item['Node']}_vlan{item['Vlan']}" - vlan_label = f"vlan.{item['Vlan']}" - vlan_node = Custom(vlan_label, self.SWITCH_ICON_PATH) - self.objects[vlan_node_name] = {"obj": vlan_node, "data": {}} - self.d_addLink(self.objects[item['Port']], self.objects[vlan_node_name], item['Port'], vlan_node_name) - self.d_addLink(self.objects[vlan_node_name], self.objects[ip_node_name], vlan_node_name, ip_node_name) - # Add BGP Peering information - bgppeer = ip_node_name - self.d_addBGP(item, ipkey, bgppeer) + ip_label2 = "" + sitename = item["Site"] + + if self.instance != None: + tempData = self.instance["intents"] + for flow in tempData: + terminals = flow["json"]["data"]["connections"][0]["terminals"] + for connection in terminals: + if connection["uri"] == sitename: + ip_label2 = connection["ipv6_prefix_list"] + break + ip_node = Custom(ip_label + "\n" + ip_label2, self.HOST_ICON_PATH) + switch1 >> Edge() << ip_node + return switch1 - def d_addBGP(self, item, ipkey, bgppeer): - """Add BGP into the network diagram""" - if not item.get('Site', None): - return - if not self.instance: - return - for intitem in self.instance.get('intents', []): - for connections in intitem.get('json', {}).get('data', {}).get('connections', []): - for terminal in connections.get('terminals', []): - if 'uri' not in terminal: - continue - if item['Site'] == terminal['uri'] and terminal.get(f'{ipkey.lower()}_prefix_list', None): - val = terminal[f'{ipkey.lower()}_prefix_list'] - bgp_node_name = f"{bgppeer}_bgp{ipkey}" - bgp_node_label = f"BGP_{ipkey}" - bgp_node = Custom(bgp_node_label, self.SWITCH_ICON_PATH) - self.objects[bgp_node_name] = {"obj": bgp_node, "data": {}} - self.d_addLink(self.objects[bgppeer], self.objects[bgp_node_name], bgppeer, bgp_node_name) - peer_node_name = f"{bgppeer}_bgp{ipkey}_peer" - peer_node_label = val - peer_node = Custom(peer_node_label, self.SWITCH_ICON_PATH) - self.objects[peer_node_name] = {"obj": peer_node, "data": {}} - self.d_addLink(self.objects[bgp_node_name], self.objects[peer_node_name], bgp_node_name, peer_node_name) + def addItem(self, item): diff --git a/autogole-api/src/python/RTMonLibs/Template.py b/autogole-api/src/python/RTMonLibs/Template.py index cf060b2..90428f7 100644 --- a/autogole-api/src/python/RTMonLibs/Template.py +++ b/autogole-api/src/python/RTMonLibs/Template.py @@ -5,7 +5,7 @@ import os.path from RTMonLibs.GeneralLibs import loadJson, dumpJson, dumpYaml, escape, getUUID, _processName from RTMonLibs.DiagramWorker import DiagramWorker - +#import json def clamp(n, minn, maxn): """Clamp the value between min and max""" return max(min(maxn, n), minn) @@ -639,6 +639,11 @@ def t_createTemplate(self, *args, **kwargs): #Generate Diagrams try: diagram_filename = f"{self.config.get('image_dir', '/srv/images')}/diagram_{kwargs['referenceUUID']}" + # diagram_json = f"diagram_{kwargs['referenceUUID']}.json" + # with open("selfOrder" + diagram_json, 'w') as file: + # json.dump(self.orderlist, file, indent=2) + # with open("original_args" + diagram_json, 'w') as file: + # json.dump(orig_args, file, indent=2) DiagramWorker(self.orderlist, *orig_args).createGraph(diagram_filename) self.logger.info(f"Diagram saved at {diagram_filename}.png") except IOError as ex: From 547e5ab77fb3ca7e7a4f09d45df00a90610555b1 Mon Sep 17 00:00:00 2001 From: sunami09 Date: Sun, 8 Dec 2024 22:44:58 -0800 Subject: [PATCH 11/13] Diagram Patched --- autogole-api/packaging/Dockerfile | 1 + .../files/etc/supervisord.d/10-server.conf | 12 ++++++ .../src/python/RTMonLibs/DiagramWorker.py | 42 +++++++++++++++---- autogole-api/src/python/RTMonLibs/Template.py | 2 +- .../src/python/RTMonLibs/imageserver.py | 36 ++++++++++++++++ 5 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 autogole-api/src/python/RTMonLibs/imageserver.py diff --git a/autogole-api/packaging/Dockerfile b/autogole-api/packaging/Dockerfile index c3d051d..870af45 100644 --- a/autogole-api/packaging/Dockerfile +++ b/autogole-api/packaging/Dockerfile @@ -29,5 +29,6 @@ RUN wget https://raw.githubusercontent.com/sdn-sense/rm-configs/master/CAs/SiteR COPY files/etc/supervisord.d/10-server.conf /etc/supervisord.d/10-server.conf COPY icons/host.png /srv/icons/host.png COPY icons/switch.png /srv/icons/switch.png +COPY imageserver.py /srv/imageserver.py # Get latest CA's RUN fetch-crl || echo "Supress warnings." diff --git a/autogole-api/packaging/files/etc/supervisord.d/10-server.conf b/autogole-api/packaging/files/etc/supervisord.d/10-server.conf index 47e27af..39e1f61 100644 --- a/autogole-api/packaging/files/etc/supervisord.d/10-server.conf +++ b/autogole-api/packaging/files/etc/supervisord.d/10-server.conf @@ -4,3 +4,15 @@ priority=-1 stdout_logfile=/var/log/rtmon/daemon.log stdout_logfile_maxbytes=0 redirect_stderr=true + +[program:image_server] +command=python3 /srv/imageserver.py +directory=/opt/devrtmon +autostart=true +autorestart=true +stderr_logfile=/var/log/rtmon/image_server_error.log +stderr_logfile_maxbytes=0 +stdout_logfile=/var/log/rtmon/image_server.log +stdout_logfile_maxbytes=0 +priority=1 +redirect_stderr=true diff --git a/autogole-api/src/python/RTMonLibs/DiagramWorker.py b/autogole-api/src/python/RTMonLibs/DiagramWorker.py index fae172b..71c21f1 100644 --- a/autogole-api/src/python/RTMonLibs/DiagramWorker.py +++ b/autogole-api/src/python/RTMonLibs/DiagramWorker.py @@ -29,18 +29,18 @@ class DiagramWorker: # HOST_ICON_PATH = '/Users/sunami/Desktop/publish/sense-rtmon/autogole-api/packaging/icons/host.png' # SWITCH_ICON_PATH = '/Users/sunami/Desktop/publish/sense-rtmon/autogole-api/packaging/icons/switch.png' - def __init__(self, indata, instance, manifest): + def __init__(self, **kwargs): """ Initialize the DiagramWorker with input data. :param indata: List of dictionaries containing host and switch details. """ - self.indata = indata self.objects = {} self.added = {} + # self.siteoveride = SiteOverride(**kwargs) self.linksadded = set() self.popreverse = None - self.instance = instance + self.instance = kwargs.get("instance") def d_find_item(self, fval, fkey): @@ -95,6 +95,33 @@ def d_addLinks(self): fKey, fItem = self.d_find_item(vals['data']['Peer'], "Port") if fKey and fItem: self.d_addLink(vals, fItem, key, fKey) + ## if Peer to host pair not found for overide + else: + try: + siteName = vals['data']['Peer'].split("::")[0] + switchName = vals['data']['Peer'].split("::")[1].split(":")[0] + portname = vals['data']['Peer'].split("::")[1].split(":")[1] + except: + parts = vals['data']['Peer'].split(":") + for i, part in enumerate(parts): + if part.isdigit() and len(part) == 4: + siteName = ":".join(parts[:i+1]) + remaining = ":".join(parts[i+1:]) + break + if "::" in remaining: + subparts = remaining.split("::") + switchName = subparts[0] + portname = subparts[1].split(":")[0] if len(subparts) > 1 else None + else: + # If no '::', fallback to splitting by ':' + subparts = remaining.split(":") + switchName = subparts[0] + portname = subparts[1] if len(subparts) > 1 else None + with Cluster(siteName): + newSwitch = Custom(switchName, self.SWITCH_ICON_PATH) + newSwitch >> Edge(label="Port 1: " + portname + '\n' + "Port 2: "+ vals['data']["Name"] + '\n' + "Vlan: " + vals['data']["Vlan"]) << vals["obj"] + + elif 'PeerHost' in vals.get('data', {}): fKey = vals['data']['PeerHost'] fItem = self.objects.get(fKey) @@ -142,6 +169,7 @@ def d_addSwitch(self, item): self.added[item['Node']] = uniqname self.objects[uniqname] = {"obj": switch1, "data": item} # Add IPv4/IPv6 on the switch + ## This is for BGP for ipkey, ipdef in {'IPv4': '?port_ipv4?', 'IPv6': '?port_ipv6?'}.items(): if ipkey in item and item[ipkey] != ipdef: ip_node_name = f"{item['Node']}_{ipkey}" @@ -207,7 +235,7 @@ def setreverse(self, item): elif item['Type'] == 'Host' and self.popreverse is True: self.popreverse = False - def createGraph(self, output_filename): + def createGraph(self, output_filename, indata): """ Create the network topology diagram and save it to a file. @@ -218,11 +246,11 @@ def createGraph(self, output_filename): os.makedirs(output_dir) with Diagram("Network Topology", show=False, filename=output_filename): - while len(self.indata) > 0: + while len(indata) > 0: if self.popreverse in (None, False): - item = self.indata.pop(0) + item =indata.pop(0) elif self.popreverse == True: - item = self.indata.pop() + item = indata.pop() self.addItem(item) self.setreverse(item) self.d_addLinks() diff --git a/autogole-api/src/python/RTMonLibs/Template.py b/autogole-api/src/python/RTMonLibs/Template.py index 90428f7..efaf58b 100644 --- a/autogole-api/src/python/RTMonLibs/Template.py +++ b/autogole-api/src/python/RTMonLibs/Template.py @@ -644,7 +644,7 @@ def t_createTemplate(self, *args, **kwargs): # json.dump(self.orderlist, file, indent=2) # with open("original_args" + diagram_json, 'w') as file: # json.dump(orig_args, file, indent=2) - DiagramWorker(self.orderlist, *orig_args).createGraph(diagram_filename) + DiagramWorker(**kwargs).createGraph(diagram_filename, self.orderlist) self.logger.info(f"Diagram saved at {diagram_filename}.png") except IOError as ex: self.logger.error('Failed to create diagram: %s', ex) diff --git a/autogole-api/src/python/RTMonLibs/imageserver.py b/autogole-api/src/python/RTMonLibs/imageserver.py new file mode 100644 index 0000000..22e8571 --- /dev/null +++ b/autogole-api/src/python/RTMonLibs/imageserver.py @@ -0,0 +1,36 @@ +from flask import Flask, send_from_directory, render_template_string, abort +import os + +app = Flask(__name__) + +IMAGE_DIRECTORY = '/srv/images' +@app.route('/') +def home(): + return "

Hello, this is the root page!

" + +@app.route('/images') +def list_images(): + if not os.path.exists(IMAGE_DIRECTORY): + return "

Image directory not found.

", 404 + + images = [f for f in os.listdir(IMAGE_DIRECTORY) if os.path.isfile(os.path.join(IMAGE_DIRECTORY, f))] + + html = """ +

Network Topology

+
    + {% for image in images %} +
  • {{ image }}
  • + {% endfor %} +
+ """ + return render_template_string(html, images=images) + +@app.route('/images/') +def serve_image(filename): + if not os.path.isfile(os.path.join(IMAGE_DIRECTORY, filename)): + abort(404) + return send_from_directory(IMAGE_DIRECTORY, filename) + +if __name__ == "__main__": + port = 8000 + app.run(host='0.0.0.0', port=port, debug=True) \ No newline at end of file From 074cc27767a2f80aab5f3c6d03319a0e0bb0e7e8 Mon Sep 17 00:00:00 2001 From: sunami09 Date: Sun, 8 Dec 2024 22:56:37 -0800 Subject: [PATCH 12/13] Diagram Collapsible Row --- autogole-api/src/python/RTMonLibs/Template.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/autogole-api/src/python/RTMonLibs/Template.py b/autogole-api/src/python/RTMonLibs/Template.py index efaf58b..eda5597 100644 --- a/autogole-api/src/python/RTMonLibs/Template.py +++ b/autogole-api/src/python/RTMonLibs/Template.py @@ -341,6 +341,13 @@ def _t_loadTemplate(self, templateName): template = loadJson(fd.read(), self.logger) return template + def t_addImageCollapsibleRow(self, image_url, title="Network Topology Image"): + """Add an Image Panel to a Collapsible Row""" + row = self.t_addRow(title=f"{title} Row", collapsed=True) + panel = self.t_addImagePanel(image_url, title=title) + return self.addRowPanel(row, [panel], recordAnnotations=False) + + def t_addImagePanel(self, image_url, title="Image Panel"): """Add an Image Panel to the Dashboard""" panel = { @@ -660,5 +667,5 @@ def t_createTemplate(self, *args, **kwargs): self.generated['panels'] += self.t_addL2Debugging(*args) base_image_url = self.config.get('baseImageURL', 'http://localhost:8080/images') image_url = f"{base_image_url}/diagram_{kwargs['referenceUUID']}.png" - self.generated['panels'].append(self.t_addImagePanel(image_url, title="Network Topology Image")) + self.generated['panels'] += self.t_addImageCollapsibleRow(image_url, title="Network Topology Image") return {"dashboard": self.generated}, {"uid": self.generated['uid'], "annotation_panels": self.annotationids} From f199541ab5742924f30b3d5405be6f6528d1695c Mon Sep 17 00:00:00 2001 From: sunami09 Date: Sun, 8 Dec 2024 23:02:41 -0800 Subject: [PATCH 13/13] Height Increased --- autogole-api/src/python/RTMonLibs/Template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogole-api/src/python/RTMonLibs/Template.py b/autogole-api/src/python/RTMonLibs/Template.py index eda5597..cc58338 100644 --- a/autogole-api/src/python/RTMonLibs/Template.py +++ b/autogole-api/src/python/RTMonLibs/Template.py @@ -356,7 +356,7 @@ def t_addImagePanel(self, image_url, title="Image Panel"): "options": { "content": f"
", }, - "gridPos": {"x": 0, "y": 0, "w": 24, "h": 10}, + "gridPos": {"x": 0, "y": 0, "w": 24, "h": 30}, "id": self._getNextID() } return panel