Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions cmd/scheduling-quality-exporter/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright SAP SE
// SPDX-License-Identifier: Apache-2.0

package main

import (
"context"
"flag"
"fmt"
"log/slog"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"

"github.com/cobaltcore-dev/cortex/internal/exporters/schedulingquality/collector"
"github.com/cobaltcore-dev/cortex/internal/exporters/schedulingquality/nova"
)

func main() {
var (
listenAddr = flag.String("listen-address", ":9199", "address to listen on for metrics")
metricsPath = flag.String("metrics-path", "/metrics", "path under which to expose metrics")
novaEndpoint = flag.String("nova-endpoint", "", "OpenStack Nova API endpoint (optional; reads OS_AUTH_URL env if unset)")
scrapeInterval = flag.Duration("scrape-interval", 30*time.Second, "minimum interval between Nova API calls")
kubeconfig = flag.String("kubeconfig", "", "path to kubeconfig (uses in-cluster config if unset)")
isNovaDisabled = flag.Bool("disable-nova", false, "disable Nova flavor lookups; use fixed minimum placeable unit")
minCPU = flag.Int64("min-cpu", 1, "minimum placeable CPU cores (used when Nova is disabled)")
minMemoryMB = flag.Int64("min-memory-mb", 512, "minimum placeable memory in MB (used when Nova is disabled)")
logLevel = flag.String("log-level", "info", "log level (debug, info, warn, error)")
)
flag.Parse()

level := slog.LevelInfo
switch *logLevel {
case "debug":
level = slog.LevelDebug
case "warn":
level = slog.LevelWarn
case "error":
level = slog.LevelError
}
logger := slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: level}))
slog.SetDefault(logger)

ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()

var novaClient *nova.Client
if !*isNovaDisabled {
endpoint := *novaEndpoint
if endpoint == "" {
endpoint = os.Getenv("OS_AUTH_URL")
}
if endpoint != "" {
var err error
novaClient, err = nova.NewClientFromEnv()
handleError(err, "failed to create nova client")
}
}

k8sClient, err := collector.NewK8sClient(*kubeconfig)
handleError(err, "failed to create kubernetes client")

c := collector.New(collector.Options{
ScrapeInterval: *scrapeInterval,
NovaClient: novaClient,
K8sClient: k8sClient,
MinCPU: *minCPU,
MinMemoryBytes: *minMemoryMB * 1024 * 1024,
})
prometheus.MustRegister(c)

mux := http.NewServeMux()
mux.Handle(*metricsPath, promhttp.Handler())
mux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "ok")
})

srv := &http.Server{
Addr: *listenAddr,
Handler: mux,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
}

go func() {
slog.Info("starting cortex-scheduling-quality-exporter", "address", *listenAddr, "path", *metricsPath)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
handleError(err, "server error")
}
}()

<-ctx.Done()
slog.Info("shutting down")
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer shutdownCancel()
_ = srv.Shutdown(shutdownCtx)

Check failure on line 103 in cmd/scheduling-quality-exporter/main.go

View workflow job for this annotation

GitHub Actions / CodeQL

Error return value of `srv.Shutdown` is not checked (errcheck)

Check failure on line 103 in cmd/scheduling-quality-exporter/main.go

View workflow job for this annotation

GitHub Actions / Checks

Error return value of `srv.Shutdown` is not checked (errcheck)
}

func handleError(err error, message string) {
if err != nil {
slog.Error(message, "error", err)
os.Exit(1)
}
}
17 changes: 17 additions & 0 deletions helm/library/cortex-scheduling-quality-exporter/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: v2
name: cortex-scheduling-quality-exporter
description: Prometheus exporter for Cortex scheduling quality and resource efficiency metrics
type: application
version: 0.1.0
appVersion: "sha-9f115a57"
keywords:
- prometheus
- exporter
- openstack
- cortex
- scheduling
sources:
- https://github.com/cobaltcore-dev/cortex
maintainers:
- name: cobaltcore-dev
dependencies: []
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
{{/*
Expand the name of the chart.
*/}}
{{- define "cortex-scheduling-quality-exporter.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "cortex-scheduling-quality-exporter.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart label.
*/}}
{{- define "cortex-scheduling-quality-exporter.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels.
*/}}
{{- define "cortex-scheduling-quality-exporter.labels" -}}
helm.sh/chart: {{ include "cortex-scheduling-quality-exporter.chart" . }}
{{ include "cortex-scheduling-quality-exporter.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels.
*/}}
{{- define "cortex-scheduling-quality-exporter.selectorLabels" -}}
app.kubernetes.io/name: {{ include "cortex-scheduling-quality-exporter.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
ServiceAccount name.
*/}}
{{- define "cortex-scheduling-quality-exporter.serviceAccountName" -}}
{{- default (include "cortex-scheduling-quality-exporter.fullname" .) .Values.serviceAccount.name }}
{{- end }}

