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) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
60 insts, err := is.InstanceSet.Instances(tags)
61 for i, inst := range insts {
62 insts[i] = &rateLimitedInstance{inst, is.ticker}
67 func (is rateLimitedInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
69 inst, err := is.InstanceSet.Create(it, image, tags, init, pk)
70 return &rateLimitedInstance{inst, is.ticker}, err
73 type rateLimitedInstance struct {
78 func (inst *rateLimitedInstance) Destroy() error {
80 return inst.Instance.Destroy()
83 func (inst *rateLimitedInstance) SetTags(tags cloud.InstanceTags) error {
85 return inst.Instance.SetTags(tags)
88 // Adds the specified defaultTags to every Create() call.
89 type defaultTaggingInstanceSet struct {
91 defaultTags cloud.InstanceTags
94 func (is defaultTaggingInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
95 allTags := cloud.InstanceTags{}
96 for k, v := range is.defaultTags {
99 for k, v := range tags {
102 return is.InstanceSet.Create(it, image, allTags, init, pk)
105 // Filter the instances returned by the wrapped InstanceSet's
106 // Instances() method (in case the wrapped InstanceSet didn't do this
108 type filteringInstanceSet struct {
110 logger logrus.FieldLogger
113 func (is filteringInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
114 instances, err := is.InstanceSet.Instances(tags)
117 var returning []cloud.Instance
119 for _, inst := range instances {
120 instTags := inst.Tags()
121 for k, v := range tags {
122 if instTags[k] != v {
124 continue nextInstance
127 returning = append(returning, inst)
129 is.logger.WithFields(logrus.Fields{
130 "returning": len(returning),
132 }).WithError(err).Debugf("filteringInstanceSet returning instances")
133 return returning, err
136 func newInstrumentedInstanceSet(is cloud.InstanceSet, reg *prometheus.Registry) cloud.InstanceSet {
137 cv := prometheus.NewCounterVec(prometheus.CounterOpts{
138 Namespace: "arvados",
139 Subsystem: "dispatchcloud",
140 Name: "driver_operations",
141 Help: "Number of instance-create/destroy/list operations performed via cloud driver.",
142 }, []string{"operation", "error"})
144 // Create all counters, so they are reported with zero values
145 // (instead of being missing) until they are incremented.
146 for _, op := range []string{"Create", "List", "Destroy", "SetTags"} {
147 for _, error := range []string{"0", "1"} {
148 cv.WithLabelValues(op, error).Add(0)
153 return instrumentedInstanceSet{is, cv}
156 type instrumentedInstanceSet struct {
158 cv *prometheus.CounterVec
161 func (is instrumentedInstanceSet) Create(it arvados.InstanceType, image cloud.ImageID, tags cloud.InstanceTags, init cloud.InitCommand, pk ssh.PublicKey) (cloud.Instance, error) {
162 inst, err := is.InstanceSet.Create(it, image, tags, init, pk)
163 is.cv.WithLabelValues("Create", boolLabelValue(err != nil)).Inc()
164 return instrumentedInstance{inst, is.cv}, err
167 func (is instrumentedInstanceSet) Instances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
168 instances, err := is.InstanceSet.Instances(tags)
169 is.cv.WithLabelValues("List", boolLabelValue(err != nil)).Inc()
170 var instrumented []cloud.Instance
171 for _, i := range instances {
172 instrumented = append(instrumented, instrumentedInstance{i, is.cv})
174 return instrumented, err
177 type instrumentedInstance struct {
179 cv *prometheus.CounterVec
182 func (inst instrumentedInstance) Destroy() error {
183 err := inst.Instance.Destroy()
184 inst.cv.WithLabelValues("Destroy", boolLabelValue(err != nil)).Inc()
188 func (inst instrumentedInstance) SetTags(tags cloud.InstanceTags) error {
189 err := inst.Instance.SetTags(tags)
190 inst.cv.WithLabelValues("SetTags", boolLabelValue(err != nil)).Inc()
194 func boolLabelValue(v bool) string {