From 56b40c7ff8462d417519798ba1b6b12e54c32e9f Mon Sep 17 00:00:00 2001
From: Philipp Matthes
Date: Tue, 24 Mar 2026 16:58:36 +0100
Subject: [PATCH] Add aggregate metadata
---
api/v1/hypervisor_types.go | 4 ++++
api/v1/zz_generated.deepcopy.go | 11 ++++++++++-
applyconfigurations/api/v1/aggregate.go | 19 +++++++++++++++++--
.../crds/kvm.cloud.sap_hypervisors.yaml | 6 ++++++
internal/openstack/aggregates.go | 5 +++--
internal/openstack/aggregates_test.go | 11 +++++++++--
6 files changed, 49 insertions(+), 7 deletions(-)
diff --git a/api/v1/hypervisor_types.go b/api/v1/hypervisor_types.go
index 97796dc..a615547 100644
--- a/api/v1/hypervisor_types.go
+++ b/api/v1/hypervisor_types.go
@@ -206,6 +206,10 @@ type Aggregate struct {
// UUID is the unique identifier of the aggregate.
UUID string `json:"uuid"`
+
+ // Metadata is the metadata of the aggregate as key-value pairs.
+ // +kubebuilder:validation:Optional
+ Metadata map[string]string `json:"metadata,omitempty"`
}
type HyperVisorUpdateStatus struct {
diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go
index 3dbdc81..802d5f3 100644
--- a/api/v1/zz_generated.deepcopy.go
+++ b/api/v1/zz_generated.deepcopy.go
@@ -30,6 +30,13 @@ import (
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Aggregate) DeepCopyInto(out *Aggregate) {
*out = *in
+ if in.Metadata != nil {
+ in, out := &in.Metadata, &out.Metadata
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Aggregate.
@@ -385,7 +392,9 @@ func (in *HypervisorStatus) DeepCopyInto(out *HypervisorStatus) {
if in.Aggregates != nil {
in, out := &in.Aggregates, &out.Aggregates
*out = make([]Aggregate, len(*in))
- copy(*out, *in)
+ for i := range *in {
+ (*in)[i].DeepCopyInto(&(*out)[i])
+ }
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
diff --git a/applyconfigurations/api/v1/aggregate.go b/applyconfigurations/api/v1/aggregate.go
index 7c008a8..5141a5e 100644
--- a/applyconfigurations/api/v1/aggregate.go
+++ b/applyconfigurations/api/v1/aggregate.go
@@ -5,8 +5,9 @@ package v1
// AggregateApplyConfiguration represents a declarative configuration of the Aggregate type for use
// with apply.
type AggregateApplyConfiguration struct {
- Name *string `json:"name,omitempty"`
- UUID *string `json:"uuid,omitempty"`
+ Name *string `json:"name,omitempty"`
+ UUID *string `json:"uuid,omitempty"`
+ Metadata map[string]string `json:"metadata,omitempty"`
}
// AggregateApplyConfiguration constructs a declarative configuration of the Aggregate type for use with
@@ -30,3 +31,17 @@ func (b *AggregateApplyConfiguration) WithUUID(value string) *AggregateApplyConf
b.UUID = &value
return b
}
+
+// WithMetadata puts the entries into the Metadata field in the declarative configuration
+// and returns the receiver, so that objects can be build by chaining "With" function invocations.
+// If called multiple times, the entries provided by each call will be put on the Metadata field,
+// overwriting an existing map entries in Metadata field with the same key.
+func (b *AggregateApplyConfiguration) WithMetadata(entries map[string]string) *AggregateApplyConfiguration {
+ if b.Metadata == nil && len(entries) > 0 {
+ b.Metadata = make(map[string]string, len(entries))
+ }
+ for k, v := range entries {
+ b.Metadata[k] = v
+ }
+ return b
+}
diff --git a/charts/openstack-hypervisor-operator/crds/kvm.cloud.sap_hypervisors.yaml b/charts/openstack-hypervisor-operator/crds/kvm.cloud.sap_hypervisors.yaml
index b479fc6..fb1e494 100644
--- a/charts/openstack-hypervisor-operator/crds/kvm.cloud.sap_hypervisors.yaml
+++ b/charts/openstack-hypervisor-operator/crds/kvm.cloud.sap_hypervisors.yaml
@@ -229,6 +229,12 @@ spec:
description: Aggregate represents an OpenStack aggregate with its
name and UUID.
properties:
+ metadata:
+ additionalProperties:
+ type: string
+ description: Metadata is the metadata of the aggregate as key-value
+ pairs.
+ type: object
name:
description: Name is the name of the aggregate.
type: string
diff --git a/internal/openstack/aggregates.go b/internal/openstack/aggregates.go
index e26bf37..079a26c 100644
--- a/internal/openstack/aggregates.go
+++ b/internal/openstack/aggregates.go
@@ -127,8 +127,9 @@ func ApplyAggregates(ctx context.Context, serviceClient *gophercloud.ServiceClie
for _, name := range desiredAggregates {
agg := aggregateMap[name] // exists as per "Verify all desired aggregates exist" check
result = append(result, kvmv1.Aggregate{
- Name: agg.Name,
- UUID: agg.UUID,
+ Name: agg.Name,
+ UUID: agg.UUID,
+ Metadata: agg.Metadata,
})
}
diff --git a/internal/openstack/aggregates_test.go b/internal/openstack/aggregates_test.go
index d99fb40..03afb74 100644
--- a/internal/openstack/aggregates_test.go
+++ b/internal/openstack/aggregates_test.go
@@ -38,7 +38,8 @@ var _ = Describe("ApplyAggregates", func() {
"deleted": false,
"id": 1,
"uuid": "uuid-agg1",
- "hosts": ["test-host"]
+ "hosts": ["test-host"],
+ "metadata": {"key1": "value1", "key2": "value2"}
},
{
"name": "agg2",
@@ -54,7 +55,8 @@ var _ = Describe("ApplyAggregates", func() {
"deleted": false,
"id": 3,
"uuid": "uuid-agg3",
- "hosts": []
+ "hosts": [],
+ "metadata": {}
}
]
}`
@@ -119,12 +121,17 @@ var _ = Describe("ApplyAggregates", func() {
// Check we have the right aggregates by name and UUID
names := make([]string, len(aggregates))
uuids := make([]string, len(aggregates))
+ metadata := make([]map[string]string, len(aggregates))
for i, agg := range aggregates {
names[i] = agg.Name
uuids[i] = agg.UUID
+ metadata[i] = agg.Metadata
}
Expect(names).To(ConsistOf("agg1", "agg2", "agg3"))
Expect(uuids).To(ConsistOf("uuid-agg1", "uuid-agg2", "uuid-agg3"))
+ Expect(metadata[0]).To(Equal(map[string]string{"key1": "value1", "key2": "value2"})) // agg1 metadata should be preserved
+ Expect(metadata[1]).To(BeNil()) // agg2 has no metadata field, should be nil
+ Expect(metadata[2]).To(BeEmpty()) // agg3 has empty metadata, should be preserved
})
})