Compare commits

...

10 Commits

Author SHA1 Message Date
Patryk Gudalewicz 52becacb1e Updating readme file 2023-07-18 14:19:44 +02:00
PATRYK GUDALEWICZ (ext.) 7748371219 Pull request #7: Dash v2
Merge in OPAPM/global_dashboard_as_code from dash_v2 to master

* commit '3be1a6d76efb709406dc6c9f7f9e7c7b65467c0f':
  OPMAAS-4530, OPMAAS-4534 Bug fixes
  OPMAAS-4530, OPMAAS-4534 Bug fixes and inline comments
  OPMAAS-4530, OPMAAS-4534 New dashboard version including config file, refactoring and custom graph expressions support
2023-07-18 11:28:29 +00:00
Patryk Gudalewicz 3be1a6d76e OPMAAS-4530, OPMAAS-4534 Bug fixes 2023-07-18 13:23:47 +02:00
Patryk Gudalewicz cab7660a8d OPMAAS-4530, OPMAAS-4534 Bug fixes and inline comments 2023-07-18 12:14:37 +02:00
Patryk Gudalewicz 272cf14224 OPMAAS-4530, OPMAAS-4534 New dashboard version including config file, refactoring and custom graph expressions support 2023-07-18 11:05:19 +02:00
Patryk Gudalewicz 23fe701e62 OPMAAS-4436, OPMAAS-4530 adding fold config to code 2023-07-05 09:51:58 +02:00
Andreas Danzer (DE-701) 0b6788bd07 createDash.py online editiert mit Bitbucket
Updated Timing
2023-06-28 06:06:45 +00:00
Patryk Gudalewicz 9d3cd124fe Fixing calls to GCDM envs 2023-06-21 16:53:10 +02:00
Patryk Gudalewicz c8347eefce Adjusting code to be able to download SLO definition from GCDM environments 2023-06-21 14:41:29 +02:00
Patryk Gudalewicz bdfaa502e7 Adjusting code to be able to download SLO definition from GCDM environments 2023-06-21 14:38:52 +02:00
12 changed files with 652 additions and 500 deletions

5
.gitignore vendored
View File

@ -1,9 +1,9 @@
dashboard_tiles_*
\[STAGING\]*
<<<<<<< HEAD
shared_configuration/
archive/
=======
tiles/
.vscode/
# Byte-compiled / optimized / DLL files
__pycache__/
@ -140,4 +140,3 @@ dmypy.json
crash.log
*.tfvars
>>>>>>> 746e496e7a7c5e8134cda7921311f6a9ba22f8d3

69
config.yaml Normal file
View File

@ -0,0 +1,69 @@
---
metadata:
basename: "Global Offboard Reliability 2.1"
stagingname: "[STAGING]Global Offboard Reliability 2.1"
owner: "PATRYK.GUDALEWICZ@partner.bmw.de"
#actual tile
single_value_header_1: "Last 1 h"
single_value_timeframe_1: "-1h"
#graph tile
graph_header: "Graph (3 days)"
graph_timeframe: "-3d"
#ytd tile
single_value_header_2: "Last 3 d"
single_value_timeframe_2: "-3d"
timeframe_ytd: "-3d"
repo:
config_repo_url: "atc.bmwgroup.net/bitbucket/scm/opapm/shared_configuration.git"
config_repo_name: "shared_configuration"
archive_repo_url: "atc.bmwgroup.net/bitbucket/scm/opapm/archive.git"
archive_repo_name: "archive"
slo_path: "./shared_configuration/slo_parameter.yaml"
#business lines are configured with scheme: DEPARTMENT PREFIX : "BUSINESS LINE NAME"
businesslines:
DE-3: "My Journey"
DE-7: "Connected Vehicle Platform"
DE-4: "My Life"
EC-DE: "China Services"
FG-6: "GCDM"
#Changing the width will cause automatic recalculation of columns position
visualconfig:
brick_size: 38
rowheight: 3
rowheight_wall: 3
singlevalue_width: 4
graph_width: 8
singlevalue_width_wall: 4
graph_width_wall: 12
description_tile_width: 7
spacing: 1
spacing_wall: 1
slo_line_color: "#9cd575"
request_count_color: "#74cff7"
response_time_color: "#c396e0"
image_width: 76
image_height: 114
image_data: "data:image/webp;base64,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"
graph_axis_min: "97"
graph_axis_max: "100.1"
hubs:
- name: "EMEA"
remote_url: "https://xxu26128.live.dynatrace.com"
remote_url_gcdm: "https://moh22956.live.dynatrace.com"
name_short: "emea"
name_env: "euprod"
- name: "NORTH AMERICA"
remote_url: "https://wgv50241.live.dynatrace.com"
remote_url_gcdm: "https://pcj77768.live.dynatrace.com"
name_short: "na"
name_env: "naprod"
- name: "CHINA"
remote_url: "https://dynatrace-cn-int.bmwgroup.com:443/e/b921f1b9-c00e-4031-b9d1-f5a0d530757b"
remote_url_gcdm: ""
name_short: "cn"
name_env: "cnprod"

View File

