Skip to content

Commit 5bd0c9d

Browse files
committed
Add the dut command from linuxboot
It is much easier to bring dut into cpu than have 2 repos. Further, dut is not a combined cpu and cpud; it is useful to have this illustrative use of the cpu package. Signed-off-by: Ronald G. Minnich <rminnich@gmail.com>
1 parent de61a4b commit 5bd0c9d

File tree

8 files changed

+516
-0
lines changed

8 files changed

+516
-0
lines changed

dut/doc.go

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// dut manages Devices Under Test (a.k.a. DUT) from a host.
2+
// A primary goal is allowing multiple hosts with any architecture to connect.
3+
//
4+
// This program was designed to be used in u-root images, as the uinit,
5+
// or in other initramfs systems. It can not function as a standalone
6+
// init: it assumes network is set up, for example.
7+
//
8+
// In this document, dut refers to this program, and DUT refers to
9+
// Devices Under Test. Hopefully this is not too confusing, but it is
10+
// convenient. Also, please note: DUT is plural (Devices). We don't need
11+
// to say DUTs -- at least one is assumed.
12+
//
13+
// The same dut binary runs on host and DUT, in either device mode (i.e.
14+
// on the DUT), or in some host-specific mode. The mode is chosen by
15+
// the first non-flag argument. If there are flags specific to that mode,
16+
// they follow that argument.
17+
// E.g., when uinit is run on the host and we want it to enable cpu daemons
18+
// on the DUT, we run it as follows:
19+
// dut cpu -key ...
20+
// the -key switch is only valid following the cpu mode argument.
21+
//
22+
// modes
23+
// dut currently supports 3 modes.
24+
//
25+
// The first, default, mode, is "device". In device mode, dut makes an http connection
26+
// to a dut running on a host, then starts an HTTP RPC server.
27+
//
28+
// The second mode is "tester". In this mode, dut calls the Welcome service, followed
29+
// by the Reboot service. Tester can be useful, run by a shell script in a for loop, for
30+
// ensure reboot is reliable.
31+
//
32+
// The third mode is "cpu". dut will direct the DUT to start a cpu service, and block until
33+
// it exits. Flags for this service:
34+
// pubkey: name of the public key file
35+
// hostkey: name of the host key file
36+
// cpuport: port on which to serve the cpu service
37+
//
38+
// Theory of Operation
39+
// dut runs on the host, accepting connections from DUT, and controlling them via
40+
// Go HTTP RPC commands. As each command is executed, its response is printed.
41+
// Commands are:
42+
//
43+
// Welcome -- get a welcome message
44+
// Argument: None
45+
// Return: a welcome message in cowsay format:
46+
// < welcome to DUT >
47+
// --------------
48+
// \ ^__^
49+
// \ (oo)\_______
50+
// (__)\ )\/\
51+
// ||----w |
52+
// || ||
53+
//
54+
// Die -- force dut on DUT to exit
55+
// Argument: time to sleep before exiting as a time.Duration
56+
// Return: no return; kills the program running on DUT
57+
//
58+
// Reboot
59+
// Argument: time to sleep before rebooting as a time.Duration
60+
//
61+
// CPU -- Start a CPU server on DUT
62+
// Arguments: public key and host key as a []byte, service port as a string
63+
// Returns: returns (possibly nil) error exit value of cpu server; blocks until it is done
64+
//
65+
//
66+
package main

