Merge branch '13497-controller'
[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 }
60
61 type InstanceType struct {
62         Name         string
63         ProviderType string
64         VCPUs        int
65         RAM          ByteSize
66         Scratch      ByteSize
67         Price        float64
68         Preemptible  bool
69 }
70
71 type InstanceTypeMap map[string]InstanceType
72
73 var errDuplicateInstanceTypeName = errors.New("duplicate instance type name")
74
75 // UnmarshalJSON handles old config files that provide an array of
76 // instance types instead of a hash.
77 func (it *InstanceTypeMap) UnmarshalJSON(data []byte) error {
78         if len(data) > 0 && data[0] == '[' {
79                 var arr []InstanceType
80                 err := json.Unmarshal(data, &arr)
81                 if err != nil {
82                         return err
83                 }
84                 if len(arr) == 0 {
85                         *it = nil
86                         return nil
87                 }
88                 *it = make(map[string]InstanceType, len(arr))
89                 for _, t := range arr {
90                         if _, ok := (*it)[t.Name]; ok {
91                                 return errDuplicateInstanceTypeName
92                         }
93                         (*it)[t.Name] = t
94                 }
95                 return nil
96         }
97         var hash map[string]InstanceType
98         err := json.Unmarshal(data, &hash)
99         if err != nil {
100                 return err
101         }
102         // Fill in Name field using hash key.
103         *it = InstanceTypeMap(hash)
104         for name, t := range *it {
105                 t.Name = name
106                 (*it)[name] = t
107         }
108         return nil
109 }
110
111 // GetNodeProfile returns a NodeProfile for the given hostname. An
112 // error is returned if the appropriate configuration can't be
113 // determined (e.g., this does not appear to be a system node). If
114 // node is empty, use the OS-reported hostname.
115 func (cc *Cluster) GetNodeProfile(node string) (*NodeProfile, error) {
116         if node == "" {
117                 hostname, err := os.Hostname()
118                 if err != nil {
119                         return nil, err
120                 }
121                 node = hostname
122         }
123         if cfg, ok := cc.NodeProfiles[node]; ok {
124                 return &cfg, nil
125         }
126         // If node is not listed, but "*" gives a default system node
127         // config, use the default config.
128         if cfg, ok := cc.NodeProfiles["*"]; ok {
129                 return &cfg, nil
130         }
131         return nil, fmt.Errorf("config does not provision host %q as a system node", node)
132 }
133
134 type NodeProfile struct {
135         Controller  SystemServiceInstance `json:"arvados-controller"`
136         Health      SystemServiceInstance `json:"arvados-health"`
137         Keepproxy   SystemServiceInstance `json:"keepproxy"`
138         Keepstore   SystemServiceInstance `json:"keepstore"`
139         Keepweb     SystemServiceInstance `json:"keep-web"`
140         Nodemanager SystemServiceInstance `json:"arvados-node-manager"`
141         RailsAPI    SystemServiceInstance `json:"arvados-api-server"`
142         Websocket   SystemServiceInstance `json:"arvados-ws"`
143         Workbench   SystemServiceInstance `json:"arvados-workbench"`
144 }
145
146 type ServiceName string
147
148 const (
149         ServiceNameRailsAPI    ServiceName = "arvados-api-server"
150         ServiceNameController  ServiceName = "arvados-controller"
151         ServiceNameNodemanager ServiceName = "arvados-node-manager"
152         ServiceNameWorkbench   ServiceName = "arvados-workbench"
153         ServiceNameWebsocket   ServiceName = "arvados-ws"
154         ServiceNameKeepweb     ServiceName = "keep-web"
155         ServiceNameKeepproxy   ServiceName = "keepproxy"
156         ServiceNameKeepstore   ServiceName = "keepstore"
157 )
158
159 // ServicePorts returns the configured listening address (or "" if
160 // disabled) for each service on the node.
161 func (np *NodeProfile) ServicePorts() map[ServiceName]string {
162         return map[ServiceName]string{
163                 ServiceNameRailsAPI:    np.RailsAPI.Listen,
164                 ServiceNameController:  np.Controller.Listen,
165                 ServiceNameNodemanager: np.Nodemanager.Listen,
166                 ServiceNameWorkbench:   np.Workbench.Listen,
167                 ServiceNameWebsocket:   np.Websocket.Listen,
168                 ServiceNameKeepweb:     np.Keepweb.Listen,
169                 ServiceNameKeepproxy:   np.Keepproxy.Listen,
170                 ServiceNameKeepstore:   np.Keepstore.Listen,
171         }
172 }
173
174 type SystemServiceInstance struct {
175         Listen string
176         TLS    bool
177 }