Skip to content

Commit

Permalink
Added: Support for multiple multiple folders, python client
Browse files Browse the repository at this point in the history
Modified: Internal logic for creating/updating dashboards
Deleted: Removed old logic as it is not flexible for automation
  • Loading branch information
Vishnu Challa committed Sep 16, 2023
1 parent c6d4989 commit 8a4f4a6
Show file tree
Hide file tree
Showing 25 changed files with 5,251 additions and 57 deletions.
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM ubuntu

WORKDIR /performance-dashboards
ARG DEBIAN_FRONTEND=noninteractive

# Install necessary libraries for subsequent commands
RUN apt-get update && apt-get install -y podman dumb-init python3.6 python3-distutils python3-pip python3-apt

COPY . .
RUN chmod -R 775 /performance-dashboards

# Install dependencies
RUN python3 -m pip install --upgrade pip
RUN pip install -r requirements.txt

# Cleanup the installation remainings
RUN apt-get clean autoclean && \
apt-get autoremove --yes && \
rm -rf /var/lib/{apt,dpkg,cache,log}/

# Start the command
ENTRYPOINT ["/usr/bin/dumb-init", "--"]
CMD ["python3", "dittybopper/syncer/entrypoint.py"]
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ SYNCER_IMG_TAG ?= quay.io/cloud-bulldozer/dittybopper-syncer:latest
PLATFORM = linux/amd64,linux/arm64,linux/ppc64le,linux/s390x

# Get all templates at $(TEMPLATESDIR)
TEMPLATES = $(wildcard $(TEMPLATESDIR)/*.jsonnet)
TEMPLATES := $(wildcard $(TEMPLATESDIR)/**/*.jsonnet)

# Replace $(TEMPLATESDIR)/*.jsonnet by $(OUTPUTDIR)/*.json
outputs = $(patsubst $(TEMPLATESDIR)/%.jsonnet, $(OUTPUTDIR)/%.json, $(TEMPLATES))
outputs := $(patsubst $(TEMPLATESDIR)/%.jsonnet, $(OUTPUTDIR)/%.json, $(TEMPLATES))

all: deps format build

Expand Down Expand Up @@ -38,10 +38,11 @@ $(BINDIR)/jsonnet:
# Build each template and output to $(OUTPUTDIR)
$(OUTPUTDIR)/%.json: $(TEMPLATESDIR)/%.jsonnet
@echo "Building template $<"
mkdir -p $(dir $@)
$(BINDIR)/jsonnet $< > $@

build-syncer-image: build
podman build --platform=${PLATFORM} -f dittybopper/syncer/Dockerfile --manifest=${SYNCER_IMG_TAG} .
podman build --platform=${PLATFORM} -f Dockerfile --manifest=${SYNCER_IMG_TAG} .

push-syncer-image:
podman manifest push ${SYNCER_IMG_TAG} ${SYNCER_IMG_TAG}
podman manifest push ${SYNCER_IMG_TAG} ${SYNCER_IMG_TAG}
4 changes: 2 additions & 2 deletions dittybopper/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ If using disconnected, you need to sync the cloud-bulldozer grafana image (shown
dittybopper/templates/dittybopper.yaml.template file) and your chosen syncer image
(defaults to quay.io/cloud-bulldozer/dittybopper-syncer:latest).

The syncer image is built with the context at the root of the repository, and the image in the dittybopper/syncer directory.
The syncer image is built with the context at the root of the repository, and the image in the root directory.
You can build it with `make build-syncer-image SYNCER_IMG_TAG=container.registry.org/organization/syncer:latest`
Alternatively, you can run the following command form the root folder of this repository: `podman build -f dittybopper/syncer/Dockerfile -t=container.registry.org/organization/syncer:latest .`
Alternatively, you can run the following command from the root folder of this repository: `podman build -f Dockerfile -t=container.registry.org/organization/syncer:latest .`

## Contribute

Expand Down
4 changes: 2 additions & 2 deletions dittybopper/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ END

export PROMETHEUS_USER=internal
export GRAFANA_ADMIN_PASSWORD=admin
export DASHBOARDS="ocp-performance.json api-performance-overview.json etcd-on-cluster-dashboard.json hypershift-performance.json ovn-dashboard.json"
export SYNCER_IMAGE=${SYNCER_IMAGE:-"quay.io/cloud-bulldozer/dittybopper-syncer:latest"} # Syncer image
export GRAFANA_URL="http://admin:${GRAFANA_ADMIN_PASSWORD}@localhost:3000"
export SYNCER_IMAGE=${SYNCER_IMAGE:-"quay.io/vchalla/syncer:latest"} # Syncer image
export GRAFANA_IMAGE=${GRAFANA_IMAGE:-"quay.io/cloud-bulldozer/grafana:9.4.3"} # Syncer image

# Set defaults for command options
Expand Down
1 change: 1 addition & 0 deletions dittybopper/k8s-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ END

export PROMETHEUS_USER=internal
export GRAFANA_ADMIN_PASSWORD=admin
export GRAFANA_URL="http://admin:${GRAFANA_ADMIN_PASSWORD}@localhost:3000"
export DASHBOARDS="k8s-performance.json"
export SYNCER_IMAGE=${SYNCER_IMAGE:-"quay.io/cloud-bulldozer/dittybopper-syncer:latest"} # Syncer image
export GRAFANA_IMAGE=${GRAFANA_IMAGE:-"quay.io/cloud-bulldozer/grafana:9.4.3"} # Syncer image
Expand Down
7 changes: 0 additions & 7 deletions dittybopper/syncer/Dockerfile

