Merge branch '14419-concurrent-map-write'
[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         HTTPRequestTimeout Duration
64         RemoteClusters     map[string]RemoteCluster
65         PostgreSQL         PostgreSQL
66         RequestLimits      RequestLimits
67 }
68
69 type PostgreSQL struct {
70         Connection     PostgreSQLConnection
71         ConnectionPool int
72 }
73
74 type PostgreSQLConnection map[string]string
75
76 type RemoteCluster struct {
77         // API endpoint host or host:port; default is {id}.arvadosapi.com
78         Host string
79         // Perform a proxy request when a local client requests an
80         // object belonging to this remote.
81         Proxy bool
82         // Scheme, default "https". Can be set to "http" for testing.
83         Scheme string
84         // Disable TLS verify. Can be set to true for testing.
85         Insecure bool
86 }
87
88 type InstanceType struct {
89         Name         string
90         ProviderType string
91         VCPUs        int
92         RAM          ByteSize
93         Scratch      ByteSize
94         Price        float64
95         Preemptible  bool
96 }
97
98 type InstanceTypeMap map[string]InstanceType
99
100 var errDuplicateInstanceTypeName = errors.New("duplicate instance type name")
101
102 // UnmarshalJSON handles old config files that provide an array of
103 // instance types instead of a hash.
104 func (it *InstanceTypeMap) UnmarshalJSON(data []byte) error {
105         if len(data) > 0 && data[0] == '[' {
106                 var arr []InstanceType
107                 err := json.Unmarshal(data, &arr)
108                 if err != nil {
109                         return err
110                 }
111                 if len(arr) == 0 {
112                         *it = nil
113                         return nil
114                 }
115                 *it = make(map[string]InstanceType, len(arr))
116                 for _, t := range arr {
117                         if _, ok := (*it)[t.Name]; ok {
118                                 return errDuplicateInstanceTypeName
119                         }
120                         (*it)[t.Name] = t
121                 }
122                 return nil
123         }
124         var hash map[string]InstanceType
125         err := json.Unmarshal(data, &hash)
126         if err != nil {
127                 return err
128         }
129         // Fill in Name field using hash key.
130         *it = InstanceTypeMap(hash)
131         for name, t := range *it {
132                 t.Name = name
133                 (*it)[name] = t
134         }
135         return nil
136 }
137
138 // GetNodeProfile returns a NodeProfile for the given hostname. An
139 // error is returned if the appropriate configuration can't be
140 // determined (e.g., this does not appear to be a system node). If
141 // node is empty, use the OS-reported hostname.
142 func (cc *Cluster) GetNodeProfile(node string) (*NodeProfile, error) {
143         if node == "" {
144                 hostname, err := os.Hostname()
145                 if err != nil {
146                         return nil, err
147                 }
148                 node = hostname
149         }
150         if cfg, ok := cc.NodeProfiles[node]; ok {
151                 return &cfg, nil
152         }
153         // If node is not listed, but "*" gives a default system node
154         // config, use the default config.
155         if cfg, ok := cc.NodeProfiles["*"]; ok {
156                 return &cfg, nil
157         }
158         return nil, fmt.Errorf("config does not provision host %q as a system node", node)
159 }
160
161 type NodeProfile struct {
162         Controller  SystemServiceInstance `json:"arvados-controller"`
163         Health      SystemServiceInstance `json:"arvados-health"`
164         Keepbalance SystemServiceInstance `json:"keep-balance"`
165         Keepproxy   SystemServiceInstance `json:"keepproxy"`
166         Keepstore   SystemServiceInstance `json:"keepstore"`
167         Keepweb     SystemServiceInstance `json:"keep-web"`
168         Nodemanager SystemServiceInstance `json:"arvados-node-manager"`
169         RailsAPI    SystemServiceInstance `json:"arvados-api-server"`
170         Websocket   SystemServiceInstance `json:"arvados-ws"`
171         Workbench   SystemServiceInstance `json:"arvados-workbench"`
172 }
173
174 type ServiceName string
175
176 const (
177         ServiceNameRailsAPI    ServiceName = "arvados-api-server"
178         ServiceNameController  ServiceName = "arvados-controller"
179         ServiceNameNodemanager ServiceName = "arvados-node-manager"
180         ServiceNameWorkbench   ServiceName = "arvados-workbench"
181         ServiceNameWebsocket   ServiceName = "arvados-ws"
182         ServiceNameKeepbalance ServiceName = "keep-balance"
183         ServiceNameKeepweb     ServiceName = "keep-web"
184         ServiceNameKeepproxy   ServiceName = "keepproxy"
185         ServiceNameKeepstore   ServiceName = "keepstore"
186 )
187
188 // ServicePorts returns the configured listening address (or "" if
189 // disabled) for each service on the node.
190 func (np *NodeProfile) ServicePorts() map[ServiceName]string {
191         return map[ServiceName]string{
192                 ServiceNameRailsAPI:    np.RailsAPI.Listen,
193                 ServiceNameController:  np.Controller.Listen,
194                 ServiceNameNodemanager: np.Nodemanager.Listen,
195                 ServiceNameWorkbench:   np.Workbench.Listen,
196                 ServiceNameWebsocket:   np.Websocket.Listen,
197                 ServiceNameKeepbalance: np.Keepbalance.Listen,
198                 ServiceNameKeepweb:     np.Keepweb.Listen,
199                 ServiceNameKeepproxy:   np.Keepproxy.Listen,
200                 ServiceNameKeepstore:   np.Keepstore.Listen,
201         }
202 }
203
204 func (h RequestLimits) GetMultiClusterRequestConcurrency() int {
205         if h.MultiClusterRequestConcurrency == 0 {
206                 return 4
207         }
208         return h.MultiClusterRequestConcurrency
209 }
210
211 func (h RequestLimits) GetMaxItemsPerResponse() int {
212         if h.MaxItemsPerResponse == 0 {
213                 return 1000
214         }
215         return h.MaxItemsPerResponse
216 }
217
218 type SystemServiceInstance struct {
219         Listen   string
220         TLS      bool
221         Insecure bool
222 }