Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
327 changes: 327 additions & 0 deletions docs/proposals/292-in-place-pod-image-update/README.md

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions operator/api/core/v1alpha1/crds/grove.io_podcliquesets.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10776,6 +10776,17 @@ spec:
templates change. This applies to both standalone PodCliques and
PodCliqueScalingGroups.
properties:
inPlaceUpdate:
description: InPlaceUpdate configures behavior for in-place update
strategies.
properties:
gracePeriodSeconds:
description: |-
GracePeriodSeconds is the delay between marking a Pod not ready through the
Grove in-place readiness gate and patching container images.
format: int32
type: integer
type: object
type:
default: RollingRecreate
description: |-
Expand All @@ -10786,6 +10797,8 @@ spec:
enum:
- RollingRecreate
- OnDelete
- InPlaceIfPossible
- InPlaceOnly
type: string
type: object
required:
Expand Down
20 changes: 19 additions & 1 deletion operator/api/core/v1alpha1/podcliqueset.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ type PodCliqueSetUpdateStrategy struct {
// Default is RollingRecreate.
// +kubebuilder:default=RollingRecreate
Type UpdateStrategyType `json:"type,omitempty"`
// InPlaceUpdate configures behavior for in-place update strategies.
// +optional
InPlaceUpdate *InPlaceUpdateStrategy `json:"inPlaceUpdate,omitempty"`
}

// InPlaceUpdateStrategy defines settings for in-place Pod image updates.
type InPlaceUpdateStrategy struct {
// GracePeriodSeconds is the delay between marking a Pod not ready through the
// Grove in-place readiness gate and patching container images.
// +optional
GracePeriodSeconds *int32 `json:"gracePeriodSeconds,omitempty"`
}

// PodCliqueSetRollingUpdateProgress captures the progress of a rolling update of the PodCliqueSet.
Expand Down Expand Up @@ -424,7 +435,7 @@ type HeadlessServiceConfig struct {
}

// UpdateStrategyType defines the type of update strategy for PodCliqueSet.
// +kubebuilder:validation:Enum={RollingRecreate,OnDelete}
// +kubebuilder:validation:Enum={RollingRecreate,OnDelete,InPlaceIfPossible,InPlaceOnly}
type UpdateStrategyType string

