-
Notifications
You must be signed in to change notification settings - Fork 93
/
daemon.go
377 lines (337 loc) · 15.4 KB
/
daemon.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
package main
import (
"context"
"errors"
"fmt"
"os"
"os/signal"
"runtime/pprof"
"strings"
"syscall"
"time"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/algorand/indexer/v3/api"
"github.com/algorand/indexer/v3/api/generated/v2"
"github.com/algorand/indexer/v3/config"
"github.com/algorand/indexer/v3/idb"
iutil "github.com/algorand/indexer/v3/util"
)
type daemonConfig struct {
flags *pflag.FlagSet
daemonServerAddr string
developerMode bool
enablePrivateNetworkAccessHeader bool
metricsMode string
tokenString string
writeTimeout time.Duration
readTimeout time.Duration
maxConn uint32
maxAPIResourcesPerAccount uint32
maxAccountListSize uint32
maxBlocksLimit uint32
defaultBlocksLimit uint32
maxTransactionsLimit uint32
defaultTransactionsLimit uint32
maxAccountsLimit uint32
defaultAccountsLimit uint32
maxAssetsLimit uint32
defaultAssetsLimit uint32
maxBoxesLimit uint32
defaultBoxesLimit uint32
maxBalancesLimit uint32
defaultBalancesLimit uint32
maxApplicationsLimit uint32
defaultApplicationsLimit uint32
enableAllParameters bool
indexerDataDir string
cpuProfile string
pidFilePath string
configFile string
suppliedAPIConfigFile string
}
// DaemonCmd creates the main cobra command, initializes flags, and viper aliases
func DaemonCmd() *cobra.Command {
cfg := &daemonConfig{}
daemonCmd := &cobra.Command{
Use: "daemon",
Short: "run indexer daemon",
Long: "run indexer daemon. Serve api on HTTP.",
//Args:
Run: func(cmd *cobra.Command, args []string) {
if err := runDaemon(cfg); err != nil {
fmt.Fprintf(os.Stderr, "Exiting with error: %s\n", err.Error())
os.Exit(1)
}
},
}
cfg.flags = daemonCmd.Flags()
cfg.flags.StringVarP(&cfg.daemonServerAddr, "server", "S", ":8980", "host:port to serve API on (default :8980)")
cfg.flags.StringVarP(&cfg.tokenString, "token", "t", "", "an optional auth token, when set REST calls must use this token in a bearer format, or in a 'X-Indexer-API-Token' header")
cfg.flags.BoolVarP(&cfg.developerMode, "dev-mode", "", false, "has no effect currently, reserved for future performance intensive operations")
cfg.flags.BoolVarP(&cfg.enablePrivateNetworkAccessHeader, "enable-private-network-access-header", "", false, "respond to Private Network Access preflight requests")
cfg.flags.StringVarP(&cfg.metricsMode, "metrics-mode", "", "OFF", "configure the /metrics endpoint to [ON, OFF, VERBOSE]")
cfg.flags.DurationVarP(&cfg.writeTimeout, "write-timeout", "", 30*time.Second, "set the maximum duration to wait before timing out writes to a http response, breaking connection")
cfg.flags.DurationVarP(&cfg.readTimeout, "read-timeout", "", 5*time.Second, "set the maximum duration for reading the entire request")
cfg.flags.Uint32VarP(&cfg.maxConn, "max-conn", "", 0, "set the maximum connections allowed in the connection pool, if the maximum is reached subsequent connections will wait until a connection becomes available, or timeout according to the read-timeout setting")
cfg.flags.StringVar(&cfg.suppliedAPIConfigFile, "api-config-file", "", "supply an API config file to enable/disable parameters")
cfg.flags.BoolVar(&cfg.enableAllParameters, "enable-all-parameters", false, "override default configuration and enable all parameters. Can't be used with --api-config-file")
cfg.flags.Uint32VarP(&cfg.maxAPIResourcesPerAccount, "max-api-resources-per-account", "", 1000, "set the maximum total number of resources (created assets, created apps, asset holdings, and application local state) per account that will be allowed in REST API lookupAccountByID and searchForAccounts responses before returning a 400 Bad Request. Set zero for no limit")
cfg.flags.Uint32VarP(&cfg.maxAccountListSize, "max-account-list-size", "", 50, "set the maximum number of items for query parameters that accept account lists. Set zero for no limit")
cfg.flags.Uint32VarP(&cfg.maxBlocksLimit, "max-blocks-limit", "", 1000, "set the maximum allowed Limit parameter for querying blocks")
cfg.flags.Uint32VarP(&cfg.defaultBlocksLimit, "default-blocks-limit", "", 100, "set the default Limit parameter for querying blocks, if none is provided")
cfg.flags.Uint32VarP(&cfg.maxTransactionsLimit, "max-transactions-limit", "", 10000, "set the maximum allowed Limit parameter for querying transactions")
cfg.flags.Uint32VarP(&cfg.defaultTransactionsLimit, "default-transactions-limit", "", 1000, "set the default Limit parameter for querying transactions, if none is provided")
cfg.flags.Uint32VarP(&cfg.maxAccountsLimit, "max-accounts-limit", "", 1000, "set the maximum allowed Limit parameter for querying accounts")
cfg.flags.Uint32VarP(&cfg.defaultAccountsLimit, "default-accounts-limit", "", 100, "set the default Limit parameter for querying accounts, if none is provided")
cfg.flags.Uint32VarP(&cfg.maxAssetsLimit, "max-assets-limit", "", 1000, "set the maximum allowed Limit parameter for querying assets")
cfg.flags.Uint32VarP(&cfg.defaultAssetsLimit, "default-assets-limit", "", 100, "set the default Limit parameter for querying assets, if none is provided")
cfg.flags.Uint32VarP(&cfg.maxBalancesLimit, "max-balances-limit", "", 10000, "set the maximum allowed Limit parameter for querying balances")
cfg.flags.Uint32VarP(&cfg.defaultBalancesLimit, "default-balances-limit", "", 1000, "set the default Limit parameter for querying balances, if none is provided")
cfg.flags.Uint32VarP(&cfg.maxApplicationsLimit, "max-applications-limit", "", 1000, "set the maximum allowed Limit parameter for querying applications")
cfg.flags.Uint32VarP(&cfg.defaultApplicationsLimit, "default-applications-limit", "", 100, "set the default Limit parameter for querying applications, if none is provided")
cfg.flags.Uint32VarP(&cfg.maxBoxesLimit, "max-boxes-limit", "", 10000, "set the maximum allowed Limit parameter for searching an app's boxes")
cfg.flags.Uint32VarP(&cfg.defaultBoxesLimit, "default-boxes-limit", "", 1000, "set the default allowed Limit parameter for searching an app's boxes")
cfg.flags.StringVarP(&cfg.indexerDataDir, "data-dir", "i", "", "path to indexer data dir, or $INDEXER_DATA")
cfg.flags.StringVarP(&cfg.cpuProfile, "cpuprofile", "", "", "file to record cpu profile to")
cfg.flags.StringVarP(&cfg.pidFilePath, "pidfile", "", "", "file to write daemon's process id to")
cfg.flags.StringVarP(&cfg.configFile, "configfile", "c", "", "file path to configuration file (indexer.yml)")
viper.RegisterAlias("algod", "algod-data-dir")
viper.RegisterAlias("algod-net", "algod-address")
viper.RegisterAlias("server", "server-address")
viper.RegisterAlias("token", "api-token")
return daemonCmd
}
func configureIndexerDataDir(indexerDataDir string) error {
var err error
if indexerDataDir == "" {
return nil
}
if _, err = os.Stat(indexerDataDir); os.IsNotExist(err) {
err = os.Mkdir(indexerDataDir, 0755)
if err != nil {
return fmt.Errorf("indexer data directory error, %v", err)
}
}
return err
}
func resolveConfigFile(indexerDataDir string, configFile string) (string, error) {
var err error
potentialIndexerConfigPath, err := iutil.GetConfigFromDataDir(indexerDataDir, autoLoadIndexerConfigFileName, config.FileTypes[:])
if err != nil {
return "", err
}
indexerConfigFound := potentialIndexerConfigPath != ""
if indexerConfigFound {
//autoload
if configFile != "" {
err = fmt.Errorf("indexer configuration was found in data directory (%s) as well as supplied via command line. Only provide one",
potentialIndexerConfigPath)
return "", err
}
return potentialIndexerConfigPath, nil
} else if configFile != "" {
// user specified
return configFile, nil
}
// neither autoload nor user specified
return "", nil
}
// loadIndexerConfig opens the file and calls viper.ReadConfig
func loadIndexerConfig(configFile string) error {
if configFile == "" {
return nil
}
configs, err := os.Open(configFile)
if err != nil {
return fmt.Errorf("config file does not exist: %w", err)
}
defer configs.Close()
err = viper.ReadConfig(configs)
if err != nil {
return fmt.Errorf("invalid config file (%s): %w", configFile, err)
}
return err
}
func loadIndexerParamConfig(cfg *daemonConfig) error {
var err error
// If someone supplied a configuration file but also said to enable all parameters,
// that's an error
if cfg.suppliedAPIConfigFile != "" && cfg.enableAllParameters {
err = errors.New("not allowed to supply an api config file and enable all parameters")
logger.WithError(err).Errorf("API Parameter Error: %v", err)
return err
}
potentialParamConfigPath, err := iutil.GetConfigFromDataDir(cfg.indexerDataDir, autoLoadParameterConfigFileName, config.FileTypes[:])
if err != nil {
logger.Error(err)
return err
}
paramConfigFound := potentialParamConfigPath != ""
// If we auto-loaded configs but a user supplied them as well, we have an error
if paramConfigFound {
if cfg.suppliedAPIConfigFile != "" {
err = fmt.Errorf("api parameter configuration was found in data directory (%s) as well as supplied via command line. Only provide one",
potentialParamConfigPath)
logger.WithError(err).Errorf("indexer parameter config error: %v", err)
return err
}
cfg.suppliedAPIConfigFile = potentialParamConfigPath
logger.Infof("Auto-loading parameter configuration file: %s", suppliedAPIConfigFile)
}
return err
}
func runDaemon(daemonConfig *daemonConfig) error {
var err error
// check for config environment variables
if daemonConfig.indexerDataDir == "" {
daemonConfig.indexerDataDir = os.Getenv("INDEXER_DATA")
}
if daemonConfig.configFile == "" {
daemonConfig.configFile = os.Getenv("INDEXER_CONFIGFILE")
}
// Create the data directory if necessary/possible
if err = configureIndexerDataDir(daemonConfig.indexerDataDir); err != nil {
return err
}
// Detect the various auto-loading configs from data directory
var configFile string
if configFile, err = resolveConfigFile(daemonConfig.indexerDataDir, daemonConfig.configFile); err != nil {
return err
}
if err = loadIndexerConfig(configFile); err != nil {
return err
}
// We need to re-run this because loading the config file could change these
config.BindFlagSet(daemonConfig.flags)
// Configure the logger as soon as we're able so that it can be used.
err = configureLogger()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to configure logger: %v", err)
return err
}
if configFile != "" {
logger.Infof("Using configuration file: %s", configFile)
}
// Load the Parameter config
if err = loadIndexerParamConfig(daemonConfig); err != nil {
return err
}
if daemonConfig.pidFilePath != "" {
err = iutil.CreateIndexerPidFile(logger, daemonConfig.pidFilePath)
if err != nil {
return err
}
defer func(name string) {
err := os.Remove(name)
if err != nil {
logger.WithError(err).Errorf("%s: could not remove pid file", daemonConfig.pidFilePath)
}
}(daemonConfig.pidFilePath)
}
if daemonConfig.cpuProfile != "" {
var err error
profFile, err = os.Create(daemonConfig.cpuProfile)
if err != nil {
logger.WithError(err).Errorf("%s: create, %v", daemonConfig.cpuProfile, err)
return err
}
defer profFile.Close()
err = pprof.StartCPUProfile(profFile)
if err != nil {
logger.WithError(err).Errorf("%s: start pprof, %v", daemonConfig.cpuProfile, err)
return err
}
defer pprof.StopCPUProfile()
}
ctx, cf := context.WithCancel(context.Background())
defer cf()
{
cancelCh := make(chan os.Signal, 1)
signal.Notify(cancelCh, syscall.SIGTERM, syscall.SIGINT)
go func() {
// Need to redefine exitHandler() for every go-routine
defer exitHandler()
<-cancelCh
logger.Println("Stopping Indexer.")
cf()
}()
}
opts := idb.IndexerDbOptions{}
opts.ReadOnly = true
opts.MaxConn = daemonConfig.maxConn
opts.IndexerDatadir = daemonConfig.indexerDataDir
db, _, err := indexerDbFromFlags(opts)
if err != nil {
return err
}
defer db.Close()
var dataError func() error
fmt.Printf("serving on %s\n", daemonConfig.daemonServerAddr)
logger.Infof("serving on %s", daemonConfig.daemonServerAddr)
options := makeOptions(daemonConfig)
api.Serve(ctx, daemonConfig.daemonServerAddr, db, dataError, logger, options)
return err
}
// makeOptions converts CLI options to server options
func makeOptions(daemonConfig *daemonConfig) (options api.ExtraOptions) {
options.EnablePrivateNetworkAccessHeader = daemonConfig.enablePrivateNetworkAccessHeader
if daemonConfig.tokenString != "" {
options.Tokens = append(options.Tokens, daemonConfig.tokenString)
}
switch strings.ToUpper(daemonConfig.metricsMode) {
case "OFF":
options.MetricsEndpoint = false
options.MetricsEndpointVerbose = false
case "ON":
options.MetricsEndpoint = true
options.MetricsEndpointVerbose = false
case "VERBOSE":
options.MetricsEndpoint = true
options.MetricsEndpointVerbose = true
}
options.WriteTimeout = daemonConfig.writeTimeout
options.ReadTimeout = daemonConfig.readTimeout
options.MaxAPIResourcesPerAccount = uint64(daemonConfig.maxAPIResourcesPerAccount)
options.MaxAccountListSize = uint64(daemonConfig.maxAccountListSize)
options.MaxBlocksLimit = uint64(daemonConfig.maxBlocksLimit)
options.DefaultBlocksLimit = uint64(daemonConfig.defaultBlocksLimit)
options.MaxTransactionsLimit = uint64(daemonConfig.maxTransactionsLimit)
options.DefaultTransactionsLimit = uint64(daemonConfig.defaultTransactionsLimit)
options.MaxAccountsLimit = uint64(daemonConfig.maxAccountsLimit)
options.DefaultAccountsLimit = uint64(daemonConfig.defaultAccountsLimit)
options.MaxAssetsLimit = uint64(daemonConfig.maxAssetsLimit)
options.DefaultAssetsLimit = uint64(daemonConfig.defaultAssetsLimit)
options.MaxBalancesLimit = uint64(daemonConfig.maxBalancesLimit)
options.DefaultBalancesLimit = uint64(daemonConfig.defaultBalancesLimit)
options.MaxApplicationsLimit = uint64(daemonConfig.maxApplicationsLimit)
options.DefaultApplicationsLimit = uint64(daemonConfig.defaultApplicationsLimit)
options.MaxBoxesLimit = uint64(daemonConfig.maxBoxesLimit)
options.DefaultBoxesLimit = uint64(daemonConfig.defaultBoxesLimit)
if daemonConfig.enableAllParameters {
options.DisabledMapConfig = api.MakeDisabledMapConfig()
} else {
options.DisabledMapConfig = api.GetDefaultDisabledMapConfigForPostgres()
}
if daemonConfig.suppliedAPIConfigFile != "" {
swag, err := generated.GetSwagger()
if err != nil {
fmt.Fprintf(os.Stderr, "failed to get swagger: %v", err)
panic(exit{1})
}
logger.Infof("supplied api configuration file located at: %s", daemonConfig.suppliedAPIConfigFile)
potentialDisabledMapConfig, err := api.MakeDisabledMapConfigFromFile(swag, daemonConfig.suppliedAPIConfigFile)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to created disabled map config from file: %v", err)
panic(exit{1})
}
if len((*potentialDisabledMapConfig).Data) == 0 {
logger.Warnf("All parameters are enabled since the provided parameter configuration file (%s) is empty.", suppliedAPIConfigFile)
}
options.DisabledMapConfig = potentialDisabledMapConfig
} else {
logger.Infof("Enable all parameters flag is set to: %v", daemonConfig.enableAllParameters)
}
return
}