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 }