const (
Expand All @@ -439,6 +450,13 @@ const (
// they are manually deleted. Changes to templates do not automatically
// trigger replica deletions.
OnDeleteStrategy UpdateStrategyType = "OnDelete"
// InPlaceIfPossibleStrategy indicates that Pods should be updated in-place
// when their changes are eligible for in-place update. Unsupported changes
// fall back to RollingRecreate.
InPlaceIfPossibleStrategy UpdateStrategyType = "InPlaceIfPossible"
// InPlaceOnlyStrategy indicates that Pods should only be updated in-place.
// Unsupported changes block the update instead of deleting Pods.
InPlaceOnlyStrategy UpdateStrategyType = "InPlaceOnly"
)

// CliqueStartupType defines the order in which each PodClique is started.
Expand Down
28 changes: 27 additions & 1 deletion operator/api/core/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,23 @@ func IsAutoUpdateStrategy(pcs *grovecorev1alpha1.PodCliqueSet) bool {
return pcs.Spec.UpdateStrategy == nil || pcs.Spec.UpdateStrategy.Type != grovecorev1alpha1.OnDeleteStrategy
}

// IsInPlaceUpdateStrategy returns true when PodCliqueSet update strategy should attempt in-place Pod image updates.
func IsInPlaceUpdateStrategy(pcs *grovecorev1alpha1.PodCliqueSet) bool {
if pcs == nil || pcs.Spec.UpdateStrategy == nil {
return false
}
return pcs.Spec.UpdateStrategy.Type == grovecorev1alpha1.InPlaceIfPossibleStrategy ||
pcs.Spec.UpdateStrategy.Type == grovecorev1alpha1.InPlaceOnlyStrategy
}

// IsInPlaceOnlyStrategy returns true when unsupported in-place changes should block instead of falling back to recreate.
func IsInPlaceOnlyStrategy(pcs *grovecorev1alpha1.PodCliqueSet) bool {
if pcs == nil || pcs.Spec.UpdateStrategy == nil {
return false
}
return pcs.Spec.UpdateStrategy.Type == grovecorev1alpha1.InPlaceOnlyStrategy
}

// GetExpectedPCLQNamesGroupByOwner returns the expected unqualified PodClique names which are either owned by PodCliqueSet or PodCliqueScalingGroup.
func GetExpectedPCLQNamesGroupByOwner(pcs *grovecorev1alpha1.PodCliqueSet) (expectedPCLQNamesForPCS []string, expectedPCLQNamesForPCSG []string) {
pcsgConfigs := pcs.Spec.Template.PodCliqueScalingGroupConfigs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,24 @@ func TestIsAutoUpdateStrategy(t *testing.T) {
},
expected: false,
},
{
name: "in_place_if_possible_is_auto",
pcs: &grovecorev1alpha1.PodCliqueSet{
Spec: grovecorev1alpha1.PodCliqueSetSpec{
UpdateStrategy: &grovecorev1alpha1.PodCliqueSetUpdateStrategy{Type: grovecorev1alpha1.InPlaceIfPossibleStrategy},
},
},
expected: true,
},
{
name: "in_place_only_is_auto",
pcs: &grovecorev1alpha1.PodCliqueSet{
Spec: grovecorev1alpha1.PodCliqueSetSpec{
UpdateStrategy: &grovecorev1alpha1.PodCliqueSetUpdateStrategy{Type: grovecorev1alpha1.InPlaceOnlyStrategy},
},
},
expected: true,
},
}

for _, tc := range tests {
Expand All @@ -310,6 +328,51 @@ func TestIsAutoUpdateStrategy(t *testing.T) {
}
}

func TestIsInPlaceUpdateStrategy(t *testing.T) {
tests := []struct {
name string
pcs *grovecorev1alpha1.PodCliqueSet
expected bool
}{
{name: "nil_pcs", pcs: nil, expected: false},
{name: "nil_strategy", pcs: &grovecorev1alpha1.PodCliqueSet{}, expected: false},
{
name: "rolling_recreate",
pcs: &grovecorev1alpha1.PodCliqueSet{Spec: grovecorev1alpha1.PodCliqueSetSpec{
UpdateStrategy: &grovecorev1alpha1.PodCliqueSetUpdateStrategy{Type: grovecorev1alpha1.RollingRecreateStrategy},
}},
expected: false,
},
{
name: "on_delete",
pcs: &grovecorev1alpha1.PodCliqueSet{Spec: grovecorev1alpha1.PodCliqueSetSpec{
UpdateStrategy: &grovecorev1alpha1.PodCliqueSetUpdateStrategy{Type: grovecorev1alpha1.OnDeleteStrategy},
}},
expected: false,
},
{
name: "in_place_if_possible",
pcs: &grovecorev1alpha1.PodCliqueSet{Spec: grovecorev1alpha1.PodCliqueSetSpec{
UpdateStrategy: &grovecorev1alpha1.PodCliqueSetUpdateStrategy{Type: grovecorev1alpha1.InPlaceIfPossibleStrategy},
}},
expected: true,
},
{
name: "in_place_only",
pcs: &grovecorev1alpha1.PodCliqueSet{Spec: grovecorev1alpha1.PodCliqueSetSpec{
UpdateStrategy: &grovecorev1alpha1.PodCliqueSetUpdateStrategy{Type: grovecorev1alpha1.InPlaceOnlyStrategy},
}},
expected: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.expected, IsInPlaceUpdateStrategy(tc.pcs))
})
}
}

// TestGetPodCliqueSet tests the GetPodCliqueSet function
func TestGetPodCliqueSet(t *testing.T) {
tests := []struct {
Expand Down
Loading
Loading