Skip to content

Commit

Permalink
add ApprovedAccepted status in approvalRequests
Browse files Browse the repository at this point in the history
  • Loading branch information
jwtty committed Jan 23, 2025
1 parent 080c442 commit c9bf1a9
Show file tree
Hide file tree
Showing 9 changed files with 323 additions and 25 deletions.
2 changes: 1 addition & 1 deletion .codespellignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ AfterAll
CROs
NotIn
fo
allReady
allReady
5 changes: 5 additions & 0 deletions apis/placement/v1alpha1/stagedupdate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,11 @@ const (
// Its condition status can be:
// - "True": The request is approved.
ApprovalRequestConditionApproved ApprovalRequestConditionType = "Approved"

// ApprovalRequestConditionApprovalAccepted indicates if the approved approval request was accepted.
// Its condition status can be:
// - "True": The request is approved.
ApprovalRequestConditionApprovalAccepted ApprovalRequestConditionType = "ApprovalAccepted"
)

// ClusterApprovalRequestList contains a list of ClusterApprovalRequest.
Expand Down
5 changes: 5 additions & 0 deletions apis/placement/v1beta1/stageupdate_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,11 @@ const (
// Its condition status can be:
// - "True": The request is approved.
ApprovalRequestConditionApproved ApprovalRequestConditionType = "Approved"

// ApprovalRequestConditionApprovalAccepted indicates if the approved approval request was accepted.
// Its condition status can be:
// - "True": The request is approved.
ApprovalRequestConditionApprovalAccepted ApprovalRequestConditionType = "ApprovalAccepted"
)

// ClusterApprovalRequestList contains a list of ClusterApprovalRequest.
Expand Down
33 changes: 22 additions & 11 deletions pkg/controllers/updaterun/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,28 +235,39 @@ func (r *Reconciler) SetupWithManager(mgr runtime.Manager) error {
// We only care about when an approval request is approved.
UpdateFunc: func(ctx context.Context, e event.UpdateEvent, q workqueue.RateLimitingInterface) {
klog.V(2).InfoS("Handling a clusterApprovalRequest update event", "clusterApprovalRequest", klog.KObj(e.ObjectNew))
handleClusterApprovalRequest(e.ObjectNew, q)
},
GenericFunc: func(ctx context.Context, e event.GenericEvent, q workqueue.RateLimitingInterface) {
klog.V(2).InfoS("Handling a clusterApprovalRequest generic event", "clusterApprovalRequest", klog.KObj(e.Object))
handleClusterApprovalRequest(e.Object, q)
handleClusterApprovalRequest(e.ObjectOld, e.ObjectNew, q)
},
}).Complete(r)
}

// handleClusterApprovalRequest finds the ClusterStagedUpdateRun creating the ClusterApprovalRequest,
// and enqueues it to the ClusterStagedUpdateRun controller queue.
func handleClusterApprovalRequest(obj client.Object, q workqueue.RateLimitingInterface) {
approvalRequest, ok := obj.(*placementv1beta1.ClusterApprovalRequest)
// and enqueues it to the ClusterStagedUpdateRun controller queue only when the approved condition gets changed.
func handleClusterApprovalRequest(oldObj, newObj client.Object, q workqueue.RateLimitingInterface) {
oldAppReq, ok := oldObj.(*placementv1beta1.ClusterApprovalRequest)
if !ok {
klog.V(2).ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("cannot cast runtime object to ClusterApprovalRequest")),
"Invalid object type", "object", klog.KObj(oldObj))
return
}
newAppReq, ok := newObj.(*placementv1beta1.ClusterApprovalRequest)
if !ok {
klog.V(2).ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("cannot cast runtime object to ClusterApprovalRequest")),
"Invalid object type", "object", klog.KObj(obj))
"Invalid object type", "object", klog.KObj(newObj))
return
}
updateRun := approvalRequest.Spec.TargetUpdateRun

approvedInOld := condition.IsConditionStatusTrue(meta.FindStatusCondition(oldAppReq.Status.Conditions, string(placementv1beta1.ApprovalRequestConditionApproved)), oldAppReq.Generation)
approvedInNew := condition.IsConditionStatusTrue(meta.FindStatusCondition(newAppReq.Status.Conditions, string(placementv1beta1.ApprovalRequestConditionApproved)), newAppReq.Generation)

if approvedInOld == approvedInNew {
klog.V(2).InfoS("The approval status is not changed, ignore queueing", "clusterApprovalRequest", klog.KObj(newAppReq))
return
}

