15003: Merge branch 'master' into 15003-preprocess-config
[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 API struct {
55         MaxItemsPerResponse     int
56         MaxRequestAmplification 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         API                API
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         Nodemanager   Service
84         RailsAPI      Service
85         WebDAV        Service
86         Websocket     Service
87         Workbench1    Service
88         Workbench2    Service
89 }
90
91 type Service struct {
92         InternalURLs map[URL]ServiceInstance
93         ExternalURL  URL
94 }
95
96 // URL is a url.URL that is also usable as a JSON key/value.
97 type URL url.URL
98
99 // UnmarshalText implements encoding.TextUnmarshaler so URL can be
100 // used as a JSON key/value.
101 func (su *URL) UnmarshalText(text []byte) error {
102         u, err := url.Parse(string(text))
103         if err == nil {
104                 *su = URL(*u)
105         }
106         return err
107 }
108
109 func (su URL) MarshalText() ([]byte, error) {
110         return []byte(fmt.Sprintf("%s", (*url.URL)(&su).String())), nil
111 }
112
113 type ServiceInstance struct{}
114
115 type Logging struct {
116         Level  string
117         Format string
118 }
119
120 type PostgreSQL struct {
121         Connection     PostgreSQLConnection
122         ConnectionPool int
123 }
124
125 type PostgreSQLConnection map[string]string
126
127 type RemoteCluster struct {
128         // API endpoint host or host:port; default is {id}.arvadosapi.com
129         Host string
130         // Perform a proxy request when a local client requests an
131         // object belonging to this remote.
132         Proxy bool
133         // Scheme, default "https". Can be set to "http" for testing.
134         Scheme string
135         // Disable TLS verify. Can be set to true for testing.
136         Insecure bool
137 }
138
139 type InstanceType struct {
140         Name            string
141         ProviderType    string
142         VCPUs           int
143         RAM             ByteSize
144         Scratch         ByteSize
145         IncludedScratch ByteSize
146         AddedScratch    ByteSize
147         Price           float64
148         Preemptible     bool
149 }
150
151 type Dispatch struct {
152         // PEM encoded SSH key (RSA, DSA, or ECDSA) able to log in to
153         // cloud VMs.
154         PrivateKey string
155
156         // Max time for workers to come up before abandoning stale
157         // locks from previous run
158         StaleLockTimeout Duration
159
160         // Interval between queue polls
161         PollInterval Duration
162
163         // Interval between probes to each worker
164         ProbeInterval Duration
165
166         // Maximum total worker probes per second
167         MaxProbesPerSecond int
168
169         // Time before repeating SIGTERM when killing a container
170         TimeoutSignal Duration
171
172         // Time to give up on SIGTERM and write off the worker
173         TimeoutTERM Duration
174 }
175
176 type CloudVMs struct {
177         // Shell command that exits zero IFF the VM is fully booted
178         // and ready to run containers, e.g., "mount | grep
179         // /encrypted-tmp"
180         BootProbeCommand string
181
182         // Listening port (name or number) of SSH servers on worker
183         // VMs
184         SSHPort string
185
186         SyncInterval Duration
187
188         // Maximum idle time before automatic shutdown
189         TimeoutIdle Duration
190
191         // Maximum booting time before automatic shutdown
192         TimeoutBooting Duration
193
194         // Maximum time with no successful probes before automatic shutdown
195         TimeoutProbe Duration
196
197         // Time after shutdown to retry shutdown
198         TimeoutShutdown Duration
199
200         // Maximum create/destroy-instance operations per second
201         MaxCloudOpsPerSecond int
202
203         ImageID string
204
205         Driver           string
206         DriverParameters json.RawMessage
207 }
208
209 type InstanceTypeMap map[string]InstanceType
210
211 var errDuplicateInstanceTypeName = errors.New("duplicate instance type name")
212
213 // UnmarshalJSON handles old config files that provide an array of
214 // instance types instead of a hash.
215 func (it *InstanceTypeMap) UnmarshalJSON(data []byte) error {
216         if len(data) > 0 && data[0] == '[' {
217                 var arr []InstanceType
218                 err := json.Unmarshal(data, &arr)
219                 if err != nil {
220                         return err
221                 }
222                 if len(arr) == 0 {
223                         *it = nil
224                         return nil
225                 }
226                 *it = make(map[string]InstanceType, len(arr))
227                 for _, t := range arr {
228                         if _, ok := (*it)[t.Name]; ok {
229                                 return errDuplicateInstanceTypeName
230                         }
231                         if t.ProviderType == "" {
232                                 t.ProviderType = t.Name
233                         }
234                         if t.Scratch == 0 {
235                                 t.Scratch = t.IncludedScratch + t.AddedScratch
236                         } else if t.AddedScratch == 0 {
237                                 t.AddedScratch = t.Scratch - t.IncludedScratch
238                         } else if t.IncludedScratch == 0 {
239                                 t.IncludedScratch = t.Scratch - t.AddedScratch
240                         }
241
242                         if t.Scratch != (t.IncludedScratch + t.AddedScratch) {
243                                 return fmt.Errorf("%v: Scratch != (IncludedScratch + AddedScratch)", t.Name)
244                         }
245                         (*it)[t.Name] = t
246                 }
247                 return nil
248         }
249         var hash map[string]InstanceType
250         err := json.Unmarshal(data, &hash)
251         if err != nil {
252                 return err
253         }
254         // Fill in Name field (and ProviderType field, if not
255         // specified) using hash key.
256         *it = InstanceTypeMap(hash)
257         for name, t := range *it {
258                 t.Name = name
259                 if t.ProviderType == "" {
260                         t.ProviderType = name
261                 }
262                 (*it)[name] = t
263         }
264         return nil
265 }
266
267 // GetNodeProfile returns a NodeProfile for the given hostname. An
268 // error is returned if the appropriate configuration can't be
269 // determined (e.g., this does not appear to be a system node). If
270 // node is empty, use the OS-reported hostname.
271 func (cc *Cluster) GetNodeProfile(node string) (*NodeProfile, error) {
272         if node == "" {
273                 hostname, err := os.Hostname()
274                 if err != nil {
275                         return nil, err
276                 }
277                 node = hostname
278         }
279         if cfg, ok := cc.NodeProfiles[node]; ok {
280                 return &cfg, nil
281         }
282         // If node is not listed, but "*" gives a default system node
283         // config, use the default config.
284         if cfg, ok := cc.NodeProfiles["*"]; ok {
285                 return &cfg, nil
286         }
287         return nil, fmt.Errorf("config does not provision host %q as a system node", node)
288 }
289
290 type NodeProfile struct {
291         Controller    SystemServiceInstance `json:"arvados-controller"`
292         Health        SystemServiceInstance `json:"arvados-health"`
293         Keepbalance   SystemServiceInstance `json:"keep-balance"`
294         Keepproxy     SystemServiceInstance `json:"keepproxy"`
295         Keepstore     SystemServiceInstance `json:"keepstore"`
296         Keepweb       SystemServiceInstance `json:"keep-web"`
297         Nodemanager   SystemServiceInstance `json:"arvados-node-manager"`
298         DispatchCloud SystemServiceInstance `json:"arvados-dispatch-cloud"`
299         RailsAPI      SystemServiceInstance `json:"arvados-api-server"`
300         Websocket     SystemServiceInstance `json:"arvados-ws"`
301         Workbench     SystemServiceInstance `json:"arvados-workbench"`
302 }
303
304 type ServiceName string
305
306 const (
307         ServiceNameRailsAPI      ServiceName = "arvados-api-server"
308         ServiceNameController    ServiceName = "arvados-controller"
309         ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud"
310         ServiceNameNodemanager   ServiceName = "arvados-node-manager"
311         ServiceNameWorkbench     ServiceName = "arvados-workbench"
312         ServiceNameWebsocket     ServiceName = "arvados-ws"
313         ServiceNameKeepbalance   ServiceName = "keep-balance"
314         ServiceNameKeepweb       ServiceName = "keep-web"
315         ServiceNameKeepproxy     ServiceName = "keepproxy"
316         ServiceNameKeepstore     ServiceName = "keepstore"
317 )
318
319 // ServicePorts returns the configured listening address (or "" if
320 // disabled) for each service on the node.
321 func (np *NodeProfile) ServicePorts() map[ServiceName]string {
322         return map[ServiceName]string{
323                 ServiceNameRailsAPI:      np.RailsAPI.Listen,
324                 ServiceNameController:    np.Controller.Listen,
325                 ServiceNameDispatchCloud: np.DispatchCloud.Listen,
326                 ServiceNameNodemanager:   np.Nodemanager.Listen,
327                 ServiceNameWorkbench:     np.Workbench.Listen,
328                 ServiceNameWebsocket:     np.Websocket.Listen,
329                 ServiceNameKeepbalance:   np.Keepbalance.Listen,
330                 ServiceNameKeepweb:       np.Keepweb.Listen,
331                 ServiceNameKeepproxy:     np.Keepproxy.Listen,
332                 ServiceNameKeepstore:     np.Keepstore.Listen,
333         }
334 }
335
336 type SystemServiceInstance struct {
337         Listen   string
338         TLS      bool
339         Insecure bool
340 }
341
342 type TLS struct {
343         Certificate string
344         Key         string
345         Insecure    bool
346 }