{{/*
OpenStack credentials Secret name.
*/}}
{{- define "cortex-scheduling-quality-exporter.osSecretName" -}}
{{- include "cortex-scheduling-quality-exporter.fullname" . }}-openstack
{{- end }}

{{/*
Returns true if Nova integration is enabled (all required openstack fields are set).
*/}}
{{- define "cortex-scheduling-quality-exporter.novaEnabled" -}}
{{- if and .Values.openstack.authURL .Values.openstack.username .Values.openstack.password .Values.openstack.projectName -}}
true
{{- end }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{ include "cortex-scheduling-quality-exporter.fullname" . }}
labels:
{{- include "cortex-scheduling-quality-exporter.labels" . | nindent 4 }}
rules:
- apiGroups:
- openstack.sapcloud.io
resources:
- hypervisors
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{ include "cortex-scheduling-quality-exporter.fullname" . }}
labels:
{{- include "cortex-scheduling-quality-exporter.labels" . | nindent 4 }}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{ include "cortex-scheduling-quality-exporter.fullname" . }}
subjects:
- kind: ServiceAccount
name: {{ include "cortex-scheduling-quality-exporter.serviceAccountName" . }}
namespace: {{ .Release.Namespace }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "cortex-scheduling-quality-exporter.fullname" . }}
labels:
{{- include "cortex-scheduling-quality-exporter.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "cortex-scheduling-quality-exporter.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: {{ .Values.port | quote }}
prometheus.io/path: {{ .Values.config.metricsPath | quote }}
{{- with .Values.podAnnotations }}
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "cortex-scheduling-quality-exporter.labels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "cortex-scheduling-quality-exporter.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
- name: exporter
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --listen-address=:{{ .Values.port }}
- --metrics-path={{ .Values.config.metricsPath }}
- --scrape-interval={{ .Values.config.scrapeInterval }}
- --log-level={{ .Values.config.logLevel }}
{{- if not (include "cortex-scheduling-quality-exporter.novaEnabled" .) }}
- --disable-nova
{{- end }}
{{- if include "cortex-scheduling-quality-exporter.novaEnabled" . }}
envFrom:
- secretRef:
name: {{ include "cortex-scheduling-quality-exporter.osSecretName" . }}
{{- end }}
ports:
- name: metrics
containerPort: {{ .Values.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: metrics
initialDelaySeconds: 5
periodSeconds: 30
readinessProbe:
httpGet:
path: /healthz
port: metrics
initialDelaySeconds: 5
periodSeconds: 15
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{{- if include "cortex-scheduling-quality-exporter.novaEnabled" . }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "cortex-scheduling-quality-exporter.osSecretName" . }}
labels:
{{- include "cortex-scheduling-quality-exporter.labels" . | nindent 4 }}
type: Opaque
stringData:
OS_AUTH_URL: {{ .Values.openstack.authURL | quote }}
OS_USERNAME: {{ .Values.openstack.username | quote }}
OS_PASSWORD: {{ .Values.openstack.password | quote }}
OS_PROJECT_NAME: {{ .Values.openstack.projectName | quote }}
OS_USER_DOMAIN_NAME: {{ .Values.openstack.userDomainName | quote }}
OS_PROJECT_DOMAIN_NAME: {{ .Values.openstack.projectDomainName | quote }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: {{ include "cortex-scheduling-quality-exporter.fullname" . }}
labels:
{{- include "cortex-scheduling-quality-exporter.labels" . | nindent 4 }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.port }}
targetPort: metrics
protocol: TCP
name: metrics
selector:
{{- include "cortex-scheduling-quality-exporter.selectorLabels" . | nindent 4 }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "cortex-scheduling-quality-exporter.serviceAccountName" . }}
labels:
{{- include "cortex-scheduling-quality-exporter.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{{- if .Values.serviceMonitor.enabled }}
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: {{ include "cortex-scheduling-quality-exporter.fullname" . }}
namespace: {{ .Values.serviceMonitor.namespace | default .Release.Namespace }}
labels:
{{- include "cortex-scheduling-quality-exporter.labels" . | nindent 4 }}
{{- with .Values.serviceMonitor.labels }}
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{- include "cortex-scheduling-quality-exporter.selectorLabels" . | nindent 6 }}
endpoints:
- port: metrics
path: {{ .Values.config.metricsPath }}
interval: {{ .Values.serviceMonitor.interval }}
scrapeTimeout: {{ .Values.serviceMonitor.scrapeTimeout }}
{{- end }}
Loading
Loading