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