Skip to content

Commit 9b0cc0a

Browse files
authored
chore(controller): request check name parameter (#174)
* chore(controller): mount should not release * fix(controller): app destroy error * chore(controller): support image pull secrets by ptype * chore(controller): list pod when pending * chore(controller): modify clean ptype check * chore(controller): request check name parameter
1 parent 9cfc4ec commit 9b0cc0a

16 files changed

Lines changed: 265 additions & 56 deletions

charts/controller/templates/_helpers.tpl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ env:
177177
{{- end }}
178178
{{- end }}
179179

180+
181+
{{- define "controller-job.envs" }}
182+
env:
183+
- name: DRYCC_DATABASE_ROUTERS
184+
value: api.routers.DefaultReplicaRouter
185+
{{- end }}
186+
180187
{{/* Generate controller deployment limits */}}
181188
{{- define "controller.limits" -}}
182189
{{- if or (.Values.limitsCpu) (.Values.limitsMemory) }}

charts/controller/templates/controller-cronjob-daily.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ spec:
3333
- -a
3434
- $(DRYCC_CONTROLLER_API_SERVICE_HOST):$(DRYCC_CONTROLLER_API_SERVICE_PORT)
3535
{{- include "controller.envs" . | indent 12 }}
36+
{{- include "controller-job.envs" . | indent 12 }}
3637
containers:
3738
- image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/controller:{{.Values.imageTag}}
3839
imagePullPolicy: {{.Values.pull_policy}}
@@ -47,6 +48,7 @@ spec:
4748
- python -u /workspace/manage.py clearsocial
4849
{{- end }}
4950
{{- include "controller.envs" . | indent 12 }}
51+
{{- include "controller-job.envs" . | indent 12 }}
5052
- image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/controller:{{.Values.imageTag}}
5153
imagePullPolicy: {{.Values.imagePullPolicy}}
5254
name: drycc-controller-load-db-state-to-k8s
@@ -60,6 +62,7 @@ spec:
6062
- python /workspace/manage.py load_db_state_to_k8s
6163
{{- end }}
6264
{{- include "controller.envs" . | indent 12 }}
65+
{{- include "controller-job.envs" . | indent 12 }}
6366
- image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/controller:{{.Values.imageTag}}
6467
imagePullPolicy: {{.Values.pull_policy}}
6568
name: drycc-controller-measure-apps
@@ -73,6 +76,7 @@ spec:
7376
- python -u /workspace/manage.py measure_apps
7477
{{- end }}
7578
{{- include "controller.envs" . | indent 12 }}
79+
{{- include "controller-job.envs" . | indent 12 }}
7680
- image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/controller:{{.Values.imageTag}}
7781
imagePullPolicy: {{.Values.pull_policy}}
7882
name: drycc-controller-measure-resources
@@ -86,6 +90,7 @@ spec:
8690
- python -u /workspace/manage.py measure_resources
8791
{{- end }}
8892
{{- include "controller.envs" . | indent 12 }}
93+
{{- include "controller-job.envs" . | indent 12 }}
8994
- image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/controller:{{.Values.imageTag}}
9095
imagePullPolicy: {{.Values.pull_policy}}
9196
name: drycc-controller-measure-volumes
@@ -99,3 +104,4 @@ spec:
99104
- python -u /workspace/manage.py measure_volumes
100105
{{- end }}
101106
{{- include "controller.envs" . | indent 12 }}
107+
{{- include "controller-job.envs" . | indent 12 }}

charts/controller/templates/controller-cronjob-hourly.yaml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ spec:
3333
- -a
3434
- $(DRYCC_CONTROLLER_API_SERVICE_HOST):$(DRYCC_CONTROLLER_API_SERVICE_PORT)
3535
{{- include "controller.envs" . | indent 12 }}
36+
{{- include "controller-job.envs" . | indent 12 }}
3637
containers:
3738
- image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/controller:{{.Values.imageTag}}
3839
imagePullPolicy: {{.Values.pull_policy}}
@@ -47,6 +48,7 @@ spec:
4748
- python -u /workspace/manage.py measure_networks
4849
{{- end }}
4950
{{- include "controller.envs" . | indent 12 }}
51+
{{- include "controller-job.envs" . | indent 12 }}
5052
- image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/controller:{{.Values.imageTag}}
5153
imagePullPolicy: {{.Values.pull_policy}}
5254
name: drycc-controller-measure-loadbalancer
@@ -59,4 +61,5 @@ spec:
5961
- -c
6062
- python -u /workspace/manage.py measure_loadbalancers
6163
{{- end }}
62-
{{- include "controller.envs" . | indent 12 }}
64+
{{- include "controller.envs" . | indent 12 }}
65+
{{- include "controller-job.envs" . | indent 12 }}

charts/controller/templates/controller-job-upgrade.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ spec:
2020
- -u
2121
- $(DRYCC_DATABASE_URL),$(DRYCC_VALKEY_URL),$(DRYCC_DATABASE_REPLICA_URL)
2222
{{- include "controller.envs" . | indent 8 }}
23+
{{- include "controller-job.envs" . | indent 12 }}
2324
containers:
2425
- name: drycc-controller-job-upgrade
2526
image: {{.Values.imageRegistry}}/{{.Values.imageOrg}}/controller:{{.Values.imageTag}}
@@ -38,6 +39,7 @@ spec:
3839
python -u /workspace/manage.py loaddata /etc/controller/limit-plans.json
3940
{{- end }}
4041
{{- include "controller.envs" . | indent 8 }}
42+
{{- include "controller-job.envs" . | indent 12 }}
4143
volumeMounts:
4244
- name: controller-config
4345
readOnly: false

rootfs/api/models/app.py

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ def restart(self, **kwargs): # noqa
270270
def scale(self, user, structure):
271271
err_msg = None
272272
release = Release.latest(self)
273-
if (PTYPE_RUN in structure or release.build is None):
273+
if (PTYPE_RUN in structure or release is None or release.build is None):
274274
if PTYPE_RUN in structure:
275275
err_msg = 'Cannot set scale for reserved types, procfile type is: run'
276276
else:
@@ -283,7 +283,7 @@ def scale(self, user, structure):
283283
def pipeline(self, release, ptypes, force_deploy=False):
284284
prefix = f"[pipeline] release {release.version_name}"
285285
try:
286-
if release.build is not None:
286+
if release is not None and release.build is not None:
287287
if release.build.dryccfile:
288288
for run in release.get_runners(ptypes):
289289
self.log(f"{prefix} starts running pipeline.run {run['image']}")
@@ -323,7 +323,7 @@ def deploy(self, release, ptypes=None, force_deploy=False, rollback_on_failure=T
323323
324324
force_deploy can be used when a deployment is broken, such as for Rollback
325325
"""
326-
if release.build is None:
326+
if release is None or release.build is None:
327327
raise DryccException('No build associated with this release')
328328
# use create to make sure minimum resources are created
329329
self.create()
@@ -352,7 +352,7 @@ def mount(self, user, volume, structure=None):
352352
if release is None or release.build is None:
353353
raise DryccException('No build associated with this release')
354354
app_settings = self.appsettings_set.latest()
355-
self._mount(user, volume, release, app_settings, structure=structure)
355+
self._mount(user, volume, app_settings, structure=structure)
356356

357357
def clean(self, release=None, ptypes=None):
358358
release = release if release else self.release_set.latest()
@@ -431,17 +431,21 @@ def get_command_and_args(pod, container_name):
431431
result = []
432432
try:
433433
pod = self.scheduler().pod.get(self.id, pod_name).json()
434-
for status in pod["status"]["containerStatuses"]:
434+
if pod["status"]['phase'] == 'Pending':
435+
statuses = pod["spec"]["containers"]
436+
else:
437+
statuses = pod["status"]["containerStatuses"]
438+
for status in statuses:
435439
command, args = get_command_and_args(pod, status["name"])
436440
result.append({
437441
"container": status["name"],
438442
"image": status["image"],
439443
"command": command,
440444
"args": args,
441-
"state": status["state"],
442-
"lastState": status["lastState"],
443-
"ready": status["ready"],
444-
"restartCount": status["restartCount"],
445+
"state": status.get("state", {}),
446+
"lastState": status.get("lastState", {}),
447+
"ready": status.get("ready", False),
448+
"restartCount": status.get("restartCount", 0),
445449
"status": pod["status"].get("phase", ""),
446450
"reason": pod["status"].get("reason", ""),
447451
"message": pod["status"].get("message", "")
@@ -470,15 +474,22 @@ def list_pods(self, *args, **kwargs):
470474
else:
471475
started = str(
472476
datetime.now(timezone.utc).strftime(settings.DRYCC_DATETIME_FORMAT))
477+
state = str(self.scheduler().pod.state(p))
478+
if p['status']['phase'] != 'Pending':
479+
ready = len([1 for s in p["status"]["containerStatuses"] if s['ready']])
480+
restarts = sum([s['restartCount'] for s in p["status"]["containerStatuses"]])
481+
else:
482+
restarts = 0
483+
ready = 0
473484
item = {
474-
'name': p['metadata']['name'], 'state': str(self.scheduler().pod.state(p)),
485+
'name': p['metadata']['name'],
486+
'state': state,
475487
'release': labels['version'], 'type': labels['type'], 'started': started,
476488
'ready': "%s/%s" % (
477-
len([1 for s in p["status"]["containerStatuses"] if s['ready']]),
478-
len(p["status"]["containerStatuses"]),
489+
ready,
490+
len(p["spec"]["containers"]),
479491
),
480-
'restarts': sum(
481-
[s['restartCount'] for s in p["status"]["containerStatuses"]]),
492+
'restarts': restarts
482493
}
483494
data.append(item)
484495
# sorting so latest start date is first
@@ -626,12 +637,12 @@ def autoscale(self, proc_type, autoscale):
626637
# let the user know about any other errors
627638
raise ServiceUnavailable(str(e)) from e
628639

629-
def image_pull_secret(self, namespace, registry, image):
640+
def image_pull_secret(self, namespace, ptype, registry, image):
630641
"""
631642
Take registry information and set as an imagePullSecret for an RC / Deployment
632643
http://kubernetes.io/docs/user-guide/images/#specifying-imagepullsecrets-on-a-pod
633644
"""
634-
docker_config, name, create = self._get_private_registry_config(image, registry)
645+
docker_config, name, create = self._get_private_registry_config(ptype, image, registry)
635646
if create is None:
636647
return
637648
elif create:
@@ -741,11 +752,14 @@ def _clean_app_logs(self):
741752
err = 'Error deleting existing application logs: {}'.format(e)
742753
self.log(err, logging.WARNING)
743754

744-
def _mount(self, user, volume, release, app_settings, structure=None):
755+
def _mount(self, user, volume, app_settings, structure=None):
745756
volumes = Volume.objects.filter(app=self)
746757
tasks = []
747758
for scale_type, replicas in structure.items() if structure else self.structure.items():
748759
if scale_type != PTYPE_RUN:
760+
release = self.release_set.filter(
761+
deployed_ptypes__contains=scale_type,
762+
failed=False).latest()
749763
replicas = self.structure.get(scale_type, 0)
750764
scale_type_volumes = [
751765
volume for volume in volumes if scale_type in volume.path.keys()]
@@ -810,7 +824,7 @@ def _deploy(self, deploys, ptypes, prev_release,
810824
except KubeException as e:
811825
# Don't rollback if the previous release doesn't have a build which means
812826
# this is the first build and all the previous releases are just config changes.
813-
if (rollback_on_failure and prev_release.build is not None):
827+
if rollback_on_failure and prev_release is not None and prev_release.build is not None: # noqa
814828
err = 'There was a problem deploying {}. Rolling back to release {}.'.format(
815829
release.version_name, prev_release.version_name)
816830
# This goes in the log before the rollback starts
@@ -1098,7 +1112,7 @@ def _build_env_vars(self, release, ptype):
10981112
Build a dict of env vars, setting default vars based on app type
10991113
and then combining with the user set ones
11001114
"""
1101-
if release.build is None:
1115+
if release is None or release.build is None:
11021116
raise DryccException('No build associated with this release to run this command')
11031117

11041118
# mix in default environment information drycc may require
@@ -1119,8 +1133,8 @@ def _build_env_vars(self, release, ptype):
11191133
default_env['PORT'] = port
11201134
return default_env
11211135

1122-
def _get_private_registry_config(self, image, registry=None):
1123-
name = settings.REGISTRY_SECRET_PREFIX
1136+
def _get_private_registry_config(self, ptype, image, registry=None):
1137+
name = settings.REGISTRY_SECRET_PREFIX + '-' + ptype
11241138
if registry:
11251139
# try to get the hostname information
11261140
hostname = registry.get('hostname', None)
@@ -1206,7 +1220,7 @@ def _gather_app_settings(self, release, app_settings, ptype, replicas, volumes=N
12061220
registry = config.registry.get(ptype, {})
12071221
# create image pull secret if needed
12081222
image_pull_secret_name = self.image_pull_secret(
1209-
self.id, registry, release.get_deploy_image(ptype))
1223+
self.id, ptype, registry, release.get_deploy_image(ptype))
12101224

12111225
# only web is routable
12121226
# https://www.drycc.cc/applications/managing-app-processes/#default-process-types

rootfs/api/models/gateway.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from .base import AuditedModel, DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT
1313

14+
1415
User = get_user_model()
1516
logger = logging.getLogger(__name__)
1617

rootfs/api/serializers/__init__.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ def validate_ptype(value):
8080
return value
8181

8282

83+
def validate_name(value):
84+
NAME_REGEX = r'^(?P<name>{})$'.format(settings.NAME_REGEX)
85+
NAME_MATCH = re.compile(NAME_REGEX)
86+
NAME_MISMATCH_MSG = "name can only supports: %s" % NAME_MATCH.pattern
87+
if not re.match(NAME_REGEX, value):
88+
raise serializers.ValidationError(NAME_MISMATCH_MSG)
89+
if len(value) < PTYPE_MIN_LENGTH or len(value) > PTYPE_MAX_LENGTH:
90+
raise serializers.ValidationError(
91+
"The length of name must be between {} and {}".format(
92+
PTYPE_MIN_LENGTH, PTYPE_MAX_LENGTH))
93+
return value
94+
95+
8396
class JSONFieldSerializer(serializers.JSONField):
8497
def __init__(self, *args, **kwargs):
8598
self.convert_to_str = kwargs.pop('convert_to_str', True)
@@ -585,7 +598,8 @@ class Meta:
585598

586599
@staticmethod
587600
def validate_autoscale(data):
588-
for _, autoscale in data.items():
601+
for ptype, autoscale in data.items():
602+
validate_ptype(ptype)
589603
if autoscale is None:
590604
continue
591605
validate_json(autoscale, AUTOSCALE_SCHEMA, serializers.ValidationError)
@@ -634,6 +648,8 @@ def validate_size(self, data):
634648
raise serializers.ValidationError(VOLUME_SIZE_MISMATCH_MSG)
635649
return data.upper()
636650

651+
validate_name = staticmethod(validate_name)
652+
637653
@staticmethod
638654
def validate_path(data):
639655
logger.debug(f"mount validate_path data: {data}")
@@ -691,6 +707,8 @@ def update(self, instance, validated_data):
691707
instance.save()
692708
return instance
693709

710+
validate_name = staticmethod(validate_name)
711+
694712

695713
class MetricSerializer(serializers.Serializer):
696714
start = serializers.IntegerField(
@@ -733,6 +751,8 @@ def to_representation(self, instance):
733751
representation['addresses'] = instance.addresses
734752
return representation
735753

754+
validate_name = staticmethod(validate_name)
755+
736756
@staticmethod
737757
def validate_protocol(value):
738758
if not re.match(GATEWAY_PROTOCOL_MATCH, value):
@@ -756,6 +776,8 @@ class Meta:
756776
model = models.gateway.Route
757777
fields = '__all__'
758778

779+
validate_name = staticmethod(validate_name)
780+
759781
@staticmethod
760782
def validate_kind(value):
761783
if not re.match(ROUTE_PROTOCOL_MATCH, value):

rootfs/api/settings/production.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@
189189
'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
190190
},
191191
'simple': {
192-
'format': '%(levelname)s %(message)s'
192+
'format': '%(levelname)s %(asctime)s %(message)s'
193193
},
194194
},
195195
'filters': {
@@ -372,12 +372,16 @@
372372
DATABASES["replica"] = dj_database_url.config(default=DRYCC_DATABASE_REPLICA_URL)
373373

374374
# database routers
375-
DATABASE_ROUTERS = ['api.routers.DefaultReplicaRouter', ]
375+
# Implements: 'api.routers.DefaultReplicaRouter'
376+
DATABASE_ROUTERS = [r for r in os.environ.get('DRYCC_DATABASE_ROUTERS', '').split(',') if r]
377+
376378

377379
APP_URL_REGEX = '[a-z0-9-]+'
378380

379381
DOMAIN_URL_REGEX = r'\**\.?[-\._\w]+'
380382

383+
NAME_REGEX = r'[a-z0-9]+(\-[a-z0-9]+)*'
384+
381385
# Oauth settings
382386

383387
DRYCC_PASSPORT_URL = os.environ.get('DRYCC_PASSPORT_URL', 'https://127.0.0.1:8000')

0 commit comments

Comments
 (0)