Skip to content

Commit 7ce8a09

Browse files
author
lijianguo
committed
feat(ps):add ps:stop/start command
1 parent b42c261 commit 7ce8a09

5 files changed

Lines changed: 122 additions & 11 deletions

File tree

rootfs/api/models/app.py

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,65 @@ def scale(self, user, structure): # noqa
496496

497497
return False
498498

499+
def stop(self, user, types): # noqa
500+
"""scale containers which types contained down """
501+
502+
if self.release_set.filter(failed=False).latest().build is None:
503+
raise DryccException('No build associated with this release')
504+
505+
release = self.release_set.filter(failed=False).latest()
506+
structure = {_: 0 for _ in types}
507+
508+
# test for available process types
509+
available_process_types = release.build.procfile or {}
510+
for container_type in types:
511+
if container_type == 'cmd':
512+
continue # allow docker cmd types in case we don't have the image source
513+
514+
if container_type not in available_process_types:
515+
raise NotFound(
516+
'Container type {} does not exist in application'.format(container_type))
517+
518+
# merge current structure and the new items together
519+
old_structure = self.structure
520+
new_structure = old_structure.copy()
521+
new_structure.update(structure)
522+
523+
if new_structure != self.structure:
524+
try:
525+
self._scale_pods(structure)
526+
except ServiceUnavailable:
527+
# scaling failed, go back to old scaling numbers
528+
self._scale_pods(old_structure)
529+
raise
530+
531+
msg = '{} stopped pods '.format(user.username) + ' '.join(types)
532+
self.log(msg)
533+
534+
return True
535+
536+
return False
537+
538+
def start(self, user, types): # noqa
539+
"""scale containers which types contained up."""
540+
# use create to make sure minimum resources are created
541+
self.create()
542+
if self.release_set.filter(failed=False).latest().build is None:
543+
raise DryccException('No build associated with this release')
544+
545+
structure = {}
546+
for k, v in self.structure.items():
547+
if k in types:
548+
structure[k] = v
549+
try:
550+
self._scale_pods(structure)
551+
except ServiceUnavailable:
552+
# scaling failed, go back to old scaling numbers
553+
raise
554+
msg = '{} stopped pods '.format(user.username) + ' '.join(types)
555+
self.log(msg)
556+
return True
557+
499558
def _scale_pods(self, scale_types):
500559
release = self.release_set.filter(failed=False).latest()
501560
app_settings = self.appsettings_set.latest()
@@ -863,6 +922,7 @@ def list_pods(self, *args, **kwargs):
863922
pods = []
864923

865924
data = []
925+
exist_pod_type = []
866926
for p in pods:
867927
labels = p['metadata']['labels']
868928
# specifically ignore run pods
@@ -889,7 +949,9 @@ def list_pods(self, *args, **kwargs):
889949
else:
890950
started = str(datetime.utcnow().strftime(settings.DRYCC_DATETIME_FORMAT))
891951
item['started'] = started
892-
952+
item['replicas'] = self.structure.get(labels['type'])
953+
if labels['type'] not in exist_pod_type:
954+
exist_pod_type.append(labels['type'])
893955
data.append(item)
894956

895957
# sorting so latest start date is first

rootfs/api/serializers.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -569,11 +569,12 @@ class Meta:
569569

570570

571571
class PodSerializer(serializers.BaseSerializer):
572-
name = serializers.CharField()
572+
name = serializers.CharField(required=False)
573573
state = serializers.CharField()
574574
type = serializers.CharField()
575-
release = serializers.CharField()
576-
started = serializers.DateTimeField()
575+
release = serializers.CharField(required=False)
576+
started = serializers.DateTimeField(required=False)
577+
replicas = serializers.IntegerField(required=False)
577578

578579
def to_representation(self, obj):
579580
return obj

rootfs/api/tests/test_pods.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,20 @@ def test_container_api_heroku(self, mock_requests):
100100
response = self.client.post(url, body)
101101
self.assertEqual(response.status_code, 204, response.data)
102102

103+
# stop
104+
url = "/v2/apps/{app_id}/stop".format(**locals())
105+
# test setting one proc type at a time
106+
body = {"types": ['web']}
107+
response = self.client.post(url, body)
108+
self.assertEqual(response.status_code, 204, response.data)
109+
110+
# start
111+
url = "/v2/apps/{app_id}/start".format(**locals())
112+
# test setting one proc type at a time
113+
body = {"types": ['web']}
114+
response = self.client.post(url, body)
115+
self.assertEqual(response.status_code, 204, response.data)
116+
103117
url = "/v2/apps/{app_id}/pods".format(**locals())
104118
response = self.client.get(url)
105119
self.assertEqual(response.status_code, 200, response.data)
@@ -121,7 +135,7 @@ def test_container_api_heroku(self, mock_requests):
121135
url = "/v2/apps/{app_id}/pods".format(**locals())
122136
response = self.client.get(url)
123137
self.assertEqual(response.status_code, 200, response.data)
124-
self.assertEqual(len(response.data['results']), 0)
138+
self.assertEqual(len(response.data['results']), 2)
125139

