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