Skip to content

Commit fe79b99

Browse files
committed
feat(canary): remove canary api
1 parent a67bda1 commit fe79b99

25 files changed

Lines changed: 2557 additions & 757 deletions
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Generated by Django 4.2.11 on 2024-07-01 07:58
2+
3+
from django.db import migrations
4+
5+
6+
def migration_route_rules(apps, schema_editor):
7+
route_model = apps.get_model('api', 'Route')
8+
for route in route_model.objects.all():
9+
if isinstance(route.rules, dict):
10+
route.rules = route.rules.get("stable")
11+
route.save()
12+
13+
14+
class Migration(migrations.Migration):
15+
16+
dependencies = [
17+
('api', '0008_alter_domain_procfile_type_alter_route_procfile_type_and_more'),
18+
]
19+
20+
operations = [
21+
migrations.RemoveField(
22+
model_name='appsettings',
23+
name='canaries',
24+
),
25+
migrations.RemoveField(
26+
model_name='release',
27+
name='canary',
28+
),
29+
migrations.RemoveField(
30+
model_name='service',
31+
name='canary',
32+
),
33+
migrations.RunPython(migration_route_rules),
34+
]

rootfs/api/models/app.py

Lines changed: 27 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def validate_app_id(value):
4242
"""
4343
Check that the value follows the kubernetes name constraints
4444
"""
45-
match = re.match(r'[a-z]([a-z0-9-]*[a-z0-9])?(?<!-canary)$', value)
45+
match = re.match(r'[a-z]([a-z0-9-]*[a-z0-9])?$', value)
4646
if not match:
4747
raise ValidationError("App name must start with an alphabetic character, cannot end with a"
4848
+ " hyphen and can only contain a-z (lowercase), 0-9 and hyphens.")
@@ -190,11 +190,8 @@ def restart(self, **kwargs): # noqa
190190
Restart deployments with the kubectl rollout api
191191
"""
192192
deployments = []
193-
app_settings = self.appsettings_set.latest()
194193
if self.structure[kwargs['type']] > 0:
195-
if kwargs['type'] in app_settings.canaries:
196-
deployments.append(self._get_job_id(kwargs['type'], True))
197-
deployments.append(self._get_job_id(kwargs['type'], False))
194+
deployments.append(self._get_job_id(kwargs['type']))
198195
try:
199196
tasks = [
200197
functools.partial(
@@ -219,13 +216,6 @@ def scale(self, user, structure):
219216
self.log(err_msg, logging.WARNING)
220217
raise DryccException(err_msg)
221218
app_settings = self.appsettings_set.latest()
222-
if release.canary:
223-
self._scale(
224-
user,
225-
structure,
226-
self.release_set.filter(failed=False, canary=False).latest(),
227-
app_settings
228-
)
229219
self._scale(user, structure, release, app_settings)
230220

231221
def pipeline(self, release, force_deploy=False, rollback_on_failure=True):
@@ -293,9 +283,8 @@ def deploy(self, release, procfile_types=None, force_deploy=False, rollback_on_f
293283
if procfile_types is not None and scale_type not in procfile_types:
294284
continue
295285
scale_type_volumes = [_ for _ in volumes if scale_type in _.path.keys()]
296-
if not release.canary or scale_type in app_settings.canaries:
297-
deploys[scale_type] = self._gather_app_settings(
298-
release, app_settings, scale_type, replicas, volumes=scale_type_volumes)
286+
deploys[scale_type] = self._gather_app_settings(
287+
release, app_settings, scale_type, replicas, volumes=scale_type_volumes)
299288
self._deploy(
300289
deploys, procfile_types, prev_release, release, force_deploy, rollback_on_failure)
301290
# cleanup old release objects from kubernetes
@@ -307,22 +296,12 @@ def mount(self, user, volume, structure=None):
307296
raise DryccException('No build associated with this release')
308297
release = self.release_set.filter(failed=False).latest()
309298
app_settings = self.appsettings_set.latest()
310-
if release.canary:
311-
self._mount(
312-
user,
313-
volume,
314-
self.release_set.filter(failed=False, canary=False).latest(),
315-
app_settings,
316-
structure=structure,
317-
)
318299
self._mount(user, volume, release, app_settings, structure=structure)
319300

320301
def cleanup_old(self, procfile_types=None):
321-
names, app_settings = [], self.appsettings_set.latest()
302+
names = []
322303
for scale_type in self.structure.keys():
323-
if scale_type in app_settings.canaries:
324-
names.append(self._get_job_id(scale_type, True))
325-
names.append(self._get_job_id(scale_type, False))
304+
names.append(self._get_job_id(scale_type))
326305
labels = {'heritage': 'drycc'}
327306
if procfile_types is not None:
328307
labels["type__in"] = procfile_types
@@ -360,7 +339,7 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits):
360339
data['restart_policy'] = 'Never'
361340
data['active_deadline_seconds'] = timeout
362341
data['ttl_seconds_after_finished'] = expires
363-
name = self._get_job_id(PROCFILE_TYPE_RUN, release.canary) + '-' + pod_name()
342+
name = self._get_job_id(PROCFILE_TYPE_RUN) + '-' + pod_name()
364343
self.log("{} on {} runs '{}'".format(user.username, name, command))
365344
kwargs.update(data)
366345
try:
@@ -509,27 +488,21 @@ def image_pull_secret(self, namespace, registry, image):
509488
return name
510489

511490
def state_to_k8s(self):
512-
def _load_procfile_types(canary):
513-
procfile_types = set()
514-
for procfile_type, scale in self.structure.items():
515-
response = self.scheduler().deployment.get(
516-
self.id, self._get_job_id(procfile_type, canary),
517-
ignore_exception=True)
518-
if response.status_code == 404 and scale > 0:
519-
procfile_types.add(procfile_type)
520-
elif response.status_code != 200:
521-
data = response.json()
522-
self.log('get deployment status_code {}, message: {}'.format(
523-
response.status_code, data.get("message", "")), logging.ERROR)
524-
return procfile_types
525-
526491
release = self.release_set.filter(failed=False).latest()
527492
if release.build is None:
528493
self.log('the last release does not have a build, skipping deployment...')
529494
return
530-
procfile_types = _load_procfile_types(False)
531-
if release.canary:
532-
procfile_types = procfile_types.union(_load_procfile_types(canary=True))
495+
procfile_types = set()
496+
for procfile_type, scale in self.structure.items():
497+
response = self.scheduler().deployment.get(
498+
self.id, self._get_job_id(procfile_type),
499+
ignore_exception=True)
500+
if response.status_code == 404 and scale > 0:
501+
procfile_types.add(procfile_type)
502+
elif response.status_code != 200:
503+
data = response.json()
504+
self.log('get deployment status_code {}, message: {}'.format(
505+
response.status_code, data.get("message", "")), logging.ERROR)
533506
if len(procfile_types) == 0:
534507
self.log('the cluster status is the latest, skipping deployment...')
535508
return
@@ -585,11 +558,8 @@ def to_measurements(self, timestamp: float):
585558
def __str__(self):
586559
return self.id
587560

588-
def _get_job_id(self, container_type, canary):
589-
job_id = f"{self.id}-{container_type}"
590-
if canary:
591-
job_id = f"{job_id}-canary"
592-
return job_id
561+
def _get_job_id(self, container_type):
562+
return f"{self.id}-{container_type}"
593563

594564
def _clean_app_logs(self):
595565
"""Delete application logs stored by the logger component"""
@@ -607,23 +577,22 @@ def _mount(self, user, volume, release, app_settings, structure=None):
607577
volumes = Volume.objects.filter(app=self)
608578
tasks = []
609579
for scale_type, replicas in structure.items() if structure else self.structure.items():
610-
if scale_type != PROCFILE_TYPE_RUN and (
611-
not release.canary or scale_type in app_settings.canaries):
580+
if scale_type != PROCFILE_TYPE_RUN:
612581
replicas = self.structure.get(scale_type, 0)
613582
scale_type_volumes = [
614583
volume for volume in volumes if scale_type in volume.path.keys()]
615584
data = self._gather_app_settings(
616585
release, app_settings, scale_type, replicas, volumes=scale_type_volumes)
617586
deployment = self.scheduler().deployment.get(
618-
self.id, self._get_job_id(scale_type, release.canary)).json()
587+
self.id, self._get_job_id(scale_type)).json()
619588
spec_annotations = deployment['spec']['template']['metadata'].get(
620589
'annotations', {})
621590
self.set_application_config(release, scale_type)
622591
# gather volume proc types to be deployed
623592
tasks.append(functools.partial(
624593
self.scheduler().deployment.patch,
625594
namespace=self.id,
626-
name=self._get_job_id(scale_type, release.canary),
595+
name=self._get_job_id(scale_type),
627596
image=release.get_deploy_image(scale_type),
628597
command=release.get_deploy_command(scale_type),
629598
args=release.get_deploy_args(scale_type),
@@ -653,7 +622,7 @@ def _deploy(self, deploys, procfile_types, prev_release,
653622
tasks.append(functools.partial(
654623
self.scheduler().deploy,
655624
namespace=self.id,
656-
name=self._get_job_id(scale_type, release.canary),
625+
name=self._get_job_id(scale_type),
657626
image=release.get_deploy_image(scale_type),
658627
command=release.get_deploy_command(scale_type),
659628
args=release.get_deploy_args(scale_type),
@@ -664,8 +633,7 @@ def _deploy(self, deploys, procfile_types, prev_release,
664633
except KubeException as e:
665634
# Don't rollback if the previous release doesn't have a build which means
666635
# this is the first build and all the previous releases are just config changes.
667-
if (prev_release.canary == release.canary and
668-
rollback_on_failure and prev_release.build is not None):
636+
if (rollback_on_failure and prev_release.build is not None):
669637
err = 'There was a problem deploying {}. Rolling back to release {}.'.format(
670638
release.version_name, prev_release.version_name)
671639
# This goes in the log before the rollback starts
@@ -701,7 +669,6 @@ def _scale(self, user, structure, release, app_settings): # noqa
701669
"""Scale containers up or down to match requested structure."""
702670
# use create to make sure minimum resources are created
703671
self.create()
704-
705672
# Validate structure
706673
try:
707674
for target, count in structure.copy().items():
@@ -752,7 +719,7 @@ def _scale_pods(self, scale_types, release, app_settings):
752719
functools.partial(
753720
self.scheduler().scale,
754721
namespace=self.id,
755-
name=self._get_job_id(scale_type, release.canary),
722+
name=self._get_job_id(scale_type),
756723
image=release.get_deploy_image(scale_type),
757724
command=release.get_deploy_command(scale_type),
758725
args=release.get_deploy_args(scale_type),
@@ -905,7 +872,7 @@ def _check_deployment_in_progress(self, deploys, release, force_deploy=False):
905872
if force_deploy:
906873
return
907874
for scale_type, kwargs in deploys.items():
908-
name = self._get_job_id(scale_type, release.canary)
875+
name = self._get_job_id(scale_type)
909876
# Is there an existing deployment in progress?
910877
in_progress, deploy_okay = self.scheduler().deployment.in_progress(
911878
self.id, name, kwargs.get("deploy_timeout"), kwargs.get("deploy_batches"),

rootfs/api/models/appsettings.py

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ class AppSettings(UuidAuditedModel):
1919

2020
owner = models.ForeignKey(User, on_delete=models.PROTECT)
2121
app = models.ForeignKey('App', on_delete=models.CASCADE)
22-
canaries = models.JSONField(default=list)
2322
routable = models.BooleanField(default=True)
2423
autoscale = models.JSONField(default=dict, blank=True)
2524
label = models.JSONField(default=dict, blank=True)
@@ -63,20 +62,6 @@ def previous(self):
6362
prev_app_settings = None
6463
return prev_app_settings
6564

66-
def _update_canaries(self, previous_settings):
67-
old = getattr(previous_settings, 'canaries', [])
68-
new = getattr(self, 'canaries', [])
69-
data = old.copy()
70-
if data and not new:
71-
setattr(self, 'canaries', data)
72-
elif data != new:
73-
for procfile_type in new:
74-
if procfile_type not in data:
75-
data.append(procfile_type)
76-
setattr(self, 'canaries', data)
77-
self.summary += [
78-
"{} add canaries for process types {}".format(self.owner, ','.join(new))]
79-
8065
def _update_routable(self, previous_settings):
8166
old = getattr(previous_settings, 'routable', None)
8267
new = getattr(self, 'routable', None)
@@ -164,7 +149,7 @@ def _update_fields(self, ignore_update_fields=None):
164149
previous_settings = self.app.appsettings_set.latest()
165150
except AppSettings.DoesNotExist:
166151
pass
167-
update_fields = ["canaries", "routable", "autoscale", "label"]
152+
update_fields = ["routable", "autoscale", "label"]
168153
try:
169154
for update_field in update_fields:
170155
if ignore_update_fields is None or update_field not in ignore_update_fields:
@@ -181,23 +166,6 @@ def _update_fields(self, ignore_update_fields=None):
181166
summary = ' '.join(self.summary)
182167
self.log('summary of app setting changes: {}'.format(summary), logging.DEBUG)
183168

184-
def diff_canaries(self):
185-
prev_app_settings = self.previous()
186-
action, canaries = None, []
187-
if prev_app_settings is not None:
188-
if prev_app_settings.canaries != self.canaries:
189-
for procfile_type in self.canaries: # add canary
190-
if procfile_type not in prev_app_settings.canaries:
191-
if action is None:
192-
action = "append"
193-
canaries.append(procfile_type)
194-
for procfile_type in prev_app_settings.canaries: # delete canary
195-
if procfile_type not in self.canaries:
196-
if action is None:
197-
action = "remove"
198-
canaries.append(procfile_type)
199-
return prev_app_settings, action, canaries
200-
201169
@transaction.atomic
202170
def save(self, ignore_update_field=None, *args, **kwargs):
203171
self._update_fields(ignore_update_field)

rootfs/api/models/build.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,13 @@ def get_image(self, procfile_type, default_image=None):
7171
return default_image if default_image else self.image
7272

7373
def create_release(self, user, *args, **kwargs):
74-
app_settings = self.app.appsettings_set.latest()
7574
latest_release = self.app.release_set.filter(failed=False).latest()
7675
latest_version = self.app.release_set.latest().version
7776
try:
7877
new_release = latest_release.new(
7978
user,
8079
build=self,
8180
config=latest_release.config,
82-
canary=len(app_settings.canaries) > 0,
8381
)
8482
run_pipeline.delay(new_release)
8583
return new_release

rootfs/api/models/gateway.py

Lines changed: 10 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import re
21
import logging
32
from django.db import models
43
from django.conf import settings
@@ -201,35 +200,16 @@ def hostnames(self):
201200
@property
202201
def default_rules(self):
203202
service = get_object_or_404(self.app.service_set, procfile_type=self.procfile_type)
204-
stable_backend_refs, canary_backend_refs = [], []
203+
backend_refs = []
205204
for item in service.ports:
206205
if item["port"] == self.port:
207-
stable_backend = {
206+
backend_refs.append({
208207
"kind": "Service",
209208
"name": str(service),
210209
"port": item["port"],
211210
"weight": 100,
212-
}
213-
canary_backend = {
214-
"kind": "Service",
215-
"name": "%s-canary" % str(service),
216-
"port": item["port"],
217-
"weight": 0,
218-
}
219-
stable_backend_refs.append(stable_backend)
220-
canary_backend_refs.append(stable_backend)
221-
canary_backend_refs.append(canary_backend)
222-
return {
223-
"stable": [{"backendRefs": stable_backend_refs}],
224-
"canary": [{"backendRefs": canary_backend_refs}],
225-
}
226-
227-
@property
228-
def current_rules(self):
229-
app_settings = self.app.appsettings_set.latest()
230-
if self.procfile_type in app_settings.canaries:
231-
return self.rules["canary"]
232-
return self.rules["stable"]
211+
})
212+
return [{"backendRefs": backend_refs}]
233213

234214
def log(self, message, level=logging.INFO):
235215
"""Logs a message in the context of this service.
@@ -246,13 +226,11 @@ def check_rules(self):
246226
service = self.app.service_set.filter(
247227
procfile_type=self.procfile_type).first()
248228
ports = [item["port"] for item in service.ports]
249-
for rules in self.rules.values():
250-
for rule in rules:
251-
for backend_ref in rule["backendRefs"]:
252-
port = backend_ref["port"]
253-
name = re.sub(r'(.*)-canary', r'\1', backend_ref["name"])
254-
if port not in ports or name != str(service):
255-
return False, {"detail": "backendRefs associated with incorrect service"}
229+
for rule in self.rules:
230+
for backend_ref in rule["backendRefs"]:
231+
port = backend_ref["port"]
232+
if port not in ports or backend_ref["name"] != str(service):
233+
return False, {"detail": "backendRefs associated with incorrect service"}
256234
return True, ""
257235

258236
def refresh_to_k8s(self):
@@ -266,7 +244,7 @@ def refresh_to_k8s(self):
266244
self.scheduler().httproute.delete(self.app.id, self._https_redirect_name)
267245
else:
268246
parent_refs.extend(http_parent_refs)
269-
self._refresh_to_k8s(self.current_rules, parent_refs)
247+
self._refresh_to_k8s(self.rules, parent_refs)
270248
else:
271249
self.scheduler().httproute.delete(self.app.id, self.name)
272250
self.scheduler().httproute.delete(self.app.id, self._https_redirect_name)

0 commit comments

Comments
 (0)