Commit f807cbfb authored by Giorgos Korfiatis's avatar Giorgos Korfiatis Committed by Christos Stavrakakis
Browse files

cyclades: Detach serial from resource when resolved

parent 08cdeda0
......@@ -37,7 +37,7 @@ from synnefo.cyclades_settings import cyclades_services
from synnefo.lib.services import get_service_path
from synnefo.lib import join_urls
import synnefo.db.models_factory as dbmf
from synnefo.db.models import Network
from synnefo.db.models import Network, QuotaHolderSerial
from django.conf import settings
NETWORK_URL = get_service_path(cyclades_services, 'network',
......@@ -92,10 +92,10 @@ class NetworkTest(BaseAPITest):
self.assertEqual(commission_resources, {"cyclades.network.private": 1})
name, args, kwargs =\
self.mocked_quotaholder.resolve_commissions.mock_calls[0]
serial = Network.objects.get().serial.serial
serial = QuotaHolderSerial.objects.order_by("-serial")[0]
accepted_serials = args[0]
rejected_serials = args[1]
self.assertEqual(accepted_serials, [serial])
self.assertEqual(accepted_serials, [serial.serial])
self.assertEqual(rejected_serials, [])
# test no name
......
......@@ -89,12 +89,11 @@ def handle_vm_quotas(vm, job_id, job_opcode, job_status, job_fields):
# failed server
serial = vm.serial
if job_status == rapi.JOB_STATUS_SUCCESS:
quotas.accept_serial(serial)
quotas.accept_resource_serial(vm)
elif job_status in [rapi.JOB_STATUS_ERROR, rapi.JOB_STATUS_CANCELED]:
log.debug("Job %s failed. Rejecting related serial %s", job_id,
serial)
quotas.reject_serial(serial)
vm.serial = None
quotas.reject_resource_serial(vm)
elif job_status == rapi.JOB_STATUS_SUCCESS:
commission_info = quotas.get_commission_info(resource=vm,
action=action,
......@@ -103,22 +102,18 @@ def handle_vm_quotas(vm, job_id, job_opcode, job_status, job_fields):
# Commission for this change has not been issued, or the issued
# commission was unaware of the current change. Reject all previous
# commissions and create a new one in forced mode!
log.debug("Expected job was %s. Processing job %s.",
vm.task_job_id, job_id)
log.debug("Expected job was %s. Processing job %s. "
"Attached serial %s",
vm.task_job_id, job_id, vm.serial)
reason = ("client: dispatcher, resource: %s, ganeti_job: %s"
% (vm, job_id))
quotas.handle_resource_commission(vm, action,
action_fields=job_fields,
commission_name=reason,
force=True,
auto_accept=True)
log.debug("Issued new commission: %s", vm.serial)
# NOTE: Since we rejected the serial that was associated with the
# 'vm.task_job_id' job, we must also clear the 'vm.serial' field.
# If not, there will be no new commission for the 'vm.task_job_id'
# job!
vm.serial = None
serial = quotas.handle_resource_commission(
vm, action,
action_fields=job_fields,
commission_name=reason,
force=True,
auto_accept=True)
log.debug("Issued new commission: %s", serial)
return vm
......
......@@ -138,7 +138,7 @@ def server_command(action, action_fields=None):
log.debug("Rejecting commission: '%s', could not perform"
" action '%s': %s" % (vm.serial, action, e))
transaction.rollback()
quotas.reject_serial(vm.serial)
quotas.reject_resource_serial(vm)
transaction.commit()
raise
......@@ -147,7 +147,7 @@ def server_command(action, action_fields=None):
# commission because the VM has been stored in DB. Also, if
# communication with Ganeti fails, the job will never reach
# Ganeti, and the commission will never be resolved.
quotas.accept_serial(vm.serial)
quotas.accept_resource_serial(vm)
log.info("user: %s, vm: %s, action: %s, job_id: %s, serial: %s",
user_id, vm.id, action, job_id, vm.serial)
......
......@@ -197,7 +197,7 @@ class ServerCommandTest(TransactionTestCase):
vm.task_job_id = None
vm.save()
with mocked_quotaholder():
quotas.accept_serial(vm.serial)
quotas.accept_resource_serial(vm)
mrapi().RebootInstance.return_value = 1
with mocked_quotaholder():
servers.reboot(vm, "HARD")
......
......@@ -43,6 +43,9 @@ import logging
log = logging.getLogger(__name__)
QUOTABLE_RESOURCES = [VirtualMachine, Network, IPAddress]
DEFAULT_SOURCE = 'system'
RESOURCES = [
"cyclades.vm",
......@@ -115,30 +118,47 @@ def issue_commission(resource, action, name="", force=False, auto_accept=False,
force=force,
auto_accept=auto_accept)
if serial:
serial_info = {"serial": serial}
if auto_accept:
serial_info["pending"] = False
serial_info["accept"] = True
serial_info["resolved"] = True
return QuotaHolderSerial.objects.create(**serial_info)
else:
if not serial:
raise Exception("No serial")
serial_info = {"serial": serial}
if auto_accept:
serial_info["pending"] = False
serial_info["accept"] = True
serial_info["resolved"] = True
serial = QuotaHolderSerial.objects.create(**serial_info)
# Correlate the serial with the resource. Resolved serials are not
# attached to resources
if not auto_accept:
resource.serial = serial
resource.save()
def accept_serial(serial, strict=True):
return serial
def accept_resource_serial(resource, strict=True):
serial = resource.serial
assert serial.pending or serial.accept, "%s can't be accepted" % serial
response = resolve_commissions(accept=[serial.serial], strict=strict)
return response
log.debug("Accepting serial %s of resource %s", serial, resource)
_resolve_commissions(accept=[serial.serial], strict=strict)
resource.serial = None
resource.save()
return resource
def reject_serial(serial, strict=True):
def reject_resource_serial(resource, strict=True):
serial = resource.serial
assert serial.pending or not serial.accept, "%s can't be rejected" % serial
response = resolve_commissions(reject=[serial.serial], strict=strict)
return response
log.debug("Rejecting serial %s of resource %s", serial, resource)
_resolve_commissions(reject=[serial.serial], strict=strict)
resource.serial = None
resource.save()
return resource
def resolve_commissions(accept=None, reject=None, strict=True):
def _resolve_commissions(accept=None, reject=None, strict=True):
if accept is None:
accept = []
if reject is None:
......@@ -167,6 +187,15 @@ def resolve_commissions(accept=None, reject=None, strict=True):
return response
def reconcile_resolve_commissions(accept=None, reject=None, strict=True):
response = _resolve_commissions(accept=accept,
reject=reject,
strict=strict)
affected = response.get("accepted", []) + response.get("rejected", [])
for resource in QUOTABLE_RESOURCES:
resource.objects.filter(serial__in=affected).update(serial=None)
def resolve_pending_commissions():
"""Resolve quotaholder pending commissions.
......@@ -247,6 +276,9 @@ def issue_and_accept_commission(resource, action="BUILD", action_fields=None):
action_fields=action_fields,
commission_name=commission_reason)
if serial is None:
return
# Mark the serial as one to accept and associate it with the resource
serial.pending = False
serial.accept = True
......@@ -255,14 +287,12 @@ def issue_and_accept_commission(resource, action="BUILD", action_fields=None):
try:
# Accept the commission to quotaholder
accept_serial(serial)
accept_resource_serial(resource)
except:
# Do not crash if we can not accept commission to Quotaholder. Quotas
# have already been reserved and the resource already exists in DB.
# Just log the error
log.exception("Failed to accept commission: %s", serial)
return serial
log.exception("Failed to accept commission: %s", resource.serial)
def get_commission_info(resource, action, action_fields=None):
......@@ -342,13 +372,11 @@ def handle_resource_commission(resource, action, commission_name,
# The one who succeeds will be finally accepted, and all other will be
# rejected
force = force or (action == "DESTROY")
resolve_commission(resource.serial, force=force)
resolve_resource_commission(resource, force=force)
serial = issue_commission(resource, action, name=commission_name,
force=force, auto_accept=auto_accept,
action_fields=action_fields)
resource.serial = serial
resource.save()
return serial
......@@ -356,7 +384,8 @@ class ResolveError(Exception):
pass
def resolve_commission(serial, force=False):
def resolve_resource_commission(resource, force=False):
serial = resource.serial
if serial is None or serial.resolved:
return
if serial.pending and not force:
......@@ -364,6 +393,6 @@ def resolve_commission(serial, force=False):
raise ResolveError(m)
log.warning("Resolving pending commission: %s", serial)
if not serial.pending and serial.accept:
accept_serial(serial)
accept_resource_serial(resource)
else:
reject_serial(serial)
reject_resource_serial(resource)
......@@ -63,8 +63,9 @@ class Command(BaseCommand):
if fix and (accepted or rejected):
self.stdout.write("Fixing pending commissions..\n")
quotas.resolve_commissions(accept=accepted, reject=rejected,
strict=False)
quotas.reconcile_resolve_commissions(accept=accepted,
reject=rejected,
strict=False)
def list_to_string(l):
......
......@@ -204,7 +204,12 @@ def mocked_quotaholder(success=True):
return (len(astakos.return_value.issue_one_commission.mock_calls) +
serial)
astakos.return_value.issue_one_commission.side_effect = foo
astakos.return_value.resolve_commissions.return_value = {"failed": []}
def resolve_mock(*args, **kwargs):
return {"failed": [],
"accepted": args[0],
"rejected": args[1],
}
astakos.return_value.resolve_commissions.side_effect = resolve_mock
yield astakos.return_value
......
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