diff --git a/README.md b/README.md index 42e817e5..d86a987e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## AWS App Mesh Controller For K8s -AWS App Mesh Controller For K8s is a controller to help manage [App Mesh](https://aws.amazon.com/app-mesh/) resources for a Kubernetes cluster. The controller watches custom resources for changes and reflects those changes into the [App Mesh API](https://docs.aws.amazon.com/app-mesh/latest/APIReference/Welcome.html). It is accompanied by the deployment of three custom resource definitions ([CRDs](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)): meshes, virtualnodes, and virtualservices. These map to App Mesh API objects which the controller manages for you. +AWS App Mesh Controller For K8s is a controller to help manage [App Mesh](https://aws.amazon.com/app-mesh/) resources for a Kubernetes cluster. The controller watches custom resources for changes and reflects those changes into the [App Mesh API](https://docs.aws.amazon.com/app-mesh/latest/APIReference/Welcome.html). It is accompanied by the deployment of three custom resource definitions ([CRDs](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/)): meshes, virtualnodes, virtualservices, virtualgateways and gatewayroutes. These map to App Mesh API objects which the controller manages for you. ## Documentation Checkout our [Live Docs](https://aws.github.io/aws-app-mesh-controller-for-k8s/)! diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index cb92d125..b6f64d2d 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -7,7 +7,7 @@ resources: - bases/appmesh.k8s.aws_virtualnodes.yaml - bases/appmesh.k8s.aws_virtualrouters.yaml - bases/appmesh.k8s.aws_virtualgateways.yaml -#- bases/appmesh.k8s.aws_gatewayroutes.yaml +- bases/appmesh.k8s.aws_gatewayroutes.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 2630c7e1..64739379 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -50,6 +50,26 @@ rules: - get - patch - update +- apiGroups: + - appmesh.k8s.aws + resources: + - gatewayroutes + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - appmesh.k8s.aws + resources: + - gatewayroutes/status + verbs: + - get + - patch + - update - apiGroups: - appmesh.k8s.aws resources: diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 53504b15..6614149b 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -6,6 +6,24 @@ metadata: creationTimestamp: null name: mutating-webhook-configuration webhooks: +- clientConfig: + caBundle: Cg== + service: + name: webhook-service + namespace: system + path: /mutate-appmesh-k8s-aws-v1beta2-gatewayroute + failurePolicy: Fail + name: mgatewayroute.appmesh.k8s.aws + rules: + - apiGroups: + - appmesh.k8s.aws + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - gatewayroutes - clientConfig: caBundle: Cg== service: @@ -121,6 +139,24 @@ metadata: creationTimestamp: null name: validating-webhook-configuration webhooks: +- clientConfig: + caBundle: Cg== + service: + name: webhook-service + namespace: system + path: /validate-appmesh-k8s-aws-v1beta2-gatewayroute + failurePolicy: Fail + name: vgatewayroute.appmesh.k8s.aws + rules: + - apiGroups: + - appmesh.k8s.aws + apiVersions: + - v1beta2 + operations: + - CREATE + - UPDATE + resources: + - gatewayroutes - clientConfig: caBundle: Cg== service: diff --git a/controllers/appmesh/gatewayroute_controller.go b/controllers/appmesh/gatewayroute_controller.go index 70621b2f..56f28332 100644 --- a/controllers/appmesh/gatewayroute_controller.go +++ b/controllers/appmesh/gatewayroute_controller.go @@ -18,36 +18,92 @@ package controllers import ( "context" - + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/gatewayroute" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/k8s" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/runtime" "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/source" - appmeshv1beta2 "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2" + appmesh "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2" ) -// GatewayRouteReconciler reconciles a GatewayRoute object -type GatewayRouteReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme +// NewGatewayRouteReconciler constructs new gatewayRouteReconciler +func NewGatewayRouteReconciler( + k8sClient client.Client, + finalizerManager k8s.FinalizerManager, + grResManager gatewayroute.ResourceManager, + log logr.Logger) *gatewayRouteReconciler { + return &gatewayRouteReconciler{ + k8sClient: k8sClient, + finalizerManager: finalizerManager, + grResManager: grResManager, + enqueueRequestsForMeshEvents: gatewayroute.NewEnqueueRequestsForMeshEvents(k8sClient, log), + enqueueRequestsForVirtualGatewayEvents: gatewayroute.NewEnqueueRequestsForVirtualGatewayEvents(k8sClient, log), + log: log, + } } -//// +kubebuilder:rbac:groups=appmesh.k8s.aws,resources=gatewayroutes,verbs=get;list;watch;create;update;patch;delete -//// +kubebuilder:rbac:groups=appmesh.k8s.aws,resources=gatewayroutes/status,verbs=get;update;patch +// gatewayRouteReconciler reconciles a GatewayRoute object +type gatewayRouteReconciler struct { + k8sClient client.Client + finalizerManager k8s.FinalizerManager + grResManager gatewayroute.ResourceManager -func (r *GatewayRouteReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { - _ = context.Background() - _ = r.Log.WithValues("gatewayroute", req.NamespacedName) + enqueueRequestsForMeshEvents handler.EventHandler + enqueueRequestsForVirtualGatewayEvents handler.EventHandler + log logr.Logger +} - // your logic here +// +kubebuilder:rbac:groups=appmesh.k8s.aws,resources=gatewayroutes,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=appmesh.k8s.aws,resources=gatewayroutes/status,verbs=get;update;patch - return ctrl.Result{}, nil +func (r *gatewayRouteReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + return runtime.HandleReconcileError(r.reconcile(req), r.log) } -func (r *GatewayRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *gatewayRouteReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&appmeshv1beta2.GatewayRoute{}). + For(&appmesh.GatewayRoute{}). + Watches(&source.Kind{Type: &appmesh.Mesh{}}, r.enqueueRequestsForMeshEvents). + Watches(&source.Kind{Type: &appmesh.VirtualGateway{}}, r.enqueueRequestsForVirtualGatewayEvents). + WithOptions(controller.Options{MaxConcurrentReconciles: 3}). Complete(r) } + +func (r *gatewayRouteReconciler) reconcile(req ctrl.Request) error { + ctx := context.Background() + gr := &appmesh.GatewayRoute{} + if err := r.k8sClient.Get(ctx, req.NamespacedName, gr); err != nil { + return client.IgnoreNotFound(err) + } + if !gr.DeletionTimestamp.IsZero() { + return r.cleanupGatewayRoute(ctx, gr) + } + return r.reconcileGatewayRoute(ctx, gr) +} + +func (r *gatewayRouteReconciler) reconcileGatewayRoute(ctx context.Context, gr *appmesh.GatewayRoute) error { + if err := r.finalizerManager.AddFinalizers(ctx, gr, k8s.FinalizerAWSAppMeshResources); err != nil { + return err + } + if err := r.grResManager.Reconcile(ctx, gr); err != nil { + return err + } + return nil +} + +func (r *gatewayRouteReconciler) cleanupGatewayRoute(ctx context.Context, gr *appmesh.GatewayRoute) error { + if k8s.HasFinalizer(gr, k8s.FinalizerAWSAppMeshResources) { + if err := r.grResManager.Cleanup(ctx, gr); err != nil { + return err + } + if err := r.finalizerManager.RemoveFinalizers(ctx, gr, k8s.FinalizerAWSAppMeshResources); err != nil { + return err + } + } + return nil +} diff --git a/main.go b/main.go index d45a0b58..846d6f54 100644 --- a/main.go +++ b/main.go @@ -38,6 +38,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/gatewayroute" "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/inject" "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/mesh" "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/virtualgateway" @@ -127,12 +128,14 @@ func main() { cloudMapInstancesReconciler := cloudmap.NewDefaultInstancesReconciler(mgr.GetClient(), cloud.CloudMap(), ctrl.Log, stopChan) meshResManager := mesh.NewDefaultResourceManager(mgr.GetClient(), cloud.AppMesh(), cloud.AccountID(), ctrl.Log) vgResManager := virtualgateway.NewDefaultResourceManager(mgr.GetClient(), cloud.AppMesh(), referencesResolver, cloud.AccountID(), ctrl.Log) + grResManager := gatewayroute.NewDefaultResourceManager(mgr.GetClient(), cloud.AppMesh(), referencesResolver, cloud.AccountID(), ctrl.Log) vnResManager := virtualnode.NewDefaultResourceManager(mgr.GetClient(), cloud.AppMesh(), referencesResolver, cloud.AccountID(), ctrl.Log) vsResManager := virtualservice.NewDefaultResourceManager(mgr.GetClient(), cloud.AppMesh(), referencesResolver, cloud.AccountID(), ctrl.Log) vrResManager := virtualrouter.NewDefaultResourceManager(mgr.GetClient(), cloud.AppMesh(), referencesResolver, cloud.AccountID(), ctrl.Log) cloudMapResManager := cloudmap.NewDefaultResourceManager(mgr.GetClient(), cloud.CloudMap(), referencesResolver, virtualNodeEndpointResolver, cloudMapInstancesReconciler, enableCustomHealthCheck, ctrl.Log) msReconciler := appmeshcontroller.NewMeshReconciler(mgr.GetClient(), finalizerManager, meshMembersFinalizer, meshResManager, ctrl.Log.WithName("controllers").WithName("Mesh")) vgReconciler := appmeshcontroller.NewVirtualGatewayReconciler(mgr.GetClient(), finalizerManager, vgMembersFinalizer, vgResManager, ctrl.Log.WithName("controllers").WithName("VirtualGateway")) + grReconciler := appmeshcontroller.NewGatewayRouteReconciler(mgr.GetClient(), finalizerManager, grResManager, ctrl.Log.WithName("controllers").WithName("GatewayRoute")) vnReconciler := appmeshcontroller.NewVirtualNodeReconciler(mgr.GetClient(), finalizerManager, vnResManager, ctrl.Log.WithName("controllers").WithName("VirtualNode")) cloudMapReconciler := appmeshcontroller.NewCloudMapReconciler(mgr.GetClient(), finalizerManager, cloudMapResManager, ctrl.Log.WithName("controllers").WithName("CloudMap")) vsReconciler := appmeshcontroller.NewVirtualServiceReconciler(mgr.GetClient(), finalizerManager, referencesIndexer, vsResManager, ctrl.Log.WithName("controllers").WithName("VirtualService")) @@ -145,12 +148,14 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "VirtualService") os.Exit(1) } - if err = vgReconciler.SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "VirtualGateway") os.Exit(1) } - + if err = grReconciler.SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "GatewayRoute") + os.Exit(1) + } if err = vnReconciler.SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "VirtualNode") os.Exit(1) @@ -165,15 +170,15 @@ func main() { } meshMembershipDesignator := mesh.NewMembershipDesignator(mgr.GetClient()) - //vgMembershipDesignator := virtualgateway.NewMembershipDesignator(mgr.GetClient()) + vgMembershipDesignator := virtualgateway.NewMembershipDesignator(mgr.GetClient()) vnMembershipDesignator := virtualnode.NewMembershipDesignator(mgr.GetClient()) sidecarInjector := inject.NewSidecarInjector(injectConfig, cloud.Region(), mgr.GetClient(), referencesResolver, vnMembershipDesignator) appmeshwebhook.NewMeshMutator().SetupWithManager(mgr) appmeshwebhook.NewMeshValidator().SetupWithManager(mgr) appmeshwebhook.NewVirtualGatewayMutator(meshMembershipDesignator).SetupWithManager(mgr) appmeshwebhook.NewVirtualGatewayValidator().SetupWithManager(mgr) - //appmeshwebhook.NewGatewayRouteMutator(meshMembershipDesignator, vgMembershipDesignator).SetupWithManager(mgr) - //appmeshwebhook.NewGatewayRouteValidator().SetupWithManager(mgr) + appmeshwebhook.NewGatewayRouteMutator(meshMembershipDesignator, vgMembershipDesignator).SetupWithManager(mgr) + appmeshwebhook.NewGatewayRouteValidator().SetupWithManager(mgr) appmeshwebhook.NewVirtualNodeMutator(meshMembershipDesignator).SetupWithManager(mgr) appmeshwebhook.NewVirtualNodeValidator().SetupWithManager(mgr) appmeshwebhook.NewVirtualServiceMutator(meshMembershipDesignator).SetupWithManager(mgr) diff --git a/pkg/gatewayroute/enqueue_requests_for_mesh_events.go b/pkg/gatewayroute/enqueue_requests_for_mesh_events.go new file mode 100644 index 00000000..89ead52d --- /dev/null +++ b/pkg/gatewayroute/enqueue_requests_for_mesh_events.go @@ -0,0 +1,71 @@ +package gatewayroute + +import ( + "context" + appmesh "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/k8s" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/mesh" + "github.com/go-logr/logr" + "k8s.io/client-go/util/workqueue" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" +) + +func NewEnqueueRequestsForMeshEvents(k8sClient client.Client, log logr.Logger) *enqueueRequestsForMeshEvents { + return &enqueueRequestsForMeshEvents{ + k8sClient: k8sClient, + log: log, + } +} + +var _ handler.EventHandler = (*enqueueRequestsForMeshEvents)(nil) + +type enqueueRequestsForMeshEvents struct { + k8sClient client.Client + log logr.Logger +} + +// Create is called in response to an create event +func (h *enqueueRequestsForMeshEvents) Create(e event.CreateEvent, queue workqueue.RateLimitingInterface) { + // no-op +} + +// Update is called in response to an update event +func (h *enqueueRequestsForMeshEvents) Update(e event.UpdateEvent, queue workqueue.RateLimitingInterface) { + // gatewayRoute reconcile depends on mesh is active or not. + // so we only need to trigger gatewayRoute reconcile if mesh's active status changed. + msOld := e.ObjectOld.(*appmesh.Mesh) + msNew := e.ObjectNew.(*appmesh.Mesh) + + if mesh.IsMeshActive(msOld) != mesh.IsMeshActive(msNew) { + h.enqueueGatewayRoutesForMesh(context.Background(), queue, msNew) + } +} + +// Delete is called in response to a delete event +func (h *enqueueRequestsForMeshEvents) Delete(e event.DeleteEvent, queue workqueue.RateLimitingInterface) { + // no-op +} + +// Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or +// external trigger request +func (h *enqueueRequestsForMeshEvents) Generic(e event.GenericEvent, queue workqueue.RateLimitingInterface) { + // no-op +} + +func (h *enqueueRequestsForMeshEvents) enqueueGatewayRoutesForMesh(ctx context.Context, queue workqueue.RateLimitingInterface, ms *appmesh.Mesh) { + grList := &appmesh.GatewayRouteList{} + if err := h.k8sClient.List(ctx, grList); err != nil { + h.log.Error(err, "failed to enqueue gatewayRoutes for mesh events", + "mesh", k8s.NamespacedName(ms)) + return + } + for _, gr := range grList.Items { + if gr.Spec.MeshRef == nil || !mesh.IsMeshReferenced(ms, *gr.Spec.MeshRef) { + continue + } + queue.Add(ctrl.Request{NamespacedName: k8s.NamespacedName(&gr)}) + } +} diff --git a/pkg/gatewayroute/enqueue_requests_for_mesh_events_test.go b/pkg/gatewayroute/enqueue_requests_for_mesh_events_test.go new file mode 100644 index 00000000..1bcd93c9 --- /dev/null +++ b/pkg/gatewayroute/enqueue_requests_for_mesh_events_test.go @@ -0,0 +1,331 @@ +package gatewayroute + +import ( + "context" + appmesh "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/k8s" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/util/workqueue" + testclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "testing" +) + +func Test_enqueueRequestsForMeshEvents_Update(t *testing.T) { + gr1 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-1", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + }, + } + gr2 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-2", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + }, + } + + type env struct { + gatewayRoutes []*appmesh.GatewayRoute + } + type args struct { + e event.UpdateEvent + } + tests := []struct { + name string + env env + args args + wantRequests []reconcile.Request + }{ + { + name: "meshActive status changed", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{gr1, gr2}, + }, + args: args{ + e: event.UpdateEvent{ + ObjectOld: &appmesh.Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + }, + ObjectNew: &appmesh.Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + Status: appmesh.MeshStatus{ + Conditions: []appmesh.MeshCondition{ + { + Type: appmesh.MeshActive, + Status: corev1.ConditionFalse, + }, + }, + }, + }, + }, + }, + wantRequests: nil, + }, + { + name: "meshActive status changed", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{gr1, gr2}, + }, + args: args{ + e: event.UpdateEvent{ + ObjectOld: &appmesh.Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + }, + ObjectNew: &appmesh.Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + Status: appmesh.MeshStatus{ + Conditions: []appmesh.MeshCondition{ + { + Type: appmesh.MeshActive, + Status: corev1.ConditionTrue, + }, + }, + }, + }, + }, + }, + wantRequests: []reconcile.Request{ + { + NamespacedName: k8s.NamespacedName(gr1), + }, + { + NamespacedName: k8s.NamespacedName(gr2), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + k8sSchema := runtime.NewScheme() + clientgoscheme.AddToScheme(k8sSchema) + appmesh.AddToScheme(k8sSchema) + k8sClient := testclient.NewFakeClientWithScheme(k8sSchema) + queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) + h := &enqueueRequestsForMeshEvents{ + k8sClient: k8sClient, + log: &log.NullLogger{}, + } + + for _, gr := range tt.env.gatewayRoutes { + err := k8sClient.Create(ctx, gr.DeepCopy()) + assert.NoError(t, err) + } + + h.Update(tt.args.e, queue) + var gotRequests []reconcile.Request + queueLen := queue.Len() + for i := 0; i < queueLen; i++ { + item, _ := queue.Get() + gotRequests = append(gotRequests, item.(reconcile.Request)) + } + + opt := cmpopts.SortSlices(compareReconcileRequest) + assert.True(t, cmp.Equal(tt.wantRequests, gotRequests, opt), "diff: %v", cmp.Diff(tt.wantRequests, gotRequests, opt)) + + }) + } +} + +func Test_enqueueRequestsForMeshEvents_enqueueGatewayRoutesForMesh(t *testing.T) { + ms := &appmesh.Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + Status: appmesh.MeshStatus{ + Conditions: []appmesh.MeshCondition{ + { + Type: appmesh.MeshActive, + Status: corev1.ConditionTrue, + }, + }, + }, + } + grWithoutMeshRef := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-without-mesh-ref", + }, + Spec: appmesh.GatewayRouteSpec{}, + } + grWithNonMatchingMeshRef := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-with-non-matching-mesh-ref", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "my-mesh", + UID: "0d65db83-1b4c-40aa-90ba-57064dd73c98", + }, + }, + } + grWithMatchingMeshRef_1 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-with-non-matching-mesh-ref-1", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + }, + } + grWithMatchingMeshRef_2 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-with-non-matching-mesh-ref-2", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "my-mesh", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + }, + }, + } + + type env struct { + gatewayRoutes []*appmesh.GatewayRoute + } + type args struct { + ms *appmesh.Mesh + } + tests := []struct { + name string + env env + args args + wantRequests []reconcile.Request + }{ + { + name: "gr without meshRef shouldn't be enqueued", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grWithoutMeshRef, + }, + }, + args: args{ + ms: ms, + }, + wantRequests: nil, + }, + { + name: "gr with non-matching meshRef shouldn't be enqueued", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grWithNonMatchingMeshRef, + }, + }, + args: args{ + ms: ms, + }, + wantRequests: nil, + }, + { + name: "gr with matching meshRef should be enqueued", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grWithMatchingMeshRef_1, + }, + }, + args: args{ + ms: ms, + }, + wantRequests: []reconcile.Request{ + { + NamespacedName: k8s.NamespacedName(grWithMatchingMeshRef_1), + }, + }, + }, + { + name: "multiple gr should enqueue correctly", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grWithoutMeshRef, + grWithNonMatchingMeshRef, + grWithMatchingMeshRef_1, + grWithMatchingMeshRef_2, + }, + }, + args: args{ + ms: ms, + }, + wantRequests: []reconcile.Request{ + { + NamespacedName: k8s.NamespacedName(grWithMatchingMeshRef_1), + }, + { + NamespacedName: k8s.NamespacedName(grWithMatchingMeshRef_2), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + k8sSchema := runtime.NewScheme() + clientgoscheme.AddToScheme(k8sSchema) + appmesh.AddToScheme(k8sSchema) + k8sClient := testclient.NewFakeClientWithScheme(k8sSchema) + queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) + h := &enqueueRequestsForMeshEvents{ + k8sClient: k8sClient, + log: &log.NullLogger{}, + } + + for _, gr := range tt.env.gatewayRoutes { + err := k8sClient.Create(ctx, gr.DeepCopy()) + assert.NoError(t, err) + } + + h.enqueueGatewayRoutesForMesh(ctx, queue, tt.args.ms) + var gotRequests []reconcile.Request + queueLen := queue.Len() + for i := 0; i < queueLen; i++ { + item, _ := queue.Get() + gotRequests = append(gotRequests, item.(reconcile.Request)) + } + + opt := cmpopts.SortSlices(compareReconcileRequest) + assert.True(t, cmp.Equal(tt.wantRequests, gotRequests, opt), "diff: %v", cmp.Diff(tt.wantRequests, gotRequests, opt)) + }) + } +} + +func compareReconcileRequest(a reconcile.Request, b reconcile.Request) bool { + return a.String() < b.String() +} diff --git a/pkg/gatewayroute/enqueue_requests_for_virtualgateway_events.go b/pkg/gatewayroute/enqueue_requests_for_virtualgateway_events.go new file mode 100644 index 00000000..16e22f6e --- /dev/null +++ b/pkg/gatewayroute/enqueue_requests_for_virtualgateway_events.go @@ -0,0 +1,71 @@ +package gatewayroute + +import ( + "context" + appmesh "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/k8s" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/virtualgateway" + "github.com/go-logr/logr" + "k8s.io/client-go/util/workqueue" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" +) + +func NewEnqueueRequestsForVirtualGatewayEvents(k8sClient client.Client, log logr.Logger) *enqueueRequestsForVirtualGatewayEvents { + return &enqueueRequestsForVirtualGatewayEvents{ + k8sClient: k8sClient, + log: log, + } +} + +var _ handler.EventHandler = (*enqueueRequestsForVirtualGatewayEvents)(nil) + +type enqueueRequestsForVirtualGatewayEvents struct { + k8sClient client.Client + log logr.Logger +} + +// Create is called in response to an create event +func (h *enqueueRequestsForVirtualGatewayEvents) Create(e event.CreateEvent, queue workqueue.RateLimitingInterface) { + // no-op +} + +// Update is called in response to an update event +func (h *enqueueRequestsForVirtualGatewayEvents) Update(e event.UpdateEvent, queue workqueue.RateLimitingInterface) { + // gatewayRoute reconcile depends on virtualGateway is active or not. + // so we only need to trigger gatewayRoute reconcile if virtualGateway's active status changed. + vgOld := e.ObjectOld.(*appmesh.VirtualGateway) + vgNew := e.ObjectNew.(*appmesh.VirtualGateway) + + if virtualgateway.IsVirtualGatewayActive(vgOld) != virtualgateway.IsVirtualGatewayActive(vgNew) { + h.enqueueGatewayRoutesForVirtualGateway(context.Background(), queue, vgNew) + } +} + +// Delete is called in response to a delete event +func (h *enqueueRequestsForVirtualGatewayEvents) Delete(e event.DeleteEvent, queue workqueue.RateLimitingInterface) { + // no-op +} + +// Generic is called in response to an event of an unknown type or a synthetic event triggered as a cron or +// external trigger request +func (h *enqueueRequestsForVirtualGatewayEvents) Generic(e event.GenericEvent, queue workqueue.RateLimitingInterface) { + // no-op +} + +func (h *enqueueRequestsForVirtualGatewayEvents) enqueueGatewayRoutesForVirtualGateway(ctx context.Context, queue workqueue.RateLimitingInterface, vg *appmesh.VirtualGateway) { + grList := &appmesh.GatewayRouteList{} + if err := h.k8sClient.List(ctx, grList); err != nil { + h.log.Error(err, "failed to enqueue gatewayRoutes for virtualGateway events", + "virtualGateway", k8s.NamespacedName(vg)) + return + } + for _, gr := range grList.Items { + if gr.Spec.VirtualGatewayRef == nil || !virtualgateway.IsVirtualGatewayReferenced(vg, *gr.Spec.VirtualGatewayRef) { + continue + } + queue.Add(ctrl.Request{NamespacedName: k8s.NamespacedName(&gr)}) + } +} diff --git a/pkg/gatewayroute/enqueue_requests_for_virtualgateway_events_test.go b/pkg/gatewayroute/enqueue_requests_for_virtualgateway_events_test.go new file mode 100644 index 00000000..33e9d74d --- /dev/null +++ b/pkg/gatewayroute/enqueue_requests_for_virtualgateway_events_test.go @@ -0,0 +1,338 @@ +package gatewayroute + +import ( + "context" + appmesh "github.com/aws/aws-app-mesh-controller-for-k8s/apis/appmesh/v1beta2" + "github.com/aws/aws-app-mesh-controller-for-k8s/pkg/k8s" + "github.com/aws/aws-sdk-go/aws" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/util/workqueue" + testclient "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "testing" +) + +func Test_enqueueRequestsForVirtualGatewayEvents_Update(t *testing.T) { + gr1 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-1", + }, + Spec: appmesh.GatewayRouteSpec{ + VirtualGatewayRef: &appmesh.VirtualGatewayReference{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: aws.String("vg-ns"), + }, + }, + } + gr2 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-2", + }, + Spec: appmesh.GatewayRouteSpec{ + VirtualGatewayRef: &appmesh.VirtualGatewayReference{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: aws.String("vg-ns"), + }, + }, + } + + type env struct { + gatewayRoutes []*appmesh.GatewayRoute + } + type args struct { + e event.UpdateEvent + } + tests := []struct { + name string + env env + args args + wantRequests []reconcile.Request + }{ + { + name: "virtualGatewayActive status changed", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{gr1, gr2}, + }, + args: args{ + e: event.UpdateEvent{ + ObjectOld: &appmesh.VirtualGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: "vg-ns", + }, + }, + ObjectNew: &appmesh.VirtualGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: "vg-ns", + }, + Status: appmesh.VirtualGatewayStatus{ + Conditions: []appmesh.VirtualGatewayCondition{ + { + Type: appmesh.VirtualGatewayActive, + Status: corev1.ConditionFalse, + }, + }, + }, + }, + }, + }, + wantRequests: nil, + }, + { + name: "virtualGatewayActive status changed", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{gr1, gr2}, + }, + args: args{ + e: event.UpdateEvent{ + ObjectOld: &appmesh.VirtualGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: "vg-ns", + }, + }, + ObjectNew: &appmesh.VirtualGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: "vg-ns", + }, + Status: appmesh.VirtualGatewayStatus{ + Conditions: []appmesh.VirtualGatewayCondition{ + { + Type: appmesh.VirtualGatewayActive, + Status: corev1.ConditionTrue, + }, + }, + }, + }, + }, + }, + wantRequests: []reconcile.Request{ + { + NamespacedName: k8s.NamespacedName(gr1), + }, + { + NamespacedName: k8s.NamespacedName(gr2), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + k8sSchema := runtime.NewScheme() + clientgoscheme.AddToScheme(k8sSchema) + appmesh.AddToScheme(k8sSchema) + k8sClient := testclient.NewFakeClientWithScheme(k8sSchema) + queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) + h := &enqueueRequestsForVirtualGatewayEvents{ + k8sClient: k8sClient, + log: &log.NullLogger{}, + } + + for _, gr := range tt.env.gatewayRoutes { + err := k8sClient.Create(ctx, gr.DeepCopy()) + assert.NoError(t, err) + } + + h.Update(tt.args.e, queue) + var gotRequests []reconcile.Request + queueLen := queue.Len() + for i := 0; i < queueLen; i++ { + item, _ := queue.Get() + gotRequests = append(gotRequests, item.(reconcile.Request)) + } + + opt := cmpopts.SortSlices(compareReconcileRequest) + assert.True(t, cmp.Equal(tt.wantRequests, gotRequests, opt), "diff: %v", cmp.Diff(tt.wantRequests, gotRequests, opt)) + + }) + } +} + +func Test_enqueueRequestsForVirtualGatewayEvents_enqueueGatewayRoutesForVirtualGateway(t *testing.T) { + vg := &appmesh.VirtualGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: "vg-ns", + }, + Status: appmesh.VirtualGatewayStatus{ + Conditions: []appmesh.VirtualGatewayCondition{ + { + Type: appmesh.VirtualGatewayActive, + Status: corev1.ConditionTrue, + }, + }, + }, + } + grWithoutVirtualGatewayRef := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-without-vg-ref", + }, + Spec: appmesh.GatewayRouteSpec{}, + } + grWithNonMatchingVirtualGatewayRef := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-with-non-matching-vg-ref", + }, + Spec: appmesh.GatewayRouteSpec{ + VirtualGatewayRef: &appmesh.VirtualGatewayReference{ + Name: "my-vg", + UID: "0d65db83-1b4c-40aa-90ba-57064dd73c98", + Namespace: aws.String("vg-ns"), + }, + }, + } + grWithMatchingVirtualGatewayRef_1 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-with-non-matching-vg-ref-1", + }, + Spec: appmesh.GatewayRouteSpec{ + VirtualGatewayRef: &appmesh.VirtualGatewayReference{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: aws.String("vg-ns"), + }, + }, + } + grWithMatchingVirtualGatewayRef_2 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "my-ns", + Name: "gr-with-non-matching-vg-ref-2", + }, + Spec: appmesh.GatewayRouteSpec{ + VirtualGatewayRef: &appmesh.VirtualGatewayReference{ + Name: "my-vg", + UID: "a385048d-aba8-4235-9a11-4173764c8ab7", + Namespace: aws.String("vg-ns"), + }, + }, + } + + type env struct { + gatewayRoutes []*appmesh.GatewayRoute + } + type args struct { + vg *appmesh.VirtualGateway + } + tests := []struct { + name string + env env + args args + wantRequests []reconcile.Request + }{ + { + name: "gr without virtualGatewayRef shouldn't be enqueued", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grWithoutVirtualGatewayRef, + }, + }, + args: args{ + vg: vg, + }, + wantRequests: nil, + }, + { + name: "gr with non-matching virtualGatewayRef shouldn't be enqueued", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grWithNonMatchingVirtualGatewayRef, + }, + }, + args: args{ + vg: vg, + }, + wantRequests: nil, + }, + { + name: "gr with matching virtualGatewayRef should be enqueued", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grWithMatchingVirtualGatewayRef_1, + }, + }, + args: args{ + vg: vg, + }, + wantRequests: []reconcile.Request{ + { + NamespacedName: k8s.NamespacedName(grWithMatchingVirtualGatewayRef_1), + }, + }, + }, + { + name: "multiple gr should enqueue correctly", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grWithoutVirtualGatewayRef, + grWithNonMatchingVirtualGatewayRef, + grWithMatchingVirtualGatewayRef_1, + grWithMatchingVirtualGatewayRef_2, + }, + }, + args: args{ + vg: vg, + }, + wantRequests: []reconcile.Request{ + { + NamespacedName: k8s.NamespacedName(grWithMatchingVirtualGatewayRef_1), + }, + { + NamespacedName: k8s.NamespacedName(grWithMatchingVirtualGatewayRef_2), + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + k8sSchema := runtime.NewScheme() + clientgoscheme.AddToScheme(k8sSchema) + appmesh.AddToScheme(k8sSchema) + k8sClient := testclient.NewFakeClientWithScheme(k8sSchema) + queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) + h := &enqueueRequestsForVirtualGatewayEvents{ + k8sClient: k8sClient, + log: &log.NullLogger{}, + } + + for _, gr := range tt.env.gatewayRoutes { + err := k8sClient.Create(ctx, gr.DeepCopy()) + assert.NoError(t, err) + } + + h.enqueueGatewayRoutesForVirtualGateway(ctx, queue, tt.args.vg) + var gotRequests []reconcile.Request + queueLen := queue.Len() + for i := 0; i < queueLen; i++ { + item, _ := queue.Get() + gotRequests = append(gotRequests, item.(reconcile.Request)) + } + + opt := cmpopts.SortSlices(compareReconcileRequest) + assert.True(t, cmp.Equal(tt.wantRequests, gotRequests, opt), "diff: %v", cmp.Diff(tt.wantRequests, gotRequests, opt)) + }) + } +} diff --git a/pkg/mesh/members_finalizer.go b/pkg/mesh/members_finalizer.go index e687b003..a82d3e0a 100644 --- a/pkg/mesh/members_finalizer.go +++ b/pkg/mesh/members_finalizer.go @@ -59,11 +59,15 @@ func (m *pendingMembersFinalizer) Finalize(ctx context.Context, ms *appmesh.Mesh if err != nil { return err } - if len(vsMembers) == 0 && len(vrMembers) == 0 && len(vnMembers) == 0 && len(vgMembers) == 0 { + grMembers, err := m.findGatewayRouteMembers(ctx, ms) + if err != nil { + return err + } + if len(vsMembers) == 0 && len(vrMembers) == 0 && len(vnMembers) == 0 && len(vgMembers) == 0 && len(grMembers) == 0 { return nil } - message := m.buildPendingMembersEventMessage(ctx, vsMembers, vrMembers, vnMembers, vgMembers) + message := m.buildPendingMembersEventMessage(ctx, vsMembers, vrMembers, vnMembers, vgMembers, grMembers) m.eventRecorder.Eventf(ms, corev1.EventTypeWarning, "PendingMembersDeletion", message) return runtime.NewRequeueAfterError(errors.New("pending members deletion"), m.evaluateInterval) } @@ -136,9 +140,27 @@ func (m *pendingMembersFinalizer) findVirtualGatewayMembers(ctx context.Context, return members, nil } +// findGatewayRouteMembers find the GatewayRoute members for this mesh. +func (m *pendingMembersFinalizer) findGatewayRouteMembers(ctx context.Context, ms *appmesh.Mesh) ([]*appmesh.GatewayRoute, error) { + grList := &appmesh.GatewayRouteList{} + if err := m.k8sClient.List(ctx, grList); err != nil { + return nil, err + } + members := make([]*appmesh.GatewayRoute, 0, len(grList.Items)) + for i := range grList.Items { + gr := &grList.Items[i] + if gr.Spec.MeshRef == nil || !IsMeshReferenced(ms, *gr.Spec.MeshRef) { + continue + } + members = append(members, gr) + } + return members, nil +} + func (m *pendingMembersFinalizer) buildPendingMembersEventMessage(ctx context.Context, vsMembers []*appmesh.VirtualService, vrMembers []*appmesh.VirtualRouter, - vnMembers []*appmesh.VirtualNode, vgMembers []*appmesh.VirtualGateway) string { + vnMembers []*appmesh.VirtualNode, vgMembers []*appmesh.VirtualGateway, + grMembers []*appmesh.GatewayRoute) string { var messagePerObjectTypes []string if len(vsMembers) != 0 { message := fmt.Sprintf("virtualService: %v", len(vsMembers)) @@ -156,6 +178,10 @@ func (m *pendingMembersFinalizer) buildPendingMembersEventMessage(ctx context.Co message := fmt.Sprintf("virtualGateway: %v", len(vgMembers)) messagePerObjectTypes = append(messagePerObjectTypes, message) } + if len(grMembers) != 0 { + message := fmt.Sprintf("gatewayRoute: %v", len(grMembers)) + messagePerObjectTypes = append(messagePerObjectTypes, message) + } return "objects belong to this mesh exists, please delete them to proceed. " + strings.Join(messagePerObjectTypes, ", ") } diff --git a/pkg/mesh/members_finalizer_test.go b/pkg/mesh/members_finalizer_test.go index 38588ca4..d407e011 100644 --- a/pkg/mesh/members_finalizer_test.go +++ b/pkg/mesh/members_finalizer_test.go @@ -24,6 +24,7 @@ func Test_pendingMembersFinalizer_buildPendingMembersEventMessage(t *testing.T) vrMembers []*appmesh.VirtualRouter vnMembers []*appmesh.VirtualNode vgMembers []*appmesh.VirtualGateway + grMembers []*appmesh.GatewayRoute } tests := []struct { name string @@ -132,6 +133,34 @@ func Test_pendingMembersFinalizer_buildPendingMembersEventMessage(t *testing.T) }, want: "objects belong to this mesh exists, please delete them to proceed. virtualGateway: 2", }, + { + name: "2 gatewayRoutes and 1 virtualnode pending", + args: args{ + grMembers: []*appmesh.GatewayRoute{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "gr-1", + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "gr-2", + }, + }, + }, + vnMembers: []*appmesh.VirtualNode{ + { + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "vn-1", + }, + }, + }, + }, + want: "objects belong to this mesh exists, please delete them to proceed. virtualNode: 1, gatewayRoute: 2", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -146,12 +175,140 @@ func Test_pendingMembersFinalizer_buildPendingMembersEventMessage(t *testing.T) eventRecorder: eventRecorder, log: &log.NullLogger{}, } - got := m.buildPendingMembersEventMessage(ctx, tt.args.vsMembers, tt.args.vrMembers, tt.args.vnMembers, tt.args.vgMembers) + got := m.buildPendingMembersEventMessage(ctx, tt.args.vsMembers, tt.args.vrMembers, tt.args.vnMembers, + tt.args.vgMembers, tt.args.grMembers) assert.Equal(t, tt.want, got) }) } } +func Test_pendingMembersFinalizer_findGatewayRouteMembers(t *testing.T) { + ms := &appmesh.Mesh{ + ObjectMeta: metav1.ObjectMeta{ + Name: "mesh-1", + UID: "uid-1", + }, + } + grInMesh_1 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-1", + Name: "gr-1", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "mesh-1", + UID: "uid-1", + }, + }, + } + grInMesh_2 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-2", + Name: "gr-2", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "mesh-1", + UID: "uid-1", + }, + }, + } + grNotInMesh_1 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-3", + Name: "gr-3", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "mesh-2", + UID: "uid-2", + }, + }, + } + grNotInMesh_2 := &appmesh.GatewayRoute{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns-4", + Name: "gr-4", + }, + Spec: appmesh.GatewayRouteSpec{ + MeshRef: &appmesh.MeshReference{ + Name: "mesh-1", + UID: "uid-2", + }, + }, + } + + type env struct { + gatewayRoutes []*appmesh.GatewayRoute + } + type args struct { + ms *appmesh.Mesh + } + tests := []struct { + name string + env env + args args + want []*appmesh.GatewayRoute + wantErr error + }{ + { + name: "found no gatewayRoute", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{}, + }, + args: args{ + ms: ms, + }, + want: []*appmesh.GatewayRoute{}, + }, + { + name: "found gatewayRoutes that matches", + env: env{ + gatewayRoutes: []*appmesh.GatewayRoute{ + grInMesh_1, grInMesh_2, grNotInMesh_1, grNotInMesh_2, + }, + }, + args: args{ + ms: ms, + }, + want: []*appmesh.GatewayRoute{grInMesh_1, grInMesh_2}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := context.Background() + k8sSchema := runtime.NewScheme() + clientgoscheme.AddToScheme(k8sSchema) + appmesh.AddToScheme(k8sSchema) + k8sClient := testclient.NewFakeClientWithScheme(k8sSchema) + eventRecorder := record.NewFakeRecorder(1) + m := &pendingMembersFinalizer{ + k8sClient: k8sClient, + eventRecorder: eventRecorder, + log: &log.NullLogger{}, + evaluateInterval: pendingMembersFinalizerEvaluateInterval, + } + + for _, gr := range tt.env.gatewayRoutes { + err := k8sClient.Create(ctx, gr.DeepCopy()) + assert.NoError(t, err) + } + + got, err := m.findGatewayRouteMembers(ctx, tt.args.ms) + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + } else { + assert.NoError(t, err) + opts := cmp.Options{ + equality.IgnoreFakeClientPopulatedFields(), + cmpopts.SortSlices(compareGatewayRoute), + } + assert.True(t, cmp.Equal(tt.want, got, opts), "diff", cmp.Diff(tt.want, got, opts)) + } + }) + } +} + func Test_pendingMembersFinalizer_findVirtualServiceMembers(t *testing.T) { ms := &appmesh.Mesh{ ObjectMeta: metav1.ObjectMeta{ @@ -828,3 +985,7 @@ func compareVirtualRouter(a *appmesh.VirtualRouter, b *appmesh.VirtualRouter) bo func compareVirtualGateway(a *appmesh.VirtualGateway, b *appmesh.VirtualGateway) bool { return k8s.NamespacedName(a).String() < k8s.NamespacedName(b).String() } + +func compareGatewayRoute(a *appmesh.GatewayRoute, b *appmesh.GatewayRoute) bool { + return k8s.NamespacedName(a).String() < k8s.NamespacedName(b).String() +} diff --git a/pkg/virtualgateway/members_finalizer_test.go b/pkg/virtualgateway/members_finalizer_test.go index 2785b4cd..131bb250 100644 --- a/pkg/virtualgateway/members_finalizer_test.go +++ b/pkg/virtualgateway/members_finalizer_test.go @@ -217,8 +217,9 @@ func Test_pendingMembersFinalizer_findGatewayRouteMembers(t *testing.T) { func Test_pendingMembersFinalizer_Finalize(t *testing.T) { vg := &appmesh.VirtualGateway{ ObjectMeta: metav1.ObjectMeta{ - Name: "my-vg", - UID: "uid-1", + Name: "my-vg", + UID: "uid-1", + Namespace: "vg-ns", }, } gr := &appmesh.GatewayRoute{ @@ -228,8 +229,9 @@ func Test_pendingMembersFinalizer_Finalize(t *testing.T) { }, Spec: appmesh.GatewayRouteSpec{ VirtualGatewayRef: &appmesh.VirtualGatewayReference{ - Name: "my-vg", - UID: "uid-1", + Name: "my-vg", + UID: "uid-1", + Namespace: aws.String("vg-ns"), }, }, } diff --git a/pkg/virtualgateway/resource_manager.go b/pkg/virtualgateway/resource_manager.go index 948fe0d0..a04590dd 100644 --- a/pkg/virtualgateway/resource_manager.go +++ b/pkg/virtualgateway/resource_manager.go @@ -227,7 +227,7 @@ func (m *defaultResourceManager) updateCRDVirtualGateway(ctx context.Context, vg if !needsUpdate { return nil } - return m.k8sClient.Patch(ctx, vg, client.MergeFrom(oldVG)) + return m.k8sClient.Status().Patch(ctx, vg, client.MergeFrom(oldVG)) } func (m *defaultResourceManager) buildSDKVirtualGatewaySpec(ctx context.Context, vg *appmesh.VirtualGateway) (*appmeshsdk.VirtualGatewaySpec, error) { diff --git a/pkg/virtualgateway/utils.go b/pkg/virtualgateway/utils.go index 6a082d5d..70ed33ff 100644 --- a/pkg/virtualgateway/utils.go +++ b/pkg/virtualgateway/utils.go @@ -18,5 +18,5 @@ func IsVirtualGatewayActive(vg *appmesh.VirtualGateway) bool { // IsVirtualGatewayReferenced tests whether given virtualGateway is referenced by virtualGatewayReference func IsVirtualGatewayReferenced(vg *appmesh.VirtualGateway, reference appmesh.VirtualGatewayReference) bool { - return vg.Name == reference.Name && vg.UID == reference.UID + return vg.Name == reference.Name && vg.Namespace == *reference.Namespace && vg.UID == reference.UID } diff --git a/webhooks/appmesh/gatewayroute_mutator.go b/webhooks/appmesh/gatewayroute_mutator.go index 488706c9..ec192076 100644 --- a/webhooks/appmesh/gatewayroute_mutator.go +++ b/webhooks/appmesh/gatewayroute_mutator.go @@ -98,7 +98,7 @@ func (m *gatewayRouteMutator) designateVirtualGatewayMembership(ctx context.Cont return virtualGateway, nil } -//// +kubebuilder:webhook:path=/mutate-appmesh-k8s-aws-v1beta2-gatewayroute,mutating=true,failurePolicy=fail,groups=appmesh.k8s.aws,resources=gatewayroutes,verbs=create;update,versions=v1beta2,name=mgatewayroute.appmesh.k8s.aws +// +kubebuilder:webhook:path=/mutate-appmesh-k8s-aws-v1beta2-gatewayroute,mutating=true,failurePolicy=fail,groups=appmesh.k8s.aws,resources=gatewayroutes,verbs=create;update,versions=v1beta2,name=mgatewayroute.appmesh.k8s.aws func (m *gatewayRouteMutator) SetupWithManager(mgr ctrl.Manager) { mgr.GetWebhookServer().Register(apiPathMutateAppMeshGatewayRoute, webhook.MutatingWebhookForMutator(m)) diff --git a/webhooks/appmesh/gatewayroute_validator.go b/webhooks/appmesh/gatewayroute_validator.go index 733c7d17..6c84c800 100644 --- a/webhooks/appmesh/gatewayroute_validator.go +++ b/webhooks/appmesh/gatewayroute_validator.go @@ -63,7 +63,7 @@ func (v *gatewayRouteValidator) enforceFieldsImmutability(newGR *appmesh.Gateway return nil } -//// +kubebuilder:webhook:path=/validate-appmesh-k8s-aws-v1beta2-gatewayroute,mutating=false,failurePolicy=fail,groups=appmesh.k8s.aws,resources=gatewayroutes,verbs=create;update,versions=v1beta2,name=vgatewayroute.appmesh.k8s.aws +// +kubebuilder:webhook:path=/validate-appmesh-k8s-aws-v1beta2-gatewayroute,mutating=false,failurePolicy=fail,groups=appmesh.k8s.aws,resources=gatewayroutes,verbs=create;update,versions=v1beta2,name=vgatewayroute.appmesh.k8s.aws func (v *gatewayRouteValidator) SetupWithManager(mgr ctrl.Manager) { mgr.GetWebhookServer().Register(apiPathValidateAppMeshGatewayRoute, webhook.ValidatingWebhookForValidator(v))