github.com/matrixorigin/matrixone@v1.2.0/cmd/mo-service/dynamic.go (about)

     1  // Copyright 2021 Matrix Origin
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package main
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/http"
    21  	"os"
    22  	"path/filepath"
    23  	"syscall"
    24  	"time"
    25  
    26  	"github.com/fagongzi/goetty/v2"
    27  	"github.com/fagongzi/util/format"
    28  	"github.com/matrixorigin/matrixone/pkg/common/chaos"
    29  	"github.com/matrixorigin/matrixone/pkg/common/stopper"
    30  	"github.com/matrixorigin/matrixone/pkg/logutil"
    31  )
    32  
    33  var (
    34  	baseUUID         = 0
    35  	basePort         = 18000
    36  	baseFrontendPort = 16001
    37  	baseUnixSocket   = 0
    38  )
    39  
    40  var (
    41  	dynamicCNServicePIDs     []int
    42  	dynamicCNServiceCommands [][]string
    43  )
    44  
    45  func startDynamicCluster(
    46  	ctx context.Context,
    47  	cfg *LaunchConfig,
    48  	stopper *stopper.Stopper,
    49  	shutdownC chan struct{},
    50  ) error {
    51  	if err := startLogServiceCluster(ctx, cfg.LogServiceConfigFiles, stopper, shutdownC); err != nil {
    52  		return err
    53  	}
    54  	if err := startTNServiceCluster(ctx, cfg.TNServiceConfigsFiles, stopper, shutdownC); err != nil {
    55  		return err
    56  	}
    57  	if err := startDynamicCNServices("./mo-data", cfg.Dynamic); err != nil {
    58  		return err
    59  	}
    60  	if *withProxy {
    61  		if err := startProxyServiceCluster(ctx, cfg.ProxyServiceConfigsFiles, stopper, shutdownC); err != nil {
    62  			return err
    63  		}
    64  	}
    65  
    66  	// TODO: make configurable for 6001
    67  	cnProxy = goetty.NewProxy("0.0.0.0:6001", logutil.GetGlobalLogger().Named("mysql-proxy"))
    68  	for i := 0; i < cfg.Dynamic.ServiceCount; i++ {
    69  		port := baseFrontendPort + i
    70  		cnProxy.AddUpStream(fmt.Sprintf("127.0.0.1:%d", port), time.Second*10)
    71  	}
    72  	if err := cnProxy.Start(); err != nil {
    73  		return err
    74  	}
    75  	// }
    76  	return startDynamicCtlHTTPServer(cfg.Dynamic.CtlAddress)
    77  }
    78  
    79  func startDynamicCNServices(
    80  	baseDir string,
    81  	cfg Dynamic) error {
    82  	if err := genDynamicCNConfigs(baseDir, cfg); err != nil {
    83  		return err
    84  	}
    85  
    86  	dynamicCNServiceCommands = make([][]string, cfg.ServiceCount)
    87  	dynamicCNServicePIDs = make([]int, cfg.ServiceCount)
    88  	for i := 0; i < cfg.ServiceCount; i++ {
    89  		dynamicCNServiceCommands[i] = []string{
    90  			os.Args[0],
    91  			"-cfg", "./mo-data/cn-" + fmt.Sprintf("%d", i) + ".toml",
    92  			"-max-processor", fmt.Sprintf("%d", cfg.CpuCount),
    93  			"-debug-http", fmt.Sprintf("127.0.0.1:606%d", i),
    94  		}
    95  		if err := startDynamicCNByIndex(i); err != nil {
    96  			return err
    97  		}
    98  	}
    99  	if !cfg.Chaos.Enable {
   100  		return nil
   101  	}
   102  	cfg.Chaos.Restart.KillFunc = stopDynamicCNByIndex
   103  	cfg.Chaos.Restart.RestartFunc = startDynamicCNByIndex
   104  	chaosTester := chaos.NewChaosTester(cfg.Chaos)
   105  	return chaosTester.Start()
   106  }
   107  
   108  func genDynamicCNConfigs(
   109  	baseDir string,
   110  	cfg Dynamic) error {
   111  	baseCNConfig, err := os.ReadFile(cfg.CNTemplate)
   112  	if err != nil {
   113  		return err
   114  	}
   115  
   116  	temps := make([]string, 0, cfg.ServiceCount)
   117  	for i := 0; i < cfg.ServiceCount; i++ {
   118  		uuid := baseUUID + i
   119  		port := basePort + i*100
   120  		frontendPort := baseFrontendPort + i
   121  		unixSocketPort := baseUnixSocket + i
   122  
   123  		cfgFile := fmt.Sprintf(
   124  			string(baseCNConfig),
   125  			uuid,
   126  			port,
   127  			i,
   128  			i,
   129  			frontendPort,
   130  			unixSocketPort)
   131  		f, err := os.CreateTemp(
   132  			baseDir,
   133  			"*.tmp")
   134  		if err != nil {
   135  			return err
   136  		}
   137  		if _, err := f.WriteString(cfgFile); err != nil {
   138  			return err
   139  		}
   140  		if err := f.Sync(); err != nil {
   141  			return err
   142  		}
   143  		if err := f.Close(); err != nil {
   144  			return err
   145  		}
   146  		temps = append(temps, f.Name())
   147  	}
   148  
   149  	d, err := os.Open(baseDir)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	defer func() {
   154  		if err := d.Close(); err != nil {
   155  			panic(err)
   156  		}
   157  	}()
   158  	for i := 0; i < cfg.ServiceCount; i++ {
   159  		if err := os.Rename(
   160  			filepath.Join(temps[i]),
   161  			filepath.Join(baseDir, fmt.Sprintf("cn-%d.toml", i))); err != nil {
   162  			return err
   163  		}
   164  	}
   165  	if err := d.Sync(); err != nil {
   166  		return err
   167  	}
   168  	return nil
   169  }
   170  
   171  func startDynamicCtlHTTPServer(addr string) error {
   172  	if addr == "" {
   173  		return nil
   174  	}
   175  
   176  	http.HandleFunc("/dynamic/cn",
   177  		func(resp http.ResponseWriter, req *http.Request) {
   178  			cn := req.URL.Query().Get("cn")
   179  			action := req.URL.Query().Get("action")
   180  			if cn == "" || action == "" {
   181  				resp.WriteHeader(http.StatusBadRequest)
   182  				resp.Write([]byte("invalid request"))
   183  				return
   184  			}
   185  
   186  			index := format.MustParseStringInt(cn)
   187  			if index < 0 || index >= len(dynamicCNServiceCommands) {
   188  				resp.WriteHeader(http.StatusBadRequest)
   189  				resp.Write([]byte("invalid request"))
   190  				return
   191  			}
   192  
   193  			switch action {
   194  			case "start":
   195  				if dynamicCNServicePIDs[index] != 0 {
   196  					resp.WriteHeader(http.StatusBadRequest)
   197  					resp.Write([]byte("already started"))
   198  					return
   199  				}
   200  				if err := startDynamicCNByIndex(index); err != nil {
   201  					resp.Write([]byte(err.Error()))
   202  				} else {
   203  					resp.Write([]byte("OK"))
   204  				}
   205  			case "stop":
   206  				if dynamicCNServicePIDs[index] == 0 {
   207  					resp.WriteHeader(http.StatusBadRequest)
   208  					resp.Write([]byte("already stopped"))
   209  					return
   210  				}
   211  
   212  				if err := syscall.Kill(dynamicCNServicePIDs[index], syscall.SIGKILL); err != nil {
   213  					resp.Write([]byte(err.Error()))
   214  				} else {
   215  					resp.Write([]byte("OK"))
   216  					dynamicCNServicePIDs[index] = 0
   217  				}
   218  			default:
   219  				resp.WriteHeader(http.StatusBadRequest)
   220  				resp.Write([]byte("invalid request"))
   221  				return
   222  			}
   223  		})
   224  	go func() {
   225  		http.ListenAndServe(*httpListenAddr, nil)
   226  	}()
   227  	return nil
   228  }
   229  
   230  func stopDynamicCNByIndex(index int) error {
   231  	return syscall.Kill(dynamicCNServicePIDs[index], syscall.SIGKILL)
   232  }
   233  
   234  func startDynamicCNByIndex(index int) error {
   235  	pwd, err := os.Getwd()
   236  	if err != nil {
   237  		return err
   238  	}
   239  	pid, err := syscall.ForkExec(
   240  		dynamicCNServiceCommands[index][0],
   241  		dynamicCNServiceCommands[index],
   242  		&syscall.ProcAttr{
   243  			Dir: pwd,
   244  			Env: os.Environ(),
   245  			Sys: &syscall.SysProcAttr{
   246  				Setsid: true,
   247  			},
   248  			Files: []uintptr{0, 1, 2}, // print message to the same pty
   249  		})
   250  	if err != nil {
   251  		return err
   252  	}
   253  	dynamicCNServicePIDs[index] = pid
   254  	return nil
   255  }
   256  
   257  func stopAllDynamicCNServices() {
   258  	for _, pid := range dynamicCNServicePIDs {
   259  		syscall.Kill(pid, syscall.SIGKILL)
   260  	}
   261  }