dut/dut.go

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
// This is a very simple dut program. It builds into one binary to implement
2+
// both client and server. It's just easier to see both sides of the code and test
3+
// that way.
4+
package main
5+
6+
import (
7+
"flag"
8+
"fmt"
9+
"io/ioutil"
10+
"log"
11+
"net"
12+
"net/rpc"
13+
"os"
14+
"time"
15+
16+
"github.com/u-root/u-root/pkg/ulog"
17+
"golang.org/x/sys/unix"
18+
)
19+
20+
var (
21+
debug = flag.Bool("d", false, "Enable debug prints")
22+
host = flag.String("host", "192.168.0.1", "hostname")
23+
klog = flag.Bool("klog", false, "Direct all logging to klog -- depends on debug")
24+
port = flag.String("port", "8080", "port number")
25+
dir = flag.String("dir", ".", "directory to serve")
26+
27+
// for debug
28+
v = func(string, ...interface{}) {}
29+
)
30+
31+
func dutStart(t, host, port string) (net.Listener, error) {
32+
ln, err := net.Listen(t, host+":"+port)
33+
if err != nil {
34+
log.Print(err)
35+
return nil, err
36+
}
37+
log.Printf("Listening on %v at %v", ln.Addr(), time.Now())
38+
return ln, nil
39+
}
40+
41+
func dutAccept(l net.Listener) (net.Conn, error) {
42+
if err := l.(*net.TCPListener).SetDeadline(time.Now().Add(3 * time.Minute)); err != nil {
43+
return nil, err
44+
}
45+
c, err := l.Accept()
46+
if err != nil {
47+
log.Printf("Listen failed: %v at %v", err, time.Now())
48+
log.Print(err)
49+
return nil, err
50+
}
51+
log.Printf("Accepted %v", c)
52+
return c, nil
53+
}
54+
55+
func dutRPC(host, port string) error {
56+
l, err := dutStart("tcp", host, port)
57+
if err != nil {
58+
return err
59+
}
60+
c, err := dutAccept(l)
61+
if err != nil {
62+
return err
63+
}
64+
cl := rpc.NewClient(c)
65+
for _, cmd := range []struct {
66+
call string
67+
args interface{}
68+
}{
69+
{"Command.Welcome", &RPCWelcome{}},
70+
{"Command.Reboot", &RPCReboot{}},
71+
} {
72+
var r RPCRes
73+
if err := cl.Call(cmd.call, cmd.args, &r); err != nil {
74+
return err
75+
}
76+
fmt.Printf("%v(%v): %v\n", cmd.call, cmd.args, string(r.C))
77+
}
78+
79+
if c, err = dutAccept(l); err != nil {
80+
return err
81+
}
82+
cl = rpc.NewClient(c)
83+
var r RPCRes
84+
if err := cl.Call("Command.Welcome", &RPCWelcome{}, &r); err != nil {
85+
return err
86+
}
87+
fmt.Printf("%v(%v): %v\n", "Command.Welcome", nil, string(r.C))
88+
89+
return nil
90+
}
91+
92+
func dutcpu(host, port, pubkey, hostkey, cpuport string) error {
93+
var req = &RPCCPU{Port: cpuport}
94+
var err error
95+
96+
// we send the pubkey and hostkey as the value of the key, not the
97+
// name of the file.
98+
// TODO: maybe use ssh_config to find keys? the cpu client can do that.
99+
// Note: the public key is not optional. That said, we do not test
100+
// for len(*pubKey) > 0; if it is set to ""< ReadFile will return
101+
// an error.
102+
if req.PubKey, err = ioutil.ReadFile(pubkey); err != nil {
103+
return fmt.Errorf("Reading pubKey:%w", err)
104+
}
105+
if len(hostkey) > 0 {
106+
if req.HostKey, err = ioutil.ReadFile(hostkey); err != nil {
107+
return fmt.Errorf("Reading hostKey:%w", err)
108+
}
109+
}
110+
111+
l, err := dutStart("tcp", host, port)
112+
if err != nil {
113+
return err
114+
}
115+
116+
c, err := dutAccept(l)
117+
if err != nil {
118+
return err
119+
}
120+
121+
cl := rpc.NewClient(c)
122+
123+
for _, cmd := range []struct {
124+
call string
125+
args interface{}
126+
}{
127+
{"Command.Welcome", &RPCWelcome{}},
128+
{"Command.Welcome", &RPCWelcome{}},
129+
{"Command.CPU", req},
130+
} {
131+
var r RPCRes
132+
if err := cl.Call(cmd.call, cmd.args, &r); err != nil {
133+
return err
134+
}
135+
fmt.Printf("%v(%v): %v\n", cmd.call, cmd.args, string(r.C))
136+
}
137+
return err
138+
}
139+
140+
func main() {
141+
// for CPU
142+
flag.Parse()
143+
144+
if *debug {
145+
v = log.Printf
146+
if *klog {
147+
ulog.KernelLog.Reinit()
148+
v = ulog.KernelLog.Printf
149+
}
150+
}
151+
a := flag.Args()
152+
if len(a) == 0 {
153+
a = []string{"device"}
154+
}
155+
156+
os.Args = a
157+
var err error
158+
v("Mode is %v", a[0])
159+
switch a[0] {
160+
case "tester":
161+
err = dutRPC(*host, *port)
162+
case "cpu":
163+
var (
164+
pubKey = flag.String("pubkey", "key.pub", "public key file")
165+
hostKey = flag.String("hostkey", "", "host key file -- usually empty")
166+
cpuPort = flag.String("cpuport", "17010", "cpu port -- IANA value is ncpu tcp/17010")
167+
)
168+
v("Parse %v", os.Args)
169+
flag.Parse()
170+
v("pubkey %v", *pubKey)
171+
if err := dutcpu(*host, *port, *pubKey, *hostKey, *cpuPort); err != nil {
172+
log.Printf("cpu service: %v", err)
173+
}
174+
case "device":
175+
err = uinit(*host, *port)
176+
// What to do after a return? Reboot I suppose.
177+
log.Printf("Device returns with error %v", err)
178+
if err := unix.Reboot(int(unix.LINUX_REBOOT_CMD_RESTART)); err != nil {
179+
log.Printf("Reboot failed, not sure what to do now.")
180+
}
181+
default:
182+
log.Printf("Unknown mode %v", a[0])
183+
}
184+
log.Printf("We are now done ......................")
185+
if err != nil {
186+
log.Printf("%v", err)
187+
os.Exit(2)
188+
}
189+
}