@ -1,526 +1,119 @@
#Main dashboard generation script
import yaml
from decouple import config
import json
import argparse
import requests
from datetime import datetime
from git import Repo
from KRParser import krparser
import os
import re
import argparse
from tileFactory import createHeaderTiles
from tileFactory import createImageTile
from tileFactory import createSloTileRow
from repoConfig import clone_repo_if_notexist
from repoConfig import pull_repo
from repoConfig import push_repo
from sloHelper import load_slo_parameter
from sloConfigLoader import getSloConfigurations
from remoteDashboard import get_all_dashboards_withname
from remoteDashboard import backup_dashboards
from remoteDashboard import create_or_update_dashboard
import warnings
warnings.filterwarnings("ignore")
#set STAGING global dashboard name
DASHBOARD_NAME = "[STAGING]Global Offboard Reliability 2.0"
AUTHSTRING = config("BITBUCKET_USERNAME")+":"+config("BITBUCKET_TOKEN")
CONFIG_REPO_URL = "https://"+AUTHSTRING+"@atc.bmwgroup.net/bitbucket/scm/opapm/shared_configuration.git"
CONFIG_REPO_NAME = "shared_configuration"
ARCHIVE_REPO_URL = "https://"+AUTHSTRING+"@atc.bmwgroup.net/bitbucket/scm/opapm/archive.git"
ARCHIVE_REPO_NAME = "archive"
KEYREQ_REPO_URL = "https://"+AUTHSTRING+"@atc.bmwgroup.net/bitbucket/scm/opapm/keyrequestparser.git"
KEYREQ_REPO_NAME = "keyrequestparser"
BUSINESS_LINES = {'DE-3':'My Journey','DE-7':'Connected Vehicle Platform','DE-4':'My Life','EC-DE':'China Services'}
#Script args definition
parser = argparse.ArgumentParser(description="Generate and deploy the Dynatrace Global Dashboard as Code. Auto deployment works only for STAGING dashboard",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("-R", "--rows", type=int, help="Number of rows per dashboard. If not specified, all rows will be added to single dashboard")
parser.add_argument('--auto-upload', default=False, action='store_true', help="Auto upload to STAGING dashboard")
parser.add_argument('-D', '--department', type=str,default="ALL", required=False, help="Define department for which the dashboard should be updated: 'DE-3', 'DE-7', 'DE-4' or 'EC-DE'. Leave empty or use 'ALL' if you want to generate 1 cumulated dashboard")
parser.add_argument('--wall', default=False, action='store_true', help="By default script is generating desktop version. Use parameter to set dashboard generation to type 'Wall'.")
args = parser.parse_args()
def clone_repo_if_notexist(repourl, reponame):
if(not os.path.isdir(reponame)):
repo = Repo.clone_from(repourl, reponame)
return repo
return Repo(reponame)
def pull_repo(repo):
origin = repo.remotes.origin
origin.pull()
def push_repo(repo, message):
repo.git.add(all=True)
repo.index.commit(message)
origin = repo.remotes.origin
origin.push()
#Loads script config file
def loadConfig():
with open('./config.yaml') as file:
return yaml.safe_load(file)
def load_slo_parameter(path):
# the first part is to read a yaml and only select latest, valid config
with open(path) as file:
slo_doc = yaml.safe_load(file)
def main():
#Load script config file
script_config = loadConfig()
return slo_doc
def make_request(url, DTAPIToken,verify, method, jsondata):
headers = {
'Content-Type': 'application/json',
'Authorization': 'Api-Token ' + DTAPIToken
}
try:
if method == "get":
response = requests.get(url, headers=headers,verify=verify)
elif method == "post":
response = requests.post(url, headers=headers,verify=verify, data=jsondata)
elif method == "put":
response = requests.put(url, headers=headers,verify=verify, data=jsondata)
elif method == "delete":
response = requests.delete(url, headers=headers,verify=verify)
response.raise_for_status()
except requests.exceptions.HTTPError as errh:
return "An Http Error occurred:" + repr(errh)
except requests.exceptions.ConnectionError as errc:
return "An Error Connecting to the API occurred:" + repr(errc)
except requests.exceptions.Timeout as errt:
return "A Timeout Error occurred:" + repr(errt)
except requests.exceptions.RequestException as err:
return "An Unknown Error occurred" + repr(err)
return response
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
def get_all_dashboards_withname(DTAPIToken, DTENV,name):
DTAPIURL= DTENV + "api/config/v1/dashboards"
r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
print(r)
entityResponse = r.json()
result = []
if("dashboards" in entityResponse):
for dashboard in entityResponse["dashboards"]:
if(dashboard["name"]).startswith(name):
result.append(dashboard)
result = sorted(result, key=lambda x : x['name'], reverse=False)
return result
def backup_dashboards(DTAPIToken, DTENV, dashboards):
for dashboard in dashboards:
DTAPIURL = DTENV + "api/config/v1/dashboards/" + dashboard["id"]
r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
entityResponse = r.json()
print("Downloaded dashboard from Dynatrace: "+entityResponse["dashboardMetadata"]["name"]+", creating backup...")
now=datetime.now()
strnow = now.strftime("%Y%m%d_%H%M%S")
strnowdate = now.strftime("%Y%m%d")
if not os.path.isdir("./archive/"+strnowdate):
os.makedirs("./archive/"+strnowdate)
with open("./archive/"+strnowdate+"/"+entityResponse["dashboardMetadata"]["name"]+"_"+strnow+".json", "w") as file:
json.dump(entityResponse, file, indent=2)
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))
def create_or_update_dashboard(DTAPIToken, DTENV, dashboards, files, businessline):
if(files):
for index, filename in enumerate(files,start=1):
with open('./'+filename) as file:
tilesjson = json.load(file)
if any(dashboard["name"].endswith("#"+str(index)) for dashboard in dashboards):
existingdashboard = next((dashboard for dashboard in dashboards if dashboard["name"].endswith("#"+str(index))), None)
if existingdashboard:
print("Found dashboard for file: "+filename + ", Name: "+ existingdashboard["name"])
DTAPIURL = DTENV + "api/config/v1/dashboards/" + existingdashboard["id"]
r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
entityResponse = r.json()
entityResponse["tiles"] = tilesjson
print("Updating dashboard: "+entityResponse["dashboardMetadata"]["name"])
print(make_request(DTAPIURL,DTAPIToken,True,"put",json.dumps(entityResponse)))
dashboards.remove(existingdashboard)
else:
print("Dashboard for file: "+filename + " not found.")
if(args.department == "ALL"):
dashfullname = DASHBOARD_NAME
else:
dashfullname = DASHBOARD_NAME + " - " + businessline + " #" + str(index)
newdashboard = {
"dashboardMetadata":{
"name": dashfullname,
"owner": "PATRYK.GUDALEWICZ@partner.bmw.de"
},
"tiles":[]
}
DTAPIURL = DTENV + "api/config/v1/dashboards"
newdashboard["tiles"] = tilesjson
print("Creating dashboard: "+newdashboard["dashboardMetadata"]["name"])
creationresult = make_request(DTAPIURL,DTAPIToken,True,"post",json.dumps(newdashboard))
print(creationresult)
remove_dashboards(DTAPIToken, DTENV, dashboards)
def get_bounds (grid_row, grid_column, tile_columnwidth, tile_rowheight):
grid_brick = 38
grid_top = 0 if grid_row == 0 else grid_row * grid_brick
grod_left = 0 if grid_column == 0 else grid_column * grid_brick
grod_width = 0 if tile_columnwidth == 0 else tile_columnwidth * grid_brick
grod_height = 0 if tile_rowheight == 0 else tile_rowheight * grid_brick
bounds = { "top": grid_top, "left": grod_left, "width": grod_width, "height": grod_height }
return bounds
def get_dataExplorerTileSloThreshold(sloThresholdValuesAndColor):
value1 = int(str(sloThresholdValuesAndColor).split("|")[0].split("_")[0])
value2 = int(str(sloThresholdValuesAndColor).split("|")[1].split("_")[0])
value3 = int(str(sloThresholdValuesAndColor).split("|")[2].split("_")[0])
color1 = str(sloThresholdValuesAndColor).split("|")[0].split("_")[1]
color2 = str(sloThresholdValuesAndColor).split("|")[1].split("_")[1]
color3 = str(sloThresholdValuesAndColor).split("|")[2].split("_")[1]
dataExplorerTileThreshold = [ { "value": value1, "color": color1 }, { "value": value2, "color": color2 }, { "value": value3, "color": color3 } ]
return dataExplorerTileThreshold
def get_DataExplorerTile_Markdown(name_short, department, bounds, detailDashboardUrl_EMEA,detailDashboardUrl_NA, detailDashboardUrl_CN, docURL, slourl_EMEA, slourl_NA, slourl_CN,slorelevant, wall ):
# dataExplorerTile_Markdown = {
# "name": "Markdown",
# "tileType": "MARKDOWN",
# "configured": "true",
# "bounds": bounds,
# "tileFilter": {},
# "markdown": "___________\n## " + name_short + "\n\n" + department + " | --> [EMEA](" + detailDashboardUrl_EMEA + ") [NA](" + detailDashboardUrl_NA + ") [CN](" + detailDashboardUrl_CN + ")\n [Documentation](" + docURL + ")"
# }
#without team links
if(not wall):
markdown = "___________\n## " + name_short + "\n\n" + department + " [Documentation](" + docURL + ")"
else:
markdown = "___________\n## " + name_short + "\n\n" + department
if(slorelevant):
markdown = markdown + " [QM-Report] \n"
else:
markdown = markdown + " \n"
if(slourl_EMEA and not wall):
markdown = markdown + "[EMEA]("+slourl_EMEA+") "
if(slourl_NA and not wall):
markdown = markdown + "[NA]("+slourl_NA+") "
if(slourl_CN and not wall):
markdown = markdown + "[CN]("+slourl_CN+") "
dataExplorerTile_Markdown = {
"name": "Markdown",
"tileType": "MARKDOWN",
"configured": "true",
"bounds": bounds,
"tileFilter": {},
"markdown": markdown
}
return dataExplorerTile_Markdown
def get_DataExplorerTile_SingleValue(customName, metricSelector, remoteEnvironmentUrl, bounds, timeframe, graphThreshold):
dataExplorerTile_SingleValue = {
"name": "",
"tileType": "DATA_EXPLORER",
"configured": "true",
"bounds": bounds,
"tileFilter": { "timeframe": timeframe },
"remoteEnvironmentUri": remoteEnvironmentUrl,
"customName": metricSelector,
"queries": [
{
"id": "A",
"timeAggregation": "DEFAULT",
"metricSelector": metricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
}
],
"visualConfig": {
"type": "SINGLE_VALUE", "global": { "seriesType": "LINE", "hideLegend": "true" },
"rules": [ { "matcher": "A:", "properties": { "color": "DEFAULT", "seriesType": "LINE", "alias": "SLO" }, "seriesOverrides": [{"name": metricSelector, "color": "#ffffff"}] } ],
"axes": { "xAxis": { "visible": "true" }, "yAxes": [] },
"heatmapSettings": {},
"singleValueSettings": { "showTrend": "false", "showSparkLine": "false", "linkTileColorToThreshold": "true" },
"thresholds": [ { "axisTarget": "LEFT", "rules": graphThreshold, "queryId": "", "visible": "true" } ],
"tableSettings": { "isThresholdBackgroundAppliedToCell": "false" },
"graphChartSettings": { "connectNulls": "false" } },
"queriesSettings": { "resolution": "" }
}
return dataExplorerTile_SingleValue
def get_DataExplorerTile_Graph(customName, metricSelector, metricName, remoteEnvironmentUrl, bounds, timeframe, axisTargetMin, axisTargetMax, graphThreshold,countMetricSelector,responseMetricSelector ):
dataExplorerTile_Graph = {
"name": "",
"tileType": "DATA_EXPLORER",
"configured": "true",
"bounds": bounds,
"tileFilter": { "timeframe": timeframe },
"remoteEnvironmentUri": remoteEnvironmentUrl,
"customName": metricSelector,
"queries": [
{
"id": "A",
"timeAggregation": "DEFAULT",
"metricSelector": metricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
},
{
"id": "B",
"timeAggregation": "DEFAULT",
"metricSelector": countMetricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
},
{
"id": "C",
"timeAggregation": "DEFAULT",
"metricSelector": responseMetricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
}
],
"visualConfig": {
"type": "GRAPH_CHART", "global": { "seriesType": "LINE", "hideLegend": "true" },
"rules": [ { "matcher": "A:", "properties": { "color": "GREEN", "seriesType": "LINE", "alias": customName }, "seriesOverrides": [{"name": customName, "color": "#9cd575"}] },
{ "matcher": "B:", "properties": { "color": "BLUE", "seriesType": "COLUMN", "alias": "Request count - server" }, "seriesOverrides": [{"name": "Request count - server", "color": "#74cff7"}] },
{ "matcher": "C:", "properties": { "color": "PURPLE", "seriesType": "LINE", "alias": "Key request response time" }, "seriesOverrides": [{"name": "Key request response time", "color": "#c396e0"}] } ],
"axes": { "xAxis": { "visible": "true" }, "yAxes": [
{ "displayName": "", "visible": "true", "min": axisTargetMin, "max": axisTargetMax, "position": "LEFT", "queryIds": [ "A" ], "defaultAxis": "true" },
{ "displayName": "", "visible": "true", "min": "AUTO", "max": "AUTO", "position": "RIGHT", "queryIds": [ "B" ], "defaultAxis": "true" },
{ "displayName": "", "visible": "true", "min": "AUTO", "max": "AUTO", "position": "LEFT", "queryIds": [ "C" ], "defaultAxis": "true" }
] },
"heatmapSettings": {},
"singleValueSettings": { "showTrend": "false", "showSparkLine": "false", "linkTileColorToThreshold": "true" },
"thresholds": [ { "axisTarget": "LEFT", "rules": graphThreshold, "queryId": "", "visible": "false" } ],
"tableSettings": { "isThresholdBackgroundAppliedToCell": "false" },
"graphChartSettings": { "connectNulls": "false" } },
"queriesSettings": { "resolution": "" }
}
return dataExplorerTile_Graph
def create_default_tiles():
newDashboardTiles = []
# HEADER TILES
# Picture Tile
newDashboardTiles.append(
{
"name": "Image", "tileType": "IMAGE", "configured": "true", "bounds": {"top": 0, "left": 0, "width": 76, "height": 114}, "tileFilter": {},
"image": "data:image/webp;base64,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"
}
)
if(args.wall):
# EMEA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 7 , 20 , 2), "tileFilter": {}, "markdown": "# EMEA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 7 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Reliability Graph (2 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 11 , 12 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 2 days" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 23 , 4 , 1), "tileFilter": {} })
# NORTH AMERICA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 28 , 20 , 2), "tileFilter": {}, "markdown": "# NORTH AMERICA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 28 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Reliability Graph (2 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 32 , 12 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 2 days" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 44 , 4 , 1), "tileFilter": {} })
# CHINA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 49 , 20 , 2), "tileFilter": {}, "markdown": "# CHINA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 49 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Reliability Graph (2 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 53 , 12 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 2 days" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 65 , 4 , 1), "tileFilter": {} })
else:
# EMEA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 7 , 14 , 2), "tileFilter": {}, "markdown": "# EMEA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 7 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Reliability Graph (2 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 11 , 6 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 2 days" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 17 , 4 , 1), "tileFilter": {} })
# NORTH AMERICA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 22 , 14 , 2), "tileFilter": {}, "markdown": "# NORTH AMERICA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 22 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Reliability Graph (2 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 26 , 6 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 2 days" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 32 , 4 , 1), "tileFilter": {} })
# CHINA HUB
newDashboardTiles.append({ "name": "Header" ,"tileType": "MARKDOWN" , "configured": "true" , "bounds": get_bounds(0 , 37 , 14 , 2), "tileFilter": {}, "markdown": "# CHINA" })
newDashboardTiles.append({ "name": "Last 1 h" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 37 , 4 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Reliability Graph (2 days)" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 41 , 6 , 1), "tileFilter": {} })
newDashboardTiles.append({ "name": "Last 2 days" ,"tileType": "HEADER" , "configured": "true" , "bounds": get_bounds(2 , 47 , 4 , 1), "tileFilter": {} })
return newDashboardTiles
def getSloReqCountSelector(service_names):
selector = ""
if(service_names["selectortype"] == "KR"):
service_names = service_names["services"]
print("Building Keyrequest count selector for: "+service_names)
selector = "builtin:service.keyRequest.count.total:filter(and(or(in(\"dt.entity.service_method\",entitySelector(\"type(service_method), fromRelationship.isServiceMethodOfService( type(~\"SERVICE~\"),entityName.in("+service_names+"))\"))))):splitBy()"
elif(service_names["selectortype"] == "SRV"):
service_names = service_names["services"]
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
def getSloReqTimeSelector(service_names):
selector = ""
if(service_names["selectortype"] == "KR"):
selector = "builtin:service.keyRequest.response.server:filter(and(or(in(\"dt.entity.service_method\",entitySelector(\"type(service_method), fromRelationship.isServiceMethodOfService( type(~\"SERVICE~\"),entityName.in("+service_names["services"]+"))\"))))):splitBy()"
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
def getSloSrvNames(hub_config, configuration, doc, env):
hub = ""
namestr = ""
if env=="euprod":
hub = "emea"
elif env=="naprod":
hub = "na"
elif env=="cnprod":
hub = "cn"
krp = krparser.KRParser(name=env,options=krparser.KROption.RESOLVESERVICES, config={"threads":10, "serviceLookupParams":{"fields":"tags"}, "extendResultObjects":{"env":env}}, DTAPIURL=hub_config[env]["remote_url"], DTAPIToken=config(doc[env][2].get('env-token-name')))
sloobj = getSLO(hub,hub_config[env]["remote_url"],configuration["ids"][hub],config(doc[env][2].get('env-token-name')))
krs = krp.parse(sloobj)
slosrvnames = []
outputslos = []
selectortype = ""
for krslo in krs:
if("builtin:service.keyRequest" in krslo.metadata["metricExpression"]):
selectortype = "KR"
elif("builtin:service.keyRequest" not in krslo.metadata["metricExpression"]):
selectortype = "SRV"
for kr in krslo.keyRequests:
for srv in kr["services"]:
slosrvnames.append(srv["displayName"])
slosrvnames = list(dict.fromkeys(slosrvnames))
for srv in slosrvnames:
outputslos.append("~\""+srv+"~\"")
return {"selectortype":selectortype, "services":",".join(outputslos)}
def main(slo_path):
configrepo = clone_repo_if_notexist(CONFIG_REPO_URL, CONFIG_REPO_NAME)
#Pull shared repositories
configrepo = clone_repo_if_notexist(script_config["repo"]["config_repo_url"], script_config["repo"]["config_repo_name"])
pull_repo(configrepo)
archiverepo = clone_repo_if_notexist(ARCHIVE_REPO_URL, ARCHIVE_REPO_NAME)
archiverepo = clone_repo_if_notexist(script_config["repo"]["archive_repo_url"], script_config["repo"]["archive_repo_name"])
pull_repo(archiverepo)
keyreqrepo = clone_repo_if_notexist(KEYREQ_REPO_URL, KEYREQ_REPO_NAME)
pull_repo(keyreqrepo)
print("Generating dashboard tiles...")
#Load env file
with open('./environment.yaml') as file:
doc = yaml.safe_load(file)
slo_doc = load_slo_parameter(slo_path)
dashboard_json = create_default_tiles()
if(args.wall):
offsets = [0,21,42]
else:
offsets = [0,15,30]
hub_config = {
"euprod":
{
"offset": offsets[0],
"remote_url": 'https://xxu26128.live.dynatrace.com',
"remote_url_gcdm": 'https://moh22956.live.dynatrace.com'
},
"naprod":
{
"offset": offsets[1],
"remote_url": 'https://wgv50241.live.dynatrace.com',
"remote_url_gcdm": 'https://pcj77768.live.dynatrace.com'
},
"cnprod":
{
"offset": offsets[2],
"remote_url": 'https://dynatrace-cn-int.bmwgroup.com:443/e/b921f1b9-c00e-4031-b9d1-f5a0d530757b'
}
}
#Load SLO yaml file
slo_doc = load_slo_parameter(script_config["repo"]["slo_path"])
#Create empty dashboards and fill with default headers
dashboard_json = []
dashboard_json.append(createImageTile(script_config))
dashboard_json = dashboard_json + createHeaderTiles(script_config,args.wall)
timeframe_actual = "-1h"
timeframe_graph = "-2d"
print("Generating dashboard tiles...")
dahboardcount = 1
#Configure counters for SLO loop
dahboardcount = boundindex = 1
rowcount = 0
boundindex = 1
generatedfiles = []
if(args.rows is not None):
rowcount = args.rows
if(args.department == "ALL"):
blname = "ALL"
else:
blname = BUSINESS_LINES[args.department]
blvalue = args.department
blname = script_config["businesslines"][args.department]
slorelevant = False
if(blname and blvalue):
for slo_name, configuration in slo_doc.items():
if configuration['department'].startswith(blvalue) or blvalue == "ALL":
if(configuration['selector_var'] == "CoCo-QM-Report_Mobile"):
blvalue = args.department
#SLO loop
for slo_name, configuration in slo_doc.items():
if configuration['department'].startswith(blvalue) or blvalue == "ALL":
if(configuration['selector_var'] == "CoCo-QM-Report_Mobile"):
slorelevant = True
print("Dashboard #"+str(dahboardcount)+" : Configurint SLO "+slo_name)
if rowcount > 0 and boundindex > rowcount:
dashboard_json = create_default_tiles()
dahboardcount = dahboardcount+1
boundindex = 1
slo_display = configuration["displayname"]
slo_department = configuration["department"]
#timeframe_ytd = configuration["yearstart"] + " 00:00 to now"
timeframe_ytd = "-2d"
slo_graphThreshold_SingleValue = get_dataExplorerTileSloThreshold(configuration["thresholds"]["single_value"])
slo_graphThreshold_Graph = get_dataExplorerTileSloThreshold(configuration["thresholds"]["graph_value"])
emeaslourl = ""
naslourl = ""
cnslourl = ""
if len(configuration["hubs"]) > 0:
if(configuration["ids"]["emea"]):
emeaslourl = hub_config["euprod"]["remote_url"] + "/ui/slo?id="+configuration["ids"]["emea"]+"&sloexp="+configuration["ids"]["emea"]+"&slovis=Table"
if(configuration["ids"]["na"]):
naslourl = hub_config["naprod"]["remote_url"] + "/ui/slo?id="+configuration["ids"]["na"]+"&sloexp="+configuration["ids"]["na"]+"&slovis=Table"
if(configuration["ids"]["cn"]):
cnslourl = hub_config["cnprod"]["remote_url"] + "/ui/slo?id="+configuration["ids"]["cn"]+"&sloexp="+configuration["ids"]["cn"]+"&slovis=Table"
dashboard_json.append(get_DataExplorerTile_Markdown(slo_display, slo_department, get_bounds(((boundindex)*(3)) , 0 , 7 , 3), configuration["ops_dashboard"]["emea"], configuration["ops_dashboard"]["na"], configuration["ops_dashboard"]["cn"],configuration["doc_url"],emeaslourl,naslourl,cnslourl,slorelevant,args.wall))
for hub,tiles in configuration["hubs"].items():
if(configuration["hubs"][hub]["type"] == "gcdm"):
remoteurl = hub_config[hub]["remote_url_gcdm"]
else:
remoteurl = hub_config[hub]["remote_url"]
if 'actual' in tiles["tiles"]:
dashboard_json.append(get_DataExplorerTile_SingleValue(slo_name, configuration["metric"], remoteurl, get_bounds(((boundindex)*(3)) , 7 + hub_config[hub]["offset"] , 4 , 3), timeframe_actual, slo_graphThreshold_SingleValue))
if "graph" in tiles["tiles"]:
if(args.wall):
dashboard_json.append(get_DataExplorerTile_Graph(slo_name, configuration["metric"], configuration["selector_var"].replace("~",""), remoteurl, get_bounds(((boundindex)*(3)) , 11 + hub_config[hub]["offset"] , 12 , 3), timeframe_graph, "97", "100.1", slo_graphThreshold_Graph, getSloReqCountSelector(getSloSrvNames(hub_config, configuration, doc, hub)),getSloReqTimeSelector(getSloSrvNames(hub_config, configuration, doc, hub))))
else:
dashboard_json.append(get_DataExplorerTile_Graph(slo_name, configuration["metric"], configuration["selector_var"].replace("~",""), remoteurl, get_bounds(((boundindex)*(3)) , 11 + hub_config[hub]["offset"] , 6 , 3), timeframe_graph, "97", "100.1", slo_graphThreshold_Graph, getSloReqCountSelector(getSloSrvNames(hub_config, configuration, doc, hub)),getSloReqTimeSelector(getSloSrvNames(hub_config, configuration, doc, hub))))
if "ytd" in tiles["tiles"]:
if(args.wall):
dashboard_json.append(get_DataExplorerTile_SingleValue(slo_name, configuration["metric"], remoteurl, get_bounds(((boundindex)*(3)) , 23 + hub_config[hub]["offset"] , 4 , 3), timeframe_ytd, slo_graphThreshold_SingleValue))
else:
dashboard_json.append(get_DataExplorerTile_SingleValue(slo_name, configuration["metric"], remoteurl, get_bounds(((boundindex)*(3)) , 17 + hub_config[hub]["offset"] , 4 , 3), timeframe_ytd, slo_graphThreshold_SingleValue))
boundindex = boundindex+1
if rowcount > 0 and boundindex > rowcount:
with open("dashboard_tiles_"+str(dahboardcount)+".json", "w") as file:
json.dump(dashboard_json, file, indent=2)
generatedfiles.append("dashboard_tiles_"+str(dahboardcount)+".json")
if rowcount == 0 or (args.rows is not None and boundindex%args.rows != 0):
with open("dashboard_tiles_"+str(dahboardcount)+".json", "w") as file:
json.dump(dashboard_json, file, indent=2)
generatedfiles.append("dashboard_tiles_"+str(dahboardcount)+".json")
if args.auto_upload:
print("Getting existing STAGING dashboards from Dynatrace")
for item, doc in doc.items():
if(item == "globaldashboard"):
token = dict(doc[2])
url = dict(doc[1])
print("Crawling through: " + item)
print("Gather data, hold on a minute")
DTTOKEN = config(token.get('env-token-name'))
DTURL = url.get('env-url')
print("Downloading STAGING dashboards to local repo ("+blname+")...")
existingdashboards = get_all_dashboards_withname(DTTOKEN, DTURL,DASHBOARD_NAME +blname)
print("Uploading STAGING dashboards to Dynatrace ("+blname+")...")
backup_dashboards(DTTOKEN, DTURL, existingdashboards)
now=datetime.now()
strnowdate = now.strftime("%Y%m%d")
push_repo(archiverepo, strnowdate+"_Global dashboard as code auto-upload backup")
create_or_update_dashboard(DTTOKEN, DTURL, existingdashboards, generatedfiles, blname)
else:
print("ERROR: Could not find Business line for given department.")
print("Dashboard #"+str(dahboardcount)+" : Configurint SLO "+slo_name)
#When row splitting is configured, generate default tiles for each partial dashboard
if rowcount > 0 and boundindex > rowcount:
dashboard_json = []
dahboardcount = dahboardcount+1
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)
generatedfiles.append("dashboard_tiles_"+str(dahboardcount)+".json")
#Upload staging dashboard if auto upload is specified
if args.auto_upload:
print("Getting existing STAGING dashboards from Dynatrace")
for item, doc in doc.items():
if(item == "globaldashboard"):
token = dict(doc[2])
url = dict(doc[1])
print("Crawling through: " + item)
print("Gather data, hold on a minute")
DTTOKEN = config(token.get('env-token-name'))
DTURL = url.get('env-url')
print("Downloading STAGING dashboards to local repo ("+blname+")...")
existingdashboards = get_all_dashboards_withname(DTTOKEN, DTURL,script_config["metadata"]["stagingname"]+" - " +blname)
print("Uploading STAGING dashboards to Dynatrace ("+blname+")...")
backup_dashboards(DTTOKEN, DTURL, existingdashboards)
now=datetime.now()
strnowdate = now.strftime("%Y%m%d")
push_repo(archiverepo, strnowdate+"_Global dashboard as code auto-upload backup")
create_or_update_dashboard(DTTOKEN, DTURL, existingdashboards, generatedfiles, blname,script_config,args.department)
print("DONE")
if __name__ == "__main__":
main('./shared_configuration/slo_parameter.yaml')
main()

86
dataExplorerTile.py Normal file
View File

@ -0,0 +1,86 @@
#Single value tile definition (for json parse purpose)
class SingleValueTile:
def __init__(self,name,bounds,timeframe,remoteEnvironmentUrl,metricSelector,graphThreshold):
self.name = name
self.tileType = "DATA_EXPLORER"
self.configured = "true"
self.bounds = bounds
self.tileFilter = { "timeframe": timeframe }
self.remoteEnvironmentUri = remoteEnvironmentUrl
self.customName = metricSelector
self.queries = [
{
"id": "A",
"timeAggregation": "DEFAULT",
"metricSelector": metricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
}
]
self.visualConfig = {
"type": "SINGLE_VALUE", "global": { "seriesType": "LINE", "hideLegend": "true" },
"rules": [ { "matcher": "A:", "properties": { "color": "DEFAULT", "seriesType": "LINE", "alias": "SLO" }, "seriesOverrides": [{"name": metricSelector, "color": "#ffffff"}] } ],
"axes": { "xAxis": { "visible": "true" }, "yAxes": [] },
"heatmapSettings": {},
"singleValueSettings": { "showTrend": "false", "showSparkLine": "false", "linkTileColorToThreshold": "true" },
"thresholds": [ { "axisTarget": "LEFT", "rules": graphThreshold, "queryId": "", "visible": "true" } ],
"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
self.tileType = "DATA_EXPLORER"
self.configured = "true"
self.bounds = bounds
self.tileFilter = { "timeframe": config["metadata"]["graph_timeframe"] }
self.remoteEnvironmentUri = remoteEnvironmentUrl
self.customName = metricSelector
self.queries = [
{
"id": "A",
"timeAggregation": "DEFAULT",
"metricSelector": metricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
},
{
"id": "B",
"timeAggregation": "DEFAULT",
"metricSelector": countMetricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
},
{
"id": "C",
"timeAggregation": "DEFAULT",
"metricSelector": responseMetricSelector,
"foldTransformation": "TOTAL",
"enabled": "true"
}
]
self.visualConfig = {
"type": "GRAPH_CHART", "global": { "seriesType": "LINE", "hideLegend": "true" },
"rules": [ { "matcher": "A:", "properties": { "color": "GREEN", "seriesType": "LINE", "alias": customName }, "seriesOverrides": [{"name": customName, "color": config["visualconfig"]["slo_line_color"]}] },
{ "matcher": "B:", "properties": { "color": "BLUE", "seriesType": "COLUMN", "alias": "Request count - server" }, "seriesOverrides": [{"name": "Request count - server", "color": config["visualconfig"]["request_count_color"]}] },
{ "matcher": "C:", "properties": { "color": "PURPLE", "seriesType": "LINE", "alias": "Key request response time" }, "seriesOverrides": [{"name": "Key request response time", "color": config["visualconfig"]["response_time_color"]}] } ],
"axes": { "xAxis": { "visible": "true" }, "yAxes": [
{ "displayName": "", "visible": "true", "min": axisTargetMin, "max": axisTargetMax, "position": "LEFT", "queryIds": [ "A" ], "defaultAxis": "true" },
{ "displayName": "", "visible": "true", "min": "AUTO", "max": "AUTO", "position": "RIGHT", "queryIds": [ "B" ], "defaultAxis": "true" },
{ "displayName": "", "visible": "true", "min": "AUTO", "max": "AUTO", "position": "LEFT", "queryIds": [ "C" ], "defaultAxis": "true" }
] },
"heatmapSettings": {},
"singleValueSettings": { "showTrend": "false", "showSparkLine": "false", "linkTileColorToThreshold": "true" },
"thresholds": [ { "axisTarget": "LEFT", "rules": graphThreshold, "queryId": "", "visible": "false" } ],
"tableSettings": { "isThresholdBackgroundAppliedToCell": "false" },
"graphChartSettings": { "connectNulls": "false" } }
self.queriesSettings = { "resolution": "" }

View File

@ -2,15 +2,16 @@ globaldashboard:
- name: "globaldashboard"
- env-url: "https://jyy23483.live.dynatrace.com/"
- env-token-name: "GLOBAL_CONFIG_TOKEN"
euprod:
- name: "euprod"
- env-url: "https://xxu26128.live.dynatrace.com"
- env-token-name: "EUPROD_TOKEN"
- gcdm-token-name: "EUPROD_GCDM_TOKEN"
naprod:
- name: "naprod"
- env-url: "https://wgv50241.live.dynatrace.com"
- env-token-name: "NAPROD_TOKEN"
- gcdm-token-name: "NAPROD_GCDM_TOKEN"
cnprod:
- name: "cnprod"
- env-url: "https://dynatrace-cn-int.bmwgroup.com:443/e/b921f1b9-c00e-4031-b9d1-f5a0d530757b"

@ -1 +0,0 @@
Subproject commit 6e39e26582e8b3f89b7c135d08b1d4fba4c08244

View File

@ -68,6 +68,26 @@ File containing environments to execute --auto-upload
name: string #name ov environment
env-url: str #url of environment
env-token-name: str #name of environment variable containing API token
## config.yaml
File containing script configuration, like visual config, metadata etc.
## dataExplorerTile.py
Library containing tile object definition for JSON parse purposes
## remoteDashboard.py
Library containing functions used to interact with Dynatrace Dashboard API
## repoConfig.py
Library containing functions used to interact with git repo
## sloConfigLoader.py
Library containing functions used to parse SLO yaml definition into object
## sloHelper.py
Library containing functions used to parse interact with SLO definitions
## tileFactory.py
Library containing functions used to generate tiles JSON definitions
## requirements.txt

103
remoteDashboard.py Normal file
View File

@ -0,0 +1,103 @@
#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',
'Authorization': 'Api-Token ' + DTAPIToken
}
try:
if method == "get":
response = requests.get(url, headers=headers,verify=verify)
elif method == "post":
response = requests.post(url, headers=headers,verify=verify, data=jsondata)
elif method == "put":
response = requests.put(url, headers=headers,verify=verify, data=jsondata)
elif method == "delete":
response = requests.delete(url, headers=headers,verify=verify)
response.raise_for_status()
except requests.exceptions.HTTPError as errh:
return "An Http Error occurred:" + repr(errh)
except requests.exceptions.ConnectionError as errc:
return "An Error Connecting to the API occurred:" + repr(errc)
except requests.exceptions.Timeout as errt:
return "A Timeout Error occurred:" + repr(errt)
except requests.exceptions.RequestException as err:
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)
print(r)
entityResponse = r.json()
result = []
if("dashboards" in entityResponse):
for dashboard in entityResponse["dashboards"]:
if(dashboard["name"]).startswith(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"]
r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
entityResponse = r.json()
print("Downloaded dashboard from Dynatrace: "+entityResponse["dashboardMetadata"]["name"]+", creating backup...")
now=datetime.now()
strnow = now.strftime("%Y%m%d_%H%M%S")
strnowdate = now.strftime("%Y%m%d")
if not os.path.isdir("./archive/"+strnowdate):
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):
with open('./tiles/'+filename) as file:
tilesjson = json.load(file)
if any(dashboard["name"].endswith("#"+str(index)) for dashboard in dashboards):
existingdashboard = next((dashboard for dashboard in dashboards if dashboard["name"].endswith("#"+str(index))), None)
if existingdashboard:
print("Found dashboard for file: "+filename + ", Name: "+ existingdashboard["name"])
DTAPIURL = DTENV + "api/config/v1/dashboards/" + existingdashboard["id"]
r = make_request(DTAPIURL,DTAPIToken,True,"get",None)
entityResponse = r.json()
entityResponse["tiles"] = tilesjson
print("Updating dashboard: "+entityResponse["dashboardMetadata"]["name"])
print(make_request(DTAPIURL,DTAPIToken,True,"put",json.dumps(entityResponse)))
dashboards.remove(existingdashboard)
else:
print("Dashboard for file: "+filename + " not found.")
if(department == "ALL"):
dashfullname = config["metadata"]["stagingname"]
else:
dashfullname = config["metadata"]["stagingname"] + " - " + businessline + " #" + str(index)
newdashboard = {
"dashboardMetadata":{
"name": dashfullname,
"owner": config["metadata"]["owner"]
},
"tiles":[]
}
DTAPIURL = DTENV + "api/config/v1/dashboards"
newdashboard["tiles"] = tilesjson
print("Creating dashboard: "+newdashboard["dashboardMetadata"]["name"])
creationresult = make_request(DTAPIURL,DTAPIToken,True,"post",json.dumps(newdashboard))
print(creationresult)
remove_dashboards(DTAPIToken, DTENV, dashboards)

