e3a072582beb32fe59e4f40335186fd0706c45a9
[arvados.git] / lib / cloud / interfaces.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package cloud
6
7 import (
8         "io"
9         "time"
10
11         "git.curoverse.com/arvados.git/sdk/go/arvados"
12         "golang.org/x/crypto/ssh"
13 )
14
15 // A RateLimitError should be returned by an InstanceSet when the
16 // cloud service indicates it is rejecting all API calls for some time
17 // interval.
18 type RateLimitError interface {
19         // Time before which the caller should expect requests to
20         // fail.
21         EarliestRetry() time.Time
22         error
23 }
24
25 // A QuotaError should be returned by an InstanceSet when the cloud
26 // service indicates the account cannot create more VMs than already
27 // exist.
28 type QuotaError interface {
29         // If true, don't create more instances until some existing
30         // instances are destroyed. If false, don't handle the error
31         // as a quota error.
32         IsQuotaError() bool
33         error
34 }
35
36 type InstanceSetID string
37 type InstanceTags map[string]string
38 type InstanceID string
39 type ImageID string
40
41 // An Executor executes commands on an ExecutorTarget.
42 type Executor interface {
43         // Update the set of private keys used to authenticate to
44         // targets.
45         SetSigners(...ssh.Signer)
46
47         // Set the target used for subsequent command executions.
48         SetTarget(ExecutorTarget)
49
50         // Return the current target.
51         Target() ExecutorTarget
52
53         // Execute a shell command and return the resulting stdout and
54         // stderr. stdin can be nil.
55         Execute(cmd string, stdin io.Reader) (stdout, stderr []byte, err error)
56 }
57
58 // An ExecutorTarget is a remote command execution service.
59 type ExecutorTarget interface {
60         // SSH server hostname or IP address, or empty string if
61         // unknown while instance is booting.
62         Address() string
63
64         // Return nil if the given public key matches the instance's
65         // SSH server key. If the provided Dialer is not nil,
66         // VerifyHostKey can use it to make outgoing network
67         // connections from the instance -- e.g., to use the cloud's
68         // "this instance's metadata" API.
69         VerifyHostKey(ssh.PublicKey, *ssh.Client) error
70 }
71
72 // Instance is implemented by the provider-specific instance types.
73 type Instance interface {
74         ExecutorTarget
75
76         // ID returns the provider's instance ID. It must be stable
77         // for the life of the instance.
78         ID() InstanceID
79
80         // String typically returns the cloud-provided instance ID.
81         String() string
82
83         // Cloud provider's "instance type" ID. Matches a ProviderType
84         // in the cluster's InstanceTypes configuration.
85         ProviderType() string
86
87         // Get current tags
88         Tags() InstanceTags
89
90         // Replace tags with the given tags
91         SetTags(InstanceTags) error
92
93         // Shut down the node
94         Destroy() error
95 }
96
97 // An InstanceSet manages a set of VM instances created by an elastic
98 // cloud provider like AWS, GCE, or Azure.
99 //
100 // All public methods of an InstanceSet, and all public methods of the
101 // instances it returns, are goroutine safe.
102 type InstanceSet interface {
103         // Create a new instance. If supported by the driver, add the
104         // provided public key to /root/.ssh/authorized_keys.
105         //
106         // The returned error should implement RateLimitError and
107         // QuotaError where applicable.
108         Create(arvados.InstanceType, ImageID, InstanceTags, ssh.PublicKey) (Instance, error)
109
110         // Return all instances, including ones that are booting or
111         // shutting down. Optionally, filter out nodes that don't have
112         // all of the given InstanceTags (the caller will ignore these
113         // anyway).
114         //
115         // An instance returned by successive calls to Instances() may
116         // -- but does not need to -- be represented by the same
117         // Instance object each time. Thus, the caller is responsible
118         // for de-duplicating the returned instances by comparing the
119         // InstanceIDs returned by the instances' ID() methods.
120         Instances(InstanceTags) ([]Instance, error)
121
122         // Stop any background tasks and release other resources.
123         Stop()
124 }
125
126 // A Driver returns an InstanceSet that uses the given InstanceSetID
127 // and driver-dependent configuration parameters.
128 //
129 // The supplied id will be of the form "zzzzz-zzzzz-zzzzzzzzzzzzzzz"
130 // where each z can be any alphanum. The returned InstanceSet must use
131 // this id to tag long-lived cloud resources that it creates, and must
132 // assume control of any existing resources that are tagged with the
133 // same id. Tagging can be accomplished by including the ID in
134 // resource names, using the cloud provider's tagging feature, or any
135 // other mechanism. The tags must be visible to another instance of
136 // the same driver running on a different host.
137 //
138 // The returned InstanceSet must ignore existing resources that are
139 // visible but not tagged with the given id, except that it should log
140 // a summary of such resources -- only once -- when it starts
141 // up. Thus, two identically configured InstanceSets running on
142 // different hosts with different ids should log about the existence
143 // of each other's resources at startup, but will not interfere with
144 // each other.
145 //
146 // Example:
147 //
148 //      type exampleInstanceSet struct {
149 //              ownID     string
150 //              AccessKey string
151 //      }
152 //
153 //      type exampleDriver struct {}
154 //
155 //      func (*exampleDriver) InstanceSet(config map[string]interface{}, id InstanceSetID) (InstanceSet, error) {
156 //              var is exampleInstanceSet
157 //              if err := mapstructure.Decode(config, &is); err != nil {
158 //                      return nil, err
159 //              }
160 //              is.ownID = id
161 //              return &is, nil
162 //      }
163 //
164 //      var _ = registerCloudDriver("example", &exampleDriver{})
165 type Driver interface {
166         InstanceSet(config map[string]interface{}, id InstanceSetID) (InstanceSet, error)
167 }
168
169 // DriverFunc makes a Driver using the provided function as its
170 // InstanceSet method. This is similar to http.HandlerFunc.
171 func DriverFunc(fn func(config map[string]interface{}, id InstanceSetID) (InstanceSet, error)) Driver {
172         return driverFunc(fn)
173 }
174
175 type driverFunc func(config map[string]interface{}, id InstanceSetID) (InstanceSet, error)
176
177 func (df driverFunc) InstanceSet(config map[string]interface{}, id InstanceSetID) (InstanceSet, error) {
178         return df(config, id)
179 }