From 44b55c614210074ee7eeb1c27cd354aaa2323802 Mon Sep 17 00:00:00 2001 From: Sergey Smolnikov Date: Tue, 2 Apr 2024 09:35:12 +0200 Subject: [PATCH] Extended scheduled job status messages (#603) * Extended scheduled job status messages * Extended scheduled job status messages * Extended scheduled job status messages * Extended scheduled job status messages * Extended scheduled job and pods status messages with event message * Generated swagger * Fixing unit-tests * Fixing unit-tests * Fixing unit-tests * Cleanup * Updated versions, fixed unit-test --- api/alerting/handler.go | 4 +- api/deployments/component_handler.go | 24 +- .../models/component_deployment.go | 49 +++- api/deployments/models/deployment_builder.go | 5 +- api/deployments/models/scheduled_batch.go | 2 +- api/environments/environment_controller.go | 8 +- .../environment_controller_test.go | 212 +++++++++++++++--- api/environments/environment_handler.go | 10 +- api/environments/job_handler.go | 172 +++++++------- api/kubequery/event_test.go | 29 +++ api/kubequery/events.go | 20 ++ api/models/auxiliary_resource.go | 12 +- api/models/component.go | 21 +- api/models/deployment.go | 8 +- api/models/environment.go | 7 +- api/models/replica_summary.go | 11 +- api/pods/pod_handler.go | 32 ++- api/utils/event/event.go | 24 ++ go.mod | 5 +- go.sum | 40 +--- swaggerui/html/swagger.json | 40 +++- 21 files changed, 519 insertions(+), 216 deletions(-) create mode 100644 api/kubequery/event_test.go create mode 100644 api/kubequery/events.go create mode 100644 api/utils/event/event.go diff --git a/api/alerting/handler.go b/api/alerting/handler.go index 68984c58..3a29b53f 100644 --- a/api/alerting/handler.go +++ b/api/alerting/handler.go @@ -9,11 +9,11 @@ import ( alertModels "github.com/equinor/radix-api/api/alerting/models" "github.com/equinor/radix-api/api/utils/labelselector" "github.com/equinor/radix-api/models" + "github.com/equinor/radix-common/utils" operatoralert "github.com/equinor/radix-operator/pkg/apis/alert" "github.com/equinor/radix-operator/pkg/apis/kube" radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" crdutils "github.com/equinor/radix-operator/pkg/apis/utils" - "github.com/equinor/radix-operator/pkg/apis/utils/slice" corev1 "k8s.io/api/core/v1" kubeErrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -195,7 +195,7 @@ func (h *handler) validateUpdateAlertingConfig(config *alertModels.UpdateAlertin return InvalidAlertReceiverError(alert.Alert, alert.Receiver) } // Verify alert name is valid - if !slice.ContainsString(h.validAlertNames, alert.Alert) { + if !utils.ContainsString(h.validAlertNames, alert.Alert) { return InvalidAlertError(alert.Alert) } } diff --git a/api/deployments/component_handler.go b/api/deployments/component_handler.go index 8a298d62..5be9b9c1 100644 --- a/api/deployments/component_handler.go +++ b/api/deployments/component_handler.go @@ -5,9 +5,12 @@ import ( "strings" deploymentModels "github.com/equinor/radix-api/api/deployments/models" + "github.com/equinor/radix-api/api/kubequery" "github.com/equinor/radix-api/api/utils" + "github.com/equinor/radix-api/api/utils/event" "github.com/equinor/radix-api/api/utils/labelselector" radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/defaults" "github.com/equinor/radix-operator/pkg/apis/deployment" "github.com/equinor/radix-operator/pkg/apis/kube" @@ -169,7 +172,12 @@ func GetComponentStateFromSpec( } componentPodNames = getPodNames(componentPods) environmentVariables = getRadixEnvironmentVariables(componentPods) - replicaSummaryList = getReplicaSummaryList(componentPods) + eventList, err := kubequery.GetEventsForEnvironment(ctx, kubeClient, appName, deployment.Environment) + if err != nil { + return nil, err + } + lastEventWarnings := event.ConvertToEventWarnings(eventList) + replicaSummaryList = getReplicaSummaryList(componentPods, lastEventWarnings) auxResource, err = getAuxiliaryResources(ctx, kubeClient, appName, component, envNs) if err != nil { return nil, err @@ -305,14 +313,10 @@ func getRadixEnvironmentVariables(pods []corev1.Pod) map[string]string { return radixEnvironmentVariables } -func getReplicaSummaryList(pods []corev1.Pod) []deploymentModels.ReplicaSummary { - replicaSummaryList := make([]deploymentModels.ReplicaSummary, 0, len(pods)) - - for _, pod := range pods { - replicaSummaryList = append(replicaSummaryList, deploymentModels.GetReplicaSummary(pod)) - } - - return replicaSummaryList +func getReplicaSummaryList(pods []corev1.Pod, lastEventWarnings event.LastEventWarnings) []deploymentModels.ReplicaSummary { + return slice.Map(pods, func(pod corev1.Pod) deploymentModels.ReplicaSummary { + return deploymentModels.GetReplicaSummary(pod, lastEventWarnings[pod.GetName()]) + }) } func getAuxiliaryResources(ctx context.Context, kubeClient kubernetes.Interface, appName string, component v1.RadixCommonDeployComponent, envNamespace string) (auxResource deploymentModels.AuxiliaryResource, err error) { @@ -357,7 +361,7 @@ func getAuxiliaryResourceDeployment(ctx context.Context, kubeClient kubernetes.I if err != nil { return nil, err } - auxResourceDeployment.ReplicaList = getReplicaSummaryList(pods.Items) + auxResourceDeployment.ReplicaList = getReplicaSummaryList(pods.Items, nil) auxResourceDeployment.Status = deploymentModels.ComponentStatusFromDeployment(&deployment).String() return &auxResourceDeployment, nil } diff --git a/api/deployments/models/component_deployment.go b/api/deployments/models/component_deployment.go index 0f875f41..0881323f 100644 --- a/api/deployments/models/component_deployment.go +++ b/api/deployments/models/component_deployment.go @@ -6,6 +6,7 @@ import ( "strings" radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" corev1 "k8s.io/api/core/v1" ) @@ -280,7 +281,19 @@ type ReplicaSummary struct { // // required: false // example: 2006-01-02T15:04:05Z - Created string `json:"created"` + Created string `json:"created,omitempty"` + + // The time at which the batch job's pod startedAt + // + // required: false + // example: 2006-01-02T15:04:05Z + StartTime string `json:"startTime,omitempty"` + + // The time at which the batch job's pod finishedAt. + // + // required: false + // example: 2006-01-02T15:04:05Z + EndTime string `json:"endTime,omitempty"` // Container started timestamp // @@ -291,34 +304,43 @@ type ReplicaSummary struct { // Status describes the component container status // // required: false - Status ReplicaStatus `json:"replicaStatus"` + Status ReplicaStatus `json:"replicaStatus,omitempty"` // StatusMessage provides message describing the status of a component container inside a pod // // required: false - StatusMessage string `json:"statusMessage"` + StatusMessage string `json:"statusMessage,omitempty"` // RestartCount count of restarts of a component container inside a pod // // required: false - RestartCount int32 `json:"restartCount"` + RestartCount int32 `json:"restartCount,omitempty"` // The image the container is running. // // required: false // example: radixdev.azurecr.io/app-server:cdgkg - Image string `json:"image"` + Image string `json:"image,omitempty"` // ImageID of the container's image. // // required: false // example: radixdev.azurecr.io/app-server@sha256:d40cda01916ef63da3607c03785efabc56eb2fc2e0dab0726b1a843e9ded093f - ImageId string `json:"imageId"` + ImageId string `json:"imageId,omitempty"` + + // The index of the pod in the re-starts + PodIndex int `json:"podIndex,omitempty"` + + // Exit status from the last termination of the container + ExitCode int32 `json:"exitCode"` + + // A brief CamelCase message indicating details about why the job is in this phase + Reason string `json:"reason,omitempty"` // Resources Resource requirements for the pod // // required: false - Resources ResourceRequirements `json:"resources,omitempty"` + Resources *ResourceRequirements `json:"resources,omitempty"` } // ReplicaStatus describes the status of a component container inside a pod @@ -326,12 +348,14 @@ type ReplicaSummary struct { type ReplicaStatus struct { // Status of the container // - Pending = Container in Waiting state and the reason is ContainerCreating - // - Failing = Container in Waiting state and the reason is anything else but ContainerCreating + // - Failed = Container is failed + // - Failing = Container is failed // - Running = Container in Running state + // - Succeeded = Container in Succeeded state // - Terminated = Container in Terminated state // // required: true - // enum: Pending,Failing,Running,Terminated,Starting + // enum: Pending,Succeeded,Failing,Failed,Running,Terminated,Starting // example: Running Status string `json:"status"` } @@ -396,7 +420,7 @@ type ResourceRequirements struct { Requests Resources `json:"requests,omitempty"` } -func GetReplicaSummary(pod corev1.Pod) ReplicaSummary { +func GetReplicaSummary(pod corev1.Pod, lastEventWarning string) ReplicaSummary { replicaSummary := ReplicaSummary{} replicaSummary.Name = pod.GetName() creationTimestamp := pod.GetCreationTimestamp() @@ -450,7 +474,10 @@ func GetReplicaSummary(pod corev1.Pod) ReplicaSummary { replicaSummary.Image = containerStatus.Image replicaSummary.ImageId = containerStatus.ImageID if len(pod.Spec.Containers) > 0 { - replicaSummary.Resources = ConvertResourceRequirements(pod.Spec.Containers[0].Resources) + replicaSummary.Resources = pointers.Ptr(ConvertResourceRequirements(pod.Spec.Containers[0].Resources)) + } + if len(replicaSummary.StatusMessage) == 0 && (replicaSummary.Status.Status == Failing.String() || replicaSummary.Status.Status == Pending.String()) { + replicaSummary.StatusMessage = lastEventWarning } return replicaSummary } diff --git a/api/deployments/models/deployment_builder.go b/api/deployments/models/deployment_builder.go index 73549e95..138210ed 100644 --- a/api/deployments/models/deployment_builder.go +++ b/api/deployments/models/deployment_builder.go @@ -4,12 +4,11 @@ import ( "errors" "time" - "github.com/equinor/radix-common/utils/slice" - crdUtils "github.com/equinor/radix-operator/pkg/apis/utils" - radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/slice" "github.com/equinor/radix-operator/pkg/apis/kube" v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" + crdUtils "github.com/equinor/radix-operator/pkg/apis/utils" ) // DeploymentBuilder Builds DTOs diff --git a/api/deployments/models/scheduled_batch.go b/api/deployments/models/scheduled_batch.go index be7ad79b..1fd05585 100644 --- a/api/deployments/models/scheduled_batch.go +++ b/api/deployments/models/scheduled_batch.go @@ -30,7 +30,7 @@ type ScheduledJobSummary struct { // Status of the job // // required: true - // enum: Running,Succeeded,Failed,Waiting,Stopping,Stopped + // enum: Running,Active,Succeeded,Failed,Waiting,Stopping,Stopped // example: Waiting Status string `json:"status"` diff --git a/api/environments/environment_controller.go b/api/environments/environment_controller.go index 4f00bf08..a20626a0 100644 --- a/api/environments/environment_controller.go +++ b/api/environments/environment_controller.go @@ -1135,6 +1135,11 @@ func (c *environmentController) GetScheduledJobLog(accounts models.Accounts, w h // description: Name of scheduled job // type: string // required: true + // - name: replicaName + // in: query + // description: Name of the job replica + // type: string + // required: false // - name: sinceTime // in: query // description: Get log only from sinceTime (example 2020-03-18T07:20:41+00:00) @@ -1173,6 +1178,7 @@ func (c *environmentController) GetScheduledJobLog(accounts models.Accounts, w h appName := mux.Vars(r)["appName"] envName := mux.Vars(r)["envName"] scheduledJobName := mux.Vars(r)["scheduledJobName"] + replicaName := r.FormValue("replicaName") since, asFile, logLines, err, _ := logs.GetLogParams(r) if err != nil { @@ -1181,7 +1187,7 @@ func (c *environmentController) GetScheduledJobLog(accounts models.Accounts, w h } eh := c.environmentHandlerFactory(accounts) - logs, err := eh.GetScheduledJobLogs(r.Context(), appName, envName, scheduledJobName, &since, logLines) + logs, err := eh.GetScheduledJobLogs(r.Context(), appName, envName, scheduledJobName, replicaName, &since, logLines) if err != nil { c.ErrorResponse(w, r, err) return diff --git a/api/environments/environment_controller_test.go b/api/environments/environment_controller_test.go index 781acf6c..9cb9c0d3 100644 --- a/api/environments/environment_controller_test.go +++ b/api/environments/environment_controller_test.go @@ -1360,12 +1360,31 @@ func Test_GetJobs_Status(t *testing.T) { Jobs: []v1.RadixBatchJob{{Name: "no1"}, {Name: "no2"}, {Name: "no3"}, {Name: "no4"}, {Name: "no5"}, {Name: "no6"}, {Name: "no7"}}}, Status: v1.RadixBatchStatus{ JobStatuses: []v1.RadixBatchJobStatus{ - {Name: "no2"}, - {Name: "no3", Phase: v1.BatchJobPhaseWaiting}, - {Name: "no4", Phase: v1.BatchJobPhaseActive}, - {Name: "no5", Phase: v1.BatchJobPhaseSucceeded}, - {Name: "no6", Phase: v1.BatchJobPhaseFailed}, - {Name: "no7", Phase: v1.BatchJobPhaseStopped}, + { + Name: "no2", + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{CreationTime: &metav1.Time{Time: time.Now()}, Phase: v1.PodPending}}, + }, + { + Name: "no3", + Phase: v1.BatchJobPhaseWaiting, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{CreationTime: &metav1.Time{Time: time.Now()}, Phase: v1.PodPending}}, + }, + { + Name: "no4", Phase: v1.BatchJobPhaseActive, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{CreationTime: &metav1.Time{Time: time.Now()}, Phase: v1.PodPending}}, + }, + { + Name: "no5", Phase: v1.BatchJobPhaseSucceeded, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{CreationTime: &metav1.Time{Time: time.Now()}, Phase: v1.PodSucceeded}}, + }, + { + Name: "no6", Phase: v1.BatchJobPhaseFailed, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{CreationTime: &metav1.Time{Time: time.Now()}, Phase: v1.PodFailed}}, + }, + { + Name: "no7", Phase: v1.BatchJobPhaseStopped, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{CreationTime: &metav1.Time{Time: time.Now()}, Phase: v1.PodSucceeded}}, + }, {Name: "not-defined"}, }, }, @@ -1396,7 +1415,7 @@ func Test_GetJobs_Status(t *testing.T) { {Name: anyBatchName + "-no1", Status: jobSchedulerModels.Waiting.String()}, {Name: anyBatchName + "-no2", Status: jobSchedulerModels.Waiting.String()}, {Name: anyBatchName + "-no3", Status: jobSchedulerModels.Waiting.String()}, - {Name: anyBatchName + "-no4", Status: jobSchedulerModels.Running.String()}, + {Name: anyBatchName + "-no4", Status: jobSchedulerModels.Active.String()}, {Name: anyBatchName + "-no5", Status: jobSchedulerModels.Succeeded.String()}, {Name: anyBatchName + "-no6", Status: jobSchedulerModels.Failed.String()}, {Name: anyBatchName + "-no7", Status: jobSchedulerModels.Stopped.String()}, @@ -1433,7 +1452,16 @@ func Test_GetJobs_Status_StopIsTrue(t *testing.T) { Labels: labels.Merge(labels.ForApplicationName(anyAppName), labels.ForComponentName(anyJobName), labels.ForBatchType(kube.RadixBatchTypeJob)), }, Spec: v1.RadixBatchSpec{ - Jobs: []v1.RadixBatchJob{{Name: "no1", Stop: radixutils.BoolPtr(true)}, {Name: "no2", Stop: radixutils.BoolPtr(true)}, {Name: "no3", Stop: radixutils.BoolPtr(true)}, {Name: "no4", Stop: radixutils.BoolPtr(true)}, {Name: "no5", Stop: radixutils.BoolPtr(true)}, {Name: "no6", Stop: radixutils.BoolPtr(true)}, {Name: "no7", Stop: radixutils.BoolPtr(true)}}}, + Jobs: []v1.RadixBatchJob{ + {Name: "no1", Stop: radixutils.BoolPtr(true)}, + {Name: "no2", Stop: radixutils.BoolPtr(true)}, + {Name: "no3", Stop: radixutils.BoolPtr(true)}, + {Name: "no4", Stop: radixutils.BoolPtr(true)}, + {Name: "no5", Stop: radixutils.BoolPtr(true)}, + {Name: "no6", Stop: radixutils.BoolPtr(true)}, + {Name: "no7", Stop: radixutils.BoolPtr(true)}, + }, + }, Status: v1.RadixBatchStatus{ JobStatuses: []v1.RadixBatchJobStatus{ {Name: "no2"}, @@ -1577,6 +1605,7 @@ func Test_GetJob_AllProps(t *testing.T) { namespace := operatorutils.GetEnvironmentNamespace(anyAppName, anyEnvironment) creationTime := metav1.NewTime(time.Date(2022, 1, 2, 3, 4, 5, 0, time.UTC)) startTime := metav1.NewTime(time.Date(2022, 1, 2, 3, 4, 10, 0, time.UTC)) + podCreationTime := metav1.NewTime(time.Date(2022, 1, 2, 3, 4, 15, 0, time.UTC)) endTime := metav1.NewTime(time.Date(2022, 1, 2, 3, 4, 15, 0, time.UTC)) defaultBackoffLimit := numbers.Int32Ptr(3) @@ -1645,7 +1674,18 @@ func Test_GetJob_AllProps(t *testing.T) { }, Status: v1.RadixBatchStatus{ JobStatuses: []v1.RadixBatchJobStatus{ - {Name: "job1", Phase: v1.BatchJobPhaseSucceeded, Message: "anymessage", CreationTime: &creationTime, StartTime: &startTime, EndTime: &endTime}, + { + Name: "job1", + Phase: v1.BatchJobPhaseSucceeded, + Message: "anymessage", + CreationTime: &creationTime, + StartTime: &startTime, + EndTime: &endTime, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + CreationTime: &podCreationTime, + Phase: v1.PodSucceeded, + }}, + }, }, }, }, @@ -1676,6 +1716,10 @@ func Test_GetJob_AllProps(t *testing.T) { }, Node: &deploymentModels.Node{Gpu: "gpu1", GpuCount: "2"}, DeploymentName: anyDeployment, + ReplicaList: []deploymentModels.ReplicaSummary{{ + Created: radixutils.FormatTimestamp(podCreationTime.Time), + Status: deploymentModels.ReplicaStatus{Status: "Succeeded"}, + }}, }, actual) // Test job2 props - override props from RD jobComponent @@ -1814,15 +1858,16 @@ func Test_GetBatch_JobList(t *testing.T) { Labels: labels.Merge(labels.ForApplicationName(anyAppName), labels.ForComponentName(anyJobName), labels.ForBatchType(kube.RadixBatchTypeBatch)), }, Spec: v1.RadixBatchSpec{ - Jobs: []v1.RadixBatchJob{{Name: "no1"}, {Name: "no2"}, {Name: "no3"}, {Name: "no4"}, {Name: "no5"}, {Name: "no6"}, {Name: "no7"}}}, + Jobs: []v1.RadixBatchJob{{Name: "no1"}, {Name: "no2"}, {Name: "no3"}, {Name: "no4"}, {Name: "no5"}, {Name: "no6"}, {Name: "no7"}, {Name: "no8"}}}, Status: v1.RadixBatchStatus{ JobStatuses: []v1.RadixBatchJobStatus{ {Name: "no2"}, {Name: "no3", Phase: v1.BatchJobPhaseWaiting}, {Name: "no4", Phase: v1.BatchJobPhaseActive}, - {Name: "no5", Phase: v1.BatchJobPhaseSucceeded}, - {Name: "no6", Phase: v1.BatchJobPhaseFailed}, - {Name: "no7", Phase: v1.BatchJobPhaseStopped}, + {Name: "no5", Phase: v1.BatchJobPhaseRunning}, + {Name: "no6", Phase: v1.BatchJobPhaseSucceeded}, + {Name: "no7", Phase: v1.BatchJobPhaseFailed}, + {Name: "no8", Phase: v1.BatchJobPhaseStopped}, {Name: "not-defined"}, }, }, @@ -1840,7 +1885,7 @@ func Test_GetBatch_JobList(t *testing.T) { var actual deploymentModels.ScheduledBatchSummary err = controllertest.GetResponseBody(response, &actual) require.NoError(t, err) - require.Len(t, actual.JobList, 7) + require.Len(t, actual.JobList, 8) type assertMapped struct { Name string Status string @@ -1852,10 +1897,11 @@ func Test_GetBatch_JobList(t *testing.T) { {Name: anyBatchName + "-no1", Status: jobSchedulerModels.Waiting.String()}, {Name: anyBatchName + "-no2", Status: jobSchedulerModels.Waiting.String()}, {Name: anyBatchName + "-no3", Status: jobSchedulerModels.Waiting.String()}, - {Name: anyBatchName + "-no4", Status: jobSchedulerModels.Running.String()}, - {Name: anyBatchName + "-no5", Status: jobSchedulerModels.Succeeded.String()}, - {Name: anyBatchName + "-no6", Status: jobSchedulerModels.Failed.String()}, - {Name: anyBatchName + "-no7", Status: jobSchedulerModels.Stopped.String()}, + {Name: anyBatchName + "-no4", Status: jobSchedulerModels.Active.String()}, + {Name: anyBatchName + "-no5", Status: jobSchedulerModels.Running.String()}, + {Name: anyBatchName + "-no6", Status: jobSchedulerModels.Succeeded.String()}, + {Name: anyBatchName + "-no7", Status: jobSchedulerModels.Failed.String()}, + {Name: anyBatchName + "-no8", Status: jobSchedulerModels.Stopped.String()}, } assert.ElementsMatch(t, expected, actualMapped) } @@ -1979,6 +2025,26 @@ func Test_GetBatches_Status(t *testing.T) { Labels: labels.Merge(labels.ForApplicationName(anyAppName), labels.ForComponentName(anyJobName), labels.ForBatchType(kube.RadixBatchTypeBatch)), }, Status: v1.RadixBatchStatus{ + JobStatuses: []v1.RadixBatchJobStatus{ + {Name: "j1"}, + { + Name: "j2", + Phase: v1.BatchJobPhaseActive, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + Phase: v1.PodRunning, + CreationTime: &metav1.Time{Time: time.Now()}, + StartTime: &metav1.Time{Time: time.Now()}, + }}, + }, + { + Name: "j3", + Phase: v1.BatchJobPhaseWaiting, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + Phase: v1.PodPending, + CreationTime: &metav1.Time{Time: time.Now()}, + }}, + }, + }, Condition: v1.RadixBatchCondition{Type: v1.BatchConditionTypeActive}, }, }, @@ -1988,7 +2054,29 @@ func Test_GetBatches_Status(t *testing.T) { Labels: labels.Merge(labels.ForApplicationName(anyAppName), labels.ForComponentName(anyJobName), labels.ForBatchType(kube.RadixBatchTypeBatch)), }, Status: v1.RadixBatchStatus{ - Condition: v1.RadixBatchCondition{Type: v1.BatchConditionTypeCompleted}, + JobStatuses: []v1.RadixBatchJobStatus{ + {Name: "j1"}, + { + Name: "j2", + Phase: v1.BatchJobPhaseRunning, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + Phase: v1.PodRunning, + CreationTime: &metav1.Time{Time: time.Now()}, + StartTime: &metav1.Time{Time: time.Now()}, + }}, + }, + { + Name: "j3", + Phase: v1.BatchJobPhaseSucceeded, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + Phase: v1.PodSucceeded, + CreationTime: &metav1.Time{Time: time.Now()}, + StartTime: &metav1.Time{Time: time.Now()}, + EndTime: &metav1.Time{Time: time.Now()}, + }}, + }, + }, + Condition: v1.RadixBatchCondition{Type: v1.BatchConditionTypeActive}, }, }, { @@ -1997,8 +2085,76 @@ func Test_GetBatches_Status(t *testing.T) { Labels: labels.Merge(labels.ForApplicationName(anyAppName), labels.ForComponentName(anyJobName), labels.ForBatchType(kube.RadixBatchTypeBatch)), }, Status: v1.RadixBatchStatus{ - Condition: v1.RadixBatchCondition{Type: v1.BatchConditionTypeCompleted}, - JobStatuses: []v1.RadixBatchJobStatus{{Name: "j1"}, {Name: "j2", Phase: v1.BatchJobPhaseFailed}}, + Condition: v1.RadixBatchCondition{Type: v1.BatchConditionTypeCompleted}, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "batch-job6", + Labels: labels.Merge(labels.ForApplicationName(anyAppName), labels.ForComponentName(anyJobName), labels.ForBatchType(kube.RadixBatchTypeBatch)), + }, + Status: v1.RadixBatchStatus{ + Condition: v1.RadixBatchCondition{Type: v1.BatchConditionTypeCompleted}, + JobStatuses: []v1.RadixBatchJobStatus{ + { + Name: "j1", + Phase: v1.BatchJobPhaseFailed, + EndTime: &metav1.Time{Time: time.Now()}, + Failed: 1, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + Phase: v1.PodFailed, + CreationTime: &metav1.Time{Time: time.Now()}, + StartTime: &metav1.Time{Time: time.Now()}, + EndTime: &metav1.Time{Time: time.Now()}, + }}, + }, + { + Name: "j2", + Phase: v1.BatchJobPhaseFailed, + EndTime: &metav1.Time{Time: time.Now()}, + Failed: 1, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + Phase: v1.PodFailed, + CreationTime: &metav1.Time{Time: time.Now()}, + StartTime: &metav1.Time{Time: time.Now()}, + EndTime: &metav1.Time{Time: time.Now()}, + }}, + }, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "batch-job7", + Labels: labels.Merge(labels.ForApplicationName(anyAppName), labels.ForComponentName(anyJobName), labels.ForBatchType(kube.RadixBatchTypeBatch)), + }, + Status: v1.RadixBatchStatus{ + Condition: v1.RadixBatchCondition{Type: v1.BatchConditionTypeCompleted}, + JobStatuses: []v1.RadixBatchJobStatus{ + { + Name: "j1", + Phase: v1.BatchJobPhaseFailed, + EndTime: &metav1.Time{Time: time.Now()}, + Failed: 1, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + Phase: v1.PodFailed, + CreationTime: &metav1.Time{Time: time.Now()}, + StartTime: &metav1.Time{Time: time.Now()}, + EndTime: &metav1.Time{Time: time.Now()}, + }}, + }, + { + Name: "j2", + Phase: v1.BatchJobPhaseSucceeded, + EndTime: &metav1.Time{Time: time.Now()}, + RadixBatchJobPodStatuses: []v1.RadixBatchJobPodStatus{{ + Phase: v1.PodSucceeded, + CreationTime: &metav1.Time{Time: time.Now()}, + StartTime: &metav1.Time{Time: time.Now()}, + EndTime: &metav1.Time{Time: time.Now()}, + }}, + }, + }, }, }, { @@ -2035,14 +2191,16 @@ func Test_GetBatches_Status(t *testing.T) { expected := []assertMapped{ {Name: "batch-job1", Status: jobSchedulerModels.Waiting.String()}, {Name: "batch-job2", Status: jobSchedulerModels.Waiting.String()}, - {Name: "batch-job3", Status: jobSchedulerModels.Running.String()}, - {Name: "batch-job4", Status: jobSchedulerModels.Succeeded.String()}, - {Name: "batch-job5", Status: jobSchedulerModels.Failed.String()}, + {Name: "batch-job3", Status: jobSchedulerModels.Active.String()}, + {Name: "batch-job4", Status: jobSchedulerModels.Running.String()}, + {Name: "batch-job5", Status: jobSchedulerModels.Succeeded.String()}, + {Name: "batch-job6", Status: jobSchedulerModels.Failed.String()}, + {Name: "batch-job7", Status: jobSchedulerModels.Succeeded.String()}, } assert.ElementsMatch(t, expected, actualMapped) } -func Test_GetBatches_JobListShouldBeEmpty(t *testing.T) { +func Test_GetBatches_JobListShouldHaveJobWithStatusWaiting(t *testing.T) { namespace := operatorutils.GetEnvironmentNamespace(anyAppName, anyEnvironment) // Setup @@ -2088,8 +2246,8 @@ func Test_GetBatches_JobListShouldBeEmpty(t *testing.T) { err = controllertest.GetResponseBody(response, &actual) require.NoError(t, err) require.Len(t, actual, 1) - assert.Len(t, actual[0].JobList, 0) - + assert.Len(t, actual[0].JobList, 1) + assert.Equal(t, string(v1.BatchJobPhaseWaiting), actual[0].JobList[0].Status) } func Test_StopJob(t *testing.T) { diff --git a/api/environments/environment_handler.go b/api/environments/environment_handler.go index 14427ed2..b06b09c3 100644 --- a/api/environments/environment_handler.go +++ b/api/environments/environment_handler.go @@ -203,8 +203,12 @@ func (eh EnvironmentHandler) GetEnvironment(ctx context.Context, appName, envNam if err != nil { return nil, err } + eventList, err := kubequery.GetEventsForEnvironment(ctx, eh.accounts.ServiceAccount.Client, appName, envName) + if err != nil { + return nil, err + } - env := apimodels.BuildEnvironment(rr, ra, re, rdList, rjList, deploymentList, componentPodList, hpaList, secretList, secretProviderClassList, eh.tlsValidator) + env := apimodels.BuildEnvironment(rr, ra, re, rdList, rjList, deploymentList, componentPodList, hpaList, secretList, secretProviderClassList, eventList, eh.tlsValidator) return env, nil } @@ -303,9 +307,9 @@ func (eh EnvironmentHandler) GetLogs(ctx context.Context, appName, envName, podN } // GetScheduledJobLogs handler for GetScheduledJobLogs -func (eh EnvironmentHandler) GetScheduledJobLogs(ctx context.Context, appName, envName, scheduledJobName string, sinceTime *time.Time, logLines *int64) (io.ReadCloser, error) { +func (eh EnvironmentHandler) GetScheduledJobLogs(ctx context.Context, appName, envName, scheduledJobName, replicaName string, sinceTime *time.Time, logLines *int64) (io.ReadCloser, error) { handler := pods.Init(eh.client) - return handler.HandleGetEnvironmentScheduledJobLog(ctx, appName, envName, scheduledJobName, "", sinceTime, logLines) + return handler.HandleGetEnvironmentScheduledJobLog(ctx, appName, envName, scheduledJobName, replicaName, "", sinceTime, logLines) } // GetAuxiliaryResourcePodLog handler for GetAuxiliaryResourcePodLog diff --git a/api/environments/job_handler.go b/api/environments/job_handler.go index 36909e45..ddb8262c 100644 --- a/api/environments/job_handler.go +++ b/api/environments/job_handler.go @@ -14,6 +14,7 @@ import ( "github.com/equinor/radix-api/api/utils" radixhttp "github.com/equinor/radix-common/net/http" radixutils "github.com/equinor/radix-common/utils" + "github.com/equinor/radix-common/utils/pointers" "github.com/equinor/radix-common/utils/slice" jobsSchedulerModels "github.com/equinor/radix-job-scheduler/models" jobSchedulerModels "github.com/equinor/radix-job-scheduler/models/common" @@ -22,7 +23,6 @@ import ( radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1" operatorUtils "github.com/equinor/radix-operator/pkg/apis/utils" radixLabels "github.com/equinor/radix-operator/pkg/apis/utils/labels" - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -47,8 +47,7 @@ func (eh EnvironmentHandler) getJobs(ctx context.Context, appName, envName, jobC if err != nil { return nil, err } - - return eh.getScheduledJobSummaryList(radixBatches, nil), nil + return eh.getScheduledJobSummaryList(radixBatches), nil } // GetJob Gets job by name @@ -136,11 +135,6 @@ func (eh EnvironmentHandler) getJob(ctx context.Context, appName, envName, jobCo return nil, jobNotFoundError(jobName) } - pods, err := eh.getPodsForBatchJob(ctx, appName, envName, batchName, batchJobName) - if err != nil { - return nil, err - } - var jobComponent *radixv1.RadixDeployJobComponent namespace := operatorUtils.GetEnvironmentNamespace(appName, envName) if rd, err := eh.accounts.UserAccount.RadixClient.RadixV1().RadixDeployments(namespace).Get(ctx, batch.Spec.RadixDeploymentJobRef.Name, metav1.GetOptions{}); err == nil { @@ -149,7 +143,7 @@ func (eh EnvironmentHandler) getJob(ctx context.Context, appName, envName, jobCo } } - jobSummary := eh.getScheduledJobSummary(batch, jobs[0], pods, jobComponent) + jobSummary := eh.getScheduledJobSummary(batch, jobs[0], jobComponent) return &jobSummary, nil } @@ -248,11 +242,7 @@ func (eh EnvironmentHandler) getBatch(ctx context.Context, appName, envName, job } batchSummary := eh.getScheduledBatchSummary(batch) - pods, err := eh.getPodsForBatch(ctx, appName, envName, batchName) - if err != nil { - return nil, err - } - batchSummary.JobList = eh.getScheduledJobSummaries(batch, pods) + batchSummary.JobList = eh.getScheduledJobSummaries(batch) return &batchSummary, nil } @@ -345,32 +335,6 @@ func (eh EnvironmentHandler) getRadixBatch(ctx context.Context, appName, envName return batch, nil } -func (eh EnvironmentHandler) getPodsForBatch(ctx context.Context, appName, envName, batchName string) ([]corev1.Pod, error) { - namespace := operatorUtils.GetEnvironmentNamespace(appName, envName) - selector := radixLabels.ForBatchName(batchName) - - return eh.getPodsWithLabelSelector(ctx, namespace, selector.String()) -} - -func (eh EnvironmentHandler) getPodsForBatchJob(ctx context.Context, appName, envName, batchName, jobName string) ([]corev1.Pod, error) { - namespace := operatorUtils.GetEnvironmentNamespace(appName, envName) - selector := radixLabels.Merge( - radixLabels.ForBatchName(batchName), - radixLabels.ForBatchJobName(jobName), - ) - - return eh.getPodsWithLabelSelector(ctx, namespace, selector.String()) -} - -func (eh EnvironmentHandler) getPodsWithLabelSelector(ctx context.Context, namespace, labelSelector string) ([]corev1.Pod, error) { - pods, err := eh.accounts.UserAccount.Client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{LabelSelector: labelSelector}) - if err != nil { - return nil, err - } - - return pods.Items, nil -} - func (eh EnvironmentHandler) getScheduledBatchSummaryList(batches []radixv1.RadixBatch) (summaries []deploymentModels.ScheduledBatchSummary) { for _, batch := range batches { summaries = append(summaries, eh.getScheduledBatchSummary(&batch)) @@ -385,6 +349,7 @@ func (eh EnvironmentHandler) getScheduledBatchSummary(batch *radixv1.RadixBatch) DeploymentName: batch.Spec.RadixDeploymentJobRef.Name, Status: getScheduledBatchStatus(batch).String(), TotalJobCount: len(batch.Spec.Jobs), + JobList: eh.getScheduledJobSummaries(batch), Created: radixutils.FormatTimestamp(batch.GetCreationTimestamp().Time), Started: radixutils.FormatTime(batch.Status.Condition.ActiveTime), Ended: radixutils.FormatTime(batch.Status.Condition.CompletionTime), @@ -413,38 +378,34 @@ func (eh EnvironmentHandler) getScheduledJobStatus(jobStatus *jobSchedulerV1Mode } } -func (eh EnvironmentHandler) getScheduledJobSummaryList(batches []radixv1.RadixBatch, pods []corev1.Pod) (summaries []deploymentModels.ScheduledJobSummary) { +func (eh EnvironmentHandler) getScheduledJobSummaryList(batches []radixv1.RadixBatch) (summaries []deploymentModels.ScheduledJobSummary) { for _, batch := range batches { - summaries = append(summaries, eh.getScheduledJobSummaries(&batch, pods)...) + summaries = append(summaries, eh.getScheduledJobSummaries(&batch)...) } return } -func (eh EnvironmentHandler) getScheduledJobSummaries(batch *radixv1.RadixBatch, pods []corev1.Pod) (summaries []deploymentModels.ScheduledJobSummary) { +func (eh EnvironmentHandler) getScheduledJobSummaries(batch *radixv1.RadixBatch) (summaries []deploymentModels.ScheduledJobSummary) { for _, job := range batch.Spec.Jobs { - summaries = append(summaries, eh.getScheduledJobSummary(batch, job, pods, nil)) + summaries = append(summaries, eh.getScheduledJobSummary(batch, job, nil)) } - return } -func (eh EnvironmentHandler) getScheduledJobSummary(batch *radixv1.RadixBatch, job radixv1.RadixBatchJob, pods []corev1.Pod, jobComponent *radixv1.RadixDeployJobComponent) deploymentModels.ScheduledJobSummary { +func (eh EnvironmentHandler) getScheduledJobSummary(batch *radixv1.RadixBatch, job radixv1.RadixBatchJob, jobComponent *radixv1.RadixDeployJobComponent) deploymentModels.ScheduledJobSummary { var batchName string if batch.GetLabels()[kube.RadixBatchTypeLabel] == string(kube.RadixBatchTypeBatch) { batchName = batch.GetName() } - jobPods := slice.FindAll(pods, func(pod corev1.Pod) bool { - return isPodForBatchJob(&pod, batch.Spec.RadixDeploymentJobRef.Job, batch.GetName(), job.Name) - }) summary := deploymentModels.ScheduledJobSummary{ Name: fmt.Sprintf("%s-%s", batch.GetName(), job.Name), DeploymentName: batch.Spec.RadixDeploymentJobRef.Name, BatchName: batchName, JobId: job.JobId, - ReplicaList: getReplicaSummariesForPods(jobPods), - Status: getScheduledJobStatus(job, "").String(), + ReplicaList: getReplicaSummariesForJob(batch, job), + Status: jobSchedulerModels.Waiting.String(), } if jobComponent != nil { @@ -474,52 +435,49 @@ func (eh EnvironmentHandler) getScheduledJobSummary(batch *radixv1.RadixBatch, j } } + stopJob := job.Stop != nil && *job.Stop + if statuses := slice.FindAll(batch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { return jobStatus.Name == job.Name }); len(statuses) == 1 { status := statuses[0] - summary.Status = getScheduledJobStatus(job, status.Phase).String() + summary.Status = getScheduledJobStatus(status, stopJob).String() summary.Created = radixutils.FormatTime(status.CreationTime) summary.Started = radixutils.FormatTime(status.StartTime) summary.Ended = radixutils.FormatTime(status.EndTime) summary.Message = status.Message summary.FailedCount = status.Failed summary.Restart = status.Restart + } else if len(statuses) == 0 && stopJob { + summary.Status = jobSchedulerModels.Stopping.String() } - return summary } -func isPodForBatchJob(pod *corev1.Pod, jobComponentName, batchName, batchJobName string) bool { - return labels. - SelectorFromSet( - radixLabels.Merge( - radixLabels.ForComponentName(jobComponentName), - radixLabels.ForBatchName(batchName), - radixLabels.ForBatchJobName(batchJobName), - )). - Matches(labels.Set(pod.GetLabels())) -} - func getScheduledBatchStatus(batch *radixv1.RadixBatch) (status jobSchedulerModels.ProgressStatus) { - status = jobSchedulerModels.Waiting switch { case batch.Status.Condition.Type == radixv1.BatchConditionTypeActive: - status = jobSchedulerModels.Running - case batch.Status.Condition.Type == radixv1.BatchConditionTypeCompleted: - status = jobSchedulerModels.Succeeded if slice.Any(batch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { + return jobStatus.Phase == radixv1.BatchJobPhaseRunning + }) { + return jobSchedulerModels.Running + } + return jobSchedulerModels.Active + case batch.Status.Condition.Type == radixv1.BatchConditionTypeCompleted: + if len(batch.Status.JobStatuses) > 0 && slice.All(batch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { return jobStatus.Phase == radixv1.BatchJobPhaseFailed }) { - status = jobSchedulerModels.Failed + return jobSchedulerModels.Failed } + return jobSchedulerModels.Succeeded } - return + return jobSchedulerModels.Waiting } -func getScheduledJobStatus(job radixv1.RadixBatchJob, phase radixv1.RadixBatchJobPhase) (status jobSchedulerModels.ProgressStatus) { +func getScheduledJobStatus(jobStatus radixv1.RadixBatchJobStatus, stopJob bool) (status jobSchedulerModels.ProgressStatus) { status = jobSchedulerModels.Waiting - - switch phase { + switch jobStatus.Phase { case radixv1.BatchJobPhaseActive: + status = jobSchedulerModels.Active + case radixv1.BatchJobPhaseRunning: status = jobSchedulerModels.Running case radixv1.BatchJobPhaseSucceeded: status = jobSchedulerModels.Succeeded @@ -527,26 +485,70 @@ func getScheduledJobStatus(job radixv1.RadixBatchJob, phase radixv1.RadixBatchJo status = jobSchedulerModels.Failed case radixv1.BatchJobPhaseStopped: status = jobSchedulerModels.Stopped + case radixv1.BatchJobPhaseWaiting: + status = jobSchedulerModels.Waiting } - - var stop bool - if job.Stop != nil { - stop = *job.Stop + if stopJob && (status == jobSchedulerModels.Waiting || status == jobSchedulerModels.Active || status == jobSchedulerModels.Running) { + return jobSchedulerModels.Stopping } + if len(jobStatus.RadixBatchJobPodStatuses) > 0 && slice.All(jobStatus.RadixBatchJobPodStatuses, func(jobPodStatus radixv1.RadixBatchJobPodStatus) bool { + return jobPodStatus.Phase == radixv1.PodFailed + }) { + return jobSchedulerModels.Failed + } + return status +} - if stop && (status == jobSchedulerModels.Waiting || status == jobSchedulerModels.Running) { - status = jobSchedulerModels.Stopping +func getReplicaSummariesForJob(radixBatch *radixv1.RadixBatch, job radixv1.RadixBatchJob) []deploymentModels.ReplicaSummary { + if jobStatus, ok := slice.FindFirst(radixBatch.Status.JobStatuses, func(jobStatus radixv1.RadixBatchJobStatus) bool { + return jobStatus.Name == job.Name + }); ok { + return slice.Reduce(jobStatus.RadixBatchJobPodStatuses, make([]deploymentModels.ReplicaSummary, 0), + func(acc []deploymentModels.ReplicaSummary, status radixv1.RadixBatchJobPodStatus) []deploymentModels.ReplicaSummary { + return append(acc, getReplicaSummaryByJobPodStatus(status, job)) + }) } + return nil +} - return +func getReplicaSummaryByJobPodStatus(status radixv1.RadixBatchJobPodStatus, job radixv1.RadixBatchJob) deploymentModels.ReplicaSummary { + summary := deploymentModels.ReplicaSummary{ + Name: status.Name, + Created: radixutils.FormatTimestamp(status.CreationTime.Time), + RestartCount: status.RestartCount, + Image: status.Image, + ImageId: status.ImageID, + PodIndex: status.PodIndex, + Reason: status.Reason, + StatusMessage: status.Message, + ExitCode: status.ExitCode, + Status: getReplicaStatusByJobPodStatusPhase(status.Phase), + } + if status.StartTime != nil { + summary.StartTime = radixutils.FormatTimestamp(status.StartTime.Time) + } + if status.EndTime != nil { + summary.EndTime = radixutils.FormatTimestamp(status.EndTime.Time) + } + if job.Resources != nil { + summary.Resources = pointers.Ptr(deploymentModels.ConvertRadixResourceRequirements(*job.Resources)) + } + return summary } -func getReplicaSummariesForPods(jobPods []corev1.Pod) []deploymentModels.ReplicaSummary { - var replicaSummaries []deploymentModels.ReplicaSummary - for _, pod := range jobPods { - replicaSummaries = append(replicaSummaries, deploymentModels.GetReplicaSummary(pod)) +func getReplicaStatusByJobPodStatusPhase(statusPhase radixv1.RadixBatchJobPodPhase) deploymentModels.ReplicaStatus { + replicaStatus := deploymentModels.ReplicaStatus{} + switch statusPhase { + case radixv1.PodFailed: + replicaStatus.Status = "Failed" + case radixv1.PodRunning: + replicaStatus.Status = "Running" + case radixv1.PodSucceeded: + replicaStatus.Status = "Succeeded" + default: + replicaStatus.Status = "Pending" } - return replicaSummaries + return replicaStatus } // check if batch can be stopped diff --git a/api/kubequery/event_test.go b/api/kubequery/event_test.go new file mode 100644 index 00000000..893c7d42 --- /dev/null +++ b/api/kubequery/event_test.go @@ -0,0 +1,29 @@ +package kubequery + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubefake "k8s.io/client-go/kubernetes/fake" +) + +func Test_GetEvents(t *testing.T) { + matched := corev1.Event{ObjectMeta: metav1.ObjectMeta{Name: "event1", Namespace: "app1-dev"}} + unmatched := corev1.Event{ObjectMeta: metav1.ObjectMeta{Name: "event2", Namespace: "app2-dev"}} + client := kubefake.NewSimpleClientset(&matched, &unmatched) + + // Get existing events + actual, err := GetEventsForEnvironment(context.Background(), client, "app1", "dev") + require.NoError(t, err) + assert.Len(t, actual, 1) + assert.Equal(t, matched.GetName(), actual[0].GetName()) + + // Get non-existing events (wrong namespace) + actual, err = GetEventsForEnvironment(context.Background(), client, "app3", "dev") + require.NoError(t, err) + assert.Len(t, actual, 0) +} diff --git a/api/kubequery/events.go b/api/kubequery/events.go new file mode 100644 index 00000000..7059efbd --- /dev/null +++ b/api/kubequery/events.go @@ -0,0 +1,20 @@ +package kubequery + +import ( + "context" + + "github.com/equinor/radix-operator/pkg/apis/utils" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +// GetEventsForEnvironment returns all Events for the specified application and environment. +func GetEventsForEnvironment(ctx context.Context, client kubernetes.Interface, appName, envName string) ([]corev1.Event, error) { + ns := utils.GetEnvironmentNamespace(appName, envName) + eventList, err := client.CoreV1().Events(ns).List(ctx, v1.ListOptions{}) + if err != nil { + return nil, err + } + return eventList.Items, nil +} diff --git a/api/models/auxiliary_resource.go b/api/models/auxiliary_resource.go index 554ed419..19a3b08b 100644 --- a/api/models/auxiliary_resource.go +++ b/api/models/auxiliary_resource.go @@ -10,21 +10,21 @@ import ( corev1 "k8s.io/api/core/v1" ) -func getAuxiliaryResources(appName string, component radixv1.RadixCommonDeployComponent, deploymentList []appsv1.Deployment, podList []corev1.Pod) deploymentModels.AuxiliaryResource { +func getAuxiliaryResources(appName string, component radixv1.RadixCommonDeployComponent, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) deploymentModels.AuxiliaryResource { var auxResource deploymentModels.AuxiliaryResource if auth := component.GetAuthentication(); component.IsPublic() && auth != nil && auth.OAuth2 != nil { - auxResource.OAuth2 = getOAuth2AuxiliaryResource(appName, component.GetName(), deploymentList, podList) + auxResource.OAuth2 = getOAuth2AuxiliaryResource(appName, component.GetName(), deploymentList, podList, eventWarnings) } return auxResource } -func getOAuth2AuxiliaryResource(appName, componentName string, deploymentList []appsv1.Deployment, podList []corev1.Pod) *deploymentModels.OAuth2AuxiliaryResource { +func getOAuth2AuxiliaryResource(appName, componentName string, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) *deploymentModels.OAuth2AuxiliaryResource { return &deploymentModels.OAuth2AuxiliaryResource{ - Deployment: getAuxiliaryResourceDeployment(appName, componentName, operatordefaults.OAuthProxyAuxiliaryComponentType, deploymentList, podList), + Deployment: getAuxiliaryResourceDeployment(appName, componentName, operatordefaults.OAuthProxyAuxiliaryComponentType, deploymentList, podList, eventWarnings), } } -func getAuxiliaryResourceDeployment(appName, componentName, auxType string, deploymentList []appsv1.Deployment, podList []corev1.Pod) deploymentModels.AuxiliaryResourceDeployment { +func getAuxiliaryResourceDeployment(appName, componentName, auxType string, deploymentList []appsv1.Deployment, podList []corev1.Pod, eventWarnings map[string]string) deploymentModels.AuxiliaryResourceDeployment { var auxResourceDeployment deploymentModels.AuxiliaryResourceDeployment auxDeployments := slice.FindAll(deploymentList, predicate.IsDeploymentForAuxComponent(appName, componentName, auxType)) if len(auxDeployments) == 0 { @@ -33,7 +33,7 @@ func getAuxiliaryResourceDeployment(appName, componentName, auxType string, depl } deployment := auxDeployments[0] auxPods := slice.FindAll(podList, predicate.IsPodForAuxComponent(appName, componentName, auxType)) - auxResourceDeployment.ReplicaList = BuildReplicaSummaryList(auxPods) + auxResourceDeployment.ReplicaList = BuildReplicaSummaryList(auxPods, eventWarnings) auxResourceDeployment.Status = deploymentModels.ComponentStatusFromDeployment(&deployment).String() return auxResourceDeployment } diff --git a/api/models/component.go b/api/models/component.go index 4cd53c64..22cae155 100644 --- a/api/models/component.go +++ b/api/models/component.go @@ -5,6 +5,7 @@ import ( deploymentModels "github.com/equinor/radix-api/api/deployments/models" "github.com/equinor/radix-api/api/utils" + "github.com/equinor/radix-api/api/utils/event" "github.com/equinor/radix-api/api/utils/predicate" "github.com/equinor/radix-api/api/utils/tlsvalidation" commonutils "github.com/equinor/radix-common/utils" @@ -20,22 +21,25 @@ import ( ) // BuildComponents builds a list of Component models. -func BuildComponents(ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, tlsValidator tlsvalidation.Validator) []*deploymentModels.Component { +func BuildComponents(ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, + hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, eventList []corev1.Event, + tlsValidator tlsvalidation.Validator) []*deploymentModels.Component { + lastEventWarnings := event.ConvertToEventWarnings(eventList) var components []*deploymentModels.Component - for _, component := range rd.Spec.Components { - components = append(components, buildComponent(&component, ra, rd, deploymentList, podList, hpaList, secretList, tlsValidator)) + components = append(components, buildComponent(&component, ra, rd, deploymentList, podList, hpaList, secretList, lastEventWarnings, tlsValidator)) } for _, job := range rd.Spec.Jobs { - components = append(components, buildComponent(&job, ra, rd, deploymentList, podList, hpaList, secretList, tlsValidator)) + components = append(components, buildComponent(&job, ra, rd, deploymentList, podList, hpaList, secretList, lastEventWarnings, tlsValidator)) } return components } -func buildComponent(radixComponent radixv1.RadixCommonDeployComponent, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, tlsValidator tlsvalidation.Validator) *deploymentModels.Component { - +func buildComponent(radixComponent radixv1.RadixCommonDeployComponent, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, + deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, + secretList []corev1.Secret, lastEventWarnings map[string]string, tlsValidator tlsvalidation.Validator) *deploymentModels.Component { builder := deploymentModels.NewComponentBuilder(). WithComponent(radixComponent). WithStatus(deploymentModels.ConsistentComponent). @@ -43,13 +47,12 @@ func buildComponent(radixComponent radixv1.RadixCommonDeployComponent, ra *radix WithExternalDNS(getComponentExternalDNS(radixComponent, secretList, tlsValidator)) componentPods := slice.FindAll(podList, predicate.IsPodForComponent(ra.Name, radixComponent.GetName())) - if rd.Status.ActiveTo.IsZero() { builder.WithPodNames(slice.Map(componentPods, func(pod corev1.Pod) string { return pod.Name })) builder.WithRadixEnvironmentVariables(getRadixEnvironmentVariables(componentPods)) - builder.WithReplicaSummaryList(BuildReplicaSummaryList(componentPods)) + builder.WithReplicaSummaryList(BuildReplicaSummaryList(componentPods, lastEventWarnings)) builder.WithStatus(getComponentStatus(radixComponent, ra, rd, componentPods)) - builder.WithAuxiliaryResource(getAuxiliaryResources(ra.Name, radixComponent, deploymentList, podList)) + builder.WithAuxiliaryResource(getAuxiliaryResources(ra.Name, radixComponent, deploymentList, podList, lastEventWarnings)) } // TODO: Use radixComponent.GetType() instead? diff --git a/api/models/deployment.go b/api/models/deployment.go index f1a47c61..ab2c88b6 100644 --- a/api/models/deployment.go +++ b/api/models/deployment.go @@ -12,13 +12,15 @@ import ( ) // BuildDeployment builds a Deployment model. -func BuildDeployment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, tlsValidator tlsvalidation.Validator, rjList []radixv1.RadixJob) *deploymentModels.Deployment { - components := BuildComponents(ra, rd, deploymentList, podList, hpaList, secretList, tlsValidator) +func BuildDeployment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplication, rd *radixv1.RadixDeployment, deploymentList []appsv1.Deployment, + podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, eventList []corev1.Event, + tlsValidator tlsvalidation.Validator, rjList []radixv1.RadixJob) *deploymentModels.Deployment { + components := BuildComponents(ra, rd, deploymentList, podList, hpaList, secretList, eventList, tlsValidator) // The only error that can be returned from DeploymentBuilder is related to errors from github.com/imdario/mergo // This type of error will only happen if incorrect objects (e.g. incompatible structs) are sent as arguments to mergo, // and we should consider to panic the error in the code calling merge. - // For now we will panic the error here. + // It will currently panic the error here. radixJob, _ := slice.FindFirst(rjList, func(radixJob radixv1.RadixJob) bool { return radixJob.GetName() == rd.GetLabels()[kube.RadixJobNameLabel] }) diff --git a/api/models/environment.go b/api/models/environment.go index 9a0853bb..6bf1bf7d 100644 --- a/api/models/environment.go +++ b/api/models/environment.go @@ -14,7 +14,10 @@ import ( ) // BuildEnvironment builds and Environment model. -func BuildEnvironment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplication, re *radixv1.RadixEnvironment, rdList []radixv1.RadixDeployment, rjList []radixv1.RadixJob, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, secretList []corev1.Secret, secretProviderClassList []secretsstorev1.SecretProviderClass, tlsValidator tlsvalidation.Validator) *environmentModels.Environment { +func BuildEnvironment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplication, re *radixv1.RadixEnvironment, rdList []radixv1.RadixDeployment, + rjList []radixv1.RadixJob, deploymentList []appsv1.Deployment, podList []corev1.Pod, hpaList []autoscalingv2.HorizontalPodAutoscaler, + secretList []corev1.Secret, secretProviderClassList []secretsstorev1.SecretProviderClass, eventList []corev1.Event, + tlsValidator tlsvalidation.Validator) *environmentModels.Environment { var buildFromBranch string var activeDeployment *deploymentModels.Deployment var secrets []secretModels.Secret @@ -24,7 +27,7 @@ func BuildEnvironment(rr *radixv1.RadixRegistration, ra *radixv1.RadixApplicatio } if activeRd, ok := slice.FindFirst(rdList, isActiveDeploymentForAppAndEnv(ra.Name, re.Spec.EnvName)); ok { - activeDeployment = BuildDeployment(rr, ra, &activeRd, deploymentList, podList, hpaList, secretList, tlsValidator, rjList) + activeDeployment = BuildDeployment(rr, ra, &activeRd, deploymentList, podList, hpaList, secretList, eventList, tlsValidator, rjList) secrets = BuildSecrets(secretList, secretProviderClassList, &activeRd) } diff --git a/api/models/replica_summary.go b/api/models/replica_summary.go index ff9d38ff..2e49c3db 100644 --- a/api/models/replica_summary.go +++ b/api/models/replica_summary.go @@ -7,11 +7,8 @@ import ( ) // BuildReplicaSummaryList builds a list of ReplicaSummary models. -func BuildReplicaSummaryList(podList []corev1.Pod) []deploymentModels.ReplicaSummary { - return slice.Map(podList, BuildReplicaSummary) -} - -// BuildReplicaSummary builds a ReplicaSummary model. -func BuildReplicaSummary(pod corev1.Pod) deploymentModels.ReplicaSummary { - return deploymentModels.GetReplicaSummary(pod) +func BuildReplicaSummaryList(podList []corev1.Pod, lastEventWarnings map[string]string) []deploymentModels.ReplicaSummary { + return slice.Map(podList, func(pod corev1.Pod) deploymentModels.ReplicaSummary { + return deploymentModels.GetReplicaSummary(pod, lastEventWarnings[pod.GetName()]) + }) } diff --git a/api/pods/pod_handler.go b/api/pods/pod_handler.go index d0aab711..ca393a25 100644 --- a/api/pods/pod_handler.go +++ b/api/pods/pod_handler.go @@ -4,10 +4,12 @@ import ( "context" "fmt" "io" + "strings" "time" "github.com/equinor/radix-api/api/utils/labelselector" sortUtils "github.com/equinor/radix-api/api/utils/sort" + "github.com/equinor/radix-common/utils/slice" crdUtils "github.com/equinor/radix-operator/pkg/apis/utils" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,9 +40,9 @@ func (ph PodHandler) HandleGetEnvironmentPodLog(ctx context.Context, appName, en } // HandleGetEnvironmentScheduledJobLog Get logs from scheduled job in environment -func (ph PodHandler) HandleGetEnvironmentScheduledJobLog(ctx context.Context, appName, envName, scheduledJobName, containerName string, sinceTime *time.Time, logLines *int64) (io.ReadCloser, error) { +func (ph PodHandler) HandleGetEnvironmentScheduledJobLog(ctx context.Context, appName, envName, scheduledJobName, replicaName, containerName string, sinceTime *time.Time, logLines *int64) (io.ReadCloser, error) { envNs := crdUtils.GetEnvironmentNamespace(appName, envName) - return ph.getScheduledJobLog(ctx, envNs, scheduledJobName, containerName, sinceTime, logLines) + return ph.getScheduledJobLog(ctx, envNs, scheduledJobName, replicaName, containerName, sinceTime, logLines) } // HandleGetEnvironmentAuxiliaryResourcePodLog Get logs from auxiliary resource pod in environment @@ -67,7 +69,7 @@ func (ph PodHandler) getPodLog(ctx context.Context, namespace, podName, containe return ph.getPodLogFor(ctx, pod, containerName, sinceTime, logLines, previousLog) } -func (ph PodHandler) getScheduledJobLog(ctx context.Context, namespace, scheduledJobName, containerName string, sinceTime *time.Time, logLines *int64) (io.ReadCloser, error) { +func (ph PodHandler) getScheduledJobLog(ctx context.Context, namespace, scheduledJobName, replicaName, containerName string, sinceTime *time.Time, logLines *int64) (io.ReadCloser, error) { pods, err := ph.client.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{ LabelSelector: fmt.Sprintf("job-name=%s", scheduledJobName), }) @@ -77,10 +79,28 @@ func (ph PodHandler) getScheduledJobLog(ctx context.Context, namespace, schedule if len(pods.Items) == 0 { return nil, PodNotFoundError(scheduledJobName) } + if pod, ok := getPod(pods, replicaName); ok { + return ph.getPodLogFor(ctx, pod, containerName, sinceTime, logLines, false) + } + podNameForError := scheduledJobName + if len(replicaName) > 0 { + podNameForError = fmt.Sprintf("%s/%s", podNameForError, replicaName) + } + return nil, PodNotFoundError(podNameForError) +} - sortUtils.Pods(pods.Items, sortUtils.ByPodCreationTimestamp, sortUtils.Descending) - pod := &pods.Items[0] - return ph.getPodLogFor(ctx, pod, containerName, sinceTime, logLines, false) +func getPod(pods *corev1.PodList, replicaName string) (*corev1.Pod, bool) { + if len(pods.Items) == 0 { + return nil, false + } + if len(replicaName) == 0 { + sortUtils.Pods(pods.Items, sortUtils.ByPodCreationTimestamp, sortUtils.Descending) + return &pods.Items[0], true + } + pod, ok := slice.FindFirst(pods.Items, func(pod corev1.Pod) bool { + return strings.EqualFold(pod.GetName(), replicaName) + }) + return &pod, ok } func (ph PodHandler) getPodLogFor(ctx context.Context, pod *corev1.Pod, containerName string, sinceTime *time.Time, logLines *int64, previousLog bool) (io.ReadCloser, error) { diff --git a/api/utils/event/event.go b/api/utils/event/event.go new file mode 100644 index 00000000..f21cffc4 --- /dev/null +++ b/api/utils/event/event.go @@ -0,0 +1,24 @@ +package event + +import ( + "sort" + "strings" + + "github.com/equinor/radix-common/utils/slice" + corev1 "k8s.io/api/core/v1" +) + +type LastEventWarnings map[string]string + +// ConvertToEventWarnings converts Kubernetes Events to EventWarning +func ConvertToEventWarnings(events []corev1.Event) LastEventWarnings { + sort.Slice(events, func(i, j int) bool { + return events[i].CreationTimestamp.Before(&events[j].CreationTimestamp) + }) + return slice.Reduce(events, make(LastEventWarnings), func(acc LastEventWarnings, event corev1.Event) LastEventWarnings { + if strings.EqualFold(event.Type, "Warning") && strings.EqualFold(event.InvolvedObject.Kind, "Pod") { + acc[event.InvolvedObject.Name] = event.Message + } + return acc + }) +} diff --git a/go.mod b/go.mod index 174428db..ac27ed7b 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.21 require ( github.com/cert-manager/cert-manager v1.14.2 github.com/equinor/radix-common v1.9.2 - github.com/equinor/radix-job-scheduler v1.9.0 - github.com/equinor/radix-operator v1.50.2 + github.com/equinor/radix-job-scheduler v1.9.1 + github.com/equinor/radix-operator v1.50.7 github.com/evanphx/json-patch/v5 v5.7.0 github.com/felixge/httpsnoop v1.0.4 github.com/go-swagger/go-swagger v0.30.5 @@ -88,7 +88,6 @@ require ( github.com/prometheus/statsd_exporter v0.22.7 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect diff --git a/go.sum b/go.sum index 52e4f88b..e2f78d59 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,6 @@ github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMr github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= github.com/cert-manager/cert-manager v1.14.2 h1:C/uci6yxiCRO04PWomBbSX+T4JT58FIIpDj5SZ6Ks6I= github.com/cert-manager/cert-manager v1.14.2/go.mod h1:pik7K6jXfgh++lfVJ/i1HzEnDluSUtTVLXSHikj8Lho= -github.com/cert-manager/cert-manager v1.14.2 h1:C/uci6yxiCRO04PWomBbSX+T4JT58FIIpDj5SZ6Ks6I= -github.com/cert-manager/cert-manager v1.14.2/go.mod h1:pik7K6jXfgh++lfVJ/i1HzEnDluSUtTVLXSHikj8Lho= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -91,10 +89,10 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/equinor/radix-common v1.9.2 h1:pOYN/mSAoPe6KO/Nvudfd5DUETbLv4nLTLzFPr62ADw= github.com/equinor/radix-common v1.9.2/go.mod h1:ekn86U68NT4ccSdt3GT+ukpiclzfuhr96a7zBJKv/jw= -github.com/equinor/radix-job-scheduler v1.9.0 h1:ceq46IZPf0VfPjYr6XSRy8WPid8fn5JR0bMovD/dAJ8= -github.com/equinor/radix-job-scheduler v1.9.0/go.mod h1:8220ViUF4YLOvl2z+VJQnMOaZ6jhPMfHYWdIOuUGPdo= -github.com/equinor/radix-operator v1.50.2 h1:xa5kPUN77QT6QJq9+DJzF/ic2c7AJcl4KKztky38sdc= -github.com/equinor/radix-operator v1.50.2/go.mod h1:rl8Tbor0wvKfol67nd/p72MRh0iDTClGeQ2HcMRG/LQ= +github.com/equinor/radix-job-scheduler v1.9.1 h1:B71xs8ucCG0yD6Zy2z7MVwaC0RknJOXe+EHEEfAN9AU= +github.com/equinor/radix-job-scheduler v1.9.1/go.mod h1:R2c3jrcKA7cLhHBY+3UDLZ6shEeA399JI19qMS/E4xg= +github.com/equinor/radix-operator v1.50.7 h1:/dV00+u3DhRrevdLKy/Xk7051KJ0exJW86Mcq/9Io0I= +github.com/equinor/radix-operator v1.50.7/go.mod h1:bLL8hVfdEUuucNRGUit33uBjUhuunpNWO5youmZz8e8= github.com/evanphx/json-patch v5.7.0+incompatible h1:vgGkfT/9f8zE6tvSCe74nfpAVDQ2tG6yudJd8LBksgI= github.com/evanphx/json-patch v5.7.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.7.0 h1:nJqP7uwL84RJInrohHfW0Fx3awjbm8qZeFv0nW9SYGc= @@ -234,8 +232,6 @@ github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWS github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1 h1:6UKoz5ujsI55KNpsJH3UwCq3T8kKbZwNZBNPuTTje8U= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -247,13 +243,9 @@ github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iP github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= -github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= -github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= @@ -368,8 +360,6 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo= github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= @@ -383,8 +373,6 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= @@ -397,8 +385,6 @@ github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -407,7 +393,6 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -471,8 +456,6 @@ golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EH golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= -golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -603,7 +586,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -745,12 +727,6 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1: google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917 h1:nz5NESFLZbJGPFxDT/HCn+V1mZ8JGNoY4nUpmW/Y2eg= -google.golang.org/genproto v0.0.0-20240102182953-50ed04b92917/go.mod h1:pZqR+glSb11aJ+JQcczCvgf47+duRuzNSKqE8YAQnV0= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM= -google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917 h1:6G8oQ016D88m1xAKljMlBOOGWDZkes4kMhgGFlf8WcQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240102182953-50ed04b92917/go.mod h1:xtjpI3tXFPP051KaWnhvxkiubL/6dJ18vLVf7q2pTOU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -782,8 +758,6 @@ google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -828,10 +802,6 @@ k8s.io/kube-openapi v0.0.0-20240103051144-eec4567ac022 h1:avRdiaB03v88Mfvum2S3BB k8s.io/kube-openapi v0.0.0-20240103051144-eec4567ac022/go.mod h1:sIV51WBTkZrlGOJMCDZDA1IaPBUDTulPpD4y7oe038k= k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -k8s.io/kube-openapi v0.0.0-20240103051144-eec4567ac022 h1:avRdiaB03v88Mfvum2S3BBwkNuTlmuar4LlfO9Hajko= -k8s.io/kube-openapi v0.0.0-20240103051144-eec4567ac022/go.mod h1:sIV51WBTkZrlGOJMCDZDA1IaPBUDTulPpD4y7oe038k= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e h1:eQ/4ljkx21sObifjzXwlPKpdGLrCfRziVtos3ofG/sQ= -k8s.io/utils v0.0.0-20240102154912-e7106e64919e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= knative.dev/pkg v0.0.0-20231219072704-d513e487961e h1:br9VUyN8M4ZUaWsmKifLg5lIAy6JmNw2MdeHd6wgp9g= knative.dev/pkg v0.0.0-20231219072704-d513e487961e/go.mod h1:YWJGsIxySXQehfkslagVEpJJwHgSScUc21+KpEgBXcY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= @@ -841,8 +811,6 @@ sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigw sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= sigs.k8s.io/gateway-api v1.0.0 h1:iPTStSv41+d9p0xFydll6d7f7MOBGuqXM6p2/zVYMAs= sigs.k8s.io/gateway-api v1.0.0/go.mod h1:4cUgr0Lnp5FZ0Cdq8FdRwCvpiWws7LVhLHGIudLlf4c= -sigs.k8s.io/gateway-api v1.0.0 h1:iPTStSv41+d9p0xFydll6d7f7MOBGuqXM6p2/zVYMAs= -sigs.k8s.io/gateway-api v1.0.0/go.mod h1:4cUgr0Lnp5FZ0Cdq8FdRwCvpiWws7LVhLHGIudLlf4c= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= sigs.k8s.io/secrets-store-csi-driver v1.4.0 h1:R9JVcKOs11fEuiOLlH1BWMeyb6WYzvElRVkq1BWJkr4= diff --git a/swaggerui/html/swagger.json b/swaggerui/html/swagger.json index 821ff0d0..df0324f6 100644 --- a/swaggerui/html/swagger.json +++ b/swaggerui/html/swagger.json @@ -3546,6 +3546,12 @@ "in": "path", "required": true }, + { + "type": "string", + "description": "Name of the job replica", + "name": "replicaName", + "in": "query" + }, { "type": "string", "format": "date-time", @@ -7114,11 +7120,13 @@ ], "properties": { "status": { - "description": "Status of the container\nPending = Container in Waiting state and the reason is ContainerCreating\nFailing = Container in Waiting state and the reason is anything else but ContainerCreating\nRunning = Container in Running state\nTerminated = Container in Terminated state", + "description": "Status of the container\nPending = Container in Waiting state and the reason is ContainerCreating\nFailed = Container is failed\nFailing = Container is failed\nRunning = Container in Running state\nSucceeded = Container in Succeeded state\nTerminated = Container in Terminated state", "type": "string", "enum": [ "Pending", + "Succeeded", "Failing", + "Failed", "Running", "Terminated", "Starting" @@ -7148,6 +7156,18 @@ "x-go-name": "Created", "example": "2006-01-02T15:04:05Z" }, + "endTime": { + "description": "The time at which the batch job's pod finishedAt.", + "type": "string", + "x-go-name": "EndTime", + "example": "2006-01-02T15:04:05Z" + }, + "exitCode": { + "description": "Exit status from the last termination of the container", + "type": "integer", + "format": "int32", + "x-go-name": "ExitCode" + }, "image": { "description": "The image the container is running.", "type": "string", @@ -7166,6 +7186,17 @@ "x-go-name": "Name", "example": "server-78fc8857c4-hm76l" }, + "podIndex": { + "description": "The index of the pod in the re-starts", + "type": "integer", + "format": "int64", + "x-go-name": "PodIndex" + }, + "reason": { + "description": "A brief CamelCase message indicating details about why the job is in this phase", + "type": "string", + "x-go-name": "Reason" + }, "replicaStatus": { "$ref": "#/definitions/ReplicaStatus" }, @@ -7178,6 +7209,12 @@ "format": "int32", "x-go-name": "RestartCount" }, + "startTime": { + "description": "The time at which the batch job's pod startedAt", + "type": "string", + "x-go-name": "StartTime", + "example": "2006-01-02T15:04:05Z" + }, "statusMessage": { "description": "StatusMessage provides message describing the status of a component container inside a pod", "type": "string", @@ -7409,6 +7446,7 @@ "type": "string", "enum": [ "Running", + "Active", "Succeeded", "Failed", "Waiting",