19
repoConfig.py Normal file
View File

@ -0,0 +1,19 @@
#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)
origin = repo.remotes.origin
origin.push()

62
sloConfigLoader.py Normal file
View File

@ -0,0 +1,62 @@
#Library includes definition and function for parsing SLO yaml definition into object
from tileFactory import get_dataExplorerTileSloThreshold
class Sloconfig:
def __init__(self):
self.hub = ""
self.metric = ""
self.custom_traffic = ""
self.custom_latency = ""
self.selector = ""
self.sloid = ""
self.slorelevant = False
self.graph_threshold = ""
self.singlevalue_threshold = ""
self.displayName = ""
self.department = ""
self.remoteurl = ""
self.hubtype = ""
self.tiles_actual = False
self.tiles_graph = False
self.tiles_ytd = False
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"]:
conf = Sloconfig()
conf.hub = hub
conf.metric = sloconfig["metric"]
if "custom_latency_metric" in sloconfig:
conf.custom_latency = sloconfig["custom_latency_metric"]
if "custom_traffic_metric" in sloconfig:
conf.custom_traffic = sloconfig["custom_traffic_metric"]
conf.selector = sloconfig["selector_var"].replace("~","")
conf.singlevalue_threshold = get_dataExplorerTileSloThreshold(sloconfig["thresholds"]["single_value"])
conf.graph_threshold = get_dataExplorerTileSloThreshold(sloconfig["thresholds"]["graph_value"])
conf.displayName = sloconfig["displayname"]
conf.department = sloconfig["department"]
conf.docurl = sloconfig["doc_url"]
for tile in sloconfig["hubs"][hub]["tiles"]:
if tile == "actual":
conf.tiles_actual =True
elif tile == "graph":
conf.tiles_graph = True
elif tile == "ytd":
conf.tiles_ytd = True
for env in scriptconfig["hubs"]:
if env["name_env"] == hub:
conf.sloid = sloconfig["ids"][env["name_short"]]
if sloconfig["hubs"][hub]["type"] == "coco" :
conf.remoteurl = env["remote_url"]
conf.hubtype = "coco"
elif sloconfig["hubs"][hub]["type"] == "gcdm":
conf.remoteurl = env["remote_url_gcdm"]
conf.hubtype = "gcdm"
conf.slourl = conf.remoteurl + "/ui/slo?id="+conf.sloid+"&sloexp="+conf.sloid+"&slovis=Table"
if conf.selector.startswith("CoCo-QM-Report_"):
conf.slorelevant = True
configs.append(conf)
return configs

