-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathservice.py
More file actions
146 lines (119 loc) · 5.5 KB
/
service.py
File metadata and controls
146 lines (119 loc) · 5.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
import logging
from django.db import models
from django.conf import settings
from api.models import AuditedModel, ServiceUnavailable
from scheduler import KubeException
logger = logging.getLogger(__name__)
class Service(AuditedModel):
owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
app = models.ForeignKey('App', on_delete=models.CASCADE)
procfile_type = models.TextField(blank=False, null=False, unique=False)
path_pattern = models.TextField(blank=False, null=False, unique=False)
class Meta:
get_latest_by = 'created'
unique_together = (('app', 'procfile_type'))
ordering = ['-created']
def __str__(self):
return self._svc_name()
def as_dict(self):
return {
"procfile_type": self.procfile_type,
"path_pattern": self.path_pattern
}
def create(self, *args, **kwargs): # noqa
# create required minimum service in k8s for the application
namespace = self._namespace()
svc_name = self._svc_name()
self.log('creating Service: {}'.format(svc_name), level=logging.DEBUG)
try:
try:
self._scheduler.svc.get(namespace, svc_name)
except KubeException:
self._scheduler.svc.create(namespace, svc_name)
except KubeException as e:
raise ServiceUnavailable('Kubernetes service could not be created') from e
# config service
annotations = self._gather_settings()
routable = annotations.pop('routable')
self._update_service(namespace, self.procfile_type, routable, annotations)
def save(self, *args, **kwargs):
service = super(Service, self).save(*args, **kwargs)
self.create()
return service
def delete(self, *args, **kwargs):
namespace = self._namespace()
svc_name = self._svc_name()
self.log('deleting Service: {}'.format(svc_name), level=logging.DEBUG)
try:
self._scheduler.svc.delete(namespace, svc_name)
except KubeException:
# swallow exception
# raise ServiceUnavailable('Kubernetes service could not be deleted') from e
self.log('Kubernetes service cannot be deleted: {}'.format(svc_name),
level=logging.ERROR)
# Delete from DB
return super(Service, self).delete(*args, **kwargs)
def log(self, message, level=logging.INFO):
"""Logs a message in the context of this service.
This prefixes log messages with an application "tag" that the customized
drycc-logspout will be on the lookout for. When it's seen, the message-- usually
an application event of some sort like releasing or scaling, will be considered
as "belonging" to the application instead of the controller and will be handled
accordingly.
"""
logger.log(level, "[{}]: {}".format(self.id, message))
def maintenance_mode(self, mode):
"""
Turn service maintenance mode on/off
"""
namespace = self._namespace()
svc_name = self._svc_name()
try:
service = self._fetch_service_config(namespace, svc_name)
except (ServiceUnavailable, KubeException) as e:
# ignore non-existing services
return
old_service = service.copy() # in case anything fails for rollback
try:
service['metadata']['annotations']['router.drycc.cc/maintenance'] = str(mode).lower()
self._scheduler.svc.update(namespace, svc_name, data=service)
except KubeException as e:
self._scheduler.svc.update(namespace, svc_name, data=old_service)
raise ServiceUnavailable(str(e)) from e
def _namespace(self):
return self.app.id
def _svc_name(self):
return "{}-{}".format(self.app.id, self.procfile_type)
def _gather_settings(self):
app_settings = self.app.appsettings_set.latest()
return {
'domains': self._svc_name(),
'maintenance': app_settings.maintenance,
'routable': app_settings.routable,
'proxyDomain': self.app.id,
'proxyLocations': self.path_pattern
}
def _update_service(self, namespace, app_type, routable, annotations): # noqa
"""Update application service with all the various required information"""
svc_name = "{}-{}".format(namespace, app_type)
service = self._fetch_service_config(namespace, svc_name)
old_service = service.copy() # in case anything fails for rollback
try:
# Update service information
for key, value in annotations.items():
if value is not None:
service['metadata']['annotations']['router.drycc.cc/%s' % key] = str(value)
else:
service['metadata']['annotations'].pop('router.drycc.cc/%s' % key, None)
if routable:
service['metadata']['labels']['router.drycc.cc/routable'] = 'true'
else:
# delete the annotation
service['metadata']['labels'].pop('router.drycc.cc/routable', None)
# Set app type selector
service['spec']['selector']['type'] = app_type
self._scheduler.svc.update(namespace, svc_name, data=service)
except Exception as e:
# Fix service to old port and app type
self._scheduler.svc.update(namespace, svc_name, data=old_service)
raise ServiceUnavailable(str(e)) from e