1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
21 "git.arvados.org/arvados.git/lib/cloud"
22 "git.arvados.org/arvados.git/lib/dispatchcloud/test"
23 "git.arvados.org/arvados.git/sdk/go/arvados"
24 "github.com/prometheus/client_golang/prometheus"
25 "github.com/sirupsen/logrus"
26 "golang.org/x/crypto/ssh"
29 // Driver is the loopback implementation of the cloud.Driver interface.
30 var Driver = cloud.DriverFunc(newInstanceSet)
33 errUnimplemented = errors.New("function not implemented by loopback driver")
34 errQuota = quotaError("loopback driver is always at quota")
37 type quotaError string
39 func (e quotaError) IsQuotaError() bool { return true }
40 func (e quotaError) Error() string { return string(e) }
42 type instanceSet struct {
43 instanceSetID cloud.InstanceSetID
44 logger logrus.FieldLogger
49 func newInstanceSet(config json.RawMessage, instanceSetID cloud.InstanceSetID, _ cloud.SharedResourceTags, logger logrus.FieldLogger, reg *prometheus.Registry) (cloud.InstanceSet, error) {
51 instanceSetID: instanceSetID,
57 func (is *instanceSet) Create(it arvados.InstanceType, _ cloud.ImageID, tags cloud.InstanceTags, _ cloud.InitCommand, pubkey ssh.PublicKey) (cloud.Instance, error) {
60 if len(is.instances) > 0 {
63 // A crunch-run process running in a previous instance may
64 // have marked the node as broken. In the loopback scenario a
65 // destroy+create cycle doesn't fix whatever was broken -- but
66 // nothing else will either, so the best we can do is remove
67 // the "broken" flag and try again.
68 if err := os.Remove("/var/lock/crunch-run-broken"); err == nil {
69 is.logger.Info("removed /var/lock/crunch-run-broken")
70 } else if !errors.Is(err, os.ErrNotExist) {
73 u, err := user.Current()
77 hostRSAKey, err := rsa.GenerateKey(rand.Reader, 1024)
81 hostKey, err := ssh.NewSignerFromKey(hostRSAKey)
85 hostPubKey, err := ssh.NewPublicKey(hostRSAKey.Public())
92 adminUser: u.Username,
94 hostPubKey: hostPubKey,
95 sshService: test.SSHService{
97 AuthorizedUser: u.Username,
98 AuthorizedKeys: []ssh.PublicKey{pubkey},
101 inst.sshService.Exec = inst.sshExecFunc
102 go inst.sshService.Start()
103 is.instances = []*instance{inst}
107 func (is *instanceSet) Instances(cloud.InstanceTags) ([]cloud.Instance, error) {
109 defer is.mtx.Unlock()
110 var ret []cloud.Instance
111 for _, inst := range is.instances {
112 ret = append(ret, inst)
117 func (is *instanceSet) InstanceQuotaGroup(arvados.InstanceType) cloud.InstanceQuotaGroup {
121 func (is *instanceSet) Stop() {
123 defer is.mtx.Unlock()
124 for _, inst := range is.instances {
125 inst.sshService.Close()
129 type instance struct {
131 instanceType arvados.InstanceType
133 tags cloud.InstanceTags
134 hostPubKey ssh.PublicKey
135 sshService test.SSHService
138 func (i *instance) ID() cloud.InstanceID { return cloud.InstanceID(i.instanceType.ProviderType) }
139 func (i *instance) String() string { return i.instanceType.ProviderType }
140 func (i *instance) ProviderType() string { return i.instanceType.ProviderType }
141 func (i *instance) Address() string { return i.sshService.Address() }
142 func (i *instance) PriceHistory(arvados.InstanceType) []cloud.InstancePrice { return nil }
143 func (i *instance) RemoteUser() string { return i.adminUser }
144 func (i *instance) Tags() cloud.InstanceTags { return i.tags }
145 func (i *instance) SetTags(tags cloud.InstanceTags) error {
149 func (i *instance) Destroy() error {
151 defer i.is.mtx.Unlock()
152 i.is.instances = i.is.instances[:0]
155 func (i *instance) VerifyHostKey(pubkey ssh.PublicKey, _ *ssh.Client) error {
156 if !bytes.Equal(pubkey.Marshal(), i.hostPubKey.Marshal()) {
157 return errors.New("host key mismatch")
161 func (i *instance) sshExecFunc(env map[string]string, command string, stdin io.Reader, stdout, stderr io.Writer) uint32 {
162 cmd := exec.Command("sh", "-c", strings.TrimPrefix(command, "sudo "))
166 for k, v := range env {
167 cmd.Env = append(cmd.Env, k+"="+v)
169 // Prevent child process from using our tty.
170 cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
174 } else if err, ok := err.(*exec.ExitError); !ok {
176 } else if code := err.ExitCode(); code < 0 {