Commit a2daae31 authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Mock vm removal if creation fails

In case creating a VM fails before the OP_INSTANCE_CREATE job is
enqueued in Ganeti, the VM and its NICs must be deleted, and the
resources must be released from Quotaholder, which is exactly what is
done when a VM is deleted from Ganeti. Instead of duplicating the code,
this commit uses the same function that is used by snf-dispatcher, by
mocking an successfully OP_INSTANCE_REMOVE.
parent baae8e7f
......@@ -31,6 +31,7 @@
# interpreted as representing official policies, either expressed
# or implied, of GRNET S.A.
import datetime
from django import dispatch
from django.conf import settings
from django.conf.urls.defaults import patterns
......@@ -45,7 +46,8 @@ from synnefo.api import util
from synnefo.api.actions import server_actions
from synnefo.db.models import (VirtualMachine, VirtualMachineMetadata,
from synnefo.logic.backend import create_instance, delete_instance
from synnefo.logic.backend import (create_instance, delete_instance,
from synnefo.logic.utils import get_rsapi_state
from synnefo.logic.backend_allocator import BackendAllocator
from synnefo import quotas
......@@ -404,16 +406,14 @@ def do_create_server(userid, name, password, flavor, image, metadata={},
userid, vm, nic, backend, str(jobID))
# If an exception is raised, then the user will never get the VM id.
# So, the VM must be marked as 'deleted'. We do not delete the VM row
# from DB, because the job may have been enqueued to Ganeti. We must
# also release the VM resources.
if not vm.deleted: # just an extra check for reconciliation...
vm.deleted = True
vm.operstate = "ERROR"
vm.backendlogmsg = "Error while enqueuing job to Ganeti."
# The following call performs commit.
quotas.issue_and_accept_commission(vm, delete=True)
# In order to delete it from DB and release it's resources, we
# mock a successful OP_INSTANCE_REMOVE job.
logmsg="Reconciled eventd: VM creation failed.")
return vm
......@@ -40,6 +40,7 @@ from synnefo.logic.utils import get_rsapi_state
from synnefo.cyclades_settings import cyclades_services
from import get_service_path
from synnefo.lib import join_urls
from synnefo.logic.rapi import GanetiApiError
from mock import patch
......@@ -303,6 +304,45 @@ class ServerCreateAPITest(ComputeAPITest):
json.dumps(request), 'json')
def test_create_server_error(self, mrapi, mimage):
"""Test if the create server call returns the expected response
if a valid request has been speficied."""
mimage.return_value = {'location': 'pithos://foo',
'checksum': '1234',
"id": 1,
"name": "test_image",
'disk_format': 'diskdump'}
mrapi().CreateInstance.side_effect = GanetiApiError("..ganeti is down")
flavor = mfactory.FlavorFactory()
# Create public network and backend
network = mfactory.NetworkFactory(public=True)
backend = mfactory.BackendFactory()
mfactory.BackendNetworkFactory(network=network, backend=backend)
request = {
"server": {
"name": "new-server-test",
"userid": "test_user",
"imageRef": 1,
"metadata": {
"My Server Name": "Apache1"
"personality": []
with mocked_quotaholder():
response = self.mypost('servers', 'test_user',
json.dumps(request), 'json')
self.assertEqual(response.status_code, 500)
vm = VirtualMachine.objects.get()
# The VM has been deleted
# and it has no nics
self.assertEqual(len(vm.nics.all()), 0)
self.assertEqual(vm.backendjobid, 0)
class ServerDestroyAPITest(ComputeAPITest):
......@@ -140,8 +140,12 @@ serial = 0
def mocked_quotaholder(success=True):
with patch("synnefo.quotas.Quotaholder.get") as astakos:
global serial
serial += 1
astakos.return_value.issue_one_commission.return_value = serial
serial += 10
def foo(*args, **kwargs):
return (len(astakos.return_value.issue_one_commission.mock_calls) +
astakos.return_value.issue_one_commission.side_effect = foo
astakos.return_value.resolve_commissions.return_value = {"failed": []}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment