Skip to content

Commit bac23fd

Browse files
committed
feat(config): deploy according to procfile_type
1 parent 50b6cca commit bac23fd

9 files changed

Lines changed: 83 additions & 244 deletions

File tree

rootfs/api/models/app.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ def pipeline(self, release, force_deploy=False, rollback_on_failure=True):
257257
if state != 'down':
258258
raise DryccException(f'pipeline run state error: {state}')
259259
self.log(f"{prefix} starts running pipeline.deploy")
260-
self.deploy(release, force_deploy, rollback_on_failure)
260+
self.deploy(release, None, force_deploy, rollback_on_failure)
261261
release.state = "succeed"
262262
except Exception as e:
263263
release.state = "crashed"
@@ -268,7 +268,7 @@ def pipeline(self, release, force_deploy=False, rollback_on_failure=True):
268268
release.save()
269269
self.log(f"{prefix} run completed...")
270270

271-
def deploy(self, release, force_deploy=False, rollback_on_failure=True):
271+
def deploy(self, release, structure=None, force_deploy=False, rollback_on_failure=True):
272272
"""
273273
Deploy a new release to this application
274274
@@ -296,11 +296,13 @@ def deploy(self, release, force_deploy=False, rollback_on_failure=True):
296296
volumes = self.volume_set.all()
297297
deploys = {}
298298
for scale_type, replicas in self.structure.items():
299+
if structure is not None and scale_type not in structure:
300+
continue
299301
scale_type_volumes = [_ for _ in volumes if scale_type in _.path.keys()]
300302
if not release.canary or scale_type in app_settings.canaries:
301303
deploys[scale_type] = self._gather_app_settings(
302304
release, app_settings, scale_type, replicas, volumes=scale_type_volumes)
303-
self._deploy(deploys, prev_release, release, force_deploy, rollback_on_failure)
305+
self._deploy(deploys, structure, prev_release, release, force_deploy, rollback_on_failure)
304306
# cleanup old release objects from kubernetes
305307
self.cleanup_old()
306308
release.cleanup_old()
@@ -610,7 +612,8 @@ def _mount(self, user, volume, release, app_settings, structure=None):
610612
raise ServiceUnavailable(err) from e
611613
self.log(f'{user.username} changed volume mount for {volume}')
612614

613-
def _deploy(self, deploys, prev_release, release, force_deploy, rollback_on_failure):
615+
def _deploy(self, deploys, structure, prev_release,
616+
release, force_deploy, rollback_on_failure):
614617
# Sort deploys so routable comes first
615618
deploys = OrderedDict(sorted(deploys.items(), key=lambda d: d[1].get('routable')))
616619
# Check if any proc type has a Deployment in progress
@@ -644,7 +647,8 @@ def _deploy(self, deploys, prev_release, release, force_deploy, rollback_on_fail
644647
# This goes in the log before the rollback starts
645648
self.log(err, logging.ERROR)
646649
# revert all process types to old release
647-
self.deploy(prev_release, force_deploy=True, rollback_on_failure=False)
650+
self.deploy(prev_release, structure,
651+
force_deploy=True, rollback_on_failure=False)
648652
# let it bubble up
649653
raise DryccException('{}\n{}'.format(err, str(e))) from e
650654

@@ -898,19 +902,14 @@ def _default_structure(release):
898902

899903
def _scheduler_filter(self, **kwargs):
900904
labels = {'app': self.id, 'heritage': 'drycc'}
901-
902-
# always supply a version, either latest or a specific one
903-
if 'release' not in kwargs or kwargs['release'] is None:
904-
release = self.release_set.filter(failed=False).latest()
905-
else:
906-
release = self.release_set.get(version=kwargs['release'])
907-
908-
version = "v{}".format(release.version)
909-
labels.update({'version': version})
910-
911905
if 'type' in kwargs:
912906
labels.update({'type': kwargs['type']})
913-
907+
if 'version' in kwargs:
908+
if isinstance(kwargs['version'], int):
909+
version = "v{}".format(kwargs['version'])
910+
else:
911+
version = kwargs['version']
912+
labels.update({'version': version})
914913
return labels
915914

916915
def _build_env_vars(self, release):

rootfs/api/models/config.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.conf import settings
44
from django.db import models
55
from django.contrib.auth import get_user_model
6+
from api.utils import dict_diff
67
from api.exceptions import DryccException, UnprocessableEntity
78
from .release import Release
89
from .base import UuidAuditedModel
@@ -16,6 +17,9 @@ class Config(UuidAuditedModel):
1617
Set of configuration values applied as environment variables
1718
during runtime execution of the Application.
1819
"""
20+
procfile_fields = ("lifecycle_post_start", "lifecycle_pre_stop", "tags", "limits",
21+
"healthcheck", "termination_grace_period")
22+
all_diff_fields = ("values", "registry") + procfile_fields
1923