updateRun := newAppReq.Spec.TargetUpdateRun
if len(updateRun) == 0 {
klog.V(2).ErrorS(controller.NewUnexpectedBehaviorError(fmt.Errorf("TargetUpdateRun field in ClusterApprovalRequest is empty")),
"Invalid clusterApprovalRequest", "clusterApprovalRequest", klog.KObj(approvalRequest))
"Invalid clusterApprovalRequest", "clusterApprovalRequest", klog.KObj(newAppReq))
return
}
// enqueue to the updaterun controller queue.
Expand Down
258 changes: 249 additions & 9 deletions pkg/controllers/updaterun/controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package updaterun
import (
"testing"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/util/workqueue"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllertest"
Expand All @@ -18,47 +19,286 @@ import (

func TestHandleClusterApprovalRequest(t *testing.T) {
tests := map[string]struct {
obj client.Object
oldObj client.Object
newObj client.Object
shouldEnqueue bool
queuedName string
}{
"it should not enqueue anything if the obj is not a ClusterApprovalRequest": {
obj: &placementv1beta1.ClusterStagedUpdateRun{},
oldObj: &placementv1beta1.ClusterStagedUpdateRun{},
shouldEnqueue: false,
},
"it should not enqueue anything if targetUpdateRun in spec is empty": {
obj: &placementv1beta1.ClusterApprovalRequest{
oldObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "",
},
},
newObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionTrue,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
shouldEnqueue: false,
},
"it should enqueue the targetUpdateRun if it is not empty": {
obj: &placementv1beta1.ClusterApprovalRequest{
"it should enqueue the targetUpdateRun if oldObj is not approved while newobj is approved": {
oldObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
},
newObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionTrue,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
shouldEnqueue: true,
queuedName: "test",
},
"it should enqueue the targetUpdateRun if oldObj is not declined while newobj is approved": {
oldObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionFalse,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
newObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionTrue,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
shouldEnqueue: true,
queuedName: "test",
},
"it should enqueue the targetUpdateRun if oldObj is approved while newobj is not approved": {
oldObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionTrue,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
newObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
},
shouldEnqueue: true,
queuedName: "test",
},
"it should enqueue the targetUpdateRun if oldObj is approved while newobj is declined": {
oldObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionTrue,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
newObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionFalse,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
shouldEnqueue: true,
queuedName: "test",
},
"it should not enqueue the targetUpdateRun if neither oldObj nor newobj is approved": {
oldObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
},
newObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
},
shouldEnqueue: false,
},
"it should not enqueue the targetUpdateRun if both oldObj and newobj are approved": {
oldObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionTrue,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
newObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionTrue,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
shouldEnqueue: false,
},
"it should not enqueue the targetUpdateRun if both oldObj and newobj are declined": {
oldObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionFalse,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
newObj: &placementv1beta1.ClusterApprovalRequest{
ObjectMeta: metav1.ObjectMeta{
Generation: 1,
},
Spec: placementv1beta1.ApprovalRequestSpec{
TargetUpdateRun: "test",
},
Status: placementv1beta1.ApprovalRequestStatus{
Conditions: []metav1.Condition{
{
Status: metav1.ConditionFalse,
Type: string(placementv1beta1.ApprovalRequestConditionApproved),
ObservedGeneration: 1,
},
},
},
},
shouldEnqueue: false,
},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
queue := &controllertest.Queue{Interface: workqueue.New()}
handleClusterApprovalRequest(tt.obj, queue)
handleClusterApprovalRequest(tt.oldObj, tt.newObj, queue)
if got := queue.Len() != 0; got != tt.shouldEnqueue {
t.Errorf("handleClusterApprovalRequest() shouldEnqueue test `%s` got %t, want %t", name, got, tt.shouldEnqueue)
t.Fatalf("handleClusterApprovalRequest() shouldEnqueue test `%s` got %t, want %t", name, got, tt.shouldEnqueue)
}
if tt.shouldEnqueue {
item, _ := queue.Get()
req, ok := item.(reconcile.Request)
if !ok {
t.Errorf("handleClusterApprovalRequest() queuedItem test `%s` got %T, want reconcile.Request", name, item)
t.Fatalf("handleClusterApprovalRequest() queuedItem test `%s` got %T, want reconcile.Request", name, item)
}
if req.Name != tt.queuedName {
t.Errorf("handleClusterApprovalRequest() queuedName test `%s` got %s, want %s", name, req.Name, tt.queuedName)
t.Fatalf("handleClusterApprovalRequest() queuedName test `%s` got %s, want %s", name, req.Name, tt.queuedName)
}
}
})
Expand Down
Loading

0 comments on commit c9bf1a9

Please sign in to comment.