15003: Move keys from RequestLimits to API section.
[arvados.git] / lib / config / load.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         "bytes"
9         "encoding/json"
10         "errors"
11         "fmt"
12         "io"
13         "io/ioutil"
14         "os"
15         "strings"
16
17         "git.curoverse.com/arvados.git/sdk/go/arvados"
18         "github.com/ghodss/yaml"
19         "github.com/imdario/mergo"
20 )
21
22 type logger interface {
23         Warnf(string, ...interface{})
24 }
25
26 type deprRequestLimits struct {
27         MaxItemsPerResponse            *int
28         MultiClusterRequestConcurrency *int
29 }
30
31 type deprCluster struct {
32         RequestLimits deprRequestLimits
33         NodeProfiles  map[string]arvados.NodeProfile
34 }
35
36 type deprecatedConfig struct {
37         Clusters map[string]deprCluster
38 }
39
40 func LoadFile(path string, log logger) (*arvados.Config, error) {
41         f, err := os.Open(path)
42         if err != nil {
43                 return nil, err
44         }
45         defer f.Close()
46         return Load(f, log)
47 }
48
49 func Load(rdr io.Reader, log logger) (*arvados.Config, error) {
50         var cfg arvados.Config
51         buf, err := ioutil.ReadAll(rdr)
52         if err != nil {
53                 return nil, err
54         }
55
56         // Load the config into a dummy map to get the cluster ID
57         // keys, discarding the values; then set up defaults for each
58         // cluster ID; then load the real config on top of the
59         // defaults.
60         var dummy struct {
61                 Clusters map[string]struct{}
62         }
63         err = yaml.Unmarshal(buf, &dummy)
64         if err != nil {
65                 return nil, err
66         }
67         if len(dummy.Clusters) == 0 {
68                 return nil, errors.New("config does not define any clusters")
69         }
70
71         // We can't merge deep structs here; instead, we unmarshal the
72         // default & loaded config files into generic maps, merge
73         // those, and then json-encode+decode the result into the
74         // config struct type.
75         var merged map[string]interface{}
76         for id := range dummy.Clusters {
77                 var src map[string]interface{}
78                 err = yaml.Unmarshal(bytes.Replace(DefaultYAML, []byte(" xxxxx:"), []byte(" "+id+":"), -1), &src)
79                 if err != nil {
80                         return nil, fmt.Errorf("loading defaults for %s: %s", id, err)
81                 }
82                 mergo.Merge(&merged, src, mergo.WithOverride)
83         }
84         var src map[string]interface{}
85         err = yaml.Unmarshal(buf, &src)
86         if err != nil {
87                 return nil, fmt.Errorf("loading config data: %s", err)
88         }
89         mergo.Merge(&merged, src, mergo.WithOverride)
90
91         var errEnc error
92         pr, pw := io.Pipe()
93         go func() {
94                 errEnc = json.NewEncoder(pw).Encode(merged)
95                 pw.Close()
96         }()
97         err = json.NewDecoder(pr).Decode(&cfg)
98         if errEnc != nil {
99                 err = errEnc
100         }
101         if err != nil {
102                 return nil, fmt.Errorf("transcoding config data: %s", err)
103         }
104
105         var dc deprecatedConfig
106         err = yaml.Unmarshal(buf, &dc)
107         if err != nil {
108                 return nil, err
109         }
110         err = applyDeprecatedConfig(&cfg, &dc, log)
111         if err != nil {
112                 return nil, err
113         }
114         return &cfg, nil
115 }
116
117 func applyDeprecatedConfig(cfg *arvados.Config, dc *deprecatedConfig, log logger) error {
118         hostname, err := os.Hostname()
119         if err != nil {
120                 return err
121         }
122         for id, dcluster := range dc.Clusters {
123                 cluster, ok := cfg.Clusters[id]
124                 if !ok {
125                         return fmt.Errorf("can't load legacy config %q that is not present in current config", id)
126                 }
127                 for name, np := range dcluster.NodeProfiles {
128                         if name == "*" || name == os.Getenv("ARVADOS_NODE_PROFILE") || name == hostname {
129                                 applyDeprecatedNodeProfile(hostname, np.RailsAPI, &cluster.Services.RailsAPI)
130                                 applyDeprecatedNodeProfile(hostname, np.Controller, &cluster.Services.Controller)
131                                 applyDeprecatedNodeProfile(hostname, np.DispatchCloud, &cluster.Services.DispatchCloud)
132                         }
133                 }
134                 if dst, n := &cluster.API.MaxItemsPerResponse, dcluster.RequestLimits.MaxItemsPerResponse; n != nil && *n != *dst {
135                         log.Warnf("overriding Clusters.%s.API.MaxItemsPerResponse with deprecated config RequestLimits.MultiClusterRequestConcurrency = %d", id, *n)
136                         *dst = *n
137                 }
138                 if dst, n := &cluster.API.MaxRequestAmplification, dcluster.RequestLimits.MultiClusterRequestConcurrency; n != nil && *n != *dst {
139                         log.Warnf("overriding Clusters.%s.API.MaxRequestAmplification with deprecated config RequestLimits.MultiClusterRequestConcurrency = %d", id, *n)
140                         *dst = *n
141                 }
142                 cfg.Clusters[id] = cluster
143         }
144         return nil
145 }
146
147 func applyDeprecatedNodeProfile(hostname string, ssi arvados.SystemServiceInstance, svc *arvados.Service) {
148         scheme := "https"
149         if !ssi.TLS {
150                 scheme = "http"
151         }
152         if svc.InternalURLs == nil {
153                 svc.InternalURLs = map[arvados.URL]arvados.ServiceInstance{}
154         }
155         host := ssi.Listen
156         if host == "" {
157                 return
158         }
159         if strings.HasPrefix(host, ":") {
160                 host = hostname + host
161         }
162         svc.InternalURLs[arvados.URL{Scheme: scheme, Host: host}] = arvados.ServiceInstance{}
163 }