15003: Load config over defaults.
[arvados.git] / sdk / go / arvados / config.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "encoding/json"
9         "errors"
10         "fmt"
11         "net/url"
12         "os"
13
14         "git.curoverse.com/arvados.git/sdk/go/config"
15 )
16
17 const DefaultConfigFile = "/etc/arvados/config.yml"
18
19 type Config struct {
20         Clusters map[string]Cluster
21 }
22
23 // GetConfig returns the current system config, loading it from
24 // configFile if needed.
25 func GetConfig(configFile string) (*Config, error) {
26         var cfg Config
27         err := config.LoadFile(&cfg, configFile)
28         return &cfg, err
29 }
30
31 // GetCluster returns the cluster ID and config for the given
32 // cluster, or the default/only configured cluster if clusterID is "".
33 func (sc *Config) GetCluster(clusterID string) (*Cluster, error) {
34         if clusterID == "" {
35                 if len(sc.Clusters) == 0 {
36                         return nil, fmt.Errorf("no clusters configured")
37                 } else if len(sc.Clusters) > 1 {
38                         return nil, fmt.Errorf("multiple clusters configured, cannot choose")
39                 } else {
40                         for id, cc := range sc.Clusters {
41                                 cc.ClusterID = id
42                                 return &cc, nil
43                         }
44                 }
45         }
46         if cc, ok := sc.Clusters[clusterID]; !ok {
47                 return nil, fmt.Errorf("cluster %q is not configured", clusterID)
48         } else {
49                 cc.ClusterID = clusterID
50                 return &cc, nil
51         }
52 }
53
54 type RequestLimits struct {
55         MaxItemsPerResponse            int
56         MultiClusterRequestConcurrency int
57 }
58
59 type Cluster struct {
60         ClusterID          string `json:"-"`
61         ManagementToken    string
62         SystemRootToken    string
63         Services           Services
64         NodeProfiles       map[string]NodeProfile
65         InstanceTypes      InstanceTypeMap
66         CloudVMs           CloudVMs
67         Dispatch           Dispatch
68         HTTPRequestTimeout Duration
69         RemoteClusters     map[string]RemoteCluster
70         PostgreSQL         PostgreSQL
71         RequestLimits      RequestLimits
72         Logging            Logging
73         TLS                TLS
74 }
75
76 type Services struct {
77         Controller    Service
78         DispatchCloud Service
79         Health        Service
80         Keepbalance   Service
81         Keepproxy     Service
82         Keepstore     Service
83         Keepweb       Service
84         Nodemanager   Service
85         RailsAPI      Service
86         Websocket     Service
87         Workbench     Service
88 }
89
90 type Service struct {
91         InternalURLs map[URL]ServiceInstance
92         ExternalURL  URL
93 }
94
95 // URL is a url.URL that is also usable as a JSON key/value.
96 type URL url.URL
97
98 // UnmarshalText implements encoding.TextUnmarshaler so URL can be
99 // used as a JSON key/value.
100 func (su *URL) UnmarshalText(text []byte) error {
101         u, err := url.Parse(string(text))
102         if err == nil {
103                 *su = URL(*u)
104         }
105         return err
106 }
107
108 func (su URL) MarshalText() ([]byte, error) {
109         return []byte(fmt.Sprintf("%s", (*url.URL)(&su).String())), nil
110 }
111
112 type ServiceInstance struct{}
113
114 type Logging struct {
115         Level  string
116         Format string
117 }
118
119 type PostgreSQL struct {
120         Connection     PostgreSQLConnection
121         ConnectionPool int
122 }
123
124 type PostgreSQLConnection map[string]string
125
126 type RemoteCluster struct {
127         // API endpoint host or host:port; default is {id}.arvadosapi.com
128         Host string
129         // Perform a proxy request when a local client requests an
130         // object belonging to this remote.
131         Proxy bool
132         // Scheme, default "https". Can be set to "http" for testing.
133         Scheme string
134         // Disable TLS verify. Can be set to true for testing.
135         Insecure bool
136 }
137
138 type InstanceType struct {
139         Name            string
140         ProviderType    string
141         VCPUs           int
142         RAM             ByteSize
143         Scratch         ByteSize
144         IncludedScratch ByteSize
145         AddedScratch    ByteSize
146         Price           float64
147         Preemptible     bool
148 }
149
150 type Dispatch struct {
151         // PEM encoded SSH key (RSA, DSA, or ECDSA) able to log in to
152         // cloud VMs.
153         PrivateKey string
154
155         // Max time for workers to come up before abandoning stale
156         // locks from previous run
157         StaleLockTimeout Duration
158
159         // Interval between queue polls
160         PollInterval Duration
161
162         // Interval between probes to each worker
163         ProbeInterval Duration
164
165         // Maximum total worker probes per second
166         MaxProbesPerSecond int
167
168         // Time before repeating SIGTERM when killing a container
169         TimeoutSignal Duration
170
171         // Time to give up on SIGTERM and write off the worker
172         TimeoutTERM Duration
173 }
174
175 type CloudVMs struct {
176         // Shell command that exits zero IFF the VM is fully booted
177         // and ready to run containers, e.g., "mount | grep
178         // /encrypted-tmp"
179         BootProbeCommand string
180
181         // Listening port (name or number) of SSH servers on worker
182         // VMs
183         SSHPort string
184
185         SyncInterval Duration
186
187         // Maximum idle time before automatic shutdown
188         TimeoutIdle Duration
189
190         // Maximum booting time before automatic shutdown
191         TimeoutBooting Duration
192
193         // Maximum time with no successful probes before automatic shutdown
194         TimeoutProbe Duration
195
196         // Time after shutdown to retry shutdown
197         TimeoutShutdown Duration
198
199         // Maximum create/destroy-instance operations per second
200         MaxCloudOpsPerSecond int
201
202         ImageID string
203
204         Driver           string
205         DriverParameters json.RawMessage
206 }
207
208 type InstanceTypeMap map[string]InstanceType
209
210 var errDuplicateInstanceTypeName = errors.New("duplicate instance type name")
211
212 // UnmarshalJSON handles old config files that provide an array of
213 // instance types instead of a hash.
214 func (it *InstanceTypeMap) UnmarshalJSON(data []byte) error {
215         if len(data) > 0 && data[0] == '[' {
216                 var arr []InstanceType
217                 err := json.Unmarshal(data, &arr)
218                 if err != nil {
219                         return err
220                 }
221                 if len(arr) == 0 {
222                         *it = nil
223                         return nil
224                 }
225                 *it = make(map[string]InstanceType, len(arr))
226                 for _, t := range arr {
227                         if _, ok := (*it)[t.Name]; ok {
228                                 return errDuplicateInstanceTypeName
229                         }
230                         if t.ProviderType == "" {
231                                 t.ProviderType = t.Name
232                         }
233                         if t.Scratch == 0 {
234                                 t.Scratch = t.IncludedScratch + t.AddedScratch
235                         } else if t.AddedScratch == 0 {
236                                 t.AddedScratch = t.Scratch - t.IncludedScratch
237                         } else if t.IncludedScratch == 0 {
238                                 t.IncludedScratch = t.Scratch - t.AddedScratch
239                         }
240
241                         if t.Scratch != (t.IncludedScratch + t.AddedScratch) {
242                                 return fmt.Errorf("%v: Scratch != (IncludedScratch + AddedScratch)", t.Name)
243                         }
244                         (*it)[t.Name] = t
245                 }
246                 return nil
247         }
248         var hash map[string]InstanceType
249         err := json.Unmarshal(data, &hash)
250         if err != nil {
251                 return err
252         }
253         // Fill in Name field (and ProviderType field, if not
254         // specified) using hash key.
255         *it = InstanceTypeMap(hash)
256         for name, t := range *it {
257                 t.Name = name
258                 if t.ProviderType == "" {
259                         t.ProviderType = name
260                 }
261                 (*it)[name] = t
262         }
263         return nil
264 }
265
266 // GetNodeProfile returns a NodeProfile for the given hostname. An
267 // error is returned if the appropriate configuration can't be
268 // determined (e.g., this does not appear to be a system node). If
269 // node is empty, use the OS-reported hostname.
270 func (cc *Cluster) GetNodeProfile(node string) (*NodeProfile, error) {
271         if node == "" {
272                 hostname, err := os.Hostname()
273                 if err != nil {
274                         return nil, err
275                 }
276                 node = hostname
277         }
278         if cfg, ok := cc.NodeProfiles[node]; ok {
279                 return &cfg, nil
280         }
281         // If node is not listed, but "*" gives a default system node
282         // config, use the default config.
283         if cfg, ok := cc.NodeProfiles["*"]; ok {
284                 return &cfg, nil
285         }
286         return nil, fmt.Errorf("config does not provision host %q as a system node", node)
287 }
288
289 type NodeProfile struct {
290         Controller    SystemServiceInstance `json:"arvados-controller"`
291         Health        SystemServiceInstance `json:"arvados-health"`
292         Keepbalance   SystemServiceInstance `json:"keep-balance"`
293         Keepproxy     SystemServiceInstance `json:"keepproxy"`
294         Keepstore     SystemServiceInstance `json:"keepstore"`
295         Keepweb       SystemServiceInstance `json:"keep-web"`
296         Nodemanager   SystemServiceInstance `json:"arvados-node-manager"`
297         DispatchCloud SystemServiceInstance `json:"arvados-dispatch-cloud"`
298         RailsAPI      SystemServiceInstance `json:"arvados-api-server"`
299         Websocket     SystemServiceInstance `json:"arvados-ws"`
300         Workbench     SystemServiceInstance `json:"arvados-workbench"`
301 }
302
303 type ServiceName string
304
305 const (
306         ServiceNameRailsAPI      ServiceName = "arvados-api-server"
307         ServiceNameController    ServiceName = "arvados-controller"
308         ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud"
309         ServiceNameNodemanager   ServiceName = "arvados-node-manager"
310         ServiceNameWorkbench     ServiceName = "arvados-workbench"
311         ServiceNameWebsocket     ServiceName = "arvados-ws"
312         ServiceNameKeepbalance   ServiceName = "keep-balance"
313         ServiceNameKeepweb       ServiceName = "keep-web"
314         ServiceNameKeepproxy     ServiceName = "keepproxy"
315         ServiceNameKeepstore     ServiceName = "keepstore"
316 )
317
318 // ServicePorts returns the configured listening address (or "" if
319 // disabled) for each service on the node.
320 func (np *NodeProfile) ServicePorts() map[ServiceName]string {
321         return map[ServiceName]string{
322                 ServiceNameRailsAPI:      np.RailsAPI.Listen,
323                 ServiceNameController:    np.Controller.Listen,
324                 ServiceNameDispatchCloud: np.DispatchCloud.Listen,
325                 ServiceNameNodemanager:   np.Nodemanager.Listen,
326                 ServiceNameWorkbench:     np.Workbench.Listen,
327                 ServiceNameWebsocket:     np.Websocket.Listen,
328                 ServiceNameKeepbalance:   np.Keepbalance.Listen,
329                 ServiceNameKeepweb:       np.Keepweb.Listen,
330                 ServiceNameKeepproxy:     np.Keepproxy.Listen,
331                 ServiceNameKeepstore:     np.Keepstore.Listen,
332         }
333 }
334
335 func (h RequestLimits) GetMultiClusterRequestConcurrency() int {
336         if h.MultiClusterRequestConcurrency == 0 {
337                 return 4
338         }
339         return h.MultiClusterRequestConcurrency
340 }
341
342 func (h RequestLimits) GetMaxItemsPerResponse() int {
343         if h.MaxItemsPerResponse == 0 {
344                 return 1000
345         }
346         return h.MaxItemsPerResponse
347 }
348
349 type SystemServiceInstance struct {
350         Listen   string
351         TLS      bool
352         Insecure bool
353 }
354
355 type TLS struct {
356         Certificate string
357         Key         string
358         Insecure    bool
359 }