Skip to content

Commit c931926

Browse files
committed
feat(pods): add describe pods
1 parent 1361a2c commit c931926

7 files changed

Lines changed: 78 additions & 46 deletions

File tree

rootfs/api/models/app.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,35 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits):
385385
raise ServiceUnavailable(err) from e
386386
return name
387387

388+
def describe_pod(self, pod_name):
389+
def get_commands_and_args(pod, container_name):
390+
commands, args = [], []
391+
for container in pod["spec"]["containers"]:
392+
if container["name"] == container_name:
393+
args = container.get("args", [])
394+
commands = container.get("commands", [])
395+
break
396+
return commands, args
397+
result = []
398+
try:
399+
pod = self.scheduler().pod.get(self.id, pod_name).json()
400+
for status in pod["status"]["containerStatuses"]:
401+
commands, args = get_commands_and_args(pod, status["name"])
402+
result.append({
403+
"container": status["name"],
404+
"image": status["image"],
405+
"commands": commands,
406+
"args": args,
407+
"state": status["state"],
408+
"lastState": status["lastState"],
409+
"ready": status["ready"],
410+
"restartCount": status["restartCount"],
411+
})
412+
except KubeHTTPException as e:
413+
if e.response.status_code != 404:
414+
raise e
415+
return result
416+
388417
def list_pods(self, *args, **kwargs):
389418
"""Used to list basic information about pods running for a given application"""
390419
try:

rootfs/api/tests/test_build.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def test_build_default_containers(self, mock_requests):
113113
response = self.client.post(url, body)
114114
self.assertEqual(response.status_code, 201, response.data)
115115

116-
url = f"/v2/apps/{app_id}/pods/web"
116+
url = f"/v2/apps/{app_id}/pods"
117117
response = self.client.get(url)
118118
self.assertEqual(response.status_code, 200, response.data)
119119
self.assertPodContains(response.data['results'], app_id, 'web', "v2", "up")
@@ -132,7 +132,7 @@ def test_build_default_containers(self, mock_requests):
132132
response = self.client.post(url, body)
133133
self.assertEqual(response.status_code, 201, response.data)
134134

135-
url = f"/v2/apps/{app_id}/pods/web"
135+
url = f"/v2/apps/{app_id}/pods/"
136136
response = self.client.get(url)
137137
self.assertEqual(response.status_code, 200, response.data)
138138
self.assertPodContains(response.data['results'], app_id, 'web', "v2", "up")
@@ -150,7 +150,7 @@ def test_build_default_containers(self, mock_requests):
150150
response = self.client.post(url, body)
151151
self.assertEqual(response.status_code, 201, response.data)
152152

153-
url = f"/v2/apps/{app_id}/pods/web"
153+
url = f"/v2/apps/{app_id}/pods/"
154154
response = self.client.get(url)
155155
self.assertEqual(response.status_code, 200, response.data)
156156
self.assertPodContains(response.data['results'], app_id, 'web', "v2", "up")
@@ -171,7 +171,7 @@ def test_build_default_containers(self, mock_requests):
171171
response = self.client.post(url, body)
172172
self.assertEqual(response.status_code, 201, response.data)
173173

174-
url = f"/v2/apps/{app_id}/pods/web"
174+
url = f"/v2/apps/{app_id}/pods/"
175175
response = self.client.get(url)
176176
self.assertEqual(response.status_code, 200, response.data)
177177
self.assertPodContains(response.data['results'], app_id, 'web', "v2", "up")
@@ -192,7 +192,7 @@ def test_build_default_containers(self, mock_requests):
192192
response = self.client.post(url, body)
193193
self.assertEqual(response.status_code, 201, response.data)
194194

