Revision bcd80cd9
b/snf-cyclades-app/synnefo/api/management/commands/server-create.py | ||
---|---|---|
31 | 31 |
# interpreted as representing official policies, either expressed |
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 |
import json |
|
35 | 34 |
from optparse import make_option |
36 | 35 |
|
37 |
from django.db import transaction |
|
38 | 36 |
from django.core.management.base import BaseCommand, CommandError |
39 | 37 |
from synnefo.management import common |
40 | 38 |
|
41 |
from synnefo.db.models import VirtualMachine |
|
42 |
from synnefo.logic.backend import create_instance |
|
43 |
from synnefo.logic.backend_allocator import BackendAllocator |
|
44 | 39 |
from synnefo.api import util |
45 |
from synnefo.api.servers import server_created |
|
46 |
from synnefo import quotas |
|
40 |
from synnefo.api.servers import do_create_server |
|
47 | 41 |
|
48 | 42 |
HELP_MSG = """ |
49 | 43 |
|
... | ... | |
77 | 71 |
help="Password for the new server") |
78 | 72 |
) |
79 | 73 |
|
80 |
@transaction.commit_manually |
|
81 | 74 |
def handle(self, *args, **options): |
82 | 75 |
if args: |
83 | 76 |
raise CommandError("Command doesn't accept any arguments") |
... | ... | |
97 | 90 |
raise CommandError("password is mandatory") |
98 | 91 |
if not flavor_id: |
99 | 92 |
raise CommandError("flavor-id is mandatory") |
100 |
|
|
101 |
# Get Flavor |
|
102 |
if flavor_id: |
|
103 |
flavor = common.get_flavor(flavor_id) |
|
104 |
|
|
105 |
if image_id: |
|
106 |
img = common.get_image(image_id, user_id) |
|
107 |
|
|
108 |
properties = img.get('properties', {}) |
|
109 |
image = {} |
|
110 |
image['backend_id'] = img['location'] |
|
111 |
image['format'] = img['disk_format'] |
|
112 |
image['metadata'] = dict((key.upper(), val) |
|
113 |
for key, val in properties.items()) |
|
114 |
else: |
|
93 |
if not image_id: |
|
115 | 94 |
raise CommandError("image-id is mandatory") |
116 | 95 |
|
117 |
# Fix flavor for archipelago |
|
118 |
disk_template, provider = util.get_flavor_provider(flavor) |
|
119 |
if provider: |
|
120 |
flavor.disk_template = disk_template |
|
121 |
flavor.disk_provider = provider |
|
122 |
flavor.disk_origin = None |
|
123 |
if provider == 'vlmc': |
|
124 |
flavor.disk_origin = image['checksum'] |
|
125 |
image['backend_id'] = 'null' |
|
126 |
else: |
|
127 |
flavor.disk_provider = None |
|
128 |
|
|
129 |
try: |
|
130 |
# Get Backend |
|
131 |
if backend_id: |
|
132 |
backend = common.get_backend(backend_id) |
|
133 |
else: |
|
134 |
ballocator = BackendAllocator() |
|
135 |
backend = ballocator.allocate(user_id, flavor) |
|
136 |
if not backend: |
|
137 |
raise CommandError("Can not allocate VM") |
|
138 |
|
|
139 |
# Get Public address |
|
140 |
(network, address) = util.allocate_public_address(backend) |
|
141 |
if address is None: |
|
142 |
raise CommandError("Can not allocate a public address." |
|
143 |
" No available public network.") |
|
144 |
nic = {'ip': address, 'network': network.backend_id} |
|
145 |
|
|
146 |
# Create the VM in DB |
|
147 |
vm = VirtualMachine.objects.create(name=name, |
|
148 |
backend=backend, |
|
149 |
userid=user_id, |
|
150 |
imageid=image_id, |
|
151 |
flavor=flavor) |
|
152 |
# dispatch server created signal |
|
153 |
server_created.send(sender=vm, created_vm_params={ |
|
154 |
'img_id': image['backend_id'], |
|
155 |
'img_passwd': password, |
|
156 |
'img_format': str(image['format']), |
|
157 |
'img_personality': '[]', |
|
158 |
'img_properties': json.dumps(image['metadata']), |
|
159 |
}) |
|
160 |
|
|
161 |
quotas.issue_and_accept_commission(vm) |
|
162 |
except: |
|
163 |
transaction.rollback() |
|
164 |
raise |
|
165 |
else: |
|
166 |
transaction.commit() |
|
167 |
|
|
168 |
try: |
|
169 |
# Create the instance in Backend |
|
170 |
jobID = create_instance(vm, nic, flavor, image) |
|
96 |
flavor = common.get_flavor(flavor_id) |
|
97 |
image = common.get_image(image_id, user_id) |
|
98 |
if backend_id: |
|
99 |
backend = common.get_backend(backend_id) |
|
171 | 100 |
|
172 |
vm.backendjobid = jobID |
|
173 |
vm.save() |
|
174 |
self.stdout.write("Creating VM %s with IP %s in Backend %s." |
|
175 |
" JobID: %s\n" % (vm, address, backend, jobID)) |
|
176 |
except: |
|
177 |
transaction.rollback() |
|
178 |
raise |
|
179 |
else: |
|
180 |
transaction.commit() |
|
101 |
do_create_server(user_id, name, password, flavor, image, |
|
102 |
backend=backend) |
b/snf-cyclades-app/synnefo/api/servers.py | ||
---|---|---|
245 | 245 |
|
246 | 246 |
|
247 | 247 |
@api.api_method(http_method='POST', user_required=True, logger=log) |
248 |
# Use manual transactions. Backend and IP pool allocations need exclusive |
|
249 |
# access (SELECT..FOR UPDATE). Running create_server with commit_on_success |
|
250 |
# would result in backends and public networks to be locked until the job is |
|
251 |
# sent to the Ganeti backend. |
|
252 |
@transaction.commit_manually |
|
253 | 248 |
def create_server(request): |
254 | 249 |
# Normal Response Code: 202 |
255 | 250 |
# Error Response Codes: computeFault (400, 500), |
... | ... | |
260 | 255 |
# badRequest (400), |
261 | 256 |
# serverCapacityUnavailable (503), |
262 | 257 |
# overLimit (413) |
258 |
req = utils.get_request_dict(request) |
|
259 |
log.info('create_server %s', req) |
|
260 |
user_id = request.user_uniq |
|
261 |
|
|
263 | 262 |
try: |
264 |
req = utils.get_request_dict(request) |
|
265 |
log.info('create_server %s', req) |
|
266 |
user_id = request.user_uniq |
|
263 |
server = req['server'] |
|
264 |
name = server['name'] |
|
265 |
metadata = server.get('metadata', {}) |
|
266 |
assert isinstance(metadata, dict) |
|
267 |
image_id = server['imageRef'] |
|
268 |
flavor_id = server['flavorRef'] |
|
269 |
personality = server.get('personality', []) |
|
270 |
assert isinstance(personality, list) |
|
271 |
except (KeyError, AssertionError): |
|
272 |
raise faults.BadRequest("Malformed request") |
|
273 |
|
|
274 |
# Verify that personalities are well-formed |
|
275 |
util.verify_personality(personality) |
|
276 |
# Get image information |
|
277 |
image = util.get_image_dict(image_id, user_id) |
|
278 |
# Get flavor (ensure it is active) |
|
279 |
flavor = util.get_flavor(flavor_id, include_deleted=False) |
|
280 |
# Generate password |
|
281 |
password = util.random_password() |
|
282 |
|
|
283 |
vm = do_create_server(user_id, name, password, flavor, image, |
|
284 |
metadata=metadata, personality=personality) |
|
285 |
|
|
286 |
server = vm_to_dict(vm, detail=True) |
|
287 |
server['status'] = 'BUILD' |
|
288 |
server['adminPass'] = password |
|
289 |
|
|
290 |
response = render_server(request, server, status=202) |
|
291 |
|
|
292 |
return response |
|
293 |
|
|
267 | 294 |
|
295 |
@transaction.commit_manually |
|
296 |
def do_create_server(userid, name, password, flavor, image, metadata={}, |
|
297 |
personality=[], network=None, backend=None): |
|
298 |
if backend is None: |
|
299 |
# Allocate backend to host the server. Commit after allocation to |
|
300 |
# release the locks hold by the backend allocator. |
|
268 | 301 |
try: |
269 |
server = req['server'] |
|
270 |
name = server['name'] |
|
271 |
metadata = server.get('metadata', {}) |
|
272 |
assert isinstance(metadata, dict) |
|
273 |
image_id = server['imageRef'] |
|
274 |
flavor_id = server['flavorRef'] |
|
275 |
personality = server.get('personality', []) |
|
276 |
assert isinstance(personality, list) |
|
277 |
except (KeyError, AssertionError): |
|
278 |
raise faults.BadRequest("Malformed request") |
|
279 |
|
|
280 |
# Verify that personalities are well-formed |
|
281 |
util.verify_personality(personality) |
|
282 |
# Get image information |
|
283 |
image = util.get_image_dict(image_id, user_id) |
|
284 |
# Get flavor (ensure it is active) |
|
285 |
flavor = util.get_flavor(flavor_id, include_deleted=False) |
|
286 |
# Allocate VM to backend |
|
287 |
backend_allocator = BackendAllocator() |
|
288 |
backend = backend_allocator.allocate(request.user_uniq, flavor) |
|
289 |
|
|
290 |
if backend is None: |
|
291 |
log.error("No available backends for VM with flavor %s", flavor) |
|
292 |
raise faults.ServiceUnavailable("No available backends") |
|
293 |
except: |
|
294 |
transaction.rollback() |
|
295 |
raise |
|
296 |
else: |
|
297 |
transaction.commit() |
|
302 |
backend_allocator = BackendAllocator() |
|
303 |
backend = backend_allocator.allocate(userid, flavor) |
|
304 |
if backend is None: |
|
305 |
log.error("No available backend for VM with flavor %s", flavor) |
|
306 |
raise faults.ServiceUnavailable("No available backends") |
|
307 |
except: |
|
308 |
transaction.rollback() |
|
309 |
raise |
|
310 |
else: |
|
311 |
transaction.commit() |
|
298 | 312 |
|
299 | 313 |
# Fix flavor for archipelago |
300 |
password = util.random_password() |
|
301 | 314 |
disk_template, provider = util.get_flavor_provider(flavor) |
302 | 315 |
if provider: |
303 | 316 |
flavor.disk_template = disk_template |
... | ... | |
310 | 323 |
flavor.disk_provider = None |
311 | 324 |
|
312 | 325 |
try: |
313 |
# Allocate IP from public network |
|
314 |
(network, address) = util.get_public_ip(backend) |
|
315 |
nic = {'ip': address, 'network': network.backend_id} |
|
326 |
if network is None: |
|
327 |
# Allocate IP from public network |
|
328 |
(network, address) = util.get_public_ip(backend) |
|
329 |
nic = {'ip': address, 'network': network.backend_id} |
|
330 |
else: |
|
331 |
address = util.get_network_free_address(network) |
|
316 | 332 |
|
317 | 333 |
# We must save the VM instance now, so that it gets a valid |
318 | 334 |
# vm.backend_vm_id. |
319 | 335 |
vm = VirtualMachine.objects.create( |
320 | 336 |
name=name, |
321 | 337 |
backend=backend, |
322 |
userid=user_id,
|
|
323 |
imageid=image_id,
|
|
338 |
userid=userid, |
|
339 |
imageid=image["id"],
|
|
324 | 340 |
flavor=flavor, |
325 | 341 |
action="CREATE") |
326 | 342 |
|
... | ... | |
364 | 380 |
vm.save() |
365 | 381 |
transaction.commit() |
366 | 382 |
log.info("User %s created VM %s, NIC %s, Backend %s, JobID %s", |
367 |
user_id, vm, nic, backend, str(jobID))
|
|
383 |
userid, vm, nic, backend, str(jobID)) |
|
368 | 384 |
except GanetiApiError as e: |
369 | 385 |
log.exception("Can not communicate to backend %s: %s.", |
370 | 386 |
backend, e) |
... | ... | |
380 | 396 |
transaction.rollback() |
381 | 397 |
raise |
382 | 398 |
|
383 |
server = vm_to_dict(vm, detail=True) |
|
384 |
server['status'] = 'BUILD' |
|
385 |
server['adminPass'] = password |
|
386 |
|
|
387 |
response = render_server(request, server, status=202) |
|
388 |
|
|
389 |
return response |
|
399 |
return vm |
|
390 | 400 |
|
391 | 401 |
|
392 | 402 |
@api.api_method(http_method='GET', user_required=True, logger=log) |
b/snf-cyclades-app/synnefo/api/test/servers.py | ||
---|---|---|
172 | 172 |
if a valid request has been speficied.""" |
173 | 173 |
mimage.return_value = {'location': 'pithos://foo', |
174 | 174 |
'checksum': '1234', |
175 |
"id": 1, |
|
176 |
"name": "test_image", |
|
175 | 177 |
'disk_format': 'diskdump'} |
176 | 178 |
mrapi().CreateInstance.return_value = 12 |
177 | 179 |
flavor = mfactory.FlavorFactory() |
b/snf-cyclades-app/synnefo/api/util.py | ||
---|---|---|
158 | 158 |
image = {} |
159 | 159 |
img = get_image(image_id, user_id) |
160 | 160 |
properties = img.get('properties', {}) |
161 |
image["id"] = img["id"] |
|
162 |
image["name"] = img["name"] |
|
161 | 163 |
image['backend_id'] = img['location'] |
162 | 164 |
image['format'] = img['disk_format'] |
163 | 165 |
image['metadata'] = dict((key.upper(), val) |
b/snf-cyclades-app/synnefo/management/common.py | ||
---|---|---|
35 | 35 |
from synnefo.db.models import Backend, VirtualMachine, Network, Flavor |
36 | 36 |
|
37 | 37 |
from snf_django.lib.api import faults |
38 |
from synnefo.api.util import get_image as backend_get_image |
|
39 |
from synnefo.api.util import validate_network_params |
|
38 |
from synnefo.api import util |
|
40 | 39 |
from synnefo.logic.rapi import GanetiApiError, GanetiRapiClient |
41 | 40 |
from synnefo.logic.utils import (id_from_instance_name, |
42 | 41 |
id_from_network_name) |
... | ... | |
59 | 58 |
gateway6 = options['gateway6'] |
60 | 59 |
|
61 | 60 |
try: |
62 |
validate_network_params(subnet, gateway) |
|
61 |
util.validate_network_params(subnet, gateway)
|
|
63 | 62 |
except (faults.BadRequest, faults.OverLimit) as e: |
64 | 63 |
raise CommandError(e) |
65 | 64 |
|
... | ... | |
81 | 80 |
def get_image(image_id, user_id): |
82 | 81 |
if image_id: |
83 | 82 |
try: |
84 |
return backend_get_image(image_id, user_id)
|
|
83 |
return util.get_image_dict(image_id, user_id)
|
|
85 | 84 |
except faults.ItemNotFound: |
86 | 85 |
raise CommandError("Image with ID %s not found." |
87 | 86 |
" Use snf-manage image-list to find" |
Also available in: Unified diff