diff --git a/go.mod b/go.mod index 93afa34..7a6ef90 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( 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.74.2 @@ -20,7 +20,6 @@ require ( k8s.io/apiextensions-apiserver v0.33.2 k8s.io/apimachinery v0.33.2 k8s.io/client-go v0.33.2 - k8s.io/component-base v0.33.2 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 sigs.k8s.io/controller-runtime v0.21.0 sigs.k8s.io/kustomize/api v0.20.0 @@ -105,14 +104,14 @@ 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.40.0 // indirect + golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sync v0.14.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-20250528174236-200df99c418a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect @@ -120,6 +119,7 @@ require ( gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect k8s.io/apiserver v0.33.2 // indirect + k8s.io/component-base v0.33.2 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect diff --git a/go.sum b/go.sum index 952bd8a..9329d8d 100644 --- a/go.sum +++ b/go.sum @@ -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= @@ -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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= +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,16 +259,16 @@ 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= diff --git a/internal/cnpgi/instance/metrics.go b/internal/cnpgi/instance/metrics.go index 37ad9d6..bd307d6 100644 --- a/internal/cnpgi/instance/metrics.go +++ b/internal/cnpgi/instance/metrics.go @@ -72,12 +72,14 @@ func (m metricsImpl) Define( ValueType: &metrics.MetricType{Type: metrics.MetricType_TYPE_GAUGE}, }, { - FqName: firstRecoverabilityPointMetricName, - Help: "The first point of recoverability for the cluster as a unix timestamp", + 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", + FqName: lastAvailableBackupTimestampMetricName, + Help: "The last available backup as a unix timestamp", + ValueType: &metrics.MetricType{Type: metrics.MetricType_TYPE_GAUGE}, }, }, }, nil @@ -102,7 +104,19 @@ func (m metricsImpl) Collect( return nil, err } - x := objectStore.Status.ServerRecoveryWindow[configuration.ServerName] + x, ok := objectStore.Status.ServerRecoveryWindow[configuration.ServerName] + if !ok { + return nil, fmt.Errorf("no recovery window found for server %s", configuration.ServerName) + } + + 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{ @@ -112,11 +126,11 @@ func (m metricsImpl) Collect( }, { FqName: firstRecoverabilityPointMetricName, - Value: float64(x.FirstRecoverabilityPoint.Unix()), + Value: firstRecoverabilityPoint, }, { FqName: lastAvailableBackupTimestampMetricName, - Value: float64(x.LastSuccessfulBackupTime.Unix()), + 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..fccf3d6 --- /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(3)) + + // 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/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") +}