195-
url = f"/v2/apps/{app_id}/pods/web"
195+
url = f"/v2/apps/{app_id}/pods/"
196196
response = self.client.get(url)
197197
self.assertEqual(response.status_code, 200, response.data)
198198
self.assertPodContains(response.data['results'], app_id, 'web', "v2", "up")
@@ -238,7 +238,7 @@ def test_build_forgotten_procfile(self, mock_requests):
238238
response = self.client.post(url, body)
239239
self.assertEqual(response.status_code, 201, response.data)
240240
# verify web
241-
url = f"/v2/apps/{app_id}/pods/web"
241+
url = f"/v2/apps/{app_id}/pods/"
242242
response = self.client.get(url)
243243
self.assertEqual(response.status_code, 200, response.data)
244244
self.assertPodContains(response.data['results'], app_id, 'web', "v2", "up")
@@ -250,7 +250,7 @@ def test_build_forgotten_procfile(self, mock_requests):
250250
self.assertEqual(response.status_code, 204, response.data)
251251

252252
# verify worker
253-
url = f"/v2/apps/{app_id}/pods/worker"
253+
url = f"/v2/apps/{app_id}/pods/"
254254
response = self.client.get(url)
255255
self.assertEqual(response.status_code, 200, response.data)
256256

@@ -263,13 +263,13 @@ def test_build_forgotten_procfile(self, mock_requests):
263263
self.assertEqual(response.status_code, 201, response.data)
264264

265265
# verify worker is not there
266-
url = f"/v2/apps/{app_id}/pods/worker"
266+
url = f"/v2/apps/{app_id}/pods/"
267267
response = self.client.get(url)
268268
self.assertEqual(response.status_code, 200, response.data)
269269
self.assertEqual(len(response.data['results']), 0)
270270

271271
# verify web is not there
272-
url = f"/v2/apps/{app_id}/pods/web"
272+
url = f"/v2/apps/{app_id}/pods/"
273273
response = self.client.get(url)
274274
self.assertEqual(response.status_code, 200, response.data)
275275
self.assertEqual(len(response.data['results']), 0)
@@ -311,7 +311,7 @@ def test_build_no_remove_process(self, mock_requests):
311311
self.assertEqual(response.status_code, 204, response.data)
312312

313313
# verify worker
314-
url = f"/v2/apps/{app_id}/pods/worker"
314+
url = f"/v2/apps/{app_id}/pods/"
315315
response = self.client.get(url)
316316
self.assertEqual(response.status_code, 200, response.data)
317317
self.assertPodContains(response.data['results'], app_id, 'worker', "v2", "up")
@@ -323,13 +323,13 @@ def test_build_no_remove_process(self, mock_requests):
323323
self.assertEqual(response.status_code, 201, response.data)
324324

325325
# verify worker is still there
326-
url = f"/v2/apps/{app_id}/pods/worker"
326+
url = f"/v2/apps/{app_id}/pods/"
327327
response = self.client.get(url)
328328
self.assertEqual(response.status_code, 200, response.data)
329329
self.assertPodContains(response.data['results'], app_id, 'worker', "v3", "up")
330330

331331
# verify web is still there
332-
url = f"/v2/apps/{app_id}/pods/web"
332+
url = f"/v2/apps/{app_id}/pods/"
333333
response = self.client.get(url)
334334
self.assertEqual(response.status_code, 200, response.data)
335335
self.assertPodContains(response.data['results'], app_id, 'web', "v3", "up")
@@ -347,7 +347,7 @@ def test_build_no_remove_process(self, mock_requests):
347347
self.assertEqual(response.status_code, 204, response.data)
348348

349349
# verify worker info
350-
url = f"/v2/apps/{app_id}/pods/worker"
350+
url = f"/v2/apps/{app_id}/pods/"
351351
response = self.client.get(url)
352352
self.assertEqual(response.status_code, 200, response.data)
353353
self.assertPodContains(response.data['results'], app_id, 'worker', "v3", "up")
@@ -383,7 +383,7 @@ def test_build_forgotten_procfile_reject(self, mock_requests):
383383
self.assertEqual(response.status_code, 204, response.data)
384384

