fddf5100acb6ae01a4c72e2d7b1e848e097a53e4
[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         Price         float64
103         Preemptible   bool
104         AttachScratch bool
105 }
106
107 type Dispatch struct {
108         // PEM encoded SSH key (RSA, DSA, or ECDSA) able to log in to
109         // cloud VMs.
110         PrivateKey string
111
112         // Max time for workers to come up before abandoning stale
113         // locks from previous run
114         StaleLockTimeout Duration
115
116         // Interval between queue polls
117         PollInterval Duration
118
119         // Interval between probes to each worker
120         ProbeInterval Duration
121
122         // Maximum total worker probes per second
123         MaxProbesPerSecond int
124 }
125
126 type CloudVMs struct {
127         // Shell command that exits zero IFF the VM is fully booted
128         // and ready to run containers, e.g., "mount | grep
129         // /encrypted-tmp"
130         BootProbeCommand string
131
132         // Listening port (name or number) of SSH servers on worker
133         // VMs
134         SSHPort string
135
136         SyncInterval Duration
137
138         // Maximum idle time before automatic shutdown
139         TimeoutIdle Duration
140
141         // Maximum booting time before automatic shutdown
142         TimeoutBooting Duration
143
144         // Maximum time with no successful probes before automatic shutdown
145         TimeoutProbe Duration
146
147         // Time after shutdown to retry shutdown
148         TimeoutShutdown Duration
149
150         ImageID string
151
152         Driver           string
153         DriverParameters json.RawMessage
154 }
155
156 type InstanceTypeMap map[string]InstanceType
157
158 var errDuplicateInstanceTypeName = errors.New("duplicate instance type name")
159
160 // UnmarshalJSON handles old config files that provide an array of
161 // instance types instead of a hash.
162 func (it *InstanceTypeMap) UnmarshalJSON(data []byte) error {
163         if len(data) > 0 && data[0] == '[' {
164                 var arr []InstanceType
165                 err := json.Unmarshal(data, &arr)
166                 if err != nil {
167                         return err
168                 }
169                 if len(arr) == 0 {
170                         *it = nil
171                         return nil
172                 }
173                 *it = make(map[string]InstanceType, len(arr))
174                 for _, t := range arr {
175                         if _, ok := (*it)[t.Name]; ok {
176                                 return errDuplicateInstanceTypeName
177                         }
178                         if t.ProviderType == "" {
179                                 t.ProviderType = t.Name
180                         }
181                         (*it)[t.Name] = t
182                 }
183                 return nil
184         }
185         var hash map[string]InstanceType
186         err := json.Unmarshal(data, &hash)
187         if err != nil {
188                 return err
189         }
190         // Fill in Name field (and ProviderType field, if not
191         // specified) using hash key.
192         *it = InstanceTypeMap(hash)
193         for name, t := range *it {
194                 t.Name = name
195                 if t.ProviderType == "" {
196                         t.ProviderType = name
197                 }
198                 (*it)[name] = t
199         }
200         return nil
201 }
202
203 // GetNodeProfile returns a NodeProfile for the given hostname. An
204 // error is returned if the appropriate configuration can't be
205 // determined (e.g., this does not appear to be a system node). If
206 // node is empty, use the OS-reported hostname.
207 func (cc *Cluster) GetNodeProfile(node string) (*NodeProfile, error) {
208         if node == "" {
209                 hostname, err := os.Hostname()
210                 if err != nil {
211                         return nil, err
212                 }
213                 node = hostname
214         }
215         if cfg, ok := cc.NodeProfiles[node]; ok {
216                 return &cfg, nil
217         }
218         // If node is not listed, but "*" gives a default system node
219         // config, use the default config.
220         if cfg, ok := cc.NodeProfiles["*"]; ok {
221                 return &cfg, nil
222         }
223         return nil, fmt.Errorf("config does not provision host %q as a system node", node)
224 }
225
226 type NodeProfile struct {
227         Controller    SystemServiceInstance `json:"arvados-controller"`
228         Health        SystemServiceInstance `json:"arvados-health"`
229         Keepbalance   SystemServiceInstance `json:"keep-balance"`
230         Keepproxy     SystemServiceInstance `json:"keepproxy"`
231         Keepstore     SystemServiceInstance `json:"keepstore"`
232         Keepweb       SystemServiceInstance `json:"keep-web"`
233         Nodemanager   SystemServiceInstance `json:"arvados-node-manager"`
234         DispatchCloud SystemServiceInstance `json:"arvados-dispatch-cloud"`
235         RailsAPI      SystemServiceInstance `json:"arvados-api-server"`
236         Websocket     SystemServiceInstance `json:"arvados-ws"`
237         Workbench     SystemServiceInstance `json:"arvados-workbench"`
238 }
239
240 type ServiceName string
241
242 const (
243         ServiceNameRailsAPI      ServiceName = "arvados-api-server"
244         ServiceNameController    ServiceName = "arvados-controller"
245         ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud"
246         ServiceNameNodemanager   ServiceName = "arvados-node-manager"
247         ServiceNameWorkbench     ServiceName = "arvados-workbench"
248         ServiceNameWebsocket     ServiceName = "arvados-ws"
249         ServiceNameKeepbalance   ServiceName = "keep-balance"
250         ServiceNameKeepweb       ServiceName = "keep-web"
251         ServiceNameKeepproxy     ServiceName = "keepproxy"
252         ServiceNameKeepstore     ServiceName = "keepstore"
253 )
254
255 // ServicePorts returns the configured listening address (or "" if
256 // disabled) for each service on the node.
257 func (np *NodeProfile) ServicePorts() map[ServiceName]string {
258         return map[ServiceName]string{
259                 ServiceNameRailsAPI:      np.RailsAPI.Listen,
260                 ServiceNameController:    np.Controller.Listen,
261                 ServiceNameDispatchCloud: np.DispatchCloud.Listen,
262                 ServiceNameNodemanager:   np.Nodemanager.Listen,
263                 ServiceNameWorkbench:     np.Workbench.Listen,
264                 ServiceNameWebsocket:     np.Websocket.Listen,
265                 ServiceNameKeepbalance:   np.Keepbalance.Listen,
266                 ServiceNameKeepweb:       np.Keepweb.Listen,
267                 ServiceNameKeepproxy:     np.Keepproxy.Listen,
268                 ServiceNameKeepstore:     np.Keepstore.Listen,
269         }
270 }
271
272 func (h RequestLimits) GetMultiClusterRequestConcurrency() int {
273         if h.MultiClusterRequestConcurrency == 0 {
274                 return 4
275         }
276         return h.MultiClusterRequestConcurrency
277 }
278
279 func (h RequestLimits) GetMaxItemsPerResponse() int {
280         if h.MaxItemsPerResponse == 0 {
281                 return 1000
282         }
283         return h.MaxItemsPerResponse
284 }
285
286 type SystemServiceInstance struct {
287         Listen   string
288         TLS      bool
289         Insecure bool
290 }