2024
owner = models.ForeignKey(User, on_delete=models.PROTECT)
2125
app = models.ForeignKey('App', on_delete=models.CASCADE)
@@ -53,6 +57,13 @@ def previous(self):
5357
prev_release = None
5458
return prev_release
5559

60+
def diff(self, config=None):
61+
old_config = config if config else self.previous()
62+
result = {}
63+
for field in self.all_diff_fields:
64+
result[field] = dict_diff(getattr(self, field), getattr(old_config, field))
65+
return result
66+
5667
def save(self, **kwargs):
5768
"""merge the old config with the new"""
5869
try:

rootfs/api/models/release.py

Lines changed: 7 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from django.conf import settings
44
from django.db import models
55
from django.contrib.auth import get_user_model
6-
from api.utils import dict_diff
76
from api.tasks import run_pipeline
87
from api.exceptions import DryccException, AlreadyExists
98
from scheduler import KubeHTTPException
@@ -385,127 +384,13 @@ def save(self, *args, **kwargs): # noqa
385384
else:
386385
self.summary += "{} deployed {}".format(self.build.owner, self.build.image)
387386
elif self.config != old_config:
388-
# if env vars change, log the dict diff
389-
dict1 = self.config.values
390-
dict2 = old_config.values if old_config else {}
391-
diff = dict_diff(dict1, dict2)
392-
# try to be as succinct as possible
393-
added = ', '.join(k for k in diff.get('added', {}))
394-
added = 'added ' + added if added else ''
395-
changed = ', '.join(k for k in diff.get('changed', {}))
396-
changed = 'changed ' + changed if changed else ''
397-
deleted = ', '.join(k for k in diff.get('deleted', {}))
398-
deleted = 'deleted ' + deleted if deleted else ''
399-
changes = ', '.join(i for i in (added, changed, deleted) if i)
400-
if changes:
401-
if self.summary:
402-
self.summary += ' and '
403-
self.summary += "{} {}".format(self.config.owner, changes)
404-
405-
# if the limits changed, log the dict diff
406-
changes = []
407-
old_limits = old_config.limits if old_config else {}
408-
diff = dict_diff(self.config.limits, old_limits)
409-
if diff.get('added') or diff.get('changed') or diff.get('deleted'):
410-
changes.append('limits')
411-
if changes:
412-
changes = 'changed limits for '+', '.join(changes)
413-
self.summary += "{} {}".format(self.config.owner, changes)
414-
415-
# if the lifecycle_post_start hooks changed, log the dict diff
416-
changes = []
417-
old_lifecycle_post_start = old_config.lifecycle_post_start if old_config else {}
418-
diff = dict_diff(self.config.lifecycle_post_start, old_lifecycle_post_start)
419-
# try to be as succinct as possible
420-
added = ', '.join(k for k in diff.get('added', {}))
421-
added = 'added lifecycle_post_start ' + added if added else ''
422-
changed = ', '.join(k for k in diff.get('changed', {}))
423-
changed = 'changed lifecycle_post_start ' + changed if changed else ''
424-
deleted = ', '.join(k for k in diff.get('deleted', {}))
425-
deleted = 'deleted lifecycle_post_start ' + deleted if deleted else ''
426-
changes = ', '.join(i for i in (added, changed, deleted) if i)
427-
if changes:
428-
if self.summary:
429-
self.summary += ' and '
430-
self.summary += "{} {}".format(self.config.owner, changes)
431-
432-
# if the lifecycle_pre_stop hooks changed, log the dict diff
433-
changes = []
434-
old_lifecycle_pre_stop = old_config.lifecycle_pre_stop if old_config else {}
435-
diff = dict_diff(self.config.lifecycle_pre_stop, old_lifecycle_pre_stop)
436-
# try to be as succinct as possible
437-
added = ', '.join(k for k in diff.get('added', {}))
438-
added = 'added lifecycle_pre_stop ' + added if added else ''
439-
changed = ', '.join(k for k in diff.get('changed', {}))
440-
changed = 'changed lifecycle_pre_stop ' + changed if changed else ''
441-
deleted = ', '.join(k for k in diff.get('deleted', {}))
442-
deleted = 'deleted lifecycle_pre_stop ' + deleted if deleted else ''
443-
changes = ', '.join(i for i in (added, changed, deleted) if i)
444-
if changes:
445-
if self.summary:
446-
self.summary += ' and '
447-
448-
# if the timeouts changed, log the dict diff
449-
changes = []
450-
old_timeout = old_config.termination_grace_period if old_config else {}
451-
diff = dict_diff(self.config.termination_grace_period, old_timeout)
452-
if diff.get('added') or diff.get('changed') or diff.get('deleted'):
453-
changes.append('termination_grace_period')
454-
if changes:
455-
changes = 'changed timeouts for '+', '.join(changes)
456-
self.summary += "{} {}".format(self.config.owner, changes)
457-
458-
# if the tags changed, log the dict diff
459-
changes = []
460-
old_tags = old_config.tags if old_config else {}
461-
diff = dict_diff(self.config.tags, old_tags)
462-
# try to be as succinct as possible
463-
added = ', '.join(k for k in diff.get('added', {}))
464-
added = 'added tag ' + added if added else ''
465-
changed = ', '.join(k for k in diff.get('changed', {}))
466-
changed = 'changed tag ' + changed if changed else ''
467-
deleted = ', '.join(k for k in diff.get('deleted', {}))
468-
deleted = 'deleted tag ' + deleted if deleted else ''
469-
changes = ', '.join(i for i in (added, changed, deleted) if i)
470-
if changes:
471-
if self.summary:
472-
self.summary += ' and '
473-
self.summary += "{} {}".format(self.config.owner, changes)
474-
475-
# if the registry information changed, log the dict diff
476-
changes = []
477-
old_registry = old_config.registry if old_config else {}
478-
diff = dict_diff(self.config.registry, old_registry)
479-
# try to be as succinct as possible
480-
added = ', '.join(k for k in diff.get('added', {}))
481-
added = 'added registry info ' + added if added else ''
482-
changed = ', '.join(k for k in diff.get('changed', {}))
483-
changed = 'changed registry info ' + changed if changed else ''
484-
deleted = ', '.join(k for k in diff.get('deleted', {}))
485-
deleted = 'deleted registry info ' + deleted if deleted else ''
486-
changes = ', '.join(i for i in (added, changed, deleted) if i)
487-
if changes:
488-
if self.summary:
489-
self.summary += ' and '
490-
self.summary += "{} {}".format(self.config.owner, changes)
491-
492-
# if the healthcheck information changed, log the dict diff
493-
changes = []
494-
old_healthcheck = old_config.healthcheck if old_config else {}
495-
diff = dict_diff(self.config.healthcheck, old_healthcheck)
496-
# try to be as succinct as possible
497-
added = ', '.join(list(map(lambda x: 'default' if x == '' else x, [k for k in diff.get('added', {})]))) # noqa
498-
added = 'added healthcheck info for proc type ' + added if added else ''
499-
changed = ', '.join(list(map(lambda x: 'default' if x == '' else x, [k for k in diff.get('changed', {})]))) # noqa
500-
changed = 'changed healthcheck info for proc type ' + changed if changed else ''
501-
deleted = ', '.join(list(map(lambda x: 'default' if x == '' else x, [k for k in diff.get('deleted', {})]))) # noqa
502-
deleted = 'deleted healthcheck info for proc type ' + deleted if deleted else ''
503-
changes = ', '.join(i for i in (added, changed, deleted) if i)
504-
if changes:
505-
if self.summary:
506-
self.summary += ' and '
507-
self.summary += "{} {}".format(self.config.owner, changes)
508-
387+
for field, diff in self.config.diff(old_config).items():
388+
diff_list = []
389+
for diff_type, values in diff.items():
390+
diff_list.append(f'{diff_type} {field} {", ".join(values.keys())}')
391+
if diff_list:
392+
changes = ', '.join(diff_list)
393+
self.summary += "{} {}".format(self.config.owner, changes)
509394
if not self.summary:
510395
if self.version == 1:
511396
self.summary = "{} created the initial release".format(self.owner)