dut/dut_test.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package main
2+
3+
import (
4+
"net/rpc"
5+
"testing"
6+
"time"
7+
)
8+
9+
func TestUinit(t *testing.T) {
10+
var tests = []struct {
11+
c string
12+
r interface{}
13+
err string
14+
}{
15+
{c: "Welcome", r: RPCWelcome{}},
16+
{c: "Reboot", r: RPCReboot{}},
17+
}
18+
l, err := dutStart("tcp", "localhost", "")
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
23+
a := l.Addr()
24+
t.Logf("listening on %v", a)
25+
// Kick off our node.
26+
go func() {
27+
time.Sleep(1)
28+
if err := uinit(a.Network(), a.String(), "17010"); err != nil {
29+
t.Fatalf("starting uinit: got %v, want nil", err)
30+
}
31+
}()
32+
33+
c, err := dutAccept(l)
34+
if err != nil {
35+
t.Fatal(err)
36+
}
37+
t.Logf("Connected on %v", c)
38+
39+
cl := rpc.NewClient(c)
40+
for _, tt := range tests {
41+
t.Run(tt.c, func(t *testing.T) {
42+
var r RPCRes
43+
if err = cl.Call("Command."+tt.c, tt.r, &r); err != nil {
44+
t.Fatalf("Call to %v: got %v, want nil", tt.c, err)
45+
}
46+
if r.Err != tt.err {
47+
t.Errorf("%v: got %v, want %v", tt, r.Err, tt.err)
48+
}
49+
})
50+
}
51+
}

dut/rpc.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"log"
6+
"os"
7+
"time"
8+
9+
"golang.org/x/sys/unix"
10+
)
11+
12+
type RPCRes struct {
13+
C []byte
14+
Err string
15+
}
16+
17+
type Command int
18+
19+
type RPCWelcome struct {
20+
}
21+
22+
func (*Command) Welcome(args *RPCWelcome, r *RPCRes) error {
23+
r.C = []byte(welcome)
24+
r.Err = ""
25+
log.Printf("welcome")
26+
return nil
27+
}
28+
29+
type RPCExit struct {
30+
When time.Duration
31+
}
32+
33+
func (*Command) Die(args *RPCExit, r *RPCRes) error {
34+
go func() {
35+
time.Sleep(args.When)
36+
log.Printf("die exits")
37+
os.Exit(0)
38+
}()
39+
*r = RPCRes{}
40+
log.Printf("die returns")
41+
return nil
42+
}
43+
44+
type RPCReboot struct {
45+
When time.Duration
46+
}
47+
48+
func (*Command) Reboot(args *RPCReboot, r *RPCRes) error {
49+
go func() {
50+
time.Sleep(args.When)
51+
if err := unix.Reboot(unix.LINUX_REBOOT_CMD_RESTART); err != nil {
52+
log.Printf("%v\n", err)
53+
}
54+
}()
55+
*r = RPCRes{}
56+
log.Printf("reboot returns")
57+
return nil
58+
}
59+
60+
type RPCCPU struct {
61+
PubKey []byte
62+
HostKey []byte
63+
Port string
64+
}
65+
66+
func (*Command) CPU(args *RPCCPU, r *RPCRes) error {
67+
v("CPU")
68+
res := make(chan error)
69+
go func(pubKey, hostKey []byte, port string) {
70+
v("cpu serve(%q,%q,%q)", pubKey, hostKey, port)
71+
err := serve(pubKey, hostKey, port)
72+
v("cpu serve returns")
73+
res <- err
74+
}(args.PubKey, args.HostKey, args.Port)
75+
err := <-res
76+
*r = RPCRes{Err: fmt.Sprintf("%v", err)}
77+
v("cpud returns")
78+
return nil
79+
}

0 commit comments

Comments
 (0)