Merge branch '14745-azure-cloud-driver-fixups'
[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 string
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
125         // Listening port (name or number) of SSH servers on worker
126         // VMs
127         SSHPort string
128
129         SyncInterval Duration
130
131         // Maximum idle time before automatic shutdown
132         TimeoutIdle Duration
133
134         // Maximum booting time before automatic shutdown
135         TimeoutBooting Duration
136
137         // Maximum time with no successful probes before automatic shutdown
138         TimeoutProbe Duration
139
140         // Time after shutdown to retry shutdown
141         TimeoutShutdown Duration
142
143         ImageID string
144
145         Driver           string
146         DriverParameters json.RawMessage
147 }
148
149 type InstanceTypeMap map[string]InstanceType
150
151 var errDuplicateInstanceTypeName = errors.New("duplicate instance type name")
152
153 // UnmarshalJSON handles old config files that provide an array of
154 // instance types instead of a hash.
155 func (it *InstanceTypeMap) UnmarshalJSON(data []byte) error {
156         if len(data) > 0 && data[0] == '[' {
157                 var arr []InstanceType
158                 err := json.Unmarshal(data, &arr)
159                 if err != nil {
160                         return err
161                 }
162                 if len(arr) == 0 {
163                         *it = nil
164                         return nil
165                 }
166                 *it = make(map[string]InstanceType, len(arr))
167                 for _, t := range arr {
168                         if _, ok := (*it)[t.Name]; ok {
169                                 return errDuplicateInstanceTypeName
170                         }
171                         (*it)[t.Name] = t
172                 }
173                 return nil
174         }
175         var hash map[string]InstanceType
176         err := json.Unmarshal(data, &hash)
177         if err != nil {
178                 return err
179         }
180         // Fill in Name field using hash key.
181         *it = InstanceTypeMap(hash)
182         for name, t := range *it {
183                 t.Name = name
184                 (*it)[name] = t
185         }
186         return nil
187 }
188
189 // GetNodeProfile returns a NodeProfile for the given hostname. An
190 // error is returned if the appropriate configuration can't be
191 // determined (e.g., this does not appear to be a system node). If
192 // node is empty, use the OS-reported hostname.
193 func (cc *Cluster) GetNodeProfile(node string) (*NodeProfile, error) {
194         if node == "" {
195                 hostname, err := os.Hostname()
196                 if err != nil {
197                         return nil, err
198                 }
199                 node = hostname
200         }
201         if cfg, ok := cc.NodeProfiles[node]; ok {
202                 return &cfg, nil
203         }
204         // If node is not listed, but "*" gives a default system node
205         // config, use the default config.
206         if cfg, ok := cc.NodeProfiles["*"]; ok {
207                 return &cfg, nil
208         }
209         return nil, fmt.Errorf("config does not provision host %q as a system node", node)
210 }
211
212 type NodeProfile struct {
213         Controller    SystemServiceInstance `json:"arvados-controller"`
214         Health        SystemServiceInstance `json:"arvados-health"`
215         Keepbalance   SystemServiceInstance `json:"keep-balance"`
216         Keepproxy     SystemServiceInstance `json:"keepproxy"`
217         Keepstore     SystemServiceInstance `json:"keepstore"`
218         Keepweb       SystemServiceInstance `json:"keep-web"`
219         Nodemanager   SystemServiceInstance `json:"arvados-node-manager"`
220         DispatchCloud SystemServiceInstance `json:"arvados-dispatch-cloud"`
221         RailsAPI      SystemServiceInstance `json:"arvados-api-server"`
222         Websocket     SystemServiceInstance `json:"arvados-ws"`
223         Workbench     SystemServiceInstance `json:"arvados-workbench"`
224 }
225
226 type ServiceName string
227
228 const (
229         ServiceNameRailsAPI      ServiceName = "arvados-api-server"
230         ServiceNameController    ServiceName = "arvados-controller"
231         ServiceNameDispatchCloud ServiceName = "arvados-dispatch-cloud"
232         ServiceNameNodemanager   ServiceName = "arvados-node-manager"
233         ServiceNameWorkbench     ServiceName = "arvados-workbench"
234         ServiceNameWebsocket     ServiceName = "arvados-ws"
235         ServiceNameKeepbalance   ServiceName = "keep-balance"
236         ServiceNameKeepweb       ServiceName = "keep-web"
237         ServiceNameKeepproxy     ServiceName = "keepproxy"
238         ServiceNameKeepstore     ServiceName = "keepstore"
239 )
240
241 // ServicePorts returns the configured listening address (or "" if
242 // disabled) for each service on the node.
243 func (np *NodeProfile) ServicePorts() map[ServiceName]string {
244         return map[ServiceName]string{
245                 ServiceNameRailsAPI:      np.RailsAPI.Listen,
246                 ServiceNameController:    np.Controller.Listen,
247                 ServiceNameDispatchCloud: np.DispatchCloud.Listen,
248                 ServiceNameNodemanager:   np.Nodemanager.Listen,
249                 ServiceNameWorkbench:     np.Workbench.Listen,
250                 ServiceNameWebsocket:     np.Websocket.Listen,
251                 ServiceNameKeepbalance:   np.Keepbalance.Listen,
252                 ServiceNameKeepweb:       np.Keepweb.Listen,
253                 ServiceNameKeepproxy:     np.Keepproxy.Listen,
254                 ServiceNameKeepstore:     np.Keepstore.Listen,
255         }
256 }
257
258 func (h RequestLimits) GetMultiClusterRequestConcurrency() int {
259         if h.MultiClusterRequestConcurrency == 0 {
260                 return 4
261         }
262         return h.MultiClusterRequestConcurrency
263 }
264
265 func (h RequestLimits) GetMaxItemsPerResponse() int {
266         if h.MaxItemsPerResponse == 0 {
267                 return 1000
268         }
269         return h.MaxItemsPerResponse
270 }
271
272 type SystemServiceInstance struct {
273         Listen   string
274         TLS      bool
275         Insecure bool
276 }