[ARVADOS] created: 1.3.0-1123-gfe906ab6d

Git user git at public.curoverse.com
Fri Jun 21 12:14:41 UTC 2019


        at  fe906ab6d3e2e874c67366398c1a2bab63f49564 (commit)


commit fe906ab6d3e2e874c67366398c1a2bab63f49564
Author: Tom Clegg <tclegg at veritasgenetics.com>
Date:   Fri Jun 21 08:12:11 2019 -0400

    15026: Add cloudtest command.
    
    Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg at veritasgenetics.com>

diff --git a/build/run-tests.sh b/build/run-tests.sh
index 97c21b405..6f8f11774 100755
--- a/build/run-tests.sh
+++ b/build/run-tests.sh
@@ -80,6 +80,7 @@ lib/controller
 lib/crunchstat
 lib/cloud
 lib/cloud/azure
+lib/cloud/cloudtest
 lib/dispatchcloud
 lib/dispatchcloud/container
 lib/dispatchcloud/scheduler
@@ -734,7 +735,7 @@ do_test() {
         services/api)
             stop_services
             ;;
-        gofmt | govendor | doc | lib/cli | lib/cloud/azure | lib/cloud/ec2 | lib/cmd | lib/dispatchcloud/ssh_executor | lib/dispatchcloud/worker)
+        gofmt | govendor | doc | lib/cli | lib/cloud/azure | lib/cloud/ec2 | lib/cloud/cloudtest | lib/cmd | lib/dispatchcloud/ssh_executor | lib/dispatchcloud/worker)
             # don't care whether services are running
             ;;
         *)