63
sloHelper.py Normal file
View File

@ -0,0 +1,63 @@
#Library includes functions for interacting with SLOs
import yaml
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"):
service_names = service_names["services"]
print("Building Keyrequest count selector for: "+service_names)
selector = "builtin:service.keyRequest.count.total:filter(and(or(in(\"dt.entity.service_method\",entitySelector(\"type(service_method), fromRelationship.isServiceMethodOfService( type(~\"SERVICE~\"),entityName.in("+service_names+"))\"))))):splitBy()"
elif(service_names["selectortype"] == "SRV"):
service_names = service_names["services"]
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"):
selector = "builtin:service.keyRequest.response.server:filter(and(or(in(\"dt.entity.service_method\",entitySelector(\"type(service_method), fromRelationship.isServiceMethodOfService( type(~\"SERVICE~\"),entityName.in("+service_names["services"]+"))\"))))):splitBy()"
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')))
sloobj = getSLO(sloconfig.hub,sloconfig.remoteurl,sloconfig.sloid,config(doc[sloconfig.hub][3].get('gcdm-token-name')))
else:
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][2].get('env-token-name')))
sloobj = getSLO(sloconfig.hub,sloconfig.remoteurl,sloconfig.sloid,config(doc[sloconfig.hub][2].get('env-token-name')))
krs = krp.parse(sloobj)
slosrvnames = []
outputslos = []
selectortype = ""
for krslo in krs:
if("builtin:service.keyRequest" in krslo.metadata["metricExpression"]):
selectortype = "KR"
elif("builtin:service.keyRequest" not in krslo.metadata["metricExpression"]):
selectortype = "SRV"
for group in krslo.matchedGroups._list:
for srv in group["services"]:
slosrvnames.append(srv)
slosrvnames = list(dict.fromkeys(slosrvnames))
if(sloobj["name"] == "TP_Vehicle_Teleservices"):
print(sloobj["name"])
for srv in slosrvnames:
outputslos.append("~\""+srv+"~\"")
return {"selectortype":selectortype, "services":",".join(outputslos)}

