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 }