Skip to content

Commit

Permalink
grpc: implement ListProcesses
Browse files Browse the repository at this point in the history
ListProcesses returns a list of running processes inside
the container, this function should be called by the runtime
in the ps command implementation.

fixes kata-containers#193

Signed-off-by: Julio Montes <julio.montes@intel.com>
  • Loading branch information
Julio Montes committed Apr 10, 2018
1 parent a80ad16 commit c190e91
Show file tree
Hide file tree
Showing 7 changed files with 714 additions and 104 deletions.
95 changes: 95 additions & 0 deletions grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
"syscall"
Expand Down Expand Up @@ -623,6 +627,97 @@ func (a *agentGRPC) WaitProcess(ctx context.Context, req *pb.WaitProcessRequest)
}, nil
}

func getPIDIndex(title string) int {
// looking for PID field in ps title
fields := strings.Fields(title)
for i, f := range fields {
if f == "PID" {
return i
}
}
return -1
}

func (a *agentGRPC) ListProcesses(ctx context.Context, req *pb.ListProcessesRequest) (*pb.ListProcessesResponse, error) {
resp := &pb.ListProcessesResponse{}

c, err := a.sandbox.getContainer(req.ContainerId)
if err != nil {
return resp, err
}

// Get the list of processes that are running inside the containers.
// the PIDs match with the system PIDs, not with container's namespace
pids, err := c.container.Processes()
if err != nil {
return resp, err
}

switch req.Format {
case "table":
case "json":
resp.ProcessList, err = json.Marshal(pids)
return resp, err
default:
return resp, fmt.Errorf("invalid format option")
}

psArgs := req.Args
if len(psArgs) == 0 {
psArgs = []string{"-ef"}
}

// All container's processes are visibles from agent's namespace.
// pids already contains the list of processes that are running
// inside a container, now we have to use that list to filter
// ps output and return just container's processes
cmd := exec.Command("ps", psArgs...)
output, err := a.sandbox.subreaper.combinedOutput(cmd)
if err != nil {
return nil, fmt.Errorf("%s: %s", err, output)
}

lines := strings.Split(string(output), "\n")

pidIndex := getPIDIndex(lines[0])

// PID field not found
if pidIndex == -1 {
return nil, fmt.Errorf("failed to find PID field in ps output")
}

// append title
var result bytes.Buffer

result.WriteString(lines[0] + "\n")

for _, line := range lines[1:] {
if len(line) == 0 {
continue
}
fields := strings.Fields(line)
if pidIndex >= len(fields) {
return nil, fmt.Errorf("missing PID field: %s", line)
}

p, err := strconv.Atoi(fields[pidIndex])
if err != nil {
return nil, fmt.Errorf("failed to convert pid to int: %s", fields[pidIndex])
}

// appends pid line
for _, pid := range pids {
if pid == p {
result.WriteString(line + "\n")
break
}
}
}

resp.ProcessList = result.Bytes()
return resp, nil
}

func (a *agentGRPC) RemoveContainer(ctx context.Context, req *pb.RemoveContainerRequest) (*gpb.Empty, error) {
ctr, err := a.sandbox.getContainer(req.ContainerId)
if err != nil {
Expand Down
24 changes: 24 additions & 0 deletions grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,27 @@ func TestOnlineCPUMem(t *testing.T) {
_, err = a.OnlineCPUMem(context.TODO(), req)
assert.NoError(err)
}

func TestGetPIDIndex(t *testing.T) {
assert := assert.New(t)

title := "UID PID PPID C STIME TTY TIME CMD"
pidIndex := 1
index := getPIDIndex(title)
assert.Equal(pidIndex, index)

title = "PID PPID C STIME TTY TIME CMD"
pidIndex = 0
index = getPIDIndex(title)
assert.Equal(pidIndex, index)

title = "PPID C STIME TTY TIME CMD PID"
pidIndex = 6
index = getPIDIndex(title)
assert.Equal(pidIndex, index)

title = "PPID C STIME TTY TIME CMD"
pidIndex = -1
index = getPIDIndex(title)
assert.Equal(pidIndex, index)
}
29 changes: 28 additions & 1 deletion mockreaper.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@

package main

import "os/exec"
import (
"bytes"
"errors"
"fmt"
"os/exec"
)

type mockreaper struct {
}
Expand Down Expand Up @@ -41,3 +46,25 @@ func (r *mockreaper) lock() {

func (r *mockreaper) unlock() {
}

func (r *mockreaper) run(c *exec.Cmd) error {
if err := c.Run(); err != nil {
return fmt.Errorf("reaper: Could not start process: %v", err)
}
return nil
}

func (r *mockreaper) combinedOutput(c *exec.Cmd) ([]byte, error) {
if c.Stdout != nil {
return nil, errors.New("reaper: Stdout already set")
}
if c.Stderr != nil {
return nil, errors.New("reaper: Stderr already set")
}

var b bytes.Buffer
c.Stdout = &b
c.Stderr = &b
err := r.run(c)
return b.Bytes(), err
}
Loading

0 comments on commit c190e91

Please sign in to comment.