github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/lorry/ctl/options.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 ctl 21 22 import ( 23 "bufio" 24 "context" 25 "fmt" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "reflect" 30 "sync" 31 "syscall" 32 "time" 33 ) 34 35 // Options represents the application configuration parameters. 36 type Options struct { 37 HTTPPort int `env:"DAPR_HTTP_PORT" arg:"dapr-http-port"` 38 GRPCPort int `env:"DAPR_GRPC_PORT" arg:"dapr-grpc-port"` 39 ConfigFile string `arg:"config"` 40 Protocol string `arg:"app-protocol"` 41 Arguments []string 42 LogLevel string `arg:"log-level"` 43 ComponentsPath string `arg:"components-path"` 44 InternalGRPCPort int `arg:"dapr-internal-grpc-port"` 45 EnableAppHealth bool `arg:"enable-app-health-check"` 46 AppHealthThreshold int `arg:"app-health-threshold" ifneq:"0"` 47 } 48 49 func (options *Options) validate() error { 50 return nil 51 } 52 53 func (options *Options) getArgs() []string { 54 args := []string{"--app-id", "dbservice"} 55 schema := reflect.ValueOf(*options) 56 for i := 0; i < schema.NumField(); i++ { 57 valueField := schema.Field(i).Interface() 58 typeField := schema.Type().Field(i) 59 key := typeField.Tag.Get("arg") 60 if len(key) == 0 { 61 continue 62 } 63 key = "--" + key 64 65 ifneq, hasIfneq := typeField.Tag.Lookup("ifneq") 66 67 switch valueField.(type) { 68 case bool: 69 if valueField == true { 70 args = append(args, key) 71 } 72 default: 73 value := fmt.Sprintf("%v", reflect.ValueOf(valueField)) 74 if len(value) != 0 && (!hasIfneq || value != ifneq) { 75 args = append(args, key, value) 76 } 77 } 78 } 79 80 return args 81 } 82 83 func (options *Options) getEnv() []string { 84 env := []string{} 85 schema := reflect.ValueOf(*options) 86 for i := 0; i < schema.NumField(); i++ { 87 valueField := schema.Field(i).Interface() 88 typeField := schema.Type().Field(i) 89 key := typeField.Tag.Get("env") 90 if len(key) == 0 { 91 continue 92 } 93 if value, ok := valueField.(int); ok && value <= 0 { 94 // ignore unset numeric variables. 95 continue 96 } 97 98 value := fmt.Sprintf("%v", reflect.ValueOf(valueField)) 99 env = append(env, fmt.Sprintf("%s=%v", key, value)) 100 } 101 return env 102 } 103 104 // Commands represents the managed subprocesses. 105 type Commands struct { 106 ctx context.Context 107 WaitGroup sync.WaitGroup 108 LorryCMD *exec.Cmd 109 LorryErr error 110 LorryHTTPPort int 111 LorryGRPCPort int 112 LorryStarted chan bool 113 AppCMD *exec.Cmd 114 AppErr error 115 AppStarted chan bool 116 Options *Options 117 SigCh chan os.Signal 118 // if it's false, lorryctl will not restart db service 119 restartDB bool 120 } 121 122 func getLorryCommand(options *Options) (*exec.Cmd, error) { 123 lorryCMD := filepath.Join(lorryRuntimePath, "lorry") 124 args := options.getArgs() 125 cmd := exec.Command(lorryCMD, args...) 126 return cmd, nil 127 } 128 129 func getAppCommand(options *Options) *exec.Cmd { 130 argCount := len(options.Arguments) 131 132 if argCount == 0 { 133 return nil 134 } 135 command := options.Arguments[0] 136 137 args := []string{} 138 if argCount > 1 { 139 args = options.Arguments[1:] 140 } 141 142 cmd := exec.Command(command, args...) 143 cmd.Env = os.Environ() 144 cmd.Env = append(cmd.Env, options.getEnv()...) 145 146 return cmd 147 } 148 149 func newCommands(ctx context.Context, options *Options) (*Commands, error) { 150 //nolint 151 err := options.validate() 152 if err != nil { 153 return nil, err 154 } 155 156 lorryCMD, err := getLorryCommand(options) 157 if err != nil { 158 return nil, err 159 } 160 161 //nolint 162 var appCMD *exec.Cmd = getAppCommand(options) 163 cmd := &Commands{ 164 ctx: ctx, 165 LorryCMD: lorryCMD, 166 LorryErr: nil, 167 LorryHTTPPort: options.HTTPPort, 168 LorryGRPCPort: options.GRPCPort, 169 LorryStarted: make(chan bool, 1), 170 AppCMD: appCMD, 171 AppErr: nil, 172 AppStarted: make(chan bool, 1), 173 Options: options, 174 SigCh: make(chan os.Signal, 1), 175 restartDB: true, 176 } 177 return cmd, nil 178 } 179 180 func (commands *Commands) StartLorry() { 181 var startInfo string 182 fmt.Fprintf(os.Stdout, "Starting Lorry HTTP Port: %v. gRPC Port: %v\n", 183 commands.LorryHTTPPort, 184 commands.LorryGRPCPort) 185 fmt.Fprintln(os.Stdout, startInfo) 186 187 commands.LorryCMD.Stdout = os.Stdout 188 commands.LorryCMD.Stderr = os.Stderr 189 190 err := commands.LorryCMD.Start() 191 if err != nil { 192 fmt.Fprintln(os.Stderr, err.Error()) 193 os.Exit(1) 194 } 195 196 commands.LorryStarted <- true 197 } 198 199 func (commands *Commands) RestartDBServiceIfExited() { 200 if commands.AppCMD == nil { 201 return 202 } 203 commands.WaitGroup.Add(1) 204 for { 205 select { 206 case <-commands.ctx.Done(): 207 commands.WaitGroup.Done() 208 return 209 default: 210 } 211 212 if commands.AppCMD.ProcessState != nil { 213 Printf("DB service exits: %v\n", commands.AppCMD.ProcessState) 214 if !commands.restartDB { 215 Printf("restart DB service: %v\n", commands.restartDB) 216 time.Sleep(2 * time.Second) 217 continue 218 } 219 220 status, ok := commands.AppCMD.ProcessState.Sys().(syscall.WaitStatus) 221 if commands.AppCMD.ProcessState.Exited() || (ok && status.Signaled()) { 222 commands.RestartDBService() 223 } 224 } 225 time.Sleep(1 * time.Second) 226 } 227 } 228 229 func (commands *Commands) RestartDBService() { 230 if commands.AppCMD == nil { 231 return 232 } 233 Printf("DB service restart: %v\n", commands.AppCMD) 234 // commands.StopDBService() 235 commands.AppCMD = getAppCommand(commands.Options) 236 commands.AppErr = nil 237 commands.AppStarted = make(chan bool, 1) 238 commands.StartDBService() 239 240 } 241 242 func (commands *Commands) StartDBService() { 243 if commands.AppCMD == nil { 244 commands.AppStarted <- true 245 return 246 } 247 248 stdErrPipe, pipeErr := commands.AppCMD.StderrPipe() 249 if pipeErr != nil { 250 fmt.Fprintf(os.Stderr, "Error creating stderr for DB Service: %s\n", pipeErr.Error()) 251 commands.AppStarted <- false 252 return 253 } 254 255 stdOutPipe, pipeErr := commands.AppCMD.StdoutPipe() 256 if pipeErr != nil { 257 fmt.Fprintf(os.Stderr, "Error creating stdout for DB Service: %s\n", pipeErr.Error()) 258 commands.AppStarted <- false 259 return 260 } 261 262 errScanner := bufio.NewScanner(stdErrPipe) 263 outScanner := bufio.NewScanner(stdOutPipe) 264 go func() { 265 for errScanner.Scan() { 266 fmt.Printf("== DB Service err == %s\n", errScanner.Text()) 267 } 268 }() 269 270 go func() { 271 for outScanner.Scan() { 272 fmt.Printf("== DB Service == %s\n", outScanner.Text()) 273 } 274 }() 275 276 err := commands.AppCMD.Start() 277 if err != nil { 278 fmt.Fprintln(os.Stderr, err.Error()) 279 commands.AppStarted <- false 280 return 281 } 282 commands.AppStarted <- true 283 go commands.WaitDBService() 284 } 285 286 func (commands *Commands) StopLorry() bool { 287 exitWithError := false 288 if commands.LorryErr != nil { 289 exitWithError = true 290 fmt.Fprintf(os.Stderr, "Error exiting Lorry: %s\n", commands.LorryErr) 291 } else if commands.LorryCMD.ProcessState == nil || !commands.LorryCMD.ProcessState.Exited() { 292 err := commands.LorryCMD.Process.Signal(syscall.SIGTERM) 293 if err != nil { 294 exitWithError = true 295 fmt.Fprintf(os.Stderr, "Error exiting Lorry: %s\n", err) 296 } else { 297 fmt.Fprintln(os.Stdout, "Send SIGTERM to Lorry") 298 } 299 } 300 // state, err = commands.LorryCMD.Process.Wait() 301 // fmt.Printf("state: %v, err: %v\n", state, err) 302 commands.WaitLorry() 303 return exitWithError 304 } 305 306 func (commands *Commands) StopDBService() bool { 307 exitWithError := false 308 if commands.AppErr != nil { 309 exitWithError = true 310 fmt.Fprintf(os.Stderr, "Error exiting App: %s\n", commands.AppErr) 311 } else if commands.AppCMD != nil && (commands.AppCMD.ProcessState == nil || !commands.AppCMD.ProcessState.Exited()) { 312 err := commands.AppCMD.Process.Signal(syscall.SIGTERM) 313 if err != nil { 314 exitWithError = true 315 fmt.Fprintf(os.Stderr, "Error exiting App: %s\n", err) 316 } else { 317 fmt.Fprintln(os.Stdout, "Exited App successfully") 318 } 319 } 320 commands.WaitDBService() 321 return exitWithError 322 } 323 324 func (commands *Commands) WaitDBService() { 325 commands.AppErr = waitCmd(commands.AppCMD) 326 } 327 328 func (commands *Commands) WaitLorry() { 329 commands.LorryErr = waitCmd(commands.LorryCMD) 330 } 331 332 func waitCmd(cmd *exec.Cmd) error { 333 if cmd == nil || (cmd.ProcessState != nil && cmd.ProcessState.Exited()) { 334 return nil 335 } 336 337 err := cmd.Wait() 338 if err != nil { 339 fmt.Fprintf(os.Stderr, "The command [%s] exited with error code: %s\n", cmd.String(), err.Error()) 340 } else { 341 fmt.Fprintf(os.Stdout, "The command [%s] exited\n", cmd.String()) 342 } 343 return err 344 }