Skip to content

Commit

Permalink
Merge pull request #545 from equinor/restart-pipeline-hjob
Browse files Browse the repository at this point in the history
Restart pipeline job manually
  • Loading branch information
satr authored Oct 17, 2023
2 parents 0dcba8d + e864286 commit 9bdd2c0
Show file tree
Hide file tree
Showing 8 changed files with 377 additions and 82 deletions.
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

0 comments on commit 9bdd2c0

Please sign in to comment.