diff --git a/createDash.py b/createDash.py index 8b1a74a..483eb65 100644 --- a/createDash.py +++ b/createDash.py @@ -1,3 +1,4 @@ +#Main dashboard generation script import yaml from decouple import config import json @@ -79,8 +80,13 @@ def main(): dashboard_json.append(createImageTile(config)) dashboard_json = dashboard_json + createHeaderTiles(config,args.wall) boundindex = 1 + #Load SLO config as object from yaml definition sloconfigs = getSloConfigurations(configuration, script_config) + #Generate tile row including description, single value and graph tiles dashboard_json = dashboard_json + createSloTileRow(boundindex,script_config,args.wall,sloconfigs,doc) + #Increment row index + boundindex = boundindex+1 + #Save tile JSON to file if rowcount == 0 or (args.rows is not None and boundindex%args.rows != 0): with open("./tiles/dashboard_tiles_"+str(dahboardcount)+".json", "w") as file: json.dump(dashboard_json, file, indent=2) diff --git a/dataExplorerTile.py b/dataExplorerTile.py index 8cac8ee..a63c4e1 100644 --- a/dataExplorerTile.py +++ b/dataExplorerTile.py @@ -1,3 +1,4 @@ +#Single value tile definition (for json parse purpose) class SingleValueTile: def __init__(self,name,bounds,timeframe,remoteEnvironmentUrl,metricSelector,graphThreshold): self.name = name @@ -28,7 +29,7 @@ class SingleValueTile: "tableSettings": { "isThresholdBackgroundAppliedToCell": "false" }, "graphChartSettings": { "connectNulls": "false" } } self.queriesSettings = { "resolution": "","foldAggregation": "AVG" } - +#Graph tile definition (for json parse purpose) class GraphTile: def __init__(self,name,bounds,remoteEnvironmentUrl,metricSelector, countMetricSelector, responseMetricSelector,graphThreshold,customName,axisTargetMin,axisTargetMax,config): self.name = name diff --git a/remoteDashboard.py b/remoteDashboard.py index 1c249c4..654169e 100644 --- a/remoteDashboard.py +++ b/remoteDashboard.py @@ -1,7 +1,9 @@ +#Library includes funtions used for interacting with Dashboard API import requests from datetime import datetime import os import json +#HTTP request definition def make_request(url, DTAPIToken,verify, method, jsondata): headers = { 'Content-Type': 'application/json', @@ -27,7 +29,7 @@ def make_request(url, DTAPIToken,verify, method, jsondata): return "An Unknown Error occurred" + repr(err) return response - +#Downloads dashboards from provided environment with given name def get_all_dashboards_withname(DTAPIToken, DTENV,name): DTAPIURL= DTENV + "api/config/v1/dashboards" r = make_request(DTAPIURL,DTAPIToken,True,"get",None) @@ -40,7 +42,7 @@ def get_all_dashboards_withname(DTAPIToken, DTENV,name): result.append(dashboard) result = sorted(result, key=lambda x : x['name'], reverse=False) return result - +#Stores current dashboard json files in archive repo def backup_dashboards(DTAPIToken, DTENV, dashboards): for dashboard in dashboards: DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"] @@ -54,13 +56,13 @@ def backup_dashboards(DTAPIToken, DTENV, dashboards): os.makedirs("./archive/"+strnowdate) with open("./archive/"+strnowdate+"/"+entityResponse["dashboardMetadata"]["name"]+"_"+strnow+".json", "w") as file: json.dump(entityResponse, file, indent=2) - +#Deletes dashboards from remote tenant def remove_dashboards(DTAPIToken, DTENV, dashboards): for dashboard in dashboards: print("Removing STAGING dashboard from Dynatrace: "+dashboard["name"]) DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"] print(make_request(DTAPIURL,DTAPIToken,True,"delete",None)) - +#Creates or updates staging dashboard on given tenant def create_or_update_dashboard(DTAPIToken, DTENV, dashboards, files, businessline, config, department): if(files): for index, filename in enumerate(files,start=1): diff --git a/repoConfig.py b/repoConfig.py index 9eda8bd..045e227 100644 --- a/repoConfig.py +++ b/repoConfig.py @@ -1,15 +1,17 @@ +#Library includes funtions for interacting with git repos from git import Repo import os +#Clones repo if not exists def clone_repo_if_notexist(repourl, reponame): if(not os.path.isdir(reponame)): repo = Repo.clone_from(repourl, reponame) return repo return Repo(reponame) - +#Performs pull on existing repo def pull_repo(repo): origin = repo.remotes.origin origin.pull() - +#Performs add, commit and push on existing repo def push_repo(repo, message): repo.git.add(all=True) repo.index.commit(message) diff --git a/sloConfigLoader.py b/sloConfigLoader.py index 008dc83..b87c24c 100644 --- a/sloConfigLoader.py +++ b/sloConfigLoader.py @@ -1,4 +1,5 @@ -from tileHelper import get_dataExplorerTileSloThreshold +#Library includes definition and function for parsing SLO yaml definition into object +from tileFactory import get_dataExplorerTileSloThreshold class Sloconfig: def __init__(self): self.hub = "" @@ -20,7 +21,7 @@ class Sloconfig: self.slourl = "" self.docurl = "" - +#Loads values from yaml config files and parses them into object def getSloConfigurations(sloconfig, scriptconfig): configs = [] for hub in sloconfig["hubs"]: diff --git a/sloHelper.py b/sloHelper.py index 6745c1d..b14d559 100644 --- a/sloHelper.py +++ b/sloHelper.py @@ -1,18 +1,21 @@ +#Library includes functions for interacting with SLOs import yaml -from remoteDashboardHelper import make_request +from remoteDashboard import make_request from KRParser import krparser from decouple import config +#Loads SLOs definitions from yaml file def load_slo_parameter(path): with open(path) as file: slo_doc = yaml.safe_load(file) return slo_doc +#Downloads SLO definition from Dynatrace API def getSLO(env, envurl,sloid, DTAPIToken): url = envurl+"/api/v2/slo/"+sloid+"?timeFrame=CURRENT" response = make_request(url, DTAPIToken,True, "get", "") responseobj = response.json() responseobj["env"] = env return responseobj - +#Generates Request Count selector for given service names def getSloReqCountSelector(service_names): selector = "" if(service_names["selectortype"] == "KR"): @@ -24,6 +27,7 @@ def getSloReqCountSelector(service_names): print("Building Service requests count selector for: "+service_names) selector = "builtin:service.requestCount.total:filter(and(or(in(\"dt.entity.service\",entitySelector(\"type(service),entityName.in("+service_names+")\"))))):splitBy()" return selector +#Generates Response Time selector for given service names def getSloReqTimeSelector(service_names): selector = "" if(service_names["selectortype"] == "KR"): @@ -31,6 +35,7 @@ def getSloReqTimeSelector(service_names): elif(service_names["selectortype"] == "SRV"): selector = "builtin:service.response.server:filter(and(or(in(\"dt.entity.service\",entitySelector(\"type(service),entityName.in("+service_names["services"]+")\"))))):splitBy()" return selector +#Parses SLO definition with KRParses and returns service name array def getSloSrvNames(sloconfig, doc): if(sloconfig.hubtype == "gcdm"): krp = krparser.KRParser(name=sloconfig.hub,options=krparser.KROption.RESOLVESERVICES, config={"threads":10, "serviceLookupParams":{"fields":"tags"}, "extendResultObjects":{"env":sloconfig.hub}}, DTAPIURL=sloconfig.remoteurl, DTAPIToken=config(doc[sloconfig.hub][3].get('gcdm-token-name'))) diff --git a/tileFactory.py b/tileFactory.py index f1227b2..0a957b7 100644 --- a/tileFactory.py +++ b/tileFactory.py @@ -1,8 +1,11 @@ +#Library includes functions for tile generation from dataExplorerTile import SingleValueTile from dataExplorerTile import GraphTile from sloHelper import getSloReqTimeSelector from sloHelper import getSloReqCountSelector from sloHelper import getSloSrvNames + +#Load coloring thresholds from strings defined in yaml file def get_dataExplorerTileSloThreshold(sloThresholdValuesAndColor): value1 = int(str(sloThresholdValuesAndColor).split("|")[0].split("_")[0]) @@ -16,6 +19,7 @@ def get_dataExplorerTileSloThreshold(sloThresholdValuesAndColor): dataExplorerTileThreshold = [ { "value": value1, "color": color1 }, { "value": value2, "color": color2 }, { "value": value3, "color": color3 } ] return dataExplorerTileThreshold +#Translate row numbers into grid values def get_bounds (grid_row, grid_column, tile_columnwidth, row_height): grid_brick = 38 grid_top = 0 if grid_row == 0 else grid_row * grid_brick @@ -24,6 +28,7 @@ def get_bounds (grid_row, grid_column, tile_columnwidth, row_height): grod_height = 0 if row_height == 0 else row_height * grid_brick bounds = { "top": grid_top, "left": grod_left, "width": grod_width, "height": grod_height } return bounds +#Generate image tile for dashboard def createImageTile(config): newimage = {} newimage["name"] = "Image" @@ -33,6 +38,7 @@ def createImageTile(config): newimage["tileFilter"] = {} newimage["image"] = config["visualconfig"]["image_data"] return newimage +#Generate markdown tile for dashboard def createMarkdownTile(row, name, markdown, column, width, height): newmarkdown = {} newmarkdown["name"] = name @@ -41,6 +47,7 @@ def createMarkdownTile(row, name, markdown, column, width, height): newmarkdown["bounds"] = get_bounds(row, column, width, height) newmarkdown["markdown"] = markdown return newmarkdown +#Generate header tile for dashboard def createHeaderTile(row, name, column, width, height): newheader= {} newheader["name"] = name @@ -48,19 +55,16 @@ def createHeaderTile(row, name, column, width, height): newheader["configured"] = "true" newheader["bounds"] = get_bounds(row, column, width, height) return newheader -def createSloDescriptionTile(index, name_short, department, config, docURL,slorelevant,slourl_EMEA,slourl_NA,slourl_CN,wall): #TODO +#Generate markdown text for SLO description tile +def createSloDescriptionTile(index, name_short, department, config, docURL,slorelevant,slourls,wall): #TODO if(not wall): markdown = "___________\n## " + name_short + "\n\n" + department + " [Documentation](" + docURL + ")" if(slorelevant): markdown = markdown + " [QM-Report] \n" else: markdown = markdown + " \n" - if(slourl_EMEA): - markdown = markdown + "[EMEA]("+slourl_EMEA+") " - if(slourl_NA): - markdown = markdown + "[NA]("+slourl_NA+") " - if(slourl_CN): - markdown = markdown + "[CN]("+slourl_CN+") " + for slourl in slourls: + markdown = markdown + "["+slourl.upper()+"]("+slourls[slourl]+") " else: markdown = "___________\n## " + name_short + "\n\n" + department if(slorelevant): @@ -68,6 +72,7 @@ def createSloDescriptionTile(index, name_short, department, config, docURL,slore else: markdown = markdown + " \n" return createMarkdownTile(index*config["visualconfig"]["rowheight"],"Markdown",markdown,0,config["visualconfig"]["description_tile_width"],config["visualconfig"]["rowheight"]) +#Generate summed header tiles for all hubs def createHeaderTiles(config,wall): tiles = [] if(wall): @@ -92,17 +97,19 @@ def createHeaderTiles(config,wall): currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]+config["visualconfig"]["spacing"] return tiles +#Create full SLO row def createSloTileRow(index, config, wall, sloconfigs,doc): tiles = [] - tiles.append(createSloDescriptionTile(index,sloconfigs[0].displayName,sloconfigs[0].department,config,sloconfigs[0].docurl,sloconfigs[0].slorelevant,"","","",wall)) + sloUrls = {} currentColumn = config["visualconfig"]["description_tile_width"] for sloconfig in sloconfigs: service_names = getSloSrvNames(sloconfig, doc) + sloUrls[sloconfig.hub] = sloconfig.slourl if sloconfig.tiles_actual and wall: - tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_1"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) + tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_1"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) tiles.append(vars(tile)) elif sloconfig.tiles_actual: - tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_1"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) + tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_1"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) tiles.append(vars(tile)) currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"] if sloconfig.tiles_graph: @@ -115,15 +122,17 @@ def createSloTileRow(index, config, wall, sloconfigs,doc): else: latency_selector = getSloReqTimeSelector(service_names) if wall: - tile = GraphTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["graph_width"],config["visualconfig"]["rowheight"]),sloconfig.remoteurl,sloconfig.metric,traffic_selector,latency_selector,sloconfig.graph_threshold,sloconfig.metric,config["visualconfig"]["graph_axis_min"],config["visualconfig"]["graph_axis_max"],config) - else: tile = GraphTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["graph_width_wall"],config["visualconfig"]["rowheight"]),sloconfig.remoteurl,sloconfig.metric,traffic_selector,latency_selector,sloconfig.graph_threshold,sloconfig.metric,config["visualconfig"]["graph_axis_min"],config["visualconfig"]["graph_axis_max"],config) + else: + tile = GraphTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["graph_width"],config["visualconfig"]["rowheight"]),sloconfig.remoteurl,sloconfig.metric,traffic_selector,latency_selector,sloconfig.graph_threshold,sloconfig.metric,config["visualconfig"]["graph_axis_min"],config["visualconfig"]["graph_axis_max"],config) tiles.append(vars(tile)) currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"] if sloconfig.tiles_ytd and wall: - tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_2"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) - tiles.append(vars(tile)) - elif sloconfig.tiles_ytd: tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_2"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) tiles.append(vars(tile)) + elif sloconfig.tiles_ytd: + tile = SingleValueTile(sloconfig.displayName,get_bounds(index*config["visualconfig"]["rowheight"],currentColumn,config["visualconfig"]["singlevalue_width"],config["visualconfig"]["rowheight"]),config["metadata"]["single_value_timeframe_2"],sloconfig.remoteurl,sloconfig.metric,sloconfig.singlevalue_threshold) + tiles.append(vars(tile)) + currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]+config["visualconfig"]["spacing"] + tiles = [createSloDescriptionTile(index,sloconfigs[0].displayName,sloconfigs[0].department,config,sloconfigs[0].docurl,sloconfigs[0].slorelevant,sloUrls,wall)] +tiles return tiles