126140
url = "/v2/apps/{app_id}".format(**locals())
127141
response = self.client.get(url)
@@ -191,7 +205,7 @@ def test_container_api_docker(self, mock_requests):
191205
url = "/v2/apps/{app_id}/pods".format(**locals())
192206
response = self.client.get(url)
193207
self.assertEqual(response.status_code, 200, response.data)
194-
self.assertEqual(len(response.data['results']), 0)
208+
self.assertEqual(len(response.data['results']), 1)
195209

196210
url = "/v2/apps/{app_id}".format(**locals())
197211
response = self.client.get(url)
@@ -229,7 +243,7 @@ def test_release(self, mock_requests):
229243
url = "/v2/apps/{app_id}/pods".format(**locals())
230244
response = self.client.get(url)
231245
self.assertEqual(response.status_code, 200, response.data)
232-
self.assertEqual(len(response.data['results']), 1)
246+
self.assertEqual(len(response.data['results']), 2)
233247
self.assertEqual(response.data['results'][0]['release'], 'v2')
234248

235249
# post a new build
@@ -249,7 +263,7 @@ def test_release(self, mock_requests):
249263
url = "/v2/apps/{app_id}/pods".format(**locals())
250264
response = self.client.get(url)
251265
self.assertEqual(response.status_code, 200, response.data)
252-
self.assertEqual(len(response.data['results']), 1)
266+
self.assertEqual(len(response.data['results']), 2)
253267
self.assertEqual(response.data['results'][0]['release'], 'v3')
254268

255269
# post new config
@@ -261,7 +275,7 @@ def test_release(self, mock_requests):
261275
url = "/v2/apps/{app_id}/pods".format(**locals())
262276
response = self.client.get(url)
263277
self.assertEqual(response.status_code, 200, response.data)
264-
self.assertEqual(len(response.data['results']), 1)
278+
self.assertEqual(len(response.data['results']), 2)
265279
self.assertEqual(response.data['results'][0]['release'], 'v4')
266280

267281
def test_container_errors(self, mock_requests):
@@ -355,7 +369,7 @@ def test_pod_command_format(self, mock_requests):
355369

356370
# verify that the app._get_command property got formatted
357371
self.assertEqual(response.status_code, 200, response.data)
358-
self.assertEqual(len(response.data['results']), 1)
372+
self.assertEqual(len(response.data['results']), 2)
359373

360374
pod = response.data['results'][0]
361375
self.assertEqual(pod['type'], 'web')

rootfs/api/urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@
5757
# application actions
5858
url(r"^apps/(?P<id>{})/scale/?$".format(settings.APP_URL_REGEX),
5959
views.AppViewSet.as_view({'post': 'scale'})),
60+
url(r"^apps/(?P<id>{})/stop/?$".format(settings.APP_URL_REGEX),
61+
views.AppViewSet.as_view({'post': 'stop'})),
62+
url(r"^apps/(?P<id>{})/start/?$".format(settings.APP_URL_REGEX),
63+
views.AppViewSet.as_view({'post': 'start'})),
6064
url(r"^apps/(?P<id>{})/logs/?$".format(settings.APP_URL_REGEX),
6165
views.AppViewSet.as_view({'get': 'logs'})),
6266
url(r"^apps/(?P<id>{})/run/?$".format(settings.APP_URL_REGEX),

rootfs/api/views.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,20 @@ def scale(self, request, **kwargs):
226226
self.get_object().scale(request.user, request.data)
227227
return Response(status=status.HTTP_204_NO_CONTENT)
228228

229+
def stop(self, request, **kwargs):
230+
types = request.data.get("types")
231+
if not types:
232+
raise DryccException("types is a required field")
233+
self.get_object().stop(request.user, types)
234+
return Response(status=status.HTTP_204_NO_CONTENT)
235+
236+
def start(self, request, **kwargs):
237+
types = request.data.get("types")
238+
if not types:
239+
raise DryccException("types is a required field")
240+
self.get_object().start(request.user, types)
241+
return Response(status=status.HTTP_204_NO_CONTENT)
242+
229243
def logs(self, request, **kwargs):
230244
app = self.get_object()
231245
try:
@@ -314,7 +328,23 @@ class PodViewSet(AppResourceViewSet):
314328
def list(self, *args, **kwargs):
315329
pods = self.get_app().list_pods(*args, **kwargs)
316330
data = self.get_serializer(pods, many=True).data
317-
# fake out pagination for now
331+
332+
if not kwargs.get("type"):
333+
exist_pod_type = list(set([_["type"] for _ in data if _["type"]]))
334+
for _ in self.get_app().structure.keys():
335+
if _ not in exist_pod_type:
336+
exist_pod_type.append(_)
337+
data.append({"type": _,
338+
"replicas": self.get_app().structure[_],
339+
"state": "stopped"})
340+
341+
for _ in self.get_app().procfile_structure.keys():
342+
if _ not in exist_pod_type:
343+
data.append({"type": _,
344+
"replicas": 0,
345+
"state": "stopped"})
346+
347+
# # fake out pagination for now
318348
pagination = {'results': data, 'count': len(data)}
319349
return Response(pagination, status=status.HTTP_200_OK)
320350

0 commit comments

Comments
 (0)