This file was deleted.

129 changes: 129 additions & 0 deletions dittybopper/syncer/entrypoint.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import json
import logging
import os
import requests
import uuid
import time
from collections import defaultdict

logging.basicConfig(level=logging.INFO)


class GrafanaOperations:
"""
This class is responsible for Grafana operations
"""
def __init__(self, grafana_url: str, input_directory: str):
self.grafana_url = grafana_url
self.input_directory = input_directory
self.dashboards = defaultdict(list)
self.folder_map = dict()
self.logger = logging.getLogger(__name__)

def fetch_all_dashboards(self):
"""
This method fetches all rendered dashboards
:return:
"""
self.get_all_folders()
self.folder_map['General'] = None
for root, _, files in os.walk(self.input_directory):
folder_name = os.path.basename(root)
json_files = [os.path.join(root, filename) for filename in files if filename.endswith(".json")]
folder_name = "General" if (folder_name == "") else folder_name
if folder_name in self.folder_map:
folder_id = self.folder_map[folder_name]
else:
folder_id = self.create_folder(folder_name)
self.dashboards[folder_id].extend(json_files)

def get_all_folders(self):
"""
This method gets the entire list of folders in grafana
:return:
"""
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
try:
response = requests.get(
f"{self.grafana_url}/api/folders",
headers=headers,
)
response_json = response.json()
self.folder_map = {each_folder['title']: each_folder['id'] for each_folder in response_json}
except requests.exceptions.RequestException as e:
raise Exception(f"Error listing folders. Message: {e}")

def create_folder(self, folder_name):
"""
This method creates a folder in grafana
:return:
"""
uid = str(uuid.uuid4())
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
try:
response = requests.post(
f"{self.grafana_url}/api/folders",
headers=headers,
json={
"title": folder_name,
"uid": uid,
},
)
response_json = response.json()
self.folder_map[folder_name] = id
return response_json['id']

except requests.exceptions.RequestException as e:
raise Exception(f"Error creating folder with name:'{self.folder_name}' and uid:'{uid}'. Message: {e}")

def read_dashboard_json(self, json_file):
"""
This method reads dashboard from json file
:return:
"""
with open(json_file, 'r') as f:
return json.load(f)

def create_dashboards(self):
"""
This method creates/updates dashboard with new json
:return:
"""
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
}
for folder_id, files in self.dashboards.items():
for json_file in set(files):
dashboard_json = self.read_dashboard_json(json_file)
try:
response = requests.post(
f"{self.grafana_url}/api/dashboards/db",
headers=headers,
json={
"dashboard": dashboard_json,
"folderId": folder_id,
"overwrite": True,
},
)
if response.status_code == 200:
self.logger.info(f"Dashboard '{dashboard_json['title']}' created successfully in folder '{folder_id}'")
else:
raise Exception(
f"Failed to create dashboard '{dashboard_json['title']}' in folder '{folder_id}'. Status code: {response.status_code}. Message: {response.text}")

except requests.exceptions.RequestException as e:
raise Exception(f"Error creating dashboard '{dashboard_json['title']}' in folder '{folder_id}'. Message: {e}")

if __name__ == '__main__':
grafana_operations = GrafanaOperations(os.environ.get("GRAFANA_URL"), os.environ.get("INPUT_DIR"))
grafana_operations.fetch_all_dashboards()
grafana_operations.create_dashboards()
while True:
time.sleep(60)
22 changes: 0 additions & 22 deletions dittybopper/syncer/entrypoint.sh

This file was deleted.

8 changes: 4 additions & 4 deletions dittybopper/templates/dittybopper.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ spec:
- name: dittybopper-syncer
imagePullPolicy: Always
env:
- name: GRAFANA_ADMIN_PASSWORD
value: ${GRAFANA_ADMIN_PASSWORD}
- name: DASHBOARDS
value: ${DASHBOARDS}
- name: GRAFANA_URL
value: ${GRAFANA_URL}
- name: INPUT_DIR
value: "/performance-dashboards/rendered/"
image: ${SYNCER_IMAGE}
volumes:
- name: sc-grafana-config
Expand Down
8 changes: 4 additions & 4 deletions dittybopper/templates/k8s-dittybopper.yaml.template
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ spec:
- name: dittybopper-syncer
imagePullPolicy: Always
env:
- name: GRAFANA_ADMIN_PASSWORD
value: ${GRAFANA_ADMIN_PASSWORD}
- name: DASHBOARDS
value: ${DASHBOARDS}
- name: GRAFANA_URL
value: ${GRAFANA_URL}
- name: INPUT_DIR
value: "/performance-dashboards/rendered/"
image: ${SYNCER_IMAGE}
volumes:
- name: sc-grafana-config
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
requests==2.26.0

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local es = grafana.elasticsearch;

local worker_count = grafana.statPanel.new(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;

//Panel definitions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;


Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;

// Panel definitions
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
local grafana = import 'grafonnet-lib/grafonnet/grafana.libsonnet';
local grafana = import '../grafonnet-lib/grafonnet/grafana.libsonnet';
local prometheus = grafana.prometheus;
local stat = grafana.statPanel;

Expand Down
Loading

0 comments on commit 8a4f4a6

Please sign in to comment.