Merge remote-tracking branch 'origin/master' into 14714-keep-balance-config
[arvados.git] / lib / config / deprecated.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package config
6
7 import (
8         "fmt"
9         "io/ioutil"
10         "net/url"
11         "os"
12         "strings"
13
14         "git.curoverse.com/arvados.git/sdk/go/arvados"
15         "github.com/ghodss/yaml"
16 )
17
18 type deprRequestLimits struct {
19         MaxItemsPerResponse            *int
20         MultiClusterRequestConcurrency *int
21 }
22
23 type deprCluster struct {
24         RequestLimits deprRequestLimits
25         NodeProfiles  map[string]nodeProfile
26 }
27
28 type deprecatedConfig struct {
29         Clusters map[string]deprCluster
30 }
31
32 type nodeProfile struct {
33         Controller    systemServiceInstance `json:"arvados-controller"`
34         Health        systemServiceInstance `json:"arvados-health"`
35         Keepbalance   systemServiceInstance `json:"keep-balance"`
36         Keepproxy     systemServiceInstance `json:"keepproxy"`
37         Keepstore     systemServiceInstance `json:"keepstore"`
38         Keepweb       systemServiceInstance `json:"keep-web"`
39         Nodemanager   systemServiceInstance `json:"arvados-node-manager"`
40         DispatchCloud systemServiceInstance `json:"arvados-dispatch-cloud"`
41         RailsAPI      systemServiceInstance `json:"arvados-api-server"`
42         Websocket     systemServiceInstance `json:"arvados-ws"`
43         Workbench1    systemServiceInstance `json:"arvados-workbench"`
44 }
45
46 type systemServiceInstance struct {
47         Listen   string
48         TLS      bool
49         Insecure bool
50 }
51
52 func (ldr *Loader) applyDeprecatedConfig(cfg *arvados.Config) error {
53         var dc deprecatedConfig
54         err := yaml.Unmarshal(ldr.configdata, &dc)
55         if err != nil {
56                 return err
57         }
58         hostname, err := os.Hostname()
59         if err != nil {
60                 return err
61         }
62         for id, dcluster := range dc.Clusters {
63                 cluster, ok := cfg.Clusters[id]
64                 if !ok {
65                         return fmt.Errorf("can't load legacy config %q that is not present in current config", id)
66                 }
67                 for name, np := range dcluster.NodeProfiles {
68                         if name == "*" || name == os.Getenv("ARVADOS_NODE_PROFILE") || name == hostname {
69                                 name = "localhost"
70                         } else if ldr.Logger != nil {
71                                 ldr.Logger.Warnf("overriding Clusters.%s.Services using Clusters.%s.NodeProfiles.%s (guessing %q is a hostname)", id, id, name, name)
72                         }
73                         applyDeprecatedNodeProfile(name, np.RailsAPI, &cluster.Services.RailsAPI)
74                         applyDeprecatedNodeProfile(name, np.Controller, &cluster.Services.Controller)
75                         applyDeprecatedNodeProfile(name, np.DispatchCloud, &cluster.Services.DispatchCloud)
76                 }
77                 if dst, n := &cluster.API.MaxItemsPerResponse, dcluster.RequestLimits.MaxItemsPerResponse; n != nil && *n != *dst {
78                         *dst = *n
79                 }
80                 if dst, n := &cluster.API.MaxRequestAmplification, dcluster.RequestLimits.MultiClusterRequestConcurrency; n != nil && *n != *dst {
81                         *dst = *n
82                 }
83                 cfg.Clusters[id] = cluster
84         }
85         return nil
86 }
87
88 func applyDeprecatedNodeProfile(hostname string, ssi systemServiceInstance, svc *arvados.Service) {
89         scheme := "https"
90         if !ssi.TLS {
91                 scheme = "http"
92         }
93         if svc.InternalURLs == nil {
94                 svc.InternalURLs = map[arvados.URL]arvados.ServiceInstance{}
95         }
96         host := ssi.Listen
97         if host == "" {
98                 return
99         }
100         if strings.HasPrefix(host, ":") {
101                 host = hostname + host
102         }
103         svc.InternalURLs[arvados.URL{Scheme: scheme, Host: host}] = arvados.ServiceInstance{}
104 }
105
106 func (ldr *Loader) loadOldConfigHelper(component, path string, target interface{}) error {
107         if path == "" {
108                 return nil
109         }
110         buf, err := ioutil.ReadFile(path)
111         if err != nil {
112                 return err
113         }
114
115         ldr.Logger.Warnf("you should remove the legacy %v config file (%s) after migrating all config keys to the cluster configuration file (%s)", component, path, ldr.Path)
116
117         err = yaml.Unmarshal(buf, target)
118         if err != nil {
119                 return fmt.Errorf("%s: %s", path, err)
120         }
121         return nil
122 }
123
124 type oldCrunchDispatchSlurmConfig struct {
125         Client *arvados.Client
126
127         SbatchArguments *[]string
128         PollPeriod      *arvados.Duration
129         PrioritySpread  *int64
130
131         // crunch-run command to invoke. The container UUID will be
132         // appended. If nil, []string{"crunch-run"} will be used.
133         //
134         // Example: []string{"crunch-run", "--cgroup-parent-subsystem=memory"}
135         CrunchRunCommand *[]string
136
137         // Extra RAM to reserve (in Bytes) for SLURM job, in addition
138         // to the amount specified in the container's RuntimeConstraints
139         ReserveExtraRAM *int64
140
141         // Minimum time between two attempts to run the same container
142         MinRetryPeriod *arvados.Duration
143
144         // Batch size for container queries
145         BatchSize *int64
146 }
147
148 const defaultCrunchDispatchSlurmConfigPath = "/etc/arvados/crunch-dispatch-slurm/crunch-dispatch-slurm.yml"
149
150 func loadOldClientConfig(cluster *arvados.Cluster, client *arvados.Client) {
151         if client == nil {
152                 return
153         }
154         if client.APIHost != "" {
155                 cluster.Services.Controller.ExternalURL.Host = client.APIHost
156         }
157         if client.Scheme != "" {
158                 cluster.Services.Controller.ExternalURL.Scheme = client.Scheme
159         } else {
160                 cluster.Services.Controller.ExternalURL.Scheme = "https"
161         }
162         if client.AuthToken != "" {
163                 cluster.SystemRootToken = client.AuthToken
164         }
165         cluster.TLS.Insecure = client.Insecure
166         ks := ""
167         for i, u := range client.KeepServiceURIs {
168                 if i > 0 {
169                         ks += " "
170                 }
171                 ks += u
172         }
173         cluster.Containers.SLURM.SbatchEnvironmentVariables = map[string]string{"ARVADOS_KEEP_SERVICES": ks}
174 }
175
176 // update config using values from an crunch-dispatch-slurm config file.
177 func (ldr *Loader) loadOldCrunchDispatchSlurmConfig(cfg *arvados.Config) error {
178         if ldr.CrunchDispatchSlurmPath == "" {
179                 return nil
180         }
181         var oc oldCrunchDispatchSlurmConfig
182         err := ldr.loadOldConfigHelper("crunch-dispatch-slurm", ldr.CrunchDispatchSlurmPath, &oc)
183         if os.IsNotExist(err) && (ldr.CrunchDispatchSlurmPath == defaultCrunchDispatchSlurmConfigPath) {
184                 return nil
185         } else if err != nil {
186                 return err
187         }
188
189         cluster, err := cfg.GetCluster("")
190         if err != nil {
191                 return err
192         }
193
194         loadOldClientConfig(cluster, oc.Client)
195
196         if oc.SbatchArguments != nil {
197                 cluster.Containers.SLURM.SbatchArgumentsList = *oc.SbatchArguments
198         }
199         if oc.PollPeriod != nil {
200                 cluster.Containers.CloudVMs.PollInterval = *oc.PollPeriod
201         }
202         if oc.PrioritySpread != nil {
203                 cluster.Containers.SLURM.PrioritySpread = *oc.PrioritySpread
204         }
205         if oc.CrunchRunCommand != nil {
206                 if len(*oc.CrunchRunCommand) >= 1 {
207                         cluster.Containers.CrunchRunCommand = (*oc.CrunchRunCommand)[0]
208                 }
209                 if len(*oc.CrunchRunCommand) >= 2 {
210                         cluster.Containers.CrunchRunArgumentsList = (*oc.CrunchRunCommand)[1:]
211                 }
212         }
213         if oc.ReserveExtraRAM != nil {
214                 cluster.Containers.ReserveExtraRAM = arvados.ByteSize(*oc.ReserveExtraRAM)
215         }
216         if oc.MinRetryPeriod != nil {
217                 cluster.Containers.MinRetryPeriod = *oc.MinRetryPeriod
218         }
219         if oc.BatchSize != nil {
220                 cluster.API.MaxItemsPerResponse = int(*oc.BatchSize)
221         }
222
223         cfg.Clusters[cluster.ClusterID] = *cluster
224         return nil
225 }
226
227 type oldWsConfig struct {
228         Client       *arvados.Client
229         Postgres     *arvados.PostgreSQLConnection
230         PostgresPool *int
231         Listen       *string
232         LogLevel     *string
233         LogFormat    *string
234
235         PingTimeout      *arvados.Duration
236         ClientEventQueue *int
237         ServerEventQueue *int
238
239         ManagementToken *string
240 }
241
242 const defaultWebsocketConfigPath = "/etc/arvados/ws/ws.yml"
243
244 // update config using values from an crunch-dispatch-slurm config file.
245 func (ldr *Loader) loadOldWebsocketConfig(cfg *arvados.Config) error {
246         if ldr.WebsocketPath == "" {
247                 return nil
248         }
249         var oc oldWsConfig
250         err := ldr.loadOldConfigHelper("arvados-ws", ldr.WebsocketPath, &oc)
251         if os.IsNotExist(err) && ldr.WebsocketPath == defaultWebsocketConfigPath {
252                 return nil
253         } else if err != nil {
254                 return err
255         }
256
257         cluster, err := cfg.GetCluster("")
258         if err != nil {
259                 return err
260         }
261
262         loadOldClientConfig(cluster, oc.Client)
263
264         if oc.Postgres != nil {
265                 cluster.PostgreSQL.Connection = *oc.Postgres
266         }
267         if oc.PostgresPool != nil {
268                 cluster.PostgreSQL.ConnectionPool = *oc.PostgresPool
269         }
270         if oc.Listen != nil {
271                 cluster.Services.Websocket.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
272         }
273         if oc.LogLevel != nil {
274                 cluster.SystemLogs.LogLevel = *oc.LogLevel
275         }
276         if oc.LogFormat != nil {
277                 cluster.SystemLogs.Format = *oc.LogFormat
278         }
279         if oc.PingTimeout != nil {
280                 cluster.API.SendTimeout = *oc.PingTimeout
281         }
282         if oc.ClientEventQueue != nil {
283                 cluster.API.WebsocketClientEventQueue = *oc.ClientEventQueue
284         }
285         if oc.ServerEventQueue != nil {
286                 cluster.API.WebsocketServerEventQueue = *oc.ServerEventQueue
287         }
288         if oc.ManagementToken != nil {
289                 cluster.ManagementToken = *oc.ManagementToken
290         }
291
292         cfg.Clusters[cluster.ClusterID] = *cluster
293         return nil
294 }
295
296 type oldKeepProxyConfig struct {
297         Client          *arvados.Client
298         Listen          *string
299         DisableGet      *bool
300         DisablePut      *bool
301         DefaultReplicas *int
302         Timeout         *arvados.Duration
303         PIDFile         *string
304         Debug           *bool
305         ManagementToken *string
306 }
307
308 const defaultKeepproxyConfigPath = "/etc/arvados/keepproxy/keepproxy.yml"
309
310 func (ldr *Loader) loadOldKeepproxyConfig(cfg *arvados.Config) error {
311         if ldr.KeepproxyPath == "" {
312                 return nil
313         }
314         var oc oldKeepProxyConfig
315         err := ldr.loadOldConfigHelper("keepproxy", ldr.KeepproxyPath, &oc)
316         if os.IsNotExist(err) && ldr.KeepproxyPath == defaultKeepproxyConfigPath {
317                 return nil
318         } else if err != nil {
319                 return err
320         }
321
322         cluster, err := cfg.GetCluster("")
323         if err != nil {
324                 return err
325         }
326
327         loadOldClientConfig(cluster, oc.Client)
328
329         if oc.Listen != nil {
330                 cluster.Services.Keepproxy.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
331         }
332         if oc.DefaultReplicas != nil {
333                 cluster.Collections.DefaultReplication = *oc.DefaultReplicas
334         }
335         if oc.Timeout != nil {
336                 cluster.API.KeepServiceRequestTimeout = *oc.Timeout
337         }
338         if oc.Debug != nil {
339                 if *oc.Debug && cluster.SystemLogs.LogLevel != "debug" {
340                         cluster.SystemLogs.LogLevel = "debug"
341                 } else if !*oc.Debug && cluster.SystemLogs.LogLevel != "info" {
342                         cluster.SystemLogs.LogLevel = "info"
343                 }
344         }
345         if oc.ManagementToken != nil {
346                 cluster.ManagementToken = *oc.ManagementToken
347         }
348
349         // The following legacy options are no longer supported. If they are set to
350         // true or PIDFile has a value, error out and notify the user
351         unsupportedEntry := func(cfgEntry string) error {
352                 return fmt.Errorf("the keepproxy %s configuration option is no longer supported, please remove it from your configuration file", cfgEntry)
353         }
354         if oc.DisableGet != nil && *oc.DisableGet {
355                 return unsupportedEntry("DisableGet")
356         }
357         if oc.DisablePut != nil && *oc.DisablePut {
358                 return unsupportedEntry("DisablePut")
359         }
360         if oc.PIDFile != nil && *oc.PIDFile != "" {
361                 return unsupportedEntry("PIDFile")
362         }
363
364         cfg.Clusters[cluster.ClusterID] = *cluster
365         return nil
366 }
367
368 const defaultKeepWebConfigPath = "/etc/arvados/keep-web/keep-web.yml"
369
370 type oldKeepWebConfig struct {
371         Client *arvados.Client
372
373         Listen string
374
375         AnonymousTokens    []string
376         AttachmentOnlyHost string
377         TrustAllContent    bool
378
379         Cache struct {
380                 TTL                  arvados.Duration
381                 UUIDTTL              arvados.Duration
382                 MaxCollectionEntries int
383                 MaxCollectionBytes   int64
384                 MaxPermissionEntries int
385                 MaxUUIDEntries       int
386         }
387
388         // Hack to support old command line flag, which is a bool
389         // meaning "get actual token from environment".
390         deprecatedAllowAnonymous bool
391
392         // Authorization token to be included in all health check requests.
393         ManagementToken string
394 }
395
396 func (ldr *Loader) loadOldKeepWebConfig(cfg *arvados.Config) error {
397         if ldr.KeepWebPath == "" {
398                 return nil
399         }
400         var oc oldKeepWebConfig
401         err := ldr.loadOldConfigHelper("keep-web", ldr.KeepWebPath, &oc)
402         if os.IsNotExist(err) && ldr.KeepWebPath == defaultKeepWebConfigPath {
403                 return nil
404         } else if err != nil {
405                 return err
406         }
407
408         cluster, err := cfg.GetCluster("")
409         if err != nil {
410                 return err
411         }
412
413         loadOldClientConfig(cluster, oc.Client)
414
415         cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: oc.Listen}] = arvados.ServiceInstance{}
416         cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: oc.Listen}] = arvados.ServiceInstance{}
417         cluster.Services.WebDAVDownload.ExternalURL = arvados.URL{Host: oc.AttachmentOnlyHost}
418         cluster.TLS.Insecure = oc.Client.Insecure
419         cluster.ManagementToken = oc.ManagementToken
420         cluster.Collections.TrustAllContent = oc.TrustAllContent
421         cluster.Collections.WebDAVCache.TTL = oc.Cache.TTL
422         cluster.Collections.WebDAVCache.UUIDTTL = oc.Cache.UUIDTTL
423         cluster.Collections.WebDAVCache.MaxCollectionEntries = oc.Cache.MaxCollectionEntries
424         cluster.Collections.WebDAVCache.MaxCollectionBytes = oc.Cache.MaxCollectionBytes
425         cluster.Collections.WebDAVCache.MaxPermissionEntries = oc.Cache.MaxPermissionEntries
426         cluster.Collections.WebDAVCache.MaxUUIDEntries = oc.Cache.MaxUUIDEntries
427         if len(oc.AnonymousTokens) > 0 {
428                 cluster.Users.AnonymousUserToken = oc.AnonymousTokens[0]
429                 if len(oc.AnonymousTokens) > 1 {
430                         ldr.Logger.Warn("More than 1 anonymous tokens configured, using only the first and discarding the rest.")
431                 }
432         }
433
434         cfg.Clusters[cluster.ClusterID] = *cluster
435         return nil
436 }
437
438 const defaultGitHttpdConfigPath = "/etc/arvados/git-httpd/git-httpd.yml"
439
440 type oldGitHttpdConfig struct {
441         Client          *arvados.Client
442         Listen          string
443         GitCommand      string
444         GitoliteHome    string
445         RepoRoot        string
446         ManagementToken string
447 }
448
449 func (ldr *Loader) loadOldGitHttpdConfig(cfg *arvados.Config) error {
450         if ldr.GitHttpdPath == "" {
451                 return nil
452         }
453         var oc oldGitHttpdConfig
454         err := ldr.loadOldConfigHelper("arv-git-httpd", ldr.GitHttpdPath, &oc)
455         if os.IsNotExist(err) && ldr.GitHttpdPath == defaultGitHttpdConfigPath {
456                 return nil
457         } else if err != nil {
458                 return err
459         }
460
461         cluster, err := cfg.GetCluster("")
462         if err != nil {
463                 return err
464         }
465
466         loadOldClientConfig(cluster, oc.Client)
467
468         cluster.Services.GitHTTP.InternalURLs[arvados.URL{Host: oc.Listen}] = arvados.ServiceInstance{}
469         cluster.TLS.Insecure = oc.Client.Insecure
470         cluster.ManagementToken = oc.ManagementToken
471         cluster.Git.GitCommand = oc.GitCommand
472         cluster.Git.GitoliteHome = oc.GitoliteHome
473         cluster.Git.Repositories = oc.RepoRoot
474
475         cfg.Clusters[cluster.ClusterID] = *cluster
476         return nil
477 }
478
479 const defaultKeepBalanceConfigPath = "/etc/arvados/keep-balance/keep-balance.yml"
480
481 type oldKeepBalanceConfig struct {
482         Client              *arvados.Client
483         Listen              *string
484         KeepServiceTypes    *[]string
485         KeepServiceList     *arvados.KeepServiceList
486         RunPeriod           *arvados.Duration
487         CollectionBatchSize *int
488         CollectionBuffers   *int
489         RequestTimeout      *arvados.Duration
490         LostBlocksFile      *string
491         ManagementToken     *string
492 }
493
494 func (ldr *Loader) loadOldKeepBalanceConfig(cfg *arvados.Config) error {
495         if ldr.KeepBalancePath == "" {
496                 return nil
497         }
498         var oc oldKeepBalanceConfig
499         err := ldr.loadOldConfigHelper("keep-balance", ldr.KeepBalancePath, &oc)
500         if os.IsNotExist(err) && ldr.KeepBalancePath == defaultKeepBalanceConfigPath {
501                 return nil
502         } else if err != nil {
503                 return err
504         }
505
506         cluster, err := cfg.GetCluster("")
507         if err != nil {
508                 return err
509         }
510
511         loadOldClientConfig(cluster, oc.Client)
512
513         if oc.Listen != nil {
514                 cluster.Services.Keepbalance.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
515         }
516         if oc.ManagementToken != nil {
517                 cluster.ManagementToken = *oc.ManagementToken
518         }
519         if oc.RunPeriod != nil {
520                 cluster.Collections.BalancePeriod = *oc.RunPeriod
521         }
522         if oc.LostBlocksFile != nil {
523                 cluster.Collections.BlobMissingReport = *oc.LostBlocksFile
524         }
525         if oc.CollectionBatchSize != nil {
526                 cluster.Collections.BalanceCollectionBatch = *oc.CollectionBatchSize
527         }
528         if oc.CollectionBuffers != nil {
529                 cluster.Collections.BalanceCollectionBuffers = *oc.CollectionBuffers
530         }
531         if oc.RequestTimeout != nil {
532                 cluster.API.KeepServiceRequestTimeout = *oc.RequestTimeout
533         }
534
535         msg := "The %s configuration option is no longer supported. Please remove it from your configuration file. Keep-balance will operate on all configured volumes."
536
537         // If the keep service type provided is "disk" silently ignore it, since
538         // this is what ends up being done anyway.
539         if oc.KeepServiceTypes != nil {
540                 numTypes := len(*oc.KeepServiceTypes)
541                 if numTypes != 0 && !(numTypes == 1 && (*oc.KeepServiceTypes)[0] == "disk") {
542                         return fmt.Errorf(msg, "KeepServiceType")
543                 }
544         }
545
546         if oc.KeepServiceList != nil {
547                 return fmt.Errorf(msg, "KeepServiceList")
548         }
549
550         cfg.Clusters[cluster.ClusterID] = *cluster
551         return nil
552 }
553
554 func (ldr *Loader) loadOldEnvironmentVariables(cfg *arvados.Config) error {
555         if os.Getenv("ARVADOS_API_TOKEN") == "" && os.Getenv("ARVADOS_API_HOST") == "" {
556                 return nil
557         }
558         cluster, err := cfg.GetCluster("")
559         if err != nil {
560                 return err
561         }
562         if tok := os.Getenv("ARVADOS_API_TOKEN"); tok != "" && cluster.SystemRootToken == "" {
563                 ldr.Logger.Warn("SystemRootToken missing from cluster config, falling back to ARVADOS_API_TOKEN environment variable")
564                 cluster.SystemRootToken = tok
565         }
566         if apihost := os.Getenv("ARVADOS_API_HOST"); apihost != "" && cluster.Services.Controller.ExternalURL.Host == "" {
567                 ldr.Logger.Warn("Services.Controller.ExternalURL missing from cluster config, falling back to ARVADOS_API_HOST(_INSECURE) environment variables")
568                 u, err := url.Parse("https://" + apihost)
569                 if err != nil {
570                         return fmt.Errorf("cannot parse ARVADOS_API_HOST: %s", err)
571                 }
572                 cluster.Services.Controller.ExternalURL = arvados.URL(*u)
573                 if i := os.Getenv("ARVADOS_API_HOST_INSECURE"); i != "" && i != "0" {
574                         cluster.TLS.Insecure = true
575                 }
576         }
577         cfg.Clusters[cluster.ClusterID] = *cluster
578         return nil
579 }