Skip to content
This repository has been archived by the owner on Jun 8, 2022. It is now read-only.

Commit

Permalink
add health check support for containerized.standard.oam.dev in Health…
Browse files Browse the repository at this point in the history
…Scope

add unit tests

Signed-off-by: roy wang <seiwy2010@gmail.com>
  • Loading branch information
captainroy-hy committed Sep 22, 2020
1 parent fd7ae98 commit 5aa9a38
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 6 deletions.
15 changes: 9 additions & 6 deletions pkg/controller/v1alpha2/core/scopes/healthscope/healthscope.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ import (
const (
infoFmtUnknownWorkload = "APIVersion %v Kind %v workload is unknown for HealthScope "
infoFmtReady = "Ready: %d/%d "
infoFmtNoChildRes = "cannot get child resource references of ContainerizedWorkload %v"
infoFmtNoChildRes = "cannot get child resource references of workload %v"
errHealthCheck = "error occurs in health check "

defaultTimeout = 10 * time.Second
Expand Down Expand Up @@ -97,26 +97,30 @@ func CheckContainerziedWorkloadHealth(ctx context.Context, c client.Client, ref
HealthStatus: StatusHealthy,
TargetWorkload: ref,
}

cwObj := corev1alpha2.ContainerizedWorkload{}
cwObj.SetGroupVersionKind(corev1alpha2.SchemeGroupVersion.WithKind(kindContainerizedWorkload))
if err := c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: ref.Name}, &cwObj); err != nil {
r.HealthStatus = StatusUnhealthy
r.Diagnosis = errors.Wrap(err, errHealthCheck).Error()
return r
}

r.ComponentName = getComponentNameFromLabel(&cwObj)
r.TargetWorkload.UID = cwObj.GetUID()

subConditions := []*WorkloadHealthCondition{}
childRefs := cwObj.Status.Resources
checkContainerizedChildResources(ctx, c, namespace, r, ref, childRefs)
return r
}

