Skip to content

Commit

Permalink
Merge pull request teamhephy#22 from jianxiaoguo/master
Browse files Browse the repository at this point in the history
feat(ps):add ps:stop/start command
  • Loading branch information
duanhongyi authored Oct 15, 2020
2 parents b42c261 + 7ce8a09 commit cd5fe01
Show file tree
Hide file tree
Showing 5 changed files with 122 additions and 11 deletions.
64 changes: 63 additions & 1 deletion rootfs/api/models/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,65 @@ def scale(self, user, structure): # noqa

return False

def stop(self, user, types): # noqa
"""scale containers which types contained down """

if self.release_set.filter(failed=False).latest().build is None:
raise DryccException('No build associated with this release')

release = self.release_set.filter(failed=False).latest()
structure = {_: 0 for _ in types}

# test for available process types
available_process_types = release.build.procfile or {}
for container_type in types:
if container_type == 'cmd':
continue # allow docker cmd types in case we don't have the image source

if container_type not in available_process_types:
raise NotFound(
'Container type {} does not exist in application'.format(container_type))

# merge current structure and the new items together
old_structure = self.structure
new_structure = old_structure.copy()
new_structure.update(structure)

if new_structure != self.structure:
try:
self._scale_pods(structure)
except ServiceUnavailable:
# scaling failed, go back to old scaling numbers
self._scale_pods(old_structure)
raise

msg = '{} stopped pods '.format(user.username) + ' '.join(types)
self.log(msg)

return True

return False

def start(self, user, types): # noqa
"""scale containers which types contained up."""
# use create to make sure minimum resources are created
self.create()
if self.release_set.filter(failed=False).latest().build is None:
raise DryccException('No build associated with this release')

structure = {}
for k, v in self.structure.items():
if k in types:
structure[k] = v
try:
self._scale_pods(structure)
except ServiceUnavailable:
# scaling failed, go back to old scaling numbers
raise
msg = '{} stopped pods '.format(user.username) + ' '.join(types)
self.log(msg)
return True

def _scale_pods(self, scale_types):
release = self.release_set.filter(failed=False).latest()
app_settings = self.appsettings_set.latest()
Expand Down Expand Up @@ -863,6 +922,7 @@ def list_pods(self, *args, **kwargs):
pods = []

data = []
exist_pod_type = []
for p in pods:
labels = p['metadata']['labels']
# specifically ignore run pods
Expand All @@ -889,7 +949,9 @@ def list_pods(self, *args, **kwargs):
else:
started = str(datetime.utcnow().strftime(settings.DRYCC_DATETIME_FORMAT))
item['started'] = started

item['replicas'] = self.structure.get(labels['type'])
if labels['type'] not in exist_pod_type:
exist_pod_type.append(labels['type'])
data.append(item)

# sorting so latest start date is first
Expand Down
7 changes: 4 additions & 3 deletions rootfs/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,11 +569,12 @@ class Meta:


class PodSerializer(serializers.BaseSerializer):
name = serializers.CharField()
name = serializers.CharField(required=False)
state = serializers.CharField()
type = serializers.CharField()
release = serializers.CharField()
started = serializers.DateTimeField()
release = serializers.CharField(required=False)
started = serializers.DateTimeField(required=False)
replicas = serializers.IntegerField(required=False)

def to_representation(self, obj):
return obj
Expand Down
26 changes: 20 additions & 6 deletions rootfs/api/tests/test_pods.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,20 @@ def test_container_api_heroku(self, mock_requests):
response = self.client.post(url, body)
self.assertEqual(response.status_code, 204, response.data)

# stop
url = "/v2/apps/{app_id}/stop".format(**locals())
# test setting one proc type at a time
body = {"types": ['web']}
response = self.client.post(url, body)
self.assertEqual(response.status_code, 204, response.data)

# start
url = "/v2/apps/{app_id}/start".format(**locals())
# test setting one proc type at a time
body = {"types": ['web']}
response = self.client.post(url, body)
self.assertEqual(response.status_code, 204, response.data)

