Merge branch '15928-fs-deadlock'
[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.arvados.org/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         if oc.Listen != nil {
416                 cluster.Services.WebDAV.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
417                 cluster.Services.WebDAVDownload.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
418         }
419         if oc.AttachmentOnlyHost != nil {
420                 cluster.Services.WebDAVDownload.ExternalURL = arvados.URL{Host: *oc.AttachmentOnlyHost}
421         }
422         if oc.ManagementToken != nil {
423                 cluster.ManagementToken = *oc.ManagementToken
424         }
425         if oc.TrustAllContent != nil {
426                 cluster.Collections.TrustAllContent = *oc.TrustAllContent
427         }
428         if oc.Cache.TTL != nil {
429                 cluster.Collections.WebDAVCache.TTL = *oc.Cache.TTL
430         }
431         if oc.Cache.UUIDTTL != nil {
432                 cluster.Collections.WebDAVCache.UUIDTTL = *oc.Cache.UUIDTTL
433         }
434         if oc.Cache.MaxCollectionEntries != nil {
435                 cluster.Collections.WebDAVCache.MaxCollectionEntries = *oc.Cache.MaxCollectionEntries
436         }
437         if oc.Cache.MaxCollectionBytes != nil {
438                 cluster.Collections.WebDAVCache.MaxCollectionBytes = *oc.Cache.MaxCollectionBytes
439         }
440         if oc.Cache.MaxPermissionEntries != nil {
441                 cluster.Collections.WebDAVCache.MaxPermissionEntries = *oc.Cache.MaxPermissionEntries
442         }
443         if oc.Cache.MaxUUIDEntries != nil {
444                 cluster.Collections.WebDAVCache.MaxUUIDEntries = *oc.Cache.MaxUUIDEntries
445         }
446         if oc.AnonymousTokens != nil {
447                 if len(*oc.AnonymousTokens) > 0 {
448                         cluster.Users.AnonymousUserToken = (*oc.AnonymousTokens)[0]
449                         if len(*oc.AnonymousTokens) > 1 {
450                                 ldr.Logger.Warn("More than 1 anonymous tokens configured, using only the first and discarding the rest.")
451                         }
452                 }
453         }
454
455         cfg.Clusters[cluster.ClusterID] = *cluster
456         return nil
457 }
458
459 const defaultGitHttpdConfigPath = "/etc/arvados/git-httpd/git-httpd.yml"
460
461 type oldGitHttpdConfig struct {
462         Client          *arvados.Client
463         Listen          *string
464         GitCommand      *string
465         GitoliteHome    *string
466         RepoRoot        *string
467         ManagementToken *string
468 }
469
470 func (ldr *Loader) loadOldGitHttpdConfig(cfg *arvados.Config) error {
471         if ldr.GitHttpdPath == "" {
472                 return nil
473         }
474         var oc oldGitHttpdConfig
475         err := ldr.loadOldConfigHelper("arv-git-httpd", ldr.GitHttpdPath, &oc)
476         if os.IsNotExist(err) && ldr.GitHttpdPath == defaultGitHttpdConfigPath {
477                 return nil
478         } else if err != nil {
479                 return err
480         }
481
482         cluster, err := cfg.GetCluster("")
483         if err != nil {
484                 return err
485         }
486
487         loadOldClientConfig(cluster, oc.Client)
488
489         if oc.Listen != nil {
490                 cluster.Services.GitHTTP.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
491         }
492         if oc.ManagementToken != nil {
493                 cluster.ManagementToken = *oc.ManagementToken
494         }
495         if oc.GitCommand != nil {
496                 cluster.Git.GitCommand = *oc.GitCommand
497         }
498         if oc.GitoliteHome != nil {
499                 cluster.Git.GitoliteHome = *oc.GitoliteHome
500         }
501         if oc.RepoRoot != nil {
502                 cluster.Git.Repositories = *oc.RepoRoot
503         }
504
505         cfg.Clusters[cluster.ClusterID] = *cluster
506         return nil
507 }
508
509 const defaultKeepBalanceConfigPath = "/etc/arvados/keep-balance/keep-balance.yml"
510
511 type oldKeepBalanceConfig struct {
512         Client              *arvados.Client
513         Listen              *string
514         KeepServiceTypes    *[]string
515         KeepServiceList     *arvados.KeepServiceList
516         RunPeriod           *arvados.Duration
517         CollectionBatchSize *int
518         CollectionBuffers   *int
519         RequestTimeout      *arvados.Duration
520         LostBlocksFile      *string
521         ManagementToken     *string
522 }
523
524 func (ldr *Loader) loadOldKeepBalanceConfig(cfg *arvados.Config) error {
525         if ldr.KeepBalancePath == "" {
526                 return nil
527         }
528         var oc oldKeepBalanceConfig
529         err := ldr.loadOldConfigHelper("keep-balance", ldr.KeepBalancePath, &oc)
530         if os.IsNotExist(err) && ldr.KeepBalancePath == defaultKeepBalanceConfigPath {
531                 return nil
532         } else if err != nil {
533                 return err
534         }
535
536         cluster, err := cfg.GetCluster("")
537         if err != nil {
538                 return err
539         }
540
541         loadOldClientConfig(cluster, oc.Client)
542
543         if oc.Listen != nil {
544                 cluster.Services.Keepbalance.InternalURLs[arvados.URL{Host: *oc.Listen}] = arvados.ServiceInstance{}
545         }
546         if oc.ManagementToken != nil {
547                 cluster.ManagementToken = *oc.ManagementToken
548         }
549         if oc.RunPeriod != nil {
550                 cluster.Collections.BalancePeriod = *oc.RunPeriod
551         }
552         if oc.LostBlocksFile != nil {
553                 cluster.Collections.BlobMissingReport = *oc.LostBlocksFile
554         }
555         if oc.CollectionBatchSize != nil {
556                 cluster.Collections.BalanceCollectionBatch = *oc.CollectionBatchSize
557         }
558         if oc.CollectionBuffers != nil {
559                 cluster.Collections.BalanceCollectionBuffers = *oc.CollectionBuffers
560         }
561         if oc.RequestTimeout != nil {
562                 cluster.API.KeepServiceRequestTimeout = *oc.RequestTimeout
563         }
564
565         msg := "The %s configuration option is no longer supported. Please remove it from your configuration file. See the keep-balance upgrade notes at https://doc.arvados.org/admin/upgrading.html for more details."
566
567         // If the keep service type provided is "disk" silently ignore it, since
568         // this is what ends up being done anyway.
569         if oc.KeepServiceTypes != nil {
570                 numTypes := len(*oc.KeepServiceTypes)
571                 if numTypes != 0 && !(numTypes == 1 && (*oc.KeepServiceTypes)[0] == "disk") {
572                         return fmt.Errorf(msg, "KeepServiceTypes")
573                 }
574         }
575
576         if oc.KeepServiceList != nil {
577                 return fmt.Errorf(msg, "KeepServiceList")
578         }
579
580         cfg.Clusters[cluster.ClusterID] = *cluster
581         return nil
582 }
583
584 func (ldr *Loader) loadOldEnvironmentVariables(cfg *arvados.Config) error {
585         if os.Getenv("ARVADOS_API_TOKEN") == "" && os.Getenv("ARVADOS_API_HOST") == "" {
586                 return nil
587         }
588         cluster, err := cfg.GetCluster("")
589         if err != nil {
590                 return err
591         }
592         if tok := os.Getenv("ARVADOS_API_TOKEN"); tok != "" && cluster.SystemRootToken == "" {
593                 ldr.Logger.Warn("SystemRootToken missing from cluster config, falling back to ARVADOS_API_TOKEN environment variable")
594                 cluster.SystemRootToken = tok
595         }
596         if apihost := os.Getenv("ARVADOS_API_HOST"); apihost != "" && cluster.Services.Controller.ExternalURL.Host == "" {
597                 ldr.Logger.Warn("Services.Controller.ExternalURL missing from cluster config, falling back to ARVADOS_API_HOST(_INSECURE) environment variables")
598                 u, err := url.Parse("https://" + apihost)
599                 if err != nil {
600                         return fmt.Errorf("cannot parse ARVADOS_API_HOST: %s", err)
601                 }
602                 cluster.Services.Controller.ExternalURL = arvados.URL(*u)
603                 if i := os.Getenv("ARVADOS_API_HOST_INSECURE"); i != "" && i != "0" {
604                         cluster.TLS.Insecure = true
605                 }
606         }
607         cfg.Clusters[cluster.ClusterID] = *cluster
608         return nil
609 }