Skip to content

Commit

Permalink
Diagram class related to #127
Browse files Browse the repository at this point in the history
  • Loading branch information
Sunami Dasgupta authored and Sunami Dasgupta committed Oct 21, 2024
1 parent 96b33b3 commit 08dd2c0
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 3 deletions.
8 changes: 5 additions & 3 deletions autogole-api/packaging/Dockerfile
Original file line number Diff line number Diff line change
@@ -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/
Expand All @@ -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."
Binary file added autogole-api/packaging/icons/host.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added autogole-api/packaging/icons/switch.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions autogole-api/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ psutil
requests
pyyaml
grafana-client
diagrams
6 changes: 6 additions & 0 deletions autogole-api/src/python/RTMon/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
146 changes: 146 additions & 0 deletions autogole-api/src/python/RTMonLibs/DiagramWorker.py
Original file line number Diff line number Diff line change
@@ -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()
8 changes: 8 additions & 0 deletions autogole-api/src/python/RTMonLibs/Template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 _"""
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 08dd2c0

Please sign in to comment.