From 9a3d83cad02d5b7230c10c332d0d5467eaa7dd53 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Wed, 23 Oct 2024 16:54:23 +0200 Subject: [PATCH 01/18] Added component and pod events endpoints --- api/environments/environment_controller.go | 131 +++++++++++++++++++++ api/environments/environment_handler.go | 49 ++++++-- api/events/event_handler.go | 20 +--- api/events/event_handler_test.go | 16 +-- 4 files changed, 181 insertions(+), 35 deletions(-) diff --git a/api/environments/environment_controller.go b/api/environments/environment_controller.go index 6c0abf53..809d69e2 100644 --- a/api/environments/environment_controller.go +++ b/api/environments/environment_controller.go @@ -63,6 +63,16 @@ func (c *environmentController) GetRoutes() models.Routes { Method: "GET", HandlerFunc: c.GetEnvironmentEvents, }, + models.Route{ + Path: rootPath + "/environments/{envName}/events/components/{componentName}", + Method: "GET", + HandlerFunc: c.GetComponentEvents, + }, + models.Route{ + Path: rootPath + "/environments/{envName}/events/components/{componentName}/replicas/{podName}", + Method: "GET", + HandlerFunc: c.GetPodEvents, + }, models.Route{ Path: rootPath + "/environments/{envName}/components/{componentName}/stop", Method: "POST", @@ -533,6 +543,127 @@ func (c *environmentController) GetEnvironmentEvents(accounts models.Accounts, w } +// GetComponentEvents Get events for an application environment component +func (c *environmentController) GetComponentEvents(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /applications/{appName}/environments/{envName}/events/components/{componentName} environment getComponentEvents + // --- + // summary: Lists events for an application environment + // parameters: + // - name: appName + // in: path + // description: name of Radix application + // type: string + // required: true + // - name: envName + // in: path + // description: name of environment + // type: string + // required: true + // - name: componentName + // in: path + // description: Name of component + // type: string + // required: true + // - name: Impersonate-User + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) + // type: string + // required: false + // - name: Impersonate-Group + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) + // type: string + // required: false + // responses: + // "200": + // description: "Successful get environment events" + // schema: + // type: "array" + // items: + // "$ref": "#/definitions/Event" + // "401": + // description: "Unauthorized" + // "404": + // description: "Not found" + + appName := mux.Vars(r)["appName"] + envName := mux.Vars(r)["envName"] + componentName := mux.Vars(r)["componentName"] + + environmentHandler := c.environmentHandlerFactory(accounts) + events, err := environmentHandler.GetComponentEvents(r.Context(), appName, envName, componentName) + + if err != nil { + c.ErrorResponse(w, r, err) + return + } + + c.JSONResponse(w, r, events) +} + +// GetPodEvents Get events for an application environment component +func (c *environmentController) GetPodEvents(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { + // swagger:operation GET /applications/{appName}/environments/{envName}/events/components/{componentName}/replicas/{podName} environment getReplicaEvents + // --- + // summary: Lists events for an application environment + // parameters: + // - name: appName + // in: path + // description: name of Radix application + // type: string + // required: true + // - name: envName + // in: path + // description: name of environment + // type: string + // required: true + // - name: componentName + // in: path + // description: Name of component + // type: string + // required: true + // - name: podName + // in: path + // description: Name of pod + // type: string + // required: true + // - name: Impersonate-User + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set) + // type: string + // required: false + // - name: Impersonate-Group + // in: header + // description: Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set) + // type: string + // required: false + // responses: + // "200": + // description: "Successful get environment events" + // schema: + // type: "array" + // items: + // "$ref": "#/definitions/Event" + // "401": + // description: "Unauthorized" + // "404": + // description: "Not found" + + appName := mux.Vars(r)["appName"] + envName := mux.Vars(r)["envName"] + componentName := mux.Vars(r)["componentName"] + + environmentHandler := c.environmentHandlerFactory(accounts) + events, err := environmentHandler.GetPodEvents(r.Context(), appName, envName, componentName) + + if err != nil { + c.ErrorResponse(w, r, err) + return + } + + c.JSONResponse(w, r, events) +} + // StopComponent Stops job func (c *environmentController) StopComponent(accounts models.Accounts, w http.ResponseWriter, r *http.Request) { // swagger:operation POST /applications/{appName}/environments/{envName}/components/{componentName}/stop component stopComponent diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index edd09999..5d1b3a6a 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -256,28 +256,63 @@ func (eh EnvironmentHandler) DeleteEnvironment(ctx context.Context, appName, env // GetEnvironmentEvents Handler for GetEnvironmentEvents func (eh EnvironmentHandler) GetEnvironmentEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { - radixApplication, err := kubequery.GetRadixApplication(ctx, eh.accounts.UserAccount.RadixClient, appName) + radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) if err != nil { return nil, err } - _, err = kubequery.GetRadixEnvironment(ctx, eh.accounts.ServiceAccount.RadixClient, appName, envName) - // _, err = eh.getConfigurationStatus(ctx, envName, radixApplication) + environmentEvents, err := eh.eventHandler.GetEvents(ctx, radixApplication.Name, envName) if err != nil { - if errors.IsNotFound(err) { - return nil, environmentModels.NonExistingEnvironment(err, appName, envName) - } return nil, err } - environmentEvents, err := eh.eventHandler.GetEvents(ctx, events.RadixEnvironmentNamespace(radixApplication, envName)) + return environmentEvents, nil +} + +// GetComponentEvents Handler for GetComponentEvents +func (eh EnvironmentHandler) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { + radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) if err != nil { return nil, err } + // TODO + environmentEvents, err := eh.eventHandler.GetEvents(ctx, radixApplication.Name, envName) + if err != nil { + return nil, err + } + return environmentEvents, nil +} +// GetPodEvents Handler for GetReplicaEvents +func (eh EnvironmentHandler) GetPodEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { + radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) + if err != nil { + return nil, err + } + // TODO + environmentEvents, err := eh.eventHandler.GetEvents(ctx, radixApplication.Name, envName) + if err != nil { + return nil, err + } return environmentEvents, nil } +func (eh EnvironmentHandler) getRadixApplicationAndValidateEnvironment(ctx context.Context, appName string, envName string) (*radixv1.RadixApplication, error) { + radixApplication, err := kubequery.GetRadixApplication(ctx, eh.accounts.UserAccount.RadixClient, appName) + if err != nil { + return nil, err + } + + _, err = kubequery.GetRadixEnvironment(ctx, eh.accounts.ServiceAccount.RadixClient, appName, envName) + if err != nil { + if errors.IsNotFound(err) { + return nil, environmentModels.NonExistingEnvironment(err, appName, envName) + } + return nil, err + } + return radixApplication, err +} + // getNotOrphanedEnvNames returns a slice of non-unique-names of not-orphaned environments func (eh EnvironmentHandler) getNotOrphanedEnvNames(ctx context.Context, appName string) ([]string, error) { reList, err := kubequery.GetRadixEnvironments(ctx, eh.accounts.ServiceAccount.RadixClient, appName) diff --git a/api/events/event_handler.go b/api/events/event_handler.go index 5ae90535..95b81ef8 100644 --- a/api/events/event_handler.go +++ b/api/events/event_handler.go @@ -4,8 +4,7 @@ import ( "context" eventModels "github.com/equinor/radix-api/api/events/models" - v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" - k8sObjectUtils "github.com/equinor/radix-operator/pkg/apis/utils" + "github.com/equinor/radix-operator/pkg/apis/utils" k8v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -13,20 +12,13 @@ import ( // EventHandler defines methods for interacting with Kubernetes events type EventHandler interface { - GetEvents(ctx context.Context, namespaceFunc NamespaceFunc) ([]*eventModels.Event, error) + GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) } // NamespaceFunc defines a function that returns a namespace // Used as argument in GetEvents to filter events by namespace type NamespaceFunc func() string -// RadixEnvironmentNamespace builds a namespace from a RadixApplication and environment name -func RadixEnvironmentNamespace(ra *v1.RadixApplication, envName string) NamespaceFunc { - return func() string { - return k8sObjectUtils.GetEnvironmentNamespace(ra.Name, envName) - } -} - type eventHandler struct { kubeClient kubernetes.Interface } @@ -37,12 +29,8 @@ func Init(kubeClient kubernetes.Interface) EventHandler { } // GetEvents return events for a namespace defined by a NamespaceFunc function -func (eh *eventHandler) GetEvents(ctx context.Context, namespaceFunc NamespaceFunc) ([]*eventModels.Event, error) { - namespace := namespaceFunc() - return eh.getEvents(ctx, namespace) -} - -func (eh *eventHandler) getEvents(ctx context.Context, namespace string) ([]*eventModels.Event, error) { +func (eh *eventHandler) GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { + namespace := utils.GetEnvironmentNamespace(appName, envName) k8sEvents, err := eh.kubeClient.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{}) if err != nil { return nil, err diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index cd811f2c..6f29b503 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -20,14 +20,6 @@ func Test_EventHandler_Init(t *testing.T) { assert.Equal(t, kubeClient, eh.kubeClient) } -func Test_EventHandler_RadixEnvironmentNamespace(t *testing.T) { - appName, envName := "app", "env" - expected := operatorutils.GetEnvironmentNamespace(appName, envName) - ra := operatorutils.NewRadixApplicationBuilder().WithAppName(appName).BuildRA() - actual := RadixEnvironmentNamespace(ra, envName)() - assert.Equal(t, expected, actual) -} - func Test_EventHandler_GetEventsForRadixApplication(t *testing.T) { appName, envName := "app", "env" appNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) @@ -39,7 +31,7 @@ func Test_EventHandler_GetEventsForRadixApplication(t *testing.T) { ra := operatorutils.NewRadixApplicationBuilder().WithAppName(appName).BuildRA() eventHandler := Init(kubeClient) - events, err := eventHandler.GetEvents(context.Background(), RadixEnvironmentNamespace(ra, envName)) + events, err := eventHandler.GetEvents(context.Background(), ra.Name, envName) assert.Nil(t, err) assert.Len(t, events, 2) assert.ElementsMatch( @@ -60,7 +52,7 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { _, err := createKubernetesPod(kubeClient, "pod1", appNamespace, true, true, 0) require.NoError(t, err) eventHandler := Init(kubeClient) - events, _ := eventHandler.GetEvents(context.Background(), RadixEnvironmentNamespace(ra, envName)) + events, _ := eventHandler.GetEvents(context.Background(), ra.Name, envName) assert.Len(t, events, 1) assert.Nil(t, events[0].InvolvedObjectState) }) @@ -71,7 +63,7 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { _, err := createKubernetesPod(kubeClient, "pod1", appNamespace, true, false, 0) require.NoError(t, err) eventHandler := Init(kubeClient) - events, _ := eventHandler.GetEvents(context.Background(), RadixEnvironmentNamespace(ra, envName)) + events, _ := eventHandler.GetEvents(context.Background(), ra.Name, envName) assert.Len(t, events, 1) assert.NotNil(t, events[0].InvolvedObjectState) assert.NotNil(t, events[0].InvolvedObjectState.Pod) @@ -81,7 +73,7 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { kubeClient := kubefake.NewSimpleClientset() createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Normal", "pod1", "Pod") eventHandler := Init(kubeClient) - events, _ := eventHandler.GetEvents(context.Background(), RadixEnvironmentNamespace(ra, envName)) + events, _ := eventHandler.GetEvents(context.Background(), ra.Name, envName) assert.Len(t, events, 1) assert.Nil(t, events[0].InvolvedObjectState) }) From 0c8bd16800c10e69454e64c85620e8a4d2875f6a Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 24 Oct 2024 11:24:50 +0200 Subject: [PATCH 02/18] Added get component logic --- api/environments/environment_handler.go | 25 ++++- api/events/event_handler.go | 41 +++++++- swaggerui/html/swagger.json | 129 ++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 4 deletions(-) diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 5d1b3a6a..7066115e 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -275,15 +275,21 @@ func (eh EnvironmentHandler) GetComponentEvents(ctx context.Context, appName, en if err != nil { return nil, err } - // TODO - environmentEvents, err := eh.eventHandler.GetEvents(ctx, radixApplication.Name, envName) + ok, err := eh.existsRadixDeployComponent(ctx, radixApplication.Name, envName, componentName) + if err != nil { + return nil, err + } + if !ok { + return nil, nil + } + environmentEvents, err := eh.eventHandler.GetComponentEvents(ctx, radixApplication.Name, envName, componentName) if err != nil { return nil, err } return environmentEvents, nil } -// GetPodEvents Handler for GetReplicaEvents +// GetPodEvents Handler for GetPodEvents func (eh EnvironmentHandler) GetPodEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) if err != nil { @@ -313,6 +319,19 @@ func (eh EnvironmentHandler) getRadixApplicationAndValidateEnvironment(ctx conte return radixApplication, err } +func (eh EnvironmentHandler) existsRadixDeployComponent(ctx context.Context, appName, envName, componentName string) (bool, error) { + radixDeployments, err := kubequery.GetRadixDeploymentsForEnvironments(ctx, eh.accounts.UserAccount.RadixClient, appName, []string{envName}, 1) + if err != nil { + return false, err + } + activeRd, ok := slice.FindFirst(radixDeployments, func(rd radixv1.RadixDeployment) bool { return rd.Status.ActiveTo.IsZero() }) + if !ok { + return false, nil + } + _, ok = slice.FindFirst(activeRd.Spec.Components, func(c radixv1.RadixDeployComponent) bool { return c.GetName() == componentName }) + return ok, nil +} + // getNotOrphanedEnvNames returns a slice of non-unique-names of not-orphaned environments func (eh EnvironmentHandler) getNotOrphanedEnvNames(ctx context.Context, appName string) ([]string, error) { reList, err := kubequery.GetRadixEnvironments(ctx, eh.accounts.ServiceAccount.RadixClient, appName) diff --git a/api/events/event_handler.go b/api/events/event_handler.go index 95b81ef8..b200f0f8 100644 --- a/api/events/event_handler.go +++ b/api/events/event_handler.go @@ -2,6 +2,8 @@ package events import ( "context" + "fmt" + "regexp" eventModels "github.com/equinor/radix-api/api/events/models" "github.com/equinor/radix-operator/pkg/apis/utils" @@ -13,6 +15,7 @@ import ( // EventHandler defines methods for interacting with Kubernetes events type EventHandler interface { GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) + GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) } // NamespaceFunc defines a function that returns a namespace @@ -28,8 +31,17 @@ func Init(kubeClient kubernetes.Interface) EventHandler { return &eventHandler{kubeClient: kubeClient} } -// GetEvents return events for a namespace defined by a NamespaceFunc function +// GetEvents return events for a namespace defined by a namespace func (eh *eventHandler) GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { + return eh.getEvents(ctx, appName, envName, "") +} + +// GetEventsForComponent return events for a namespace defined by a namespace for a specific component +func (eh *eventHandler) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { + return eh.getEvents(ctx, appName, envName, componentName) +} + +func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { namespace := utils.GetEnvironmentNamespace(appName, envName) k8sEvents, err := eh.kubeClient.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{}) if err != nil { @@ -38,6 +50,9 @@ func (eh *eventHandler) GetEvents(ctx context.Context, appName, envName string) events := make([]*eventModels.Event, 0) for _, ev := range k8sEvents.Items { + if !isComponentEvent(ev, componentName) { + continue + } builder := eventModels.NewEventBuilder().WithKubernetesEvent(ev) buildObjectState(ctx, builder, ev, eh.kubeClient) event := builder.Build() @@ -47,6 +62,30 @@ func (eh *eventHandler) GetEvents(ctx context.Context, appName, envName string) return events, nil } +func isComponentEvent(ev k8v1.Event, componentName string) bool { + if len(componentName) == 0 { + return true + } + if ev.InvolvedObject.Kind == "Deployment" && ev.InvolvedObject.Name == componentName { + return true + } + replicaSetNameRegex, err := regexp.Compile(fmt.Sprintf("^%s-[a-z0-9]{9,10}$", componentName)) + if err != nil { + return false + } + if ev.InvolvedObject.Kind == "ReplicaSet" && replicaSetNameRegex.MatchString(ev.InvolvedObject.Name) { + return true + } + podNameRegex, err := regexp.Compile(fmt.Sprintf("^%s-[a-z0-9]{9,10}-[a-z0-9]{5}$", componentName)) + if err != nil { + return false + } + if ev.InvolvedObject.Kind == "Pod" && podNameRegex.MatchString(ev.InvolvedObject.Name) { + return true + } + return false +} + func buildObjectState(ctx context.Context, builder eventModels.EventBuilder, event k8v1.Event, kubeClient kubernetes.Interface) { if event.Type == "Normal" { return diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 8ceb289f..a3bebd9e 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -2642,6 +2642,135 @@ } } }, + "/applications/{appName}/environments/{envName}/events/components/{componentName}": { + "get": { + "tags": [ + "environment" + ], + "summary": "Lists events for an application environment", + "operationId": "getComponentEvents", + "parameters": [ + { + "type": "string", + "description": "name of Radix application", + "name": "appName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of environment", + "name": "envName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of component", + "name": "componentName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", + "name": "Impersonate-User", + "in": "header" + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set)", + "name": "Impersonate-Group", + "in": "header" + } + ], + "responses": { + "200": { + "description": "Successful get environment events", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Event" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + } + } + }, + "/applications/{appName}/environments/{envName}/events/components/{componentName}/replicas/{podName}": { + "get": { + "tags": [ + "environment" + ], + "summary": "Lists events for an application environment", + "operationId": "getReplicaEvents", + "parameters": [ + { + "type": "string", + "description": "name of Radix application", + "name": "appName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "name of environment", + "name": "envName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of component", + "name": "componentName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Name of pod", + "name": "podName", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of test users (Required if Impersonate-Group is set)", + "name": "Impersonate-User", + "in": "header" + }, + { + "type": "string", + "description": "Works only with custom setup of cluster. Allow impersonation of a comma-seperated list of test groups (Required if Impersonate-User is set)", + "name": "Impersonate-Group", + "in": "header" + } + ], + "responses": { + "200": { + "description": "Successful get environment events", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Event" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "404": { + "description": "Not found" + } + } + } + }, "/applications/{appName}/environments/{envName}/jobcomponents/{jobComponentName}/batches": { "get": { "tags": [ From 64293de085a9156da84c5e511ccf49f240744bfe Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 24 Oct 2024 13:47:44 +0200 Subject: [PATCH 03/18] Added get pod events logic --- api/environments/environment_controller.go | 3 +- api/environments/environment_handler.go | 28 +++++------ api/events/event_handler.go | 56 ++++++++++++++++------ api/events/models/event_builder.go | 3 +- 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/api/environments/environment_controller.go b/api/environments/environment_controller.go index 809d69e2..3c1d63a0 100644 --- a/api/environments/environment_controller.go +++ b/api/environments/environment_controller.go @@ -652,9 +652,10 @@ func (c *environmentController) GetPodEvents(accounts models.Accounts, w http.Re appName := mux.Vars(r)["appName"] envName := mux.Vars(r)["envName"] componentName := mux.Vars(r)["componentName"] + podName := mux.Vars(r)["podName"] environmentHandler := c.environmentHandlerFactory(accounts) - events, err := environmentHandler.GetPodEvents(r.Context(), appName, envName, componentName) + events, err := environmentHandler.GetPodEvents(r.Context(), appName, envName, componentName, podName) if err != nil { c.ErrorResponse(w, r, err) diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 7066115e..5bed6f8a 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -271,18 +271,10 @@ func (eh EnvironmentHandler) GetEnvironmentEvents(ctx context.Context, appName, // GetComponentEvents Handler for GetComponentEvents func (eh EnvironmentHandler) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { - radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) - if err != nil { - return nil, err - } - ok, err := eh.existsRadixDeployComponent(ctx, radixApplication.Name, envName, componentName) - if err != nil { + if exists, err := eh.existsDeployedComponent(ctx, appName, envName, componentName); !exists || err != nil { return nil, err } - if !ok { - return nil, nil - } - environmentEvents, err := eh.eventHandler.GetComponentEvents(ctx, radixApplication.Name, envName, componentName) + environmentEvents, err := eh.eventHandler.GetComponentEvents(ctx, appName, envName, componentName) if err != nil { return nil, err } @@ -290,13 +282,11 @@ func (eh EnvironmentHandler) GetComponentEvents(ctx context.Context, appName, en } // GetPodEvents Handler for GetPodEvents -func (eh EnvironmentHandler) GetPodEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { - radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) - if err != nil { +func (eh EnvironmentHandler) GetPodEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*eventModels.Event, error) { + if exists, err := eh.existsDeployedComponent(ctx, appName, envName, componentName); !exists || err != nil { return nil, err } - // TODO - environmentEvents, err := eh.eventHandler.GetEvents(ctx, radixApplication.Name, envName) + environmentEvents, err := eh.eventHandler.GetPodEvents(ctx, appName, envName, componentName, podName) if err != nil { return nil, err } @@ -332,6 +322,14 @@ func (eh EnvironmentHandler) existsRadixDeployComponent(ctx context.Context, app return ok, nil } +func (eh EnvironmentHandler) existsDeployedComponent(ctx context.Context, appName string, envName string, componentName string) (bool, error) { + radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) + if err != nil { + return true, err + } + return eh.existsRadixDeployComponent(ctx, radixApplication.Name, envName, componentName) +} + // getNotOrphanedEnvNames returns a slice of non-unique-names of not-orphaned environments func (eh EnvironmentHandler) getNotOrphanedEnvNames(ctx context.Context, appName string) ([]string, error) { reList, err := kubequery.GetRadixEnvironments(ctx, eh.accounts.ServiceAccount.RadixClient, appName) diff --git a/api/events/event_handler.go b/api/events/event_handler.go index b200f0f8..52133c6a 100644 --- a/api/events/event_handler.go +++ b/api/events/event_handler.go @@ -16,6 +16,7 @@ import ( type EventHandler interface { GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) + GetPodEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*eventModels.Event, error) } // NamespaceFunc defines a function that returns a namespace @@ -33,15 +34,20 @@ func Init(kubeClient kubernetes.Interface) EventHandler { // GetEvents return events for a namespace defined by a namespace func (eh *eventHandler) GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { - return eh.getEvents(ctx, appName, envName, "") + return eh.getEvents(ctx, appName, envName, "", "") } -// GetEventsForComponent return events for a namespace defined by a namespace for a specific component +// GetComponentEvents return events for a namespace defined by a namespace for a specific component func (eh *eventHandler) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { - return eh.getEvents(ctx, appName, envName, componentName) + return eh.getEvents(ctx, appName, envName, componentName, "") } -func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { +// GetPodEvents return events for a namespace defined by a namespace for a specific pod of a component +func (eh *eventHandler) GetPodEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*eventModels.Event, error) { + return eh.getEvents(ctx, appName, envName, componentName, podName) +} + +func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*eventModels.Event, error) { namespace := utils.GetEnvironmentNamespace(appName, envName) k8sEvents, err := eh.kubeClient.CoreV1().Events(namespace).List(ctx, metav1.ListOptions{}) if err != nil { @@ -50,7 +56,10 @@ func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, compone events := make([]*eventModels.Event, 0) for _, ev := range k8sEvents.Items { - if !isComponentEvent(ev, componentName) { + if len(podName) > 0 && !eventIsRelatedToPod(ev, componentName, podName) { + continue + } + if len(componentName) > 0 && !eventIsRelatedToComponent(ev, componentName) { continue } builder := eventModels.NewEventBuilder().WithKubernetesEvent(ev) @@ -58,32 +67,49 @@ func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, compone event := builder.Build() events = append(events, event) } - return events, nil } -func isComponentEvent(ev k8v1.Event, componentName string) bool { - if len(componentName) == 0 { +func eventIsRelatedToComponent(ev k8v1.Event, componentName string) bool { + if matchingToDeployment(ev, componentName) || matchingToReplicaSet(ev, componentName, "") { return true } - if ev.InvolvedObject.Kind == "Deployment" && ev.InvolvedObject.Name == componentName { + podNameRegex, err := regexp.Compile(fmt.Sprintf("^%s-[a-z0-9]{9,10}-[a-z0-9]{5}$", componentName)) + if err != nil { + return false + } + if ev.InvolvedObject.Kind == "Pod" && podNameRegex.MatchString(ev.InvolvedObject.Name) { return true } - replicaSetNameRegex, err := regexp.Compile(fmt.Sprintf("^%s-[a-z0-9]{9,10}$", componentName)) - if err != nil { + return false +} + +func matchingToDeployment(ev k8v1.Event, componentName string) bool { + return ev.InvolvedObject.Kind == "Deployment" && ev.InvolvedObject.Name == componentName +} + +func matchingToReplicaSet(ev k8v1.Event, componentName, podName string) bool { + if ev.InvolvedObject.Kind != "ReplicaSet" { + return false + } + if replicaSetNameRegex, err := regexp.Compile(fmt.Sprintf("^%s-[a-z0-9]{9,10}$", componentName)); err != nil || !replicaSetNameRegex.MatchString(ev.InvolvedObject.Name) { return false } - if ev.InvolvedObject.Kind == "ReplicaSet" && replicaSetNameRegex.MatchString(ev.InvolvedObject.Name) { + if len(podName) == 0 || len(ev.Message) == 0 { return true } - podNameRegex, err := regexp.Compile(fmt.Sprintf("^%s-[a-z0-9]{9,10}-[a-z0-9]{5}$", componentName)) + podNameRegex, err := regexp.Compile(fmt.Sprintf(`^[\w\s]*:\s%s$`, podName)) if err != nil { return false } - if ev.InvolvedObject.Kind == "Pod" && podNameRegex.MatchString(ev.InvolvedObject.Name) { + return podNameRegex.MatchString(ev.Message) +} + +func eventIsRelatedToPod(ev k8v1.Event, componentName, podName string) bool { + if ev.InvolvedObject.Kind == "Pod" && ev.InvolvedObject.Name == podName { return true } - return false + return matchingToDeployment(ev, componentName) || matchingToReplicaSet(ev, componentName, podName) } func buildObjectState(ctx context.Context, builder eventModels.EventBuilder, event k8v1.Event, kubeClient kubernetes.Interface) { diff --git a/api/events/models/event_builder.go b/api/events/models/event_builder.go index 28cb43b0..204a99e9 100644 --- a/api/events/models/event_builder.go +++ b/api/events/models/event_builder.go @@ -1,6 +1,7 @@ package models import ( + "strings" "time" v1 "k8s.io/api/core/v1" @@ -100,6 +101,6 @@ func (eb *eventBuilder) Build() *Event { InvolvedObjectState: eb.involvedObjectState, Type: eb.eventType, Reason: eb.reason, - Message: eb.message, + Message: strings.ReplaceAll(eb.message, `\"`, "'"), } } From af165c56cf460d1eff3a1813b1b12fc5d40cb272 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Thu, 24 Oct 2024 14:45:49 +0200 Subject: [PATCH 04/18] Improved get pods performance --- api/events/event_handler.go | 87 ++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 35 deletions(-) diff --git a/api/events/event_handler.go b/api/events/event_handler.go index 52133c6a..7884cd31 100644 --- a/api/events/event_handler.go +++ b/api/events/event_handler.go @@ -6,12 +6,21 @@ import ( "regexp" eventModels "github.com/equinor/radix-api/api/events/models" + "github.com/equinor/radix-api/api/kubequery" + "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/utils" - k8v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sTypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" ) +const ( + k8sKindDeployment = "Deployment" + k8sKindReplicaSet = "ReplicaSet" + k8sKindPod = "Pod" +) + // EventHandler defines methods for interacting with Kubernetes events type EventHandler interface { GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) @@ -53,6 +62,10 @@ func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, compone if err != nil { return nil, err } + environmentComponentsPodMap, err := eh.getEnvironmentComponentsPodMap(ctx, appName, envName, err) + if err != nil { + return nil, err + } events := make([]*eventModels.Event, 0) for _, ev := range k8sEvents.Items { @@ -62,15 +75,35 @@ func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, compone if len(componentName) > 0 && !eventIsRelatedToComponent(ev, componentName) { continue } - builder := eventModels.NewEventBuilder().WithKubernetesEvent(ev) - buildObjectState(ctx, builder, ev, eh.kubeClient) - event := builder.Build() + event := eh.buildEvent(ev, environmentComponentsPodMap) events = append(events, event) } return events, nil } -func eventIsRelatedToComponent(ev k8v1.Event, componentName string) bool { +func (eh *eventHandler) getEnvironmentComponentsPodMap(ctx context.Context, appName string, envName string, err error) (map[k8sTypes.UID]*corev1.Pod, error) { + componentPodList, err := kubequery.GetPodsForEnvironmentComponents(ctx, eh.kubeClient, appName, envName) + if err != nil { + return nil, err + } + podMap := slice.Reduce(componentPodList, make(map[k8sTypes.UID]*corev1.Pod), func(acc map[k8sTypes.UID]*corev1.Pod, pod corev1.Pod) map[k8sTypes.UID]*corev1.Pod { + acc[pod.GetUID()] = &pod + return acc + }) + return podMap, nil +} + +func (eh *eventHandler) buildEvent(ev corev1.Event, podMap map[k8sTypes.UID]*corev1.Pod) *eventModels.Event { + builder := eventModels.NewEventBuilder().WithKubernetesEvent(ev) + if ev.Type != "Normal" { + if objectState := getObjectState(ev, podMap); objectState != nil { + builder.WithInvolvedObjectState(objectState) + } + } + return builder.Build() +} + +func eventIsRelatedToComponent(ev corev1.Event, componentName string) bool { if matchingToDeployment(ev, componentName) || matchingToReplicaSet(ev, componentName, "") { return true } @@ -78,18 +111,18 @@ func eventIsRelatedToComponent(ev k8v1.Event, componentName string) bool { if err != nil { return false } - if ev.InvolvedObject.Kind == "Pod" && podNameRegex.MatchString(ev.InvolvedObject.Name) { + if ev.InvolvedObject.Kind == k8sKindPod && podNameRegex.MatchString(ev.InvolvedObject.Name) { return true } return false } -func matchingToDeployment(ev k8v1.Event, componentName string) bool { - return ev.InvolvedObject.Kind == "Deployment" && ev.InvolvedObject.Name == componentName +func matchingToDeployment(ev corev1.Event, componentName string) bool { + return ev.InvolvedObject.Kind == k8sKindDeployment && ev.InvolvedObject.Name == componentName } -func matchingToReplicaSet(ev k8v1.Event, componentName, podName string) bool { - if ev.InvolvedObject.Kind != "ReplicaSet" { +func matchingToReplicaSet(ev corev1.Event, componentName, podName string) bool { + if ev.InvolvedObject.Kind != k8sKindReplicaSet { return false } if replicaSetNameRegex, err := regexp.Compile(fmt.Sprintf("^%s-[a-z0-9]{9,10}$", componentName)); err != nil || !replicaSetNameRegex.MatchString(ev.InvolvedObject.Name) { @@ -105,45 +138,29 @@ func matchingToReplicaSet(ev k8v1.Event, componentName, podName string) bool { return podNameRegex.MatchString(ev.Message) } -func eventIsRelatedToPod(ev k8v1.Event, componentName, podName string) bool { - if ev.InvolvedObject.Kind == "Pod" && ev.InvolvedObject.Name == podName { +func eventIsRelatedToPod(ev corev1.Event, componentName, podName string) bool { + if ev.InvolvedObject.Kind == k8sKindPod && ev.InvolvedObject.Name == podName { return true } return matchingToDeployment(ev, componentName) || matchingToReplicaSet(ev, componentName, podName) } -func buildObjectState(ctx context.Context, builder eventModels.EventBuilder, event k8v1.Event, kubeClient kubernetes.Interface) { - if event.Type == "Normal" { - return - } - - if objectState := getObjectState(ctx, event, kubeClient); objectState != nil { - builder.WithInvolvedObjectState(objectState) - } -} - -func getObjectState(ctx context.Context, event k8v1.Event, kubeClient kubernetes.Interface) *eventModels.ObjectState { +func getObjectState(ev corev1.Event, podMap map[k8sTypes.UID]*corev1.Pod) *eventModels.ObjectState { builder := eventModels.NewObjectStateBuilder() - build := false - obj := event.InvolvedObject + obj := ev.InvolvedObject switch obj.Kind { - case "Pod": - if pod, err := kubeClient.CoreV1().Pods(obj.Namespace).Get(ctx, obj.Name, metav1.GetOptions{}); err == nil { + case k8sKindPod: + if pod, ok := podMap[ev.InvolvedObject.UID]; ok { state := getPodState(pod) builder.WithPodState(state) - build = true + return builder.Build() } } - - if !build { - return nil - } - - return builder.Build() + return nil } -func getPodState(pod *k8v1.Pod) *eventModels.PodState { +func getPodState(pod *corev1.Pod) *eventModels.PodState { return eventModels.NewPodStateBuilder(). WithPod(pod). Build() From c47ab545a3f0833c594413d8828338edbf6bbf9b Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 25 Oct 2024 14:22:27 +0200 Subject: [PATCH 05/18] Extracted event logic to eventHandler. Added ingress events --- api/environments/environment_controller.go | 6 +- api/environments/environment_handler.go | 79 +---------- api/events/event_handler.go | 155 +++++++++++++++++---- api/events/event_handler_test.go | 35 +++-- api/events/models/event.go | 15 ++ api/events/models/ingress_builder.go | 59 ++++++++ api/events/models/object_state_builder.go | 15 +- swaggerui/html/swagger.json | 36 +++++ 8 files changed, 279 insertions(+), 121 deletions(-) create mode 100644 api/events/models/ingress_builder.go diff --git a/api/environments/environment_controller.go b/api/environments/environment_controller.go index 3c1d63a0..059b6a4a 100644 --- a/api/environments/environment_controller.go +++ b/api/environments/environment_controller.go @@ -532,7 +532,7 @@ func (c *environmentController) GetEnvironmentEvents(accounts models.Accounts, w envName := mux.Vars(r)["envName"] environmentHandler := c.environmentHandlerFactory(accounts) - events, err := environmentHandler.GetEnvironmentEvents(r.Context(), appName, envName) + events, err := environmentHandler.eventHandler.GetEnvironmentEvents(r.Context(), appName, envName) if err != nil { c.ErrorResponse(w, r, err) @@ -591,7 +591,7 @@ func (c *environmentController) GetComponentEvents(accounts models.Accounts, w h componentName := mux.Vars(r)["componentName"] environmentHandler := c.environmentHandlerFactory(accounts) - events, err := environmentHandler.GetComponentEvents(r.Context(), appName, envName, componentName) + events, err := environmentHandler.eventHandler.GetComponentEvents(r.Context(), appName, envName, componentName) if err != nil { c.ErrorResponse(w, r, err) @@ -655,7 +655,7 @@ func (c *environmentController) GetPodEvents(accounts models.Accounts, w http.Re podName := mux.Vars(r)["podName"] environmentHandler := c.environmentHandlerFactory(accounts) - events, err := environmentHandler.GetPodEvents(r.Context(), appName, envName, componentName, podName) + events, err := environmentHandler.eventHandler.GetPodEvents(r.Context(), appName, envName, componentName, podName) if err != nil { c.ErrorResponse(w, r, err) diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 5bed6f8a..d51dff2a 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -10,7 +10,6 @@ import ( deploymentModels "github.com/equinor/radix-api/api/deployments/models" environmentModels "github.com/equinor/radix-api/api/environments/models" "github.com/equinor/radix-api/api/events" - eventModels "github.com/equinor/radix-api/api/events/models" "github.com/equinor/radix-api/api/kubequery" apimodels "github.com/equinor/radix-api/api/models" "github.com/equinor/radix-api/api/pods" @@ -39,7 +38,7 @@ type EnvironmentHandlerOptions func(*EnvironmentHandler) func WithAccounts(accounts models.Accounts) EnvironmentHandlerOptions { return func(eh *EnvironmentHandler) { eh.deployHandler = deployments.Init(accounts) - eh.eventHandler = events.Init(accounts.UserAccount.Client) + eh.eventHandler = events.Init(accounts.UserAccount.Client, accounts.UserAccount.RadixClient) eh.accounts = accounts } } @@ -254,82 +253,6 @@ func (eh EnvironmentHandler) DeleteEnvironment(ctx context.Context, appName, env return nil } -// GetEnvironmentEvents Handler for GetEnvironmentEvents -func (eh EnvironmentHandler) GetEnvironmentEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { - radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) - if err != nil { - return nil, err - } - - environmentEvents, err := eh.eventHandler.GetEvents(ctx, radixApplication.Name, envName) - if err != nil { - return nil, err - } - - return environmentEvents, nil -} - -// GetComponentEvents Handler for GetComponentEvents -func (eh EnvironmentHandler) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { - if exists, err := eh.existsDeployedComponent(ctx, appName, envName, componentName); !exists || err != nil { - return nil, err - } - environmentEvents, err := eh.eventHandler.GetComponentEvents(ctx, appName, envName, componentName) - if err != nil { - return nil, err - } - return environmentEvents, nil -} - -// GetPodEvents Handler for GetPodEvents -func (eh EnvironmentHandler) GetPodEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*eventModels.Event, error) { - if exists, err := eh.existsDeployedComponent(ctx, appName, envName, componentName); !exists || err != nil { - return nil, err - } - environmentEvents, err := eh.eventHandler.GetPodEvents(ctx, appName, envName, componentName, podName) - if err != nil { - return nil, err - } - return environmentEvents, nil -} - -func (eh EnvironmentHandler) getRadixApplicationAndValidateEnvironment(ctx context.Context, appName string, envName string) (*radixv1.RadixApplication, error) { - radixApplication, err := kubequery.GetRadixApplication(ctx, eh.accounts.UserAccount.RadixClient, appName) - if err != nil { - return nil, err - } - - _, err = kubequery.GetRadixEnvironment(ctx, eh.accounts.ServiceAccount.RadixClient, appName, envName) - if err != nil { - if errors.IsNotFound(err) { - return nil, environmentModels.NonExistingEnvironment(err, appName, envName) - } - return nil, err - } - return radixApplication, err -} - -func (eh EnvironmentHandler) existsRadixDeployComponent(ctx context.Context, appName, envName, componentName string) (bool, error) { - radixDeployments, err := kubequery.GetRadixDeploymentsForEnvironments(ctx, eh.accounts.UserAccount.RadixClient, appName, []string{envName}, 1) - if err != nil { - return false, err - } - activeRd, ok := slice.FindFirst(radixDeployments, func(rd radixv1.RadixDeployment) bool { return rd.Status.ActiveTo.IsZero() }) - if !ok { - return false, nil - } - _, ok = slice.FindFirst(activeRd.Spec.Components, func(c radixv1.RadixDeployComponent) bool { return c.GetName() == componentName }) - return ok, nil -} - -func (eh EnvironmentHandler) existsDeployedComponent(ctx context.Context, appName string, envName string, componentName string) (bool, error) { - radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) - if err != nil { - return true, err - } - return eh.existsRadixDeployComponent(ctx, radixApplication.Name, envName, componentName) -} - // getNotOrphanedEnvNames returns a slice of non-unique-names of not-orphaned environments func (eh EnvironmentHandler) getNotOrphanedEnvNames(ctx context.Context, appName string) ([]string, error) { reList, err := kubequery.GetRadixEnvironments(ctx, eh.accounts.ServiceAccount.RadixClient, appName) diff --git a/api/events/event_handler.go b/api/events/event_handler.go index 7884cd31..95f8e91e 100644 --- a/api/events/event_handler.go +++ b/api/events/event_handler.go @@ -5,11 +5,16 @@ import ( "fmt" "regexp" + environmentModels "github.com/equinor/radix-api/api/environments/models" eventModels "github.com/equinor/radix-api/api/events/models" "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-common/utils/slice" + radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/equinor/radix-operator/pkg/apis/utils" + radixclient "github.com/equinor/radix-operator/pkg/client/clientset/versioned" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sTypes "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes" @@ -18,12 +23,13 @@ import ( const ( k8sKindDeployment = "Deployment" k8sKindReplicaSet = "ReplicaSet" + k8sKindIngress = "Ingress" k8sKindPod = "Pod" ) // EventHandler defines methods for interacting with Kubernetes events type EventHandler interface { - GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) + GetEnvironmentEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) GetPodEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*eventModels.Event, error) } @@ -33,27 +39,82 @@ type EventHandler interface { type NamespaceFunc func() string type eventHandler struct { - kubeClient kubernetes.Interface + kubeClient kubernetes.Interface + radixClient radixclient.Interface } // Init creates a new EventHandler -func Init(kubeClient kubernetes.Interface) EventHandler { - return &eventHandler{kubeClient: kubeClient} +func Init(kubeClient kubernetes.Interface, radixClient radixclient.Interface) EventHandler { + return &eventHandler{kubeClient: kubeClient, radixClient: radixClient} } -// GetEvents return events for a namespace defined by a namespace -func (eh *eventHandler) GetEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { - return eh.getEvents(ctx, appName, envName, "", "") +// GetEnvironmentEvents return events for a namespace defined by a namespace +func (eh *eventHandler) GetEnvironmentEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { + radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) + if err != nil { + return nil, err + } + environmentEvents, err := eh.getEvents(ctx, radixApplication.Name, envName, "", "") + if err != nil { + return nil, err + } + return environmentEvents, nil } // GetComponentEvents return events for a namespace defined by a namespace for a specific component func (eh *eventHandler) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*eventModels.Event, error) { - return eh.getEvents(ctx, appName, envName, componentName, "") + if ok, err := eh.existsRadixDeployComponent(ctx, appName, envName, componentName); err != nil || !ok { + return nil, err + } + environmentEvents, err := eh.getEvents(ctx, appName, envName, componentName, "") + if err != nil { + return nil, err + } + return environmentEvents, nil } // GetPodEvents return events for a namespace defined by a namespace for a specific pod of a component func (eh *eventHandler) GetPodEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*eventModels.Event, error) { - return eh.getEvents(ctx, appName, envName, componentName, podName) + if ok, err := eh.existsRadixDeployComponent(ctx, appName, envName, componentName); err != nil || !ok { + return nil, err + } + environmentEvents, err := eh.getEvents(ctx, appName, envName, componentName, podName) + if err != nil { + return nil, err + } + return environmentEvents, nil +} + +func (eh *eventHandler) getRadixApplicationAndValidateEnvironment(ctx context.Context, appName string, envName string) (*radixv1.RadixApplication, error) { + radixApplication, err := kubequery.GetRadixApplication(ctx, eh.radixClient, appName) + if err != nil { + return nil, err + } + + _, err = kubequery.GetRadixEnvironment(ctx, eh.radixClient, appName, envName) + if err != nil { + if errors.IsNotFound(err) { + return nil, environmentModels.NonExistingEnvironment(err, appName, envName) + } + return nil, err + } + return radixApplication, err +} + +func (eh *eventHandler) existsRadixDeployComponent(ctx context.Context, appName, envName, componentName string) (bool, error) { + _, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) + if err != nil { + return false, err + } + radixDeployments, err := kubequery.GetRadixDeploymentsForEnvironments(ctx, eh.radixClient, appName, []string{envName}, 1) + if err != nil { + return false, err + } + activeRd, ok := slice.FindFirst(radixDeployments, func(rd radixv1.RadixDeployment) bool { return rd.Status.ActiveTo.IsZero() }) + if !ok { + return false, nil + } + return slice.Any(activeRd.Spec.Components, func(c radixv1.RadixDeployComponent) bool { return c.GetName() == componentName }), nil } func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*eventModels.Event, error) { @@ -62,49 +123,68 @@ func (eh *eventHandler) getEvents(ctx context.Context, appName, envName, compone if err != nil { return nil, err } - environmentComponentsPodMap, err := eh.getEnvironmentComponentsPodMap(ctx, appName, envName, err) + environmentComponentsPodMap, err := eh.getEnvironmentComponentsPodMap(ctx, appName, envName) + if err != nil { + return nil, err + } + + environmentComponentsIngressMap, err := eh.getEnvironmentComponentsIngressMap(ctx, appName, envName) if err != nil { return nil, err } events := make([]*eventModels.Event, 0) for _, ev := range k8sEvents.Items { - if len(podName) > 0 && !eventIsRelatedToPod(ev, componentName, podName) { + if len(podName) > 0 && !eventIsRelatedToPod(ev, componentName, podName, environmentComponentsIngressMap) { continue } - if len(componentName) > 0 && !eventIsRelatedToComponent(ev, componentName) { + if len(componentName) > 0 && !eventIsRelatedToComponent(ev, componentName, environmentComponentsIngressMap) { continue } - event := eh.buildEvent(ev, environmentComponentsPodMap) + event := eh.buildEvent(ev, componentName, environmentComponentsPodMap, environmentComponentsIngressMap) events = append(events, event) } return events, nil } -func (eh *eventHandler) getEnvironmentComponentsPodMap(ctx context.Context, appName string, envName string, err error) (map[k8sTypes.UID]*corev1.Pod, error) { - componentPodList, err := kubequery.GetPodsForEnvironmentComponents(ctx, eh.kubeClient, appName, envName) +func (eh *eventHandler) getEnvironmentComponentsPodMap(ctx context.Context, appName string, envName string) (map[k8sTypes.UID]*corev1.Pod, error) { + componentPods, err := kubequery.GetPodsForEnvironmentComponents(ctx, eh.kubeClient, appName, envName) if err != nil { return nil, err } - podMap := slice.Reduce(componentPodList, make(map[k8sTypes.UID]*corev1.Pod), func(acc map[k8sTypes.UID]*corev1.Pod, pod corev1.Pod) map[k8sTypes.UID]*corev1.Pod { + podMap := slice.Reduce(componentPods, make(map[k8sTypes.UID]*corev1.Pod), func(acc map[k8sTypes.UID]*corev1.Pod, pod corev1.Pod) map[k8sTypes.UID]*corev1.Pod { acc[pod.GetUID()] = &pod return acc }) return podMap, nil } -func (eh *eventHandler) buildEvent(ev corev1.Event, podMap map[k8sTypes.UID]*corev1.Pod) *eventModels.Event { +func (eh *eventHandler) getEnvironmentComponentsIngressMap(ctx context.Context, appName string, envName string) (map[string]*networkingv1.Ingress, error) { + ingresses, err := kubequery.GetIngressesForEnvironments(ctx, eh.kubeClient, appName, []string{envName}, 1) + if err != nil { + return nil, err + } + ingressMap := slice.Reduce(ingresses, make(map[string]*networkingv1.Ingress), func(acc map[string]*networkingv1.Ingress, ingress networkingv1.Ingress) map[string]*networkingv1.Ingress { + acc[ingress.GetName()] = &ingress + return acc + }) + return ingressMap, nil +} + +func (eh *eventHandler) buildEvent(ev corev1.Event, componentName string, podMap map[k8sTypes.UID]*corev1.Pod, ingressMap map[string]*networkingv1.Ingress) *eventModels.Event { builder := eventModels.NewEventBuilder().WithKubernetesEvent(ev) - if ev.Type != "Normal" { - if objectState := getObjectState(ev, podMap); objectState != nil { + if ev.Type != "Normal" || ev.InvolvedObject.Kind == k8sKindIngress { + if objectState := getObjectState(ev, podMap, ingressMap, componentName); objectState != nil { builder.WithInvolvedObjectState(objectState) } } return builder.Build() } -func eventIsRelatedToComponent(ev corev1.Event, componentName string) bool { - if matchingToDeployment(ev, componentName) || matchingToReplicaSet(ev, componentName, "") { +func eventIsRelatedToComponent(ev corev1.Event, componentName string, ingressMap map[string]*networkingv1.Ingress) bool { + if matchingToDeployment(ev, componentName) || + matchingToReplicaSet(ev, componentName, "") || + matchingToIngress(ev, componentName, ingressMap) { return true } podNameRegex, err := regexp.Compile(fmt.Sprintf("^%s-[a-z0-9]{9,10}-[a-z0-9]{5}$", componentName)) @@ -138,14 +218,32 @@ func matchingToReplicaSet(ev corev1.Event, componentName, podName string) bool { return podNameRegex.MatchString(ev.Message) } -func eventIsRelatedToPod(ev corev1.Event, componentName, podName string) bool { +func matchingToIngress(ev corev1.Event, componentName string, ingressMap map[string]*networkingv1.Ingress) bool { + if ev.InvolvedObject.Kind != k8sKindIngress { + return false + } + ingress, ok := ingressMap[ev.InvolvedObject.Name] + if !ok { + return false + } + for _, ingressRule := range ingress.Spec.Rules { + for _, ingressPath := range ingressRule.HTTP.Paths { + if ingressPath.Backend.Service != nil && ingressPath.Backend.Service.Name == componentName { + return true + } + } + } + return false +} + +func eventIsRelatedToPod(ev corev1.Event, componentName, podName string, ingressMap map[string]*networkingv1.Ingress) bool { if ev.InvolvedObject.Kind == k8sKindPod && ev.InvolvedObject.Name == podName { return true } - return matchingToDeployment(ev, componentName) || matchingToReplicaSet(ev, componentName, podName) + return matchingToDeployment(ev, componentName) || matchingToReplicaSet(ev, componentName, podName) || matchingToIngress(ev, componentName, ingressMap) } -func getObjectState(ev corev1.Event, podMap map[k8sTypes.UID]*corev1.Pod) *eventModels.ObjectState { +func getObjectState(ev corev1.Event, podMap map[k8sTypes.UID]*corev1.Pod, ingressMap map[string]*networkingv1.Ingress, componentName string) *eventModels.ObjectState { builder := eventModels.NewObjectStateBuilder() obj := ev.InvolvedObject @@ -156,10 +254,19 @@ func getObjectState(ev corev1.Event, podMap map[k8sTypes.UID]*corev1.Pod) *event builder.WithPodState(state) return builder.Build() } + case k8sKindIngress: + if ingress, ok := ingressMap[ev.InvolvedObject.Name]; ok { + builder.WithIngress(getIngress(ingress, componentName)) + return builder.Build() + } } return nil } +func getIngress(ingress *networkingv1.Ingress, componentName string) []eventModels.IngressRule { + return eventModels.NewIngressBuilder().WithIngress(ingress).WithComponent(componentName).Build() +} + func getPodState(pod *corev1.Pod) *eventModels.PodState { return eventModels.NewPodStateBuilder(). WithPod(pod). diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index 6f29b503..777a81ad 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -5,6 +5,7 @@ import ( "testing" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" + radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" @@ -13,9 +14,15 @@ import ( kubefake "k8s.io/client-go/kubernetes/fake" ) -func Test_EventHandler_Init(t *testing.T) { +func setupTest() (*kubefake.Clientset, *radixfake.Clientset) { kubeClient := kubefake.NewSimpleClientset() - eh := Init(kubeClient).(*eventHandler) + radixClient := radixfake.NewSimpleClientset() + return kubeClient, radixClient +} + +func Test_EventHandler_Init(t *testing.T) { + kubeClient, radixClient := setupTest() + eh := Init(kubeClient, radixClient).(*eventHandler) assert.NotNil(t, eh) assert.Equal(t, kubeClient, eh.kubeClient) } @@ -23,15 +30,15 @@ func Test_EventHandler_Init(t *testing.T) { func Test_EventHandler_GetEventsForRadixApplication(t *testing.T) { appName, envName := "app", "env" appNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) - kubeClient := kubefake.NewSimpleClientset() + kubeClient, radixClient := setupTest() createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Normal", "pod1", "Pod") createKubernetesEvent(t, kubeClient, appNamespace, "ev2", "Normal", "pod2", "Pod") createKubernetesEvent(t, kubeClient, "app2-env", "ev3", "Normal", "pod3", "Pod") ra := operatorutils.NewRadixApplicationBuilder().WithAppName(appName).BuildRA() - eventHandler := Init(kubeClient) - events, err := eventHandler.GetEvents(context.Background(), ra.Name, envName) + eventHandler := Init(kubeClient, radixClient) + events, err := eventHandler.GetEnvironmentEvents(context.Background(), ra.Name, envName) assert.Nil(t, err) assert.Len(t, events, 2) assert.ElementsMatch( @@ -47,33 +54,33 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { ra := operatorutils.NewRadixApplicationBuilder().WithAppName(appName).BuildRA() t.Run("ObjectState is nil for normal event type", func(t *testing.T) { - kubeClient := kubefake.NewSimpleClientset() + kubeClient, radixClient := setupTest() createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Normal", "pod1", "Pod") _, err := createKubernetesPod(kubeClient, "pod1", appNamespace, true, true, 0) require.NoError(t, err) - eventHandler := Init(kubeClient) - events, _ := eventHandler.GetEvents(context.Background(), ra.Name, envName) + eventHandler := Init(kubeClient, radixClient) + events, _ := eventHandler.GetEnvironmentEvents(context.Background(), ra.Name, envName) assert.Len(t, events, 1) assert.Nil(t, events[0].InvolvedObjectState) }) t.Run("ObjectState has Pod state for warning event type", func(t *testing.T) { - kubeClient := kubefake.NewSimpleClientset() + kubeClient, radixClient := setupTest() createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Warning", "pod1", "Pod") _, err := createKubernetesPod(kubeClient, "pod1", appNamespace, true, false, 0) require.NoError(t, err) - eventHandler := Init(kubeClient) - events, _ := eventHandler.GetEvents(context.Background(), ra.Name, envName) + eventHandler := Init(kubeClient, radixClient) + events, _ := eventHandler.GetEnvironmentEvents(context.Background(), ra.Name, envName) assert.Len(t, events, 1) assert.NotNil(t, events[0].InvolvedObjectState) assert.NotNil(t, events[0].InvolvedObjectState.Pod) }) t.Run("ObjectState is nil for warning event type when pod not exist", func(t *testing.T) { - kubeClient := kubefake.NewSimpleClientset() + kubeClient, radixClient := setupTest() createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Normal", "pod1", "Pod") - eventHandler := Init(kubeClient) - events, _ := eventHandler.GetEvents(context.Background(), ra.Name, envName) + eventHandler := Init(kubeClient, radixClient) + events, _ := eventHandler.GetEnvironmentEvents(context.Background(), ra.Name, envName) assert.Len(t, events, 1) assert.Nil(t, events[0].InvolvedObjectState) }) diff --git a/api/events/models/event.go b/api/events/models/event.go index cf5a17cf..8e8d4576 100644 --- a/api/events/models/event.go +++ b/api/events/models/event.go @@ -23,11 +23,26 @@ type PodState struct { RestartCount int32 `json:"restartCount"` } +// IngressRule specs +// swagger:model IngressRule +type IngressRule struct { + // The service name of the ingress + Service string `json:"service"` + // The host name of the ingress + Host string `json:"host"` + // The port of the ingress + Port int32 `json:"port"` + // The path of the ingress + Path string `json:"path"` +} + // ObjectState holds information about the state of objects involved in an event // swagger:model ObjectState type ObjectState struct { // Details about the pod state for a pod related event Pod *PodState `json:"pod"` + // Details about the ingress rules for an ingress related event + IngressRules []IngressRule `json:"ingressRules"` } // Event holds information about Kubernetes events diff --git a/api/events/models/ingress_builder.go b/api/events/models/ingress_builder.go new file mode 100644 index 00000000..65a7c4a2 --- /dev/null +++ b/api/events/models/ingress_builder.go @@ -0,0 +1,59 @@ +package models + +import ( + networkingv1 "k8s.io/api/networking/v1" +) + +// IngressStateBuilder Build Ingress +type IngressStateBuilder interface { + // WithIngress sets the Ingress + WithIngress(*networkingv1.Ingress) IngressStateBuilder + // WithComponent sets the component name + WithComponent(componentName string) IngressStateBuilder + Build() []IngressRule +} + +type ingressStateBuilder struct { + ingress *networkingv1.Ingress + componentName string +} + +// NewIngressBuilder Constructor for ingressBuilder +func NewIngressBuilder() IngressStateBuilder { + return &ingressStateBuilder{} +} + +func (b *ingressStateBuilder) WithIngress(v *networkingv1.Ingress) IngressStateBuilder { + b.ingress = v + return b +} + +func (b *ingressStateBuilder) WithComponent(componentName string) IngressStateBuilder { + b.componentName = componentName + return b +} + +func (b *ingressStateBuilder) Build() []IngressRule { + if b.ingress == nil { + return nil + } + + var ingressRiles []IngressRule + for _, rule := range b.ingress.Spec.Rules { + if rule.HTTP != nil { + for _, path := range rule.HTTP.Paths { + ingressRule := IngressRule{Host: rule.Host, Path: path.Path} + + if path.Backend.Service != nil { + if len(b.componentName) == 0 { + ingressRule.Service = path.Backend.Service.Name // provide service name only component name is not set + } + ingressRule.Port = path.Backend.Service.Port.Number + } + ingressRiles = append(ingressRiles, ingressRule) + } + } + } + + return ingressRiles +} diff --git a/api/events/models/object_state_builder.go b/api/events/models/object_state_builder.go index f19ac42b..c1fee399 100644 --- a/api/events/models/object_state_builder.go +++ b/api/events/models/object_state_builder.go @@ -2,12 +2,17 @@ package models // ObjectStateBuilder Build ObjectState DTOs type ObjectStateBuilder interface { + // WithPodState sets the PodState WithPodState(*PodState) ObjectStateBuilder + // WithIngress sets the IngressRules + WithIngress(rules []IngressRule) ObjectStateBuilder + // Build the ObjectState Build() *ObjectState } type objectStateBuilder struct { - podState *PodState + podState *PodState + ingressRules []IngressRule } // NewObjectStateBuilder Constructor for objectStateBuilder @@ -20,8 +25,14 @@ func (b *objectStateBuilder) WithPodState(v *PodState) ObjectStateBuilder { return b } +func (b *objectStateBuilder) WithIngress(rules []IngressRule) ObjectStateBuilder { + b.ingressRules = rules + return b +} + func (b *objectStateBuilder) Build() *ObjectState { return &ObjectState{ - Pod: b.podState, + Pod: b.podState, + IngressRules: b.ingressRules, } } diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index a3bebd9e..be17b4fb 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -6889,6 +6889,34 @@ }, "x-go-package": "github.com/equinor/radix-api/api/deployments/models" }, + "IngressRule": { + "description": "IngressRule specs", + "type": "object", + "properties": { + "host": { + "description": "The host name of the ingress", + "type": "string", + "x-go-name": "Host" + }, + "path": { + "description": "The path of the ingress", + "type": "string", + "x-go-name": "Path" + }, + "port": { + "description": "The port of the ingress", + "type": "integer", + "format": "int32", + "x-go-name": "Port" + }, + "service": { + "description": "The service name of the ingress", + "type": "string", + "x-go-name": "Service" + } + }, + "x-go-package": "github.com/equinor/radix-api/api/events/models" + }, "Job": { "description": "Job holds general information about job", "type": "object", @@ -7202,6 +7230,14 @@ "description": "ObjectState holds information about the state of objects involved in an event", "type": "object", "properties": { + "ingressRules": { + "description": "Details about the ingress rules for an ingress related event", + "type": "array", + "items": { + "$ref": "#/definitions/IngressRule" + }, + "x-go-name": "IngressRules" + }, "pod": { "$ref": "#/definitions/PodState" } From 695e3c4e9c9a67ebc31701ffc16b2edd759d7d8d Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 25 Oct 2024 15:27:42 +0200 Subject: [PATCH 06/18] Added unit-tests --- api/events/event_handler_test.go | 112 ++++++++++++++++++++++--------- 1 file changed, 82 insertions(+), 30 deletions(-) diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index 777a81ad..b93446b2 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -5,15 +5,23 @@ import ( "testing" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" + radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/assert" - v1 "k8s.io/api/core/v1" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" kubefake "k8s.io/client-go/kubernetes/fake" ) +const ( + uid1 = "d1bf3ab3-0693-4291-a559-96a12ace9f33" + uid2 = "2dcc9cf7-086d-49a0-abe1-ea3610594eb2" + uid3 = "0c43a075-d174-479e-96b1-70183d67464c" + uid4 = "805a5ffe-a2ca-4d1e-a178-7988ab1f03ca" +) + func setupTest() (*kubefake.Clientset, *radixfake.Clientset) { kubeClient := kubefake.NewSimpleClientset() radixClient := radixfake.NewSimpleClientset() @@ -28,17 +36,17 @@ func Test_EventHandler_Init(t *testing.T) { } func Test_EventHandler_GetEventsForRadixApplication(t *testing.T) { - appName, envName := "app", "env" - appNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) + appName, envName := "app1", "env1" + envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) kubeClient, radixClient := setupTest() - createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Normal", "pod1", "Pod") - createKubernetesEvent(t, kubeClient, appNamespace, "ev2", "Normal", "pod2", "Pod") - createKubernetesEvent(t, kubeClient, "app2-env", "ev3", "Normal", "pod3", "Pod") + createRadixAppWithEnvironment(t, radixClient, appName, envName) + createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) + createKubernetesEvent(t, kubeClient, envNamespace, "ev2", "Normal", "pod2", "Pod", uid2) + createKubernetesEvent(t, kubeClient, "app2-env", "ev3", "Normal", "pod3", "Pod", uid3) - ra := operatorutils.NewRadixApplicationBuilder().WithAppName(appName).BuildRA() eventHandler := Init(kubeClient, radixClient) - events, err := eventHandler.GetEnvironmentEvents(context.Background(), ra.Name, envName) + events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) assert.Nil(t, err) assert.Len(t, events, 2) assert.ElementsMatch( @@ -48,29 +56,57 @@ func Test_EventHandler_GetEventsForRadixApplication(t *testing.T) { ) } +func Test_EventHandler_NoEventsWhenThereIsNoRadixApplication(t *testing.T) { + appName, envName := "app1", "env1" + envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) + kubeClient, radixClient := setupTest() + + createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) + + eventHandler := Init(kubeClient, radixClient) + events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) + assert.NotNil(t, err) + assert.Len(t, events, 0) +} + +func Test_EventHandler_NoEventsWhenThereIsNoRadixEnvironment(t *testing.T) { + appName, envName := "app1", "env1" + envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) + kubeClient, radixClient := setupTest() + + createRadixApp(t, radixClient, appName, envName) + createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) + + eventHandler := Init(kubeClient, radixClient) + events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) + assert.NotNil(t, err) + assert.Len(t, events, 0) +} + func Test_EventHandler_GetEvents_PodState(t *testing.T) { - appName, envName := "app", "env" - appNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) - ra := operatorutils.NewRadixApplicationBuilder().WithAppName(appName).BuildRA() + appName, envName := "app1", "env1" + envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) t.Run("ObjectState is nil for normal event type", func(t *testing.T) { kubeClient, radixClient := setupTest() - createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Normal", "pod1", "Pod") - _, err := createKubernetesPod(kubeClient, "pod1", appNamespace, true, true, 0) + createRadixAppWithEnvironment(t, radixClient, appName, envName) + _, err := createKubernetesPod(kubeClient, "pod1", appName, envName, true, true, 0, uid1) + createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) require.NoError(t, err) eventHandler := Init(kubeClient, radixClient) - events, _ := eventHandler.GetEnvironmentEvents(context.Background(), ra.Name, envName) + events, _ := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) assert.Len(t, events, 1) assert.Nil(t, events[0].InvolvedObjectState) }) t.Run("ObjectState has Pod state for warning event type", func(t *testing.T) { kubeClient, radixClient := setupTest() - createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Warning", "pod1", "Pod") - _, err := createKubernetesPod(kubeClient, "pod1", appNamespace, true, false, 0) + createRadixAppWithEnvironment(t, radixClient, appName, envName) + _, err := createKubernetesPod(kubeClient, "pod1", appName, envName, true, false, 0, uid1) + createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Warning", "pod1", "Pod", uid1) require.NoError(t, err) eventHandler := Init(kubeClient, radixClient) - events, _ := eventHandler.GetEnvironmentEvents(context.Background(), ra.Name, envName) + events, _ := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) assert.Len(t, events, 1) assert.NotNil(t, events[0].InvolvedObjectState) assert.NotNil(t, events[0].InvolvedObjectState.Pod) @@ -78,41 +114,57 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { t.Run("ObjectState is nil for warning event type when pod not exist", func(t *testing.T) { kubeClient, radixClient := setupTest() - createKubernetesEvent(t, kubeClient, appNamespace, "ev1", "Normal", "pod1", "Pod") + createRadixAppWithEnvironment(t, radixClient, appName, envName) + createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) eventHandler := Init(kubeClient, radixClient) - events, _ := eventHandler.GetEnvironmentEvents(context.Background(), ra.Name, envName) + events, _ := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) assert.Len(t, events, 1) assert.Nil(t, events[0].InvolvedObjectState) }) } -func createKubernetesEvent(t *testing.T, client *kubefake.Clientset, namespace, - name, eventType, involvedObjectName, involvedObjectKind string) { - _, err := client.CoreV1().Events(namespace).CreateWithEventNamespace(&v1.Event{ +func createKubernetesEvent(t *testing.T, client *kubefake.Clientset, namespace, name, eventType, involvedObjectName, involvedObjectKind, uid string) { + _, err := client.CoreV1().Events(namespace).CreateWithEventNamespace(&corev1.Event{ ObjectMeta: metav1.ObjectMeta{ Name: name, }, - InvolvedObject: v1.ObjectReference{ + InvolvedObject: corev1.ObjectReference{ Kind: involvedObjectKind, Name: involvedObjectName, Namespace: namespace, + UID: types.UID(uid), }, Type: eventType, }) require.NoError(t, err) } -func createKubernetesPod(client *kubefake.Clientset, name, namespace string, started, ready bool, restartCount int32) (*v1.Pod, error) { +func createKubernetesPod(client *kubefake.Clientset, name, appName, envName string, started, ready bool, restartCount int32, uid string) (*corev1.Pod, error) { + namespace := operatorutils.GetEnvironmentNamespace(appName, envName) return client.CoreV1().Pods(namespace).Create(context.Background(), - &v1.Pod{ + &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: name, + UID: types.UID(uid), + Labels: radixlabels.ForApplicationName(appName), }, - Status: v1.PodStatus{ - ContainerStatuses: []v1.ContainerStatus{ + Status: corev1.PodStatus{ + ContainerStatuses: []corev1.ContainerStatus{ {Started: &started, Ready: ready, RestartCount: restartCount}, }, }, }, metav1.CreateOptions{}) } + +func createRadixAppWithEnvironment(t *testing.T, radixClient *radixfake.Clientset, appName string, envName string) { + createRadixApp(t, radixClient, appName, envName) + re := operatorutils.NewEnvironmentBuilder().WithAppName(appName).WithEnvironmentName(envName).BuildRE() + _, err := radixClient.RadixV1().RadixEnvironments().Create(context.Background(), re, metav1.CreateOptions{}) + require.NoError(t, err) +} + +func createRadixApp(t *testing.T, radixClient *radixfake.Clientset, appName string, envName string) { + _, err := radixClient.RadixV1().RadixApplications(operatorutils.GetAppNamespace(appName)).Create(context.Background(), operatorutils.NewRadixApplicationBuilder().WithAppName(appName).WithEnvironment(envName, "").BuildRA(), metav1.CreateOptions{}) + require.NoError(t, err) +} From 99c6d327c59067ce1d3112e62aab2c9abe313da1 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 25 Oct 2024 16:29:06 +0200 Subject: [PATCH 07/18] Added unit-tests --- api/events/event_handler.go | 12 ++-- api/events/event_handler_test.go | 110 ++++++++++++++++++++++++++----- 2 files changed, 102 insertions(+), 20 deletions(-) diff --git a/api/events/event_handler.go b/api/events/event_handler.go index 95f8e91e..50f179b6 100644 --- a/api/events/event_handler.go +++ b/api/events/event_handler.go @@ -21,10 +21,12 @@ import ( ) const ( - k8sKindDeployment = "Deployment" - k8sKindReplicaSet = "ReplicaSet" - k8sKindIngress = "Ingress" - k8sKindPod = "Pod" + k8sKindDeployment = "Deployment" + k8sKindReplicaSet = "ReplicaSet" + k8sKindIngress = "Ingress" + k8sKindPod = "Pod" + k8sEventTypeNormal = "Normal" + k8sEventTypeWarning = "Warning" ) // EventHandler defines methods for interacting with Kubernetes events @@ -173,7 +175,7 @@ func (eh *eventHandler) getEnvironmentComponentsIngressMap(ctx context.Context, func (eh *eventHandler) buildEvent(ev corev1.Event, componentName string, podMap map[k8sTypes.UID]*corev1.Pod, ingressMap map[string]*networkingv1.Ingress) *eventModels.Event { builder := eventModels.NewEventBuilder().WithKubernetesEvent(ev) - if ev.Type != "Normal" || ev.InvolvedObject.Kind == k8sKindIngress { + if ev.Type != k8sEventTypeNormal || ev.InvolvedObject.Kind == k8sKindIngress { if objectState := getObjectState(ev, podMap, ingressMap, componentName); objectState != nil { builder.WithInvolvedObjectState(objectState) } diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index b93446b2..0d8aaa81 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -4,6 +4,7 @@ import ( "context" "testing" + eventModels "github.com/equinor/radix-api/api/events/models" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" @@ -16,10 +17,21 @@ import ( ) const ( - uid1 = "d1bf3ab3-0693-4291-a559-96a12ace9f33" - uid2 = "2dcc9cf7-086d-49a0-abe1-ea3610594eb2" - uid3 = "0c43a075-d174-479e-96b1-70183d67464c" - uid4 = "805a5ffe-a2ca-4d1e-a178-7988ab1f03ca" + ev1 = "ev1" + ev2 = "ev2" + ev3 = "ev3" + uid1 = "d1bf3ab3-0693-4291-a559-96a12ace9f33" + uid2 = "2dcc9cf7-086d-49a0-abe1-ea3610594eb2" + uid3 = "0c43a075-d174-479e-96b1-70183d67464c" + deploy1 = "server1" + deploy2 = "server2" + deploy3 = "server3" + replicaSetServer1 = "server2-795977897d" + replicaSetServer2 = "server2-795977897d" + replicaSetServer3 = "server3-5c97b4c698" + podServer1 = "server1-5bf67cf976-9v333" + podServer2 = "server2-795977897d-m2sw8" + podServer3 = "server3-5c97b4c698-6x4cg" ) func setupTest() (*kubefake.Clientset, *radixfake.Clientset) { @@ -41,9 +53,9 @@ func Test_EventHandler_GetEventsForRadixApplication(t *testing.T) { kubeClient, radixClient := setupTest() createRadixAppWithEnvironment(t, radixClient, appName, envName) - createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) - createKubernetesEvent(t, kubeClient, envNamespace, "ev2", "Normal", "pod2", "Pod", uid2) - createKubernetesEvent(t, kubeClient, "app2-env", "ev3", "Normal", "pod3", "Pod", uid3) + createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) + createKubernetesEvent(t, kubeClient, envNamespace, ev2, k8sEventTypeNormal, podServer2, k8sKindPod, uid2) + createKubernetesEvent(t, kubeClient, "app2-env", ev3, k8sEventTypeNormal, podServer3, k8sKindPod, uid3) eventHandler := Init(kubeClient, radixClient) events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) @@ -51,7 +63,7 @@ func Test_EventHandler_GetEventsForRadixApplication(t *testing.T) { assert.Len(t, events, 2) assert.ElementsMatch( t, - []string{"pod1", "pod2"}, + []string{podServer1, podServer2}, []string{events[0].InvolvedObjectName, events[1].InvolvedObjectName}, ) } @@ -61,7 +73,7 @@ func Test_EventHandler_NoEventsWhenThereIsNoRadixApplication(t *testing.T) { envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) kubeClient, radixClient := setupTest() - createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) + createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) eventHandler := Init(kubeClient, radixClient) events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) @@ -75,7 +87,7 @@ func Test_EventHandler_NoEventsWhenThereIsNoRadixEnvironment(t *testing.T) { kubeClient, radixClient := setupTest() createRadixApp(t, radixClient, appName, envName) - createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) + createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) eventHandler := Init(kubeClient, radixClient) events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) @@ -90,8 +102,8 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { t.Run("ObjectState is nil for normal event type", func(t *testing.T) { kubeClient, radixClient := setupTest() createRadixAppWithEnvironment(t, radixClient, appName, envName) - _, err := createKubernetesPod(kubeClient, "pod1", appName, envName, true, true, 0, uid1) - createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) + _, err := createKubernetesPod(kubeClient, podServer1, appName, envName, true, true, 0, uid1) + createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) require.NoError(t, err) eventHandler := Init(kubeClient, radixClient) events, _ := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) @@ -102,8 +114,8 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { t.Run("ObjectState has Pod state for warning event type", func(t *testing.T) { kubeClient, radixClient := setupTest() createRadixAppWithEnvironment(t, radixClient, appName, envName) - _, err := createKubernetesPod(kubeClient, "pod1", appName, envName, true, false, 0, uid1) - createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Warning", "pod1", "Pod", uid1) + _, err := createKubernetesPod(kubeClient, podServer1, appName, envName, true, false, 0, uid1) + createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeWarning, podServer1, k8sKindPod, uid1) require.NoError(t, err) eventHandler := Init(kubeClient, radixClient) events, _ := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) @@ -115,7 +127,7 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { t.Run("ObjectState is nil for warning event type when pod not exist", func(t *testing.T) { kubeClient, radixClient := setupTest() createRadixAppWithEnvironment(t, radixClient, appName, envName) - createKubernetesEvent(t, kubeClient, envNamespace, "ev1", "Normal", "pod1", "Pod", uid1) + createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) eventHandler := Init(kubeClient, radixClient) events, _ := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) assert.Len(t, events, 1) @@ -123,6 +135,74 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { }) } +type scenario struct { + name string + existingEventProps []eventProps + expectedEvents []eventModels.Event +} + +type eventProps struct { + name string + namespace string + eventType string + objectName string + objectUid string + objectKind string +} + +func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { + appName, envName := "app1", "env1" + envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) + + scenarios := []scenario{ + {name: "NoEvents"}, + { + name: "Pod events", + existingEventProps: []eventProps{ + {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: podServer1, objectKind: k8sKindPod, objectUid: uid1}, + {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: podServer2, objectKind: k8sKindPod, objectUid: uid2}, + {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: podServer3, objectKind: k8sKindPod, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: podServer1, InvolvedObjectKind: k8sKindPod, InvolvedObjectNamespace: envNamespace}, + {InvolvedObjectName: podServer2, InvolvedObjectKind: k8sKindPod, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Deploy events", + existingEventProps: []eventProps{ + {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: deploy1, objectKind: k8sKindDeployment, objectUid: uid1}, + {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: deploy2, objectKind: k8sKindDeployment, objectUid: uid2}, + {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: deploy3, objectKind: k8sKindDeployment, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: deploy1, InvolvedObjectKind: k8sKindDeployment, InvolvedObjectNamespace: envNamespace}, + {InvolvedObjectName: deploy2, InvolvedObjectKind: k8sKindDeployment, InvolvedObjectNamespace: envNamespace}, + }, + }, + } + for _, ts := range scenarios { + t.Run(ts.name, func(t *testing.T) { + kubeClient, radixClient := setupTest() + createRadixAppWithEnvironment(t, radixClient, appName, envName) + for _, evProps := range ts.existingEventProps { + createKubernetesEvent(t, kubeClient, evProps.namespace, evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) + } + + eventHandler := Init(kubeClient, radixClient) + events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) + assert.Nil(t, err) + if assert.Len(t, events, len(ts.expectedEvents)) { + for i := 0; i < len(ts.expectedEvents); i++ { + assert.Equal(t, ts.expectedEvents[i].InvolvedObjectName, events[i].InvolvedObjectName) + assert.Equal(t, ts.expectedEvents[i].InvolvedObjectKind, events[i].InvolvedObjectKind) + assert.Equal(t, ts.expectedEvents[i].InvolvedObjectNamespace, events[i].InvolvedObjectNamespace) + } + } + }) + } +} + func createKubernetesEvent(t *testing.T, client *kubefake.Clientset, namespace, name, eventType, involvedObjectName, involvedObjectKind, uid string) { _, err := client.CoreV1().Events(namespace).CreateWithEventNamespace(&corev1.Event{ ObjectMeta: metav1.ObjectMeta{ From a08b82d4a4686fd7d22f6ed4dd2fd66d622bb138 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Fri, 25 Oct 2024 17:00:21 +0200 Subject: [PATCH 08/18] Added unit-tests --- api/events/event_handler_test.go | 100 ++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 3 deletions(-) diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index 0d8aaa81..f5fc04a3 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -5,12 +5,14 @@ import ( "testing" eventModels "github.com/equinor/radix-api/api/events/models" + "github.com/equinor/radix-common/utils/pointers" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" + networkingv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" kubefake "k8s.io/client-go/kubernetes/fake" @@ -32,6 +34,15 @@ const ( podServer1 = "server1-5bf67cf976-9v333" podServer2 = "server2-795977897d-m2sw8" podServer3 = "server3-5c97b4c698-6x4cg" + ingressName1 = "ingress1" + ingressHost1 = "ingress1.example.com" + ingressName2 = "ingress2" + ingressHost2 = "ingress2.example.com" + ingressName3 = "ingress3" + ingressHost3 = "ingress3.example.com" + port8080 = int32(8080) + port8090 = int32(8090) + port9090 = int32(9090) ) func setupTest() (*kubefake.Clientset, *radixfake.Clientset) { @@ -136,9 +147,10 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { } type scenario struct { - name string - existingEventProps []eventProps - expectedEvents []eventModels.Event + name string + existingEventProps []eventProps + expectedEvents []eventModels.Event + existingIngressRuleProps []ingressRuleProps } type eventProps struct { @@ -150,6 +162,15 @@ type eventProps struct { objectKind string } +type ingressRuleProps struct { + name string + host string + service string + port int32 + appName string + envName string +} + func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { appName, envName := "app1", "env1" envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) @@ -180,6 +201,47 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { {InvolvedObjectName: deploy2, InvolvedObjectKind: k8sKindDeployment, InvolvedObjectNamespace: envNamespace}, }, }, + { + name: "ReplicaSet events", + existingEventProps: []eventProps{ + {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: replicaSetServer1, objectKind: k8sKindReplicaSet, objectUid: uid1}, + {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: replicaSetServer2, objectKind: k8sKindReplicaSet, objectUid: uid2}, + {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: replicaSetServer3, objectKind: k8sKindReplicaSet, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: replicaSetServer1, InvolvedObjectKind: k8sKindReplicaSet, InvolvedObjectNamespace: envNamespace}, + {InvolvedObjectName: replicaSetServer2, InvolvedObjectKind: k8sKindReplicaSet, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Ingress events", + existingEventProps: []eventProps{ + {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, + {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: ingressName1, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, + {InvolvedObjectName: ingressName2, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Ingress events with rules", + existingEventProps: []eventProps{ + {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, + {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + }, + existingIngressRuleProps: []ingressRuleProps{ + {name: ingressName1, appName: appName, envName: envName, host: ingressHost1, service: deploy1, port: port8080}, + {name: ingressName2, appName: appName, envName: envName, host: ingressHost2, service: deploy2, port: port8090}, + {name: ingressName3, appName: "app2", envName: envName, host: ingressHost3, service: deploy3, port: port9090}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: ingressName1, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, + {InvolvedObjectName: ingressName2, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, + }, + }, } for _, ts := range scenarios { t.Run(ts.name, func(t *testing.T) { @@ -188,6 +250,9 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { for _, evProps := range ts.existingEventProps { createKubernetesEvent(t, kubeClient, evProps.namespace, evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) } + for _, ingressRuleProp := range ts.existingIngressRuleProps { + createIngressRule(t, kubeClient, ingressRuleProp) + } eventHandler := Init(kubeClient, radixClient) events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) @@ -203,6 +268,35 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { } } +func createIngressRule(t *testing.T, kubeClient *kubefake.Clientset, props ingressRuleProps) { + _, err := kubeClient.NetworkingV1().Ingresses(operatorutils.GetEnvironmentNamespace(props.appName, props.envName)).Create(context.Background(), &networkingv1.Ingress{ + ObjectMeta: metav1.ObjectMeta{Name: props.name, + Labels: radixlabels.ForApplicationName(props.appName)}, + Spec: networkingv1.IngressSpec{ + Rules: []networkingv1.IngressRule{ + {Host: props.host, + IngressRuleValue: networkingv1.IngressRuleValue{ + HTTP: &networkingv1.HTTPIngressRuleValue{ + Paths: []networkingv1.HTTPIngressPath{ + { + Path: "/", + PathType: pointers.Ptr(networkingv1.PathTypeImplementationSpecific), + Backend: networkingv1.IngressBackend{ + Service: &networkingv1.IngressServiceBackend{ + Name: props.service, + Port: networkingv1.ServiceBackendPort{Number: props.port}, + }, + }, + }, + }, + }, + }}, + }, + }, + }, metav1.CreateOptions{}) + require.NoError(t, err) +} + func createKubernetesEvent(t *testing.T, client *kubefake.Clientset, namespace, name, eventType, involvedObjectName, involvedObjectKind, uid string) { _, err := client.CoreV1().Events(namespace).CreateWithEventNamespace(&corev1.Event{ ObjectMeta: metav1.ObjectMeta{ From c106335cdfd955c086c82b992263355dc8a98924 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 10:27:19 +0100 Subject: [PATCH 09/18] Fixed unit-tests --- .../environment_controller_test.go | 38 -------- api/events/event_handler_test.go | 97 ++++++++++--------- api/events/mock/event_handler_mock.go | 43 ++++++-- 3 files changed, 89 insertions(+), 89 deletions(-) diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 87ff7c8a..d415717e 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -8,12 +8,9 @@ import ( "testing" "time" - certclient "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned" certclientfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" deploymentModels "github.com/equinor/radix-api/api/deployments/models" environmentModels "github.com/equinor/radix-api/api/environments/models" - event "github.com/equinor/radix-api/api/events" - eventMock "github.com/equinor/radix-api/api/events/mock" eventModels "github.com/equinor/radix-api/api/events/models" "github.com/equinor/radix-api/api/secrets" secretModels "github.com/equinor/radix-api/api/secrets/models" @@ -21,7 +18,6 @@ import ( controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/utils" authnmock "github.com/equinor/radix-api/api/utils/token/mock" - "github.com/equinor/radix-api/models" radixhttp "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" "github.com/equinor/radix-common/utils/numbers" @@ -47,7 +43,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes" kubefake "k8s.io/client-go/kubernetes/fake" testing2 "k8s.io/client-go/testing" secretsstorevclient "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned" @@ -987,27 +982,6 @@ func TestCreateSecret(t *testing.T) { assert.Equal(t, http.StatusOK, response.Code) } -func Test_GetEnvironmentEvents_Handler(t *testing.T) { - commonTestUtils, _, _, kubeclient, radixclient, kedaClient, _, secretproviderclient, certClient := setupTest(t, nil) - ctrl := gomock.NewController(t) - defer ctrl.Finish() - eventHandler := eventMock.NewMockEventHandler(ctrl) - handler := initHandler(kubeclient, radixclient, kedaClient, secretproviderclient, certClient, WithEventHandler(eventHandler)) - raBuilder := operatorutils.ARadixApplication().WithAppName(anyAppName).WithEnvironment(anyEnvironment, "master") - - _, err := commonTestUtils.ApplyApplication(raBuilder) - require.NoError(t, err) - nsFunc := event.RadixEnvironmentNamespace(raBuilder.BuildRA(), anyEnvironment) - eventHandler.EXPECT(). - GetEvents(context.Background(), controllertest.EqualsNamespaceFunc(nsFunc)). - Return([]*eventModels.Event{{}, {}}, nil). - Times(1) - - events, err := handler.GetEnvironmentEvents(context.Background(), anyAppName, anyEnvironment) - assert.Nil(t, err) - assert.Len(t, events, 2) -} - func TestRestartAuxiliaryResource(t *testing.T) { auxType := "oauth" called := 0 @@ -2702,18 +2676,6 @@ func Test_DeleteBatch(t *testing.T) { } } -func initHandler(client kubernetes.Interface, - radixclient radixclient.Interface, - kedaClient kedav2.Interface, - secretproviderclient secretsstorevclient.Interface, - certClient certclient.Interface, - handlerConfig ...EnvironmentHandlerOptions) EnvironmentHandler { - accounts := models.NewAccounts(client, radixclient, kedaClient, secretproviderclient, nil, certClient, client, radixclient, kedaClient, secretproviderclient, nil, certClient) - options := []EnvironmentHandlerOptions{WithAccounts(accounts)} - options = append(options, handlerConfig...) - return Init(options...) -} - type ComponentCreatorStruct struct { name string number int diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index f5fc04a3..9a83a569 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -19,6 +19,8 @@ import ( ) const ( + appName = "app1" + envName = "env1" ev1 = "ev1" ev2 = "ev2" ev3 = "ev3" @@ -45,6 +47,31 @@ const ( port9090 = int32(9090) ) +type scenario struct { + name string + existingEventProps []eventProps + expectedEvents []eventModels.Event + existingIngressRuleProps []ingressRuleProps +} + +type eventProps struct { + name string + namespace string + eventType string + objectName string + objectUid string + objectKind string +} + +type ingressRuleProps struct { + name string + host string + service string + port int32 + appName string + envName string +} + func setupTest() (*kubefake.Clientset, *radixfake.Clientset) { kubeClient := kubefake.NewSimpleClientset() radixClient := radixfake.NewSimpleClientset() @@ -146,33 +173,7 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { }) } -type scenario struct { - name string - existingEventProps []eventProps - expectedEvents []eventModels.Event - existingIngressRuleProps []ingressRuleProps -} - -type eventProps struct { - name string - namespace string - eventType string - objectName string - objectUid string - objectKind string -} - -type ingressRuleProps struct { - name string - host string - service string - port int32 - appName string - envName string -} - func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { - appName, envName := "app1", "env1" envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) scenarios := []scenario{ @@ -245,29 +246,37 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { } for _, ts := range scenarios { t.Run(ts.name, func(t *testing.T) { - kubeClient, radixClient := setupTest() - createRadixAppWithEnvironment(t, radixClient, appName, envName) - for _, evProps := range ts.existingEventProps { - createKubernetesEvent(t, kubeClient, evProps.namespace, evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) - } - for _, ingressRuleProp := range ts.existingIngressRuleProps { - createIngressRule(t, kubeClient, ingressRuleProp) - } - - eventHandler := Init(kubeClient, radixClient) - events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) + eventHandler := setupTestEnvForHandler(t, appName, envName, ts) + actualEvents, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) assert.Nil(t, err) - if assert.Len(t, events, len(ts.expectedEvents)) { - for i := 0; i < len(ts.expectedEvents); i++ { - assert.Equal(t, ts.expectedEvents[i].InvolvedObjectName, events[i].InvolvedObjectName) - assert.Equal(t, ts.expectedEvents[i].InvolvedObjectKind, events[i].InvolvedObjectKind) - assert.Equal(t, ts.expectedEvents[i].InvolvedObjectNamespace, events[i].InvolvedObjectNamespace) - } - } + assertEvents(t, ts.expectedEvents, actualEvents) }) } } +func assertEvents(t *testing.T, expectedEvents []eventModels.Event, actualEvents []*eventModels.Event) { + if assert.Len(t, actualEvents, len(expectedEvents)) { + for i := 0; i < len(expectedEvents); i++ { + assert.Equal(t, expectedEvents[i].InvolvedObjectName, actualEvents[i].InvolvedObjectName) + assert.Equal(t, expectedEvents[i].InvolvedObjectKind, actualEvents[i].InvolvedObjectKind) + assert.Equal(t, expectedEvents[i].InvolvedObjectNamespace, actualEvents[i].InvolvedObjectNamespace) + } + } +} + +func setupTestEnvForHandler(t *testing.T, appName string, envName string, ts scenario) EventHandler { + kubeClient, radixClient := setupTest() + createRadixAppWithEnvironment(t, radixClient, appName, envName) + for _, evProps := range ts.existingEventProps { + createKubernetesEvent(t, kubeClient, evProps.namespace, evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) + } + for _, ingressRuleProp := range ts.existingIngressRuleProps { + createIngressRule(t, kubeClient, ingressRuleProp) + } + eventHandler := Init(kubeClient, radixClient) + return eventHandler +} + func createIngressRule(t *testing.T, kubeClient *kubefake.Clientset, props ingressRuleProps) { _, err := kubeClient.NetworkingV1().Ingresses(operatorutils.GetEnvironmentNamespace(props.appName, props.envName)).Create(context.Background(), &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{Name: props.name, diff --git a/api/events/mock/event_handler_mock.go b/api/events/mock/event_handler_mock.go index f3a2222f..5f635a27 100644 --- a/api/events/mock/event_handler_mock.go +++ b/api/events/mock/event_handler_mock.go @@ -8,7 +8,6 @@ import ( context "context" reflect "reflect" - events "github.com/equinor/radix-api/api/events" models "github.com/equinor/radix-api/api/events/models" gomock "github.com/golang/mock/gomock" ) @@ -36,17 +35,47 @@ func (m *MockEventHandler) EXPECT() *MockEventHandlerMockRecorder { return m.recorder } -// GetEvents mocks base method. -func (m *MockEventHandler) GetEvents(ctx context.Context, namespaceFunc events.NamespaceFunc) ([]*models.Event, error) { +// GetComponentEvents mocks base method. +func (m *MockEventHandler) GetComponentEvents(ctx context.Context, appName, envName, componentName string) ([]*models.Event, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetEvents", ctx, namespaceFunc) + ret := m.ctrl.Call(m, "GetComponentEvents", ctx, appName, envName, componentName) ret0, _ := ret[0].([]*models.Event) ret1, _ := ret[1].(error) return ret0, ret1 } -// GetEvents indicates an expected call of GetEvents. -func (mr *MockEventHandlerMockRecorder) GetEvents(ctx, namespaceFunc interface{}) *gomock.Call { +// GetComponentEvents indicates an expected call of GetComponentEvents. +func (mr *MockEventHandlerMockRecorder) GetComponentEvents(ctx, appName, envName, componentName interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEvents", reflect.TypeOf((*MockEventHandler)(nil).GetEvents), ctx, namespaceFunc) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetComponentEvents", reflect.TypeOf((*MockEventHandler)(nil).GetComponentEvents), ctx, appName, envName, componentName) +} + +// GetEnvironmentEvents mocks base method. +func (m *MockEventHandler) GetEnvironmentEvents(ctx context.Context, appName, envName string) ([]*models.Event, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEnvironmentEvents", ctx, appName, envName) + ret0, _ := ret[0].([]*models.Event) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEnvironmentEvents indicates an expected call of GetEnvironmentEvents. +func (mr *MockEventHandlerMockRecorder) GetEnvironmentEvents(ctx, appName, envName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEnvironmentEvents", reflect.TypeOf((*MockEventHandler)(nil).GetEnvironmentEvents), ctx, appName, envName) +} + +// GetPodEvents mocks base method. +func (m *MockEventHandler) GetPodEvents(ctx context.Context, appName, envName, componentName, podName string) ([]*models.Event, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPodEvents", ctx, appName, envName, componentName, podName) + ret0, _ := ret[0].([]*models.Event) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPodEvents indicates an expected call of GetPodEvents. +func (mr *MockEventHandlerMockRecorder) GetPodEvents(ctx, appName, envName, componentName, podName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPodEvents", reflect.TypeOf((*MockEventHandler)(nil).GetPodEvents), ctx, appName, envName, componentName, podName) } From 816d60d18b0aa358c3e256a774208adeba455562 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 12:34:36 +0100 Subject: [PATCH 10/18] Fixed unit-tests --- api/events/event_handler.go | 10 +- api/events/event_handler_test.go | 280 ++++++++++++++++++++++--------- 2 files changed, 206 insertions(+), 84 deletions(-) diff --git a/api/events/event_handler.go b/api/events/event_handler.go index 50f179b6..57efed54 100644 --- a/api/events/event_handler.go +++ b/api/events/event_handler.go @@ -5,7 +5,6 @@ import ( "fmt" "regexp" - environmentModels "github.com/equinor/radix-api/api/environments/models" eventModels "github.com/equinor/radix-api/api/events/models" "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-common/utils/slice" @@ -54,6 +53,9 @@ func Init(kubeClient kubernetes.Interface, radixClient radixclient.Interface) Ev func (eh *eventHandler) GetEnvironmentEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) if err != nil { + if errors.IsNotFound(err) { + return nil, nil + } return nil, err } environmentEvents, err := eh.getEvents(ctx, radixApplication.Name, envName, "", "") @@ -95,9 +97,6 @@ func (eh *eventHandler) getRadixApplicationAndValidateEnvironment(ctx context.Co _, err = kubequery.GetRadixEnvironment(ctx, eh.radixClient, appName, envName) if err != nil { - if errors.IsNotFound(err) { - return nil, environmentModels.NonExistingEnvironment(err, appName, envName) - } return nil, err } return radixApplication, err @@ -106,6 +105,9 @@ func (eh *eventHandler) getRadixApplicationAndValidateEnvironment(ctx context.Co func (eh *eventHandler) existsRadixDeployComponent(ctx context.Context, appName, envName, componentName string) (bool, error) { _, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) if err != nil { + if errors.IsNotFound(err) { + return false, nil + } return false, err } radixDeployments, err := kubequery.GetRadixDeploymentsForEnvironments(ctx, eh.radixClient, appName, []string{envName}, 1) diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index 9a83a569..20a396cd 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -2,10 +2,13 @@ package events import ( "context" + "fmt" "testing" + "time" eventModels "github.com/equinor/radix-api/api/events/models" "github.com/equinor/radix-common/utils/pointers" + "github.com/equinor/radix-common/utils/slice" operatorutils "github.com/equinor/radix-operator/pkg/apis/utils" radixlabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake" @@ -19,23 +22,27 @@ import ( ) const ( - appName = "app1" - envName = "env1" + appName1 = "app1" + appName2 = "app2" + envName1 = "env1" ev1 = "ev1" ev2 = "ev2" ev3 = "ev3" uid1 = "d1bf3ab3-0693-4291-a559-96a12ace9f33" uid2 = "2dcc9cf7-086d-49a0-abe1-ea3610594eb2" uid3 = "0c43a075-d174-479e-96b1-70183d67464c" - deploy1 = "server1" - deploy2 = "server2" - deploy3 = "server3" - replicaSetServer1 = "server2-795977897d" - replicaSetServer2 = "server2-795977897d" - replicaSetServer3 = "server3-5c97b4c698" - podServer1 = "server1-5bf67cf976-9v333" - podServer2 = "server2-795977897d-m2sw8" - podServer3 = "server3-5c97b4c698-6x4cg" + component1 = "server1" + component2 = "server2" + component3 = "server3" + deploy1 = component1 + deploy2 = component2 + deploy3 = component3 + replicaSetServer1 = deploy1 + "-4bf67cf976" + replicaSetServer2 = deploy2 + "-795977897d" + replicaSetServer3 = deploy3 + "-5c97b4c698" + podServer1 = replicaSetServer1 + "-9v333" + podServer2 = replicaSetServer2 + "-m2sw8" + podServer3 = replicaSetServer3 + "-6x4cg" ingressName1 = "ingress1" ingressHost1 = "ingress1.example.com" ingressName2 = "ingress2" @@ -55,12 +62,14 @@ type scenario struct { } type eventProps struct { - name string - namespace string - eventType string - objectName string - objectUid string - objectKind string + name string + appName string + envName string + componentName string + eventType string + objectName string + objectUid string + objectKind string } type ingressRuleProps struct { @@ -85,27 +94,6 @@ func Test_EventHandler_Init(t *testing.T) { assert.Equal(t, kubeClient, eh.kubeClient) } -func Test_EventHandler_GetEventsForRadixApplication(t *testing.T) { - appName, envName := "app1", "env1" - envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) - kubeClient, radixClient := setupTest() - - createRadixAppWithEnvironment(t, radixClient, appName, envName) - createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) - createKubernetesEvent(t, kubeClient, envNamespace, ev2, k8sEventTypeNormal, podServer2, k8sKindPod, uid2) - createKubernetesEvent(t, kubeClient, "app2-env", ev3, k8sEventTypeNormal, podServer3, k8sKindPod, uid3) - - eventHandler := Init(kubeClient, radixClient) - events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) - assert.Nil(t, err) - assert.Len(t, events, 2) - assert.ElementsMatch( - t, - []string{podServer1, podServer2}, - []string{events[0].InvolvedObjectName, events[1].InvolvedObjectName}, - ) -} - func Test_EventHandler_NoEventsWhenThereIsNoRadixApplication(t *testing.T) { appName, envName := "app1", "env1" envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) @@ -115,7 +103,7 @@ func Test_EventHandler_NoEventsWhenThereIsNoRadixApplication(t *testing.T) { eventHandler := Init(kubeClient, radixClient) events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) - assert.NotNil(t, err) + assert.NoError(t, err) assert.Len(t, events, 0) } @@ -124,12 +112,14 @@ func Test_EventHandler_NoEventsWhenThereIsNoRadixEnvironment(t *testing.T) { envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) kubeClient, radixClient := setupTest() - createRadixApp(t, radixClient, appName, envName) + createRadixApp(t, kubeClient, radixClient, appName, envName) + err := radixClient.RadixV1().RadixEnvironments().Delete(context.Background(), fmt.Sprintf("%s-%s", appName, envName), metav1.DeleteOptions{}) + require.NoError(t, err) createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) eventHandler := Init(kubeClient, radixClient) events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) - assert.NotNil(t, err) + assert.NoError(t, err) assert.Len(t, events, 0) } @@ -139,7 +129,7 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { t.Run("ObjectState is nil for normal event type", func(t *testing.T) { kubeClient, radixClient := setupTest() - createRadixAppWithEnvironment(t, radixClient, appName, envName) + createRadixApp(t, kubeClient, radixClient, appName, envName) _, err := createKubernetesPod(kubeClient, podServer1, appName, envName, true, true, 0, uid1) createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) require.NoError(t, err) @@ -151,7 +141,7 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { t.Run("ObjectState has Pod state for warning event type", func(t *testing.T) { kubeClient, radixClient := setupTest() - createRadixAppWithEnvironment(t, radixClient, appName, envName) + createRadixApp(t, kubeClient, radixClient, appName, envName) _, err := createKubernetesPod(kubeClient, podServer1, appName, envName, true, false, 0, uid1) createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeWarning, podServer1, k8sKindPod, uid1) require.NoError(t, err) @@ -164,7 +154,7 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { t.Run("ObjectState is nil for warning event type when pod not exist", func(t *testing.T) { kubeClient, radixClient := setupTest() - createRadixAppWithEnvironment(t, radixClient, appName, envName) + createRadixApp(t, kubeClient, radixClient, appName, envName) createKubernetesEvent(t, kubeClient, envNamespace, ev1, k8sEventTypeNormal, podServer1, k8sKindPod, uid1) eventHandler := Init(kubeClient, radixClient) events, _ := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) @@ -174,16 +164,16 @@ func Test_EventHandler_GetEvents_PodState(t *testing.T) { } func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { - envNamespace := operatorutils.GetEnvironmentNamespace(appName, envName) + envNamespace := operatorutils.GetEnvironmentNamespace(appName1, envName1) scenarios := []scenario{ {name: "NoEvents"}, { name: "Pod events", existingEventProps: []eventProps{ - {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: podServer1, objectKind: k8sKindPod, objectUid: uid1}, - {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: podServer2, objectKind: k8sKindPod, objectUid: uid2}, - {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: podServer3, objectKind: k8sKindPod, objectUid: uid3}, + {name: ev1, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: podServer1, objectKind: k8sKindPod, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: podServer2, objectKind: k8sKindPod, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, eventType: k8sEventTypeNormal, objectName: podServer3, objectKind: k8sKindPod, objectUid: uid3}, }, expectedEvents: []eventModels.Event{ {InvolvedObjectName: podServer1, InvolvedObjectKind: k8sKindPod, InvolvedObjectNamespace: envNamespace}, @@ -193,9 +183,9 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { { name: "Deploy events", existingEventProps: []eventProps{ - {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: deploy1, objectKind: k8sKindDeployment, objectUid: uid1}, - {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: deploy2, objectKind: k8sKindDeployment, objectUid: uid2}, - {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: deploy3, objectKind: k8sKindDeployment, objectUid: uid3}, + {name: ev1, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: deploy1, objectKind: k8sKindDeployment, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: deploy2, objectKind: k8sKindDeployment, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, eventType: k8sEventTypeNormal, objectName: deploy3, objectKind: k8sKindDeployment, objectUid: uid3}, }, expectedEvents: []eventModels.Event{ {InvolvedObjectName: deploy1, InvolvedObjectKind: k8sKindDeployment, InvolvedObjectNamespace: envNamespace}, @@ -205,9 +195,9 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { { name: "ReplicaSet events", existingEventProps: []eventProps{ - {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: replicaSetServer1, objectKind: k8sKindReplicaSet, objectUid: uid1}, - {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: replicaSetServer2, objectKind: k8sKindReplicaSet, objectUid: uid2}, - {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: replicaSetServer3, objectKind: k8sKindReplicaSet, objectUid: uid3}, + {name: ev1, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: replicaSetServer1, objectKind: k8sKindReplicaSet, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: replicaSetServer2, objectKind: k8sKindReplicaSet, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, eventType: k8sEventTypeNormal, objectName: replicaSetServer3, objectKind: k8sKindReplicaSet, objectUid: uid3}, }, expectedEvents: []eventModels.Event{ {InvolvedObjectName: replicaSetServer1, InvolvedObjectKind: k8sKindReplicaSet, InvolvedObjectNamespace: envNamespace}, @@ -217,9 +207,9 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { { name: "Ingress events", existingEventProps: []eventProps{ - {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, - {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, - {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + {name: ev1, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, }, expectedEvents: []eventModels.Event{ {InvolvedObjectName: ingressName1, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, @@ -229,14 +219,14 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { { name: "Ingress events with rules", existingEventProps: []eventProps{ - {name: ev1, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, - {name: ev2, namespace: envNamespace, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, - {name: ev3, namespace: "app2-env", eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + {name: ev1, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, }, existingIngressRuleProps: []ingressRuleProps{ - {name: ingressName1, appName: appName, envName: envName, host: ingressHost1, service: deploy1, port: port8080}, - {name: ingressName2, appName: appName, envName: envName, host: ingressHost2, service: deploy2, port: port8090}, - {name: ingressName3, appName: "app2", envName: envName, host: ingressHost3, service: deploy3, port: port9090}, + {name: ingressName1, appName: appName1, envName: envName1, host: ingressHost1, service: deploy1, port: port8080}, + {name: ingressName2, appName: appName1, envName: envName1, host: ingressHost2, service: deploy2, port: port8090}, + {name: ingressName3, appName: "app2", envName: envName1, host: ingressHost3, service: deploy3, port: port9090}, }, expectedEvents: []eventModels.Event{ {InvolvedObjectName: ingressName1, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, @@ -246,14 +236,143 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { } for _, ts := range scenarios { t.Run(ts.name, func(t *testing.T) { - eventHandler := setupTestEnvForHandler(t, appName, envName, ts) - actualEvents, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) + eventHandler, _ := setupTestEnvForHandler(t, ts) + actualEvents, err := eventHandler.GetEnvironmentEvents(context.Background(), appName1, envName1) + assert.Nil(t, err) + assertEvents(t, ts.expectedEvents, actualEvents) + }) + } +} + +func Test_EventHandler_GetComponentEvents(t *testing.T) { + envNamespace := operatorutils.GetEnvironmentNamespace(appName1, envName1) + + scenarios := []scenario{ + {name: "NoEvents"}, + { + name: "Pod events", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: podServer1, objectKind: k8sKindPod, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, eventType: k8sEventTypeNormal, objectName: podServer2, objectKind: k8sKindPod, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: podServer3, objectKind: k8sKindPod, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: podServer1, InvolvedObjectKind: k8sKindPod, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Deploy events", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: deploy1, objectKind: k8sKindDeployment, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, eventType: k8sEventTypeNormal, objectName: deploy2, objectKind: k8sKindDeployment, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: deploy3, objectKind: k8sKindDeployment, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: deploy1, InvolvedObjectKind: k8sKindDeployment, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "ReplicaSet events", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: replicaSetServer1, objectKind: k8sKindReplicaSet, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, eventType: k8sEventTypeNormal, objectName: replicaSetServer2, objectKind: k8sKindReplicaSet, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: replicaSetServer3, objectKind: k8sKindReplicaSet, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: replicaSetServer1, InvolvedObjectKind: k8sKindReplicaSet, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Ingress events", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + }, + existingIngressRuleProps: []ingressRuleProps{ + {name: ingressName1, appName: appName1, envName: envName1, host: ingressHost1, service: deploy1, port: port8080}, + {name: ingressName2, appName: appName1, envName: envName1, host: ingressHost2, service: deploy2, port: port8090}, + {name: ingressName3, appName: "app2", envName: envName1, host: ingressHost3, service: deploy3, port: port9090}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: ingressName1, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Ingress events with rules", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + }, + existingIngressRuleProps: []ingressRuleProps{ + {name: ingressName1, appName: appName1, envName: envName1, host: ingressHost1, service: deploy1, port: port8080}, + {name: ingressName2, appName: appName1, envName: envName1, host: ingressHost2, service: deploy2, port: port8090}, + {name: ingressName3, appName: "app2", envName: envName1, host: ingressHost3, service: deploy3, port: port9090}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: ingressName1, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, + }, + }, + } + for _, ts := range scenarios { + t.Run(ts.name, func(t *testing.T) { + eventHandler, radixClient := setupTestEnvForHandler(t, ts) + createActiveRadixDeployments(t, ts, radixClient) + + actualEvents, err := eventHandler.GetComponentEvents(context.Background(), appName1, envName1, component1) assert.Nil(t, err) assertEvents(t, ts.expectedEvents, actualEvents) }) } } +func createActiveRadixDeployments(t *testing.T, ts scenario, radixClient *radixfake.Clientset) { + appEnvComponentMap := getAppEnvComponentMap(ts) + for appName, envComponentNameMap := range appEnvComponentMap { + for envName, componentNameMap := range envComponentNameMap { + builder := operatorutils. + NewDeploymentBuilder(). + WithAppName(appName). + WithEnvironment(envName) + for componentName := range componentNameMap { + builder = builder.WithComponent(operatorutils. + NewDeployComponentBuilder(). + WithName(componentName)) + } + rd := builder.WithActiveFrom(time.Now()).BuildRD() + _, err := radixClient.RadixV1().RadixDeployments(operatorutils.GetEnvironmentNamespace(appName, envName)). + Create(context.Background(), rd, metav1.CreateOptions{}) + require.NoError(t, err) + } + } +} + +func createRadixApplications(t *testing.T, ts scenario, kubeClient *kubefake.Clientset, radixClient *radixfake.Clientset) { + appEnvComponentMap := getAppEnvComponentMap(ts) + for appName, envComponentNameMap := range appEnvComponentMap { + var envNames []string + for envName := range envComponentNameMap { + envNames = append(envNames, envName) + } + createRadixApp(t, kubeClient, radixClient, appName, envNames...) + } +} + +func getAppEnvComponentMap(ts scenario) map[string]map[string]map[string]struct{} { + appEnvComponentMap := slice.Reduce(ts.existingEventProps, make(map[string]map[string]map[string]struct{}), func(acc map[string]map[string]map[string]struct{}, evProps eventProps) map[string]map[string]map[string]struct{} { + if _, ok := acc[evProps.appName]; !ok { + acc[evProps.appName] = make(map[string]map[string]struct{}) + } + if _, ok := acc[evProps.appName][evProps.envName]; !ok { + acc[evProps.appName][evProps.envName] = make(map[string]struct{}) + } + acc[evProps.appName][evProps.envName][evProps.componentName] = struct{}{} + return acc + }) + return appEnvComponentMap +} + func assertEvents(t *testing.T, expectedEvents []eventModels.Event, actualEvents []*eventModels.Event) { if assert.Len(t, actualEvents, len(expectedEvents)) { for i := 0; i < len(expectedEvents); i++ { @@ -264,20 +383,20 @@ func assertEvents(t *testing.T, expectedEvents []eventModels.Event, actualEvents } } -func setupTestEnvForHandler(t *testing.T, appName string, envName string, ts scenario) EventHandler { +func setupTestEnvForHandler(t *testing.T, ts scenario) (EventHandler, *radixfake.Clientset) { kubeClient, radixClient := setupTest() - createRadixAppWithEnvironment(t, radixClient, appName, envName) + createRadixApplications(t, ts, kubeClient, radixClient) for _, evProps := range ts.existingEventProps { - createKubernetesEvent(t, kubeClient, evProps.namespace, evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) + createKubernetesEvent(t, kubeClient, operatorutils.GetEnvironmentNamespace(evProps.appName, evProps.envName), evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) } for _, ingressRuleProp := range ts.existingIngressRuleProps { - createIngressRule(t, kubeClient, ingressRuleProp) + createIngress(t, kubeClient, ingressRuleProp) } eventHandler := Init(kubeClient, radixClient) - return eventHandler + return eventHandler, radixClient } -func createIngressRule(t *testing.T, kubeClient *kubefake.Clientset, props ingressRuleProps) { +func createIngress(t *testing.T, kubeClient *kubefake.Clientset, props ingressRuleProps) { _, err := kubeClient.NetworkingV1().Ingresses(operatorutils.GetEnvironmentNamespace(props.appName, props.envName)).Create(context.Background(), &networkingv1.Ingress{ ObjectMeta: metav1.ObjectMeta{Name: props.name, Labels: radixlabels.ForApplicationName(props.appName)}, @@ -340,14 +459,15 @@ func createKubernetesPod(client *kubefake.Clientset, name, appName, envName stri metav1.CreateOptions{}) } -func createRadixAppWithEnvironment(t *testing.T, radixClient *radixfake.Clientset, appName string, envName string) { - createRadixApp(t, radixClient, appName, envName) - re := operatorutils.NewEnvironmentBuilder().WithAppName(appName).WithEnvironmentName(envName).BuildRE() - _, err := radixClient.RadixV1().RadixEnvironments().Create(context.Background(), re, metav1.CreateOptions{}) - require.NoError(t, err) -} - -func createRadixApp(t *testing.T, radixClient *radixfake.Clientset, appName string, envName string) { - _, err := radixClient.RadixV1().RadixApplications(operatorutils.GetAppNamespace(appName)).Create(context.Background(), operatorutils.NewRadixApplicationBuilder().WithAppName(appName).WithEnvironment(envName, "").BuildRA(), metav1.CreateOptions{}) +func createRadixApp(t *testing.T, kubeClient *kubefake.Clientset, radixClient *radixfake.Clientset, appName string, envName ...string) { + builder := operatorutils.NewRadixApplicationBuilder().WithAppName(appName) + for _, envName := range envName { + builder = builder.WithEnvironment(envName, "") + _, err := radixClient.RadixV1().RadixEnvironments().Create(context.Background(), operatorutils.NewEnvironmentBuilder().WithAppName(appName).WithEnvironmentName(envName).BuildRE(), metav1.CreateOptions{}) + require.NoError(t, err) + _, err = kubeClient.CoreV1().Namespaces().Create(context.Background(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: operatorutils.GetEnvironmentNamespace(appName, envName)}}, metav1.CreateOptions{}) + require.NoError(t, err) + } + _, err := radixClient.RadixV1().RadixApplications(operatorutils.GetAppNamespace(appName)).Create(context.Background(), builder.BuildRA(), metav1.CreateOptions{}) require.NoError(t, err) } From bc596c28606013bbbeaaf3d87c880d8ab63a38c3 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 12:43:31 +0100 Subject: [PATCH 11/18] Added unit-tests --- api/events/event_handler_test.go | 87 ++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index 20a396cd..c8c08b7b 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -66,6 +66,7 @@ type eventProps struct { appName string envName string componentName string + podName string eventType string objectName string objectUid string @@ -327,6 +328,89 @@ func Test_EventHandler_GetComponentEvents(t *testing.T) { } } +func Test_EventHandler_GetPodEvents(t *testing.T) { + envNamespace := operatorutils.GetEnvironmentNamespace(appName1, envName1) + + scenarios := []scenario{ + {name: "NoEvents"}, + { + name: "Pod events", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: podServer1, objectKind: k8sKindPod, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: podServer2, objectKind: k8sKindPod, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: podServer3, objectKind: k8sKindPod, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: podServer1, InvolvedObjectKind: k8sKindPod, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Deploy events", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: deploy1, objectKind: k8sKindDeployment, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: deploy2, objectKind: k8sKindDeployment, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: deploy3, objectKind: k8sKindDeployment, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: deploy1, InvolvedObjectKind: k8sKindDeployment, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "ReplicaSet events", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: replicaSetServer1, objectKind: k8sKindReplicaSet, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: replicaSetServer2, objectKind: k8sKindReplicaSet, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: replicaSetServer3, objectKind: k8sKindReplicaSet, objectUid: uid3}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: replicaSetServer1, InvolvedObjectKind: k8sKindReplicaSet, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Ingress events", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + }, + existingIngressRuleProps: []ingressRuleProps{ + {name: ingressName1, appName: appName1, envName: envName1, host: ingressHost1, service: deploy1, port: port8080}, + {name: ingressName2, appName: appName1, envName: envName1, host: ingressHost2, service: deploy2, port: port8090}, + {name: ingressName3, appName: "app2", envName: envName1, host: ingressHost3, service: deploy3, port: port9090}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: ingressName1, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, + }, + }, + { + name: "Ingress events with rules", + existingEventProps: []eventProps{ + {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + }, + existingIngressRuleProps: []ingressRuleProps{ + {name: ingressName1, appName: appName1, envName: envName1, host: ingressHost1, service: deploy1, port: port8080}, + {name: ingressName2, appName: appName1, envName: envName1, host: ingressHost2, service: deploy2, port: port8090}, + {name: ingressName3, appName: "app2", envName: envName1, host: ingressHost3, service: deploy3, port: port9090}, + }, + expectedEvents: []eventModels.Event{ + {InvolvedObjectName: ingressName1, InvolvedObjectKind: k8sKindIngress, InvolvedObjectNamespace: envNamespace}, + }, + }, + } + for _, ts := range scenarios { + t.Run(ts.name, func(t *testing.T) { + eventHandler, radixClient := setupTestEnvForHandler(t, ts) + createActiveRadixDeployments(t, ts, radixClient) + + actualEvents, err := eventHandler.GetPodEvents(context.Background(), appName1, envName1, component1, podServer1) + assert.Nil(t, err) + assertEvents(t, ts.expectedEvents, actualEvents) + }) + } +} + func createActiveRadixDeployments(t *testing.T, ts scenario, radixClient *radixfake.Clientset) { appEnvComponentMap := getAppEnvComponentMap(ts) for appName, envComponentNameMap := range appEnvComponentMap { @@ -388,6 +472,9 @@ func setupTestEnvForHandler(t *testing.T, ts scenario) (EventHandler, *radixfake createRadixApplications(t, ts, kubeClient, radixClient) for _, evProps := range ts.existingEventProps { createKubernetesEvent(t, kubeClient, operatorutils.GetEnvironmentNamespace(evProps.appName, evProps.envName), evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) + if evProps.podName != "" { + createKubernetesPod(kubeClient, evProps.podName, evProps.appName, evProps.envName, true, true, 0, evProps.objectUid) + } } for _, ingressRuleProp := range ts.existingIngressRuleProps { createIngress(t, kubeClient, ingressRuleProp) From 3fc474bf43284019b020ff5b7ece5803e2ac750f Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 12:57:00 +0100 Subject: [PATCH 12/18] Updated ref --- go.mod | 3 +-- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index dc8ecf28..ca7f705a 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/cert-manager/cert-manager v1.15.0 github.com/equinor/radix-common v1.9.5 github.com/equinor/radix-job-scheduler v1.11.0 - github.com/equinor/radix-operator v1.62.0 + github.com/equinor/radix-operator v1.64.0 github.com/evanphx/json-patch/v5 v5.9.0 github.com/felixge/httpsnoop v1.0.4 github.com/golang-jwt/jwt/v5 v5.2.1 @@ -18,7 +18,6 @@ require ( github.com/gorilla/mux v1.8.1 github.com/kedacore/keda/v2 v2.15.1 github.com/kelseyhightower/envconfig v1.4.0 - github.com/marstr/guid v1.1.0 github.com/oauth2-proxy/mockoidc v0.0.0-20240214162133-caebfff84d25 github.com/prometheus-operator/prometheus-operator/pkg/client v0.76.0 github.com/prometheus/client_golang v1.20.3 diff --git a/go.sum b/go.sum index 9ed82c22..1c01ef61 100644 --- a/go.sum +++ b/go.sum @@ -89,8 +89,8 @@ github.com/equinor/radix-common v1.9.5 h1:p1xldkYUoavwIMguoxxOyVkOXLPA6K8qMsgzez github.com/equinor/radix-common v1.9.5/go.mod h1:+g0Wj0D40zz29DjNkYKVmCVeYy4OsFWKI7Qi9rA6kpY= github.com/equinor/radix-job-scheduler v1.11.0 h1:8wCmXOVl/1cto8q2WJQEE06Cw68/QmfoifYVR49vzkY= github.com/equinor/radix-job-scheduler v1.11.0/go.mod h1:yPXn3kDcMY0Z3kBkosjuefsdY1x2g0NlBeybMmHz5hc= -github.com/equinor/radix-operator v1.62.0 h1:lurDVymrDhlyopd46KMV28eUltrVUPCk3bnBRFuyCsU= -github.com/equinor/radix-operator v1.62.0/go.mod h1:uRW9SgVZ94hkpq87npVv2YVviRuXNJ1zgCleya1uvr8= +github.com/equinor/radix-operator v1.64.0 h1:KbP0ZAX8zZSNzgejc7oOo087RkdrlTpBg4myk7zs48o= +github.com/equinor/radix-operator v1.64.0/go.mod h1:uRW9SgVZ94hkpq87npVv2YVviRuXNJ1zgCleya1uvr8= github.com/evanphx/json-patch v5.9.0+incompatible h1:fBXyNpNMuTTDdquAq/uisOr2lShz4oaXpDTX2bLe7ls= github.com/evanphx/json-patch v5.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= @@ -266,8 +266,6 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/marstr/guid v1.1.0 h1:/M4H/1G4avsieL6BbUwCOBzulmoeKVP5ux/3mQNnbyI= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= From a2ffadf5f1d80e1afd5284693cb036a511e49503 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 13:25:39 +0100 Subject: [PATCH 13/18] Removed outdated env-var --- api/applications/applications_controller_test.go | 3 --- api/buildstatus/build_status_controller_test.go | 4 ---- 2 files changed, 7 deletions(-) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 80ed311c..44984d79 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -8,7 +8,6 @@ import ( "fmt" "net/http" "net/url" - "os" "strings" "testing" "time" @@ -77,7 +76,6 @@ func setupTestWithFactory(t *testing.T, handlerFactory ApplicationHandlerFactory commonTestUtils := commontest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient) err := commonTestUtils.CreateClusterPrerequisites(clusterName, egressIps, subscriptionId) require.NoError(t, err) - _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, clusterName) prometheusHandlerMock := createPrometheusHandlerMock(t, radixclient, nil) // controllerTestUtils is used for issuing HTTP request and processing responses @@ -1684,7 +1682,6 @@ func TestHandleTriggerPipeline_Promote_JobHasCorrectParameters(t *testing.T) { const ( appName = "an-app" commitId = "475f241c-478b-49da-adfb-3c336aaab8d2" - deploymentName = "a-deployment" fromEnvironment = "origin" toEnvironment = "target" ) diff --git a/api/buildstatus/build_status_controller_test.go b/api/buildstatus/build_status_controller_test.go index 8a2556b4..628b4219 100644 --- a/api/buildstatus/build_status_controller_test.go +++ b/api/buildstatus/build_status_controller_test.go @@ -3,7 +3,6 @@ package buildstatus import ( "errors" "io" - "os" "testing" "time" @@ -15,7 +14,6 @@ import ( certclientfake "github.com/cert-manager/cert-manager/pkg/client/clientset/versioned/fake" controllertest "github.com/equinor/radix-api/api/test" "github.com/equinor/radix-api/api/test/mock" - "github.com/equinor/radix-operator/pkg/apis/defaults" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" commontest "github.com/equinor/radix-operator/pkg/apis/test" builders "github.com/equinor/radix-operator/pkg/apis/utils" @@ -43,8 +41,6 @@ func setupTest(t *testing.T) (*commontest.Utils, *kubefake.Clientset, *radixfake commonTestUtils := commontest.NewTestUtils(kubeclient, radixclient, kedaClient, secretproviderclient) err := commonTestUtils.CreateClusterPrerequisites(clusterName, egressIps, subscriptionId) require.NoError(t, err) - _ = os.Setenv(defaults.ActiveClusternameEnvironmentVariable, clusterName) - return &commonTestUtils, kubeclient, radixclient, kedaClient, secretproviderclient, certClient } From f2f0a78e51ba4649e0dac3f0ff5f414bb8b8e085 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 15:43:36 +0100 Subject: [PATCH 14/18] Update api/events/models/ingress_builder.go Co-authored-by: Richard Hagen --- api/events/models/ingress_builder.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/events/models/ingress_builder.go b/api/events/models/ingress_builder.go index 65a7c4a2..35918c85 100644 --- a/api/events/models/ingress_builder.go +++ b/api/events/models/ingress_builder.go @@ -38,7 +38,7 @@ func (b *ingressStateBuilder) Build() []IngressRule { return nil } - var ingressRiles []IngressRule + var ingressRules []IngressRule for _, rule := range b.ingress.Spec.Rules { if rule.HTTP != nil { for _, path := range rule.HTTP.Paths { @@ -50,10 +50,10 @@ func (b *ingressStateBuilder) Build() []IngressRule { } ingressRule.Port = path.Backend.Service.Port.Number } - ingressRiles = append(ingressRiles, ingressRule) + ingressRules = append(ingressRules , ingressRule) } } } - return ingressRiles + return ingressRules } From 3f8a33e0e819761ff97a9dacf4a621cfb5a2c564 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 16:26:38 +0100 Subject: [PATCH 15/18] Fixed unit-tests --- .../applications_controller_test.go | 1 + .../environment_controller_test.go | 15 +++++++------ api/environments/environment_handler.go | 2 +- .../environment_configuration_status.go | 2 +- api/utils/predicate/radix.go | 2 +- api/utils/predicate/radix_test.go | 21 ++++++++++--------- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/api/applications/applications_controller_test.go b/api/applications/applications_controller_test.go index 44984d79..90838b6b 100644 --- a/api/applications/applications_controller_test.go +++ b/api/applications/applications_controller_test.go @@ -972,6 +972,7 @@ func TestGetApplication_WithEnvironments(t *testing.T) { WithEnvironmentName(anyOrphanedEnvironment)) orphanedRe.Status.Reconciled = metav1.Now() orphanedRe.Status.Orphaned = true + orphanedRe.Status.OrphanedTimestamp = pointers.Ptr(metav1.Now()) _, err = radix.RadixV1().RadixEnvironments().Update(context.Background(), orphanedRe, metav1.UpdateOptions{}) require.NoError(t, err) diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index d415717e..60f1154f 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -323,7 +323,8 @@ func TestDeleteEnvironment_OneOrphanedEnvironment_OnlyOrphanedCanBeDeleted(t *te NewEnvironmentBuilder(). WithAppLabel(). WithAppName(anyAppName). - WithEnvironmentName(anyOrphanedEnvironment)) + WithEnvironmentName(anyOrphanedEnvironment). + WithOrphaned(true)) require.NoError(t, err) // Test @@ -589,13 +590,11 @@ func Test_GetEnvironmentEvents_Controller(t *testing.T) { t.Run("Get events for non-existing environment", func(t *testing.T) { responseChannel := environmentControllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/environments/%s/events", anyAppName, "prod")) response := <-responseChannel - assert.Equal(t, http.StatusNotFound, response.Code) - errResponse, _ := controllertest.GetErrorResponse(response) - assert.Equal( - t, - environmentModels.NonExistingEnvironment(nil, anyAppName, "prod").Error(), - errResponse.Message, - ) + assert.Equal(t, http.StatusOK, response.Code) + events := make([]eventModels.Event, 0) + err = controllertest.GetResponseBody(response, &events) + require.NoError(t, err) + assert.Len(t, events, 0) }) t.Run("Get events for non-existing application", func(t *testing.T) { diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index d51dff2a..9c41b582 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -238,7 +238,7 @@ func (eh EnvironmentHandler) DeleteEnvironment(ctx context.Context, appName, env return err } - if !re.Status.Orphaned { + if !re.Status.Orphaned && re.Status.OrphanedTimestamp == nil { // Must be removed from radix config first return environmentModels.CannotDeleteNonOrphanedEnvironment(appName, envName) } diff --git a/api/models/environment_configuration_status.go b/api/models/environment_configuration_status.go index 7464f245..1299fad5 100644 --- a/api/models/environment_configuration_status.go +++ b/api/models/environment_configuration_status.go @@ -9,7 +9,7 @@ func getEnvironmentConfigurationStatus(re *radixv1.RadixEnvironment) environment switch { case re == nil: return environmentModels.Pending - case re.Status.Orphaned: + case re.Status.Orphaned || re.Status.OrphanedTimestamp != nil: return environmentModels.Orphan case re.Status.Reconciled.IsZero(): return environmentModels.Pending diff --git a/api/utils/predicate/radix.go b/api/utils/predicate/radix.go index 251dc45c..b678ef1c 100644 --- a/api/utils/predicate/radix.go +++ b/api/utils/predicate/radix.go @@ -11,7 +11,7 @@ func IsNotOrphanEnvironment(re radixv1.RadixEnvironment) bool { } func IsOrphanEnvironment(re radixv1.RadixEnvironment) bool { - return re.Status.Orphaned + return re.Status.Orphaned || re.Status.OrphanedTimestamp != nil } func IsBatchJobStatusForBatchJob(job radixv1.RadixBatchJob) func(jobStatus radixv1.RadixBatchJobStatus) bool { diff --git a/api/utils/predicate/radix_test.go b/api/utils/predicate/radix_test.go index de9866a8..55b3f6bd 100644 --- a/api/utils/predicate/radix_test.go +++ b/api/utils/predicate/radix_test.go @@ -3,9 +3,10 @@ package predicate import ( "testing" + "github.com/equinor/radix-common/utils/pointers" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" "github.com/stretchr/testify/assert" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Test_IsActiveRadixDeployment(t *testing.T) { @@ -16,14 +17,14 @@ func Test_IsActiveRadixDeployment(t *testing.T) { func Test_IsNotOrphanEnvironment(t *testing.T) { assert.True(t, IsNotOrphanEnvironment(radixv1.RadixEnvironment{})) - assert.True(t, IsNotOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: false}})) - assert.False(t, IsNotOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: true}})) + assert.True(t, IsNotOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: false, OrphanedTimestamp: nil}})) + assert.False(t, IsNotOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: true, OrphanedTimestamp: pointers.Ptr(metav1.Now())}})) } func Test_IsOrphanEnvironment(t *testing.T) { - assert.True(t, IsOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: true}})) + assert.True(t, IsOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: true, OrphanedTimestamp: pointers.Ptr(metav1.Now())}})) assert.False(t, IsOrphanEnvironment(radixv1.RadixEnvironment{})) - assert.False(t, IsOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: false}})) + assert.False(t, IsOrphanEnvironment(radixv1.RadixEnvironment{Status: radixv1.RadixEnvironmentStatus{Orphaned: false, OrphanedTimestamp: nil}})) } func Test_IsBatchJobStatusForBatchJob(t *testing.T) { @@ -40,7 +41,7 @@ func Test_IsBatchJobWithName(t *testing.T) { func Test_IsRadixDeploymentForRadixBatch(t *testing.T) { batch := &radixv1.RadixBatch{ - ObjectMeta: v1.ObjectMeta{Namespace: "batchns"}, + ObjectMeta: metav1.ObjectMeta{Namespace: "batchns"}, Spec: radixv1.RadixBatchSpec{ RadixDeploymentJobRef: radixv1.RadixDeploymentJobComponentSelector{ LocalObjectReference: radixv1.LocalObjectReference{Name: "deployname"}, @@ -48,21 +49,21 @@ func Test_IsRadixDeploymentForRadixBatch(t *testing.T) { }, } sut := IsRadixDeploymentForRadixBatch(batch) - assert.True(t, sut(radixv1.RadixDeployment{ObjectMeta: v1.ObjectMeta{ + assert.True(t, sut(radixv1.RadixDeployment{ObjectMeta: metav1.ObjectMeta{ Name: "deployname", Namespace: "batchns", }})) - assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: v1.ObjectMeta{ + assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: metav1.ObjectMeta{ Name: "otherdeployname", Namespace: "batchns", }})) - assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: v1.ObjectMeta{ + assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: metav1.ObjectMeta{ Name: "deployname", Namespace: "otherbatchns", }})) sut = IsRadixDeploymentForRadixBatch(nil) - assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: v1.ObjectMeta{ + assert.False(t, sut(radixv1.RadixDeployment{ObjectMeta: metav1.ObjectMeta{ Name: "anydeployname", Namespace: "anybatchns", }})) From a2b0ad104797e909b5bf856e7a56c389c775765b Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 16:39:26 +0100 Subject: [PATCH 16/18] Fixed unit-tests --- api/environments/environment_controller_test.go | 12 +++++++----- api/events/event_handler.go | 9 +++++---- api/events/event_handler_test.go | 7 ++----- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 60f1154f..69bfb08b 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -590,11 +590,13 @@ func Test_GetEnvironmentEvents_Controller(t *testing.T) { t.Run("Get events for non-existing environment", func(t *testing.T) { responseChannel := environmentControllerTestUtils.ExecuteRequest("GET", fmt.Sprintf("/api/v1/applications/%s/environments/%s/events", anyAppName, "prod")) response := <-responseChannel - assert.Equal(t, http.StatusOK, response.Code) - events := make([]eventModels.Event, 0) - err = controllertest.GetResponseBody(response, &events) - require.NoError(t, err) - assert.Len(t, events, 0) + assert.Equal(t, http.StatusNotFound, response.Code) + errResponse, _ := controllertest.GetErrorResponse(response) + assert.Equal( + t, + environmentModels.NonExistingEnvironment(nil, anyAppName, "prod").Error(), + errResponse.Message, + ) }) t.Run("Get events for non-existing application", func(t *testing.T) { diff --git a/api/events/event_handler.go b/api/events/event_handler.go index 57efed54..9037949c 100644 --- a/api/events/event_handler.go +++ b/api/events/event_handler.go @@ -5,6 +5,7 @@ import ( "fmt" "regexp" + environmentModels "github.com/equinor/radix-api/api/environments/models" eventModels "github.com/equinor/radix-api/api/events/models" "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-common/utils/slice" @@ -53,9 +54,6 @@ func Init(kubeClient kubernetes.Interface, radixClient radixclient.Interface) Ev func (eh *eventHandler) GetEnvironmentEvents(ctx context.Context, appName, envName string) ([]*eventModels.Event, error) { radixApplication, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) if err != nil { - if errors.IsNotFound(err) { - return nil, nil - } return nil, err } environmentEvents, err := eh.getEvents(ctx, radixApplication.Name, envName, "", "") @@ -97,6 +95,9 @@ func (eh *eventHandler) getRadixApplicationAndValidateEnvironment(ctx context.Co _, err = kubequery.GetRadixEnvironment(ctx, eh.radixClient, appName, envName) if err != nil { + if errors.IsNotFound(err) { + return nil, environmentModels.NonExistingEnvironment(err, appName, envName) + } return nil, err } return radixApplication, err @@ -106,7 +107,7 @@ func (eh *eventHandler) existsRadixDeployComponent(ctx context.Context, appName, _, err := eh.getRadixApplicationAndValidateEnvironment(ctx, appName, envName) if err != nil { if errors.IsNotFound(err) { - return false, nil + return false, environmentModels.NonExistingComponent(appName, componentName) } return false, err } diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index c8c08b7b..9b2f23f3 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -104,7 +104,7 @@ func Test_EventHandler_NoEventsWhenThereIsNoRadixApplication(t *testing.T) { eventHandler := Init(kubeClient, radixClient) events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) - assert.NoError(t, err) + assert.NotNil(t, err) assert.Len(t, events, 0) } @@ -120,7 +120,7 @@ func Test_EventHandler_NoEventsWhenThereIsNoRadixEnvironment(t *testing.T) { eventHandler := Init(kubeClient, radixClient) events, err := eventHandler.GetEnvironmentEvents(context.Background(), appName, envName) - assert.NoError(t, err) + assert.NotNil(t, err) assert.Len(t, events, 0) } @@ -168,7 +168,6 @@ func Test_EventHandler_GetEnvironmentEvents(t *testing.T) { envNamespace := operatorutils.GetEnvironmentNamespace(appName1, envName1) scenarios := []scenario{ - {name: "NoEvents"}, { name: "Pod events", existingEventProps: []eventProps{ @@ -249,7 +248,6 @@ func Test_EventHandler_GetComponentEvents(t *testing.T) { envNamespace := operatorutils.GetEnvironmentNamespace(appName1, envName1) scenarios := []scenario{ - {name: "NoEvents"}, { name: "Pod events", existingEventProps: []eventProps{ @@ -332,7 +330,6 @@ func Test_EventHandler_GetPodEvents(t *testing.T) { envNamespace := operatorutils.GetEnvironmentNamespace(appName1, envName1) scenarios := []scenario{ - {name: "NoEvents"}, { name: "Pod events", existingEventProps: []eventProps{ From c09a8e33f337105359f2d3c95bf0befa0876476c Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 16:43:21 +0100 Subject: [PATCH 17/18] Fixed unit-tests --- api/events/event_handler_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index 9b2f23f3..ecd101b6 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -470,7 +470,8 @@ func setupTestEnvForHandler(t *testing.T, ts scenario) (EventHandler, *radixfake for _, evProps := range ts.existingEventProps { createKubernetesEvent(t, kubeClient, operatorutils.GetEnvironmentNamespace(evProps.appName, evProps.envName), evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) if evProps.podName != "" { - createKubernetesPod(kubeClient, evProps.podName, evProps.appName, evProps.envName, true, true, 0, evProps.objectUid) + _, err := createKubernetesPod(kubeClient, evProps.podName, evProps.appName, evProps.envName, true, true, 0, evProps.objectUid) + require.NoError(t, err) } } for _, ingressRuleProp := range ts.existingIngressRuleProps { From 56e10174e8ce81e59a47216b7ac8e1178b452cf3 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 29 Oct 2024 16:58:09 +0100 Subject: [PATCH 18/18] Fixed unit-tests --- api/events/event_handler_test.go | 49 +++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/api/events/event_handler_test.go b/api/events/event_handler_test.go index ecd101b6..849b7cc9 100644 --- a/api/events/event_handler_test.go +++ b/api/events/event_handler_test.go @@ -334,8 +334,8 @@ func Test_EventHandler_GetPodEvents(t *testing.T) { name: "Pod events", existingEventProps: []eventProps{ {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: podServer1, objectKind: k8sKindPod, objectUid: uid1}, - {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: podServer2, objectKind: k8sKindPod, objectUid: uid2}, - {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: podServer3, objectKind: k8sKindPod, objectUid: uid3}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer2, eventType: k8sEventTypeNormal, objectName: podServer2, objectKind: k8sKindPod, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer3, eventType: k8sEventTypeNormal, objectName: podServer3, objectKind: k8sKindPod, objectUid: uid3}, }, expectedEvents: []eventModels.Event{ {InvolvedObjectName: podServer1, InvolvedObjectKind: k8sKindPod, InvolvedObjectNamespace: envNamespace}, @@ -345,8 +345,8 @@ func Test_EventHandler_GetPodEvents(t *testing.T) { name: "Deploy events", existingEventProps: []eventProps{ {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: deploy1, objectKind: k8sKindDeployment, objectUid: uid1}, - {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: deploy2, objectKind: k8sKindDeployment, objectUid: uid2}, - {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: deploy3, objectKind: k8sKindDeployment, objectUid: uid3}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer2, eventType: k8sEventTypeNormal, objectName: deploy2, objectKind: k8sKindDeployment, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer3, eventType: k8sEventTypeNormal, objectName: deploy3, objectKind: k8sKindDeployment, objectUid: uid3}, }, expectedEvents: []eventModels.Event{ {InvolvedObjectName: deploy1, InvolvedObjectKind: k8sKindDeployment, InvolvedObjectNamespace: envNamespace}, @@ -356,8 +356,8 @@ func Test_EventHandler_GetPodEvents(t *testing.T) { name: "ReplicaSet events", existingEventProps: []eventProps{ {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: replicaSetServer1, objectKind: k8sKindReplicaSet, objectUid: uid1}, - {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: replicaSetServer2, objectKind: k8sKindReplicaSet, objectUid: uid2}, - {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: replicaSetServer3, objectKind: k8sKindReplicaSet, objectUid: uid3}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer2, eventType: k8sEventTypeNormal, objectName: replicaSetServer2, objectKind: k8sKindReplicaSet, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer3, eventType: k8sEventTypeNormal, objectName: replicaSetServer3, objectKind: k8sKindReplicaSet, objectUid: uid3}, }, expectedEvents: []eventModels.Event{ {InvolvedObjectName: replicaSetServer1, InvolvedObjectKind: k8sKindReplicaSet, InvolvedObjectNamespace: envNamespace}, @@ -367,8 +367,8 @@ func Test_EventHandler_GetPodEvents(t *testing.T) { name: "Ingress events", existingEventProps: []eventProps{ {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, - {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, - {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer2, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer3, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, }, existingIngressRuleProps: []ingressRuleProps{ {name: ingressName1, appName: appName1, envName: envName1, host: ingressHost1, service: deploy1, port: port8080}, @@ -383,8 +383,8 @@ func Test_EventHandler_GetPodEvents(t *testing.T) { name: "Ingress events with rules", existingEventProps: []eventProps{ {name: ev1, appName: appName1, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName1, objectKind: k8sKindIngress, objectUid: uid1}, - {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, - {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer1, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, + {name: ev2, appName: appName1, envName: envName1, componentName: component2, podName: podServer2, eventType: k8sEventTypeNormal, objectName: ingressName2, objectKind: k8sKindIngress, objectUid: uid2}, + {name: ev3, appName: appName2, envName: envName1, componentName: component1, podName: podServer3, eventType: k8sEventTypeNormal, objectName: ingressName3, objectKind: k8sKindIngress, objectUid: uid3}, }, existingIngressRuleProps: []ingressRuleProps{ {name: ingressName1, appName: appName1, envName: envName1, host: ingressHost1, service: deploy1, port: port8080}, @@ -454,6 +454,23 @@ func getAppEnvComponentMap(ts scenario) map[string]map[string]map[string]struct{ return appEnvComponentMap } +func getAppEnvPodsMap(ts scenario) map[string]map[string]map[string]string { + appEnvComponentMap := slice.Reduce(ts.existingEventProps, make(map[string]map[string]map[string]string), func(acc map[string]map[string]map[string]string, evProps eventProps) map[string]map[string]map[string]string { + if len(evProps.podName) == 0 { + return nil + } + if _, ok := acc[evProps.appName]; !ok { + acc[evProps.appName] = make(map[string]map[string]string) + } + if _, ok := acc[evProps.appName][evProps.envName]; !ok { + acc[evProps.appName][evProps.envName] = make(map[string]string) + } + acc[evProps.appName][evProps.envName][evProps.podName] = evProps.objectUid + return acc + }) + return appEnvComponentMap +} + func assertEvents(t *testing.T, expectedEvents []eventModels.Event, actualEvents []*eventModels.Event) { if assert.Len(t, actualEvents, len(expectedEvents)) { for i := 0; i < len(expectedEvents); i++ { @@ -469,11 +486,17 @@ func setupTestEnvForHandler(t *testing.T, ts scenario) (EventHandler, *radixfake createRadixApplications(t, ts, kubeClient, radixClient) for _, evProps := range ts.existingEventProps { createKubernetesEvent(t, kubeClient, operatorutils.GetEnvironmentNamespace(evProps.appName, evProps.envName), evProps.name, evProps.eventType, evProps.objectName, evProps.objectKind, evProps.objectUid) - if evProps.podName != "" { - _, err := createKubernetesPod(kubeClient, evProps.podName, evProps.appName, evProps.envName, true, true, 0, evProps.objectUid) - require.NoError(t, err) + } + appEnvPodsMap := getAppEnvPodsMap(ts) + for appName, envPodNameMap := range appEnvPodsMap { + for envName, podNameMap := range envPodNameMap { + for podName, uid := range podNameMap { + _, err := createKubernetesPod(kubeClient, podName, appName, envName, true, true, 0, uid) + require.NoError(t, err) + } } } + for _, ingressRuleProp := range ts.existingIngressRuleProps { createIngress(t, kubeClient, ingressRuleProp) }