Skip to content

Commit 1f51b1d

Browse files
committed
feat(rootfs/api): connect api to k8s service.
extend helpers, create/update/delete k8s service by api request
1 parent ae1fbd7 commit 1f51b1d

4 files changed

Lines changed: 85 additions & 49 deletions

File tree

rootfs/api/models/__init__.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,12 @@ def _scheduler(self):
7272
mod = importlib.import_module(settings.SCHEDULER_MODULE)
7373
return mod.SchedulerClient(settings.SCHEDULER_URL, settings.K8S_API_VERIFY_TLS)
7474

75-
def _fetch_service_config(self, app):
75+
def _fetch_service_config(self, app, svc_name=None):
7676
try:
7777
# Get the service from k8s to attach the domain correctly
78-
svc = self._scheduler.svc.get(app, app).json()
78+
if svc_name is None:
79+
svc_name = app
80+
svc = self._scheduler.svc.get(app, svc_name).json()
7981
except KubeException as e:
8082
raise ServiceUnavailable('Could not fetch Kubernetes Service {}'.format(app)) from e
8183

@@ -90,9 +92,9 @@ def _fetch_service_config(self, app):
9092

9193
return svc
9294

93-
def _load_service_config(self, app, component):
95+
def _load_service_config(self, app, component, svc_name=None):
9496
# fetch setvice definition with minimum structure
95-
svc = self._fetch_service_config(app)
97+
svc = self._fetch_service_config(app, svc_name)
9698

9799
# always assume a .deis.io/ ending
98100
component = "%s.deis.io/" % component
@@ -103,9 +105,11 @@ def _load_service_config(self, app, component):
103105

104106
return config
105107

106-
def _save_service_config(self, app, component, data):
108+
def _save_service_config(self, app, component, data, svc_name=None):
109+
if svc_name is None:
110+
svc_name = app
107111
# fetch setvice definition with minimum structure
108-
svc = self._fetch_service_config(app)
112+
svc = self._fetch_service_config(app, svc_name)
109113

110114
# always assume a .deis.io ending
111115
component = "%s.deis.io/" % component
@@ -116,7 +120,7 @@ def _save_service_config(self, app, component, data):
116120

117121
# Update the k8s service for the application with new service information
118122
try:
119-
self._scheduler.svc.update(app, app, svc)
123+
self._scheduler.svc.update(app, svc_name, svc)
120124
except KubeException as e:
121125
raise ServiceUnavailable('Could not update Kubernetes Service {}'.format(app)) from e
122126

rootfs/api/models/service.py

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -23,50 +23,82 @@ def as_dict(self):
2323
"path_pattern": self.path_pattern
2424
}
2525

26-
def save(self, *args, **kwargs):
27-
# app = str(self.app)
28-
# domain = str(self.domain)
29-
30-
# # get config for the service
31-
# config = self._load_service_config(app, 'router')
32-
33-
# # See if domains are available
34-
# if 'domains' not in config:
35-
# config['domains'] = ''
36-
37-
# # convert from string to list to work with and filter out empty strings
38-
# domains = [_f for _f in config['domains'].split(',') if _f]
39-
# if domain not in domains:
40-
# domains.append(domain)
41-
# config['domains'] = ','.join(domains)
26+
def create(self, *args, **kwargs): # noqa
27+
# create required minimum service in k8s for the application
28+
namespace = self.app.id
29+
svc_name = "{}-{}".format(self.id, self.procfile_type)
30+
self.log('creating Service: {}'.format(svc_name), level=logging.DEBUG)
31+
try:
32+
try:
33+
self._scheduler.svc.get(namespace, svc_name)
34+
except KubeException:
35+
self._scheduler.svc.create(namespace, svc_name)
36+
except KubeException as e:
37+
raise ServiceUnavailable('Kubernetes service could not be created') from e
38+
# config service
39+
annotations = self._gather_settings()
40+
routable = annotations.pop('routable')
41+
self._update_service(namespace, self.procfile_type, routable, annotations)
4242

43-
# self._save_service_config(app, 'router', config)
43+
def save(self, *args, **kwargs):
44+
namespace = self.app.id
45+
svc_name = "{}-{}".format(self.id, self.procfile_type)
46+
self.log('updating Service: {}'.format(svc_name), level=logging.DEBUG)
47+
annotations = self._gather_settings()
48+
routable = annotations.pop('routable')
49+
self._update_service(namespace, self.procfile_type, routable, annotations)
4450

