Revision 1af851fd
b/snf-cyclades-app/synnefo/api/actions.py | ||
---|---|---|
41 | 41 |
from django.utils import simplejson as json |
42 | 42 |
|
43 | 43 |
from snf_django.lib.api import faults |
44 |
from synnefo.api import util |
|
44 | 45 |
from synnefo.api.util import (random_password, get_vm, get_nic_from_index, |
45 | 46 |
get_network_free_address) |
46 | 47 |
from synnefo.db.models import NetworkInterface |
... | ... | |
168 | 169 |
# serverCapacityUnavailable (503), |
169 | 170 |
# overLimit (413), |
170 | 171 |
# resizeNotAllowed (403) |
172 |
log.debug("resize %s: %s", vm, args) |
|
173 |
flavorRef = args.get("flavorRef", None) |
|
174 |
if flavorRef is None: |
|
175 |
raise faults.BadRequest("Missing 'flavorRef' attribute") |
|
176 |
|
|
177 |
new_flavor = util.get_flavor(flavor_id=flavorRef, include_deleted=False) |
|
178 |
old_flavor = vm.flavor |
|
179 |
# User requested the same flavor |
|
180 |
if old_flavor.id == new_flavor.id: |
|
181 |
return HttpResponse(status=200) |
|
182 |
# Check that resize can be performed |
|
183 |
if old_flavor.disk != new_flavor.disk: |
|
184 |
raise faults.BadRequest("Can not resize instance disk") |
|
185 |
if old_flavor.disk_template != new_flavor.disk_template: |
|
186 |
raise faults.BadRequest("Can not change instance disk template") |
|
187 |
|
|
188 |
if not vm_is_stopped(vm): |
|
189 |
raise faults.BadRequest("Can not resize running instance") |
|
190 |
|
|
191 |
vcpus, memory = new_flavor.cpu, new_flavor.ram |
|
192 |
jobId = backend.resize_instance(vm, vcpus=vcpus, memory=memory) |
|
193 |
|
|
194 |
log.info("User '%s' resized VM from flavor '%s' to '%s', job %s", |
|
195 |
request.user_uniq, old_flavor, new_flavor, jobId) |
|
196 |
|
|
197 |
# Save operstate now, since you don't want any other action when an |
|
198 |
# an instance is resizing |
|
199 |
vm.operstate = "RESIZE" |
|
200 |
util.start_action(vm, "RESIZE", jobId) |
|
201 |
|
|
202 |
vm.save() |
|
203 |
return HttpResponse(status=202) |
|
171 | 204 |
|
172 |
raise faults.NotImplemented('Resize not supported.') |
|
205 |
|
|
206 |
def vm_is_stopped(vm): |
|
207 |
"""Check if a VirtualMachine is currently stopped. |
|
208 |
|
|
209 |
A server is stopped if it's operstate is 'STOPPED'. Also, you must check |
|
210 |
that no other job is currently running, because this job may start the |
|
211 |
instance |
|
212 |
""" |
|
213 |
return vm.operstate == "STOPPED" and vm.backendjobstatus == "success" |
|
173 | 214 |
|
174 | 215 |
|
175 | 216 |
@server_action('confirmResize') |
b/snf-cyclades-app/synnefo/api/test/servers.py | ||
---|---|---|
443 | 443 |
self.assertBadRequest(response) |
444 | 444 |
self.assertFalse(mrapi.mock_calls) |
445 | 445 |
|
446 |
def test_resize_vm(self, mrapi, mimage): |
|
447 |
flavor = mfactory.FlavorFactory(cpu=1, ram=1024) |
|
448 |
# Check building VM |
|
449 |
vm = self.get_vm(flavor=flavor, operstate="BUILD") |
|
450 |
request = {'resize': {'flavorRef': flavor.id}} |
|
451 |
response = self.post('/api/v1.1/servers/%d/action' % vm.id, |
|
452 |
vm.userid, json.dumps(request), 'json') |
|
453 |
self.assertFault(response, 409, "buildInProgress") |
|
454 |
# Check same Flavor |
|
455 |
vm = self.get_vm(flavor=flavor, operstate="STOPPED") |
|
456 |
request = {'resize': {'flavorRef': flavor.id}} |
|
457 |
response = self.post('/api/v1.1/servers/%d/action' % vm.id, |
|
458 |
vm.userid, json.dumps(request), 'json') |
|
459 |
self.assertSuccess(response) |
|
460 |
# Check flavor with different disk |
|
461 |
flavor2 = mfactory.FlavorFactory(disk=1024) |
|
462 |
flavor3 = mfactory.FlavorFactory(disk=2048) |
|
463 |
vm = self.get_vm(flavor=flavor2, operstate="STOPPED") |
|
464 |
request = {'resize': {'flavorRef': flavor3.id}} |
|
465 |
response = self.post('/api/v1.1/servers/%d/action' % vm.id, |
|
466 |
vm.userid, json.dumps(request), 'json') |
|
467 |
self.assertBadRequest(response) |
|
468 |
flavor2 = mfactory.FlavorFactory(disk_template="foo") |
|
469 |
flavor3 = mfactory.FlavorFactory(disk_template="baz") |
|
470 |
vm = self.get_vm(flavor=flavor2, operstate="STOPPED") |
|
471 |
request = {'resize': {'flavorRef': flavor3.id}} |
|
472 |
response = self.post('/api/v1.1/servers/%d/action' % vm.id, |
|
473 |
vm.userid, json.dumps(request), 'json') |
|
474 |
self.assertBadRequest(response) |
|
475 |
# Check success |
|
476 |
vm = self.get_vm(flavor=flavor, operstate="STOPPED") |
|
477 |
flavor4 = mfactory.FlavorFactory(disk_template=flavor.disk_template, |
|
478 |
disk=flavor.disk, |
|
479 |
cpu=4, ram=2048) |
|
480 |
request = {'resize': {'flavorRef': flavor4.id}} |
|
481 |
mrapi().ModifyInstance.return_value = 42 |
|
482 |
response = self.post('/api/v1.1/servers/%d/action' % vm.id, |
|
483 |
vm.userid, json.dumps(request), 'json') |
|
484 |
self.assertEqual(response.status_code, 202) |
|
485 |
vm = VirtualMachine.objects.get(id=vm.id) |
|
486 |
self.assertEqual(vm.backendjobid, 42) |
|
487 |
name, args, kwargs = mrapi().ModifyInstance.mock_calls[0] |
|
488 |
self.assertEqual(kwargs["beparams"]["vcpus"], 4) |
|
489 |
self.assertEqual(kwargs["beparams"]["minmem"], 2048) |
|
490 |
self.assertEqual(kwargs["beparams"]["maxmem"], 2048) |
|
491 |
|
|
492 |
def get_vm(self, flavor, operstate): |
|
493 |
vm = mfactory.VirtualMachineFactory(flavor=flavor) |
|
494 |
vm.operstate = operstate |
|
495 |
vm.backendjobstatus = "success" |
|
496 |
vm.save() |
|
497 |
return vm |
|
498 |
|
|
446 | 499 |
|
447 | 500 |
class ServerVNCConsole(ComputeAPITest): |
448 | 501 |
def test_not_active_server(self): |
b/snf-cyclades-app/synnefo/db/models.py | ||
---|---|---|
240 | 240 |
('STOP', 'Shutdown VM'), |
241 | 241 |
('SUSPEND', 'Admin Suspend VM'), |
242 | 242 |
('REBOOT', 'Reboot VM'), |
243 |
('DESTROY', 'Destroy VM') |
|
243 |
('DESTROY', 'Destroy VM'), |
|
244 |
('RESIZE', 'Resize a VM'), |
|
244 | 245 |
) |
245 | 246 |
|
246 | 247 |
# The internal operating state of a VM |
... | ... | |
249 | 250 |
('ERROR', 'Creation failed'), |
250 | 251 |
('STOPPED', 'Stopped'), |
251 | 252 |
('STARTED', 'Started'), |
252 |
('DESTROYED', 'Destroyed') |
|
253 |
('DESTROYED', 'Destroyed'), |
|
254 |
('RESIZE', 'Resizing') |
|
253 | 255 |
) |
254 | 256 |
|
255 | 257 |
# The list of possible operations on the backend |
... | ... | |
304 | 306 |
"ERROR": "ERROR", |
305 | 307 |
"STOPPED": "STOPPED", |
306 | 308 |
"STARTED": "ACTIVE", |
307 |
"DESTROYED": "DELETED" |
|
309 |
"DESTROYED": "DELETED", |
|
310 |
"RESIZE": "RESIZE" |
|
308 | 311 |
} |
309 | 312 |
|
310 | 313 |
name = models.CharField('Virtual Machine Name', max_length=255) |
Also available in: Unified diff