Skip to content

Commit 16b287b

Browse files
authored
Merge pull request containerd#69 from coryb/started-chan
allow for optional started channel to receive runc pid
2 parents ee817f5 + 8abfc31 commit 16b287b

File tree

2 files changed

+148
-9
lines changed

2 files changed

+148
-9
lines changed

Diff for: runc.go

+26-9
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const (
5656

5757
// List returns all containers created inside the provided runc root directory
5858
func (r *Runc) List(context context.Context) ([]*Container, error) {
59-
data, err := cmdOutput(r.command(context, "list", "--format=json"), false)
59+
data, err := cmdOutput(r.command(context, "list", "--format=json"), false, nil)
6060
defer putBuf(data)
6161
if err != nil {
6262
return nil, err
@@ -70,7 +70,7 @@ func (r *Runc) List(context context.Context) ([]*Container, error) {
7070

7171
// State returns the state for the container provided by id
7272
func (r *Runc) State(context context.Context, id string) (*Container, error) {
73-
data, err := cmdOutput(r.command(context, "state", id), true)
73+
data, err := cmdOutput(r.command(context, "state", id), true, nil)
7474
defer putBuf(data)
7575
if err != nil {
7676
return nil, fmt.Errorf("%s: %s", err, data.String())
@@ -95,6 +95,7 @@ type CreateOpts struct {
9595
NoPivot bool
9696
NoNewKeyring bool
9797
ExtraFiles []*os.File
98+
Started chan<- int
9899
}
99100

100101
func (o *CreateOpts) args() (out []string, err error) {
@@ -140,7 +141,7 @@ func (r *Runc) Create(context context.Context, id, bundle string, opts *CreateOp
140141
cmd.ExtraFiles = opts.ExtraFiles
141142

142143
if cmd.Stdout == nil && cmd.Stderr == nil {
143-
data, err := cmdOutput(cmd, true)
144+
data, err := cmdOutput(cmd, true, nil)
144145
defer putBuf(data)
145146
if err != nil {
146147
return fmt.Errorf("%s: %s", err, data.String())
@@ -175,6 +176,7 @@ type ExecOpts struct {
175176
PidFile string
176177
ConsoleSocket ConsoleSocket
177178
Detach bool
179+
Started chan<- int
178180
}
179181

180182
func (o *ExecOpts) args() (out []string, err error) {
@@ -197,6 +199,9 @@ func (o *ExecOpts) args() (out []string, err error) {
197199
// Exec executes an additional process inside the container based on a full
198200
// OCI Process specification
199201
func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts *ExecOpts) error {
202+
if opts.Started != nil {
203+
defer close(opts.Started)
204+
}
200205
f, err := ioutil.TempFile(os.Getenv("XDG_RUNTIME_DIR"), "runc-process")
201206
if err != nil {
202207
return err
@@ -220,7 +225,7 @@ func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts
220225
opts.Set(cmd)
221226
}
222227
if cmd.Stdout == nil && cmd.Stderr == nil {
223-
data, err := cmdOutput(cmd, true)
228+
data, err := cmdOutput(cmd, true, opts.Started)
224229
defer putBuf(data)
225230
if err != nil {
226231
return fmt.Errorf("%w: %s", err, data.String())
@@ -231,6 +236,9 @@ func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts
231236
if err != nil {
232237
return err
233238
}
239+
if opts.Started != nil {
240+
opts.Started <- cmd.Process.Pid
241+
}
234242
if opts != nil && opts.IO != nil {
235243
if c, ok := opts.IO.(StartCloser); ok {
236244
if err := c.CloseAfterStart(); err != nil {
@@ -248,6 +256,9 @@ func (r *Runc) Exec(context context.Context, id string, spec specs.Process, opts
248256
// Run runs the create, start, delete lifecycle of the container
249257
// and returns its exit status after it has exited
250258
func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts) (int, error) {
259+
if opts.Started != nil {
260+
defer close(opts.Started)
261+
}
251262
args := []string{"run", "--bundle", bundle}
252263
if opts != nil {
253264
oargs, err := opts.args()
@@ -264,6 +275,9 @@ func (r *Runc) Run(context context.Context, id, bundle string, opts *CreateOpts)
264275
if err != nil {
265276
return -1, err
266277
}
278+
if opts.Started != nil {
279+
opts.Started <- cmd.Process.Pid
280+
}
267281
status, err := Monitor.Wait(cmd, ec)
268282
if err == nil && status != 0 {
269283
err = fmt.Errorf("%s did not terminate successfully: %w", cmd.Args[0], &ExitError{status})
@@ -387,7 +401,7 @@ func (r *Runc) Resume(context context.Context, id string) error {
387401

388402
// Ps lists all the processes inside the container returning their pids
389403
func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
390-
data, err := cmdOutput(r.command(context, "ps", "--format", "json", id), true)
404+
data, err := cmdOutput(r.command(context, "ps", "--format", "json", id), true, nil)
391405
defer putBuf(data)
392406
if err != nil {
393407
return nil, fmt.Errorf("%s: %s", err, data.String())
@@ -401,7 +415,7 @@ func (r *Runc) Ps(context context.Context, id string) ([]int, error) {
401415

402416
// Top lists all the processes inside the container returning the full ps data
403417
func (r *Runc) Top(context context.Context, id string, psOptions string) (*TopResults, error) {
404-
data, err := cmdOutput(r.command(context, "ps", "--format", "table", id, psOptions), true)
418+
data, err := cmdOutput(r.command(context, "ps", "--format", "table", id, psOptions), true, nil)
405419
defer putBuf(data)
406420
if err != nil {
407421
return nil, fmt.Errorf("%s: %s", err, data.String())
@@ -613,7 +627,7 @@ type Version struct {
613627

614628
// Version returns the runc and runtime-spec versions
615629
func (r *Runc) Version(context context.Context) (Version, error) {
616-
data, err := cmdOutput(r.command(context, "--version"), false)
630+
data, err := cmdOutput(r.command(context, "--version"), false, nil)
617631
defer putBuf(data)
618632
if err != nil {
619633
return Version{}, err
@@ -685,7 +699,7 @@ func (r *Runc) runOrError(cmd *exec.Cmd) error {
685699
}
686700
return err
687701
}
688-
data, err := cmdOutput(cmd, true)
702+
data, err := cmdOutput(cmd, true, nil)
689703
defer putBuf(data)
690704
if err != nil {
691705
return fmt.Errorf("%s: %s", err, data.String())
@@ -695,7 +709,7 @@ func (r *Runc) runOrError(cmd *exec.Cmd) error {
695709

696710
// callers of cmdOutput are expected to call putBuf on the returned Buffer
697711
// to ensure it is released back to the shared pool after use.
698-
func cmdOutput(cmd *exec.Cmd, combined bool) (*bytes.Buffer, error) {
712+
func cmdOutput(cmd *exec.Cmd, combined bool, started chan<- int) (*bytes.Buffer, error) {
699713
b := getBuf()
700714

701715
cmd.Stdout = b
@@ -706,6 +720,9 @@ func cmdOutput(cmd *exec.Cmd, combined bool) (*bytes.Buffer, error) {
706720
if err != nil {
707721
return nil, err
708722
}
723+
if started != nil {
724+
started <- cmd.Process.Pid
725+
}
709726

710727
status, err := Monitor.Wait(cmd, ec)
711728
if err == nil && status != 0 {

Diff for: runc_test.go

+122
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@ package runc
1919
import (
2020
"context"
2121
"errors"
22+
"io/ioutil"
23+
"os"
2224
"sync"
25+
"syscall"
2326
"testing"
27+
"time"
2428

2529
specs "github.com/opencontainers/runtime-spec/specs-go"
2630
)
@@ -182,6 +186,78 @@ func TestRuncExecExit(t *testing.T) {
182186
}
183187
}
184188

189+
func TestRuncStarted(t *testing.T) {
190+
ctx, timeout := context.WithTimeout(context.Background(), 10*time.Second)
191+
defer timeout()
192+
193+
dummyCommand, err := dummySleepRunc()
194+
if err != nil {
195+
t.Fatalf("Failed to create dummy sleep runc: %s", err)
196+
}
197+
defer os.Remove(dummyCommand)
198+
sleepRunc := &Runc{
199+
Command: dummyCommand,
200+
}
201+
202+
var wg sync.WaitGroup
203+
defer wg.Wait()
204+
205+
started := make(chan int)
206+
wg.Add(1)
207+
go func() {
208+
defer wg.Done()
209+
interrupt(ctx, t, started)
210+
}()
211+
status, err := sleepRunc.Run(ctx, "fake-id", "fake-bundle", &CreateOpts{
212+
Started: started,
213+
})
214+
if err == nil {
215+
t.Fatal("Expected error from Run, but got nil")
216+
}
217+
if status != -1 {
218+
t.Fatalf("Expected exit status 0 from Run, got %d", status)
219+
}
220+
221+
started = make(chan int)
222+
wg.Add(1)
223+
go func() {
224+
defer wg.Done()
225+
interrupt(ctx, t, started)
226+
}()
227+
err = sleepRunc.Exec(ctx, "fake-id", specs.Process{}, &ExecOpts{
228+
Started: started,
229+
})
230+
if err == nil {
231+
t.Fatal("Expected error from Exec, but got nil")
232+
}
233+
status = extractStatus(err)
234+
if status != -1 {
235+
t.Fatalf("Expected exit status -1 from Exec, got %d", status)
236+
}
237+
238+
started = make(chan int)
239+
wg.Add(1)
240+
go func() {
241+
defer wg.Done()
242+
interrupt(ctx, t, started)
243+
}()
244+
io, err := NewSTDIO()
245+
if err != nil {
246+
t.Fatalf("Unexpected error from NewSTDIO: %s", err)
247+
}
248+
err = sleepRunc.Exec(ctx, "fake-id", specs.Process{}, &ExecOpts{
249+
IO: io,
250+
Started: started,
251+
})
252+
if err == nil {
253+
t.Fatal("Expected error from Exec, but got nil")
254+
}
255+
status = extractStatus(err)
256+
if status != -1 {
257+
t.Fatalf("Expected exit status 1 from Exec, got %d", status)
258+
}
259+
}
260+
185261
func extractStatus(err error) int {
186262
if err == nil {
187263
return 0
@@ -192,3 +268,49 @@ func extractStatus(err error) int {
192268
}
193269
return -1
194270
}
271+
272+
// interrupt waits for the pid over the started channel then sends a
273+
// SIGINT to the process.
274+
func interrupt(ctx context.Context, t *testing.T, started <-chan int) {
275+
select {
276+
case <-ctx.Done():
277+
t.Fatal("Timed out waiting for started message")
278+
case pid, ok := <-started:
279+
if !ok {
280+
t.Fatal("Started channel closed without sending pid")
281+
}
282+
process, _ := os.FindProcess(pid)
283+
defer process.Release()
284+
err := process.Signal(syscall.SIGINT)
285+
if err != nil {
286+
t.Fatalf("Failed to send SIGINT to %d: %s", pid, err)
287+
}
288+
}
289+
}
290+
291+
// dummySleepRunc creates s simple script that just runs `sleep 10` to replace
292+
// runc for testing process that are longer running.
293+
func dummySleepRunc() (_ string, err error) {
294+
fh, err := ioutil.TempFile("", "*.sh")
295+
if err != nil {
296+
return "", err
297+
}
298+
defer func() {
299+
if err != nil {
300+
os.Remove(fh.Name())
301+
}
302+
}()
303+
_, err = fh.Write([]byte("#!/bin/sh\nexec /bin/sleep 10"))
304+
if err != nil {
305+
return "", err
306+
}
307+
err = fh.Close()
308+
if err != nil {
309+
return "", err
310+
}
311+
err = os.Chmod(fh.Name(), 0755)
312+
if err != nil {
313+
return "", err
314+
}
315+
return fh.Name(), nil
316+
}

0 commit comments

Comments
 (0)