Skip to content

Commit

Permalink
feat: add PodDisruptionBudget trackability (#999)
Browse files Browse the repository at this point in the history
add PDB trackability and unit test
  • Loading branch information
britaniar authored Jan 23, 2025
1 parent 4884796 commit a7f9b82
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
k8s.io/apimachinery v0.30.3
k8s.io/client-go v0.30.3
k8s.io/component-base v0.30.2
k8s.io/component-helpers v0.28.3
k8s.io/klog/v2 v2.130.1
k8s.io/metrics v0.25.2
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
Expand Down
19 changes: 19 additions & 0 deletions pkg/controllers/workapplier/availability_tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import (

appv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
apiextensionshelpers "k8s.io/apiextensions-apiserver/pkg/apihelpers"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/component-helpers/apps/poddisruptionbudget"
"k8s.io/klog/v2"

"go.goms.io/fleet/pkg/utils"
Expand Down Expand Up @@ -79,6 +81,8 @@ func trackInMemberClusterObjAvailabilityByGVR(
return trackServiceAvailability(inMemberClusterObj)
case utils.CustomResourceDefinitionGVR:
return trackCRDAvailability(inMemberClusterObj)
case utils.PodDisruptionBudgetGVR:
return trackPDBAvailability(inMemberClusterObj)
default:
if isDataResource(*gvr) {
klog.V(2).InfoS("The object from the member cluster is a data object, consider it to be immediately available",
Expand Down Expand Up @@ -218,6 +222,21 @@ func trackCRDAvailability(inMemberClusterObj *unstructured.Unstructured) (Manife
return ManifestProcessingAvailabilityResultTypeNotYetAvailable, nil
}

// trackPDBAvailability tracks the availability of a pod disruption budget in the member cluster
func trackPDBAvailability(curObj *unstructured.Unstructured) (ManifestProcessingAvailabilityResultType, error) {
var pdb policyv1.PodDisruptionBudget
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(curObj.Object, &pdb); err != nil {
return ManifestProcessingAvailabilityResultTypeFailed, controller.NewUnexpectedBehaviorError(err)
}
// Check if conditions are up-to-date
if poddisruptionbudget.ConditionsAreUpToDate(&pdb) {
klog.V(2).InfoS("PodDisruptionBudget is available", "pdb", klog.KObj(curObj))
return ManifestProcessingAvailabilityResultTypeAvailable, nil
}
klog.V(2).InfoS("Still need to wait for PodDisruptionBudget to be available", "pdb", klog.KObj(curObj))
return ManifestProcessingAvailabilityResultTypeNotYetAvailable, nil
}

// isDataResource checks if the resource is a data resource; such resources are
// available immediately after creation.
func isDataResource(gvr schema.GroupVersionResource) bool {
Expand Down
105 changes: 105 additions & 0 deletions pkg/controllers/workapplier/availability_tracker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import (
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
policyv1 "k8s.io/api/policy/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/klog/v2"
"k8s.io/utils/ptr"

Expand Down Expand Up @@ -136,6 +138,22 @@ var (
},
},
}
minAvailable = intstr.FromInt32(1)

pdbTemplate = &policyv1.PodDisruptionBudget{
TypeMeta: metav1.TypeMeta{
APIVersion: "policy/v1",
Kind: "PodDisruptionBudget",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-pdb",
Namespace: nsName,
Generation: 2,
},
Spec: policyv1.PodDisruptionBudgetSpec{
MinAvailable: &minAvailable,
},
}
)

// TestTrackDeploymentAvailability tests the trackDeploymentAvailability function.
Expand Down Expand Up @@ -667,6 +685,93 @@ func TestTrackCRDAvailability(t *testing.T) {
}
}

// TestTrackPDBAvailability tests the trackPDBAvailability function.
func TestTrackPDBAvailability(t *testing.T) {
availablePDB := pdbTemplate.DeepCopy()
availablePDB.Status = policyv1.PodDisruptionBudgetStatus{
DisruptionsAllowed: 1,
CurrentHealthy: 2,
ObservedGeneration: 2,
DesiredHealthy: 2,
ExpectedPods: 1,
Conditions: []metav1.Condition{
{
Type: policyv1.DisruptionAllowedCondition,
Status: metav1.ConditionTrue,
Reason: policyv1.SufficientPodsReason,
ObservedGeneration: 2,
},
},
}
unavailablePDBInsufficientPods := pdbTemplate.DeepCopy()
unavailablePDBInsufficientPods.Status = policyv1.PodDisruptionBudgetStatus{
DisruptionsAllowed: 0,
CurrentHealthy: 1,
ObservedGeneration: 2,
DesiredHealthy: 2,
ExpectedPods: 1,
Conditions: []metav1.Condition{
{
Type: policyv1.DisruptionAllowedCondition,
Status: metav1.ConditionTrue,
Reason: policyv1.SufficientPodsReason,
ObservedGeneration: 2,
},
},
}

unavailablePDBStaleCondition := pdbTemplate.DeepCopy()
unavailablePDBStaleCondition.Status = policyv1.PodDisruptionBudgetStatus{
DisruptionsAllowed: 1,
CurrentHealthy: 2,
ObservedGeneration: 1,
DesiredHealthy: 2,
ExpectedPods: 1,
Conditions: []metav1.Condition{
{
Type: policyv1.DisruptionAllowedCondition,
Status: metav1.ConditionTrue,
Reason: policyv1.SufficientPodsReason,
ObservedGeneration: 1,
},
},
}

testCases := []struct {
name string
pdb *policyv1.PodDisruptionBudget
wantManifestProcessingAvailabilityResultType ManifestProcessingAvailabilityResultType
}{
{
name: "available PDB",
pdb: availablePDB,
wantManifestProcessingAvailabilityResultType: ManifestProcessingAvailabilityResultTypeAvailable,
},
{
name: "unavailable PDB (insufficient pods)",
pdb: unavailablePDBInsufficientPods,
wantManifestProcessingAvailabilityResultType: ManifestProcessingAvailabilityResultTypeNotYetAvailable,
},
{
name: "unavailable PDB (stale condition)",
pdb: unavailablePDBStaleCondition,
wantManifestProcessingAvailabilityResultType: ManifestProcessingAvailabilityResultTypeNotYetAvailable,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
gotResTyp, err := trackPDBAvailability(toUnstructured(t, tc.pdb))
if err != nil {
t.Fatalf("trackPDBAvailability() = %v, want no error", err)
}
if gotResTyp != tc.wantManifestProcessingAvailabilityResultType {
t.Errorf("manifestProcessingAvailabilityResultType = %v, want %v", gotResTyp, tc.wantManifestProcessingAvailabilityResultType)
}
})
}
}

// TestTrackInMemberClusterObjAvailabilityByGVR tests the trackInMemberClusterObjAvailabilityByGVR function.
func TestTrackInMemberClusterObjAvailabilityByGVR(t *testing.T) {
availableDeploy := deploy.DeepCopy()
Expand Down
7 changes: 7 additions & 0 deletions pkg/utils/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
policyv1 "k8s.io/api/policy/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
"k8s.io/apimachinery/pkg/api/equality"
Expand Down Expand Up @@ -263,6 +264,12 @@ var (
Kind: "Pod",
}

PodDisruptionBudgetGVR = schema.GroupVersionResource{
Group: policyv1.GroupName,
Version: policyv1.SchemeGroupVersion.Version,
Resource: "poddisruptionbudgets",
}

RoleMetaGVK = metav1.GroupVersionKind{
Group: rbacv1.SchemeGroupVersion.Group,
Version: rbacv1.SchemeGroupVersion.Version,
Expand Down

0 comments on commit a7f9b82

Please sign in to comment.