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/sdk/go/arvados"
15 "github.com/prometheus/client_golang/prometheus"
16 "github.com/sirupsen/logrus"
17 "golang.org/x/crypto/ssh"
20 // Drivers is a map of available cloud drivers.
21 // Clusters.*.Containers.CloudVMs.Driver configuration values
22 // correspond to keys in this map.
23 var Drivers = map[string]cloud.Driver{
24 "azure": azure.Driver,
28 func newInstanceSet(cluster *arvados.Cluster, setID cloud.InstanceSetID, logger logrus.FieldLogger, reg *prometheus.Registry) (cloud.InstanceSet, error) {
29 driver, ok := Drivers[cluster.Containers.CloudVMs.Driver]
31 return nil, fmt.Errorf("unsupported cloud driver %q", cluster.Containers.CloudVMs.Driver)
33 sharedResourceTags := cloud.SharedResourceTags(cluster.Containers.CloudVMs.ResourceTags)
34 is, err := driver.InstanceSet(cluster.Containers.CloudVMs.DriverParameters, setID, sharedResourceTags, logger)
35 is = newInstrumentedInstanceSet(is, reg)
36 if maxops := cluster.Containers.CloudVMs.MaxCloudOpsPerSecond; maxops > 0 {
37 is = rateLimitedInstanceSet{
39 ticker: time.NewTicker(time.Second / time.Duration(maxops)),
42 is = defaultTaggingInstanceSet{
44 defaultTags: cloud.InstanceTags(cluster.Containers.CloudVMs.ResourceTags),
46 is = filteringInstanceSet{
53 type rateLimitedInstanceSet struct {
58 func (is rateLimitedInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
60 inst, err := is.InstanceSet.Create(it, image, tags, init, pk)
61 return &rateLimitedInstance{inst, is.ticker}, err
64 type rateLimitedInstance struct {
69 func (inst *rateLimitedInstance) Destroy() error {
71 return inst.Instance.Destroy()
74 // Adds the specified defaultTags to every Create() call.
75 type defaultTaggingInstanceSet struct {
77 defaultTags cloud.InstanceTags
80 func (is defaultTaggingInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
81 allTags := cloud.InstanceTags{}
82 for k, v := range is.defaultTags {
85 for k, v := range tags {
88 return is.InstanceSet.Create(it, image, allTags, init, pk)
91 // Filter the instances returned by the wrapped InstanceSet's
92 // Instances() method (in case the wrapped InstanceSet didn't do this
94 type filteringInstanceSet struct {
96 logger logrus.FieldLogger
99 func (is filteringInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
100 instances, err := is.InstanceSet.Instances(tags)
103 var returning []cloud.Instance
105 for _, inst := range instances {
106 instTags := inst.Tags()
107 for k, v := range tags {
108 if instTags[k] != v {
110 continue nextInstance
113 returning = append(returning, inst)
115 is.logger.WithFields(logrus.Fields{
116 "returning": len(returning),
118 }).WithError(err).Debugf("filteringInstanceSet returning instances")
119 return returning, err
122 func newInstrumentedInstanceSet(is cloud.InstanceSet, reg *prometheus.Registry) cloud.InstanceSet {
123 cv := prometheus.NewCounterVec(prometheus.CounterOpts{
124 Namespace: "arvados",
125 Subsystem: "dispatchcloud",
126 Name: "driver_operations",
127 Help: "Number of instance-create/destroy/list operations performed via cloud driver.",
128 }, []string{"operation", "error"})
130 // Create all counters, so they are reported with zero values
131 // (instead of being missing) until they are incremented.
132 for _, op := range []string{"Create", "List", "Destroy", "SetTags"} {
133 for _, error := range []string{"0", "1"} {
134 cv.WithLabelValues(op, error).Add(0)
139 return instrumentedInstanceSet{is, cv}
142 type instrumentedInstanceSet struct {
144 cv *prometheus.CounterVec
147 func (is instrumentedInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
148 inst, err := is.InstanceSet.Create(it, image, tags, init, pk)
149 is.cv.WithLabelValues("Create", boolLabelValue(err != nil)).Inc()
150 return instrumentedInstance{inst, is.cv}, err
153 func (is instrumentedInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
154 instances, err := is.InstanceSet.Instances(tags)
155 is.cv.WithLabelValues("List", boolLabelValue(err != nil)).Inc()
156 var instrumented []cloud.Instance
157 for _, i := range instances {
158 instrumented = append(instrumented, instrumentedInstance{i, is.cv})
160 return instrumented, err
163 type instrumentedInstance struct {
165 cv *prometheus.CounterVec
168 func (inst instrumentedInstance) Destroy() error {
169 err := inst.Instance.Destroy()
170 inst.cv.WithLabelValues("Destroy", boolLabelValue(err != nil)).Inc()
174 func (inst instrumentedInstance) SetTags(tags cloud.InstanceTags) error {
175 err := inst.Instance.SetTags(tags)
176 inst.cv.WithLabelValues("SetTags", boolLabelValue(err != nil)).Inc()
180 func boolLabelValue(v bool) string {