Skip to content

Commit d2ca0b0

Browse files
committed
feat(controller): add svc type support
1 parent 948eacb commit d2ca0b0

7 files changed

Lines changed: 33 additions & 36 deletions

File tree

rootfs/api/exceptions.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ def custom_exception_handler(exc, context):
4444
# No response means DRF couldn't handle it
4545
# Output a generic 500 in a JSON format
4646
if response is None:
47+
raise exc
4748
logging.exception('Uncaught Exception', exc_info=exc)
4849
set_rollback()
4950
return Response({'detail': 'Server Error'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

rootfs/api/migrations/0001_initial.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,8 @@ class Migration(migrations.Migration):
202202
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
203203
('created', models.DateTimeField(auto_now_add=True)),
204204
('updated', models.DateTimeField(auto_now=True)),
205+
('service_type', models.TextField()),
205206
('procfile_type', models.TextField()),
206-
('path_pattern', models.TextField()),
207207
('app', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.app')),
208208
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
209209
],

rootfs/api/models/service.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
class Service(AuditedModel):
1414
owner = models.ForeignKey(User, on_delete=models.PROTECT)
1515
app = models.ForeignKey('App', on_delete=models.CASCADE)
16+
service_type = models.TextField(blank=False, null=False, unique=False)
1617
procfile_type = models.TextField(blank=False, null=False, unique=False)
17-
path_pattern = models.TextField(blank=False, null=False, unique=False)
18+
1819

1920
class Meta:
2021
get_latest_by = 'created'
@@ -26,8 +27,8 @@ def __str__(self):
2627

2728
def as_dict(self):
2829
return {
29-
"procfile_type": self.procfile_type,
30-
"path_pattern": self.path_pattern
30+
"service_type": self.service_type,
31+
"procfile_type": self.procfile_type
3132
}
3233

3334
def create(self, *args, **kwargs): # noqa
@@ -39,7 +40,7 @@ def create(self, *args, **kwargs): # noqa
3940
try:
4041
self._scheduler.svc.get(namespace, svc_name)
4142
except KubeException:
42-
self._scheduler.svc.create(namespace, svc_name)
43+
self._scheduler.svc.create(namespace, svc_name, self.service_type)
4344
except KubeException as e:
4445
raise ServiceUnavailable('Kubernetes service could not be created') from e
4546

rootfs/api/serializers.py

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
User = get_user_model()
2727
logger = logging.getLogger(__name__)
2828

29+
SVCTYPE_MATCH = re.compile(r'^(ClusterIP|LoadBalancer)$')
30+
SVCTYPE_MISMATCH_MSG = "The service type currently only supports ClusterIP and LoadBalancer"
2931
PROCTYPE_MATCH = re.compile(r'^(?P<type>[a-z0-9]+(\-[a-z0-9]+)*)$')
3032
PROCTYPE_MISMATCH_MSG = "Process types can only contain lowercase alphanumeric characters"
3133
MEMLIMIT_MATCH = re.compile(r'^(?P<mem>([1-9][0-9]*[mgMG]))$', re.IGNORECASE)
@@ -511,34 +513,26 @@ class ServiceSerializer(serializers.ModelSerializer):
511513

512514
app = serializers.SlugRelatedField(slug_field='id', queryset=models.app.App.objects.all())
513515
owner = serializers.ReadOnlyField(source='owner.username')
516+
service_type = serializers.CharField(allow_blank=False, allow_null=False, required=True)
514517
procfile_type = serializers.CharField(allow_blank=False, allow_null=False, required=True)
515-
path_pattern = serializers.CharField(allow_blank=False, allow_null=False, required=True)
516518

517519
class Meta:
518520
"""Metadata options for a :class:`ServiceSerializer`."""
519521
model = models.service.Service
520-
fields = ['owner', 'created', 'updated', 'app', 'procfile_type', 'path_pattern']
522+
fields = ['owner', 'created', 'updated', 'app', 'service_type', 'procfile_type']
521523
read_only_fields = ['uuid']
522524

523525
@staticmethod
524-
def validate_procfile_type(value):
525-
if not re.match(PROCTYPE_MATCH, value):
526-
raise serializers.ValidationError(PROCTYPE_MISMATCH_MSG)
526+
def validate_service_type(value):
527+
if not re.match(SVCTYPE_MATCH, value):
528+
raise serializers.ValidationError(SVCTYPE_MISMATCH_MSG)
527529

528530
return value
529531

530532
@staticmethod
531-
def validate_path_pattern(value):
532-
for pattern in str(value).split(","):
533-
if not pattern.strip():
534-
raise serializers.ValidationError(
535-
"Service value should be valid regex (or set of regex split by comma)")
536-
try:
537-
re.compile(pattern)
538-
except re.error as e:
539-
logger.exception(e)
540-
raise serializers.ValidationError(
541-
"Service value should be valid regex (or set of regex split by comma)")
533+
def validate_procfile_type(value):
534+
if not re.match(PROCTYPE_MATCH, value):
535+
raise serializers.ValidationError(PROCTYPE_MISMATCH_MSG)
542536

543537
return value
544538

rootfs/api/tests/test_services.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,46 +34,46 @@ def test_service_basic_ops(self):
3434
# create 1st service
3535
response = self.client.post(
3636
'/v2/apps/{}/services'.format(app_id),
37-
{'procfile_type': 'test', 'path_pattern': '/testep/notify'}
37+
{'service_type': 'ClusterIP', 'procfile_type': 'test'}
3838
)
3939
self.assertEqual(response.status_code, 201, response.data)
4040
# list 1st service
4141
response = self.client.get('/v2/apps/{}/services'.format(app_id))
4242
self.assertEqual(response.status_code, 200, response.data)
4343
self.assertEqual(len(response.data['services']), 1)
4444
expected1 = {
45-
'procfile_type': 'test',
46-
'path_pattern': '/testep/notify'
45+
'service_type': 'ClusterIP',
46+
'procfile_type': 'test'
4747
}
4848
self.assertDictContainsSubset(expected1, response.data['services'][0])
4949
# update 1st service
5050
response = self.client.post(
5151
'/v2/apps/{}/services'.format(app_id),
52-
{'procfile_type': 'test', 'path_pattern': '/testep/notify_new'}
52+
{'service_type': 'LoadBalancer', 'procfile_type': 'test'}
5353
)
5454
self.assertEqual(response.status_code, 201, response.data)
5555
# list 1st service and get new value
5656
response = self.client.get('/v2/apps/{}/services'.format(app_id))
5757
self.assertEqual(response.status_code, 200, response.data)
5858
self.assertEqual(len(response.data['services']), 1)
5959
expected1 = {
60-
'procfile_type': 'test',
61-
'path_pattern': '/testep/notify_new'
60+
'service_type': 'LoadBalancer',
61+
'procfile_type': 'test'
6262
}
6363
self.assertDictContainsSubset(expected1, response.data['services'][0])
6464
# create 2nd service
6565
response = self.client.post(
6666
'/v2/apps/{}/services'.format(app_id),
67-
{'procfile_type': 'test2', 'path_pattern': '/testep2/notify'}
67+
{'service_type': 'ClusterIP', 'procfile_type': 'test2'}
6868
)
6969
self.assertEqual(response.status_code, 201, response.data)
7070
# list two services
7171
response = self.client.get('/v2/apps/{}/services'.format(app_id))
7272
self.assertEqual(response.status_code, 200, response.data)
7373
self.assertEqual(len(response.data['services']), 2)
7474
expected2 = {
75-
'procfile_type': 'test2',
76-
'path_pattern': '/testep2/notify'
75+
'service_type': 'ClusterIP',
76+
'procfile_type': 'test2'
7777
}
7878
self.assertDictContainsSubset(expected2, response.data['services'][0])
7979
self.assertDictContainsSubset(expected1, response.data['services'][1])

rootfs/api/views.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -421,19 +421,19 @@ def list(self, *args, **kwargs):
421421
return Response({"services": data}, status=status.HTTP_200_OK)
422422

423423
def create_or_update(self, request, **kwargs):
424+
svt = self.get_serializer().validate_service_type(request.data.get('service_type'))
424425
pft = self.get_serializer().validate_procfile_type(request.data.get('procfile_type'))
425-
pp = self.get_serializer().validate_path_pattern(request.data.get('path_pattern'))
426426
app = self.get_app()
427427
svc = app.service_set.filter(procfile_type=pft).first()
428428
if svc:
429-
if svc.path_pattern == pp:
429+
if svc.service_type == svt:
430430
return Response(status=status.HTTP_204_NO_CONTENT)
431431
else:
432-
svc.path_pattern = pp
432+
svc.service_type = svt
433433
svc.save()
434434
else:
435435
svc = models.service.Service.objects.create(
436-
owner=app.owner, app=app, procfile_type=pft, path_pattern=pp)
436+
owner=app.owner, app=app, service_type=svt, procfile_type=pft)
437437
return Response(status=status.HTTP_201_CREATED)
438438

439439
def delete(self, request, **kwargs):

rootfs/scheduler/resources/service.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def get(self, namespace, name=None, **kwargs):
2727

2828
return response
2929

30-
def create(self, namespace, name, **kwargs):
30+
def create(self, namespace, name, type="ClusterIP", **kwargs):
3131
# Ports and app type will be overwritten as required
3232
manifest = {
3333
'kind': 'Service',
@@ -41,14 +41,15 @@ def create(self, namespace, name, **kwargs):
4141
'annotations': {}
4242
},
4343
'spec': {
44+
'type': type,
4445
'ports': [{
4546
'name': 'http',
4647
'port': 80,
4748
'targetPort': 5000,
4849
'protocol': 'TCP'
4950
}],
5051
'selector': {
51-
'app': namespace,
52+
'app': name,
5253
'heritage': 'drycc'
5354
}
5455
}

0 commit comments

Comments
 (0)