Skip to content

Commit de4267b

Browse files
committed
Add LCOW logpath within uVM
Add annotation to tee container `stdin` and `stderr` to a specified path within the uVM. Add a dedicated `trapsort.MultiWriter` type that writes to both an underlying `transport.Connection` and the provided `io.Writer`. Also, move `logConnection` from `stdio` to `transport`, to keep the different `Connection` implementations together. Add functional tests. Streamline `test/internal/util` by making `CleanName` operation on the test name directly (`testing.TB.Name()`), since that is the majority of the usage. Add `CleanString` to handle the original functionality. Signed-off-by: Hamza El-Saawy <[email protected]>
1 parent 512dd27 commit de4267b

File tree

13 files changed

+297
-69
lines changed

13 files changed

+297
-69
lines changed

internal/guest/runtime/hcsv2/container.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ const (
4545
)
4646

4747
type Container struct {
48-
id string
49-
vsock transport.Transport
48+
id string
49+
50+
vsock transport.Transport
51+
logFile *os.File
5052

5153
spec *oci.Spec
5254
ociBundlePath string
@@ -77,10 +79,17 @@ type Container struct {
7779

7880
func (c *Container) Start(ctx context.Context, conSettings stdio.ConnectionSettings) (int, error) {
7981
log.G(ctx).WithField(logfields.ContainerID, c.id).Info("opengcs::Container::Start")
80-
stdioSet, err := stdio.Connect(c.vsock, conSettings)
82+
83+
// only use the logfile for the init process, since we don't want to tee stdio of execs
84+
t := c.vsock
85+
if c.logFile != nil {
86+
t = transport.NewMultiWriter(c.vsock, c.logFile)
87+
}
88+
stdioSet, err := stdio.Connect(t, conSettings)
8189
if err != nil {
8290
return -1, err
8391
}
92+
8493
if c.initProcess.spec.Terminal {
8594
ttyr := c.container.Tty()
8695
ttyr.ReplaceConnectionSet(stdioSet)
@@ -180,12 +189,24 @@ func (c *Container) GetAllProcessPids(ctx context.Context) ([]int, error) {
180189

181190
// Kill sends 'signal' to the container process.
182191
func (c *Container) Kill(ctx context.Context, signal syscall.Signal) error {
183-
log.G(ctx).WithField(logfields.ContainerID, c.id).Info("opengcs::Container::Kill")
192+
entity := log.G(ctx).WithField(logfields.ContainerID, c.id)
193+
entity.Info("opengcs::Container::Kill")
184194
err := c.container.Kill(signal)
185195
if err != nil {
186196
return err
187197
}
198+
188199
c.setExitType(signal)
200+
if c.logFile != nil {
201+
if err = c.logFile.Close(); err != nil {
202+
entity.WithFields(logrus.Fields{
203+
logrus.ErrorKey: err,
204+
logfields.Path: c.logFile.Name(),
205+
}).Warn("failed to close log file")
206+
}
207+
c.logFile = nil
208+
}
209+
189210
return nil
190211
}
191212

internal/guest/runtime/hcsv2/uvm.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646
"github.com/Microsoft/hcsshim/internal/guest/storage/scsi"
4747
"github.com/Microsoft/hcsshim/internal/guest/transport"
4848
"github.com/Microsoft/hcsshim/internal/log"
49+
"github.com/Microsoft/hcsshim/internal/logfields"
4950
"github.com/Microsoft/hcsshim/internal/oci"
5051
"github.com/Microsoft/hcsshim/internal/protocol/guestrequest"
5152
"github.com/Microsoft/hcsshim/internal/protocol/guestresource"
@@ -442,6 +443,26 @@ func (h *Host) CreateContainer(ctx context.Context, id string, settings *prot.VM
442443
c.vsock = h.devNullTransport
443444
}
444445

446+
logPath := settings.OCISpecification.Annotations[annotations.LCOWTeeLogPath]
447+
if logPath != "" {
448+
if !allowStdio {
449+
return nil, errors.Errorf("teeing container stdio to log path %q denied due to policy not allowing stdio access", logPath)
450+
}
451+
452+
if logPath, err = filepath.Abs(logPath); err != nil {
453+
return nil, errors.Wrapf(err, "failed to evaluate log path: %s", logPath)
454+
}
455+
456+
log.G(ctx).WithField(logfields.Path, logPath).Debug("creating container log file")
457+
dir := filepath.Dir(logPath)
458+
if err := mkdirAllModePerm(dir); err != nil {
459+
return nil, errors.Wrapf(err, "failed to create log file parent directory: %s", dir)
460+
}
461+
if c.logFile, err = os.Create(logPath); err != nil {
462+
return nil, errors.Wrapf(err, "failed to create log file: %s", logPath)
463+
}
464+
}
465+
445466
if envToKeep != nil {
446467
settings.OCISpecification.Process.Env = []string(envToKeep)
447468
}

internal/guest/stdio/connection.go

Lines changed: 4 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@
44
package stdio
55

66
import (
7-
"os"
7+
"github.com/pkg/errors"
88

99
"github.com/Microsoft/hcsshim/internal/guest/transport"
10-
"github.com/pkg/errors"
11-
"github.com/sirupsen/logrus"
1210
)
1311

1412
// ConnectionSettings describe the stdin, stdout, stderr ports to connect the
@@ -19,49 +17,6 @@ type ConnectionSettings struct {
1917
StdErr *uint32
2018
}
2119

22-
type logConnection struct {
23-
con transport.Connection
24-
port uint32
25-
}
26-
27-
func (lc *logConnection) Read(b []byte) (int, error) {
28-
return lc.con.Read(b)
29-
}
30-
31-
func (lc *logConnection) Write(b []byte) (int, error) {
32-
return lc.con.Write(b)
33-
}
34-
35-
func (lc *logConnection) Close() error {
36-
logrus.WithFields(logrus.Fields{
37-
"port": lc.port,
38-
}).Debug("opengcs::logConnection::Close - closing connection")
39-
40-
return lc.con.Close()
41-
}
42-
43-
func (lc *logConnection) CloseRead() error {
44-
logrus.WithFields(logrus.Fields{
45-
"port": lc.port,
46-
}).Debug("opengcs::logConnection::Close - closing read connection")
47-
48-
return lc.con.CloseRead()
49-
}
50-
51-
func (lc *logConnection) CloseWrite() error {
52-
logrus.WithFields(logrus.Fields{
53-
"port": lc.port,
54-
}).Debug("opengcs::logConnection::Close - closing write connection")
55-
56-
return lc.con.CloseWrite()
57-
}
58-
59-
func (lc *logConnection) File() (*os.File, error) {
60-
return lc.con.File()
61-
}
62-
63-
var _ = (transport.Connection)(&logConnection{})
64-
6520
// Connect returns new transport.Connection instances, one for each stdio pipe
6621
// to be used. If CreateStd*Pipe for a given pipe is false, the given Connection
6722
// is set to nil.
@@ -77,30 +32,21 @@ func Connect(tport transport.Transport, settings ConnectionSettings) (_ *Connect
7732
if err != nil {
7833
return nil, errors.Wrap(err, "failed creating stdin Connection")
7934
}
80-
connSet.In = &logConnection{
81-
con: c,
82-
port: *settings.StdIn,
83-
}
35+
connSet.In = transport.NewLogConnection(c, *settings.StdIn)
8436
}
8537
if settings.StdOut != nil {
8638
c, err := tport.Dial(*settings.StdOut)
8739
if err != nil {
8840
return nil, errors.Wrap(err, "failed creating stdout Connection")
8941
}
90-
connSet.Out = &logConnection{
91-
con: c,
92-
port: *settings.StdOut,
93-
}
42+
connSet.Out = transport.NewLogConnection(c, *settings.StdOut)
9443
}
9544
if settings.StdErr != nil {
9645
c, err := tport.Dial(*settings.StdErr)
9746
if err != nil {
9847
return nil, errors.Wrap(err, "failed creating stderr Connection")
9948
}
100-
connSet.Err = &logConnection{
101-
con: c,
102-
port: *settings.StdErr,
103-
}
49+
connSet.Err = transport.NewLogConnection(c, *settings.StdErr)
10450
}
10551
return connSet, nil
10652
}

internal/guest/transport/log.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//go:build linux
2+
3+
package transport
4+
5+
import (
6+
"github.com/sirupsen/logrus"
7+
)
8+
9+
// logConnection wraps the underlying [Connection] and logs the Close*() operations with
10+
// the connection's port number.
11+
type logConnection struct {
12+
Connection
13+
entry *logrus.Entry
14+
}
15+
16+
var _ Connection = &logConnection{}
17+
18+
func NewLogConnection(c Connection, port uint32) Connection {
19+
return &logConnection{c, logrus.WithField("port", port)}
20+
}
21+
22+
func (c *logConnection) Close() error {
23+
c.entry.Debug("opengcs::logConnection::Close - closing connection")
24+
25+
return c.Connection.Close()
26+
}
27+
28+
func (c *logConnection) CloseRead() error {
29+
c.entry.Debug("opengcs::logConnection::Close - closing read connection")
30+
31+
return c.Connection.CloseRead()
32+
}
33+
34+
func (c *logConnection) CloseWrite() error {
35+
c.entry.Debug("opengcs::logConnection::Close - closing write connection")
36+
37+
return c.Connection.CloseWrite()
38+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//go:build linux
2+
3+
package transport
4+
5+
import (
6+
"fmt"
7+
"io"
8+
)
9+
10+
// multiWriter writes to both the underlying connection and the [io.Writer], w, via [io.multiWriter].
11+
type multiWriter struct {
12+
t Transport
13+
w io.Writer
14+
}
15+
16+
var _ Transport = &multiWriter{}
17+
18+
func NewMultiWriter(t Transport, w io.Writer) Transport {
19+
return &multiWriter{t, w}
20+
}
21+
22+
// Dial accepts a vsock socket port number as configuration, and
23+
// returns an unconnected VsockConnection struct.
24+
func (t *multiWriter) Dial(port uint32) (Connection, error) {
25+
if t == nil || t.w == nil || t.t == nil {
26+
return nil, fmt.Errorf("invalid transpot")
27+
}
28+
29+
conn, err := t.t.Dial(port)
30+
if err != nil {
31+
return nil, fmt.Errorf("multiwriter base transport dial: %w", err)
32+
}
33+
return &multiWriterConnection{conn, io.MultiWriter(conn, t.w)}, nil
34+
}
35+
36+
type multiWriterConnection struct {
37+
Connection
38+
multi io.Writer
39+
}
40+
41+
var _ Connection = &multiWriterConnection{}
42+
43+
func (c *multiWriterConnection) Write(buf []byte) (int, error) {
44+
return c.multi.Write(buf)
45+
}

internal/guest/transport/transport.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import (
55
"os"
66
)
77

8+
// TODO: specialized [Transport] and [Connection] for [io.Reader]/[io.Writer] instead of both,
9+
// so either stdin or stdout/stderr can be specialized without affecting the other.
10+
// i.e., don't use [multiWriter] for stdin.
11+
812
// Transport is the interface defining a method of transporting data in a
913
// connection-like way.
1014
// Examples of a Transport implementation could be:

pkg/annotations/annotations.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ const (
105105

106106
// LCOWPrivileged is used to specify that the container should be run in privileged mode.
107107
LCOWPrivileged = "io.microsoft.virtualmachine.lcow.privileged"
108+
109+
// LCOWTeeLogPath specifies a path inside the Linux uVM to write container's stdio to,
110+
// in addition to the usual vsock pipe.
111+
//
112+
// Functionally, it is similar to `LogDirectory` and `LogPath` for CRI, but within the guest.
113+
LCOWTeeLogPath = "io.microsoft.container.lcow.tee-log-path"
108114
)
109115

110116
// LCOW integrity protection and confidential container annotations.

0 commit comments

Comments
 (0)