@@ -992,6 +993,7 @@ gostuff=(
     lib/cloud
     lib/cloud/azure
     lib/cloud/ec2
+    lib/cloud/cloudtest
     lib/config
     lib/dispatchcloud
     lib/dispatchcloud/container
diff --git a/cmd/arvados-server/cmd.go b/cmd/arvados-server/cmd.go
index 983159382..2506bd2c9 100644
--- a/cmd/arvados-server/cmd.go
+++ b/cmd/arvados-server/cmd.go
@@ -7,6 +7,7 @@ package main
 import (
 	"os"
 
+	"git.curoverse.com/arvados.git/lib/cloud/cloudtest"
 	"git.curoverse.com/arvados.git/lib/cmd"
 	"git.curoverse.com/arvados.git/lib/config"
 	"git.curoverse.com/arvados.git/lib/controller"
@@ -20,6 +21,7 @@ var (
 		"-version":  cmd.Version(version),
 		"--version": cmd.Version(version),
 
+		"cloudtest":      cloudtest.Command,
 		"config-check":   config.CheckCommand,
 		"config-dump":    config.DumpCommand,
 		"controller":     controller.Command,
diff --git a/lib/cloud/cloudtest/cmd.go b/lib/cloud/cloudtest/cmd.go
new file mode 100644
index 000000000..1f94ea6da
--- /dev/null
+++ b/lib/cloud/cloudtest/cmd.go
@@ -0,0 +1,153 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package cloudtest
+
+import (
+	"bufio"
+	"errors"
+	"flag"
+	"fmt"
+	"io"
+	"os"
+	"os/user"
+
+	"git.curoverse.com/arvados.git/lib/cloud"
+	"git.curoverse.com/arvados.git/lib/config"
+	"git.curoverse.com/arvados.git/lib/dispatchcloud"
+	"git.curoverse.com/arvados.git/sdk/go/arvados"
+	"git.curoverse.com/arvados.git/sdk/go/ctxlog"
+	"golang.org/x/crypto/ssh"
+)
+
+var Command command
+
+type command struct{}
+
+func (command) RunCommand(prog string, args []string, stdin io.Reader, stdout, stderr io.Writer) int {
+	var err error
+	defer func() {
+		if err != nil {
+			fmt.Fprintf(stderr, "%s\n", err)
+		}
+	}()
+
+	flags := flag.NewFlagSet("", flag.ContinueOnError)
+	flags.SetOutput(stderr)
+	configFile := flags.String("config", arvados.DefaultConfigFile, "Site configuration `file`")
+	instanceSetID := flags.String("instance-set-id", defaultInstanceSetID(), "InstanceSetID tag `value` to use on the test instance")
+	imageID := flags.String("image-id", "", "Image ID to use when creating the test instance (if empty, use cluster config)")
+	instanceType := flags.String("instance-type", "", "Instance type to create (if empty, use cheapest type in config)")
+	destroyExisting := flags.Bool("destroy-existing", false, "Destroy any existing instances tagged with our InstanceSetID, instead of erroring out")
+	shellCommand := flags.String("command", "", "Run an interactive shell command on the test instance when it boots")
+	pauseBeforeDestroy := flags.Bool("pause-before-destroy", false, "Prompt and wait before destroying the test instance")
+	err = flags.Parse(args)
+	if err == flag.ErrHelp {
+		err = nil
+		return 0
+	} else if err != nil {
+		return 2
+	}
+
+	if len(flags.Args()) != 0 {
+		flags.Usage()
+		return 2
+	}
+	logger := ctxlog.New(stderr, "text", "info")
+	defer func() {
+		if err != nil {
+			logger.WithError(err).Error("fatal")
+			// suppress output from the other error-printing func
+			err = nil
+		}
+		logger.Info("exiting")
+	}()
+
+	cfg, err := config.LoadFile(*configFile, logger)
+	if err != nil {
+		return 1
+	}
+	cluster, err := cfg.GetCluster("")
+	if err != nil {
+		return 1
+	}
+	key, err := ssh.ParsePrivateKey([]byte(cluster.Containers.DispatchPrivateKey))
+	if err != nil {
+		err = fmt.Errorf("error parsing configured Containers.DispatchPrivateKey: %s", err)
+		return 1
+	}
+	driver, ok := dispatchcloud.Drivers[cluster.Containers.CloudVMs.Driver]
+	if !ok {
+		err = fmt.Errorf("unsupported cloud driver %q", cluster.Containers.CloudVMs.Driver)
+		return 1
+	}
+	if *imageID == "" {
+		*imageID = cluster.Containers.CloudVMs.ImageID
+	}
+	it, err := chooseInstanceType(cluster, *instanceType)
+	if err != nil {
+		return 1
+	}
+	tags := cloud.SharedResourceTags(cluster.Containers.CloudVMs.ResourceTags)
+	tagKeyPrefix := cluster.Containers.CloudVMs.TagKeyPrefix
+	tags[tagKeyPrefix+"CloudTestPID"] = fmt.Sprintf("%d", os.Getpid())
+	if !(&tester{
+		Logger:           logger,
+		Tags:             tags,
+		TagKeyPrefix:     tagKeyPrefix,
+		SetID:            cloud.InstanceSetID(*instanceSetID),
+		DestroyExisting:  *destroyExisting,
+		ProbeInterval:    cluster.Containers.CloudVMs.ProbeInterval.Duration(),
+		SyncInterval:     cluster.Containers.CloudVMs.SyncInterval.Duration(),
+		TimeoutBooting:   cluster.Containers.CloudVMs.TimeoutBooting.Duration(),
+		Driver:           driver,
+		DriverParameters: cluster.Containers.CloudVMs.DriverParameters,
+		ImageID:          cloud.ImageID(*imageID),
+		InstanceType:     it,
+		SSHKey:           key,
+		SSHPort:          cluster.Containers.CloudVMs.SSHPort,
+		BootProbeCommand: cluster.Containers.CloudVMs.BootProbeCommand,
+		ShellCommand:     *shellCommand,
+		PauseBeforeDestroy: func() {
+			if *pauseBeforeDestroy {
+				logger.Info("waiting for operator to press Enter")
+				fmt.Fprint(stderr, "Press Enter to continue: ")
+				bufio.NewReader(stdin).ReadString('\n')
+			}
+		},
+	}).Run() {
+		return 1
+	}
+	return 0
+}
+
+func defaultInstanceSetID() string {
+	username := ""
+	if u, err := user.Current(); err == nil {
+		username = u.Username
+	}
+	hostname, _ := os.Hostname()
+	return fmt.Sprintf("cloudtest-%s@%s", username, hostname)
+}
+
+// Return the named instance type, or the cheapest type if name=="".
+func chooseInstanceType(cluster *arvados.Cluster, name string) (arvados.InstanceType, error) {
+	if len(cluster.InstanceTypes) == 0 {
+		return arvados.InstanceType{}, errors.New("no instance types are configured")
+	} else if name == "" {
+		first := true
+		var best arvados.InstanceType
+		for _, it := range cluster.InstanceTypes {
+			if first || best.Price > it.Price {
+				best = it
+				first = false
+			}
+		}
+		return best, nil
+	} else if it, ok := cluster.InstanceTypes[name]; !ok {
+		return it, fmt.Errorf("requested instance type %q is not configured", name)
+	} else {
+		return it, nil
+	}
+}
diff --git a/lib/cloud/cloudtest/tester.go b/lib/cloud/cloudtest/tester.go
new file mode 100644
index 000000000..90cade823
--- /dev/null
+++ b/lib/cloud/cloudtest/tester.go
@@ -0,0 +1,325 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package cloudtest
+
+import (
+	"crypto/rand"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"time"
+
+	"git.curoverse.com/arvados.git/lib/cloud"
+	"git.curoverse.com/arvados.git/lib/dispatchcloud/ssh_executor"
+	"git.curoverse.com/arvados.git/lib/dispatchcloud/worker"
+	"git.curoverse.com/arvados.git/sdk/go/arvados"
+	"github.com/sirupsen/logrus"
+	"golang.org/x/crypto/ssh"
+)
+
+var (
+	errTestInstanceNotFound = errors.New("test instance missing from cloud provider's list")
+)
+
+// A tester does a sequence of operations to test a cloud driver and
+// configuration. Run() should be called only once, after assigning
+// suitable values to public fields.
+type tester struct {
+	Logger             logrus.FieldLogger
+	Tags               cloud.SharedResourceTags
+	TagKeyPrefix       string
+	SetID              cloud.InstanceSetID
+	DestroyExisting    bool
+	ProbeInterval      time.Duration
+	SyncInterval       time.Duration
+	TimeoutBooting     time.Duration
+	Driver             cloud.Driver
+	DriverParameters   json.RawMessage
+	InstanceType       arvados.InstanceType
+	ImageID            cloud.ImageID
+	SSHKey             ssh.Signer
+	SSHPort            string
+	BootProbeCommand   string
+	ShellCommand       string
+	PauseBeforeDestroy func()
+
+	is           cloud.InstanceSet
+	testInstance cloud.Instance
+	secret       string
+	executor     *ssh_executor.Executor
+
+	failed bool
+}
+
+// Run the test suite as specified, clean up as needed, and return
+// true (everything is OK) or false (something went wrong).
+func (t *tester) Run() bool {
+	// This flag gets set when we encounter a non-fatal error, so
+	// we can continue doing more tests but remember to return
+	// false (failure) at the end.
+	deferredError := false
+
+	var err error
+	t.is, err = t.Driver.InstanceSet(t.DriverParameters, t.SetID, t.Tags, t.Logger)
+	if err != nil {
+		t.Logger.WithError(err).Info("error initializing driver")
+		return false
+	}
+	insts, err := t.getInstances(nil)
+	if err != nil {
+		t.Logger.WithError(err).Info("error getting initial list of instances")
+		return false
+	}
+	for {
+		foundExisting := false
+		for _, i := range insts {
+			if i.Tags()[t.TagKeyPrefix+"InstanceSetID"] != string(t.SetID) {
+				continue
+			}
+			lgr := t.Logger.WithFields(logrus.Fields{
+				"Instance":      i.ID(),
+				"InstanceSetID": t.SetID,
+			})
+			foundExisting = true
+			if t.DestroyExisting {
+				lgr.Info("destroying existing instance with our InstanceSetID")
+				err := i.Destroy()
+				if err != nil {
+					lgr.WithError(err).Error("error destroying existing instance")
+				} else {
+					lgr.Info("Destroy() call succeeded")
+				}
+			} else {
+				lgr.Error("found existing instance with our InstanceSetID")
+			}
+		}
+		if !foundExisting {
+			break
+		} else if t.DestroyExisting {
+			t.sleepSyncInterval()
+		} else {
+			t.Logger.Error("cannot continue with existing instances -- clean up manually, use -destroy-existing=true, or choose a different -instance-set-id")
+			return false
+		}
+	}
+
+	t.secret = randomHex(40)
+
+	tags := cloud.InstanceTags{}
+	for k, v := range t.Tags {
+		tags[k] = v
+	}
+	tags[t.TagKeyPrefix+"InstanceSetID"] = string(t.SetID)
+	tags[t.TagKeyPrefix+"InstanceSecret"] = t.secret
+
+	defer t.destroyTestInstance()
+
+	bootDeadline := time.Now().Add(t.TimeoutBooting)
+	initCommand := worker.TagVerifier{nil, t.secret}.InitCommand()
+
+	t.Logger.WithFields(logrus.Fields{
+		"InstanceType":         t.InstanceType.Name,
+		"ProviderInstanceType": t.InstanceType.ProviderType,
+		"ImageID":              t.ImageID,
+		"Tags":                 tags,
+		"InitCommand":          initCommand,
+	}).Info("creating instance")
+	inst, err := t.is.Create(t.InstanceType, t.ImageID, tags, initCommand, t.SSHKey.PublicKey())
+	if err != nil {
+		deferredError = true
+		t.Logger.WithError(err).Error("error creating test instance")
+		t.Logger.WithField("Deadline", bootDeadline).Info("waiting for instance to appear anyway, in case the Create response was incorrect")
+		for err = t.refreshTestInstance(); err != nil; err = t.refreshTestInstance() {
+			if time.Now().After(bootDeadline) {
+				t.Logger.Error("timed out")
+				return false
+			} else {
+				t.sleepSyncInterval()
+			}
+		}
+	} else {
+		t.Logger.WithField("Instance", inst.ID()).Info("created instance")
+		t.testInstance = inst
+		err = t.refreshTestInstance()
+		if err == errTestInstanceNotFound {
+			t.Logger.WithError(err).Error("cloud/driver Create succeeded, but instance is not in list")
+			deferredError = true
+		} else if err != nil {
+			t.Logger.WithError(err).Error("error getting list of instances")
+			return false
+		}
+	}
+	t.testInstance = worker.TagVerifier{t.testInstance, t.secret}
+	t.Logger.WithField("Instance", t.testInstance.ID()).Info("created instance")
+
+	if !t.checkTags() {
+		// checkTags() already logged the errors
+		return false
+	}
+
+	if !t.waitForBoot(bootDeadline) {
+		return false
+	}
+
+	err = t.runShellCommand(t.ShellCommand)
+	if err != nil {
+		deferredError = true
+	}
+
+	if fn := t.PauseBeforeDestroy; fn != nil {
+		fn()
+	}
+
+	return !deferredError
+}
+
+func (t *tester) refreshTestInstance() error {
+	insts, err := t.getInstances(cloud.InstanceTags{t.TagKeyPrefix + "InstanceSetID": string(t.SetID)})
+	if err != nil {
+		return err
+	}
+	for _, i := range insts {
+		if t.testInstance == nil {
+			// Filter by InstanceSetID tag value
+			if i.Tags()[t.TagKeyPrefix+"InstanceSetID"] != string(t.SetID) {
+				continue
+			}
+		} else {
+			// Filter by instance ID
+			if i.ID() != t.testInstance.ID() {
+				continue
+			}
+		}
+		t.Logger.WithFields(logrus.Fields{
+			"Instance": i.ID(),
+			"Address":  i.Address(),
+		}).Info("found our instance in returned list")
+		t.testInstance = worker.TagVerifier{i, t.secret}
+		return nil
+	}
+	return errTestInstanceNotFound
+}
+
+func (t *tester) getInstances(tags cloud.InstanceTags) ([]cloud.Instance, error) {
+	var ret []cloud.Instance
+	t.Logger.WithField("FilterTags", tags).Info("getting instance list")
+	insts, err := t.is.Instances(tags)
+	if err != nil {
+		return nil, err
+	}
+	t.Logger.WithField("N", len(insts)).Info("got instance list")
+	for _, i := range insts {
+		if i.Tags()[t.TagKeyPrefix+"InstanceSetID"] == string(t.SetID) {
+			ret = append(ret, i)
+		}
+	}
+	return ret, nil
+}
+
+func (t *tester) checkTags() bool {
+	ok := true
+	for k, v := range t.Tags {
+		if got := t.testInstance.Tags()[k]; got != v {
+			ok = false
+			t.Logger.WithFields(logrus.Fields{
+				"Key":           k,
+				"ExpectedValue": v,
+				"GotValue":      got,
+			}).Error("tag is missing from test instance")
+		}
+	}
+	if ok {
+		t.Logger.Info("all expected tags are present")
+	}
+	return ok
+}
+
+func (t *tester) waitForBoot(deadline time.Time) bool {
+	for time.Now().Before(deadline) {
+		err := t.runShellCommand(t.BootProbeCommand)
+		if err == nil {
+			return true
+		}
+		t.sleepSyncInterval()
+		t.refreshTestInstance()
+	}
+	t.Logger.Error("timed out")
+	return false
+}
+
+func (t *tester) runShellCommand(cmd string) error {
+	if t.executor == nil {
+		t.executor = ssh_executor.New(t.testInstance)
+		t.executor.SetTargetPort(t.SSHPort)
+		t.executor.SetSigners(t.SSHKey)
+	} else {
+		t.executor.SetTarget(t.testInstance)
+	}
+	t.Logger.WithFields(logrus.Fields{
+		"Command": cmd,
+	}).Info("executing remote command")
+	stdout, stderr, err := t.executor.Execute(nil, cmd, nil)
+	lgr := t.Logger.WithFields(logrus.Fields{
+		"Command": cmd,
+		"stdout":  string(stdout),
+		"stderr":  string(stderr),
+	})
+	if err != nil {
+		lgr.WithError(err).Warn("remote command failed")
+	} else {
+		lgr.Info("remote command succeeded")
+	}
+	return err
+}
+
+// currently, this tries forever until it can return true (success).
+func (t *tester) destroyTestInstance() bool {
+	if t.testInstance == nil {
+		return true
+	}
+	for {
+		t.Logger.WithField("Instance", t.testInstance.ID()).Info("destroying instance")
+		err := t.testInstance.Destroy()
+		if err != nil {
+			t.Logger.WithError(err).WithField("Instance", t.testInstance.ID()).Error("error destroying instance")
+		} else {
+			t.Logger.WithField("Instance", t.testInstance.ID()).Info("destroyed instance")
+		}
+		err = t.refreshTestInstance()
+		if err == errTestInstanceNotFound {
+			t.Logger.WithField("Instance", t.testInstance.ID()).Info("instance no longer appears in list")
+			t.testInstance = nil
+			return true
+		} else if err == nil {
+			t.Logger.WithField("Instance", t.testInstance.ID()).Info("instance still exists after calling Destroy")
+			t.sleepSyncInterval()
+			continue
+		} else {
+			t.Logger.WithError(err).Error("error getting list of instances")
+			continue
+		}
+	}
+}
+
+func (t *tester) sleepSyncInterval() {
+	t.Logger.WithField("Duration", t.SyncInterval).Info("waiting SyncInterval")
+	time.Sleep(t.SyncInterval)
+}
+
+func (t *tester) sleepProbeInterval() {
+	t.Logger.WithField("Duration", t.ProbeInterval).Info("waiting ProbeInterval")
+	time.Sleep(t.ProbeInterval)
+}
+
+// Return a random string of n hexadecimal digits (n*4 random bits). n
+// must be even.
+func randomHex(n int) string {
+	buf := make([]byte, n/2)
+	_, err := rand.Read(buf)
+	if err != nil {
+		panic(err)
+	}
+	return fmt.Sprintf("%x", buf)
+}
diff --git a/lib/cloud/cloudtest/tester_test.go b/lib/cloud/cloudtest/tester_test.go
new file mode 100644
index 000000000..358530bf8
--- /dev/null
+++ b/lib/cloud/cloudtest/tester_test.go
@@ -0,0 +1,97 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+package cloudtest
+
+import (
+	"bytes"
+	"testing"
+	"time"
+
+	"git.curoverse.com/arvados.git/lib/cloud"
+	"git.curoverse.com/arvados.git/lib/dispatchcloud/test"
+	"git.curoverse.com/arvados.git/sdk/go/arvados"
+	"git.curoverse.com/arvados.git/sdk/go/ctxlog"
+	"golang.org/x/crypto/ssh"
+	check "gopkg.in/check.v1"
+)
+
+// Gocheck boilerplate
+func Test(t *testing.T) {
+	check.TestingT(t)
+}
+
+var _ = check.Suite(&TesterSuite{})
+
+type TesterSuite struct {
+	stubDriver *test.StubDriver
+	cluster    *arvados.Cluster
+	tester     *tester
+	log        bytes.Buffer
+}
+
+func (s *TesterSuite) SetUpTest(c *check.C) {
+	pubkey, privkey := test.LoadTestKey(c, "../../dispatchcloud/test/sshkey_dispatch")
+	_, privhostkey := test.LoadTestKey(c, "../../dispatchcloud/test/sshkey_vm")
+	s.stubDriver = &test.StubDriver{
+		HostKey:                   privhostkey,
+		AuthorizedKeys:            []ssh.PublicKey{pubkey},
+		ErrorRateDestroy:          0.1,
+		MinTimeBetweenCreateCalls: time.Millisecond,
+	}
+	tagKeyPrefix := "tagprefix:"
+	s.cluster = &arvados.Cluster{
+		ManagementToken: "test-management-token",
+		Containers: arvados.ContainersConfig{
+			CloudVMs: arvados.CloudVMsConfig{
+				SyncInterval:   arvados.Duration(10 * time.Millisecond),
+				TimeoutBooting: arvados.Duration(150 * time.Millisecond),
+				TimeoutProbe:   arvados.Duration(15 * time.Millisecond),
+				ProbeInterval:  arvados.Duration(5 * time.Millisecond),
+				ResourceTags:   map[string]string{"testtag": "test value"},
+			},
+		},
+		InstanceTypes: arvados.InstanceTypeMap{
+			test.InstanceType(1).Name: test.InstanceType(1),
+			test.InstanceType(2).Name: test.InstanceType(2),
+			test.InstanceType(3).Name: test.InstanceType(3),
+		},
+	}
+	s.tester = &tester{
+		Logger:           ctxlog.New(&s.log, "text", "info"),
+		Tags:             cloud.SharedResourceTags{"testtagkey": "testtagvalue"},
+		TagKeyPrefix:     tagKeyPrefix,
+		SetID:            cloud.InstanceSetID("test-instance-set-id"),
+		ProbeInterval:    5 * time.Millisecond,
+		SyncInterval:     10 * time.Millisecond,
+		TimeoutBooting:   150 * time.Millisecond,
+		Driver:           s.stubDriver,
+		DriverParameters: nil,
+		InstanceType:     test.InstanceType(2),
+		ImageID:          "test-image-id",
+		SSHKey:           privkey,
+		BootProbeCommand: "crunch-run --list",
+		ShellCommand:     "true",
+	}
+}
+
+func (s *TesterSuite) TestSuccess(c *check.C) {
+	s.tester.Logger = ctxlog.TestLogger(c)
+	ok := s.tester.Run()
+	c.Check(ok, check.Equals, true)
+}
+
+func (s *TesterSuite) TestBootFail(c *check.C) {
+	s.tester.BootProbeCommand = "falsey"
+	ok := s.tester.Run()
+	c.Check(ok, check.Equals, false)
+	c.Check(s.log.String(), check.Matches, `(?ms).*\\"falsey\\": command not found.*`)
+}
+
+func (s *TesterSuite) TestShellCommandFail(c *check.C) {
+	s.tester.ShellCommand = "falsey"
+	ok := s.tester.Run()
+	c.Check(ok, check.Equals, false)
+	c.Check(s.log.String(), check.Matches, `(?ms).*\\"falsey\\": command not found.*`)
+}
diff --git a/lib/dispatchcloud/dispatcher_test.go b/lib/dispatchcloud/dispatcher_test.go
index 6b73e71cc..fbe04393a 100644
--- a/lib/dispatchcloud/dispatcher_test.go
+++ b/lib/dispatchcloud/dispatcher_test.go
@@ -107,7 +107,7 @@ func (s *DispatcherSuite) TearDownTest(c *check.C) {
 // a fake queue and cloud driver. The fake cloud driver injects
 // artificial errors in order to exercise a variety of code paths.
 func (s *DispatcherSuite) TestDispatchToStubDriver(c *check.C) {
-	drivers["test"] = s.stubDriver
+	Drivers["test"] = s.stubDriver
 	s.disp.setupOnce.Do(s.disp.initialize)
 	queue := &test.Queue{
 		ChooseType: func(ctr *arvados.Container) (arvados.InstanceType, error) {
@@ -210,7 +210,7 @@ func (s *DispatcherSuite) TestDispatchToStubDriver(c *check.C) {
 
 func (s *DispatcherSuite) TestAPIPermissions(c *check.C) {
 	s.cluster.ManagementToken = "abcdefgh"
-	drivers["test"] = s.stubDriver
+	Drivers["test"] = s.stubDriver
 	s.disp.setupOnce.Do(s.disp.initialize)
 	s.disp.queue = &test.Queue{}
 	go s.disp.run()
@@ -232,7 +232,7 @@ func (s *DispatcherSuite) TestAPIPermissions(c *check.C) {
 
 func (s *DispatcherSuite) TestAPIDisabled(c *check.C) {
 	s.cluster.ManagementToken = ""
-	drivers["test"] = s.stubDriver
+	Drivers["test"] = s.stubDriver
 	s.disp.setupOnce.Do(s.disp.initialize)
 	s.disp.queue = &test.Queue{}
 	go s.disp.run()
@@ -251,7 +251,7 @@ func (s *DispatcherSuite) TestAPIDisabled(c *check.C) {
 func (s *DispatcherSuite) TestInstancesAPI(c *check.C) {
 	s.cluster.ManagementToken = "abcdefgh"
 	s.cluster.Containers.CloudVMs.TimeoutBooting = arvados.Duration(time.Second)
-	drivers["test"] = s.stubDriver
+	Drivers["test"] = s.stubDriver
 	s.disp.setupOnce.Do(s.disp.initialize)
 	s.disp.queue = &test.Queue{}
 	go s.disp.run()
diff --git a/lib/dispatchcloud/driver.go b/lib/dispatchcloud/driver.go
index a8f3d5b5e..49437e318 100644
--- a/lib/dispatchcloud/driver.go
+++ b/lib/dispatchcloud/driver.go
@@ -17,13 +17,13 @@ import (
 	"golang.org/x/crypto/ssh"
 )
 
-var drivers = map[string]cloud.Driver{
+var Drivers = map[string]cloud.Driver{
 	"azure": azure.Driver,
 	"ec2":   ec2.Driver,
 }
 
 func newInstanceSet(cluster *arvados.Cluster, setID cloud.InstanceSetID, logger logrus.FieldLogger, reg *prometheus.Registry) (cloud.InstanceSet, error) {
-	driver, ok := drivers[cluster.Containers.CloudVMs.Driver]
+	driver, ok := Drivers[cluster.Containers.CloudVMs.Driver]
 	if !ok {
 		return nil, fmt.Errorf("unsupported cloud driver %q", cluster.Containers.CloudVMs.Driver)
 	}
@@ -85,7 +85,7 @@ func (is defaultTaggingInstanceSet) Create(it arvados.InstanceType, image cloud.
 	return is.InstanceSet.Create(it, image, allTags, init, pk)
 }
 
-// Filters the instances returned by the wrapped InstanceSet's
+// Filter the instances returned by the wrapped InstanceSet's
 // Instances() method (in case the wrapped InstanceSet didn't do this
 // itself).
 type filteringInstanceSet struct {
diff --git a/lib/dispatchcloud/worker/pool.go b/lib/dispatchcloud/worker/pool.go
index 201e8aad2..e00a8683a 100644
--- a/lib/dispatchcloud/worker/pool.go
+++ b/lib/dispatchcloud/worker/pool.go
@@ -292,7 +292,7 @@ func (wp *Pool) Create(it arvados.InstanceType) bool {
 			wp.tagKeyPrefix + tagKeyIdleBehavior:   string(IdleBehaviorRun),
 			wp.tagKeyPrefix + tagKeyInstanceSecret: secret,
 		}
-		initCmd := cloud.InitCommand(fmt.Sprintf("umask 0177 && echo -n %q >%s", secret, instanceSecretFilename))
+		initCmd := TagVerifier{nil, secret}.InitCommand()
 		inst, err := wp.instanceSet.Create(it, wp.imageID, tags, initCmd, wp.installPublicKey)
 		wp.mtx.Lock()
 		defer wp.mtx.Unlock()
@@ -346,7 +346,7 @@ func (wp *Pool) SetIdleBehavior(id cloud.InstanceID, idleBehavior IdleBehavior)
 // Caller must have lock.
 func (wp *Pool) updateWorker(inst cloud.Instance, it arvados.InstanceType) (*worker, bool) {
 	secret := inst.Tags()[wp.tagKeyPrefix+tagKeyInstanceSecret]
-	inst = tagVerifier{inst, secret}
+	inst = TagVerifier{inst, secret}
 	id := inst.ID()
 	if wkr := wp.workers[id]; wkr != nil {
 		wkr.executor.SetTarget(inst)
diff --git a/lib/dispatchcloud/worker/verify.go b/lib/dispatchcloud/worker/verify.go
index 330071951..c71870210 100644
--- a/lib/dispatchcloud/worker/verify.go
+++ b/lib/dispatchcloud/worker/verify.go
@@ -21,13 +21,17 @@ var (
 	instanceSecretLength   = 40 // hex digits
 )
 
-type tagVerifier struct {
+type TagVerifier struct {
 	cloud.Instance
-	secret string
+	Secret string
 }
 
-func (tv tagVerifier) VerifyHostKey(pubKey ssh.PublicKey, client *ssh.Client) error {
-	if err := tv.Instance.VerifyHostKey(pubKey, client); err != cloud.ErrNotImplemented || tv.secret == "" {
+func (tv TagVerifier) InitCommand() cloud.InitCommand {
+	return cloud.InitCommand(fmt.Sprintf("umask 0177 && echo -n %q >%s", tv.Secret, instanceSecretFilename))
+}
+
+func (tv TagVerifier) VerifyHostKey(pubKey ssh.PublicKey, client *ssh.Client) error {
+	if err := tv.Instance.VerifyHostKey(pubKey, client); err != cloud.ErrNotImplemented || tv.Secret == "" {
 		// If the wrapped instance indicates it has a way to
 		// verify the key, return that decision.
 		return err
@@ -49,7 +53,7 @@ func (tv tagVerifier) VerifyHostKey(pubKey ssh.PublicKey, client *ssh.Client) er
 	if err != nil {
 		return err
 	}
-	if stdout.String() != tv.secret {
+	if stdout.String() != tv.Secret {
 		return errBadInstanceSecret
 	}
 	return nil
diff --git a/services/login-sync/Gemfile.lock b/services/login-sync/Gemfile.lock
index d03512d59..8a53a1265 100644
--- a/services/login-sync/Gemfile.lock
+++ b/services/login-sync/Gemfile.lock
@@ -1,7 +1,7 @@
 PATH
   remote: .
   specs:
-    arvados-login-sync (1.3.3.20190528194843)
+    arvados-login-sync (1.4.0.20190610174652)
       arvados (~> 1.3.0, >= 1.3.0)
 
 GEM
@@ -62,7 +62,7 @@ GEM
     multi_json (1.13.1)
     multipart-post (2.1.1)
     os (1.0.1)
-    public_suffix (3.0.3)
+    public_suffix (3.1.0)
     rake (12.3.2)
     retriable (1.4.1)
     signet (0.11.0)

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list