github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/migration/logs.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 migration
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/spf13/cobra"
    30  	corev1 "k8s.io/api/core/v1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/cli-runtime/pkg/genericiooptions"
    33  	"k8s.io/client-go/dynamic"
    34  	"k8s.io/client-go/kubernetes"
    35  	cmdlogs "k8s.io/kubectl/pkg/cmd/logs"
    36  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    37  	"k8s.io/kubectl/pkg/polymorphichelpers"
    38  
    39  	"github.com/1aal/kubeblocks/pkg/cli/exec"
    40  	"github.com/1aal/kubeblocks/pkg/cli/types"
    41  	migrationv1 "github.com/1aal/kubeblocks/pkg/cli/types/migrationapi"
    42  	"github.com/1aal/kubeblocks/pkg/cli/util"
    43  )
    44  
    45  type LogsOptions struct {
    46  	taskName string
    47  	step     string
    48  	Client   *kubernetes.Clientset
    49  	Dynamic  dynamic.Interface
    50  	*exec.ExecOptions
    51  	logOptions cmdlogs.LogsOptions
    52  }
    53  
    54  func NewMigrationLogsCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
    55  	l := &LogsOptions{
    56  		ExecOptions: exec.NewExecOptions(f, streams),
    57  		logOptions: cmdlogs.LogsOptions{
    58  			Tail:      -1,
    59  			IOStreams: streams,
    60  		},
    61  	}
    62  	cmd := &cobra.Command{
    63  		Use:               "logs NAME",
    64  		Short:             "Access migration task log file.",
    65  		Example:           LogsExample,
    66  		ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.MigrationTaskGVR()),
    67  		Run: func(cmd *cobra.Command, args []string) {
    68  			util.CheckErr(l.ExecOptions.Complete())
    69  			util.CheckErr(l.complete(f, cmd, args))
    70  			util.CheckErr(l.validate())
    71  			util.CheckErr(l.runLogs())
    72  		},
    73  	}
    74  	l.addFlags(cmd)
    75  	return cmd
    76  }
    77  
    78  func (o *LogsOptions) addFlags(cmd *cobra.Command) {
    79  	cmd.Flags().StringVar(&o.step, "step", "", "Specify the step. Allow values: precheck,init-struct,init-data,cdc")
    80  
    81  	o.logOptions.AddFlags(cmd)
    82  }
    83  
    84  // complete customs complete function for logs
    85  func (o *LogsOptions) complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
    86  	if len(args) == 0 {
    87  		return fmt.Errorf("migration task name should be specified")
    88  	}
    89  	if len(args) > 0 {
    90  		o.taskName = args[0]
    91  	}
    92  	if o.step == "" {
    93  		return fmt.Errorf("migration task step should be specified")
    94  	}
    95  	var err error
    96  	o.logOptions.Namespace, _, err = f.ToRawKubeConfigLoader().Namespace()
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	o.Dynamic, err = f.DynamicClient()
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	o.Client, err = f.KubernetesClientSet()
   107  	if err != nil {
   108  		return err
   109  	}
   110  
   111  	if _, err = IsMigrationCrdValidWithDynamic(&o.Dynamic); err != nil {
   112  		PrintCrdInvalidError(err)
   113  	}
   114  
   115  	taskObj, err := o.getMigrationObjects(o.taskName)
   116  	if err != nil {
   117  		return fmt.Errorf("failed to find the migrationtask")
   118  	}
   119  	pod := o.getPodByStep(taskObj, strings.TrimSpace(o.step))
   120  	if pod == nil {
   121  		return fmt.Errorf("migrationtask[%s] step[%s] 's pod not found", taskObj.Task.Name, o.step)
   122  	}
   123  	o.logOptions.RESTClientGetter = f
   124  	o.logOptions.LogsForObject = polymorphichelpers.LogsForObjectFn
   125  	o.logOptions.Object = pod
   126  	o.logOptions.Options, _ = o.logOptions.ToLogOptions()
   127  	o.Pod = pod
   128  
   129  	return nil
   130  }
   131  
   132  func (o *LogsOptions) validate() error {
   133  	if len(o.taskName) == 0 {
   134  		return fmt.Errorf("migration task name must be specified")
   135  	}
   136  
   137  	if o.logOptions.LimitBytes < 0 {
   138  		return fmt.Errorf("--limit-bytes must be greater than 0")
   139  	}
   140  	if o.logOptions.Tail < -1 {
   141  		return fmt.Errorf("--tail must be greater than or equal to -1")
   142  	}
   143  	if len(o.logOptions.SinceTime) > 0 && o.logOptions.SinceSeconds != 0 {
   144  		return fmt.Errorf("at most one of `sinceTime` or `sinceSeconds` may be specified")
   145  	}
   146  	logsOptions, ok := o.logOptions.Options.(*corev1.PodLogOptions)
   147  	if !ok {
   148  		return fmt.Errorf("unexpected logs options object")
   149  	}
   150  	if logsOptions.SinceSeconds != nil && *logsOptions.SinceSeconds < int64(0) {
   151  		return fmt.Errorf("--since must be greater than 0")
   152  	}
   153  	if logsOptions.TailLines != nil && *logsOptions.TailLines < -1 {
   154  		return fmt.Errorf("--tail must be greater than or equal to -1")
   155  	}
   156  	return nil
   157  }
   158  
   159  func (o *LogsOptions) getMigrationObjects(taskName string) (*migrationv1.MigrationObjects, error) {
   160  	obj := &migrationv1.MigrationObjects{
   161  		Task:     &migrationv1.MigrationTask{},
   162  		Template: &migrationv1.MigrationTemplate{},
   163  	}
   164  	var err error
   165  	taskGvr := types.MigrationTaskGVR()
   166  	if err = APIResource(&o.Dynamic, &taskGvr, taskName, o.logOptions.Namespace, obj.Task); err != nil {
   167  		return nil, err
   168  	}
   169  	templateGvr := types.MigrationTemplateGVR()
   170  	if err = APIResource(&o.Dynamic, &templateGvr, obj.Task.Spec.Template, "", obj.Template); err != nil {
   171  		return nil, err
   172  	}
   173  	listOpts := func() metav1.ListOptions {
   174  		return metav1.ListOptions{
   175  			LabelSelector: fmt.Sprintf("%s=%s", MigrationTaskLabel, taskName),
   176  		}
   177  	}
   178  	if obj.Pods, err = o.Client.CoreV1().Pods(o.logOptions.Namespace).List(context.Background(), listOpts()); err != nil {
   179  		return nil, err
   180  	}
   181  	return obj, nil
   182  }
   183  
   184  func (o *LogsOptions) runLogs() error {
   185  	requests, err := o.logOptions.LogsForObject(o.logOptions.RESTClientGetter, o.logOptions.Object, o.logOptions.Options, 60*time.Second, false)
   186  	if err != nil {
   187  		return err
   188  	}
   189  	for _, request := range requests {
   190  		if err := cmdlogs.DefaultConsumeRequest(request, o.Out); err != nil {
   191  			if !o.logOptions.IgnoreLogErrors {
   192  				return err
   193  			}
   194  			fmt.Fprintf(o.Out, "error: %v\n", err)
   195  		}
   196  	}
   197  	return nil
   198  }
   199  
   200  func (o *LogsOptions) getPodByStep(taskObj *migrationv1.MigrationObjects, step string) *corev1.Pod {
   201  	if taskObj == nil || len(taskObj.Pods.Items) == 0 {
   202  		return nil
   203  	}
   204  	switch step {
   205  	case migrationv1.CliStepCdc.String():
   206  		for _, pod := range taskObj.Pods.Items {
   207  			if pod.Annotations[MigrationTaskStepAnnotation] == migrationv1.StepCdc.String() {
   208  				return &pod
   209  			}
   210  		}
   211  	case migrationv1.CliStepPreCheck.String(), migrationv1.CliStepInitStruct.String(), migrationv1.CliStepInitData.String():
   212  		stepArr := BuildInitializationStepsOrder(taskObj.Task, taskObj.Template)
   213  		orderNo := "-1"
   214  		for index, stepByTemplate := range stepArr {
   215  			if step == stepByTemplate {
   216  				orderNo = strconv.Itoa(index)
   217  				break
   218  			}
   219  		}
   220  		for _, pod := range taskObj.Pods.Items {
   221  			if pod.Annotations[SerialJobOrderAnnotation] != "" &&
   222  				pod.Annotations[SerialJobOrderAnnotation] == orderNo {
   223  				return &pod
   224  			}
   225  		}
   226  	}
   227  	return nil
   228  }