Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restart pipeline job manually #545

Merged
merged 14 commits into from
Oct 17, 2023
53 changes: 53 additions & 0 deletions api/jobs/job_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ func (jc *jobController) GetRoutes() models.Routes {
Method: "POST",
HandlerFunc: StopApplicationJob,
},
models.Route{
Path: rootPath + "/jobs/{jobName}/rerun",
Method: "POST",
HandlerFunc: RerunApplicationJob,
},
models.Route{
Path: rootPath + "/jobs/{jobName}/pipelineruns",
Method: "GET",
Expand Down Expand Up @@ -228,6 +233,54 @@ func StopApplicationJob(accounts models.Accounts, w http.ResponseWriter, r *http
w.WriteHeader(http.StatusNoContent)
}

// RerunApplicationJob Reruns the pipeline job
func RerunApplicationJob(accounts models.Accounts, w http.ResponseWriter, r *http.Request) {
// swagger:operation POST /applications/{appName}/jobs/{jobName}/restart pipeline-job rerunApplicationJob
// ---
// summary: Reruns the pipeline job
// parameters:
// - name: appName
// in: path
// description: name of application
// type: string
// required: true
// - name: jobName
// in: path
// description: name of job
// 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 test group (Required if Impersonate-User is set)
// type: array
// items:
// type: string
// required: false
// responses:
// "204":
// description: "Job rerun ok"
// "401":
// description: "Unauthorized"
// "404":
// description: "Not found"
appName := mux.Vars(r)["appName"]
jobName := mux.Vars(r)["jobName"]
handler := Init(accounts, deployments.Init(accounts))
err := handler.RerunJob(r.Context(), appName, jobName)

if err != nil {
radixhttp.ErrorResponse(w, r, err)
return
}

w.WriteHeader(http.StatusNoContent)
}

// GetTektonPipelineRuns Get the Tekton pipeline runs overview
func GetTektonPipelineRuns(accounts models.Accounts, w http.ResponseWriter, r *http.Request) {
// swagger:operation GET /applications/{appName}/jobs/{jobName}/pipelineruns pipeline-job getTektonPipelineRuns
Expand Down
147 changes: 122 additions & 25 deletions api/jobs/job_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ import (
"testing"
"time"

secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake"

deployMock "github.com/equinor/radix-api/api/deployments/mock"
deploymentModels "github.com/equinor/radix-api/api/deployments/models"
jobModels "github.com/equinor/radix-api/api/jobs/models"
"github.com/equinor/radix-api/models"
radixmodels "github.com/equinor/radix-common/models"
radixutils "github.com/equinor/radix-common/utils"
v1 "github.com/equinor/radix-operator/pkg/apis/radix/v1"
radixv1 "github.com/equinor/radix-operator/pkg/apis/radix/v1"
"github.com/equinor/radix-operator/pkg/apis/utils"
"github.com/equinor/radix-operator/pkg/apis/utils/slice"
radixfake "github.com/equinor/radix-operator/pkg/client/clientset/versioned/fake"
Expand All @@ -23,6 +21,7 @@ import (
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubefake "k8s.io/client-go/kubernetes/fake"
secretproviderfake "sigs.k8s.io/secrets-store-csi-driver/pkg/client/clientset/versioned/fake"
)

type JobHandlerTestSuite struct {
Expand All @@ -47,50 +46,75 @@ type jobCreatedScenario struct {
type jobStatusScenario struct {
scenarioName string
jobName string
condition v1.RadixJobCondition
condition radixv1.RadixJobCondition
stop bool
expectedStatus string
}

type jobProperties struct {
name string
condition radixv1.RadixJobCondition
stop bool
}

type jobRerunScenario struct {
scenarioName string
existingJob *jobProperties
jobNameToRerun string
expectedError error
}

func TestRunJobHandlerTestSuite(t *testing.T) {
suite.Run(t, new(JobHandlerTestSuite))
}

func (s *JobHandlerTestSuite) SetupTest() {
s.setupTest()
}

func (s *JobHandlerTestSuite) setupTest() {
s.inKubeClient, s.inRadixClient, s.outKubeClient, s.outRadixClient, s.inSecretProviderClient, s.outSecretProviderClient = s.getUtils()
accounts := models.NewAccounts(s.inKubeClient, s.inRadixClient, s.inSecretProviderClient, nil, s.outKubeClient, s.outRadixClient, s.outSecretProviderClient, nil, "", radixmodels.Impersonation{})
s.accounts = accounts
}

func (s *JobHandlerTestSuite) getUtils() (inKubeClient *kubefake.Clientset, inRadixClient *radixfake.Clientset, outKubeClient *kubefake.Clientset, outRadixClient *radixfake.Clientset, inSecretProviderClient *secretproviderfake.Clientset, outSecretProviderClient *secretproviderfake.Clientset) {
inKubeClient, outKubeClient = kubefake.NewSimpleClientset(), kubefake.NewSimpleClientset()
inRadixClient, outRadixClient = radixfake.NewSimpleClientset(), radixfake.NewSimpleClientset()
inSecretProviderClient, outSecretProviderClient = secretproviderfake.NewSimpleClientset(), secretproviderfake.NewSimpleClientset()
return
}

func (s *JobHandlerTestSuite) Test_GetApplicationJob() {
jobName, appName, branch, commitId, pipeline, triggeredBy := "a_job", "an_app", "a_branch", "a_commitid", v1.BuildDeploy, "a_user"
jobName, appName, branch, commitId, pipeline, triggeredBy := "a_job", "an_app", "a_branch", "a_commitid", radixv1.BuildDeploy, "a_user"
started, ended := metav1.NewTime(time.Date(2020, 1, 1, 0, 0, 0, 0, time.Local)), metav1.NewTime(time.Date(2020, 1, 2, 0, 0, 0, 0, time.Local))
step1Name, step1Pod, step1Condition, step1Started, step1Ended, step1Components := "step1_name", "step1_pod", v1.JobRunning, metav1.Now(), metav1.NewTime(time.Now().Add(1*time.Hour)), []string{"step1_comp1", "step1_comp2"}
step1Name, step1Pod, step1Condition, step1Started, step1Ended, step1Components := "step1_name", "step1_pod", radixv1.JobRunning, metav1.Now(), metav1.NewTime(time.Now().Add(1*time.Hour)), []string{"step1_comp1", "step1_comp2"}
step2Name := "step2_name"

rj := &v1.RadixJob{
rj := &radixv1.RadixJob{
ObjectMeta: metav1.ObjectMeta{
Name: jobName,
Namespace: utils.GetAppNamespace(appName),
},
Spec: v1.RadixJobSpec{
Build: v1.RadixBuildSpec{
Spec: radixv1.RadixJobSpec{
Build: radixv1.RadixBuildSpec{
Branch: branch,
CommitID: commitId,
},
PipeLineType: pipeline,
TriggeredBy: triggeredBy,
},
Status: v1.RadixJobStatus{
Status: radixv1.RadixJobStatus{
Started: &started,
Ended: &ended,
Steps: []v1.RadixJobStep{
Steps: []radixv1.RadixJobStep{
{Name: step1Name, PodName: step1Pod, Condition: step1Condition, Started: &step1Started, Ended: &step1Ended, Components: step1Components},
{Name: step2Name},
},
},
}
s.outRadixClient.RadixV1().RadixJobs(rj.Namespace).Create(context.Background(), rj, metav1.CreateOptions{})
_, err := s.outRadixClient.RadixV1().RadixJobs(rj.Namespace).Create(context.Background(), rj, metav1.CreateOptions{})
s.NoError(err)

deploymentName := "a_deployment"
comp1Name, comp1Type, comp1Image := "comp1", "type1", "image1"
Expand Down Expand Up @@ -183,7 +207,7 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob_Created() {
dh := deployMock.NewMockDeployHandler(ctrl)
dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
h := Init(s.accounts, dh)
rj := v1.RadixJob{ObjectMeta: metav1.ObjectMeta{Name: scenario.jobName, Namespace: utils.GetAppNamespace(appName), CreationTimestamp: scenario.creationTimestamp}}
rj := radixv1.RadixJob{ObjectMeta: metav1.ObjectMeta{Name: scenario.jobName, Namespace: utils.GetAppNamespace(appName), CreationTimestamp: scenario.creationTimestamp}}
if scenario.jobStatusCreated != emptyTime {
rj.Status.Created = &scenario.jobStatusCreated
}
Expand All @@ -199,10 +223,10 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob_Created() {
func (s *JobHandlerTestSuite) Test_GetApplicationJob_Status() {
appName := "any_app"
scenarios := []jobStatusScenario{
{scenarioName: "status is set to condition when stop is false", jobName: "job1", condition: v1.JobFailed, stop: false, expectedStatus: jobModels.Failed.String()},
{scenarioName: "status is Stopping when stop is true and condition is not Stopped", jobName: "job2", condition: v1.JobRunning, stop: true, expectedStatus: jobModels.Stopping.String()},
{scenarioName: "status is Stopped when stop is true and condition is Stopped", jobName: "job3", condition: v1.JobStopped, stop: true, expectedStatus: jobModels.Stopped.String()},
{scenarioName: "status is JobStoppedNoChanges when there is no changes", jobName: "job4", condition: v1.JobStoppedNoChanges, stop: false, expectedStatus: jobModels.StoppedNoChanges.String()},
{scenarioName: "status is set to condition when stop is false", jobName: "job1", condition: radixv1.JobFailed, stop: false, expectedStatus: jobModels.Failed.String()},
{scenarioName: "status is Stopping when stop is true and condition is not Stopped", jobName: "job2", condition: radixv1.JobRunning, stop: true, expectedStatus: jobModels.Stopping.String()},
{scenarioName: "status is Stopped when stop is true and condition is Stopped", jobName: "job3", condition: radixv1.JobStopped, stop: true, expectedStatus: jobModels.Stopped.String()},
{scenarioName: "status is JobStoppedNoChanges when there is no changes", jobName: "job4", condition: radixv1.JobStoppedNoChanges, stop: false, expectedStatus: jobModels.StoppedNoChanges.String()},
{scenarioName: "status is Waiting when condition is empty", jobName: "job5", expectedStatus: jobModels.Waiting.String()},
}

Expand All @@ -214,10 +238,10 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob_Status() {
dh := deployMock.NewMockDeployHandler(ctrl)
dh.EXPECT().GetDeploymentsForPipelineJob(context.Background(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(1)
h := Init(s.accounts, dh)
rj := v1.RadixJob{
rj := radixv1.RadixJob{
ObjectMeta: metav1.ObjectMeta{Name: scenario.jobName, Namespace: utils.GetAppNamespace(appName)},
Spec: v1.RadixJobSpec{Stop: scenario.stop},
Status: v1.RadixJobStatus{Condition: scenario.condition},
Spec: radixv1.RadixJobSpec{Stop: scenario.stop},
Status: radixv1.RadixJobStatus{Condition: scenario.condition},
}

_, err := s.outRadixClient.RadixV1().RadixJobs(rj.Namespace).Create(context.Background(), &rj, metav1.CreateOptions{})
Expand All @@ -229,9 +253,82 @@ func (s *JobHandlerTestSuite) Test_GetApplicationJob_Status() {
}
}

func (s *JobHandlerTestSuite) getUtils() (inKubeClient *kubefake.Clientset, inRadixClient *radixfake.Clientset, outKubeClient *kubefake.Clientset, outRadixClient *radixfake.Clientset, inSecretProviderClient *secretproviderfake.Clientset, outSecretProviderClient *secretproviderfake.Clientset) {
inKubeClient, outKubeClient = kubefake.NewSimpleClientset(), kubefake.NewSimpleClientset()
inRadixClient, outRadixClient = radixfake.NewSimpleClientset(), radixfake.NewSimpleClientset()
inSecretProviderClient, outSecretProviderClient = secretproviderfake.NewSimpleClientset(), secretproviderfake.NewSimpleClientset()
return
func (s *JobHandlerTestSuite) TestJobHandler_RerunJob() {
appName := "anyApp"
namespace := utils.GetAppNamespace(appName)
tests := []jobRerunScenario{
{scenarioName: "existing failed job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobFailed}, jobNameToRerun: "job1", expectedError: nil},
{scenarioName: "existing stopped job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobStopped, stop: true}, jobNameToRerun: "job1", expectedError: nil},
{scenarioName: "existing running job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobRunning}, jobNameToRerun: "job1", expectedError: jobModels.JobHasInvalidConditionToRerunError(appName, "job1", radixv1.JobRunning)},
{scenarioName: "existing stopped-no-changes job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobStoppedNoChanges}, jobNameToRerun: "job1", expectedError: jobModels.JobHasInvalidConditionToRerunError(appName, "job1", radixv1.JobStoppedNoChanges)},
{scenarioName: "existing queued job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobQueued}, jobNameToRerun: "job1", expectedError: jobModels.JobHasInvalidConditionToRerunError(appName, "job1", radixv1.JobQueued)},
{scenarioName: "existing succeeded job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobSucceeded}, jobNameToRerun: "job1", expectedError: jobModels.JobHasInvalidConditionToRerunError(appName, "job1", radixv1.JobSucceeded)},
{scenarioName: "existing waiting job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobWaiting}, jobNameToRerun: "job1", expectedError: jobModels.JobHasInvalidConditionToRerunError(appName, "job1", radixv1.JobWaiting)},
{scenarioName: "not existing job", existingJob: nil, jobNameToRerun: "job1", expectedError: jobModels.PipelineNotFoundError(appName, "job1")},
}
for _, tt := range tests {
s.T().Run(tt.scenarioName, func(t *testing.T) {
s.setupTest()
ctrl := gomock.NewController(s.T())
defer ctrl.Finish()
dh := deployMock.NewMockDeployHandler(ctrl)
jh := s.getJobHandler(dh)
if tt.existingJob != nil {
_, err := s.accounts.UserAccount.RadixClient.RadixV1().RadixJobs(namespace).Create(context.Background(), &radixv1.RadixJob{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: tt.existingJob.name},
Spec: radixv1.RadixJobSpec{Stop: tt.existingJob.stop},
Status: radixv1.RadixJobStatus{Condition: tt.existingJob.condition},
}, metav1.CreateOptions{})
s.NoError(err)
}

err := jh.RerunJob(context.Background(), appName, tt.jobNameToRerun)
s.Equal(tt.expectedError, err)
})
}
}

func (s *JobHandlerTestSuite) TestJobHandler_StopJob() {
appName := "anyApp"
namespace := utils.GetAppNamespace(appName)
tests := []jobRerunScenario{
{scenarioName: "existing failed job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobFailed}, jobNameToRerun: "job1", expectedError: jobModels.JobHasInvalidConditionToStopError(appName, "job1", radixv1.JobFailed)},
{scenarioName: "existing stopped job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobStopped, stop: true}, jobNameToRerun: "job1", expectedError: jobModels.JobAlreadyRequestedToStopError(appName, "job1")},
{scenarioName: "existing running job with stop in spec", existingJob: &jobProperties{name: "job1", condition: radixv1.JobRunning, stop: true}, jobNameToRerun: "job1", expectedError: jobModels.JobAlreadyRequestedToStopError(appName, "job1")},
{scenarioName: "existing running job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobRunning}, jobNameToRerun: "job1", expectedError: nil},
{scenarioName: "existing stopped-no-changes job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobStoppedNoChanges}, jobNameToRerun: "job1", expectedError: jobModels.JobHasInvalidConditionToStopError(appName, "job1", radixv1.JobStoppedNoChanges)},
{scenarioName: "existing queued job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobQueued}, jobNameToRerun: "job1", expectedError: nil},
{scenarioName: "existing succeeded job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobSucceeded}, jobNameToRerun: "job1", expectedError: nil},
{scenarioName: "existing waiting job", existingJob: &jobProperties{name: "job1", condition: radixv1.JobWaiting}, jobNameToRerun: "job1", expectedError: nil},
{scenarioName: "not existing job", existingJob: nil, jobNameToRerun: "job1", expectedError: jobModels.PipelineNotFoundError(appName, "job1")},
}
for _, tt := range tests {
s.T().Run(tt.scenarioName, func(t *testing.T) {
s.setupTest()
ctrl := gomock.NewController(s.T())
defer ctrl.Finish()
dh := deployMock.NewMockDeployHandler(ctrl)
jh := s.getJobHandler(dh)
if tt.existingJob != nil {
_, err := s.accounts.UserAccount.RadixClient.RadixV1().RadixJobs(namespace).Create(context.Background(), &radixv1.RadixJob{
ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: tt.existingJob.name},
Spec: radixv1.RadixJobSpec{Stop: tt.existingJob.stop},
Status: radixv1.RadixJobStatus{Condition: tt.existingJob.condition},
}, metav1.CreateOptions{})
s.NoError(err)
}

err := jh.StopJob(context.Background(), appName, tt.jobNameToRerun)
s.Equal(tt.expectedError, err)
})
}
}

func (s *JobHandlerTestSuite) getJobHandler(dh *deployMock.MockDeployHandler) JobHandler {
return JobHandler{
accounts: s.accounts,
userAccount: s.accounts.UserAccount,
serviceAccount: s.accounts.ServiceAccount,
deploy: dh,
}
}
Loading