Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Implement RPC status and summary methods

Includes cli output formatting

Change-Id: I617289a5faf9655e2835a5fc351f8b258d7a51f1
  • Loading branch information...
commit 9003452163d0e90e41d9d18b24215ee2fac4d41b 1 parent eb2b8da
Doug MacEachern authored August 23, 2012
119  api.go
@@ -4,7 +4,7 @@ package gonit
4 4
 
5 5
 import (
6 6
 	"errors"
7  
-	"fmt"
  7
+	"github.com/cloudfoundry/gosigar"
8 8
 )
9 9
 
10 10
 // until stubs are implemented
@@ -14,12 +14,18 @@ type API struct {
14 14
 	control *Control
15 15
 }
16 16
 
  17
+type ProcessSummary struct {
  18
+	Name         string
  19
+	Running      bool
  20
+	ControlState processState
  21
+}
  22
+
17 23
 type ProcessStatus struct {
18  
-	Name   string
19  
-	Type   int
20  
-	Mode   int
21  
-	Status int
22  
-	// XXX cpu, mem, etc
  24
+	Summary ProcessSummary
  25
+	Pid     int
  26
+	State   sigar.ProcState
  27
+	Time    sigar.ProcTime
  28
+	Mem     sigar.ProcMem
23 29
 }
24 30
 
