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