385385
# verify worker
386-
url = f"/v2/apps/{app_id}/pods/worker"
386+
url = f"/v2/apps/{app_id}/pods/"
387387
response = self.client.get(url)
388388
self.assertEqual(response.status_code, 200, response.data)
389389
self.assertPodContains(response.data['results'], app_id, 'worker', "v2", "up")
@@ -467,7 +467,7 @@ def test_new_build_does_not_scale_up_automatically(self, mock_requests):
467467
response = self.client.post(url, body)
468468
self.assertEqual(response.status_code, 201, response.data)
469469

470-
url = f"/v2/apps/{app_id}/pods/web"
470+
url = f"/v2/apps/{app_id}/pods/"
471471
response = self.client.get(url)
472472
self.assertEqual(response.status_code, 200, response.data)
473473
self.assertPodContains(response.data['results'], app_id, 'web', "v2", "up")
@@ -491,7 +491,7 @@ def test_new_build_does_not_scale_up_automatically(self, mock_requests):
491491
}
492492
response = self.client.post(url, body)
493493
self.assertEqual(response.status_code, 201, response.data)
494-
url = f"/v2/apps/{app_id}/pods/web"
494+
url = f"/v2/apps/{app_id}/pods/"
495495
response = self.client.get(url)
496496
self.assertEqual(response.status_code, 200, response.data)
497497
self.assertEqual(len(response.data['results']), 0)

rootfs/api/tests/test_hooks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,7 +230,7 @@ def test_build_hook_procfile(self, mock_requests):
230230
self.assertEqual(build['procfile'], PROCFILE)
231231

232232
# test listing/retrieving container info
233-
url = f"/v2/apps/{app_id}/pods/web"
233+
url = f"/v2/apps/{app_id}/pods/"
234234
response = self.client.get(url)
235235
self.assertEqual(response.status_code, 200, response.data)
236236
self.assertPodContains(response.data['results'], app_id, 'web', 'v2')
@@ -270,7 +270,7 @@ def test_build_hook_dockerfile(self, mock_requests):
270270
self.assertEqual(build['sha'], SHA)
271271
self.assertEqual(build['dockerfile'], DOCKERFILE)
272272
# test default container
273-
url = f"/v2/apps/{app_id}/pods/web"
273+
url = f"/v2/apps/{app_id}/pods/"
274274
response = self.client.get(url)
275275
self.assertEqual(response.status_code, 200, response.data)
276276
self.assertPodContains(response.data['results'], app_id, 'web', 'v2')

rootfs/api/tests/test_pods.py

Lines changed: 6 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,6 @@ def test_container_api_heroku(self, mock_requests):
8282
self.assertEqual(response.data['structure']['web'], 4)
8383
self.assertEqual(response.data['structure']['worker'], 2)
8484

85-
# test listing/retrieving container info
86-
url = f"/v2/apps/{app_id}/pods/web"
87-
response = self.client.get(url)
88-
self.assertEqual(response.status_code, 200, response.data)
89-
self.assertEqual(response.data['count'], 4)
90-
self.assertEqual(len(response.data['results']), 4)
91-
92-
name = response.data['results'][0]['name']
93-
url = f"/v2/apps/{app_id}/pods/web/{name}"
94-
response = self.client.get(url)
95-
self.assertEqual(response.status_code, 200, response.data)
96-
self.assertEqual(response.data['count'], 1)
97-
self.assertEqual(response.data['results'][0]['name'], name)
98-
9985
# scale down
10086
url = f"/v2/apps/{app_id}/scale"
10187
# test setting two proc types at a time
@@ -164,12 +150,6 @@ def test_container_api(self, mock_requests):
164150
response = self.client.get(url)
165151
self.assertEqual(response.status_code, 200, response.data)
166152

