1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
11 "git.curoverse.com/arvados.git/lib/cloud"
12 "git.curoverse.com/arvados.git/lib/cloud/azure"
13 "git.curoverse.com/arvados.git/lib/cloud/ec2"
14 "git.curoverse.com/arvados.git/sdk/go/arvados"
15 "github.com/prometheus/client_golang/prometheus"
16 "github.com/sirupsen/logrus"
17 "golang.org/x/crypto/ssh"
20 var drivers = map[string]cloud.Driver{
21 "azure": azure.Driver,
25 func newInstanceSet(cluster *arvados.Cluster, setID cloud.InstanceSetID, logger logrus.FieldLogger, reg *prometheus.Registry) (cloud.InstanceSet, error) {
26 driver, ok := drivers[cluster.Containers.CloudVMs.Driver]
28 return nil, fmt.Errorf("unsupported cloud driver %q", cluster.Containers.CloudVMs.Driver)
30 sharedResourceTags := cloud.SharedResourceTags(cluster.Containers.CloudVMs.ResourceTags)
31 is, err := driver.InstanceSet(cluster.Containers.CloudVMs.DriverParameters, setID, sharedResourceTags, logger)
32 is = newInstrumentedInstanceSet(is, reg)
33 if maxops := cluster.Containers.CloudVMs.MaxCloudOpsPerSecond; maxops > 0 {
34 is = rateLimitedInstanceSet{
36 ticker: time.NewTicker(time.Second / time.Duration(maxops)),
39 is = defaultTaggingInstanceSet{
41 defaultTags: cloud.InstanceTags(cluster.Containers.CloudVMs.ResourceTags),
43 is = filteringInstanceSet{
50 type rateLimitedInstanceSet struct {
55 func (is rateLimitedInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
57 inst, err := is.InstanceSet.Create(it, image, tags, init, pk)
58 return &rateLimitedInstance{inst, is.ticker}, err
61 type rateLimitedInstance struct {
66 func (inst *rateLimitedInstance) Destroy() error {
68 return inst.Instance.Destroy()
71 // Adds the specified defaultTags to every Create() call.
72 type defaultTaggingInstanceSet struct {
74 defaultTags cloud.InstanceTags
77 func (is defaultTaggingInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
78 allTags := cloud.InstanceTags{}
79 for k, v := range is.defaultTags {
82 for k, v := range tags {
85 return is.InstanceSet.Create(it, image, allTags, init, pk)
88 // Filters the instances returned by the wrapped InstanceSet's
89 // Instances() method (in case the wrapped InstanceSet didn't do this
91 type filteringInstanceSet struct {
93 logger logrus.FieldLogger
96 func (is filteringInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
97 instances, err := is.InstanceSet.Instances(tags)
100 var returning []cloud.Instance
102 for _, inst := range instances {
103 instTags := inst.Tags()
104 for k, v := range tags {
105 if instTags[k] != v {
107 continue nextInstance
110 returning = append(returning, inst)
112 is.logger.WithFields(logrus.Fields{
113 "returning": len(returning),
115 }).WithError(err).Debugf("filteringInstanceSet returning instances")
116 return returning, err
119 func newInstrumentedInstanceSet(is cloud.InstanceSet, reg *prometheus.Registry) cloud.InstanceSet {
120 cv := prometheus.NewCounterVec(prometheus.CounterOpts{
121 Namespace: "arvados",
122 Subsystem: "dispatchcloud",
123 Name: "driver_operations",
124 Help: "Number of instance-create/destroy/list operations performed via cloud driver.",
125 }, []string{"operation", "error"})
127 // Create all counters, so they are reported with zero values
128 // (instead of being missing) until they are incremented.
129 for _, op := range []string{"Create", "List", "Destroy", "SetTags"} {
130 for _, error := range []string{"0", "1"} {
131 cv.WithLabelValues(op, error).Add(0)
136 return instrumentedInstanceSet{is, cv}
139 type instrumentedInstanceSet struct {
141 cv *prometheus.CounterVec
144 func (is instrumentedInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
145 inst, err := is.InstanceSet.Create(it, image, tags, init, pk)
146 is.cv.WithLabelValues("Create", boolLabelValue(err != nil)).Inc()
147 return instrumentedInstance{inst, is.cv}, err
150 func (is instrumentedInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
151 instances, err := is.InstanceSet.Instances(tags)
152 is.cv.WithLabelValues("List", boolLabelValue(err != nil)).Inc()
153 return instances, err
156 type instrumentedInstance struct {
158 cv *prometheus.CounterVec
161 func (inst instrumentedInstance) Destroy() error {
162 err := inst.Instance.Destroy()
163 inst.cv.WithLabelValues("Destroy", boolLabelValue(err != nil)).Inc()
167 func (inst instrumentedInstance) SetTags(tags cloud.InstanceTags) error {
168 err := inst.Instance.SetTags(tags)
169 inst.cv.WithLabelValues("SetTags", boolLabelValue(err != nil)).Inc()
173 func boolLabelValue(v bool) string {