Merge branch 'main' into 21224-project-details
[arvados.git] / lib / cloud / loopback / loopback_test.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 package loopback
6
7 import (
8         "crypto/rand"
9         "crypto/rsa"
10         "encoding/json"
11         "testing"
12         "time"
13
14         "git.arvados.org/arvados.git/lib/cloud"
15         "git.arvados.org/arvados.git/lib/dispatchcloud/sshexecutor"
16         "git.arvados.org/arvados.git/sdk/go/arvados"
17         "git.arvados.org/arvados.git/sdk/go/ctxlog"
18         "golang.org/x/crypto/ssh"
19         check "gopkg.in/check.v1"
20 )
21
22 func Test(t *testing.T) {
23         check.TestingT(t)
24 }
25
26 type suite struct{}
27
28 var _ = check.Suite(&suite{})
29
30 func (*suite) TestCreateListExecDestroy(c *check.C) {
31         logger := ctxlog.TestLogger(c)
32         is, err := Driver.InstanceSet(json.RawMessage("{}"), "testInstanceSetID", cloud.SharedResourceTags{"sharedTag": "sharedTagValue"}, logger, nil)
33         c.Assert(err, check.IsNil)
34
35         clientRSAKey, err := rsa.GenerateKey(rand.Reader, 1024)
36         c.Assert(err, check.IsNil)
37         clientSSHKey, err := ssh.NewSignerFromKey(clientRSAKey)
38         c.Assert(err, check.IsNil)
39         clientSSHPubKey, err := ssh.NewPublicKey(clientRSAKey.Public())
40         c.Assert(err, check.IsNil)
41
42         it := arvados.InstanceType{
43                 Name:         "localhost",
44                 ProviderType: "localhost",
45                 RAM:          1002003004,
46                 VCPUs:        5,
47         }
48
49         // First call to Create should succeed, and the returned
50         // instance's SSH target address should be available in << 1s.
51         inst, err := is.Create(it, "testImageID", cloud.InstanceTags{"instanceTag": "instanceTagValue"}, "testInitCommand", clientSSHPubKey)
52         c.Assert(err, check.IsNil)
53         for deadline := time.Now().Add(time.Second); inst.Address() == ""; time.Sleep(time.Second / 100) {
54                 if deadline.Before(time.Now()) {
55                         c.Fatal("timed out")
56                 }
57         }
58
59         // Another call to Create should fail with a quota error.
60         inst2, err := is.Create(it, "testImageID", cloud.InstanceTags{"instanceTag": "instanceTagValue"}, "testInitCommand", clientSSHPubKey)
61         c.Check(inst2, check.IsNil)
62         qerr, ok := err.(cloud.QuotaError)
63         if c.Check(ok, check.Equals, true, check.Commentf("expect cloud.QuotaError, got %#v", err)) {
64                 c.Check(qerr.IsQuotaError(), check.Equals, true)
65         }
66
67         // Instance list should now have one entry, for the new
68         // instance.
69         list, err := is.Instances(nil)
70         c.Assert(err, check.IsNil)
71         c.Assert(list, check.HasLen, 1)
72         inst = list[0]
73         c.Check(inst.String(), check.Equals, "localhost")
74
75         // Instance's SSH server should execute shell commands.
76         exr := sshexecutor.New(inst)
77         exr.SetSigners(clientSSHKey)
78
79         stdout, stderr, err := exr.Execute(nil, "echo ok", nil)
80         c.Check(err, check.IsNil)
81         c.Check(string(stdout), check.Equals, "ok\n")
82         c.Check(string(stderr), check.Equals, "")
83
84         // SSH server should propagate stderr and non-zero exit
85         // status.
86         stdout, stderr, err = exr.Execute(nil, "echo fail && echo -n fail2 >&2 && false", nil)
87         c.Check(err, check.FitsTypeOf, &ssh.ExitError{})
88         c.Check(string(stdout), check.Equals, "fail\n")
89         c.Check(string(stderr), check.Equals, "fail2")
90
91         // SSH server should strip "sudo" from the front of the
92         // command.
93         withoutsudo, _, err := exr.Execute(nil, "whoami", nil)
94         c.Check(err, check.IsNil)
95         withsudo, _, err := exr.Execute(nil, "sudo whoami", nil)
96         c.Check(err, check.IsNil)
97         c.Check(string(withsudo), check.Equals, string(withoutsudo))
98
99         // SSH server should reject keys other than the one whose
100         // public key we passed to Create.
101         badRSAKey, err := rsa.GenerateKey(rand.Reader, 1024)
102         c.Assert(err, check.IsNil)
103         badSSHKey, err := ssh.NewSignerFromKey(badRSAKey)
104         c.Assert(err, check.IsNil)
105         // Create a new executor here, otherwise Execute would reuse
106         // the existing connection instead of authenticating with
107         // badRSAKey.
108         exr = sshexecutor.New(inst)
109         exr.SetSigners(badSSHKey)
110         stdout, stderr, err = exr.Execute(nil, "true", nil)
111         c.Check(err, check.ErrorMatches, `.*unable to authenticate.*`)
112
113         // Destroying the instance causes it to disappear from the
114         // list, and allows us to create one more.
115         err = inst.Destroy()
116         c.Check(err, check.IsNil)
117         list, err = is.Instances(nil)
118         c.Assert(err, check.IsNil)
119         c.Assert(list, check.HasLen, 0)
120         _, err = is.Create(it, "testImageID", cloud.InstanceTags{"instanceTag": "instanceTagValue"}, "testInitCommand", clientSSHPubKey)
121         c.Check(err, check.IsNil)
122         _, err = is.Create(it, "testImageID", cloud.InstanceTags{"instanceTag": "instanceTagValue"}, "testInitCommand", clientSSHPubKey)
123         c.Check(err, check.NotNil)
124         list, err = is.Instances(nil)
125         c.Assert(err, check.IsNil)
126         c.Assert(list, check.HasLen, 1)
127 }