1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
11 "git.arvados.org/arvados.git/lib/cloud"
12 "git.arvados.org/arvados.git/lib/cloud/azure"
13 "git.arvados.org/arvados.git/lib/cloud/ec2"
14 "git.arvados.org/arvados.git/lib/cloud/loopback"
15 "git.arvados.org/arvados.git/sdk/go/arvados"
16 "github.com/prometheus/client_golang/prometheus"
17 "github.com/sirupsen/logrus"
18 "golang.org/x/crypto/ssh"
21 // Drivers is a map of available cloud drivers.
22 // Clusters.*.Containers.CloudVMs.Driver configuration values
23 // correspond to keys in this map.
24 var Drivers = map[string]cloud.Driver{
25 "azure": azure.Driver,
27 "loopback": loopback.Driver,
30 func newInstanceSet(cluster *arvados.Cluster, setID cloud.InstanceSetID, logger logrus.FieldLogger, reg *prometheus.Registry) (cloud.InstanceSet, error) {
31 driver, ok := Drivers[cluster.Containers.CloudVMs.Driver]
33 return nil, fmt.Errorf("unsupported cloud driver %q", cluster.Containers.CloudVMs.Driver)
35 sharedResourceTags := cloud.SharedResourceTags(cluster.Containers.CloudVMs.ResourceTags)
36 is, err := driver.InstanceSet(cluster.Containers.CloudVMs.DriverParameters, setID, sharedResourceTags, logger, reg)
37 is = newInstrumentedInstanceSet(is, reg)
38 if maxops := cluster.Containers.CloudVMs.MaxCloudOpsPerSecond; maxops > 0 {
39 is = rateLimitedInstanceSet{
41 ticker: time.NewTicker(time.Second / time.Duration(maxops)),
44 is = defaultTaggingInstanceSet{
46 defaultTags: cloud.InstanceTags(cluster.Containers.CloudVMs.ResourceTags),
48 is = filteringInstanceSet{
55 type rateLimitedInstanceSet struct {
60 func (is rateLimitedInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
62 insts, err := is.InstanceSet.Instances(tags)
63 for i, inst := range insts {
64 insts[i] = &rateLimitedInstance{inst, is.ticker}
69 func (is rateLimitedInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
71 inst, err := is.InstanceSet.Create(it, image, tags, init, pk)
72 return &rateLimitedInstance{inst, is.ticker}, err
75 type rateLimitedInstance struct {
80 func (inst *rateLimitedInstance) Destroy() error {
82 return inst.Instance.Destroy()
85 func (inst *rateLimitedInstance) SetTags(tags cloud.InstanceTags) error {
87 return inst.Instance.SetTags(tags)
90 // Adds the specified defaultTags to every Create() call.
91 type defaultTaggingInstanceSet struct {
93 defaultTags cloud.InstanceTags
96 func (is defaultTaggingInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
97 allTags := cloud.InstanceTags{}
98 for k, v := range is.defaultTags {
101 for k, v := range tags {
104 return is.InstanceSet.Create(it, image, allTags, init, pk)
107 // Filter the instances returned by the wrapped InstanceSet's
108 // Instances() method (in case the wrapped InstanceSet didn't do this
110 type filteringInstanceSet struct {
112 logger logrus.FieldLogger
115 func (is filteringInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
116 instances, err := is.InstanceSet.Instances(tags)
119 var returning []cloud.Instance
121 for _, inst := range instances {
122 instTags := inst.Tags()
123 for k, v := range tags {
124 if instTags[k] != v {
126 continue nextInstance
129 returning = append(returning, inst)
131 is.logger.WithFields(logrus.Fields{
132 "returning": len(returning),
134 }).WithError(err).Debugf("filteringInstanceSet returning instances")
135 return returning, err
138 func newInstrumentedInstanceSet(is cloud.InstanceSet, reg *prometheus.Registry) cloud.InstanceSet {
139 cv := prometheus.NewCounterVec(prometheus.CounterOpts{
140 Namespace: "arvados",
141 Subsystem: "dispatchcloud",
142 Name: "driver_operations",
143 Help: "Number of instance-create/destroy/list operations performed via cloud driver.",
144 }, []string{"operation", "error"})
146 // Create all counters, so they are reported with zero values
147 // (instead of being missing) until they are incremented.
148 for _, op := range []string{"Create", "List", "Destroy", "SetTags"} {
149 for _, error := range []string{"0", "1"} {
150 cv.WithLabelValues(op, error).Add(0)
155 return instrumentedInstanceSet{is, cv}
158 type instrumentedInstanceSet struct {
160 cv *prometheus.CounterVec
163 func (is instrumentedInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
164 inst, err := is.InstanceSet.Create(it, image, tags, init, pk)
165 is.cv.WithLabelValues("Create", boolLabelValue(err != nil)).Inc()
166 return instrumentedInstance{inst, is.cv}, err
169 func (is instrumentedInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
170 instances, err := is.InstanceSet.Instances(tags)
171 is.cv.WithLabelValues("List", boolLabelValue(err != nil)).Inc()
172 var instrumented []cloud.Instance
173 for _, i := range instances {
174 instrumented = append(instrumented, instrumentedInstance{i, is.cv})
176 return instrumented, err
179 type instrumentedInstance struct {
181 cv *prometheus.CounterVec
184 func (inst instrumentedInstance) Destroy() error {
185 err := inst.Instance.Destroy()
186 inst.cv.WithLabelValues("Destroy", boolLabelValue(err != nil)).Inc()
190 func (inst instrumentedInstance) SetTags(tags cloud.InstanceTags) error {
191 err := inst.Instance.SetTags(tags)
192 inst.cv.WithLabelValues("SetTags", boolLabelValue(err != nil)).Inc()
196 func boolLabelValue(v bool) string {