Skip to content

Commit

Permalink
skip interpret health of resources without a hook
Browse files Browse the repository at this point in the history
Signed-off-by: Amir Alavi <[email protected]>
  • Loading branch information
a7i committed Sep 13, 2024
1 parent 4ac95b9 commit 8e50532
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 14 deletions.
40 changes: 26 additions & 14 deletions pkg/controllers/status/work_status_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"
"reflect"

"github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
Expand Down Expand Up @@ -365,20 +366,7 @@ func (c *WorkStatusController) reflectStatus(ctx context.Context, work *workv1al
}
c.EventRecorder.Eventf(work, corev1.EventTypeNormal, events.EventReasonReflectStatusSucceed, "Reflect status for object(%s/%s/%s) succeed.", clusterObj.GetKind(), clusterObj.GetNamespace(), clusterObj.GetName())

var resourceHealth workv1alpha1.ResourceHealth
// When an unregistered resource kind is requested with the ResourceInterpreter,
// the interpreter will return an error, we treat its health status as Unknown.
healthy, err := c.ResourceInterpreter.InterpretHealth(clusterObj)
if err != nil {
resourceHealth = workv1alpha1.ResourceUnknown
c.EventRecorder.Eventf(work, corev1.EventTypeWarning, events.EventReasonInterpretHealthFailed, "Interpret health of object(%s/%s/%s) failed, err: %s.", clusterObj.GetKind(), clusterObj.GetNamespace(), clusterObj.GetName(), err.Error())
} else if healthy {
resourceHealth = workv1alpha1.ResourceHealthy
c.EventRecorder.Eventf(work, corev1.EventTypeNormal, events.EventReasonInterpretHealthSucceed, "Interpret health of object(%s/%s/%s) as healthy.", clusterObj.GetKind(), clusterObj.GetNamespace(), clusterObj.GetName())
} else {
resourceHealth = workv1alpha1.ResourceUnhealthy
c.EventRecorder.Eventf(work, corev1.EventTypeNormal, events.EventReasonInterpretHealthSucceed, "Interpret health of object(%s/%s/%s) as unhealthy.", clusterObj.GetKind(), clusterObj.GetNamespace(), clusterObj.GetName())
}
resourceHealth := c.interpretHealth(clusterObj, work)

identifier, err := c.buildStatusIdentifier(work, clusterObj)
if err != nil {
Expand All @@ -400,6 +388,30 @@ func (c *WorkStatusController) reflectStatus(ctx context.Context, work *workv1al
})
}

func (c *WorkStatusController) interpretHealth(clusterObj *unstructured.Unstructured, work *workv1alpha1.Work) workv1alpha1.ResourceHealth {
// For kind that doesn't have health check, we treat it as healthy.
healthy := true
var err error
if c.ResourceInterpreter.HookEnabled(clusterObj.GroupVersionKind(), v1alpha1.InterpreterOperationInterpretHealth) {
healthy, err = c.ResourceInterpreter.InterpretHealth(clusterObj)
} else {
klog.V(5).Infof("skipping health status of objects: %v %s/%s.", clusterObj.GroupVersionKind(), clusterObj.GetNamespace(), clusterObj.GetName())
}

var resourceHealth workv1alpha1.ResourceHealth
if err != nil {
resourceHealth = workv1alpha1.ResourceUnknown
c.EventRecorder.Eventf(work, corev1.EventTypeWarning, events.EventReasonInterpretHealthFailed, "Interpret health of object(%s/%s/%s) failed, err: %s.", clusterObj.GetKind(), clusterObj.GetNamespace(), clusterObj.GetName(), err.Error())
} else if healthy {
resourceHealth = workv1alpha1.ResourceHealthy
c.EventRecorder.Eventf(work, corev1.EventTypeNormal, events.EventReasonInterpretHealthSucceed, "Interpret health of object(%s/%s/%s) as healthy.", clusterObj.GetKind(), clusterObj.GetNamespace(), clusterObj.GetName())
} else {
resourceHealth = workv1alpha1.ResourceUnhealthy
c.EventRecorder.Eventf(work, corev1.EventTypeNormal, events.EventReasonInterpretHealthSucceed, "Interpret health of object(%s/%s/%s) as unhealthy.", clusterObj.GetKind(), clusterObj.GetNamespace(), clusterObj.GetName())
}
return resourceHealth
}

