Skip to content

Commit

Permalink
interface: add v1beta1 API for eviction & PDB (#1013)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arvindthiru authored Jan 8, 2025
1 parent 8a8f6d0 commit 3479ddb
Show file tree
Hide file tree
Showing 20 changed files with 927 additions and 214 deletions.
6 changes: 0 additions & 6 deletions apis/placement/v1alpha1/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,4 @@ const (

// OverrideClusterNameVariable is the reserved variable in the override value that will be replaced by the actual cluster name.
OverrideClusterNameVariable = "${MEMBER-CLUSTER-NAME}"

// ClusterResourcePlacementEvictionKind is the kind of the ClusterResourcePlacementEviction.
ClusterResourcePlacementEvictionKind = "ClusterResourcePlacementEviction"

// ClusterResourcePlacementDisruptionBudgetKind is the kind of the ClusterResourcePlacementDisruptionBudget.
ClusterResourcePlacementDisruptionBudgetKind = "ClusterResourcePlacementDisruptionBudget"
)
1 change: 0 additions & 1 deletion apis/placement/v1alpha1/disruptionbudget_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=crpdb
// +kubebuilder:storageversion

// ClusterResourcePlacementDisruptionBudget is the policy applied to a ClusterResourcePlacement
// object that specifies its disruption budget, i.e., how many placements (clusters) can be
Expand Down
1 change: 0 additions & 1 deletion apis/placement/v1alpha1/eviction_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=crpe
// +kubebuilder:subresource:status
// +kubebuilder:storageversion

// ClusterResourcePlacementEviction is an eviction attempt on a specific placement from
// a ClusterResourcePlacement object; one may use this API to force the removal of specific
Expand Down
4 changes: 4 additions & 0 deletions apis/placement/v1beta1/commons.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ const (
ClusterStagedUpdateStrategyKind = "ClusterStagedUpdateStrategy"
// ClusterApprovalRequestKind is the kind of the ClusterApprovalRequest.
ClusterApprovalRequestKind = "ClusterApprovalRequest"
// ClusterResourcePlacementEvictionKind is the kind of the ClusterResourcePlacementEviction.
ClusterResourcePlacementEvictionKind = "ClusterResourcePlacementEviction"
// ClusterResourcePlacementDisruptionBudgetKind is the kind of the ClusterResourcePlacementDisruptionBudget.
ClusterResourcePlacementDisruptionBudgetKind = "ClusterResourcePlacementDisruptionBudget"
)

const (
Expand Down
111 changes: 111 additions & 0 deletions apis/placement/v1beta1/disruptionbudget_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

package v1beta1

import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=crpdb
// +kubebuilder:storageversion

// ClusterResourcePlacementDisruptionBudget is the policy applied to a ClusterResourcePlacement
// object that specifies its disruption budget, i.e., how many placements (clusters) can be
// down at the same time due to voluntary disruptions (e.g., evictions). Involuntary
// disruptions are not subject to this budget, but will still count against it.
//
// To apply a ClusterResourcePlacementDisruptionBudget to a ClusterResourcePlacement, use the
// same name for the ClusterResourcePlacementDisruptionBudget object as the ClusterResourcePlacement
// object. This guarantees a 1:1 link between the two objects.
type ClusterResourcePlacementDisruptionBudget struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// Spec is the desired state of the ClusterResourcePlacementDisruptionBudget.
// +kubebuilder:validation:XValidation:rule="!(has(self.maxUnavailable) && has(self.minAvailable))",message="Both MaxUnavailable and MinAvailable cannot be specified"
// +required
Spec PlacementDisruptionBudgetSpec `json:"spec"`
}

// PlacementDisruptionBudgetSpec is the desired state of the PlacementDisruptionBudget.
type PlacementDisruptionBudgetSpec struct {
// MaxUnavailable is the maximum number of placements (clusters) that can be down at the
// same time due to voluntary disruptions. For example, a setting of 1 would imply that
// a voluntary disruption (e.g., an eviction) can only happen if all placements (clusters)
// from the linked Placement object are applied and available.
//
// This can be either an absolute value (e.g., 1) or a percentage (e.g., 10%).
//
// If a percentage is specified, Fleet will calculate the corresponding absolute values
// as follows:
// * if the linked Placement object is of the PickFixed placement type,
// we don't perform any calculation because eviction is not allowed for PickFixed CRP.
// * if the linked Placement object is of the PickAll placement type, MaxUnavailable cannot
// be specified since we cannot derive the total number of clusters selected.
// * if the linked Placement object is of the PickN placement type,
// the percentage is against the number of clusters specified in the placement (i.e., the
// value of the NumberOfClusters fields in the placement policy).
// The end result will be rounded up to the nearest integer if applicable.
//
// One may use a value of 0 for this field; in this case, no voluntary disruption would be
// allowed.
//
// This field is mutually exclusive with the MinAvailable field in the spec; exactly one
// of them can be set at a time.
//
// +kubebuilder:validation:XIntOrString
// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches('^(100|[0-9]{1,2})%$') : self >= 0",message="If supplied value is String should match regex '^(100|[0-9]{1,2})%$' or If supplied value is Integer must be greater than or equal to 0"
// +optional
MaxUnavailable *intstr.IntOrString `json:"maxUnavailable,omitempty"`

// MinAvailable is the minimum number of placements (clusters) that must be available at any
// time despite voluntary disruptions. For example, a setting of 10 would imply that
// a voluntary disruption (e.g., an eviction) can only happen if there are at least 11
// placements (clusters) from the linked Placement object are applied and available.
//
// This can be either an absolute value (e.g., 1) or a percentage (e.g., 10%).
//
// If a percentage is specified, Fleet will calculate the corresponding absolute values
// as follows:
// * if the linked Placement object is of the PickFixed placement type,
// we don't perform any calculation because eviction is not allowed for PickFixed CRP.
// * if the linked Placement object is of the PickAll placement type, MinAvailable can be
// specified but only as an integer since we cannot derive the total number of clusters selected.
// * if the linked Placement object is of the PickN placement type,
// the percentage is against the number of clusters specified in the placement (i.e., the
// value of the NumberOfClusters fields in the placement policy).
// The end result will be rounded up to the nearest integer if applicable.
//
// One may use a value of 0 for this field; in this case, voluntary disruption would be
// allowed at any time.
//
// This field is mutually exclusive with the MaxUnavailable field in the spec; exactly one
// of them can be set at a time.
//
// +kubebuilder:validation:XIntOrString
// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches('^(100|[0-9]{1,2})%$') : self >= 0",message="If supplied value is String should match regex '^(100|[0-9]{1,2})%$' or If supplied value is Integer must be greater than or equal to 0"
// +optional
MinAvailable *intstr.IntOrString `json:"minAvailable,omitempty"`
}

// ClusterResourcePlacementDisruptionBudgetList contains a list of ClusterResourcePlacementDisruptionBudget objects.
// +kubebuilder:resource:scope=Cluster
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ClusterResourcePlacementDisruptionBudgetList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

// Items is the list of PlacementDisruptionBudget objects.
Items []ClusterResourcePlacementDisruptionBudget `json:"items"`
}

