Skip to content

Commit f929631

Browse files
committed
feat(ps): query k8s to get the ps:list information instead of db
Fetches all the information directly from k8s and uses the pod name in the output. Can no longer do a limit fetch as the k8s API does not support it Fixes #160
1 parent 786f3eb commit f929631

11 files changed

Lines changed: 188 additions & 70 deletions

File tree

client/cmd/ps.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ import (
1414
// PsList lists an app's processes.
1515
func PsList(appID string, results int) error {
1616
c, appID, err := load(appID)
17-
1817
if err != nil {
1918
return err
2019
}
@@ -23,13 +22,12 @@ func PsList(appID string, results int) error {
2322
results = c.ResponseLimit
2423
}
2524

26-
processes, count, err := ps.List(c, appID, results)
27-
25+
processes, _, err := ps.List(c, appID, results)
2826
if err != nil {
2927
return err
3028
}
3129

32-
printProcesses(appID, processes, count)
30+
printProcesses(appID, processes)
3331

3432
return nil
3533
}
@@ -73,13 +71,12 @@ func PsScale(appID string, targets []string) error {
7371

7472
fmt.Printf("done in %ds\n", int(time.Since(startTime).Seconds()))
7573

76-
processes, count, err := ps.List(c, appID, c.ResponseLimit)
77-
74+
processes, _, err := ps.List(c, appID, c.ResponseLimit)
7875
if err != nil {
7976
return err
8077
}
8178

82-
printProcesses(appID, processes, count)
79+
printProcesses(appID, processes)
8380
return nil
8481
}
8582

@@ -123,26 +120,25 @@ func PsRestart(appID, target string) error {
123120

124121
fmt.Printf("done in %ds\n", int(time.Since(startTime).Seconds()))
125122

126-
processes, count, err := ps.List(c, appID, c.ResponseLimit)
127-
123+
processes, _, err := ps.List(c, appID, c.ResponseLimit)
128124
if err != nil {
129125
return err
130126
}
131127

132-
printProcesses(appID, processes, count)
128+
printProcesses(appID, processes)
133129
return nil
134130
}
135131

136-
func printProcesses(appID string, processes []api.Process, count int) {
132+
func printProcesses(appID string, processes []api.Pods) {
137133
psMap := ps.ByType(processes)
138134

139-
fmt.Printf("=== %s Processes%s", appID, limitCount(len(processes), count))
135+
fmt.Printf("=== %s Processes\n", appID)
140136

141137
for psType, procs := range psMap {
142138
fmt.Printf("--- %s:\n", psType)
143139

144140
for _, proc := range procs {
145-
fmt.Printf("%s.%d %s (%s)\n", proc.Type, proc.Num, proc.State, proc.Release)
141+
fmt.Printf("%s %s (%s)\n", proc.Name, proc.State, proc.Release)
146142
}
147143
}
148144
}

client/controller/api/ps.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,11 @@ type Process struct {
1212
Num int `json:"num"`
1313
State string `json:"state"`
1414
}
15+
16+
// Pods defines the structure of a process.
17+
type Pods struct {
18+
Release string `json:"release"`
19+
Type string `json:"type"`
20+
Name string `json:"name"`
21+
State string `json:"state"`
22+
}

client/controller/models/ps/ps.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,16 @@ import (
1010
)
1111

1212
// List an app's processes.
13-
func List(c *client.Client, appID string, results int) ([]api.Process, int, error) {
14-
u := fmt.Sprintf("/v2/apps/%s/containers/", appID)
13+
func List(c *client.Client, appID string, results int) ([]api.Pods, int, error) {
14+
u := fmt.Sprintf("/v2/apps/%s/pods/", appID)
1515
body, count, err := c.LimitedRequest(u, results)
16-
1716
if err != nil {
18-
return []api.Process{}, -1, err
17+
return []api.Pods{}, -1, err
1918
}
2019

21-
var procs []api.Process
20+
var procs []api.Pods
2221
if err = json.Unmarshal([]byte(body), &procs); err != nil {
23-
return []api.Process{}, -1, err
22+
return []api.Pods{}, -1, err
2423
}
2524

2625
return procs, count, nil
@@ -69,8 +68,8 @@ func Restart(c *client.Client, appID string, procType string, num int) ([]api.Pr
6968
}
7069

7170
// ByType organizes processes of an app by process type.
72-
func ByType(processes []api.Process) map[string][]api.Process {
73-
psMap := make(map[string][]api.Process)
71+
func ByType(processes []api.Pods) map[string][]api.Pods {
72+
psMap := make(map[string][]api.Pods)
7473

7574
for _, ps := range processes {
7675
psMap[ps.Type] = append(psMap[ps.Type], ps)

client/controller/models/ps/ps_test.go

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,9 @@ const processesFixture string = `
2121
"previous": null,
2222
"results": [
2323
{
24-
"owner": "test",
25-
"app": "example-go",
2624
"release": "v2",
27-
"created": "2014-01-01T00:00:00UTC",
28-
"updated": "2014-01-01T00:00:00UTC",
29-
"uuid": "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75",
3025
"type": "web",
31-
"num": 1,
26+
"name": "example-go-v2-web-45678",
3227
"state": "up"
3328
}
3429
]
@@ -86,7 +81,7 @@ type fakeHTTPServer struct{}
8681
func (fakeHTTPServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
8782
res.Header().Add("DEIS_API_VERSION", version.APIVersion)
8883

89-
if req.URL.Path == "/v2/apps/example-go/containers/" && req.Method == "GET" {
84+
if req.URL.Path == "/v2/apps/example-go/pods/" && req.Method == "GET" {
9085
res.Write([]byte(processesFixture))
9186
return
9287
}
@@ -135,16 +130,11 @@ func (fakeHTTPServer) ServeHTTP(res http.ResponseWriter, req *http.Request) {
135130
func TestProcessesList(t *testing.T) {
136131
t.Parallel()
137132

138-
expected := []api.Process{
133+
expected := []api.Pods{
139134
{
140-
Owner: "test",
141-
App: "example-go",
142135
Release: "v2",
143-
Created: "2014-01-01T00:00:00UTC",
144-
Updated: "2014-01-01T00:00:00UTC",
145-
UUID: "de1bf5b5-4a72-4f94-a10c-d2a3741cdf75",
146136
Type: "web",
147-
Num: 1,
137+
Name: "example-go-v2-web-45678",
148138
State: "up",
149139
},
150140
}

client/parser/ps.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,23 +48,15 @@ Usage: deis ps:list [options]
4848
Options:
4949
-a --app=<app>
5050
the uniquely identifiable name for the application.
51-
-l --limit=<num>
52-
the maximum number of results to display, defaults to config setting
5351
`
5452

5553
args, err := docopt.Parse(usage, argv, true, "", false, true)
56-
57-
if err != nil {
58-
return err
59-
}
60-
61-
results, err := responseLimit(safeGetValue(args, "--limit"))
62-
6354
if err != nil {
6455
return err
6556
}
6657

67-
return cmd.PsList(safeGetValue(args, "--app"), results)
58+
// The 1000 is fake for now until API understands limits
59+
return cmd.PsList(safeGetValue(args, "--app"), 1000)
6860
}
6961

7062
func psRestart(argv []string) error {

rootfs/api/models/app.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ def validate_reserved_names(value):
4444
raise ValidationError('{} is a reserved name.'.format(value))
4545

4646

47+
class Pod(object):
48+
pass
49+
50+
4751
class App(UuidAuditedModel):
4852
"""
4953
Application used to service requests on behalf of end-users
@@ -428,3 +432,40 @@ def run(self, user, command):
428432
# SECURITY: shell-escape user input
429433
escaped_command = command.replace("'", "'\\''")
430434
return c.run(escaped_command)
435+
436+
def list_pods(self, *args, **kwargs):
437+
"""Used to list basic information about pods running for a given application"""
438+
try:
439+
labels = {'app': str(self)}
440+
441+
if 'release' in kwargs:
442+
if kwargs['release'] is None:
443+
release = self.release_set.latest()
444+
445+
version = "v{}".format(release.version)
446+
labels.update({'version': version})
447+
448+
if 'type' in kwargs:
449+
labels.update({'type': kwargs['type']})
450+
451+
pods = self._scheduler._get_pods(str(self), labels=labels).json()
452+
453+
data = []
454+
for pod in pods['items']:
455+
# specifically ignore run pods
456+
if pod['metadata']['labels']['type'] == 'run':
457+
continue
458+
459+
item = Pod()
460+
item.name = pod['metadata']['name']
461+
item.state = self._scheduler.resolve_state(pod).name
462+
item.release = pod['metadata']['labels']['version']
463+
item.type = pod['metadata']['labels']['type']
464+
465+
data.append(item)
466+
467+
return data
468+
except Exception as e:
469+
err = '(list pods): {}'.format(e)
470+
log_event(self, err, logging.ERROR)
471+
raise

rootfs/api/serializers.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,18 @@ class Meta:
309309
model = models.Push
310310
fields = ['owner', 'app', 'sha', 'fingerprint', 'receive_user', 'receive_repo',
311311
'ssh_connection', 'ssh_original_command', 'created', 'updated']
312+
313+
314+
class PodSerializer(serializers.BaseSerializer):
315+
name = serializers.CharField()
316+
state = serializers.CharField()
317+
type = serializers.CharField()
318+
release = serializers.CharField()
319+
320+
def to_representation(self, obj):
321+
return {
322+
'name': obj.name,
323+
'state': obj.state,
324+
'type': obj.type,
325+
'release': obj.release
326+
}

rootfs/api/urls.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@
4444
views.ContainerViewSet.as_view({'get': 'list'})),
4545
url(r"^apps/(?P<id>{})/containers/?".format(settings.APP_URL_REGEX),
4646
views.ContainerViewSet.as_view({'get': 'list'})),
47+
# list pods
48+
url(r"^apps/(?P<id>{})/pods/(?P<type>[-_\w]+)/(?P<name>[-_\w]+)/?".format(
49+
settings.APP_URL_REGEX),
50+
views.PodViewSet.as_view({'get': 'list'})),
51+
url(r"^apps/(?P<id>{})/pods/(?P<type>[-_\w.]+)/?".format(settings.APP_URL_REGEX),
52+
views.PodViewSet.as_view({'get': 'list'})),
53+
url(r"^apps/(?P<id>{})/pods/?".format(settings.APP_URL_REGEX),
54+
views.PodViewSet.as_view({'get': 'list'})),
4755
# application domains
4856
url(r"^apps/(?P<id>{})/domains/(?P<domain>\**\.?[-\._\w]+)/?".format(settings.APP_URL_REGEX),
4957
views.DomainViewSet.as_view({'delete': 'destroy'})),

rootfs/api/views.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,23 @@ def restart(self, *args, **kwargs):
335335
return Response({'detail': str(e)}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
336336

337337

338+
class PodViewSet(AppResourceViewSet):
339+
model = models.App
340+
serializer_class = serializers.PodSerializer
341+
342+
def list(self, *args, **kwargs):
343+
app = self.get_app()
344+
345+
try:
346+
pods = app.list_pods(*args, **kwargs)
347+
data = self.get_serializer(pods, many=True).data
348+
# fake out pagination for now
349+
pagination = {'results': data, 'count': len(data)}
350+
return Response(pagination, status=status.HTTP_200_OK)
351+
except Exception as e:
352+
return Response({'detail': str(e)}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
353+
354+
338355
class DomainViewSet(AppResourceViewSet):
339356
"""A viewset for interacting with Domain objects."""
340357
model = models.Domain

0 commit comments

Comments
 (0)