github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/dataprotection/action/action_job.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package action
    21  
    22  import (
    23  	"fmt"
    24  
    25  	batchv1 "k8s.io/api/batch/v1"
    26  	corev1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	ref "k8s.io/client-go/tools/reference"
    29  	"sigs.k8s.io/controller-runtime/pkg/client"
    30  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    31  
    32  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    33  	ctrlutil "github.com/1aal/kubeblocks/pkg/controllerutil"
    34  	"github.com/1aal/kubeblocks/pkg/dataprotection/types"
    35  	"github.com/1aal/kubeblocks/pkg/dataprotection/utils"
    36  )
    37  
    38  // JobAction is an action that creates a batch job.
    39  type JobAction struct {
    40  	// Name is the Name of the action.
    41  	Name string
    42  
    43  	// Owner is the owner of the job.
    44  	Owner client.Object
    45  
    46  	// ObjectMeta is the metadata of the job.
    47  	ObjectMeta metav1.ObjectMeta
    48  
    49  	// PodSpec is the
    50  	PodSpec *corev1.PodSpec
    51  
    52  	// BackOffLimit is the number of retries before considering a JobAction as failed.
    53  	BackOffLimit *int32
    54  }
    55  
    56  func (j *JobAction) GetName() string {
    57  	return j.Name
    58  }
    59  
    60  func (j *JobAction) Type() dpv1alpha1.ActionType {
    61  	return dpv1alpha1.ActionTypeJob
    62  }
    63  
    64  func (j *JobAction) Execute(ctx Context) (*dpv1alpha1.ActionStatus, error) {
    65  	sb := newStatusBuilder(j)
    66  	handleErr := func(err error) (*dpv1alpha1.ActionStatus, error) {
    67  		return sb.withErr(err).build(), err
    68  	}
    69  
    70  	if err := j.validate(); err != nil {
    71  		return handleErr(err)
    72  	}
    73  
    74  	key := client.ObjectKey{
    75  		Namespace: j.ObjectMeta.Namespace,
    76  		Name:      j.ObjectMeta.Name,
    77  	}
    78  	original := batchv1.Job{}
    79  	exists, err := ctrlutil.CheckResourceExists(ctx.Ctx, ctx.Client, key, &original)
    80  	if err != nil {
    81  		return handleErr(err)
    82  	}
    83  
    84  	// job exists, check job status and set action status accordingly
    85  	if exists {
    86  		objRef, _ := ref.GetReference(ctx.Scheme, &original)
    87  		sb = sb.startTimestamp(&original.CreationTimestamp).objectRef(objRef)
    88  		_, finishedType, msg := utils.IsJobFinished(&original)
    89  		switch finishedType {
    90  		case batchv1.JobComplete:
    91  			return sb.phase(dpv1alpha1.ActionPhaseCompleted).
    92  				completionTimestamp(nil).
    93  				build(), nil
    94  		case batchv1.JobFailed:
    95  			return sb.phase(dpv1alpha1.ActionPhaseFailed).
    96  				completionTimestamp(nil).
    97  				reason(msg).
    98  				build(), nil
    99  		}
   100  		// job is running
   101  		return handleErr(nil)
   102  	}
   103  
   104  	// job doesn't exist, create it
   105  	job := &batchv1.Job{
   106  		ObjectMeta: j.ObjectMeta,
   107  		Spec: batchv1.JobSpec{
   108  			Template: corev1.PodTemplateSpec{
   109  				ObjectMeta: j.ObjectMeta,
   110  				Spec:       *j.PodSpec,
   111  			},
   112  			BackoffLimit: j.BackOffLimit,
   113  		},
   114  	}
   115  
   116  	controllerutil.AddFinalizer(job, types.DataProtectionFinalizerName)
   117  	if err = utils.SetControllerReference(j.Owner, job, ctx.Scheme); err != nil {
   118  		return handleErr(err)
   119  	}
   120  	msg := fmt.Sprintf("creating job %s/%s", job.Namespace, job.Name)
   121  	ctx.Recorder.Event(j.Owner, corev1.EventTypeNormal, "CreatingJob", msg)
   122  	return handleErr(client.IgnoreAlreadyExists(ctx.Client.Create(ctx.Ctx, job)))
   123  }
   124  
   125  func (j *JobAction) validate() error {
   126  	if j.ObjectMeta.Name == "" {
   127  		return fmt.Errorf("name is required")
   128  	}
   129  	if j.PodSpec == nil {
   130  		return fmt.Errorf("PodSpec is required")
   131  	}
   132  	if j.BackOffLimit == nil {
   133  		j.BackOffLimit = &types.DefaultBackOffLimit
   134  	}
   135  	return nil
   136  }
   137  
   138  var _ Action = &JobAction{}