func checkContainerizedChildResources(ctx context.Context, c client.Client, namespace string, r *WorkloadHealthCondition, ref runtimev1alpha1.TypedReference, childRefs []runtimev1alpha1.TypedReference) {
subConditions := []*WorkloadHealthCondition{}
if len(childRefs) != 2 {
// one deployment and one svc are required by containerizedworkload
r.Diagnosis = fmt.Sprintf(infoFmtNoChildRes, ref.Name)
r.HealthStatus = StatusUnhealthy
return r
return
}

for _, childRef := range childRefs {
switch childRef.Kind {
case kindDeployment:
Expand Down Expand Up @@ -145,7 +149,6 @@ func CheckContainerziedWorkloadHealth(ctx context.Context, c client.Client, ref
}
r.Diagnosis = fmt.Sprintf("%s%s", r.Diagnosis, sc.Diagnosis)
}
return r
}

// CheckDeploymentHealth checks health condition of Deployment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ func NewReconciler(m ctrl.Manager, o ...ReconcilerOption) *Reconciler {
record: event.NewNopRecorder(),
traitChecker: WorkloadHealthCheckFn(CheckByHealthCheckTrait),
checkers: []WorloadHealthChecker{
WorkloadHealthCheckFn(CheckStandardContainerziedHealth),
WorkloadHealthCheckFn(CheckContainerziedWorkloadHealth),
WorkloadHealthCheckFn(CheckDeploymentHealth),
WorkloadHealthCheckFn(CheckStatefulsetHealth),
Expand Down
55 changes: 55 additions & 0 deletions pkg/controller/v1alpha2/core/scopes/healthscope/standard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package healthscope

import (
"context"

runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/pkg/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
kuberuntime "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var (
standardContainerziedGVK = schema.GroupVersionKind{
Group: "standard.oam.dev",
Version: "v1alpha1",
Kind: "Containerized",
}
)

// CheckStandardContainerziedHealth check health condition of containerizeds.standard.oam.dev
func CheckStandardContainerziedHealth(ctx context.Context, c client.Client, ref runtimev1alpha1.TypedReference, namespace string) *WorkloadHealthCondition {
if ref.GroupVersionKind() != standardContainerziedGVK {
return nil
}
r := &WorkloadHealthCondition{
HealthStatus: StatusHealthy,
TargetWorkload: ref,
}
containerizedObj := unstructured.Unstructured{}
containerizedObj.SetGroupVersionKind(ref.GroupVersionKind())
if err := c.Get(ctx, types.NamespacedName{Namespace: namespace, Name: ref.Name}, &containerizedObj); err != nil {
r.HealthStatus = StatusUnhealthy
r.Diagnosis = errors.Wrap(err, errHealthCheck).Error()
return r
}
r.ComponentName = getComponentNameFromLabel(&containerizedObj)
r.TargetWorkload.UID = containerizedObj.GetUID()

childRefsData, _, _ := unstructured.NestedSlice(containerizedObj.Object, "status", "resources")
childRefs := []runtimev1alpha1.TypedReference{}
for _, v := range childRefsData {
v := v.(map[string]interface{})
tmpChildRef := &runtimev1alpha1.TypedReference{}
if err := kuberuntime.DefaultUnstructuredConverter.FromUnstructured(v, tmpChildRef); err != nil {
r.HealthStatus = StatusUnhealthy
r.Diagnosis = errors.Wrap(err, errHealthCheck).Error()
}
childRefs = append(childRefs, *tmpChildRef)
}
checkContainerizedChildResources(ctx, c, namespace, r, ref, childRefs)
return r
}
135 changes: 135 additions & 0 deletions pkg/controller/v1alpha2/core/scopes/healthscope/standard_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package healthscope

import (
"context"
"testing"

runtimev1alpha1 "github.com/crossplane/crossplane-runtime/apis/core/v1alpha1"
"github.com/crossplane/crossplane-runtime/pkg/test"
"github.com/stretchr/testify/assert"
apps "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"

"github.com/crossplane/oam-kubernetes-runtime/pkg/oam/util"
)

func TestCheckStandardContainerziedHealth(t *testing.T) {
mockClient := test.NewMockClient()
scRef := runtimev1alpha1.TypedReference{}
scRef.SetGroupVersionKind(standardContainerziedGVK)

deployRef := runtimev1alpha1.TypedReference{}
deployRef.SetGroupVersionKind(apps.SchemeGroupVersion.WithKind(kindDeployment))
svcRef := runtimev1alpha1.TypedReference{}
svcRef.SetGroupVersionKind(apps.SchemeGroupVersion.WithKind(kindService))

deployRefData, _ := util.Object2Map(deployRef)
svcRefData, _ := util.Object2Map(svcRef)

scUnstructured := unstructured.Unstructured{}
scUnstructured.SetGroupVersionKind(standardContainerziedGVK)
unstructured.SetNestedSlice(scUnstructured.Object, []interface{}{deployRefData, svcRefData}, "status", "resources")

tests := []struct {
caseName string
mockGetFn test.MockGetFn
wlRef runtimev1alpha1.TypedReference
expect *WorkloadHealthCondition
}{
{
caseName: "not matched checker",
wlRef: runtimev1alpha1.TypedReference{},
expect: nil,
},
{
caseName: "healthy workload",
wlRef: scRef,
mockGetFn: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error {
if o, ok := obj.(*unstructured.Unstructured); ok {
*o = scUnstructured
return nil
}
if o, ok := obj.(*apps.Deployment); ok {
*o = apps.Deployment{
Spec: apps.DeploymentSpec{
Replicas: &varInt1,
},
Status: apps.DeploymentStatus{
ReadyReplicas: 1, // healthy
},
}
}
return nil
},
expect: &WorkloadHealthCondition{
HealthStatus: StatusHealthy,
},
},
{
caseName: "unhealthy for deployment not ready",
wlRef: scRef,
mockGetFn: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error {
if o, ok := obj.(*unstructured.Unstructured); ok {
*o = scUnstructured
return nil
}
if o, ok := obj.(*apps.Deployment); ok {
*o = apps.Deployment{
Spec: apps.DeploymentSpec{
Replicas: &varInt1,
},
Status: apps.DeploymentStatus{
ReadyReplicas: 0, // unhealthy
},
}
}
return nil
},
expect: &WorkloadHealthCondition{
HealthStatus: StatusUnhealthy,
},
},
{
caseName: "unhealthy for ContainerizedWorkload not found",
wlRef: scRef,
mockGetFn: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error {
return errMockErr
},
expect: &WorkloadHealthCondition{
HealthStatus: StatusUnhealthy,
},
},
{
caseName: "unhealthy for deployment not found",
wlRef: scRef,
mockGetFn: func(ctx context.Context, key types.NamespacedName, obj runtime.Object) error {
if o, ok := obj.(*unstructured.Unstructured); ok {
*o = scUnstructured
return nil
}
if _, ok := obj.(*apps.Deployment); ok {
return errMockErr
}
return nil
},
expect: &WorkloadHealthCondition{
HealthStatus: StatusUnhealthy,
},
},
}

for _, tc := range tests {
func(t *testing.T) {
mockClient.MockGet = tc.mockGetFn
result := CheckStandardContainerziedHealth(ctx, mockClient, tc.wlRef, namespace)
if tc.expect == nil {
assert.Nil(t, result, tc.caseName)
} else {
assert.Equal(t, tc.expect.HealthStatus, result.HealthStatus, tc.caseName)
}

}(t)
}
}

0 comments on commit 5aa9a38

Please sign in to comment.