rootfs/api/tests/__init__.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import random
33
import requests_mock
44
import time
5+
import unittest
56
from os.path import dirname, realpath
67

78
from django.test.runner import DiscoverRunner
@@ -51,7 +52,7 @@ def run_tests(self, test_labels, extra_tests=None, **kwargs):
5152
test_labels, extra_tests, **kwargs)
5253

5354

54-
class DryccTestCase(APITestCase):
55+
class DryccBaseTestCase(unittest.TestCase):
5556

5657
def create_app(self, name=None):
5758
body = {}
@@ -63,14 +64,23 @@ def create_app(self, name=None):
6364
self.assertIn('id', response.data)
6465
return response.data['id']
6566

67+
def assertPodContains(self, pods, app_id, procfile_type, version, state="up"):
68+
for pod in pods:
69+
if (pod["type"] == procfile_type and
70+
pod["release"] == version and pod["state"] == state):
71+
pod_name = app_id + '-%s-[0-9]{1,10}-[a-z0-9]{5}' % procfile_type
72+
self.assertRegex(pod['name'], pod_name)
73+
return
74+
raise ValueError(
75+
"pod not contains: procfile_type={}, version={}, state={}".format(
76+
procfile_type, version, state
77+
)
78+
)
6679

67-
class DryccTransactionTestCase(APITransactionTestCase):
68-
def create_app(self, name=None):
69-
body = {}
70-
if name:
71-
body = {'id': name}
7280

73-
response = self.client.post('/v2/apps', body)
74-
self.assertEqual(response.status_code, 201, response.data)
75-
self.assertIn('id', response.data)
76-
return response.data['id']
81+
class DryccTransactionTestCase(DryccBaseTestCase, APITransactionTestCase):
82+
pass
83+
84+
85+
class DryccTestCase(DryccBaseTestCase, APITestCase):
86+
pass

0 commit comments

Comments
 (0)