func init() {
SchemeBuilder.Register(
&ClusterResourcePlacementDisruptionBudget{},
&ClusterResourcePlacementDisruptionBudgetList{})
}
142 changes: 142 additions & 0 deletions apis/placement/v1beta1/eviction_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
Copyright (c) Microsoft Corporation.
Licensed under the MIT license.
*/

package v1beta1

import (
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster,categories={fleet,fleet-placement},shortName=crpe
// +kubebuilder:subresource:status
// +kubebuilder:storageversion

// ClusterResourcePlacementEviction is an eviction attempt on a specific placement from
// a ClusterResourcePlacement object; one may use this API to force the removal of specific
// resources from a cluster.
//
// An eviction is a voluntary disruption; its execution is subject to the disruption budget
// linked with the target ClusterResourcePlacement object (if present).
//
// Beware that an eviction alone does not guarantee that a placement will not re-appear; i.e.,
// after an eviction, the Fleet scheduler might still pick the previous target cluster for
// placement. To prevent this, considering adding proper taints to the target cluster before running
// an eviction that will exclude it from future placements; this is especially true in scenarios
// where one would like to perform a cluster replacement.
//
// For safety reasons, Fleet will only execute an eviction once; the spec in this object is immutable,
// and once executed, the object will be ignored after. To trigger another eviction attempt on the
// same placement from the same ClusterResourcePlacement object, one must re-create (delete and
// create) the same Eviction object. Note also that an Eviction object will be
// ignored once it is deemed invalid (e.g., such an object might be targeting a CRP object or
// a placement that does not exist yet), even if it does become valid later
// (e.g., the CRP object or the placement appears later). To fix the situation, re-create the
// Eviction object.
//
// Note: Eviction of resources from a cluster propagated by a PickFixed CRP is not allowed.
// If the user wants to remove resources from a cluster propagated by a PickFixed CRP simply
// remove the cluster name from cluster names field from the CRP spec.
//
// Executed evictions might be kept around for a while for auditing purposes; the Fleet controllers might
// have a TTL set up for such objects and will garbage collect them automatically. For further
// information, see the Fleet documentation.
type ClusterResourcePlacementEviction struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`

// Spec is the desired state of the ClusterResourcePlacementEviction.
//
// Note that all fields in the spec are immutable.
// +required
Spec PlacementEvictionSpec `json:"spec"`

// Status is the observed state of the ClusterResourcePlacementEviction.
// +optional
Status PlacementEvictionStatus `json:"status,omitempty"`
}

// PlacementEvictionSpec is the desired state of the parent PlacementEviction.
type PlacementEvictionSpec struct {
// PlacementName is the name of the Placement object which
// the Eviction object targets.
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="The PlacementName field is immutable"
// +kubebuilder:validation:MaxLength=255
PlacementName string `json:"placementName"`

// ClusterName is the name of the cluster that the Eviction object targets.
// +kubebuilder:validation:Required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="The ClusterName field is immutable"
// +kubebuilder:validation:MaxLength=255
ClusterName string `json:"clusterName"`
}

// PlacementEvictionStatus is the observed state of the parent PlacementEviction.
type PlacementEvictionStatus struct {
// Conditions is the list of currently observed conditions for the
// PlacementEviction object.
//
// Available condition types include:
// * Valid: whether the Eviction object is valid, i.e., it targets at a valid placement.
// * Executed: whether the Eviction object has been executed.
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}

// PlacementEvictionConditionType identifies a specific condition of the
// PlacementEviction.
type PlacementEvictionConditionType string

const (
// PlacementEvictionConditionTypeValid indicates whether the Eviction object is valid.
//
// The following values are possible:
// * True: the Eviction object is valid.
// * False: the Eviction object is invalid; it might be targeting a CRP object or a placement
// that does not exist yet.
// Note that this is a terminal state; once an Eviction object is deemed invalid, it will
// not be evaluated again, even if the target appears later.
PlacementEvictionConditionTypeValid PlacementEvictionConditionType = "Valid"

// PlacementEvictionConditionTypeExecuted indicates whether the Eviction object has been executed.
//
// The following values are possible:
// * True: the Eviction object has been executed.
// Note that this is a terminal state; once an Eviction object is executed, it will not be
// executed again.
// * False: the Eviction object has not been executed yet.
PlacementEvictionConditionTypeExecuted PlacementEvictionConditionType = "Executed"
)

// ClusterResourcePlacementEvictionList contains a list of ClusterResourcePlacementEviction objects.
// +kubebuilder:resource:scope=Cluster
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ClusterResourcePlacementEvictionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

// Items is the list of ClusterResourcePlacementEviction objects.
Items []ClusterResourcePlacementEviction `json:"items"`
}

// SetConditions set the given conditions on the ClusterResourcePlacementEviction.
func (e *ClusterResourcePlacementEviction) SetConditions(conditions ...metav1.Condition) {
for _, c := range conditions {
meta.SetStatusCondition(&e.Status.Conditions, c)
}
}

// GetCondition returns the condition of the given ClusterResourcePlacementEviction.
func (e *ClusterResourcePlacementEviction) GetCondition(conditionType string) *metav1.Condition {
return meta.FindStatusCondition(e.Status.Conditions, conditionType)
}

func init() {
SchemeBuilder.Register(
&ClusterResourcePlacementEviction{},
&ClusterResourcePlacementEvictionList{})
}
Loading

0 comments on commit 3479ddb

Please sign in to comment.