Revision dca7553e
b/snf-cyclades-app/synnefo/api/servers.py | ||
---|---|---|
31 | 31 |
# interpreted as representing official policies, either expressed |
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 |
from base64 import b64decode |
|
35 |
|
|
36 | 34 |
from django.conf import settings |
37 | 35 |
from django.conf.urls.defaults import patterns |
38 | 36 |
from django.db import transaction |
... | ... | |
48 | 46 |
from synnefo.logic.utils import get_rsapi_state |
49 | 47 |
from synnefo.logic.rapi import GanetiApiError |
50 | 48 |
from synnefo.logic.backend_allocator import BackendAllocator |
51 |
from random import choice |
|
52 | 49 |
|
53 | 50 |
|
54 | 51 |
from logging import getLogger |
... | ... | |
258 | 255 |
try: |
259 | 256 |
req = util.get_request_dict(request) |
260 | 257 |
log.info('create_server %s', req) |
258 |
user_id = request.user_uniq |
|
261 | 259 |
|
262 | 260 |
try: |
263 | 261 |
server = req['server'] |
... | ... | |
274 | 272 |
if len(personality) > settings.MAX_PERSONALITY: |
275 | 273 |
raise faults.OverLimit("Maximum number of personalities exceeded") |
276 | 274 |
|
277 |
for p in personality: |
|
278 |
# Verify that personalities are well-formed |
|
279 |
try: |
|
280 |
assert isinstance(p, dict) |
|
281 |
keys = set(p.keys()) |
|
282 |
allowed = set(['contents', 'group', 'mode', 'owner', 'path']) |
|
283 |
assert keys.issubset(allowed) |
|
284 |
contents = p['contents'] |
|
285 |
if len(contents) > settings.MAX_PERSONALITY_SIZE: |
|
286 |
# No need to decode if contents already exceed limit |
|
287 |
raise faults.OverLimit("Maximum size of personality exceeded") |
|
288 |
if len(b64decode(contents)) > settings.MAX_PERSONALITY_SIZE: |
|
289 |
raise faults.OverLimit("Maximum size of personality exceeded") |
|
290 |
except AssertionError: |
|
291 |
raise faults.BadRequest("Malformed personality in request") |
|
292 |
|
|
293 |
image = {} |
|
294 |
img = util.get_image(image_id, request.user_uniq) |
|
295 |
properties = img.get('properties', {}) |
|
296 |
image['backend_id'] = img['location'] |
|
297 |
image['format'] = img['disk_format'] |
|
298 |
image['metadata'] = dict((key.upper(), val) \ |
|
299 |
for key, val in properties.items()) |
|
300 |
|
|
301 |
# Ensure that request if for active flavor |
|
302 |
flavor = util.get_flavor(flavor_id, include_deleted=False) |
|
275 |
util.verify_personality(personality) |
|
276 |
image = util.get_image_dict(image_id, user_id) |
|
277 |
flavor = util.get_flavor(flavor_id) |
|
303 | 278 |
password = util.random_password() |
304 | 279 |
|
305 |
count = VirtualMachine.objects.filter(userid=request.user_uniq,
|
|
280 |
count = VirtualMachine.objects.filter(userid=user_id,
|
|
306 | 281 |
deleted=False).count() |
307 | 282 |
|
308 | 283 |
# get user limit |
309 | 284 |
vms_limit_for_user = \ |
310 |
settings.VMS_USER_QUOTA.get(request.user_uniq,
|
|
285 |
settings.VMS_USER_QUOTA.get(user_id,
|
|
311 | 286 |
settings.MAX_VMS_PER_USER) |
312 | 287 |
|
313 | 288 |
if count >= vms_limit_for_user: |
... | ... | |
326 | 301 |
transaction.commit() |
327 | 302 |
|
328 | 303 |
try: |
329 |
if settings.PUBLIC_USE_POOL: |
|
330 |
(network, address) = util.allocate_public_address(backend) |
|
331 |
if address is None: |
|
332 |
log.error("Public networks of backend %s are full", backend) |
|
333 |
msg = "Failed to allocate public IP for new VM" |
|
334 |
raise faults.ServiceUnavailable(msg) |
|
335 |
nic = {'ip': address, 'network': network.backend_id} |
|
336 |
else: |
|
337 |
network = choice(list(util.backend_public_networks(backend))) |
|
338 |
nic = {'ip': 'pool', 'network': network.backend_id} |
|
304 |
(network, address) = util.get_public_ip(backend) |
|
305 |
nic = {'ip': address, 'network': network.backend_id} |
|
339 | 306 |
except: |
340 | 307 |
transaction.rollback() |
341 |
raise |
|
342 | 308 |
else: |
343 | 309 |
transaction.commit() |
344 | 310 |
|
... | ... | |
348 | 314 |
vm = VirtualMachine.objects.create( |
349 | 315 |
name=name, |
350 | 316 |
backend=backend, |
351 |
userid=request.user_uniq,
|
|
317 |
userid=user_id,
|
|
352 | 318 |
imageid=image_id, |
353 | 319 |
flavor=flavor, |
354 | 320 |
action="CREATE") |
355 | 321 |
|
356 | 322 |
try: |
357 |
jobID = create_instance(vm, nic, flavor, image, password, personality) |
|
323 |
jobID = create_instance(vm, nic, flavor, image, password, |
|
324 |
personality) |
|
358 | 325 |
except GanetiApiError: |
359 | 326 |
vm.delete() |
360 | 327 |
raise |
361 | 328 |
|
362 | 329 |
log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s", |
363 |
request.user_uniq, vm, nic, backend, str(jobID))
|
|
330 |
user_id, vm, nic, backend, str(jobID))
|
|
364 | 331 |
|
365 | 332 |
vm.backendjobid = jobID |
366 | 333 |
vm.save() |
b/snf-cyclades-app/synnefo/api/util.py | ||
---|---|---|
33 | 33 |
|
34 | 34 |
import datetime |
35 | 35 |
|
36 |
from base64 import b64encode |
|
36 |
from base64 import b64encode, b64decode
|
|
37 | 37 |
from datetime import timedelta, tzinfo |
38 | 38 |
from functools import wraps |
39 | 39 |
from hashlib import sha256 |
... | ... | |
57 | 57 |
|
58 | 58 |
from synnefo.api.faults import (Fault, BadRequest, BuildInProgress, |
59 | 59 |
ItemNotFound, ServiceUnavailable, Unauthorized, |
60 |
BadMediaType, Forbidden) |
|
60 |
BadMediaType, Forbidden, OverLimit)
|
|
61 | 61 |
from synnefo.db.models import (Flavor, VirtualMachine, VirtualMachineMetadata, |
62 | 62 |
Network, BackendNetwork, NetworkInterface, |
63 | 63 |
BridgePoolTable, MacPrefixPoolTable) |
... | ... | |
200 | 200 |
backend.close() |
201 | 201 |
|
202 | 202 |
|
203 |
def get_image_dict(image_id, user_id): |
|
204 |
image = {} |
|
205 |
img = get_image(image_id, user_id) |
|
206 |
properties = img.get('properties', {}) |
|
207 |
image['backend_id'] = img['location'] |
|
208 |
image['format'] = img['disk_format'] |
|
209 |
image['metadata'] = dict((key.upper(), val) \ |
|
210 |
for key, val in properties.items()) |
|
211 |
return image |
|
212 |
|
|
213 |
|
|
203 | 214 |
def get_flavor(flavor_id, include_deleted=False): |
204 | 215 |
"""Return a Flavor instance or raise ItemNotFound.""" |
205 | 216 |
|
... | ... | |
242 | 253 |
return (None, None) |
243 | 254 |
|
244 | 255 |
|
256 |
def get_public_ip(backend): |
|
257 |
"""Reserve an IP from a public network. |
|
258 |
|
|
259 |
This method should run inside a transaction. |
|
260 |
|
|
261 |
""" |
|
262 |
address = None |
|
263 |
if settings.PUBLIC_ROUTED_USE_POOL: |
|
264 |
(network, address) = allocate_public_address(backend) |
|
265 |
else: |
|
266 |
for net in list(backend_public_networks(backend)): |
|
267 |
pool = net.get_pool() |
|
268 |
if not pool.empty(): |
|
269 |
address = 'pool' |
|
270 |
network = net |
|
271 |
break |
|
272 |
if address is None: |
|
273 |
log.error("Public networks of backend %s are full", backend) |
|
274 |
raise OverLimit("Can not allocate IP for new machine." |
|
275 |
" Public networks are full.") |
|
276 |
return (network, address) |
|
277 |
|
|
278 |
|
|
245 | 279 |
def backend_public_networks(backend): |
246 | 280 |
"""Return available public networks of the backend. |
247 | 281 |
|
... | ... | |
445 | 479 |
raise BadRequest('Unknown network type') |
446 | 480 |
|
447 | 481 |
return link, mac_prefix |
482 |
|
|
483 |
|
|
484 |
def verify_personality(personality): |
|
485 |
for p in personality: |
|
486 |
# Verify that personalities are well-formed |
|
487 |
try: |
|
488 |
assert isinstance(p, dict) |
|
489 |
keys = set(p.keys()) |
|
490 |
allowed = set(['contents', 'group', 'mode', 'owner', 'path']) |
|
491 |
assert keys.issubset(allowed) |
|
492 |
contents = p['contents'] |
|
493 |
if len(contents) > settings.MAX_PERSONALITY_SIZE: |
|
494 |
# No need to decode if contents already exceed limit |
|
495 |
raise OverLimit("Maximum size of personality exceeded") |
|
496 |
if len(b64decode(contents)) > settings.MAX_PERSONALITY_SIZE: |
|
497 |
raise OverLimit("Maximum size of personality exceeded") |
|
498 |
except AssertionError: |
|
499 |
raise BadRequest("Malformed personality in request") |
Also available in: Unified diff