url = "/v2/apps/{app_id}/pods".format(**locals())
response = self.client.get(url)
self.assertEqual(response.status_code, 200, response.data)
Expand All @@ -121,7 +135,7 @@ def test_container_api_heroku(self, mock_requests):
url = "/v2/apps/{app_id}/pods".format(**locals())
response = self.client.get(url)
self.assertEqual(response.status_code, 200, response.data)
self.assertEqual(len(response.data['results']), 0)
self.assertEqual(len(response.data['results']), 2)

url = "/v2/apps/{app_id}".format(**locals())
response = self.client.get(url)
Expand Down Expand Up @@ -191,7 +205,7 @@ def test_container_api_docker(self, mock_requests):
url = "/v2/apps/{app_id}/pods".format(**locals())
response = self.client.get(url)
self.assertEqual(response.status_code, 200, response.data)
self.assertEqual(len(response.data['results']), 0)
self.assertEqual(len(response.data['results']), 1)

url = "/v2/apps/{app_id}".format(**locals())
response = self.client.get(url)
Expand Down Expand Up @@ -229,7 +243,7 @@ def test_release(self, mock_requests):
url = "/v2/apps/{app_id}/pods".format(**locals())
response = self.client.get(url)
self.assertEqual(response.status_code, 200, response.data)
self.assertEqual(len(response.data['results']), 1)
self.assertEqual(len(response.data['results']), 2)
self.assertEqual(response.data['results'][0]['release'], 'v2')

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

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

def test_container_errors(self, mock_requests):
Expand Down Expand Up @@ -355,7 +369,7 @@ def test_pod_command_format(self, mock_requests):

# verify that the app._get_command property got formatted
self.assertEqual(response.status_code, 200, response.data)
self.assertEqual(len(response.data['results']), 1)
self.assertEqual(len(response.data['results']), 2)

pod = response.data['results'][0]
self.assertEqual(pod['type'], 'web')
Expand Down
4 changes: 4 additions & 0 deletions rootfs/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
# application actions
url(r"^apps/(?P<id>{})/scale/?$".format(settings.APP_URL_REGEX),
views.AppViewSet.as_view({'post': 'scale'})),
url(r"^apps/(?P<id>{})/stop/?$".format(settings.APP_URL_REGEX),
views.AppViewSet.as_view({'post': 'stop'})),
url(r"^apps/(?P<id>{})/start/?$".format(settings.APP_URL_REGEX),
views.AppViewSet.as_view({'post': 'start'})),
url(r"^apps/(?P<id>{})/logs/?$".format(settings.APP_URL_REGEX),
views.AppViewSet.as_view({'get': 'logs'})),
url(r"^apps/(?P<id>{})/run/?$".format(settings.APP_URL_REGEX),
Expand Down
32 changes: 31 additions & 1 deletion rootfs/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,20 @@ def scale(self, request, **kwargs):
self.get_object().scale(request.user, request.data)
return Response(status=status.HTTP_204_NO_CONTENT)

def stop(self, request, **kwargs):
types = request.data.get("types")
if not types:
raise DryccException("types is a required field")
self.get_object().stop(request.user, types)
return Response(status=status.HTTP_204_NO_CONTENT)

def start(self, request, **kwargs):
types = request.data.get("types")
if not types:
raise DryccException("types is a required field")
self.get_object().start(request.user, types)
return Response(status=status.HTTP_204_NO_CONTENT)

def logs(self, request, **kwargs):
app = self.get_object()
try:
Expand Down Expand Up @@ -314,7 +328,23 @@ class PodViewSet(AppResourceViewSet):
def list(self, *args, **kwargs):
pods = self.get_app().list_pods(*args, **kwargs)
data = self.get_serializer(pods, many=True).data
# fake out pagination for now

if not kwargs.get("type"):
exist_pod_type = list(set([_["type"] for _ in data if _["type"]]))
for _ in self.get_app().structure.keys():
if _ not in exist_pod_type:
exist_pod_type.append(_)
data.append({"type": _,
"replicas": self.get_app().structure[_],
"state": "stopped"})

for _ in self.get_app().procfile_structure.keys():
if _ not in exist_pod_type:
data.append({"type": _,
"replicas": 0,
"state": "stopped"})

# # fake out pagination for now
pagination = {'results': data, 'count': len(data)}
return Response(pagination, status=status.HTTP_200_OK)

Expand Down

0 comments on commit cd5fe01

Please sign in to comment.