Skip to content

Commit 75d6863

Browse files
committed
This change mostly consists of three parts:
1. A MachinePool lifecycle controller: Implements `machinepool-lifecycle-controller` monitoring MachinePool health, with corresponding API additions for MachinePool/VolumePool/BucketPool conditions, codegen updates 2. A pool health heartbeat runnable in the machinepoollet: The poollet now actively reports pool liveness so the lifecycle controller can set pools whose poollets have gone away to Unknown. It also includes kustomizations adding the Lease namespace and RBAC adjustments. The MachinePoolHeartbeat, a ticker-driven Runnable that probes the IRI runtime via Status, renews the pool's Lease in ironcore-machinepool-lease, and patches Ready only when its value or observedGeneration actually changes. Errors on either sub-step are logged and retried on the next tick; the lifecycle controller's grace period absorbs short blips. Lease takeover from a previous holder is logged at Info. Contains app arguments that make the heartbeat intervals configurable, defaulting to the IEP-15 values. 3. Lease namespaces and RBACs Contributes to #1472
1 parent caaf1d3 commit 75d6863

49 files changed

Lines changed: 1787 additions & 20 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ PROTOC_GEN_GO_GRPC ?= $(LOCALBIN)/protoc-gen-go-grpc
358358

359359
## Tool Versions
360360
KUSTOMIZE_VERSION ?= v5.1.1
361-
VGOPATH_VERSION ?= v0.1.3
361+
VGOPATH_VERSION ?= v0.1.10
362362
CONTROLLER_TOOLS_VERSION ?= v0.20.0
363363
GEN_CRD_API_REFERENCE_DOCS_VERSION ?= v0.3.0
364364
ADDLICENSE_VERSION ?= v1.1.1

api/compute/v1alpha1/common.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ import (
99
corev1 "k8s.io/api/core/v1"
1010
)
1111

