From 33172b6466b57e23dc0479fbb9d7af53362dba91 Mon Sep 17 00:00:00 2001 From: Armando Ruocco Date: Fri, 8 Aug 2025 01:15:19 +0200 Subject: [PATCH] feat: add upstream backup and recovery metrics (#459) Introduce two new Prometheus metrics sourced from the Barman Cloud plugin: - `barman_cloud_cloudnative_pg_io_first_recoverability_point` - `barman_cloud_cloudnative_pg_io_last_available_backup_timestamp` These metrics supersede the following deprecated CNPG metrics: - `cnpg_collector_first_recoverability_point` - `cnpg_collector_last_available_backup_timestamp` Depends on: https://github.com/cloudnative-pg/cloudnative-pg/pull/8033 Relates to: #380 Signed-off-by: Armando Ruocco Signed-off-by: Leonardo Cecchi Co-authored-by: Leonardo Cecchi --- go.mod | 30 ++--- go.sum | 64 +++++----- hack/build-dev-image.sh | 14 --- hack/examples/cluster-example.yaml | 1 + hack/minio/kustomization.yaml | 8 ++ hack/minio/minio-certificate.yaml | 21 ++++ hack/minio/minio-delete.sh | 1 + hack/minio/minio-deployment.yaml | 18 ++- hack/minio/minio-service.yaml | 5 + hack/minio/selfsigned-issuer.yaml | 6 + internal/cnpgi/instance/backup.go | 38 ++++++ internal/cnpgi/instance/identity.go | 7 ++ internal/cnpgi/instance/manager.go | 12 ++ internal/cnpgi/instance/metrics.go | 135 +++++++++++++++++++++ internal/cnpgi/instance/metrics_test.go | 123 +++++++++++++++++++ internal/cnpgi/instance/recovery_window.go | 43 +++++++ internal/cnpgi/instance/retention.go | 34 +----- internal/cnpgi/instance/start.go | 4 + internal/cnpgi/instance/suite_test.go | 13 ++ 19 files changed, 481 insertions(+), 96 deletions(-) delete mode 100755 hack/build-dev-image.sh create mode 100644 hack/minio/kustomization.yaml create mode 100644 hack/minio/minio-certificate.yaml create mode 100644 hack/minio/minio-delete.sh create mode 100644 hack/minio/selfsigned-issuer.yaml create mode 100644 internal/cnpgi/instance/metrics.go create mode 100644 internal/cnpgi/instance/metrics_test.go create mode 100644 internal/cnpgi/instance/recovery_window.go create mode 100644 internal/cnpgi/instance/suite_test.go diff --git a/go.mod b/go.mod index 1f855c7..7a6ef90 100644 --- a/go.mod +++ b/go.mod @@ -7,14 +7,14 @@ require ( github.com/cloudnative-pg/api v1.26.0 github.com/cloudnative-pg/barman-cloud v0.3.1 github.com/cloudnative-pg/cloudnative-pg v1.26.0 - github.com/cloudnative-pg/cnpg-i v0.2.1 + github.com/cloudnative-pg/cnpg-i v0.2.2-0.20250723093238-963c368523c2 github.com/cloudnative-pg/cnpg-i-machinery v0.4.0 github.com/cloudnative-pg/machinery v0.3.0 github.com/onsi/ginkgo/v2 v2.23.4 - github.com/onsi/gomega v1.37.0 + github.com/onsi/gomega v1.38.0 github.com/spf13/cobra v1.9.1 github.com/spf13/viper v1.20.1 - google.golang.org/grpc v1.73.0 + google.golang.org/grpc v1.74.2 gopkg.in/yaml.v3 v3.0.1 k8s.io/api v0.33.2 k8s.io/apiextensions-apiserver v0.33.2 @@ -27,7 +27,7 @@ require ( ) require ( - cel.dev/expr v0.23.1 // indirect + cel.dev/expr v0.24.0 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect @@ -91,12 +91,12 @@ require ( github.com/xlab/treeprint v1.2.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/sdk v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect go.opentelemetry.io/proto/otlp v1.4.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.uber.org/multierr v1.11.0 // indirect @@ -104,17 +104,17 @@ require ( go.yaml.in/yaml/v2 v2.4.2 // indirect go.yaml.in/yaml/v3 v3.0.3 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.14.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect + golang.org/x/sync v0.15.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/term v0.32.0 // indirect - golang.org/x/text v0.25.0 // indirect + golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.9.0 // indirect - golang.org/x/tools v0.31.0 // indirect + golang.org/x/tools v0.33.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/go.sum b/go.sum index 8ee5d18..9329d8d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -cel.dev/expr v0.23.1 h1:K4KOtPCJQjVggkARsjG9RWXP6O4R73aHeJMa/dmCQQg= -cel.dev/expr v0.23.1/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -22,8 +22,8 @@ github.com/cloudnative-pg/barman-cloud v0.3.1 h1:kzkY77k2lN/caoyh7ibXDSZjJeSJTNv github.com/cloudnative-pg/barman-cloud v0.3.1/go.mod h1:4HL3AjY9oEl2Ed0HSkyvTZEQPhwyFOaAnuCz9lfVeYQ= github.com/cloudnative-pg/cloudnative-pg v1.26.0 h1:X+ayWg6TXqglziSPejKiviPTf/CSh4AmYXx1mrS3i8s= github.com/cloudnative-pg/cloudnative-pg v1.26.0/go.mod h1:aNh0g7UHKxyka4HYOqLaYDWtZ6tORQ7HbIHLvb79sx8= -github.com/cloudnative-pg/cnpg-i v0.2.1 h1:g96BE1ojdiFtDwtb7tg5wUF9a2kAh0eVg4SkjsO8jnk= -github.com/cloudnative-pg/cnpg-i v0.2.1/go.mod h1:kPfJpPGAKN1/2xvwBcC3WzMP46pj3sKLHLNB8NHr77U= +github.com/cloudnative-pg/cnpg-i v0.2.2-0.20250723093238-963c368523c2 h1:uLooqDE54OE0tBMdwHws1CwD3X4098K9oZyNt3xdQuE= +github.com/cloudnative-pg/cnpg-i v0.2.2-0.20250723093238-963c368523c2/go.mod h1:pJaTIy0d6Yd3CA554AHZD81CJM7/jiDNmk7BFTMb3Fk= github.com/cloudnative-pg/cnpg-i-machinery v0.4.0 h1:16wQt9qFFqvyxeg+9dPt8ic8dh3PRPq0jCGXVuZyjO4= github.com/cloudnative-pg/cnpg-i-machinery v0.4.0/go.mod h1:4MCJzbCOsB7ianxlm8rqD+gDpkgVTHoTuglle/i72WA= github.com/cloudnative-pg/machinery v0.3.0 h1:t1DzXGeK3RUYXS5KWIdIk30oh4EmwxZ+6sWM4wJDBac= @@ -131,8 +131,8 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/onsi/ginkgo/v2 v2.23.4 h1:ktYTpKJAVZnDT4VjxSbiBenUjmlL/5QkBEocaWXiQus= github.com/onsi/ginkgo/v2 v2.23.4/go.mod h1:Bt66ApGPBFzHyR+JO10Zbt0Gsp4uWxu5mIOTusL46e8= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/gomega v1.38.0 h1:c/WX+w8SLAinvuKKQFh77WEucCnPk4j2OTUr7lt7BeY= +github.com/onsi/gomega v1.38.0/go.mod h1:OcXcwId0b9QsE7Y49u+BTrL4IdKOBOKnD6VQNTJEB6o= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -202,20 +202,20 @@ go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJyS go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -241,15 +241,15 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -259,28 +259,28 @@ golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= -golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463 h1:hE3bRWtU6uceqlh4fhrSnUyjKHMKB9KrTLLG+bc0ddM= -google.golang.org/genproto/googleapis/api v0.0.0-20250324211829-b45e905df463/go.mod h1:U90ffi8eUL9MwPcrJylN5+Mk2v3vuPDptd5yyNUiRR8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463 h1:e0AIkUUhxyBKh6ssZNrAMeqhA7RKUj42346d1y02i2g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250324211829-b45e905df463/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= -google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a h1:SGktgSolFCo75dnHJF2yMvnns6jCmHFJ0vE4Vn2JKvQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250528174236-200df99c418a/go.mod h1:a77HrdMjoeKbnd2jmgcWdaS++ZLZAEq3orIOAEIKiVw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= +google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/hack/build-dev-image.sh b/hack/build-dev-image.sh deleted file mode 100755 index 8f6f6cd..0000000 --- a/hack/build-dev-image.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env sh - -# This script builds the images of the barman cloud plugin, to be used -# to quickly test images in a development environment. -# -# After each run, the built images will have these names: -# -# - `plugin-barman-cloud:dev` -# - `plugin-barman-cloud-sidecar:dev` - -set -eu - -docker build -t plugin-barman-cloud:dev --file containers/Dockerfile.plugin . -docker build -t plugin-barman-cloud-sidecar:dev --file containers/Dockerfile.sidecar . diff --git a/hack/examples/cluster-example.yaml b/hack/examples/cluster-example.yaml index 421f7a4..1d240b3 100644 --- a/hack/examples/cluster-example.yaml +++ b/hack/examples/cluster-example.yaml @@ -4,6 +4,7 @@ metadata: name: cluster-example spec: instances: 3 + imagePullPolicy: Always plugins: - name: barman-cloud.cloudnative-pg.io isWALArchiver: true diff --git a/hack/minio/kustomization.yaml b/hack/minio/kustomization.yaml new file mode 100644 index 0000000..de537e5 --- /dev/null +++ b/hack/minio/kustomization.yaml @@ -0,0 +1,8 @@ +resources: +- minio-deployment.yaml +- minio-pvc.yaml +- minio-secret.yaml +- minio-service.yaml +- minio-certificate.yaml +- selfsigned-issuer.yaml + diff --git a/hack/minio/minio-certificate.yaml b/hack/minio/minio-certificate.yaml new file mode 100644 index 0000000..845fc7d --- /dev/null +++ b/hack/minio/minio-certificate.yaml @@ -0,0 +1,21 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: minio-server +spec: + secretName: minio-server-tls + commonName: minio + dnsNames: + - minio + + duration: 2160h # 90d + renewBefore: 360h # 15d + + isCA: false + usages: + - server auth + + issuerRef: + name: selfsigned-issuer + kind: Issuer + group: cert-manager.io diff --git a/hack/minio/minio-delete.sh b/hack/minio/minio-delete.sh new file mode 100644 index 0000000..c098b3c --- /dev/null +++ b/hack/minio/minio-delete.sh @@ -0,0 +1 @@ +kubectl exec -ti mc -- mc rm -r --force minio/backups diff --git a/hack/minio/minio-deployment.yaml b/hack/minio/minio-deployment.yaml index 315ea6e..4bd3d07 100644 --- a/hack/minio/minio-deployment.yaml +++ b/hack/minio/minio-deployment.yaml @@ -22,16 +22,20 @@ spec: volumeMounts: - mountPath: /data name: data + - mountPath: /opt/minio/certs + name: certs args: - server + - --certs-dir + - /opt/minio/certs - /data env: - - name: MINIO_ACCESS_KEY + - name: MINIO_ROOT_USER valueFrom: secretKeyRef: name: minio key: ACCESS_KEY_ID - - name: MINIO_SECRET_KEY + - name: MINIO_ROOT_PASSWORD valueFrom: secretKeyRef: name: minio @@ -40,3 +44,13 @@ spec: - name: data persistentVolumeClaim: claimName: minio + - name: certs + projected: + sources: + - secret: + name: minio-server-tls + items: + - key: tls.crt + path: public.crt + - key: tls.key + path: private.key diff --git a/hack/minio/minio-service.yaml b/hack/minio/minio-service.yaml index c2b356b..401c745 100644 --- a/hack/minio/minio-service.yaml +++ b/hack/minio/minio-service.yaml @@ -9,3 +9,8 @@ spec: - protocol: TCP port: 9000 targetPort: 9000 + name: api + - protocol: TCP + port: 36261 + targetPort: 36261 + name: webui diff --git a/hack/minio/selfsigned-issuer.yaml b/hack/minio/selfsigned-issuer.yaml new file mode 100644 index 0000000..8d3d6ee --- /dev/null +++ b/hack/minio/selfsigned-issuer.yaml @@ -0,0 +1,6 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer +spec: + selfSigned: {} diff --git a/internal/cnpgi/instance/backup.go b/internal/cnpgi/instance/backup.go index 23b57ca..155a539 100644 --- a/internal/cnpgi/instance/backup.go +++ b/internal/cnpgi/instance/backup.go @@ -7,6 +7,7 @@ import ( "time" barmanBackup "github.com/cloudnative-pg/barman-cloud/pkg/backup" + barmanCommand "github.com/cloudnative-pg/barman-cloud/pkg/command" barmanCredentials "github.com/cloudnative-pg/barman-cloud/pkg/credentials" "github.com/cloudnative-pg/cloudnative-pg/pkg/postgres" "github.com/cloudnative-pg/cnpg-i/pkg/backup" @@ -114,6 +115,43 @@ func (b BackupServiceImplementation) Backup( } contextLogger.Info("Backup completed", "backup", executedBackupInfo.ID) + + // Refresh the recovery window + contextLogger.Info( + "Refreshing the recovery window", + "backupName", executedBackupInfo.BackupName, + ) + backupList, err := barmanCommand.GetBackupList( + ctx, + &objectStore.Spec.Configuration, + configuration.ServerName, + env, + ) + if err != nil { + contextLogger.Error(err, "while reading the backup list") + return nil, err + } + + if err := updateRecoveryWindow( + ctx, + b.Client, + backupList, + &objectStore, + configuration.ServerName, + ); err != nil { + contextLogger.Error( + err, + "Error while updating the recovery window in the ObjectStore status stanza. Skipping.", + "backupName", executedBackupInfo.BackupName, + ) + } else { + contextLogger.Debug( + "backupName", executedBackupInfo.BackupName, + "Updated the recovery window in the ObjectStore status stanza", + "serverRecoveryWindow", objectStore.Status.ServerRecoveryWindow, + ) + } + return &backup.BackupResult{ BackupId: executedBackupInfo.ID, BackupName: executedBackupInfo.BackupName, diff --git a/internal/cnpgi/instance/identity.go b/internal/cnpgi/instance/identity.go index ef9c845..e7e7d16 100644 --- a/internal/cnpgi/instance/identity.go +++ b/internal/cnpgi/instance/identity.go @@ -44,6 +44,13 @@ func (i IdentityImplementation) GetPluginCapabilities( }, }, }, + { + Type: &identity.PluginCapability_Service_{ + Service: &identity.PluginCapability_Service{ + Type: identity.PluginCapability_Service_TYPE_METRICS, + }, + }, + }, }, }, nil } diff --git a/internal/cnpgi/instance/manager.go b/internal/cnpgi/instance/manager.go index e0f3aeb..cb7f710 100644 --- a/internal/cnpgi/instance/manager.go +++ b/internal/cnpgi/instance/manager.go @@ -35,6 +35,18 @@ func Start(ctx context.Context) error { controllerOptions := ctrl.Options{ Scheme: scheme, Client: client.Options{ + // Important: the caching options below are used by + // controller-runtime only. + // The plugin code uses an enhanced client with a + // custom caching strategy specifically for ObjectStores + // and Clusters. + // + // This custom strategy is necessary because we lack + // permission to list these resources at the namespace + // level. Additionally, controller-runtime does not + // support caching a closed (explicit) set of objects + // within a namespace - it can only cache either individual + // objects or all objects in a namespace. Cache: &client.CacheOptions{ DisableFor: []client.Object{ &corev1.Secret{}, diff --git a/internal/cnpgi/instance/metrics.go b/internal/cnpgi/instance/metrics.go new file mode 100644 index 0000000..f1614a1 --- /dev/null +++ b/internal/cnpgi/instance/metrics.go @@ -0,0 +1,135 @@ +package instance + +import ( + "context" + "fmt" + "strings" + + "github.com/cloudnative-pg/cnpg-i/pkg/metrics" + "github.com/cloudnative-pg/machinery/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/client" + + barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1" + "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata" + "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/operator/config" +) + +// Sanitize the plugin name to be a valid Prometheus metric namespace +var metricsDomain = strings.NewReplacer(".", "_", "-", "_").Replace(metadata.PluginName) + +type metricsImpl struct { + // important the client should be one with a underlying cache + Client client.Client + metrics.UnimplementedMetricsServer +} + +func buildFqName(name string) string { + // Build the fully qualified name for the metric + return fmt.Sprintf("%s_%s", metricsDomain, strings.NewReplacer(".", "_", "-", "_").Replace(name)) +} + +var ( + firstRecoverabilityPointMetricName = buildFqName("first_recoverability_point") + lastAvailableBackupTimestampMetricName = buildFqName("last_available_backup_timestamp") +) + +func (m metricsImpl) GetCapabilities( + ctx context.Context, + _ *metrics.MetricsCapabilitiesRequest, +) (*metrics.MetricsCapabilitiesResult, error) { + contextLogger := log.FromContext(ctx) + contextLogger.Trace("metrics capabilities call received") + + return &metrics.MetricsCapabilitiesResult{ + Capabilities: []*metrics.MetricsCapability{ + { + Type: &metrics.MetricsCapability_Rpc{ + Rpc: &metrics.MetricsCapability_RPC{ + Type: metrics.MetricsCapability_RPC_TYPE_METRICS, + }, + }, + }, + }, + }, nil +} + +func (m metricsImpl) Define( + ctx context.Context, + _ *metrics.DefineMetricsRequest, +) (*metrics.DefineMetricsResult, error) { + contextLogger := log.FromContext(ctx) + contextLogger.Trace("metrics define call received") + + return &metrics.DefineMetricsResult{ + Metrics: []*metrics.Metric{ + { + FqName: firstRecoverabilityPointMetricName, + Help: "The first point of recoverability for the cluster as a unix timestamp", + ValueType: &metrics.MetricType{Type: metrics.MetricType_TYPE_GAUGE}, + }, + { + FqName: lastAvailableBackupTimestampMetricName, + Help: "The last available backup as a unix timestamp", + ValueType: &metrics.MetricType{Type: metrics.MetricType_TYPE_GAUGE}, + }, + }, + }, nil +} + +func (m metricsImpl) Collect( + ctx context.Context, + req *metrics.CollectMetricsRequest, +) (*metrics.CollectMetricsResult, error) { + contextLogger := log.FromContext(ctx) + contextLogger.Trace("metrics collect call received") + + configuration, err := config.NewFromClusterJSON(req.ClusterDefinition) + if err != nil { + contextLogger.Error(err, "while creating configuration from cluster definition") + return nil, fmt.Errorf("while creating configuration from cluster definition: %w", err) + } + + var objectStore barmancloudv1.ObjectStore + if err := m.Client.Get(ctx, configuration.GetBarmanObjectKey(), &objectStore); err != nil { + contextLogger.Error(err, "while getting object store", "key", configuration.GetRecoveryBarmanObjectKey()) + return nil, err + } + + x, ok := objectStore.Status.ServerRecoveryWindow[configuration.ServerName] + if !ok { + return &metrics.CollectMetricsResult{ + Metrics: []*metrics.CollectMetric{ + { + FqName: firstRecoverabilityPointMetricName, + Value: 0, + }, + { + FqName: lastAvailableBackupTimestampMetricName, + Value: 0, + }, + }, + }, nil + } + + var firstRecoverabilityPoint float64 + var lastAvailableBackup float64 + if x.FirstRecoverabilityPoint != nil { + firstRecoverabilityPoint = float64(x.FirstRecoverabilityPoint.Unix()) + } + if x.LastSuccessfulBackupTime != nil { + lastAvailableBackup = float64(x.LastSuccessfulBackupTime.Unix()) + } + + return &metrics.CollectMetricsResult{ + Metrics: []*metrics.CollectMetric{ + { + FqName: firstRecoverabilityPointMetricName, + Value: firstRecoverabilityPoint, + }, + { + FqName: lastAvailableBackupTimestampMetricName, + Value: lastAvailableBackup, + }, + }, + }, nil +} diff --git a/internal/cnpgi/instance/metrics_test.go b/internal/cnpgi/instance/metrics_test.go new file mode 100644 index 0000000..1841f2b --- /dev/null +++ b/internal/cnpgi/instance/metrics_test.go @@ -0,0 +1,123 @@ +package instance + +import ( + "context" + "encoding/json" + cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" + "github.com/cloudnative-pg/plugin-barman-cloud/internal/cnpgi/metadata" + "k8s.io/utils/ptr" + "time" + + "github.com/cloudnative-pg/cnpg-i/pkg/metrics" + barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var _ = Describe("Metrics Collect method", func() { + const ( + clusterName = "test-cluster" + namespace = "test-ns" + objectStoreName = "test-object-store" + ) + + var ( + fakeClient client.Client + m metricsImpl + ctx context.Context + req *metrics.CollectMetricsRequest + objectStore *barmancloudv1.ObjectStore + ) + + BeforeEach(func() { + ctx = context.Background() + scheme := runtime.NewScheme() + Expect(barmancloudv1.AddToScheme(scheme)).To(Succeed()) + + // Timestamps for the test + firstRecoverabilityPoint := metav1.NewTime(time.Now().Add(-24 * time.Hour)) + lastSuccessfulBackupTime := metav1.NewTime(time.Now()) + + // Create a fake ObjectStore with a status + objectStore = &barmancloudv1.ObjectStore{ + ObjectMeta: metav1.ObjectMeta{ + Name: objectStoreName, + Namespace: namespace, + }, + Status: barmancloudv1.ObjectStoreStatus{ + ServerRecoveryWindow: map[string]barmancloudv1.RecoveryWindow{ + clusterName: { + FirstRecoverabilityPoint: &firstRecoverabilityPoint, + LastSuccessfulBackupTime: &lastSuccessfulBackupTime, + }, + }, + }, + } + + // Create a fake client with the ObjectStore + fakeClient = fake.NewClientBuilder(). + WithScheme(scheme). + WithStatusSubresource(&barmancloudv1.ObjectStore{}). + WithObjects(objectStore). + Build() + + m = metricsImpl{Client: fakeClient} + + // Create a minimal cluster definition + clusterDefinition := cnpgv1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName, + Namespace: namespace, + }, + Spec: cnpgv1.ClusterSpec{ + Plugins: []cnpgv1.PluginConfiguration{ + { + Name: metadata.PluginName, + Enabled: ptr.To(true), + Parameters: map[string]string{ + "serverName": clusterName, + "barmanObjectName": objectStoreName, + }, + }, + }, + }, + } + clusterJSON, err := json.Marshal(clusterDefinition) + Expect(err).ToNot(HaveOccurred()) + + req = &metrics.CollectMetricsRequest{ + ClusterDefinition: clusterJSON, + } + }) + + It("should collect metrics successfully", func() { + res, err := m.Collect(ctx, req) + Expect(err).ToNot(HaveOccurred()) + Expect(res).ToNot(BeNil()) + Expect(res.Metrics).To(HaveLen(2)) + + // Verify the metrics + metricsMap := make(map[string]float64) + for _, metric := range res.Metrics { + metricsMap[metric.FqName] = metric.Value + } + + // Check timestamp metrics + expectedFirstPoint, _ := metricsMap[firstRecoverabilityPointMetricName] + Expect(expectedFirstPoint).To(BeNumerically("~", float64(objectStore.Status.ServerRecoveryWindow[clusterName].FirstRecoverabilityPoint.Unix()), 1)) + + expectedLastBackup, _ := metricsMap[lastAvailableBackupTimestampMetricName] + Expect(expectedLastBackup).To(BeNumerically("~", float64(objectStore.Status.ServerRecoveryWindow[clusterName].LastSuccessfulBackupTime.Unix()), 1)) + }) + + It("should return an error if the object store is not found", func() { + // Use a client without any objects + m.Client = fake.NewClientBuilder().Build() + _, err := m.Collect(ctx, req) + Expect(err).To(HaveOccurred()) + }) +}) diff --git a/internal/cnpgi/instance/recovery_window.go b/internal/cnpgi/instance/recovery_window.go new file mode 100644 index 0000000..8e3aea9 --- /dev/null +++ b/internal/cnpgi/instance/recovery_window.go @@ -0,0 +1,43 @@ +package instance + +import ( + "context" + "time" + + "github.com/cloudnative-pg/barman-cloud/pkg/catalog" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client" + + barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1" +) + +// updateRecoveryWindow updates the recovery window inside the object +// store status subresource +func updateRecoveryWindow( + ctx context.Context, + c client.Client, + backupList *catalog.Catalog, + objectStore *barmancloudv1.ObjectStore, + serverName string, +) error { + // Set the recovery window inside the barman object store object + convertTime := func(t *time.Time) *metav1.Time { + if t == nil { + return nil + } + return ptr.To(metav1.NewTime(*t)) + } + + recoveryWindow := barmancloudv1.RecoveryWindow{ + FirstRecoverabilityPoint: convertTime(backupList.GetFirstRecoverabilityPoint()), + LastSuccessfulBackupTime: convertTime(backupList.GetLastSuccessfulBackupTime()), + } + + if objectStore.Status.ServerRecoveryWindow == nil { + objectStore.Status.ServerRecoveryWindow = make(map[string]barmancloudv1.RecoveryWindow) + } + objectStore.Status.ServerRecoveryWindow[serverName] = recoveryWindow + + return c.Status().Update(ctx, objectStore) +} diff --git a/internal/cnpgi/instance/retention.go b/internal/cnpgi/instance/retention.go index 6b68dfb..8d64955 100644 --- a/internal/cnpgi/instance/retention.go +++ b/internal/cnpgi/instance/retention.go @@ -7,15 +7,12 @@ import ( "slices" "time" - "github.com/cloudnative-pg/barman-cloud/pkg/catalog" barmanCommand "github.com/cloudnative-pg/barman-cloud/pkg/command" barmanCredentials "github.com/cloudnative-pg/barman-cloud/pkg/credentials" cnpgv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1" "github.com/cloudnative-pg/machinery/pkg/log" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" - "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" barmancloudv1 "github.com/cloudnative-pg/plugin-barman-cloud/api/v1" @@ -157,36 +154,7 @@ func (c *CatalogMaintenanceRunnable) maintenance( return err } - return c.updateRecoveryWindow(ctx, backupList, objectStore, configuration.ServerName) -} - -// updateRecoveryWindow updates the recovery window inside the object -// store status subresource -func (c *CatalogMaintenanceRunnable) updateRecoveryWindow( - ctx context.Context, - backupList *catalog.Catalog, - objectStore *barmancloudv1.ObjectStore, - serverName string, -) error { - // Set the recovery window inside the barman object store object - convertTime := func(t *time.Time) *metav1.Time { - if t == nil { - return nil - } - return ptr.To(metav1.NewTime(*t)) - } - - recoveryWindow := barmancloudv1.RecoveryWindow{ - FirstRecoverabilityPoint: convertTime(backupList.GetFirstRecoverabilityPoint()), - LastSuccessfulBackupTime: convertTime(backupList.GetLastSuccessfulBackupTime()), - } - - if objectStore.Status.ServerRecoveryWindow == nil { - objectStore.Status.ServerRecoveryWindow = make(map[string]barmancloudv1.RecoveryWindow) - } - objectStore.Status.ServerRecoveryWindow[serverName] = recoveryWindow - - return c.Client.Status().Update(ctx, objectStore) + return updateRecoveryWindow(ctx, c.Client, backupList, objectStore, configuration.ServerName) } // deleteBackupsNotInCatalog deletes all Backup objects pointing to the given cluster that are not diff --git a/internal/cnpgi/instance/start.go b/internal/cnpgi/instance/start.go index 5eefa79..ec0affe 100644 --- a/internal/cnpgi/instance/start.go +++ b/internal/cnpgi/instance/start.go @@ -5,6 +5,7 @@ import ( "github.com/cloudnative-pg/cnpg-i-machinery/pkg/pluginhelper/http" "github.com/cloudnative-pg/cnpg-i/pkg/backup" + "github.com/cloudnative-pg/cnpg-i/pkg/metrics" "github.com/cloudnative-pg/cnpg-i/pkg/wal" "google.golang.org/grpc" "sigs.k8s.io/controller-runtime/pkg/client" @@ -37,6 +38,9 @@ func (c *CNPGI) Start(ctx context.Context) error { Client: c.Client, InstanceName: c.InstanceName, }) + metrics.RegisterMetricsServer(server, &metricsImpl{ + Client: c.Client, + }) common.AddHealthCheck(server) return nil } diff --git a/internal/cnpgi/instance/suite_test.go b/internal/cnpgi/instance/suite_test.go new file mode 100644 index 0000000..5ae3e3d --- /dev/null +++ b/internal/cnpgi/instance/suite_test.go @@ -0,0 +1,13 @@ +package instance_test + +import ( + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +func TestInstance(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Instance Suite") +}