138
tileFactory.py Normal file
View File

@ -0,0 +1,138 @@
#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])
value2 = int(str(sloThresholdValuesAndColor).split("|")[1].split("_")[0])
value3 = int(str(sloThresholdValuesAndColor).split("|")[2].split("_")[0])
color1 = str(sloThresholdValuesAndColor).split("|")[0].split("_")[1]
color2 = str(sloThresholdValuesAndColor).split("|")[1].split("_")[1]
color3 = str(sloThresholdValuesAndColor).split("|")[2].split("_")[1]
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
grod_left = 0 if grid_column == 0 else grid_column * grid_brick
grod_width = 0 if tile_columnwidth == 0 else tile_columnwidth * grid_brick
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"
newimage["tileType"] = "IMAGE"
newimage["configured"] = "true"
newimage["bounds"] = {"top": 0,"left": 0, "width":config["visualconfig"]["image_width"],"height":config["visualconfig"]["image_height"]}
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
newmarkdown["tileType"] = "MARKDOWN"
newmarkdown["configured"] = "true"
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
newheader["tileType"] = "HEADER"
newheader["configured"] = "true"
newheader["bounds"] = get_bounds(row, column, width, height)
return newheader
#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"
for slourl in slourls:
markdown = markdown + "["+slourl.upper()+"]("+slourls[slourl]+") "
else:
markdown = "___________\n## " + name_short + "\n\n" + department
if(slorelevant):
markdown = markdown + " [QM-Report] \n"
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):
currentColumn = config["visualconfig"]["description_tile_width"]
for hub in config["hubs"]:
tiles.append(createMarkdownTile(0,"Header","# "+hub["name"]+"",currentColumn,2*config["visualconfig"]["singlevalue_width_wall"]+config["visualconfig"]["graph_width_wall"],2))
tiles.append(createHeaderTile(2,config["metadata"]["single_value_header_1"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],1))
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]
tiles.append(createHeaderTile(2,config["metadata"]["graph_header"],currentColumn,config["visualconfig"]["graph_width_wall"],1))
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]
tiles.append(createHeaderTile(2,config["metadata"]["single_value_header_2"],currentColumn,config["visualconfig"]["singlevalue_width_wall"],1))
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]+config["visualconfig"]["spacing"]
else:
currentColumn = config["visualconfig"]["description_tile_width"]
for hub in config["hubs"]:
tiles.append(createMarkdownTile(0,"Header","# "+hub["name"]+"",currentColumn,2*config["visualconfig"]["singlevalue_width"]+config["visualconfig"]["graph_width"],2))
tiles.append(createHeaderTile(2,config["metadata"]["single_value_header_1"],currentColumn,config["visualconfig"]["singlevalue_width"],1))
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]
tiles.append(createHeaderTile(2,config["metadata"]["graph_header"],currentColumn,config["visualconfig"]["graph_width"],1))
currentColumn = tiles[len(tiles)-1]["bounds"]["left"]/config["visualconfig"]["brick_size"] + tiles[len(tiles)-1]["bounds"]["width"]/config["visualconfig"]["brick_size"]
tiles.append(createHeaderTile(2,config["metadata"]["single_value_header_2"],currentColumn,config["visualconfig"]["singlevalue_width"],1))
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 = []
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_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"],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:
if(sloconfig.custom_traffic):
traffic_selector = sloconfig.custom_traffic
else:
traffic_selector = getSloReqCountSelector(service_names)
if(sloconfig.custom_latency):
latency_selector = sloconfig.custom_latency
else:
latency_selector = getSloReqTimeSelector(service_names)
if wall:
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_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