12+
const (
13+
NamespaceMachinePoolLease = "ironcore-machinepool-lease"
14+
)
15+
1216
const (
1317
MachineMachinePoolRefNameField = "spec.machinePoolRef.name"
1418
MachineMachineClassRefNameField = "spec.machineClassRef.name"

api/compute/v1alpha1/conditions.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1
5+
6+
import (
7+
"slices"
8+
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
)
11+
12+
// FindMachinePoolCondition returns a pointer to the condition of the given type,
13+
// or nil if no condition of that type is present.
14+
func FindMachinePoolCondition(conditions []MachinePoolCondition, typ MachinePoolConditionType) *MachinePoolCondition {
15+
idx := slices.IndexFunc(conditions, func(cond MachinePoolCondition) bool {
16+
return cond.Type == typ
17+
})
18+
if idx < 0 {
19+
return nil
20+
}
21+
return &conditions[idx]
22+
}
23+
24+
// SetMachinePoolCondition inserts or updates a condition of the given type in the
25+
// conditions slice. LastUpdateTime is always set to now. LastTransitionTime is set
26+
// to now only when the condition is newly inserted or its Status differs from the
27+
// previous value.
28+
func SetMachinePoolCondition(conditions []MachinePoolCondition, cond MachinePoolCondition) []MachinePoolCondition {
29+
idx := slices.IndexFunc(conditions, func(c MachinePoolCondition) bool {
30+
return c.Type == cond.Type
31+
})
32+
33+
cond.LastUpdateTime = metav1.Now()
34+
35+
if idx < 0 || conditions[idx].Status != cond.Status {
36+
cond.LastTransitionTime = metav1.Now()
37+
} else {
38+
cond.LastTransitionTime = conditions[idx].LastTransitionTime
39+
}
40+
41+
if idx < 0 {
42+
return append(conditions, cond)
43+
}
44+
conditions[idx] = cond
45+
return conditions
46+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// SPDX-FileCopyrightText: 2026 SAP SE or an SAP affiliate company and IronCore contributors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package v1alpha1_test
5+
6+
import (
7+
"testing"
8+
"time"
9+
10+
computev1alpha1 "github.com/ironcore-dev/ironcore/api/compute/v1alpha1"
11+
corev1 "k8s.io/api/core/v1"
12+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
13+
)
14+
15+
func TestFindMachinePoolCondition_returnsMatch(t *testing.T) {
16+
conds := []computev1alpha1.MachinePoolCondition{
17+
{Type: "Other", Status: corev1.ConditionTrue},
18+
{Type: computev1alpha1.MachinePoolReady, Status: corev1.ConditionFalse, Reason: "X"},
19+
}
20+
got := computev1alpha1.FindMachinePoolCondition(conds, computev1alpha1.MachinePoolReady)
21+
if got == nil || got.Reason != "X" {
22+
t.Fatalf("expected Ready condition with reason X, got %+v", got)
23+
}
24+
}
25+
26+
func TestFindMachinePoolCondition_returnsNilWhenMissing(t *testing.T) {
27+
conds := []computev1alpha1.MachinePoolCondition{{Type: "Other"}}
28+
if got := computev1alpha1.FindMachinePoolCondition(conds, computev1alpha1.MachinePoolReady); got != nil {
29+
t.Fatalf("expected nil, got %+v", got)
30+
}
31+
}
32+
33+
func TestSetMachinePoolCondition_appendsWhenAbsent(t *testing.T) {
34+
out := computev1alpha1.SetMachinePoolCondition(nil, computev1alpha1.MachinePoolCondition{
35+
Type: computev1alpha1.MachinePoolReady,
36+
Status: corev1.ConditionTrue,
37+
})
38+
if len(out) != 1 || out[0].Type != computev1alpha1.MachinePoolReady {
39+
t.Fatalf("expected Ready appended, got %+v", out)
40+
}
41+
if out[0].LastTransitionTime.IsZero() {
42+
t.Fatal("expected LastTransitionTime to be set on first append")
43+
}
44+
if out[0].LastUpdateTime.IsZero() {
45+
t.Fatal("expected LastUpdateTime to be set on first append")
46+
}
47+
}
48+
49+
func TestSetMachinePoolCondition_updatesInPlaceWithoutTransition(t *testing.T) {
50+
earlier := metav1.NewTime(time.Now().Add(-time.Hour))
51+
in := []computev1alpha1.MachinePoolCondition{{
52+
Type: computev1alpha1.MachinePoolReady,
53+
Status: corev1.ConditionTrue,
54+
LastTransitionTime: earlier,
55+
}}
56+
out := computev1alpha1.SetMachinePoolCondition(in, computev1alpha1.MachinePoolCondition{
57+
Type: computev1alpha1.MachinePoolReady,
58+
Status: corev1.ConditionTrue, // same status
59+
Message: "still ready",
60+
})
61+
if !out[0].LastTransitionTime.Equal(&earlier) {
62+
t.Fatalf("expected LastTransitionTime preserved when status unchanged, got %v", out[0].LastTransitionTime)
63+
}
64+
if out[0].LastUpdateTime.IsZero() {
65+
t.Fatal("expected LastUpdateTime advanced")
66+
}
67+
}
68+
69+
func TestSetMachinePoolCondition_advancesTransitionWhenStatusChanges(t *testing.T) {
70+
earlier := metav1.NewTime(time.Now().Add(-time.Hour))
71+
in := []computev1alpha1.MachinePoolCondition{{
72+
Type: computev1alpha1.MachinePoolReady,
73+
Status: corev1.ConditionTrue,
74+
LastTransitionTime: earlier,
75+
}}
76+
out := computev1alpha1.SetMachinePoolCondition(in, computev1alpha1.MachinePoolCondition{
77+
Type: computev1alpha1.MachinePoolReady,
78+
Status: corev1.ConditionFalse,
79+
})
80+
if out[0].LastTransitionTime.Equal(&earlier) {
81+
t.Fatal("expected LastTransitionTime to advance when status changes")
82+
}
83+
}

api/compute/v1alpha1/machinepool_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ type MachinePoolAddress struct {
8585
// MachinePoolConditionType is a type a MachinePoolCondition can have.
8686
type MachinePoolConditionType string
8787

88+
const (
89+
// MachinePoolReady means the machine pool is healthy and ready to accept machines.
90+
MachinePoolReady MachinePoolConditionType = "Ready"
91+
)
92+
8893
// MachinePoolCondition is one of the conditions of a MachinePool.
8994
type MachinePoolCondition struct {
9095
// Type is the type of the condition.
@@ -97,6 +102,8 @@ type MachinePoolCondition struct {
97102
Message string `json:"message"`
98103
// ObservedGeneration represents the .metadata.generation that the condition was set based upon.
99104
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
105+
// LastUpdateTime is the last time this condition was updated.
106+
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
100107
// LastTransitionTime is the last time the status of a condition has transitioned from one state to another.
101108
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
102109
}

api/compute/v1alpha1/zz_generated.deepcopy.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/storage/v1alpha1/bucketpool_types.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,38 @@ type BucketPoolSpec struct {
2222
// BucketPoolStatus defines the observed state of BucketPool
2323
type BucketPoolStatus struct {
2424
// State represents the infrastructure state of a BucketPool.
25-
State BucketPoolState `json:"state,omitempty"`
25+
State BucketPoolState `json:"state,omitempty"`
26+
Conditions []BucketPoolCondition `json:"conditions,omitempty"`
2627
// AvailableBucketClasses list the references of any supported BucketClass of this pool
2728
AvailableBucketClasses []corev1.LocalObjectReference `json:"availableBucketClasses,omitempty"`
2829
}
2930

31+
// BucketPoolConditionType is a type a BucketPoolCondition can have.
32+
type BucketPoolConditionType string
33+
34+
const (
35+
// BucketPoolReady means the bucket pool is healthy and ready to accept buckets.
36+
BucketPoolReady BucketPoolConditionType = "Ready"
37+
)
38+
39+
// BucketPoolCondition is one of the conditions of a BucketPool.
40+
type BucketPoolCondition struct {
41+
// Type is the type of the condition.
42+
Type BucketPoolConditionType `json:"type"`
43+
// Status is the status of the condition.
44+
Status corev1.ConditionStatus `json:"status"`
45+
// Reason is a machine-readable indication of why the condition is in a certain state.
46+
Reason string `json:"reason"`
47+
// Message is a human-readable explanation of why the condition has a certain reason / state.
48+
Message string `json:"message"`
49+
// ObservedGeneration represents the .metadata.generation that the condition was set based upon.
50+
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
51+
// LastUpdateTime is the last time this condition was updated.
52+
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
53+
// LastTransitionTime is the last time the status of a condition has transitioned from one state to another.
54+
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
55+
}
56+
3057
type BucketPoolState string
3158

3259
const (

api/storage/v1alpha1/common.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ package v1alpha1
55

66
import corev1 "k8s.io/api/core/v1"
77

8+
const (
9+
NamespaceVolumePoolLease = "ironcore-volumepool-lease"
10+
NamespaceBucketPoolLease = "ironcore-bucketpool-lease"
11+
)
12+
813
const (
914
VolumeVolumePoolRefNameField = "spec.volumePoolRef.name"
1015
VolumeVolumeClassRefNameField = "spec.volumeClassRef.name"

api/storage/v1alpha1/volumepool_types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ const (
4343
// VolumePoolConditionType is a type a VolumePoolCondition can have.
4444
type VolumePoolConditionType string
4545

46+
const (
47+
// VolumePoolReady means the volume pool is healthy and ready to accept volumes.
48+
VolumePoolReady VolumePoolConditionType = "Ready"
49+
)
50+
4651
// VolumePoolCondition is one of the conditions of a volume.
4752
type VolumePoolCondition struct {
4853
// Type is the type of the condition.
@@ -55,6 +60,8 @@ type VolumePoolCondition struct {
5560
Message string `json:"message"`
5661
// ObservedGeneration represents the .metadata.generation that the condition was set based upon.
5762
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
63+
// LastUpdateTime is the last time this condition was updated.
64+
LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
5865
// LastTransitionTime is the last time the status of a condition has transitioned from one state to another.
5966
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
6067
}

api/storage/v1alpha1/zz_generated.deepcopy.go

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)