25 31
 type SystemStatus struct {
@@ -27,8 +33,12 @@ type SystemStatus struct {
27 33
 }
28 34
 
29 35
 type ProcessGroupStatus struct {
30  
-	Name     string
31  
-	Processs []ProcessStatus
  36
+	Name  string
  37
+	Group []ProcessStatus
  38
+}
  39
+
  40
+type Summary struct {
  41
+	Processes []ProcessSummary
32 42
 }
33 43
 
34 44
 type About struct {
@@ -92,24 +102,56 @@ func (a *API) UnmonitorProcess(name string, r *ActionResult) error {
92 102
 	return a.control.callAction(name, r, ACTION_UNMONITOR)
93 103
 }
94 104
 
  105
+func (c *Control) processSummary(process *Process, summary *ProcessSummary) {
  106
+	summary.Name = process.Name
  107
+	summary.Running = process.IsRunning()
  108
+	summary.ControlState = *c.State(process)
  109
+}
  110
+
  111
+func (c *Control) processStatus(process *Process, status *ProcessStatus) error {
  112
+	c.processSummary(process, &status.Summary)
  113
+
  114
+	if !status.Summary.Running {
  115
+		return nil
  116
+	}
  117
+
  118
+	pid, err := process.Pid()
  119
+	if err != nil {
  120
+		return err
  121
+	}
  122
+	status.Pid = pid
  123
+
  124
+	status.State.Get(pid)
  125
+	status.Time.Get(pid)
  126
+	status.Mem.Get(pid)
  127
+
  128
+	return nil
  129
+}
  130
+
95 131
 func (a *API) StatusProcess(name string, r *ProcessStatus) error {
96  
-	return notimpl
  132
+	process, err := a.control.Config().FindProcess(name)
  133
+
  134
+	if err != nil {
  135
+		return err
  136
+	}
  137
+
  138
+	return a.control.processStatus(process, r)
97 139
 }
98 140
 
99 141
 // *Group methods apply to a service group
100 142
 
101 143
 func (c *Control) groupAction(name string, r *ActionResult, action int) error {
102  
-	group, exists := c.Config().ProcessGroups[name]
  144
+	group, err := c.Config().FindGroup(name)
103 145
 
104  
-	if exists {
105  
-		for name := range group.Processes {
106  
-			c.callAction(name, r, action)
107  
-		}
108  
-		return nil
  146
+	if err != nil {
  147
+		return &ActionError{err}
109 148
 	}
110 149
 
111  
-	err := fmt.Errorf("process group %q does not exist", name)
112  
-	return &ActionError{err}
  150
+	for name := range group.Processes {
  151
+		c.callAction(name, r, action)
  152
+	}
  153
+
  154
+	return nil
113 155
 }
114 156
 
115 157
 func (a *API) StartGroup(name string, r *ActionResult) error {
@@ -132,8 +174,29 @@ func (a *API) UnmonitorGroup(name string, r *ActionResult) error {
132 174
 	return a.control.groupAction(name, r, ACTION_UNMONITOR)
133 175
 }
134 176
 
  177
+func (c *Control) groupStatus(group *ProcessGroup,
  178
+	groupStatus *ProcessGroupStatus) error {
  179
+
  180
+	for _, process := range group.Processes {
  181
+		status := ProcessStatus{}
  182
+		c.processStatus(process, &status)
  183
+		groupStatus.Group = append(groupStatus.Group, status)
  184
+	}
  185
+
  186
+	return nil
  187
+}
  188
+
135 189
 func (a *API) StatusGroup(name string, r *ProcessGroupStatus) error {
136  
-	return notimpl
  190
+	group, err := a.control.Config().FindGroup(name)
  191
+
  192
+	if err != nil {
  193
+		return err
  194
+	}
  195
+
  196
+	r.Name = name
  197
+	a.control.groupStatus(group, r)
  198
+
  199
+	return nil
137 200
 }
138 201
 
139 202
 // *All methods apply to all services
@@ -168,7 +231,25 @@ func (a *API) UnmonitorAll(unused interface{}, r *ActionResult) error {
168 231
 }
169 232
 
170 233
 func (a *API) StatusAll(name string, r *ProcessGroupStatus) error {
171  
-	return notimpl
  234
+	r.Name = name
  235
+
  236
+	for _, processGroup := range a.control.Config().ProcessGroups {
  237
+		a.control.groupStatus(processGroup, r)
  238
+	}
  239
+
  240
+	return nil
  241
+}
  242
+
  243
+func (a *API) Summary(unused interface{}, s *Summary) error {
  244
+	for _, group := range a.control.Config().ProcessGroups {
  245
+		for _, process := range group.Processes {
  246
+			summary := ProcessSummary{}
  247
+			a.control.processSummary(process, &summary)
  248
+			s.Processes = append(s.Processes, summary)
  249
+		}
  250
+	}
  251
+
  252
+	return nil
172 253
 }
173 254
 
174 255
 // server info
127  api_test.go
@@ -3,18 +3,23 @@
3 3
 package gonit_test
4 4
 
5 5
 import (
  6
+	"bytes"
6 7
 	"github.com/bmizerany/assert"
7 8
 	. "github.com/cloudfoundry/gonit"
8 9
 	"github.com/cloudfoundry/gonit/test/helper"
  10
+	"io/ioutil"
  11
+	"math"
9 12
 	"net/rpc"
  13
+	"os"
10 14
 	"strings"
11 15
 	"testing"
12 16
 )
13 17
 
14 18
 var rpcName = "TestAPI"
  19
+var config = &ConfigManager{}
15 20
 
16 21
 func init() {
17  
-	rpc.RegisterName(rpcName, NewAPI(nil))
  22
+	rpc.RegisterName(rpcName, NewAPI(config))
18 23
 }
19 24
 
20 25
 func isActionError(err error) bool {
@@ -66,3 +71,123 @@ func TestAbout(t *testing.T) {
66 71
 
67 72
 	assert.Equal(t, nil, err)
68 73
 }
  74
+
  75
+func tmpPidFile(t *testing.T, pid int) string {
  76
+	file, err := ioutil.TempFile("", "api_test")
  77
+	if err != nil {
  78
+		t.Error(err)
  79
+	}
  80
+	if err := WritePidFile(pid, file.Name()); err != nil {
  81
+		t.Fatal(err)
  82
+	}
  83
+	return file.Name()
  84
+}
  85
+
  86
+// simple exercise of CliFormatters
  87
+func testCliPrint(t *testing.T, value CliFormatter) {
  88
+	buf := new(bytes.Buffer)
  89
+	value.Print(buf)
  90
+	assert.NotEqual(t, 0, buf.Len())
  91
+}
  92
+
  93
+func TestStatus(t *testing.T) {
  94
+	nprocesses := 0
  95
+
  96
+	type testProcess struct {
  97
+		name    string
  98
+		pid     int
  99
+		ppid    int
  100
+		running bool
  101
+	}
  102
+
  103
+	// use pid/ppid of the go test process to test
  104
+	// Running, sigar.ProcState, etc.
  105
+	pid := os.Getpid()
  106
+	ppid := os.Getppid()
  107
+
  108
+	groups := []struct {
  109
+		name      string
  110
+		processes []testProcess
  111
+	}{
  112
+		{
  113
+			name: "a_group",
  114
+			processes: []testProcess{
  115
+				{"a_process", pid, ppid, true},
  116
+				{"x_process", math.MaxInt32, -1, false},
  117
+			},
  118
+		},
  119
+		{
  120
+			name: "b_group",
  121
+			processes: []testProcess{
  122
+				{"b_process", pid, ppid, true},
  123
+			},
  124
+		},
  125
+	}
  126
+
  127
+	for _, group := range groups {
  128
+		for _, process := range group.processes {
  129
+			// write pidfile for use by process.IsRunning()
  130
+			pidfile := tmpPidFile(t, process.pid)
  131
+			defer os.Remove(pidfile)
  132
+
  133
+			config.AddProcess(group.name, &Process{
  134
+				Name:    process.name,
  135
+				Pidfile: pidfile,
  136
+			})
  137
+
  138
+			nprocesses++
  139
+		}
  140
+	}
  141
+
  142
+	err := helper.WithRpcServer(func(c *rpc.Client) {
  143
+		statusGroup := rpcName + ".StatusGroup"
  144
+		statusProcess := rpcName + ".StatusProcess"
  145
+
  146
+		// should get an error if group does not exist
  147
+		err := c.Call(statusGroup, "enogroup", &ProcessGroupStatus{})
  148
+		assert.NotEqual(t, nil, err)
  149
+
  150
+		// should get an error if process does not exist
  151
+		err = c.Call(statusProcess, "enoprocess", &ProcessStatus{})
  152
+		assert.NotEqual(t, nil, err)
  153
+
  154
+		for _, group := range groups {
  155
+			gstat := &ProcessGroupStatus{}
  156
+			err := c.Call(statusGroup, group.name, gstat)
  157
+			assert.Equal(t, nil, err)
  158
+			assert.Equal(t, len(group.processes), len(gstat.Group))
  159
+			testCliPrint(t, gstat)
  160
+
  161
+			for _, process := range group.processes {
  162
+				stat := &ProcessStatus{}
  163
+				err := c.Call(statusProcess, process.name, stat)
  164
+				assert.Equal(t, nil, err)
  165
+
  166
+				running := stat.Summary.Running
  167
+				assert.Equal(t, process.running, running)
  168
+				testCliPrint(t, stat)
  169
+
  170
+				if !running {
  171
+					continue
  172
+				}
  173
+
  174
+				assert.Equal(t, process.pid, stat.Pid)
  175
+				assert.Equal(t, process.ppid, stat.State.Ppid)
  176
+			}
  177
+		}
  178
+
  179
+		all := &ProcessGroupStatus{}
  180
+		err = c.Call(rpcName+".StatusAll", "", all)
  181
+		assert.Equal(t, nil, err)
  182
+		assert.Equal(t, nprocesses, len(all.Group))
  183
+		testCliPrint(t, all)
  184
+
  185
+		summary := &Summary{}
  186
+		err = c.Call(rpcName+".Summary", "", summary)
  187
+		assert.Equal(t, nil, err)
  188
+		assert.Equal(t, nprocesses, len(summary.Processes))
  189
+		testCliPrint(t, summary)
  190
+	})
  191
+
  192
+	assert.Equal(t, nil, err)
  193
+}
108  cli_formatter.go
... ...
@@ -0,0 +1,108 @@
  1
+// Copyright (c) 2012 VMware, Inc.
  2
+
  3
+package gonit
  4
+
  5
+import (
  6
+	"fmt"
  7
+	"io"
  8
+	"text/tabwriter"
  9
+	"time"
  10
+)
  11
+
  12
+// format data returned from the API
  13
+type CliFormatter interface {
  14
+	Print(w io.Writer)
  15
+}
  16
+
  17
+func (p *ProcessStatus) Print(w io.Writer) {
  18
+	writeTable(w, func(tw io.Writer) {
  19
+		p.write(tw)
  20
+	})
  21
+}
  22
+
  23
+func (g *ProcessGroupStatus) Print(w io.Writer) {
  24
+	writeTable(w, func(tw io.Writer) {
  25
+		for _, p := range g.Group {
  26
+			p.write(tw)
  27
+		}
  28
+	})
  29
+}
  30
+
  31
+func (s *Summary) Print(w io.Writer) {
  32
+	writeTable(w, func(tw io.Writer) {
  33
+		for _, p := range s.Processes {
  34
+			p.write(tw)
  35
+		}
  36
+	})
  37
+}
  38
+
  39
+func writeTable(w io.Writer, f func(io.Writer)) {
  40
+	tw := new(tabwriter.Writer)
  41
+	tw.Init(w, 0, 8, 8, ' ', 0)
  42
+	f(tw)
  43
+	tw.Flush()
  44
+}
  45
+
  46
+func (p *ProcessSummary) monitorString() string {
  47
+	monitor := []struct {
  48
+		mode  int
  49
+		label string
  50
+	}{
  51
+		{MONITOR_WAITING, "waiting"},
  52
+		{MONITOR_INIT, "initializing"},
  53
+		{MONITOR_YES, "monitored"},
  54
+		{MONITOR_NOT, "not monitored"},
  55
+	}
  56
+
  57
+	for _, state := range monitor {
  58
+		if (p.ControlState.Monitor & state.mode) == state.mode {
  59
+			return state.label
  60
+		}
  61
+	}
  62
+
  63
+	panic("not reached")
  64
+}
  65
+
  66
+func (p *ProcessSummary) runningString() string {
  67
+	if p.Running {
  68
+		return "running"
  69
+	}
  70
+	return "not running"
  71
+}
  72
+
  73
+func (p *ProcessStatus) uptime() string {
  74
+	if p.Time.StartTime == 0 {
  75
+		return "-"
  76
+	}
  77
+	start := time.Unix(int64(p.Time.StartTime)/1000, 0)
  78
+	return time.Since(start).String()
  79
+}
  80
+
  81
+func (p *ProcessSummary) write(tw io.Writer) {
  82
+	fmt.Fprintf(tw, "Process '%s'\t%s\n", p.Name, p.runningString())
  83
+}
  84
+
  85
+func (p *ProcessStatus) write(tw io.Writer) {
  86
+	fmt.Fprintf(tw, "Process '%s'\t\n", p.Summary.Name)
  87
+
  88
+	status := []struct {
  89
+		label string
  90
+		data  interface{}
  91
+	}{
  92
+		{"status", p.Summary.runningString()},
  93
+		{"monitoring status", p.Summary.monitorString()},
  94
+		{"starts", p.Summary.ControlState.Starts},
  95
+		{"pid", p.Pid},
  96
+		{"parent pid", p.State.Ppid},
  97
+		{"uptime", p.uptime()},
  98
+		{"memory kilobytes", p.Mem.Resident / 1024},
  99
+		{"cpu", p.Time.FormatTotal()}, // TODO %cpu
  100
+		// TODO "data collected"
  101
+	}
  102
+
  103
+	for _, entry := range status {
  104
+		fmt.Fprintf(tw, "  %s\t%v\n", entry.label, entry.data)
  105
+	}
  106
+
  107
+	fmt.Fprintf(tw, "\t\n")
  108
+}
17  control.go
@@ -81,13 +81,24 @@ func (c *ConfigManager) FindProcess(name string) (*Process, error) {
81 81
 	return nil, fmt.Errorf("process %q not found", name)
82 82
 }
83 83
 
  84
+// TODO should probably be in configmanager.go
  85
+// Helper method to find a ProcessGroup by name
  86
+func (c *ConfigManager) FindGroup(name string) (*ProcessGroup, error) {
  87
+	if group, exists := c.ProcessGroups[name]; exists {
  88
+		return group, nil
  89
+	}
  90
+	return nil, fmt.Errorf("process group %q not found", name)
  91
+}
  92
+
84 93
 // configManager accessor (exported for tests)
85 94
 func (c *Control) Config() *ConfigManager {
86 95
 	if c.configManager == nil {
  96
+		c.configManager = &ConfigManager{}
  97
+	}
  98
+
  99
+	if c.configManager.ProcessGroups == nil {
87 100
 		// XXX TODO NewConfigManager() ?
88  
-		c.configManager = &ConfigManager{
89  
-			ProcessGroups: make(map[string]*ProcessGroup),
90  
-		}
  101
+		c.configManager.ProcessGroups = make(map[string]*ProcessGroup)
91 102
 	}
92 103
 
93 104
 	return c.configManager
7  gonit/main.go
@@ -129,10 +129,15 @@ func runCommand(cmd, arg string) {
129 129
 
130 130
 	reply, err := client.Call(method, name)
131 131
 
132  
-	log.Printf("%#v", reply) // XXX make perty
133 132
 	if err != nil {
134 133
 		log.Fatal(err)
135 134
 	}
  135
+
  136
+	if formatter, ok := reply.(gonit.CliFormatter); ok {
  137
+		formatter.Print(os.Stdout)
  138
+	} else {
  139
+		log.Printf("%#v", reply) // TODO make perty
  140
+	}
136 141
 }
137 142
 
138 143
 func reload() {

Git Notes

review

Code-Review+2: Ben Lisbakken <lisbakke@rbcon.com>
Verified+1: CI Master <cf-ci@rbcon.com>
Submitted-by: Doug MacEachern <dougm@vmware.com>
Submitted-at: Tue, 04 Sep 2012 20:34:15 +0000
Reviewed-on: http://reviews.cloudfoundry.org/8700
Project: gonit
Branch: refs/heads/master

0 notes on commit 9003452

Please sign in to comment.
Something went wrong with that request. Please try again.