code.vegaprotocol.io/vega@v0.79.0/visor/config/visor_config.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package config
    17  
    18  import (
    19  	"context"
    20  	"fmt"
    21  	"path"
    22  	"runtime"
    23  	"sync"
    24  	"time"
    25  
    26  	"code.vegaprotocol.io/vega/logging"
    27  	"code.vegaprotocol.io/vega/paths"
    28  
    29  	"github.com/fsnotify/fsnotify"
    30  	"golang.org/x/sync/errgroup"
    31  )
    32  
    33  const (
    34  	currentFolder      = "current"
    35  	genesisFolder      = "genesis"
    36  	configFileName     = "config.toml"
    37  	RunConfigFileName  = "run-config.toml"
    38  	VegaBinaryName     = "vega"
    39  	DataNodeBinaryName = "data-node"
    40  )
    41  
    42  /*
    43  description: Defines the name of the asset to be downloaded.
    44  */
    45  type AssetsConfig struct {
    46  	// description: Name of the asset file on Github.
    47  	Name string `toml:"name"`
    48  	/*
    49  		description: |
    50  			Name of the binary if the asset is a zip file and the binary is included inside of it.
    51  	*/
    52  	BinaryName *string `toml:"binaryName"`
    53  }
    54  
    55  func (a AssetsConfig) GetBinaryPath() string {
    56  	if a.BinaryName != nil {
    57  		return *a.BinaryName
    58  	}
    59  
    60  	return a.Name
    61  }
    62  
    63  /*
    64  description: Determines if the assets should be automatically downloaded and installed. If so this defines the assets that should be downloaded from GitHub for a specific release.
    65  
    66  example:
    67  
    68  	type: toml
    69  	value: |
    70  		[autoInstall]
    71  			enabled = true
    72  			repositoryOwner = "vegaprotocol"
    73  			repository = "vega"
    74  			[autoInstall.asset]
    75  				name = "vega-darwin-amd64.zip"
    76  				binaryName = "vega"
    77  */
    78  type AutoInstallConfig struct {
    79  	/*
    80  		description: Auto Install flag
    81  		default: true
    82  	*/
    83  	Enabled bool `toml:"enabled"`
    84  	/*
    85  		description: Owner of the repository from where the assets should be downloaded.
    86  		default: vegaprotocol
    87  	*/
    88  	GithubRepositoryOwner string `toml:"repositoryOwner"`
    89  	/*
    90  		description: Name of the repository from where the assets should be downloaded.
    91  		default: vega
    92  	*/
    93  	GithubRepository string `toml:"repository"`
    94  	/*
    95  		description: |
    96  			Definition of the asset that should be downloaded from the GitHub repository.
    97  			If the asset is contained in a zip file, the name of the binary is given.
    98  		example:
    99  			type: toml
   100  			value: |
   101  				[autoInstall.asset]
   102  					name = "vega-darwin-amd64.zip"
   103  					binaryName = "vega"
   104  	*/
   105  	Asset AssetsConfig `toml:"asset"`
   106  }
   107  
   108  /*
   109  description: Root of the config file
   110  example:
   111  
   112  	type: toml
   113  	value: |
   114  		maxNumberOfRestarts = 3
   115  		restartsDelaySeconds = 5
   116  
   117  		[upgradeFolders]
   118  			"vX.X.X" = "vX.X.X"
   119  
   120  		[autoInstall]
   121  			enabled = false
   122  */
   123  type VisorConfigFile struct {
   124  	/*
   125  		description: |
   126  			Visor communicates with the core node via RPC API.
   127  			This variable allows a validator to specify how many times Visor should try to establish a connection to the core node before the Visor process fails.
   128  			The `maxNumberOfFirstConnectionRetries` is only taken into account during the first start up of the Core node process - not restarts.
   129  		note: |
   130  			There is a 2 second delay between each attempt. Setting the max retry number to 5 means Visor will try to establish a connection 5 times in 10 seconds.
   131  		default: 175000
   132  	*/
   133  	MaxNumberOfFirstConnectionRetries int `toml:"maxNumberOfFirstConnectionRetries,optional"`
   134  	/*
   135  		description: |
   136  			Visor communicates with the core node via RPC API.
   137  			This variable allows a validator to specify how many times Visor should try to establish a connection to the core node before the Visor process fails.
   138  			The `MaxNumberOfRestartConnectionRetries` is only taken into account after the first start up of the Core node process where it is expected that the
   139  			time to restart will be much shorter than when originally started.
   140  		note: |
   141  			There is a 2 second delay between each attempt. Setting the max retry number to 5 means Visor will try to establish a connection 5 times in 10 seconds.
   142  		default: 10
   143  	*/
   144  	MaxNumberOfRestartConnectionRetries int `toml:"maxNumberOfRestartConnectionRetries,optional"`
   145  	/*
   146  		description: |
   147  			Defines the maximum number of restarts in case any of
   148  			the processes have failed before the Visor process fails.
   149  		note: |
   150  			The amount of time Visor waits between restarts can be set by `restartsDelaySeconds`.
   151  		default: 3
   152  	*/
   153  	MaxNumberOfRestarts int `toml:"maxNumberOfRestarts,optional"`
   154  	/*
   155  		description: |
   156  			Number of seconds that Visor waits before it tries to re-start the processes.
   157  		default: 5
   158  	*/
   159  	RestartsDelaySeconds int `toml:"restartsDelaySeconds,optional"`
   160  	/*
   161  		description: |
   162  			Number of seconds that Visor waits before it sends a termination signal (SIGTERM) to running processes
   163  			that are ready for upgrade.
   164  			After the time has elapsed Visor stops the running binaries (SIGTERM).
   165  		default: 0
   166  	*/
   167  	StopDelaySeconds int `toml:"stopDelaySeconds,optional"`
   168  	/*
   169  		description: |
   170  			Number of seconds that Visor waits after it sends termination signal (SIGTERM) to running processes.
   171  			After the time has elapsed Visor force-kills (SIGKILL) any running processes.
   172  		default: 15
   173  	*/
   174  	StopSignalTimeoutSeconds int `toml:"stopSignalTimeoutSeconds,optional"`
   175  
   176  	/*
   177  		description: |
   178  			During the upgrade, by default Visor looks for a folder with a name identical to the upgrade version.
   179  			The default behaviour can be changed by providing mapping between `version` and `custom_folder_name`.
   180  			If a custom mapping is provided, during the upgrade Visor uses the folder given in the mapping for specific version.
   181  
   182  		example:
   183  			type: toml
   184  			value: |
   185  				[upgradeFolders]
   186  					"v99.9.9" = "custom_upgrade_folder_name"
   187  	*/
   188  	UpgradeFolders map[string]string `toml:"upgradeFolders,optional"`
   189  
   190  	/*
   191  		description: |
   192  			Defines the assets that should be automatically downloaded from Github for a specific release.
   193  
   194  		example:
   195  			type: toml
   196  			value: |
   197  				[autoInstall]
   198  					enabled = true
   199  					repositoryOwner = "vegaprotocol"
   200  					repository = "vega"
   201  					[autoInstall.assets]
   202  						[autoInstall.assets.vega]
   203  							asset_name = "vega-darwin-amd64.zip"
   204  							binary_name = "vega"
   205  
   206  	*/
   207  	AutoInstall AutoInstallConfig `toml:"autoInstall"`
   208  }
   209  
   210  func parseAndValidateVisorConfigFile(path string) (*VisorConfigFile, error) {
   211  	conf := VisorConfigFile{}
   212  	if err := paths.ReadStructuredFile(path, &conf); err != nil {
   213  		// Do not wrap error as underlying errors are meaningful enough.
   214  		return nil, err
   215  	}
   216  
   217  	return &conf, nil
   218  }
   219  
   220  type VisorConfig struct {
   221  	mut        sync.RWMutex
   222  	configPath string
   223  	homePath   string
   224  	data       *VisorConfigFile
   225  	log        *logging.Logger
   226  }
   227  
   228  func DefaultVisorConfig(log *logging.Logger, homePath string) *VisorConfig {
   229  	return &VisorConfig{
   230  		log:        log,
   231  		homePath:   homePath,
   232  		configPath: path.Join(homePath, configFileName),
   233  		data: &VisorConfigFile{
   234  			UpgradeFolders:                      map[string]string{"vX.X.X": "vX.X.X"},
   235  			MaxNumberOfRestarts:                 3,
   236  			MaxNumberOfFirstConnectionRetries:   175000,
   237  			MaxNumberOfRestartConnectionRetries: 10,
   238  			RestartsDelaySeconds:                5,
   239  			StopDelaySeconds:                    0,
   240  			StopSignalTimeoutSeconds:            15,
   241  			AutoInstall: AutoInstallConfig{
   242  				Enabled:               true,
   243  				GithubRepositoryOwner: "vegaprotocol",
   244  				GithubRepository:      "vega",
   245  				Asset: AssetsConfig{
   246  					Name:       fmt.Sprintf("vega-%s-%s.zip", runtime.GOOS, "amd64"),
   247  					BinaryName: toPointer("vega"),
   248  				},
   249  			},
   250  		},
   251  	}
   252  }
   253  
   254  func NewVisorConfig(log *logging.Logger, homePath string) (*VisorConfig, error) {
   255  	configPath := path.Join(homePath, configFileName)
   256  
   257  	dataFile, err := parseAndValidateVisorConfigFile(configPath)
   258  	if err != nil {
   259  		return nil, fmt.Errorf("could not retrieve configuration at %q: %w", configPath, err)
   260  	}
   261  
   262  	return &VisorConfig{
   263  		configPath: configPath,
   264  		homePath:   homePath,
   265  		data:       dataFile,
   266  		log:        log,
   267  	}, nil
   268  }
   269  
   270  func (pc *VisorConfig) reload() error {
   271  	pc.log.Info("Reloading config")
   272  	dataFile, err := parseAndValidateVisorConfigFile(pc.configPath)
   273  	if err != nil {
   274  		return fmt.Errorf("could not retrieve configuration: %w", err)
   275  	}
   276  
   277  	pc.mut.Lock()
   278  	pc.data.MaxNumberOfFirstConnectionRetries = dataFile.MaxNumberOfFirstConnectionRetries
   279  	pc.data.MaxNumberOfRestarts = dataFile.MaxNumberOfRestarts
   280  	pc.data.RestartsDelaySeconds = dataFile.RestartsDelaySeconds
   281  	pc.data.StopSignalTimeoutSeconds = dataFile.StopSignalTimeoutSeconds
   282  	pc.data.StopDelaySeconds = dataFile.StopDelaySeconds
   283  	pc.data.UpgradeFolders = dataFile.UpgradeFolders
   284  	pc.data.AutoInstall = dataFile.AutoInstall
   285  	pc.mut.Unlock()
   286  
   287  	pc.log.Info("Reloading config success")
   288  
   289  	return nil
   290  }
   291  
   292  func (pc *VisorConfig) WatchForUpdate(ctx context.Context) error {
   293  	watcher, err := fsnotify.NewWatcher()
   294  	if err != nil {
   295  		return err
   296  	}
   297  	defer watcher.Close()
   298  
   299  	var eg errgroup.Group
   300  	eg.Go(func() error {
   301  		for {
   302  			select {
   303  			case <-ctx.Done():
   304  				return ctx.Err()
   305  			case event, ok := <-watcher.Events:
   306  				if !ok {
   307  					return nil
   308  				}
   309  				if event.Has(fsnotify.Write) {
   310  					// add a small sleep here in order to handle vi
   311  					// vi do not send a write event / edit the file in place,
   312  					// it always create a temporary file, then delete the original one,
   313  					// and then rename the temp file with the name of the original file.
   314  					// if we try to update the conf as soon as we get the event, the file is not
   315  					// always created and we get a no such file or directory error
   316  					time.Sleep(50 * time.Millisecond)
   317  
   318  					pc.reload()
   319  				}
   320  			case err, ok := <-watcher.Errors:
   321  				if !ok {
   322  					return nil
   323  				}
   324  				return err
   325  			}
   326  		}
   327  	})
   328  
   329  	if err := watcher.Add(pc.configPath); err != nil {
   330  		return err
   331  	}
   332  
   333  	return eg.Wait()
   334  }
   335  
   336  func (pc *VisorConfig) CurrentFolder() string {
   337  	pc.mut.RLock()
   338  	defer pc.mut.RUnlock()
   339  
   340  	return path.Join(pc.homePath, currentFolder)
   341  }
   342  
   343  func (pc *VisorConfig) CurrentRunConfigPath() string {
   344  	pc.mut.RLock()
   345  	defer pc.mut.RUnlock()
   346  
   347  	return path.Join(pc.CurrentFolder(), RunConfigFileName)
   348  }
   349  
   350  func (pc *VisorConfig) GenesisFolder() string {
   351  	pc.mut.RLock()
   352  	defer pc.mut.RUnlock()
   353  
   354  	return path.Join(pc.homePath, genesisFolder)
   355  }
   356  
   357  func (pc *VisorConfig) UpgradeFolder(releaseTag string) string {
   358  	pc.mut.RLock()
   359  	defer pc.mut.RUnlock()
   360  
   361  	if folderName, ok := pc.data.UpgradeFolders[releaseTag]; ok {
   362  		return path.Join(pc.homePath, folderName)
   363  	}
   364  
   365  	return path.Join(pc.homePath, releaseTag)
   366  }
   367  
   368  func (pc *VisorConfig) MaxNumberOfRestarts() int {
   369  	pc.mut.RLock()
   370  	defer pc.mut.RUnlock()
   371  
   372  	return pc.data.MaxNumberOfRestarts
   373  }
   374  
   375  func (pc *VisorConfig) MaxNumberOfFirstConnectionRetries() int {
   376  	pc.mut.RLock()
   377  	defer pc.mut.RUnlock()
   378  
   379  	return pc.data.MaxNumberOfFirstConnectionRetries
   380  }
   381  
   382  func (pc *VisorConfig) MaxNumberOfRestartConnectionRetries() int {
   383  	pc.mut.RLock()
   384  	defer pc.mut.RUnlock()
   385  
   386  	return pc.data.MaxNumberOfRestartConnectionRetries
   387  }
   388  
   389  func (pc *VisorConfig) RestartsDelaySeconds() int {
   390  	pc.mut.RLock()
   391  	defer pc.mut.RUnlock()
   392  
   393  	return pc.data.RestartsDelaySeconds
   394  }
   395  
   396  func (pc *VisorConfig) StopSignalTimeoutSeconds() int {
   397  	pc.mut.RLock()
   398  	defer pc.mut.RUnlock()
   399  
   400  	return pc.data.StopSignalTimeoutSeconds
   401  }
   402  
   403  func (pc *VisorConfig) StopDelaySeconds() int {
   404  	pc.mut.RLock()
   405  	defer pc.mut.RUnlock()
   406  
   407  	return pc.data.StopDelaySeconds
   408  }
   409  
   410  func (pc *VisorConfig) AutoInstall() AutoInstallConfig {
   411  	pc.mut.RLock()
   412  	defer pc.mut.RUnlock()
   413  
   414  	return pc.data.AutoInstall
   415  }
   416  
   417  func (pc *VisorConfig) WriteToFile() error {
   418  	pc.mut.RLock()
   419  	defer pc.mut.RUnlock()
   420  
   421  	return paths.WriteStructuredFile(pc.configPath, pc.data)
   422  }
   423  
   424  func toPointer[T any](val T) *T {
   425  	return &val
   426  }