Skip to content

Commit bf10ee9

Browse files
committed
fix: lazy SSH connection to host in testdevice
Previously, newHost dialed SSH immediately at device initialization, causing failures when the host OS hadn't finished booting yet or when running BMC-only suites that don't need host access. Now the SSH connection is established on first use via ExecuteCommandLine. Signed-off-by: llogen <christoph.lange@blindspot.software>
1 parent 1477792 commit bf10ee9

File tree

5 files changed

+177
-14
lines changed

5 files changed

+177
-14
lines changed

pkg/bmc/bmc.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package bmc
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/9elements/bmc-test-go/pkg/configuration"
7+
"github.com/stmcginnis/gofish"
8+
)
9+
10+
type BMC struct {
11+
con BMCConnection
12+
redfishService *gofish.Service
13+
}
14+
15+
func makeGofishConfig(config *configuration.Config) gofish.ClientConfig {
16+
return gofish.ClientConfig{
17+
Endpoint: "https://" + config.BMCHost,
18+
Username: config.BMCUser,
19+
Password: config.BMCPassword,
20+
Insecure: true,
21+
}
22+
}
23+
24+
func NewBMC(execEnv string, cfg *configuration.Config) (*BMC, error) {
25+
ret := &BMC{}
26+
switch execEnv {
27+
case "local":
28+
ret.con = &LocalConn{}
29+
case "remote":
30+
hostport := fmt.Sprintf("%s:%s", cfg.BMCHost, cfg.SSHPort)
31+
remoteConn, err := NewRemoteConn(hostport, cfg.BMCUser, cfg.BMCPassword)
32+
if err != nil {
33+
return nil, err
34+
}
35+
ret.con = remoteConn
36+
default:
37+
return nil, fmt.Errorf("unknown connection type: %s", execEnv)
38+
}
39+
40+
conn, err := gofish.Connect(makeGofishConfig(cfg))
41+
if err != nil {
42+
return nil, err
43+
}
44+
45+
ret.redfishService = conn.Service
46+
47+
return ret, nil
48+
}
49+
50+
func (b *BMC) Close() error {
51+
return b.con.Close()
52+
}
53+
54+
func (b *BMC) ExecuteCommandLine(cmd string, args ...string) ([]byte, error) {
55+
return b.con.ExecuteCmdline(cmd, args...)
56+
}
57+
58+
func (b *BMC) RedfishService() *gofish.Service {
59+
return b.redfishService
60+
}

pkg/bmc/interface.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package bmc
2+
3+
type BMCConnection interface {
4+
Close() error
5+
ExecuteCmdline(cmd string, args ...string) ([]byte, error)
6+
}

pkg/bmc/localconn.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package bmc
2+
3+
import "os/exec"
4+
5+
type LocalConn struct{}
6+
7+
func (l *LocalConn) ExecuteCmdline(cmd string, args ...string) ([]byte, error) {
8+
command := exec.Command(cmd, args...)
9+
resp, err := command.Output()
10+
if err != nil {
11+
return resp, err
12+
}
13+
return resp, nil
14+
}
15+
16+
func (l *LocalConn) Close() error {
17+
return nil
18+
}

pkg/bmc/remoteconn.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package bmc
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"log"
7+
8+
"golang.org/x/crypto/ssh"
9+
)
10+
11+
type RemoteConn struct {
12+
IP string
13+
User string
14+
Password string
15+
client *ssh.Client
16+
}
17+
18+
func NewRemoteConn(ip string, user string, password string) (*RemoteConn, error) {
19+
sshConfig := &ssh.ClientConfig{
20+
User: user,
21+
Auth: []ssh.AuthMethod{ssh.Password(password)},
22+
}
23+
sshConfig.HostKeyCallback = ssh.InsecureIgnoreHostKey()
24+
25+
client, err := ssh.Dial("tcp", ip, sshConfig)
26+
if err != nil {
27+
return nil, err
28+
}
29+
30+
return &RemoteConn{
31+
IP: ip,
32+
User: user,
33+
Password: password,
34+
client: client,
35+
}, nil
36+
}
37+
38+
func (r *RemoteConn) Close() error {
39+
return r.client.Close()
40+
}
41+
42+
func (r *RemoteConn) ExecuteCmdline(cmd string, args ...string) ([]byte, error) {
43+
session, err := r.client.NewSession()
44+
if err != nil {
45+
return nil, err
46+
}
47+
defer func() {
48+
if err := session.Close(); err != nil && err != io.EOF {
49+
log.Print(err)
50+
}
51+
}()
52+
53+
cmdsArgs := cmd
54+
for _, arg := range args {
55+
cmdsArgs += fmt.Sprintf(" %s", arg)
56+
}
57+
58+
resp, err := session.CombinedOutput(cmdsArgs)
59+
if err != nil {
60+
return resp, err
61+
}
62+
return resp, nil
63+
}

pkg/testdevice/host.go

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import (
99
)
1010

1111
type Host struct {
12-
Name string
13-
RemoteConn
12+
Name string
13+
hostport string
14+
sshConfig *ssh.ClientConfig
15+
remoteConn *RemoteConn
1416
}
1517

1618
func newHost(sshPrivKeyPath string, hostCfg configuration.Host) (*Host, error) {
@@ -38,25 +40,39 @@ func newHost(sshPrivKeyPath string, hostCfg configuration.Host) (*Host, error) {
3840
}
3941
}
4042

41-
hostport := fmt.Sprintf("%s:%s", hostCfg.IP, hostCfg.SSHPort)
42-
client, err := ssh.Dial("tcp", hostport, sshConfig)
43+
return &Host{
44+
Name: hostCfg.Name,
45+
hostport: fmt.Sprintf("%s:%s", hostCfg.IP, hostCfg.SSHPort),
46+
sshConfig: sshConfig,
47+
}, nil
48+
}
49+
50+
func (h *Host) connect() error {
51+
if h.remoteConn != nil {
52+
return nil
53+
}
54+
client, err := ssh.Dial("tcp", h.hostport, h.sshConfig)
4355
if err != nil {
44-
return nil, err
56+
return err
4557
}
46-
47-
con := RemoteConn{
48-
IP: hostCfg.IP,
49-
User: hostCfg.Username,
50-
Password: "",
51-
client: client,
58+
h.remoteConn = &RemoteConn{
59+
IP: h.hostport,
60+
User: h.sshConfig.User,
61+
client: client,
5262
}
53-
return &Host{Name: hostCfg.Name, RemoteConn: con}, nil
63+
return nil
5464
}
5565

5666
func (h *Host) Close() error {
57-
return h.RemoteConn.Close()
67+
if h.remoteConn == nil {
68+
return nil
69+
}
70+
return h.remoteConn.Close()
5871
}
5972

6073
func (h *Host) ExecuteCommandLine(cmd string, args ...string) ([]byte, error) {
61-
return h.RemoteConn.ExecuteCmdline(cmd, args...)
74+
if err := h.connect(); err != nil {
75+
return nil, err
76+
}
77+
return h.remoteConn.ExecuteCmdline(cmd, args...)
6278
}

0 commit comments

Comments
 (0)