4551
# Save to DB
4652
return super(Service, self).save(*args, **kwargs)
4753

4854
def delete(self, *args, **kwargs):
49-
# app = str(self.app)
50-
# domain = str(self.domain)
51-
52-
# # Deatch cert, updates k8s
53-
# if self.certificate:
54-
# self.certificate.detach(domain=domain)
55-
56-
# # get config for the service
57-
# config = self._load_service_config(app, 'router')
58-
59-
# # See if domains are available
60-
# if 'domains' not in config:
61-
# config['domains'] = ''
62-
63-
# # convert from string to list to work with and filter out empty strings
64-
# domains = [_f for _f in config['domains'].split(',') if _f]
65-
# if domain in domains:
66-
# domains.remove(domain)
67-
# config['domains'] = ','.join(domains)
68-
69-
# self._save_service_config(app, 'router', config)
55+
namespace = self.app.id
56+
svc_name = "{}-{}".format(self.id, self.procfile_type)
57+
self.log('deleting Service: {}'.format(svc_name), level=logging.DEBUG)
58+
try:
59+
self._scheduler.svc.delete(namespace, svc_name)
60+
except KubeException:
61+
# swallow exception
62+
# raise ServiceUnavailable('Kubernetes service could not be deleted') from e
63+
self.log('Kubernetes service cannot be deleted: {}'.format(svc_name), level=logging.ERROR)
7064

7165
# Delete from DB
7266
return super(Service, self).delete(*args, **kwargs)
67+
68+
def _gather_settings(self):
69+
app_settings = self.app.appsettings_set.latest()
70+
return {
71+
'domains': "{}-{}".format(self.app.id, self.procfile_type)
72+
'maintenance': app_settings.maintenance,
73+
'routable': app_settings.routable,
74+
'proxyDomain': self.app.id,
75+
'proxyLocations': self.path_pattern
76+
}
77+
78+
def _update_service(self, namespace, app_type, routable, annotations): # noqa
79+
"""Update application service with all the various required information"""
80+
svc_name = "{}-{}".format(namespace, app_type)
81+
service = self._fetch_service_config(namespace, svc_name)
82+
old_service = service.copy() # in case anything fails for rollback
83+
84+
try:
85+
# Update service information
86+
for key, value in annotations.items():
87+
if value is not None:
88+
service['metadata']['annotations']['router.deis.io/%s' % key] = str(value)
89+
else:
90+
service['metadata']['annotations'].pop('router.deis.io/%s' % key, None)
91+
if routable:
92+
service['metadata']['labels']['router.deis.io/routable'] = 'true'
93+
else:
94+
# delete the annotation
95+
service['metadata']['labels'].pop('router.deis.io/routable', None)
96+
97+
# Set app type selector
98+
service['spec']['selector']['type'] = app_type
99+
100+
self._scheduler.svc.update(namespace, svc_name, data=service)
101+
except Exception as e:
102+
# Fix service to old port and app type
103+
self._scheduler.svc.update(namespace, svc_name, data=old_service)
104+
raise ServiceUnavailable(str(e)) from e

rootfs/api/serializers.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,7 @@ def validate_path_pattern(self, value):
483483
re.compile(pattern)
484484
except re.error as e:
485485
raise serializers.ValidationError(
486-
"Service value should be valid regex (or set of regex split by comma), error: {}"\
487-
.format(e.message))
486+
"Service value should be valid regex (or set of regex split by comma)")
488487

489488
return value
490489

rootfs/api/views.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -384,15 +384,16 @@ def list(self, *args, **kwargs):
384384
def create_or_update(self, request, **kwargs):
385385
pft = self.get_serializer().validate_procfile_type(request.data.get('procfile_type'))
386386
pp = self.get_serializer().validate_path_pattern(request.data.get('path_pattern'))
387-
svc = self.get_app().service_set.filter(procfile_type=pft).first()
387+
app = self.get_app()
388+
svc = app.service_set.filter(procfile_type=pft).first()
388389
if svc:
389390
if svc.path_pattern == pp:
390391
return Response(status=status.HTTP_204_NO_CONTENT)
391392
else:
392393
svc.path_pattern = pp
393394
svc.save()
394395
else:
395-
svc = Service.create(owner=self.owner, app=self.app, procfile_type=pft, path_pattern=pp)
396+
svc = models.Service.objects.create(owner=app.owner, app=app, procfile_type=pft, path_pattern=pp)
396397
return Response(status=status.HTTP_201_CREATED)
397398

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

0 commit comments

Comments
 (0)