operator: Add support for reconciling loki-mixin dashboards on OpenShift Console (#9468)

Co-authored-by: Robert Jacob <xperimental@solidproject.de>
pull/9692/head
Periklis Tsirakidis 2 years ago committed by GitHub
parent 8ca1d1e0a4
commit 38556c1d37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      operator/.bingo/Variables.mk
  2. 5
      operator/.bingo/jb.mod
  3. 50
      operator/.bingo/jb.sum
  4. 5
      operator/.bingo/jsonnet.mod
  5. 17
      operator/.bingo/jsonnet.sum
  6. 5
      operator/.bingo/jsonnetfmt.mod
  7. 17
      operator/.bingo/jsonnetfmt.sum
  8. 6
      operator/.bingo/variables.env
  9. 4
      operator/.gitignore
  10. 1
      operator/CHANGELOG.md
  11. 12
      operator/Makefile
  12. 3
      operator/apis/config/v1/projectconfig_types.go
  13. 1
      operator/bundle/community-openshift/manifests/loki-operator-manager-config_v1_configmap.yaml
  14. 1
      operator/bundle/openshift/manifests/loki-operator-manager-config_v1_configmap.yaml
  15. 1
      operator/config/overlays/community-openshift/controller_manager_config.yaml
  16. 1
      operator/config/overlays/openshift/controller_manager_config.yaml
  17. 66
      operator/controllers/loki/dashboards_controller.go
  18. 51
      operator/internal/handlers/dashboards_create.go
  19. 61
      operator/internal/handlers/dashboards_create_test.go
  20. 30
      operator/internal/handlers/dashboards_delete.go
  21. 34
      operator/internal/handlers/dashboards_delete_test.go
  22. 11
      operator/internal/handlers/lokistack_check_cert_expiry_test.go
  23. 5
      operator/internal/handlers/lokistack_create_or_update.go
  24. 43
      operator/internal/handlers/lokistack_create_or_update_test.go
  25. 7
      operator/internal/handlers/lokistack_enable_zone_awareness_test.go
  26. 19
      operator/internal/handlers/lokistack_rotate_certs_test.go
  27. 71
      operator/internal/manifests/openshift/dashboards.go
  28. 32
      operator/internal/manifests/openshift/dashboards_test.go
  29. 60
      operator/internal/manifests/openshift/internal/dashboards/build.go
  30. 27
      operator/internal/manifests/openshift/internal/dashboards/build_test.go
  31. 1090
      operator/internal/manifests/openshift/internal/dashboards/static/grafana-dashboard-lokistack-chunks.json
  32. 1050
      operator/internal/manifests/openshift/internal/dashboards/static/grafana-dashboard-lokistack-reads.json
  33. 680
      operator/internal/manifests/openshift/internal/dashboards/static/grafana-dashboard-lokistack-retention.json
  34. 33
      operator/internal/manifests/openshift/internal/dashboards/static/grafana-dashboard-lokistack-rules.json
  35. 862
      operator/internal/manifests/openshift/internal/dashboards/static/grafana-dashboard-lokistack-writes.json
  36. 2
      operator/internal/manifests/openshift/var.go
  37. 16
      operator/internal/operator/operator.go
  38. 219
      operator/jsonnet/config.libsonnet
  39. 10
      operator/jsonnet/dashboards.jsonnet
  40. 15
      operator/jsonnet/jsonnetfile.json
  41. 66
      operator/jsonnet/jsonnetfile.lock.json
  42. 1
      operator/jsonnet/main.jsonnet
  43. 21
      operator/main.go

@ -53,6 +53,24 @@ $(HUGO): $(BINGO_DIR)/hugo.mod
@echo "(re)installing $(GOBIN)/hugo-v0.80.0"
@cd $(BINGO_DIR) && GOWORK=off CGO_ENABLED=1 $(GO) build -tags=extended -mod=mod -modfile=hugo.mod -o=$(GOBIN)/hugo-v0.80.0 "github.com/gohugoio/hugo"
JB := $(GOBIN)/jb-v0.5.1
$(JB): $(BINGO_DIR)/jb.mod
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
@echo "(re)installing $(GOBIN)/jb-v0.5.1"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=jb.mod -o=$(GOBIN)/jb-v0.5.1 "github.com/jsonnet-bundler/jsonnet-bundler/cmd/jb"
JSONNET := $(GOBIN)/jsonnet-v0.20.0
$(JSONNET): $(BINGO_DIR)/jsonnet.mod
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
@echo "(re)installing $(GOBIN)/jsonnet-v0.20.0"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=jsonnet.mod -o=$(GOBIN)/jsonnet-v0.20.0 "github.com/google/go-jsonnet/cmd/jsonnet"
JSONNETFMT := $(GOBIN)/jsonnetfmt-v0.20.0
$(JSONNETFMT): $(BINGO_DIR)/jsonnetfmt.mod
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.
@echo "(re)installing $(GOBIN)/jsonnetfmt-v0.20.0"
@cd $(BINGO_DIR) && GOWORK=off $(GO) build -mod=mod -modfile=jsonnetfmt.mod -o=$(GOBIN)/jsonnetfmt-v0.20.0 "github.com/google/go-jsonnet/cmd/jsonnetfmt"
KIND := $(GOBIN)/kind-v0.17.0
$(KIND): $(BINGO_DIR)/kind.mod
@# Install binary/ries using Go 1.14+ build command. This is using bwplotka/bingo-controlled, separate go module with pinned dependencies.

@ -0,0 +1,5 @@
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
go 1.20
require github.com/jsonnet-bundler/jsonnet-bundler v0.5.1 // cmd/jb

@ -0,0 +1,50 @@
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc=
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/jsonnet-bundler/jsonnet-bundler v0.5.1 h1:eUd6EA1Qzz73Q4NLNLOrNkMb96+6NTTERbX9lqaxVwk=
github.com/jsonnet-bundler/jsonnet-bundler v0.5.1/go.mod h1:Qrdw/7mOFS2SKCOALKFfEH8gdvXJi8XZjw9g5ilpf4I=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -0,0 +1,5 @@
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
go 1.20
require github.com/google/go-jsonnet v0.20.0 // cmd/jsonnet

@ -0,0 +1,17 @@
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g=
github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

@ -0,0 +1,5 @@
module _ // Auto generated by https://github.com/bwplotka/bingo. DO NOT EDIT
go 1.20
require github.com/google/go-jsonnet v0.20.0 // cmd/jsonnetfmt

@ -0,0 +1,17 @@
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g=
github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

@ -20,6 +20,12 @@ GOLANGCI_LINT="${GOBIN}/golangci-lint-v1.51.2"
HUGO="${GOBIN}/hugo-v0.80.0"
JB="${GOBIN}/jb-v0.5.1"
JSONNET="${GOBIN}/jsonnet-v0.20.0"
JSONNETFMT="${GOBIN}/jsonnetfmt-v0.20.0"
KIND="${GOBIN}/kind-v0.17.0"
KUSTOMIZE="${GOBIN}/kustomize-v4.5.7"

@ -24,6 +24,10 @@ testbin/*
*.swo
*~
# JSONNet vendored files
jsonnet/vendor/*
# website
website/public/*

@ -1,5 +1,6 @@
## Main
- [9468](https://github.com/grafana/loki/pull/9468) **periklis**: Add support for reconciling loki-mixin dashboards on OpenShift Console
- [9942](https://github.com/grafana/loki/pull/9942) **btaani**: Use a condition to warn when there are no nodes with matching labels for zone-awareness
## 0.4.0 (2023-07-27)

@ -339,3 +339,15 @@ check-operatorhub-pr-template:
curl https://raw.githubusercontent.com/operator-framework/community-operators/master/docs/pull_request_template.md -o hack/.operatorhub-pr-template.md -s > /dev/null 2>&1
git diff -s --exit-code hack/.operatorhub-pr-template.md || (echo "Build failed: the PR template for OperatorHub has changed. Sync it and try again." && exit 1)
JSONNET_SRC = $(shell find . -type f -not -path './jsonnet/vendor/*' \( -name '*.libsonnet' -o -name '*.jsonnet' \))
JSONNET_VENDOR_DIR = jsonnet/vendor
$(JSONNET_VENDOR_DIR): $(JB) jsonnet/jsonnetfile.json jsonnet/jsonnetfile.lock.json
@cd jsonnet/ && $(JB) install
jsonnet-format: $(JSONNETFMT) $(JSONNET_SRC)
@$(JSONNETFMT) -n 2 --max-blank-lines 2 --string-style s --comment-style s -i $(JSONNET_SRC)
generate-dashboards: $(JSONNET) jsonnet-format $(JSONNET_VENDOR_DIR)
@rm -f internal/manifests/openshift/internal/dashboards/static/*.json
@$(JSONNET) -J "$(JSONNET_VENDOR_DIR)" -m internal/manifests/openshift/internal/dashboards/static jsonnet/main.jsonnet

@ -48,6 +48,9 @@ type OpenShiftFeatureGates struct {
// ClusterProxy enables usage of the proxy variables set in the proxy resource.
// More details: https://docs.openshift.com/container-platform/4.11/networking/enable-cluster-wide-proxy.html#enable-cluster-wide-proxy
ClusterProxy bool `json:"clusterProxy,omitempty"`
// Dashboards enables the loki-mixin dashboards into the OpenShift Console
Dashboards bool `json:"dashboards,omitempty"`
}
// FeatureGates is the supported set of all operator feature gates.

@ -56,6 +56,7 @@ data:
ruleExtendedValidation: true
clusterTLSPolicy: true
clusterProxy: true
dashboards: true
kind: ConfigMap
metadata:
labels:

@ -59,6 +59,7 @@ data:
ruleExtendedValidation: true
clusterTLSPolicy: true
clusterProxy: true
dashboards: true
kind: ConfigMap
metadata:
labels:

@ -53,3 +53,4 @@ featureGates:
ruleExtendedValidation: true
clusterTLSPolicy: true
clusterProxy: true
dashboards: true

@ -56,3 +56,4 @@ featureGates:
ruleExtendedValidation: true
clusterTLSPolicy: true
clusterProxy: true
dashboards: true

@ -0,0 +1,66 @@
package controllers
import (
"context"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/go-logr/logr"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/handlers"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/predicate"
)
var createOrDeletesPred = builder.WithPredicates(predicate.Funcs{
UpdateFunc: func(e event.UpdateEvent) bool { return false },
CreateFunc: func(e event.CreateEvent) bool { return true },
DeleteFunc: func(e event.DeleteEvent) bool { return true },
GenericFunc: func(e event.GenericEvent) bool { return false },
})
// DashboardsReconciler deploys and removes the cluster-global resources needed
// for the metrics dashboards depending on whether any LokiStacks exist.
type DashboardsReconciler struct {
client.Client
Scheme *runtime.Scheme
Log logr.Logger
OperatorNs string
}
// Reconcile creates all LokiStack dashboard ConfigMap and PrometheusRule objects on OpenShift clusters when
// the at least one LokiStack custom resource exists or removes all when none.
func (r *DashboardsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var stacks lokiv1.LokiStackList
if err := r.List(ctx, &stacks, client.MatchingLabelsSelector{Selector: labels.Everything()}); err != nil {
return ctrl.Result{}, kverrors.Wrap(err, "failed to list any lokistack instances")
}
if len(stacks.Items) == 0 {
// Removes all LokiStack dashboard resources on OpenShift clusters when
// the last LokiStack custom resource is deleted.
if err := handlers.DeleteDashboards(ctx, r.Client, r.OperatorNs); err != nil {
return ctrl.Result{}, kverrors.Wrap(err, "failed to delete dashboard resources")
}
return ctrl.Result{}, nil
}
// Creates all LokiStack dashboard resources on OpenShift clusters when
// the first LokiStack custom resource is created.
if err := handlers.CreateDashboards(ctx, r.Log, r.OperatorNs, r.Client, r.Scheme); err != nil {
return ctrl.Result{}, kverrors.Wrap(err, "failed to create dashboard resources", "req", req)
}
return ctrl.Result{}, nil
}
// SetupWithManager sets up the controller with the Manager to only call this controller on create/delete/generic events.
func (r *DashboardsReconciler) SetupWithManager(mgr manager.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&lokiv1.LokiStack{}, createOrDeletesPred).
Complete(r)
}

@ -0,0 +1,51 @@
package handlers
import (
"context"
"fmt"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/go-logr/logr"
"github.com/grafana/loki/operator/internal/external/k8s"
"github.com/grafana/loki/operator/internal/manifests"
"github.com/grafana/loki/operator/internal/manifests/openshift"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
ctrlutil "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)
// CreateDashboards handles the LokiStack dashboards create events.
func CreateDashboards(ctx context.Context, log logr.Logger, operatorNs string, k k8s.Client, s *runtime.Scheme) error {
objs, err := openshift.BuildDashboards(operatorNs)
if err != nil {
return kverrors.Wrap(err, "failed to build dashboard manifests")
}
var errCount int32
for _, obj := range objs {
desired := obj.DeepCopyObject().(client.Object)
mutateFn := manifests.MutateFuncFor(obj, desired, nil)
op, err := ctrl.CreateOrUpdate(ctx, k, obj, mutateFn)
if err != nil {
log.Error(err, "failed to configure resource")
errCount++
continue
}
msg := fmt.Sprintf("Resource has been %s", op)
switch op {
case ctrlutil.OperationResultNone:
log.V(1).Info(msg)
default:
log.Info(msg)
}
}
if errCount > 0 {
return kverrors.New("failed to configure lokistack dashboard resources")
}
return nil
}

@ -0,0 +1,61 @@
package handlers
import (
"context"
"testing"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func TestCreateDashboards_ReturnsResourcesInManagedNamespaces(t *testing.T) {
sw := &k8sfakes.FakeStatusWriter{}
k := &k8sfakes.FakeClient{}
r := ctrl.Request{
NamespacedName: types.NamespacedName{
Name: "my-stack",
Namespace: "some-ns",
},
}
stack := lokiv1.LokiStack{
TypeMeta: metav1.TypeMeta{
Kind: "LokiStack",
},
ObjectMeta: metav1.ObjectMeta{
Name: "my-stack",
Namespace: "some-ns",
UID: "b23f9a38-9672-499f-8c29-15ede74d3ece",
},
}
k.GetStub = func(_ context.Context, name types.NamespacedName, out client.Object, _ ...client.GetOption) error {
if r.Name == name.Name && r.Namespace == name.Namespace {
k.SetClientObject(out, &stack)
return nil
}
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found")
}
k.CreateStub = func(_ context.Context, o client.Object, _ ...client.CreateOption) error {
assert.NotEqual(t, r.Namespace, o.GetNamespace())
return nil
}
k.StatusStub = func() client.StatusWriter { return sw }
err := CreateDashboards(context.TODO(), logger, "test", k, scheme)
require.NoError(t, err)
// make sure create was called
require.NotZero(t, k.CreateCallCount())
}

@ -0,0 +1,30 @@
package handlers
import (
"context"
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/grafana/loki/operator/internal/external/k8s"
"github.com/grafana/loki/operator/internal/manifests/openshift"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/client"
)
// DeleteDashboards removes all cluster-scoped dashboard resources.
func DeleteDashboards(ctx context.Context, k k8s.Client, operatorNs string) error {
objs, err := openshift.BuildDashboards(operatorNs)
if err != nil {
return kverrors.Wrap(err, "failed to build dashboards manifests")
}
for _, obj := range objs {
key := client.ObjectKeyFromObject(obj)
if err := k.Delete(ctx, obj, &client.DeleteOptions{}); err != nil {
if apierrors.IsNotFound(err) {
continue
}
return kverrors.Wrap(err, "failed to delete dashboard", "key", key)
}
}
return nil
}

@ -0,0 +1,34 @@
package handlers
import (
"context"
"testing"
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes"
"github.com/grafana/loki/operator/internal/manifests/openshift"
"github.com/stretchr/testify/require"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
)
func TestDeleteDashboards(t *testing.T) {
objs, err := openshift.BuildDashboards("operator-ns")
require.NoError(t, err)
k := &k8sfakes.FakeClient{}
err = DeleteDashboards(context.TODO(), k, "operator-ns")
require.NoError(t, err)
require.Equal(t, k.DeleteCallCount(), len(objs))
}
func TestDeleteDashboards_ReturnsNoError_WhenNotFound(t *testing.T) {
k := &k8sfakes.FakeClient{}
k.DeleteStub = func(context.Context, client.Object, ...client.DeleteOption) error {
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found")
}
err := DeleteDashboards(context.TODO(), k, "operator-ns")
require.NoError(t, err)
}

@ -1,4 +1,4 @@
package handlers_test
package handlers
import (
"context"
@ -9,7 +9,6 @@ import (
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/certrotation"
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes"
"github.com/grafana/loki/operator/internal/handlers"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -37,7 +36,7 @@ func TestCheckCertExpiry_WhenGetReturnsNotFound_DoesNotError(t *testing.T) {
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CheckCertExpiry(context.TODO(), logger, r, k, featureGates)
err := CheckCertExpiry(context.TODO(), logger, r, k, featureGates)
require.NoError(t, err)
// make sure create was NOT called because the Get failed
@ -61,7 +60,7 @@ func TestCheckCertExpiry_WhenGetReturnsAnErrorOtherThanNotFound_ReturnsTheError(
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CheckCertExpiry(context.TODO(), logger, r, k, featureGates)
err := CheckCertExpiry(context.TODO(), logger, r, k, featureGates)
require.Equal(t, badRequestErr, errors.Unwrap(err))
@ -100,7 +99,7 @@ func TestCheckCertExpiry_WhenGetOptionsReturnsSignerNotFound_DoesNotError(t *tes
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CheckCertExpiry(context.TODO(), logger, r, k, featureGates)
err := CheckCertExpiry(context.TODO(), logger, r, k, featureGates)
require.NoError(t, err)
// make sure create was NOT called because the Get failed
@ -179,7 +178,7 @@ func TestCheckCertExpiry_WhenGetOptionsReturnsCABUndleNotFound_DoesNotError(t *t
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CheckCertExpiry(context.TODO(), logger, r, k, featureGates)
err := CheckCertExpiry(context.TODO(), logger, r, k, featureGates)
require.NoError(t, err)
// make sure create was NOT called because the Get failed

@ -366,7 +366,7 @@ func CreateOrUpdateLokiStack(
"object_kind", obj.GetObjectKind(),
)
if isNamespaceScoped(obj) {
if isNamespacedResource(obj) {
obj.SetNamespace(req.Namespace)
if err := ctrl.SetControllerReference(&stack, obj, s); err != nil {
@ -432,7 +432,8 @@ func dependentAnnotations(ctx context.Context, k k8s.Client, obj client.Object)
}, nil
}
func isNamespaceScoped(obj client.Object) bool {
// isNamespacedResource determines if an object should be managed or not by a LokiStack
func isNamespacedResource(obj client.Object) bool {
switch obj.(type) {
case *rbacv1.ClusterRole, *rbacv1.ClusterRoleBinding:
return false

@ -1,4 +1,4 @@
package handlers_test
package handlers
import (
"context"
@ -11,7 +11,6 @@ import (
configv1 "github.com/grafana/loki/operator/apis/config/v1"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes"
"github.com/grafana/loki/operator/internal/handlers"
"github.com/grafana/loki/operator/internal/status"
"github.com/ViaQ/logerr/v2/log"
@ -146,7 +145,7 @@ func TestCreateOrUpdateLokiStack_WhenGetReturnsNotFound_DoesNotError(t *testing.
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create was NOT called because the Get failed
@ -170,7 +169,7 @@ func TestCreateOrUpdateLokiStack_WhenGetReturnsAnErrorOtherThanNotFound_ReturnsT
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
require.Equal(t, badRequestErr, errors.Unwrap(err))
@ -256,7 +255,7 @@ func TestCreateOrUpdateLokiStack_SetsNamespaceOnAllObjects(t *testing.T) {
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create was called
@ -363,7 +362,7 @@ func TestCreateOrUpdateLokiStack_SetsOwnerRefOnAllObjects(t *testing.T) {
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create was called
@ -422,7 +421,7 @@ func TestCreateOrUpdateLokiStack_WhenSetControllerRefInvalid_ContinueWithOtherOb
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
@ -524,7 +523,7 @@ func TestCreateOrUpdateLokiStack_WhenGetReturnsNoError_UpdateObjects(t *testing.
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create not called
@ -591,7 +590,7 @@ func TestCreateOrUpdateLokiStack_WhenCreateReturnsError_ContinueWithOtherObjects
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
@ -699,7 +698,7 @@ func TestCreateOrUpdateLokiStack_WhenUpdateReturnsError_ContinueWithOtherObjects
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
@ -759,7 +758,7 @@ func TestCreateOrUpdateLokiStack_WhenMissingSecret_SetDegraded(t *testing.T) {
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned
require.Error(t, err)
@ -824,7 +823,7 @@ func TestCreateOrUpdateLokiStack_WhenInvalidSecret_SetDegraded(t *testing.T) {
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned
require.Error(t, err)
@ -898,7 +897,7 @@ func TestCreateOrUpdateLokiStack_WithInvalidStorageSchema_SetDegraded(t *testing
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned
require.Error(t, err)
@ -970,7 +969,7 @@ func TestCreateOrUpdateLokiStack_WhenMissingCAConfigMap_SetDegraded(t *testing.T
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned
require.Error(t, err)
@ -1045,7 +1044,7 @@ func TestCreateOrUpdateLokiStack_WhenInvalidCAConfigMap_SetDegraded(t *testing.T
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned
require.Error(t, err)
@ -1129,7 +1128,7 @@ func TestCreateOrUpdateLokiStack_WhenInvalidTenantsConfiguration_SetDegraded(t *
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, ff)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, ff)
// make sure error is returned
require.Error(t, err)
@ -1218,7 +1217,7 @@ func TestCreateOrUpdateLokiStack_WhenMissingGatewaySecret_SetDegraded(t *testing
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, ff)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, ff)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
@ -1311,7 +1310,7 @@ func TestCreateOrUpdateLokiStack_WhenInvalidGatewaySecret_SetDegraded(t *testing
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, ff)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, ff)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
@ -1382,7 +1381,7 @@ func TestCreateOrUpdateLokiStack_MissingTenantsSpec_SetDegraded(t *testing.T) {
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, ff)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, ff)
// make sure error is returned
require.Error(t, err)
@ -1454,7 +1453,7 @@ func TestCreateOrUpdateLokiStack_WhenInvalidQueryTimeout_SetDegraded(t *testing.
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned
require.Error(t, err)
@ -1563,7 +1562,7 @@ func TestCreateOrUpdateLokiStack_RemovesRulerResourcesWhenDisabled(t *testing.T)
return nil
}
err := handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create was called
@ -1603,7 +1602,7 @@ func TestCreateOrUpdateLokiStack_RemovesRulerResourcesWhenDisabled(t *testing.T)
}
return apierrors.NewNotFound(schema.GroupResource{}, "something wasn't found")
}
err = handlers.CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
err = CreateOrUpdateLokiStack(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure delete was called twice (delete rules configmap and ruler statefulset)

@ -1,4 +1,4 @@
package handlers_test
package handlers
import (
"context"
@ -9,7 +9,6 @@ import (
"github.com/ViaQ/logerr/v2/kverrors"
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes"
"github.com/grafana/loki/operator/internal/handlers"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -42,7 +41,7 @@ func TestAnnotatePodWithAvailabilityZone_WhenGetReturnsAnErrorOtherThanNotFound_
return badRequestErr
}
err := handlers.AnnotatePodWithAvailabilityZone(context.TODO(), logger, k, &defaultPod)
err := AnnotatePodWithAvailabilityZone(context.TODO(), logger, k, &defaultPod)
require.Equal(t, badRequestErr, errors.Unwrap(err))
// make sure patch was NOT called because the Get failed
@ -96,7 +95,7 @@ func TestAnnotatePodWithAvailabilityZone_WhenGetReturnsNode_DoesNotError(t *test
},
})
err := handlers.AnnotatePodWithAvailabilityZone(context.TODO(), logger, k, &testPod)
err := AnnotatePodWithAvailabilityZone(context.TODO(), logger, k, &testPod)
require.NoError(t, err)
// make sure patch was called because the Get succeeded

@ -1,4 +1,4 @@
package handlers_test
package handlers
import (
"context"
@ -7,7 +7,6 @@ import (
lokiv1 "github.com/grafana/loki/operator/apis/loki/v1"
"github.com/grafana/loki/operator/internal/external/k8s/k8sfakes"
"github.com/grafana/loki/operator/internal/handlers"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
@ -36,7 +35,7 @@ func TestCreateOrRotateCertificates_WhenGetReturnsNotFound_DoesNotError(t *testi
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create was NOT called because the Get failed
@ -60,7 +59,7 @@ func TestCreateOrRotateCertificates_WhenGetReturnsAnErrorOtherThanNotFound_Retur
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
require.Equal(t, badRequestErr, errors.Unwrap(err))
@ -146,7 +145,7 @@ func TestCreateOrRotateCertificates_SetsNamespaceOnAllObjects(t *testing.T) {
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create was called
@ -253,7 +252,7 @@ func TestCreateOrRotateCertificates_SetsOwnerRefOnAllObjects(t *testing.T) {
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create was called
@ -312,7 +311,7 @@ func TestCreateOrRotateCertificates_WhenSetControllerRefInvalid_ContinueWithOthe
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
@ -399,7 +398,7 @@ func TestCreateOrRotateCertificates_WhenGetReturnsNoError_UpdateObjects(t *testi
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
require.NoError(t, err)
// make sure create not called
@ -466,7 +465,7 @@ func TestCreateOrRotateCertificats_WhenCreateReturnsError_ContinueWithOtherObjec
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)
@ -559,7 +558,7 @@ func TestCreateOrRotateCertificates_WhenUpdateReturnsError_ContinueWithOtherObje
k.StatusStub = func() client.StatusWriter { return sw }
err := handlers.CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
err := CreateOrRotateCertificates(context.TODO(), logger, r, k, scheme, featureGates)
// make sure error is returned to re-trigger reconciliation
require.Error(t, err)

@ -0,0 +1,71 @@
package openshift
import (
"strings"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/grafana/loki/operator/internal/manifests/openshift/internal/dashboards"
)
const (
labelConsoleDashboard = "console.openshift.io/dashboard"
managedConfigNamespace = "openshift-config-managed"
)
func BuildDashboards(operatorNs string) ([]client.Object, error) {
ds, rules := dashboards.Content()
var objs []client.Object
for name, content := range ds {
objs = append(objs, newDashboardConfigMap(name, content))
}
promRule, err := newDashboardPrometheusRule(operatorNs, rules)
if err != nil {
return nil, err
}
objs = append(objs, promRule)
return objs, nil
}
func newDashboardConfigMap(filename string, content []byte) *corev1.ConfigMap {
cmName := strings.Split(filename, ".")[0]
return &corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: corev1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: cmName,
Namespace: managedConfigNamespace,
Labels: map[string]string{
labelConsoleDashboard: "true",
},
},
Data: map[string]string{
filename: string(content),
},
}
}
func newDashboardPrometheusRule(namespace string, spec *monitoringv1.PrometheusRuleSpec) (*monitoringv1.PrometheusRule, error) {
return &monitoringv1.PrometheusRule{
TypeMeta: metav1.TypeMeta{
Kind: "PrometheusRule",
APIVersion: monitoringv1.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: dashboardPrometheusRulesName,
Namespace: namespace,
},
Spec: *spec,
}, nil
}

@ -0,0 +1,32 @@
package openshift
import (
"testing"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
)
func TestBuildDashboards_ReturnsDashboardConfigMaps(t *testing.T) {
objs, err := BuildDashboards("test")
require.NoError(t, err)
for _, d := range objs {
switch d.(type) {
case *corev1.ConfigMap:
require.Equal(t, d.GetNamespace(), managedConfigNamespace)
require.Contains(t, d.GetLabels(), labelConsoleDashboard)
}
}
}
func TestBuildDashboards_ReturnsPrometheusRules(t *testing.T) {
objs, err := BuildDashboards("test")
require.NoError(t, err)
rules := objs[len(objs)-1].(*monitoringv1.PrometheusRule)
require.Equal(t, rules.GetName(), dashboardPrometheusRulesName)
require.Equal(t, rules.GetNamespace(), "test")
require.NotNil(t, rules.Spec)
}

@ -0,0 +1,60 @@
package dashboards
import (
"embed"
"encoding/json"
"io/fs"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
)
const (
staticDir = "static"
lokiStackDashboardRulesFile = "grafana-dashboard-lokistack-rules.json"
)
var (
//go:embed static/*.json
lokiStackDashboards embed.FS
dashboardMap map[string][]byte
dashboardRules *monitoringv1.PrometheusRuleSpec
)
func init() {
var err error
subDir, err := fs.Sub(lokiStackDashboards, staticDir)
if err != nil {
panic(err)
}
jsonFiles, err := fs.Glob(subDir, "*.json")
if err != nil {
panic(err)
}
dashboardMap = map[string][]byte{}
for _, file := range jsonFiles {
var content []byte
content, err = fs.ReadFile(subDir, file)
if err != nil {
panic(err)
}
switch file {
case lokiStackDashboardRulesFile:
err := json.Unmarshal(content, &dashboardRules)
if err != nil {
panic(err)
}
default:
dashboardMap[file] = content
}
}
}
// Content returns the byte slices one for each LokiStack dashboard
// and a separate byte slice for the recording rules from the embedded filesystem.
func Content() (map[string][]byte, *monitoringv1.PrometheusRuleSpec) {
return dashboardMap, dashboardRules
}

@ -0,0 +1,27 @@
package dashboards
import (
"testing"
"github.com/stretchr/testify/require"
)
const (
lokiStackChunkDashboardFile = "grafana-dashboard-lokistack-chunks.json"
lokiStackReadsDashboardFile = "grafana-dashboard-lokistack-reads.json"
lokiStackWritesDashboardFile = "grafana-dashboard-lokistack-writes.json"
lokiStackRetentionDashboardFile = "grafana-dashboard-lokistack-retention.json"
)
func TestContent(t *testing.T) {
m, r := Content()
require.Len(t, m, 4)
require.Equal(t, dashboardMap, m)
require.Equal(t, dashboardRules, r)
require.Contains(t, m, lokiStackChunkDashboardFile)
require.Contains(t, m, lokiStackReadsDashboardFile)
require.Contains(t, m, lokiStackWritesDashboardFile)
require.Contains(t, m, lokiStackRetentionDashboardFile)
require.NotContains(t, m, lokiStackDashboardRulesFile)
}

@ -0,0 +1,680 @@
{
"annotations": {
"list": [ ]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"hideControls": false,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"loki"
],
"targetBlank": false,
"title": "Loki Dashboards",
"type": "dashboards"
}
],
"refresh": "10s",
"rows": [
{
"collapse": false,
"height": "250px",
"panels": [
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 1,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [
{
"alias": "request",
"color": "#FFC000",
"fill": 0
},
{
"alias": "limit",
"color": "#E02F44",
"fill": 0
}
],
"spaceLength": 10,
"span": 4,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum by(pod) (rate(container_cpu_usage_seconds_total{ namespace=~\"$namespace\", container=~\".+-compactor\"}[$__rate_interval]))",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{pod}}",
"legendLink": null,
"step": 10
},
{
"expr": "min(kube_pod_container_resource_requests{ namespace=~\"$namespace\", container=~\".+-compactor\", resource=\"cpu\"} > 0)",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "request",
"legendLink": null,
"step": 10
},
{
"expr": "min(container_spec_cpu_quota{ namespace=~\"$namespace\", container=~\".+-compactor\"} / container_spec_cpu_period{ namespace=~\"$namespace\", container=~\".+-compactor\"})",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "limit",
"legendLink": null,
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "CPU",
"tooltip": {
"sort": 2
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
},
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [
{
"alias": "request",
"color": "#FFC000",
"fill": 0
},
{
"alias": "limit",
"color": "#E02F44",
"fill": 0
}
],
"spaceLength": 10,
"span": 4,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "max by(pod) (container_memory_working_set_bytes{ namespace=~\"$namespace\", container=~\".+-compactor\"})",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{pod}}",
"legendLink": null,
"step": 10
},
{
"expr": "min(kube_pod_container_resource_requests{ namespace=~\"$namespace\", container=~\".+-compactor\", resource=\"memory\"} > 0)",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "request",
"legendLink": null,
"step": 10
},
{
"expr": "min(container_spec_memory_limit_bytes{ namespace=~\"$namespace\", container=~\".+-compactor\"} > 0)",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "limit",
"legendLink": null,
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Memory (workingset)",
"tooltip": {
"sort": 2
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
},
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 3,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 4,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum by(pod) (go_memstats_heap_inuse_bytes{ namespace=\"$namespace\", job=~\".+-compactor-http\"})",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{pod}}",
"legendLink": null,
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Memory (go heap inuse)",
"tooltip": {
"sort": 2
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "bytes",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": true,
"title": "Resource Usage",
"titleSize": "h6"
},
{
"collapse": false,
"height": "250px",
"panels": [
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fieldConfig": {
"defaults": {
"color": {
"fixedColor": "blue",
"mode": "fixed"
},
"custom": { },
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "dateTimeFromNow"
}
},
"fill": 1,
"id": 4,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"text": { },
"textMode": "auto"
},
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 4,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "loki_boltdb_shipper_compact_tables_operation_last_successful_run_timestamp_seconds{ namespace=~\"$namespace\"} * 1e3",
"format": "time_series",
"instant": true,
"refId": "A"
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Last Compact and Mark Operation Success",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "stat",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
},
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 5,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 4,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "loki_boltdb_shipper_compact_tables_operation_duration_seconds{ namespace=~\"$namespace\"}",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "duration",
"legendLink": null,
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Compact and Mark Operations Duration",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "s",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
},
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 6,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 4,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "sum by (status)(rate(loki_boltdb_shipper_compact_tables_operation_total{ namespace=~\"$namespace\"}[$__rate_interval]))",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{success}}",
"legendLink": null,
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Compact and Mark Operations Per Status",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": true,
"title": "Compact and Mark",
"titleSize": "h6"
}
],
"schemaVersion": 14,
"style": "dark",
"tags": [
"loki",
"logging",
"loki-mixin"
],
"templating": {
"list": [
{
"current": {
"selected": true,
"text": "default",
"value": "default"
},
"hide": 0,
"label": "Data Source",
"name": "datasource",
"options": [ ],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"allValue": null,
"current": {
"selected": false,
"text": "openshift-logging",
"value": "openshift-logging"
},
"datasource": "${datasource}",
"definition": "label_values(loki_build_info, namespace)",
"hide": 0,
"includeAll": false,
"label": "namespace",
"multi": false,
"name": "namespace",
"options": [ ],
"query": "label_values(loki_build_info, namespace)",
"refresh": 1,
"regex": "",
"sort": 2,
"tagValuesQuery": "",
"tags": [ ],
"tagsQuery": "",
"type": "query",
"useTags": false
},
{
"hide": 0,
"label": null,
"name": "loki_datasource",
"options": [ ],
"query": "loki",
"refresh": 1,
"regex": "",
"type": "datasource"
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "utc",
"title": "OpenShift Logging / LokiStack / Retention",
"uid": "RetCujSHzC8gd9i5fck9a3v9n2EvTzA",
"version": 0
}

@ -0,0 +1,33 @@
{
"groups": [
{
"name": "loki_rules",
"rules": [
{
"expr": "sum(rate(loki_request_duration_seconds_bucket[1m])) by (le, job, route)",
"record": "job_route:loki_request_duration_seconds_bucket:sum_rate"
},
{
"expr": "sum(rate(loki_request_duration_seconds_sum[1m])) by (job, route)",
"record": "job_route:loki_request_duration_seconds_sum:sum_rate"
},
{
"expr": "sum(rate(loki_request_duration_seconds_count[1m])) by (job, route)",
"record": "job_route:loki_request_duration_seconds_count:sum_rate"
},
{
"expr": "sum(rate(loki_request_duration_seconds_bucket[1m])) by (le, namespace, job, route)",
"record": "namespace_job_route:loki_request_duration_seconds_bucket:sum_rate"
},
{
"expr": "sum(rate(loki_request_duration_seconds_sum[1m])) by (namespace, job, route)",
"record": "namespace_job_route:loki_request_duration_seconds_sum:sum_rate"
},
{
"expr": "sum(rate(loki_request_duration_seconds_count[1m])) by (namespace, job, route)",
"record": "namespace_job_route:loki_request_duration_seconds_count:sum_rate"
}
]
}
]
}

@ -0,0 +1,862 @@
{
"annotations": {
"list": [ ]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"hideControls": false,
"links": [
{
"asDropdown": true,
"icon": "external link",
"includeVars": true,
"keepTime": true,
"tags": [
"loki"
],
"targetBlank": false,
"title": "Loki Dashboards",
"type": "dashboards"
}
],
"refresh": "10s",
"rows": [
{
"collapse": false,
"height": "250px",
"panels": [
{
"aliasColors": {
"1xx": "#EAB839",
"2xx": "#7EB26D",
"3xx": "#6ED0E0",
"4xx": "#EF843C",
"5xx": "#E24D42",
"error": "#E24D42",
"success": "#7EB26D"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 10,
"id": 1,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 0,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 6,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum by (status) (\n label_replace(label_replace(rate(loki_request_duration_seconds_count{namespace=\"$namespace\",job=~\".+-distributor-http\",route=\"loki_api_v1_push\", route=~\"api_prom_push|loki_api_v1_push|/httpgrpc.HTTP/Handle\"}[$__rate_interval]),\n \"status\", \"${1}xx\", \"status_code\", \"([0-9])..\"),\n \"status\", \"${1}\", \"status_code\", \"([a-z]+)\"))\n",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{status}}",
"refId": "A",
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "QPS",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
},
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 2,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "histogram_quantile(0.99, sum by (le) (namespace_job_route:loki_request_duration_seconds_bucket:sum_rate{namespace=\"$namespace\", job=~\".+-distributor-http\", route=\"loki_api_v1_push\"})) * 1e3",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "99th Percentile",
"refId": "A",
"step": 10
},
{
"expr": "histogram_quantile(0.50, sum by (le) (namespace_job_route:loki_request_duration_seconds_bucket:sum_rate{namespace=\"$namespace\", job=~\".+-distributor-http\", route=\"loki_api_v1_push\"})) * 1e3",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "50th Percentile",
"refId": "B",
"step": 10
},
{
"expr": "1e3 * sum(namespace_job_route:loki_request_duration_seconds_sum:sum_rate{namespace=\"$namespace\", job=~\".+-distributor-http\", route=\"loki_api_v1_push\"}) / sum(namespace_job_route:loki_request_duration_seconds_count:sum_rate{namespace=\"$namespace\", job=~\".+-distributor-http\", route=\"loki_api_v1_push\"})",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "Average",
"refId": "C",
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Latency",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "ms",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": true,
"title": "Distributor",
"titleSize": "h6"
},
{
"collapse": false,
"height": "250px",
"panels": [
{
"aliasColors": {
"1xx": "#EAB839",
"2xx": "#7EB26D",
"3xx": "#6ED0E0",
"4xx": "#EF843C",
"5xx": "#E24D42",
"error": "#E24D42",
"success": "#7EB26D"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 10,
"id": 5,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 0,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 6,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum by (status) (\n label_replace(label_replace(rate(loki_request_duration_seconds_count{namespace=\"$namespace\",job=~\".+-ingester-http\", route=\"/logproto.Pusher/Push\"}[$__rate_interval]),\n \"status\", \"${1}xx\", \"status_code\", \"([0-9])..\"),\n \"status\", \"${1}\", \"status_code\", \"([a-z]+)\"))\n",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{status}}",
"refId": "A",
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "QPS",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
},
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 6,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "histogram_quantile(0.99, sum by (le) (namespace_job_route:loki_request_duration_seconds_bucket:sum_rate{namespace=\"$namespace\", job=~\".+-ingester-http\", route=\"/logproto.Pusher/Push\"})) * 1e3",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "99th Percentile",
"refId": "A",
"step": 10
},
{
"expr": "histogram_quantile(0.50, sum by (le) (namespace_job_route:loki_request_duration_seconds_bucket:sum_rate{namespace=\"$namespace\", job=~\".+-ingester-http\", route=\"/logproto.Pusher/Push\"})) * 1e3",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "50th Percentile",
"refId": "B",
"step": 10
},
{
"expr": "1e3 * sum(namespace_job_route:loki_request_duration_seconds_sum:sum_rate{namespace=\"$namespace\", job=~\".+-ingester-http\", route=\"/logproto.Pusher/Push\"}) / sum(namespace_job_route:loki_request_duration_seconds_count:sum_rate{namespace=\"$namespace\", job=~\".+-ingester-http\", route=\"/logproto.Pusher/Push\"})",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "Average",
"refId": "C",
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Latency",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "ms",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": true,
"title": "Ingester",
"titleSize": "h6"
},
{
"collapse": false,
"height": "250px",
"panels": [
{
"aliasColors": {
"1xx": "#EAB839",
"2xx": "#7EB26D",
"3xx": "#6ED0E0",
"4xx": "#EF843C",
"5xx": "#E24D42",
"error": "#E24D42",
"success": "#7EB26D"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 10,
"id": 7,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 0,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 6,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum by (status) (\n label_replace(label_replace(rate(loki_index_request_duration_seconds_count{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"index_chunk\"}[$__rate_interval]),\n \"status\", \"${1}xx\", \"status_code\", \"([0-9])..\"),\n \"status\", \"${1}\", \"status_code\", \"([a-z]+)\"))\n",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{status}}",
"refId": "A",
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "QPS",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
},
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 8,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(loki_index_request_duration_seconds_bucket{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"index_chunk\"}[$__rate_interval])) by (le)) * 1e3",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "99th Percentile",
"refId": "A",
"step": 10
},
{
"expr": "histogram_quantile(0.50, sum(rate(loki_index_request_duration_seconds_bucket{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"index_chunk\"}[$__rate_interval])) by (le)) * 1e3",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "50th Percentile",
"refId": "B",
"step": 10
},
{
"expr": "sum(rate(loki_index_request_duration_seconds_sum{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"index_chunk\"}[$__rate_interval])) * 1e3 / sum(rate(loki_index_request_duration_seconds_count{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"index_chunk\"}[$__rate_interval]))",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "Average",
"refId": "C",
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Latency",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "ms",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": true,
"title": "Index",
"titleSize": "h6"
},
{
"collapse": false,
"height": "250px",
"panels": [
{
"aliasColors": {
"1xx": "#EAB839",
"2xx": "#7EB26D",
"3xx": "#6ED0E0",
"4xx": "#EF843C",
"5xx": "#E24D42",
"error": "#E24D42",
"success": "#7EB26D"
},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 10,
"id": 9,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 0,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 6,
"stack": true,
"steppedLine": false,
"targets": [
{
"expr": "sum by (status) (\n label_replace(label_replace(rate(loki_boltdb_shipper_request_duration_seconds_count{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"WRITE\"}[$__rate_interval]),\n \"status\", \"${1}xx\", \"status_code\", \"([0-9])..\"),\n \"status\", \"${1}\", \"status_code\", \"([a-z]+)\"))\n",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "{{status}}",
"refId": "A",
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "QPS",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
},
{
"aliasColors": { },
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "$datasource",
"fill": 1,
"id": 10,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"links": [ ],
"nullPointMode": "null as zero",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [ ],
"spaceLength": 10,
"span": 6,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "histogram_quantile(0.99, sum(rate(loki_boltdb_shipper_request_duration_seconds_bucket{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"WRITE\"}[$__rate_interval])) by (le)) * 1e3",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "99th Percentile",
"refId": "A",
"step": 10
},
{
"expr": "histogram_quantile(0.50, sum(rate(loki_boltdb_shipper_request_duration_seconds_bucket{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"WRITE\"}[$__rate_interval])) by (le)) * 1e3",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "50th Percentile",
"refId": "B",
"step": 10
},
{
"expr": "sum(rate(loki_boltdb_shipper_request_duration_seconds_sum{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"WRITE\"}[$__rate_interval])) * 1e3 / sum(rate(loki_boltdb_shipper_request_duration_seconds_count{namespace=\"$namespace\",job=~\".+-ingester-http\", operation=\"WRITE\"}[$__rate_interval]))",
"format": "time_series",
"intervalFactor": 2,
"legendFormat": "Average",
"refId": "C",
"step": 10
}
],
"thresholds": [ ],
"timeFrom": null,
"timeShift": null,
"title": "Latency",
"tooltip": {
"shared": true,
"sort": 2,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": [ ]
},
"yaxes": [
{
"format": "ms",
"label": null,
"logBase": 1,
"max": null,
"min": 0,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": false
}
]
}
],
"repeat": null,
"repeatIteration": null,
"repeatRowId": null,
"showTitle": true,
"title": "BoltDB Shipper",
"titleSize": "h6"
}
],
"schemaVersion": 14,
"style": "dark",
"tags": [
"loki",
"logging",
"loki-mixin"
],
"templating": {
"list": [
{
"current": {
"selected": true,
"text": "default",
"value": "default"
},
"hide": 0,
"label": "Data Source",
"name": "datasource",
"options": [ ],
"query": "prometheus",
"refresh": 1,
"regex": "",
"type": "datasource"
},
{
"allValue": null,
"current": {
"selected": false,
"text": "openshift-logging",
"value": "openshift-logging"
},
"datasource": "${datasource}",
"definition": "label_values(loki_build_info, namespace)",
"hide": 0,
"includeAll": false,
"label": "namespace",
"multi": false,
"name": "namespace",
"options": [ ],
"query": "label_values(loki_build_info, namespace)",
"refresh": 1,
"regex": "",
"sort": 2,
"tagValuesQuery": "",
"tags": [ ],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-1h",
"to": "now"
},
"timepicker": {
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"timezone": "utc",
"title": "OpenShift Logging / LokiStack / Writes",
"uid": "F6nRYKuXmFVpVSFQmXr7cgXy5j7UNr",
"version": 0
}

@ -9,6 +9,8 @@ const (
annotationGatewayRouteTimeout = "haproxy.router.openshift.io/timeout"
gatewayRouteTimeoutExtension = 15 * time.Second
dashboardPrometheusRulesName = "lokistack-dashboard-rules"
)
var (

@ -0,0 +1,16 @@
package operator
import "os"
const inClusterNamespaceFile = "/var/run/secrets/kubernetes.io/serviceaccount/namespace"
// GetNamespace returns the namespace the operator is running in from
// the `/var/run/secrets/kubernetes.io/serviceaccount/namespace` file.
func GetNamespace() (string, error) {
b, err := os.ReadFile(inClusterNamespaceFile)
if err != nil {
return "", err
}
return string(b), nil
}

@ -0,0 +1,219 @@
local utils = (import 'github.com/grafana/jsonnet-libs/mixin-utils/utils.libsonnet');
{
loki: {
local withDatasource = function(ds) ds + (
if ds.name == 'datasource' then {
query: 'prometheus',
current: {
selected: true,
text: 'default',
value: 'default',
},
} else {}
),
local withNamespace = function(ns) ns + (
if ns.label == 'namespace' then {
datasource: '${datasource}',
current: {
selected: false,
text: 'openshift-logging',
value: 'openshift-logging',
},
definition: 'label_values(loki_build_info, namespace)',
query: 'label_values(loki_build_info, namespace)',
} else {}
),
local defaultLokiTags = function(t)
std.uniq(t + ['logging', 'loki-mixin']),
local replaceMatchers = function(replacements)
function(p) p {
targets: [
t {
expr: std.foldl(function(x, rp) std.strReplace(x, rp.from, rp.to), replacements, t.expr),
}
for t in p.targets
if std.objectHas(p, 'targets')
],
},
// dropPanels removes unnecessary panels from the loki dashboards
// that are of obsolete usage on our AWS-based deployment environment.
local dropPanels = function(panels, dropList, fn)
[
p
for p in panels
if !std.member(dropList, p.title) && fn(p)
],
// mapPanels applies recursively a set of functions over all panels.
// Note: A Grafana dashboard panel can include other panels.
// Example: Replace job label in expression and add axis units for all panels.
local mapPanels = function(funcs, panels)
[
// Transform the current panel by applying all transformer funcs.
// Keep the last version after foldl ends.
std.foldl(function(agg, fn) fn(agg), funcs, p) + (
// Recursively apply all transformer functions to any
// children panels.
if std.objectHas(p, 'panels') then {
panels: mapPanels(funcs, p.panels),
} else {}
)
for p in panels
],
// mapTemplateParameters applies a static list of transformer functions to
// all dashboard template parameters. The static list includes:
// - cluster-prometheus as datasource based on environment.
local mapTemplateParameters = function(ls)
[
std.foldl(function(x, fn) fn(x), [withDatasource, withNamespace], item)
for item in ls
if item.name != 'cluster'
],
local dropRules = function(rules, dropList)
[
r
for r in rules
if !std.member(dropList, r.record)
],
prometheusRules+: {
local dropList = [
'cluster_job:loki_request_duration_seconds:99quantile',
'cluster_job:loki_request_duration_seconds:50quantile',
'cluster_job:loki_request_duration_seconds:avg',
'cluster_job:loki_request_duration_seconds_bucket:sum_rate',
'cluster_job:loki_request_duration_seconds_sum:sum_rate',
'cluster_job:loki_request_duration_seconds_count:sum_rate',
'cluster_job_route:loki_request_duration_seconds:99quantile',
'cluster_job_route:loki_request_duration_seconds:50quantile',
'cluster_job_route:loki_request_duration_seconds:avg',
'cluster_namespace_job_route:loki_request_duration_seconds:99quantile',
'cluster_namespace_job_route:loki_request_duration_seconds:50quantile',
'cluster_namespace_job_route:loki_request_duration_seconds:avg',
],
groups: [
g {
rules: [
r {
expr: std.strReplace(r.expr, 'cluster, ', ''),
record: std.strReplace(r.record, 'cluster_', ''),
}
for r in dropRules(g.rules, dropList)
],
}
for g in super.groups
],
},
local dropHistograms = function(p)
local elems = std.filter(function(p) p.type == 'heatmap', p.panels);
std.length(elems) == 0,
grafanaDashboards+: {
'loki-retention.json'+: {
local dropList = ['Logs', 'Per Table Marker', 'Sweeper', ''],
local replacements = [
{ from: 'cluster=~"$cluster",', to: '' },
{ from: 'container="compactor"', to: 'container=~".+-compactor"' },
{ from: 'job=~"($namespace)/compactor"', to: 'namespace="$namespace", job=~".+-compactor-http"' },
],
uid: 'RetCujSHzC8gd9i5fck9a3v9n2EvTzA',
title: 'OpenShift Logging / LokiStack / Retention',
tags: defaultLokiTags(super.tags),
rows: [
r {
panels: mapPanels([replaceMatchers(replacements)], r.panels),
}
for r in dropPanels(super.rows, dropList, function(p) true)
],
templating+: {
list: mapTemplateParameters(super.list),
},
},
'loki-chunks.json'+: {
local dropList = ['Utilization'],
uid: 'GtCujSHzC8gd9i5fck9a3v9n2EvTzA',
title: 'OpenShift Logging / LokiStack / Chunks',
tags: defaultLokiTags(super.tags),
showMultiCluster:: false,
namespaceQuery:: 'label_values(loki_build_info, namespace)',
namespaceType:: 'query',
labelsSelector:: 'namespace="$namespace", job=~".+-ingester-http"',
rows: [
r
for r in dropPanels(super.rows, dropList, dropHistograms)
],
templating+: {
list: mapTemplateParameters(super.list),
},
},
'loki-reads.json'+: {
local dropList = ['BigTable', 'Ingester - Zone Aware'],
uid: '62q5jjYwhVSaz4Mcrm8tV3My3gcKED',
title: 'OpenShift Logging / LokiStack / Reads',
tags: defaultLokiTags(super.tags),
showMultiCluster:: false,
namespaceQuery:: 'label_values(loki_build_info, namespace)',
namespaceType:: 'query',
matchers:: {
cortexgateway:: [],
queryFrontend:: [
utils.selector.eq('namespace', '$namespace'),
utils.selector.re('job', '.+-query-frontend-http'),
],
querier:: [
utils.selector.eq('namespace', '$namespace'),
utils.selector.re('job', '.+-querier-http'),
],
ingester:: [
utils.selector.eq('namespace', '$namespace'),
utils.selector.re('job', '.+-ingester-http'),
],
ingesterZoneAware:: [],
querierOrIndexGateway:: [
utils.selector.eq('namespace', '$namespace'),
utils.selector.re('job', '.+-index-gateway-http'),
],
},
rows: dropPanels(super.rows, dropList, function(p) true),
templating+: {
list: mapTemplateParameters(super.list),
},
},
'loki-writes.json'+: {
local dropList = ['Ingester - Zone Aware'],
uid: 'F6nRYKuXmFVpVSFQmXr7cgXy5j7UNr',
title: 'OpenShift Logging / LokiStack / Writes',
tags: defaultLokiTags(super.tags),
showMultiCluster:: false,
namespaceQuery:: 'label_values(loki_build_info, namespace)',
namespaceType:: 'query',
matchers:: {
cortexgateway:: [],
distributor:: [
utils.selector.eq('namespace', '$namespace'),
utils.selector.re('job', '.+-distributor-http'),
utils.selector.eq('route', 'loki_api_v1_push'),
],
ingester:: [
utils.selector.eq('namespace', '$namespace'),
utils.selector.re('job', '.+-ingester-http'),
],
ingester_zone:: [],
},
rows: dropPanels(super.rows, dropList, function(p) true),
templating+: {
list: mapTemplateParameters(super.list),
},
},
},
},
}

@ -0,0 +1,10 @@
local cfg = (import 'config.libsonnet');
local loki = (import 'github.com/grafana/loki/production/loki-mixin/mixin.libsonnet') + cfg.loki;
{
'grafana-dashboard-lokistack-chunks.json': loki.grafanaDashboards['loki-chunks.json'],
'grafana-dashboard-lokistack-reads.json': loki.grafanaDashboards['loki-reads.json'],
'grafana-dashboard-lokistack-writes.json': loki.grafanaDashboards['loki-writes.json'],
'grafana-dashboard-lokistack-retention.json': loki.grafanaDashboards['loki-retention.json'],
'grafana-dashboard-lokistack-rules.json': loki.prometheusRules,
}

@ -0,0 +1,15 @@
{
"version": 1,
"dependencies": [
{
"source": {
"git": {
"remote": "https://github.com/grafana/loki.git",
"subdir": "production/loki-mixin"
}
},
"version": "v2.8.2"
}
],
"legacyImports": true
}

@ -0,0 +1,66 @@
{
"version": 1,
"dependencies": [
{
"source": {
"git": {
"remote": "https://github.com/grafana/grafonnet-lib.git",
"subdir": "grafonnet"
}
},
"version": "f0b70307b8e5f12236b277883d998af129a8211f",
"sum": "342u++/7rViR/zj2jeJOjshzglkZ1SY+hFNuyCBFMdc="
},
{
"source": {
"git": {
"remote": "https://github.com/grafana/jsonnet-libs.git",
"subdir": "grafana-builder"
}
},
"version": "e8e0f6f536f3397a22a49bd9014a7e0a1dfa151d",
"sum": "tDR6yT2GVfw0wTU12iZH+m01HrbIr6g/xN+/8nzNkU0="
},
{
"source": {
"git": {
"remote": "https://github.com/grafana/jsonnet-libs.git",
"subdir": "mixin-utils"
}
},
"version": "e8e0f6f536f3397a22a49bd9014a7e0a1dfa151d",
"sum": "N1Uz6AJpnQXv4w8F6lxDqMwoT7azPVrpuhJ4N7Oqzcw="
},
{
"source": {
"git": {
"remote": "https://github.com/grafana/loki.git",
"subdir": "production/loki-mixin"
}
},
"version": "b7ce95402ac5e2712432e8bfc638b262793b8811",
"sum": "iYoW3v95aLI2zY2zoQvS0TrIe6kVzdIYyCaGUJ0oC+s="
},
{
"source": {
"git": {
"remote": "https://github.com/grafana/mimir.git",
"subdir": "operations/mimir-mixin"
}
},
"version": "154abe81c5c6b3f263e2b39084f186dd564860af",
"sum": "xnakaDny0zXG7mhuyUH90swndHinwpt5/RRVFotDqFY="
},
{
"source": {
"git": {
"remote": "https://github.com/prometheus-operator/kube-prometheus.git",
"subdir": "jsonnet/kube-prometheus/lib"
}
},
"version": "b176baa776a4d6002fe162846dacb424965df1bc",
"sum": "QKRgrgEZ3k9nLmLCrDBaeIGVqQZf+AvZTcnhdLk3TrA="
}
],
"legacyImports": false
}

@ -0,0 +1 @@
(import 'dashboards.jsonnet')

@ -9,6 +9,7 @@ import (
"github.com/ViaQ/logerr/v2/kverrors"
"github.com/ViaQ/logerr/v2/log"
"github.com/grafana/loki/operator/internal/operator"
"github.com/grafana/loki/operator/internal/validation"
"github.com/grafana/loki/operator/internal/validation/openshift"
@ -110,6 +111,26 @@ func main() {
logger.Error(err, "unable to create controller", "controller", "lokistack")
os.Exit(1)
}
if ctrlCfg.Gates.ServiceMonitors && ctrlCfg.Gates.OpenShift.Enabled && ctrlCfg.Gates.OpenShift.Dashboards {
var ns string
ns, err = operator.GetNamespace()
if err != nil {
logger.Error(err, "unable to read in operator namespace")
os.Exit(1)
}
if err = (&lokictrl.DashboardsReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
Log: logger.WithName("controllers").WithName("lokistack-dashboards"),
OperatorNs: ns,
}).SetupWithManager(mgr); err != nil {
logger.Error(err, "unable to create controller", "controller", "lokistack-dashboards")
os.Exit(1)
}
}
if ctrlCfg.Gates.LokiStackWebhook {
v := &validation.LokiStackValidator{}
if err = v.SetupWebhookWithManager(mgr); err != nil {

Loading…
Cancel
Save