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