Merge pull request #2329 from zhuwint/devhealthy
implement InterpretHealth for resource interpreter
This commit is contained in:
commit
431e06a684
|
|
@ -6,7 +6,7 @@ metadata:
|
|||
webhooks:
|
||||
- name: workloads.example.com
|
||||
rules:
|
||||
- operations: [ "InterpretReplica","ReviseReplica","Retain","AggregateStatus" ]
|
||||
- operations: [ "InterpretReplica","ReviseReplica","Retain","AggregateStatus", "InterpretHealth" ]
|
||||
apiGroups: [ "workload.example.io" ]
|
||||
apiVersions: [ "v1alpha1" ]
|
||||
kinds: [ "Workload" ]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
workloadv1alpha1 "github.com/karmada-io/karmada/examples/customresourceinterpreter/apis/workload/v1alpha1"
|
||||
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
|
||||
|
|
@ -41,6 +42,8 @@ func (e *workloadInterpreter) Handle(ctx context.Context, req interpreter.Reques
|
|||
return e.responseWithExploreRetaining(workload, req)
|
||||
case configv1alpha1.InterpreterOperationAggregateStatus:
|
||||
return e.responseWithExploreAggregateStatus(workload, req)
|
||||
case configv1alpha1.InterpreterOperationInterpretHealth:
|
||||
return e.responseWithExploreInterpretHealth(workload)
|
||||
default:
|
||||
return interpreter.Errored(http.StatusBadRequest, fmt.Errorf("wrong request operation type: %s", req.Operation))
|
||||
}
|
||||
|
|
@ -109,3 +112,14 @@ func (e *workloadInterpreter) responseWithExploreAggregateStatus(workload *workl
|
|||
}
|
||||
return interpreter.PatchResponseFromRaw(req.Object.Raw, marshaledBytes)
|
||||
}
|
||||
|
||||
func (e *workloadInterpreter) responseWithExploreInterpretHealth(workload *workloadv1alpha1.Workload) interpreter.Response {
|
||||
healthy := pointer.Bool(false)
|
||||
if workload.Status.ReadyReplicas == *workload.Spec.Replicas {
|
||||
healthy = pointer.Bool(true)
|
||||
}
|
||||
|
||||
res := interpreter.Succeeded("")
|
||||
res.Healthy = healthy
|
||||
return res
|
||||
}
|
||||
|
|
|
|||
|
|
@ -313,3 +313,18 @@ func (e *CustomizedInterpreter) ReflectStatus(ctx context.Context, attributes *w
|
|||
|
||||
return &response.RawStatus, matched, nil
|
||||
}
|
||||
|
||||
// InterpretHealth returns the health state of the object.
|
||||
// return matched value to indicate whether there is a matching hook.
|
||||
func (e *CustomizedInterpreter) InterpretHealth(ctx context.Context, attributes *webhook.RequestAttributes) (healthy bool, matched bool, err error) {
|
||||
var response *webhook.ResponseAttributes
|
||||
response, matched, err = e.interpret(ctx, attributes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if !matched {
|
||||
return
|
||||
}
|
||||
|
||||
return response.Healthy, matched, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ type DefaultInterpreter struct {
|
|||
aggregateStatusHandlers map[schema.GroupVersionKind]aggregateStatusInterpreter
|
||||
dependenciesHandlers map[schema.GroupVersionKind]dependenciesInterpreter
|
||||
reflectStatusHandlers map[schema.GroupVersionKind]reflectStatusInterpreter
|
||||
healthHandlers map[schema.GroupVersionKind]healthInterpreter
|
||||
}
|
||||
|
||||
// NewDefaultInterpreter return a new DefaultInterpreter.
|
||||
|
|
@ -32,6 +33,7 @@ func NewDefaultInterpreter() *DefaultInterpreter {
|
|||
aggregateStatusHandlers: getAllDefaultAggregateStatusInterpreter(),
|
||||
dependenciesHandlers: getAllDefaultDependenciesInterpreter(),
|
||||
reflectStatusHandlers: getAllDefaultReflectStatusInterpreter(),
|
||||
healthHandlers: getAllDefaultHealthInterpreter(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -60,7 +62,8 @@ func (e *DefaultInterpreter) HookEnabled(kind schema.GroupVersionKind, operation
|
|||
}
|
||||
case configv1alpha1.InterpreterOperationInterpretStatus:
|
||||
return true
|
||||
|
||||
case configv1alpha1.InterpreterOperationInterpretHealth:
|
||||
return true
|
||||
// TODO(RainbowMango): more cases should be added here
|
||||
}
|
||||
|
||||
|
|
@ -123,3 +126,13 @@ func (e *DefaultInterpreter) ReflectStatus(object *unstructured.Unstructured) (s
|
|||
// for resource types that don't have a build-in handler, try to collect the whole status from '.status' filed.
|
||||
return reflectWholeStatus(object)
|
||||
}
|
||||
|
||||
// InterpretHealth returns the health state of the object.
|
||||
func (e *DefaultInterpreter) InterpretHealth(object *unstructured.Unstructured) (bool, error) {
|
||||
handler, exist := e.healthHandlers[object.GroupVersionKind()]
|
||||
if exist {
|
||||
return handler(object)
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("default %s interpreter for %q not found", configv1alpha1.InterpreterOperationInterpretHealth, object.GroupVersionKind())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package defaultinterpreter
|
||||
|
||||
import (
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/karmada-io/karmada/pkg/util"
|
||||
"github.com/karmada-io/karmada/pkg/util/helper"
|
||||
)
|
||||
|
||||
type healthInterpreter func(object *unstructured.Unstructured) (bool, error)
|
||||
|
||||
func getAllDefaultHealthInterpreter() map[schema.GroupVersionKind]healthInterpreter {
|
||||
s := make(map[schema.GroupVersionKind]healthInterpreter)
|
||||
s[appsv1.SchemeGroupVersion.WithKind(util.DeploymentKind)] = interpretDeploymentHealth
|
||||
s[appsv1.SchemeGroupVersion.WithKind(util.StatefulSetKind)] = interpretStatefulSetHealth
|
||||
return s
|
||||
}
|
||||
|
||||
func interpretDeploymentHealth(object *unstructured.Unstructured) (bool, error) {
|
||||
deploy := &appsv1.Deployment{}
|
||||
err := helper.ConvertToTypedObject(object, deploy)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
healthy := (deploy.Status.UpdatedReplicas == *deploy.Spec.Replicas) && (deploy.Generation == deploy.Status.ObservedGeneration)
|
||||
return healthy, nil
|
||||
}
|
||||
|
||||
func interpretStatefulSetHealth(object *unstructured.Unstructured) (bool, error) {
|
||||
statefulSet := &appsv1.StatefulSet{}
|
||||
err := helper.ConvertToTypedObject(object, &statefulSet)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
healthy := (statefulSet.Status.UpdatedReplicas == *statefulSet.Spec.Replicas) && (statefulSet.Generation == statefulSet.Status.ObservedGeneration)
|
||||
return healthy, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
package defaultinterpreter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
func Test_interpretDeploymentHealth(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
object *unstructured.Unstructured
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "failed convert to Deployment object",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
},
|
||||
"spec": "format error",
|
||||
"status": "format error",
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "deployment healthy",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
"generation": 1,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": 3,
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"updatedReplicas": 3,
|
||||
"observedGeneration": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "generation not equal to observedGeneration",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
"generation": 1,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": 3,
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"updatedReplicas": 3,
|
||||
"observedGeneration": 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "replicas not equal to updatedReplicas",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-deployment",
|
||||
"generation": 1,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": 3,
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"updatedReplicas": 1,
|
||||
"observedGeneration": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := interpretDeploymentHealth(tt.object)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("interpretDeploymentHealth() err = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("interpretDeploymentHealth() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_interpretStatefulSetHealth(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
object *unstructured.Unstructured
|
||||
want bool
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "failed convert to StatefulSet object",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "StatefulSet",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-statefulSet",
|
||||
},
|
||||
"spec": "format error",
|
||||
"status": "format error",
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "statefulSet healthy",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "StatefulSet",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-statefulSet",
|
||||
"generation": 1,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": 3,
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"updatedReplicas": 3,
|
||||
"observedGeneration": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: true,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "generation not equal to observedGeneration",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "StatefulSet",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-statefulSet",
|
||||
"generation": 1,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": 3,
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"updatedReplicas": 3,
|
||||
"observedGeneration": 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "replicas not equal to updatedReplicas",
|
||||
object: &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "StatefulSet",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "fake-statefulSet",
|
||||
"generation": 1,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"replicas": 3,
|
||||
},
|
||||
"status": map[string]interface{}{
|
||||
"updatedReplicas": 1,
|
||||
"observedGeneration": 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
want: false,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := interpretStatefulSetHealth(tt.object)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("interpretStatefulSetHealth() err = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("interpretStatefulSetHealth() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -42,6 +42,9 @@ type ResourceInterpreter interface {
|
|||
// ReflectStatus returns the status of the object.
|
||||
ReflectStatus(object *unstructured.Unstructured) (status *runtime.RawExtension, err error)
|
||||
|
||||
// InterpretHealth returns the health state of the object.
|
||||
InterpretHealth(object *unstructured.Unstructured) (healthy bool, err error)
|
||||
|
||||
// other common method
|
||||
}
|
||||
|
||||
|
|
@ -198,3 +201,22 @@ func (i *customResourceInterpreterImpl) ReflectStatus(object *unstructured.Unstr
|
|||
status, err = i.defaultInterpreter.ReflectStatus(object)
|
||||
return
|
||||
}
|
||||
|
||||
// InterpretHealth returns the health state of the object.
|
||||
func (i *customResourceInterpreterImpl) InterpretHealth(object *unstructured.Unstructured) (healthy bool, err error) {
|
||||
klog.V(4).Infof("Begin to check health for object: %v %s/%s.", object.GroupVersionKind(), object.GetNamespace(), object.GetName())
|
||||
|
||||
healthy, hookEnabled, err := i.customizedInterpreter.InterpretHealth(context.TODO(), &webhook.RequestAttributes{
|
||||
Operation: configv1alpha1.InterpreterOperationInterpretHealth,
|
||||
Object: object,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if hookEnabled {
|
||||
return
|
||||
}
|
||||
|
||||
healthy, err = i.defaultInterpreter.InterpretHealth(object)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ var supportedInterpreterOperation = sets.NewString(
|
|||
string(configv1alpha1.InterpreterOperationRetain),
|
||||
string(configv1alpha1.InterpreterOperationAggregateStatus),
|
||||
string(configv1alpha1.InterpreterOperationInterpretStatus),
|
||||
string(configv1alpha1.InterpreterOperationInterpretHealth),
|
||||
)
|
||||
|
||||
var acceptedInterpreterContextVersions = []string{configv1alpha1.GroupVersion.Version}
|
||||
|
|
|
|||
Loading…
Reference in New Issue