func (c *WorkStatusController) buildStatusIdentifier(work *workv1alpha1.Work, clusterObj *unstructured.Unstructured) (*workv1alpha1.ResourceIdentifier, error) {
manifestRef := helper.ManifestReference{APIVersion: clusterObj.GetAPIVersion(), Kind: clusterObj.GetKind(),
Namespace: clusterObj.GetNamespace(), Name: clusterObj.GetName()}
Expand Down
50 changes: 50 additions & 0 deletions pkg/controllers/status/work_status_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,26 @@ import (

"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid"
dynamicfake "k8s.io/client-go/dynamic/fake"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/record"
"k8s.io/utils/ptr"
controllerruntime "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/fake"

clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
workv1alpha1 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/events"
"github.com/karmada-io/karmada/pkg/resourceinterpreter/default/native"
"github.com/karmada-io/karmada/pkg/sharedcli/ratelimiterflag"
"github.com/karmada-io/karmada/pkg/util"
Expand Down Expand Up @@ -723,6 +727,9 @@ func newWorkStatusController(cluster *clusterv1alpha1.Cluster, dynamicClientSets
m.Add(corev1.SchemeGroupVersion.WithKind("Pod"), meta.RESTScopeNamespace)
return m
}(),
ResourceInterpreter: FakeResourceInterpreter{
DefaultInterpreter: native.NewDefaultInterpreter(),
},
}

if len(dynamicClientSets) > 0 {
Expand Down Expand Up @@ -1021,3 +1028,46 @@ func TestWorkStatusController_registerInformersAndStart(t *testing.T) {
assert.NotEmpty(t, err)
})
}

func TestWorkStatusController_interpretHealth(t *testing.T) {
tests := []struct {
name string
clusterObj client.Object
expectedResourceHealth workv1alpha1.ResourceHealth
expectedEventReason string
}{
{
name: "deployment without status is interpreted as unhealthy",
clusterObj: testhelper.NewDeployment("foo", "bar"),
expectedResourceHealth: workv1alpha1.ResourceUnhealthy,
expectedEventReason: events.EventReasonInterpretHealthSucceed,
},
{
name: "cluster role without status is interpreted as healthy",
clusterObj: testhelper.NewClusterRole("foo", []rbacv1.PolicyRule{}),
expectedResourceHealth: workv1alpha1.ResourceHealthy,
expectedEventReason: events.EventReasonInterpretHealthSucceed,
},
}

cluster := newCluster("cluster", clusterv1alpha1.ClusterConditionReady, metav1.ConditionTrue)
c := newWorkStatusController(cluster)

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
work := testhelper.NewWork(tt.clusterObj.GetName(), tt.clusterObj.GetNamespace(), string(uuid.NewUUID()), []byte{})
obj, err := helper.ToUnstructured(tt.clusterObj)
assert.NoError(t, err)

resourceHealth := c.interpretHealth(obj, work)
assert.Equalf(t, tt.expectedResourceHealth, resourceHealth, "expected resource health %v, got %v", tt.expectedResourceHealth, resourceHealth)

if tt.expectedEventReason != "" {
eventRecorder := c.EventRecorder.(*record.FakeRecorder)
assert.Equal(t, 1, len(eventRecorder.Events))
e := <-eventRecorder.Events
assert.Containsf(t, e, tt.expectedEventReason, "expected event reason %v, got %v", tt.expectedEventReason, e)
}
})
}
}

0 comments on commit 8e50532

Please sign in to comment.