-
Notifications
You must be signed in to change notification settings - Fork 0
/
instance.py
312 lines (260 loc) · 14.6 KB
/
instance.py
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
import uuid
import cherrypy
from deli.counter.http.mounts.root.routes.v1.errors.quota import QuotaError
from deli.counter.http.mounts.root.routes.v1.validation_models.images import ResponseImage
from deli.counter.http.mounts.root.routes.v1.validation_models.instances import RequestCreateInstance, \
ResponseInstance, ParamsInstance, ParamsListInstance, RequestInstancePowerOffRestart, RequestInstanceImage
from deli.http.request_methods import RequestMethods
from deli.http.route import Route
from deli.http.router import Router
from deli.kubernetes.resources.const import REGION_LABEL, IMAGE_LABEL, ZONE_LABEL, ATTACHED_TO_LABEL
from deli.kubernetes.resources.model import ResourceState
from deli.kubernetes.resources.project import Project
from deli.kubernetes.resources.v1alpha1.flavor.model import Flavor
from deli.kubernetes.resources.v1alpha1.image.model import Image, ImageVisibility
from deli.kubernetes.resources.v1alpha1.instance.model import Instance, VMPowerState
from deli.kubernetes.resources.v1alpha1.keypair.keypair import Keypair
from deli.kubernetes.resources.v1alpha1.network.model import NetworkPort, Network
from deli.kubernetes.resources.v1alpha1.project_quota.model import ProjectQuota
from deli.kubernetes.resources.v1alpha1.region.model import Region
from deli.kubernetes.resources.v1alpha1.service_account.model import ServiceAccount
from deli.kubernetes.resources.v1alpha1.volume.model import Volume
from deli.kubernetes.resources.v1alpha1.zone.model import Zone
class InstanceRouter(Router):
def __init__(self):
super().__init__(uri_base='instances')
@Route(methods=[RequestMethods.POST])
@cherrypy.tools.project_scope()
@cherrypy.tools.model_in(cls=RequestCreateInstance)
@cherrypy.tools.model_out(cls=ResponseInstance)
@cherrypy.tools.enforce_policy(policy_name="instances:create")
def create(self):
request: RequestCreateInstance = cherrypy.request.model
project: Project = cherrypy.request.project
# TODO: do we care about unique instance names in a project?
# instance = Instance.get_by_name(project, request.name)
# if instance is not None:
# raise cherrypy.HTTPError(409, 'An instance with the requested name already exists.')
region = Region.get(request.region_id)
if region is None:
raise cherrypy.HTTPError(404, 'A region with the requested id does not exist.')
if region.state != ResourceState.Created:
raise cherrypy.HTTPError(400, 'Can only create a instance with a region in the following state: %s'.format(
ResourceState.Created))
zone = None
if request.zone_id is not None:
zone = Zone.get(request.zone_id)
if zone is None:
raise cherrypy.HTTPError(404, 'A zone with the requested id does not exist.')
if zone.region.id != region.id:
raise cherrypy.HTTPError(409, 'The requested zone is not within the requested region')
if zone.state != ResourceState.Created:
raise cherrypy.HTTPError(400,
'Can only create a instance with a zone in the following state: %s'.format(
ResourceState.Created))
network = Network.get(request.network_id)
if network is None:
raise cherrypy.HTTPError(404, 'A network with the requested id does not exist.')
if network.region.id != region.id:
raise cherrypy.HTTPError(409, 'The requested network is not within the requested region')
if network.state != ResourceState.Created:
raise cherrypy.HTTPError(400, 'Can only create a instance with a network in the following state: %s'.format(
ResourceState.Created))
image: Image = Image.get(request.image_id)
if image is None:
raise cherrypy.HTTPError(404, 'An image with the requested id does not exist.')
if image.visibility == ImageVisibility.PRIVATE:
if image.project_id != project.id:
if image.is_member(project.id) is False:
raise cherrypy.HTTPError(404, 'An image with the requested id does not exist.')
if image.region.id != region.id:
raise cherrypy.HTTPError(409, 'The requested image is not within the requested region')
if image.state != ResourceState.Created:
raise cherrypy.HTTPError(400, 'Can only create a instance with a image in the following state: %s'.format(
ResourceState.Created))
flavor: Flavor = Flavor.get(request.flavor_id)
if flavor is None:
raise cherrypy.HTTPError(404, 'A flavor with the requested id does not exist.')
keypairs = []
for keypair_id in request.keypair_ids:
keypair = Keypair.get(project, keypair_id)
if keypair is None:
raise cherrypy.HTTPError(404,
'A keypair with the requested id of %s does not exist.'.format(keypair_id))
keypairs.append(keypair)
if request.service_account_id is not None:
service_account = ServiceAccount.get(project, request.service_account_id)
if service_account is None:
raise cherrypy.HTTPError(404, 'A service account with the requested id of %s does not exist.'.format(
request.service_account_id))
else:
service_account = ServiceAccount.get_by_name(project, 'default')
if service_account is None:
raise cherrypy.HTTPError(404, 'Could not find a default service account to attach to the instance.')
quota: ProjectQuota = ProjectQuota.list(project)[0]
used_vcpu = quota.used_vcpu + flavor.vcpus
used_ram = quota.used_ram + flavor.ram
requested_disk = flavor.disk
if request.disk is not None:
requested_disk = request.disk
used_disk = quota.used_disk + requested_disk
if quota.vcpu != -1:
if used_vcpu > quota.vcpu:
raise QuotaError("VCPU", flavor.vcpus, quota.used_vcpu, quota.vcpu)
if quota.ram != -1:
if used_ram > quota.ram:
raise QuotaError("Ram", flavor.ram, quota.used_ram, quota.ram)
if quota.disk != -1:
if used_disk > quota.disk:
raise QuotaError("Disk", requested_disk, quota.used_disk, quota.disk)
quota.used_vcpu = used_vcpu
quota.used_ram = used_ram
quota.used_disk = used_disk
quota.save()
network_port = NetworkPort()
network_port.project = project
network_port.network = network
network_port.create()
instance = Instance()
instance.name = request.name
instance.project = project
instance.region = region
if zone is not None:
instance.zone = zone
instance.image = image
instance.service_account = service_account
instance.network_port = network_port
instance.keypairs = keypairs
for k, v in request.tags.items():
instance.add_tag(k, v)
instance.flavor = flavor
if request.disk is not None:
instance.disk = request.disk
instance.create()
return ResponseInstance.from_database(instance)
@Route(route='{instance_id}')
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.model_out(cls=ResponseInstance)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="instances:get")
def get(self, **_):
return ResponseInstance.from_database(cherrypy.request.resource_object)
@Route()
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsListInstance)
@cherrypy.tools.model_out_pagination(cls=ResponseInstance)
@cherrypy.tools.enforce_policy(policy_name="instances:list")
def list(self, image_id, region_id, zone_id, limit: int, marker: uuid.UUID):
kwargs = {
'project': cherrypy.request.project,
'label_selector': [],
}
if image_id is not None:
image: Image = Image.get(cherrypy.request.project, image_id)
if image is None:
raise cherrypy.HTTPError(404, "An image with the requested id does not exist.")
kwargs['label_selector'].append(IMAGE_LABEL + '=' + image.id)
if region_id is not None:
region: Region = Region.get(region_id)
if region is None:
raise cherrypy.HTTPError(404, "A region with the requested id does not exist.")
kwargs['label_selector'].append(REGION_LABEL + '=' + region.id)
if zone_id is not None:
zone: Zone = Zone.get(zone_id)
if zone is None:
raise cherrypy.HTTPError(404, 'A zone with the requested id does not exist.')
kwargs['label_selector'].append(ZONE_LABEL + '=' + zone.id)
if len(kwargs['label_selector']) > 0:
kwargs['label_selector'] = ",".join(kwargs['label_selector'])
else:
del kwargs['label_selector']
return self.paginate(Instance, ResponseInstance, limit, marker, **kwargs)
@Route(route='{instance_id}', methods=[RequestMethods.DELETE])
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="instances:delete")
def delete(self, **_):
cherrypy.response.status = 204
instance: Instance = cherrypy.request.resource_object
if instance.task is not None and instance.state != ResourceState.Error:
raise cherrypy.HTTPError(400, "Please wait for the current task to finish.")
if instance.state == ResourceState.ToDelete or instance.state == ResourceState.Deleting:
raise cherrypy.HTTPError(400, "Instance is already being deleting")
if instance.state == ResourceState.Deleted:
raise cherrypy.HTTPError(400, "Instance has already been deleted")
instance.delete()
@Route(route='{instance_id}/action/start', methods=[RequestMethods.PUT])
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="nstances:action:stop")
def action_start(self, **_):
cherrypy.response.status = 202
instance: Instance = cherrypy.request.resource_object
if instance.state != ResourceState.Created:
raise cherrypy.HTTPError(400, 'Instance is not in the following state: ' + ResourceState.Created.value)
if instance.task is not None:
raise cherrypy.HTTPError(400, 'Please wait for the current task to finish.')
if instance.power_state != VMPowerState.POWERED_OFF:
raise cherrypy.HTTPError(400, 'Instance must be powered off.')
instance.action_start()
@Route(route='{instance_id}/action/stop', methods=[RequestMethods.PUT])
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.model_in(cls=RequestInstancePowerOffRestart)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="nstances:action:start")
def action_stop(self, **_):
request: RequestInstancePowerOffRestart = cherrypy.request.model
cherrypy.response.status = 202
instance: Instance = cherrypy.request.resource_object
if instance.state != ResourceState.Created:
raise cherrypy.HTTPError(400, 'Instance is not in the following state: ' + ResourceState.Created.value)
if instance.task is not None:
raise cherrypy.HTTPError(400, 'Please wait for the current task to finish.')
if instance.power_state != VMPowerState.POWERED_ON:
raise cherrypy.HTTPError(400, 'Instance must be powered on.')
instance.action_stop(request.hard, request.timeout)
@Route(route='{instance_id}/action/restart', methods=[RequestMethods.PUT])
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.model_in(cls=RequestInstancePowerOffRestart)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="nstances:action:restart")
def action_restart(self, **_):
request: RequestInstancePowerOffRestart = cherrypy.request.model
cherrypy.response.status = 202
instance: Instance = cherrypy.request.resource_object
if instance.state != ResourceState.Created:
raise cherrypy.HTTPError(400, 'Instance is not in the following state: ' + ResourceState.Created.value)
if instance.task is not None:
raise cherrypy.HTTPError(400, 'Please wait for the current task to finish.')
if instance.power_state != VMPowerState.POWERED_ON:
raise cherrypy.HTTPError(400, 'Instance must be powered on.')
instance.action_restart(request.hard, request.timeout)
@Route(route='{instance_id}/action/image', methods=[RequestMethods.POST])
@cherrypy.tools.project_scope()
@cherrypy.tools.model_params(cls=ParamsInstance)
@cherrypy.tools.model_in(cls=RequestInstanceImage)
@cherrypy.tools.model_out(cls=ResponseImage)
@cherrypy.tools.resource_object(id_param="instance_id", cls=Instance)
@cherrypy.tools.enforce_policy(policy_name="nstances:action:image")
def action_image(self, **_):
project: Project = cherrypy.request.project
request: RequestInstanceImage = cherrypy.request.model
instance: Instance = cherrypy.request.resource_object
if instance.state != ResourceState.Created:
raise cherrypy.HTTPError(400, 'Instance is not in the following state: ' + ResourceState.Created.value)
if instance.task is not None:
raise cherrypy.HTTPError(400, 'Please wait for the current task to finish.')
if instance.power_state != VMPowerState.POWERED_OFF:
raise cherrypy.HTTPError(400, 'Instance must be powered off.')
if Image.get_by_name(request.name, project=project) is not None:
raise cherrypy.HTTPError(400, 'An image with the requested name already exists.')
attached_volumes = Volume.list(project, label_selector=ATTACHED_TO_LABEL + "=" + str(instance.id))
if len(attached_volumes) > 0:
raise cherrypy.HTTPError(409, 'Cannot create an image while volumes are attached.')
image = instance.action_image(request.name)
return ResponseImage.from_database(image)