From 2ae03480b48d7b1bffb20227889c7f1c2a12f60e Mon Sep 17 00:00:00 2001 From: Ahmad Malik Ibrahim Date: Mon, 18 Dec 2023 14:10:18 -0800 Subject: [PATCH] feat: implement client to send FinalizeCleanup requests to spectro-cleanup (#155) * feat: implement gRPC client to send FinalizeCleanup requests to spectro-cleanup * test: add unit tests for emitFinalizeCleanup() --- go.mod | 3 + go.sum | 6 ++ .../controller/validatorconfig_controller.go | 49 ++++++++++++- .../validatorconfig_controller_test.go | 73 +++++++++++++++++++ 4 files changed, 130 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index fb451fe1..0350d081 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,9 @@ module github.com/spectrocloud-labs/validator go 1.20 require ( + buf.build/gen/go/spectrocloud/spectro-cleanup/connectrpc/go v1.13.0-20231213011348-5645e27c876a.1 + buf.build/gen/go/spectrocloud/spectro-cleanup/protocolbuffers/go v1.31.0-20231213011348-5645e27c876a.2 + connectrpc.com/connect v1.13.0 github.com/go-logr/logr v1.3.0 github.com/onsi/ginkgo/v2 v2.13.2 github.com/onsi/gomega v1.30.0 diff --git a/go.sum b/go.sum index b8503fb3..c6159aa2 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +buf.build/gen/go/spectrocloud/spectro-cleanup/connectrpc/go v1.13.0-20231213011348-5645e27c876a.1 h1:flYv+oyV4sGFTc8UrA6bSxCkGE7mvSUyydrCiXk5s7A= +buf.build/gen/go/spectrocloud/spectro-cleanup/connectrpc/go v1.13.0-20231213011348-5645e27c876a.1/go.mod h1:pNAXVmeA3b2y1Hi/j2poNtPTT0Bvo2LgRK7FThfG0oc= +buf.build/gen/go/spectrocloud/spectro-cleanup/protocolbuffers/go v1.31.0-20231213011348-5645e27c876a.2 h1:ub8BpTL/wC0JVAjnfKzSdqu3xjJBFn4ndVPGu0u3KHU= +buf.build/gen/go/spectrocloud/spectro-cleanup/protocolbuffers/go v1.31.0-20231213011348-5645e27c876a.2/go.mod h1:629c8Zj/8OXoFZZfqhsjqXJ0MIIVuonsR0x8/Nngi+U= +connectrpc.com/connect v1.13.0 h1:lGs5maZZzWOOD+PFFiOt5OncKmMsk9ZdPwpy5jcmaYg= +connectrpc.com/connect v1.13.0/go.mod h1:uHAFHtYgeSZJxXrkN1IunDpKghnTXhYbVh0wW4StPW0= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/internal/controller/validatorconfig_controller.go b/internal/controller/validatorconfig_controller.go index e68c5f72..d48188b8 100644 --- a/internal/controller/validatorconfig_controller.go +++ b/internal/controller/validatorconfig_controller.go @@ -20,11 +20,16 @@ import ( "context" "crypto/sha256" "encoding/base64" + "errors" "fmt" + "net/http" + "os" + "strconv" "strings" "sync" "time" + connect "connectrpc.com/connect" "github.com/go-logr/logr" "golang.org/x/exp/slices" corev1 "k8s.io/api/core/v1" @@ -35,6 +40,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "buf.build/gen/go/spectrocloud/spectro-cleanup/connectrpc/go/cleanup/v1/cleanupv1connect" + cleanv1 "buf.build/gen/go/spectrocloud/spectro-cleanup/protocolbuffers/go/cleanup/v1" v1alpha1 "github.com/spectrocloud-labs/validator/api/v1alpha1" "github.com/spectrocloud-labs/validator/pkg/helm" ) @@ -100,7 +107,14 @@ func (r *ValidatorConfigReconciler) Reconcile(ctx context.Context, req ctrl.Requ if err := r.deletePlugins(ctx, vc); err != nil { return ctrl.Result{}, err } - return ctrl.Result{}, removeFinalizer(ctx, r.Client, vc, CleanupFinalizer) + + err = removeFinalizer(ctx, r.Client, vc, CleanupFinalizer) + + if emitErr := r.emitFinalizeCleanup(); emitErr != nil { + r.Log.Error(emitErr, "Failed to emit FinalizeCleanup request") + } + + return ctrl.Result{}, err } // TODO: implement a proper patcher to avoid this hacky approach with global vars @@ -373,3 +387,36 @@ func isConditionTrue(vc *v1alpha1.ValidatorConfig, chartName string, conditionTy } return vc.Status.Conditions[idx], vc.Status.Conditions[idx].Status == corev1.ConditionTrue } + +func (r *ValidatorConfigReconciler) emitFinalizeCleanup() error { + grpcEnabled := os.Getenv("CLEANUP_GRPC_SERVER_ENABLED") + if grpcEnabled != "true" { + r.Log.V(0).Info("Cleanup job gRPC server is not enabled") + return nil + } + + host := os.Getenv("CLEANUP_GRPC_SERVER_HOST") + if host == "" { + return errors.New("CLEANUP_GRPC_SERVER_HOST is invalid") + } + + port := os.Getenv("CLEANUP_GRPC_SERVER_PORT") + _, err := strconv.Atoi(port) + if err != nil { + return fmt.Errorf("CLEANUP_GRPC_SERVER_PORT is invalid: %w", err) + } + + url := fmt.Sprintf("https://%s:%s", host, port) + client := cleanupv1connect.NewCleanupServiceClient( + http.DefaultClient, + url, + ) + _, err = client.FinalizeCleanup( + context.Background(), + connect.NewRequest(&cleanv1.FinalizeCleanupRequest{}), + ) + if err != nil { + return fmt.Errorf("FinalizeCleanup request to %s failed: %w", url, err) + } + return nil +} diff --git a/internal/controller/validatorconfig_controller_test.go b/internal/controller/validatorconfig_controller_test.go index ce4d3579..d854fb8d 100644 --- a/internal/controller/validatorconfig_controller_test.go +++ b/internal/controller/validatorconfig_controller_test.go @@ -210,3 +210,76 @@ func TestConfigureHelmBasicAuth(t *testing.T) { } } } + +func TestEmitFinalizeCleanup(t *testing.T) { + cs := []struct { + name string + reconciler ValidatorConfigReconciler + env map[string]string + expected error + }{ + { + name: "CLEANUP_GRPC_SERVER_ENABLED is empty", + reconciler: ValidatorConfigReconciler{}, + env: map[string]string{}, + expected: errors.New("CLEANUP_GRPC_SERVER_HOST is empty"), + }, + { + name: "CLEANUP_GRPC_SERVER_ENABLED is disabled", + reconciler: ValidatorConfigReconciler{}, + env: map[string]string{"CLEANUP_GRPC_SERVER_ENABLED": "false"}, + expected: nil, + }, + { + name: "CLEANUP_GRPC_SERVER_HOST is empty", + reconciler: ValidatorConfigReconciler{}, + env: map[string]string{ + "CLEANUP_GRPC_SERVER_ENABLED": "true", + "CLEANUP_GRPC_SERVER_PORT": "1234", + }, + expected: errors.New("CLEANUP_GRPC_SERVER_HOST is invalid"), + }, + { + name: "CLEANUP_GRPC_SERVER_PORT is empty", + reconciler: ValidatorConfigReconciler{}, + env: map[string]string{ + "CLEANUP_GRPC_SERVER_ENABLED": "true", + "CLEANUP_GRPC_SERVER_HOST": "localhost", + }, + expected: errors.New(`CLEANUP_GRPC_SERVER_PORT is invalid: strconv.Atoi: parsing "": invalid syntax`), + }, + { + name: "CLEANUP_GRPC_SERVER_PORT is invalid", + reconciler: ValidatorConfigReconciler{}, + env: map[string]string{ + "CLEANUP_GRPC_SERVER_ENABLED": "true", + "CLEANUP_GRPC_SERVER_HOST": "localhost", + "CLEANUP_GRPC_SERVER_PORT": "abcd", + }, + expected: errors.New(`CLEANUP_GRPC_SERVER_PORT is invalid: strconv.Atoi: parsing "abcd": invalid syntax`), + }, + { + name: "Request fails", + reconciler: ValidatorConfigReconciler{}, + env: map[string]string{ + "CLEANUP_GRPC_SERVER_ENABLED": "true", + "CLEANUP_GRPC_SERVER_HOST": "localhost", + "CLEANUP_GRPC_SERVER_PORT": "1234", + }, + expected: errors.New(`FinalizeCleanup request to https://localhost:1234 failed: unavailable: dial tcp [::1]:1234: connect: connection refused`), + }, + } + for _, c := range cs { + t.Log(c.name) + + os.Clearenv() + for k, v := range c.env { + os.Setenv(k, v) + } + + err := c.reconciler.emitFinalizeCleanup() + if err != nil && !reflect.DeepEqual(err.Error(), c.expected.Error()) { + t.Errorf("expected (%v), got (%v)", c.expected, err) + } + } +}