From 7f5a0c4d56c92509d500b8e88855d158987397f4 Mon Sep 17 00:00:00 2001 From: lijianguo Date: Fri, 18 Oct 2024 09:52:41 +0800 Subject: [PATCH] fix(controller): scale cover structure --- rootfs/api/models/app.py | 36 +++++++++++++++++++++------------- rootfs/api/tests/test_build.py | 10 +++++----- rootfs/api/tests/test_pods.py | 5 ++--- 3 files changed, 29 insertions(+), 22 deletions(-) diff --git a/rootfs/api/models/app.py b/rootfs/api/models/app.py index 251ad77b..a7aa2443 100644 --- a/rootfs/api/models/app.py +++ b/rootfs/api/models/app.py @@ -16,10 +16,11 @@ from docker import auth as docker_auth from django.conf import settings from django.db import models +from django.db.models import F, Func, Value, JSONField from django.contrib.auth import get_user_model from rest_framework.exceptions import ValidationError, NotFound -from api.utils import get_session +from api.utils import get_session, dict_diff from api.exceptions import AlreadyExists, DryccException, ServiceUnavailable from api.utils import DeployLock, generate_app_name, apply_tasks from scheduler import KubeHTTPException, KubeException @@ -828,29 +829,36 @@ def _scale(self, user, structure, release, app_settings): # noqa structure[target] = int(count) validate_app_structure(structure) except (TypeError, ValueError, ValidationError) as e: - raise DryccException('Invalid scaling format: {}'.format(e)) + err_msg = 'Invalid scaling format: {}'.format(e) + self.log(err_msg) + raise DryccException(err_msg) # test for available process types for ptype in structure: if ptype not in (PTYPE_WEB, PTYPE_RUN) and \ ptype not in release.ptypes: - raise NotFound( - 'Container type {} does not exist in application'.format(ptype)) - - # 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: + err_msg = 'Container type {} does not exist in application'.format(ptype) + self.log(err_msg) + raise NotFound(err_msg) + + new_scale = dict_diff(structure, self.structure).get("changed", {}) + old_scale = dict_diff(self.structure, structure).get("changed", {}) + + if new_scale: try: - self._scale_pods(structure, release, app_settings) + self._scale_pods(new_scale, release, app_settings) except ServiceUnavailable: # scaling failed, go back to old scaling numbers - self._scale_pods(old_structure, release, app_settings) + self._scale_pods(old_scale, release, app_settings) raise # save new structure to the database - self.structure = new_structure - self.save() + App.objects.filter(id=self.id).update( + structure=Func( + F("structure"), + Value(new_scale, JSONField()), + function="jsonb_concat", + ) + ) msg = '{} scaled pods '.format(user.username) + ' '.join( "{}={}".format(k, v) for k, v in list(structure.items())) self.log(msg) diff --git a/rootfs/api/tests/test_build.py b/rootfs/api/tests/test_build.py index 57be0485..3bad527e 100644 --- a/rootfs/api/tests/test_build.py +++ b/rootfs/api/tests/test_build.py @@ -259,23 +259,23 @@ def test_build_forgotten_procfile(self, mock_requests): response = self.client.post(url, body) self.assertEqual(response.status_code, 201, response.data) - # verify worker is not there + # verify worker is not there, only web url = f"/v2/apps/{app_id}/pods/" 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) - # verify web is not there + # verify web is in there url = f"/v2/apps/{app_id}/pods/" 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) # look at the app structure url = f"/v2/apps/{app_id}" response = self.client.get(url) self.assertEqual(response.status_code, 200, response.data) - self.assertEqual(response.json()['structure'], {'web': 0}) + self.assertEqual(response.json()['structure'], {'web': 1}) @override_settings(DRYCC_DEPLOY_PROCFILE_MISSING_REMOVE=False) def test_build_no_remove_process(self, mock_requests): diff --git a/rootfs/api/tests/test_pods.py b/rootfs/api/tests/test_pods.py index f9ebf32e..2ccf94bb 100644 --- a/rootfs/api/tests/test_pods.py +++ b/rootfs/api/tests/test_pods.py @@ -752,11 +752,10 @@ def test_modified_procfile_from_build_removes_pods(self, mock_requests): } response = self.client.post(build_url, body) self.assertEqual(response.status_code, 201, response.data) - - # make sure no pods are web + # web is default ptype application = App.objects.get(id=app_id) pods = application.list_pods(type='web') - self.assertEqual(len(pods), 0) + self.assertEqual(len(pods), 4) def test_list_pods_failure(self, mock_requests): """