167-
# test listing/retrieving container info
168-
url = f"/v2/apps/{app_id}/pods/web"
169-
response = self.client.get(url)
170-
self.assertEqual(response.status_code, 200, response.data)
171-
self.assertEqual(len(response.data['results']), 6)
172-
173153
# scale down
174154
url = f"/v2/apps/{app_id}/scale"
175155
body = {'web': 3}
@@ -263,6 +243,12 @@ def test_release(self, mock_requests):
263243
response = self.client.get(url)
264244
self.assertEqual(response.status_code, 200, response.data)
265245
self.assertPodContains(response.data['results'], app_id, "web", 'v4')
246+
pod_name = response.data['results'][0]["name"]
247+
response = self.client.get(f"/v2/apps/{app_id}/pods/{pod_name}/describe/")
248+
self.assertEqual(response.status_code, 200, response.data)
249+
pod_name = "no-exists-pod-name"
250+
response = self.client.get(f"/v2/apps/{app_id}/pods/{pod_name}/describe/")
251+
self.assertEqual(response.status_code, 404, response.data)
266252

267253
def test_container_errors(self, mock_requests):
268254
app_id = self.create_app()

rootfs/api/urls.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,13 @@
5353
r"^apps/(?P<id>{})/pods/(?P<type>[-_\w.]+)/restart/?$".format(settings.APP_URL_REGEX),
5454
views.PodViewSet.as_view({'post': 'restart'})),
5555
# list pods
56-
re_path(
57-
r"^apps/(?P<id>{})/pods/(?P<type>[-_\w]+)/(?P<name>[-_\w]+)/?$".format(
58-
settings.APP_URL_REGEX),
59-
views.PodViewSet.as_view({'get': 'list'})),
60-
re_path(
61-
r"^apps/(?P<id>{})/pods/(?P<type>[-_\w.]+)/?$".format(settings.APP_URL_REGEX),
62-
views.PodViewSet.as_view({'get': 'list'})),
6356
re_path(
6457
r"^apps/(?P<id>{})/pods/?$".format(settings.APP_URL_REGEX),
6558
views.PodViewSet.as_view({'get': 'list'})),
59+
# describe pod
60+
re_path(
61+
r"^apps/(?P<id>{})/pods/(?P<name>[-_\w]+)/describe/?$".format(settings.APP_URL_REGEX),
62+
views.PodViewSet.as_view({'get': 'describe'})),
6663
# application domains
6764
re_path(
6865
r"^apps/(?P<id>{})/domains/(?P<domain>\**\.?[-\._\w]+)/?$".format(settings.APP_URL_REGEX),

rootfs/api/views.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,15 @@ def restart(self, *args, **kwargs):
441441
restart_app.delay(self.get_app(), **kwargs)
442442
return Response(status=status.HTTP_204_NO_CONTENT)
443443

444+
def describe(self, *args, **kwargs):
445+
pod_name = kwargs["name"]
446+
data = self.get_app().describe_pod(pod_name)
447+
if len(data) == 0:
448+
return Response(status=status.HTTP_404_NOT_FOUND)
449+
# fake out pagination for now
450+
pagination = {'results': data, 'count': len(data)}
451+
return Response(pagination, status=status.HTTP_200_OK)
452+
444453

445454
class AppSettingsViewSet(AppResourceViewSet):
446455
model = models.appsettings.AppSettings

rootfs/scheduler/mock.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,14 +368,25 @@ def create_pods(url, labels, base, new_pods):
368368
'containerStatuses': [
369369
{
370370
'name': '{}-{}'.format(labels['app'], labels['type']),
371+
'image': '127.0.0.1/{}-{}'.format(labels['app'], labels['type']),
371372
# TODO ready can be True / False (boolean)
372373
'ready': True,
373374
# TODO can be running / terminated / waiting
374375
'state': {
375376
'running': {
376377
'startedAt': timestamp
377378
}
378-
}
379+
},
380+
'lastState': {
381+
"terminated": {
382+
"containerID": f"cri-o://{uuid.uuid4().hex * 2}",
383+
"exitCode": 1,
384+
"finishedAt": timestamp,
385+
"reason": "Error",
386+
"startedAt": timestamp
387+
}
388+
},
389+
'restartCount': 0,
379390
}
380391
],
381392
}

0 commit comments

Comments
 (0)