Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

Commit

Permalink
Merge pull request #194 from devimc/cli/fixPs
Browse files Browse the repository at this point in the history
grpc: implement ListProcesses
  • Loading branch information
grahamwhaley authored Apr 13, 2018
2 parents a6166b7 + ff7eaa1 commit 7aa11d8
Show file tree
Hide file tree
Showing 10 changed files with 1,022 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
71 changes: 71 additions & 0 deletions grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,74 @@ 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)
}

func TestListProcesses(t *testing.T) {
containerID := "1"
assert := assert.New(t)
req := &pb.ListProcessesRequest{
ContainerId: containerID,
Format: "table",
Args: []string{"-ef"},
}

a := &agentGRPC{
sandbox: &sandbox{
containers: make(map[string]*container),
subreaper: &mockreaper{},
},
}
// getContainer should fail
r, err := a.ListProcesses(context.TODO(), req)
assert.Error(err)
assert.NotNil(r)

// should fail, unknown format
req.Format = "unknown"
a.sandbox.containers[containerID] = &container{
container: &mockContainer{
id: containerID,
processes: []int{1},
},
}
r, err = a.ListProcesses(context.TODO(), req)
assert.Error(err)
assert.NotNil(r)

// json format
req.Format = "json"
r, err = a.ListProcesses(context.TODO(), req)
assert.NoError(err)
assert.NotNil(r)
assert.NotEmpty(r.ProcessList)

// table format
req.Format = "table"
r, err = a.ListProcesses(context.TODO(), req)
assert.NoError(err)
assert.NotNil(r)
assert.NotEmpty(r.ProcessList)
}
92 changes: 92 additions & 0 deletions mockcontainer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//

package main

import (
"os"

"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/configs"
)

type mockContainer struct {
id string
status libcontainer.Status
processes []int
}

func (m *mockContainer) ID() string {
return m.id
}

func (m *mockContainer) Status() (libcontainer.Status, error) {
return m.status, nil
}

func (m *mockContainer) State() (*libcontainer.State, error) {
return nil, nil
}

func (m *mockContainer) Config() configs.Config {
return configs.Config{}
}

func (m *mockContainer) Processes() ([]int, error) {
return m.processes, nil
}

func (m *mockContainer) Stats() (*libcontainer.Stats, error) {
return nil, nil
}

func (m *mockContainer) Set(config configs.Config) error {
return nil
}

func (m *mockContainer) Start(process *libcontainer.Process) (err error) {
return nil
}

func (m *mockContainer) Run(process *libcontainer.Process) (err error) {
return nil
}

func (m *mockContainer) Destroy() error {
return nil
}

func (m *mockContainer) Signal(s os.Signal, all bool) error {
return nil
}

func (m *mockContainer) Exec() error {
return nil
}

func (m *mockContainer) Checkpoint(criuOpts *libcontainer.CriuOpts) error {
return nil
}

func (m *mockContainer) Restore(process *libcontainer.Process, criuOpts *libcontainer.CriuOpts) error {
return nil
}

func (m *mockContainer) Pause() error {
return nil
}

func (m *mockContainer) Resume() error {
return nil
}

func (m *mockContainer) NotifyOOM() (<-chan struct{}, error) {
return nil, nil
}

func (m *mockContainer) NotifyMemoryPressure(level libcontainer.PressureLevel) (<-chan struct{}, error) {
return nil, nil
}
Loading

0 comments on commit 7aa11d8

Please sign in to comment.