Revision 41a7fae7 snf-cyclades-app/synnefo/logic/backend.py

b/snf-cyclades-app/synnefo/logic/backend.py
1
# Copyright 2011 GRNET S.A. All rights reserved.
1
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2 2
#
3 3
# Redistribution and use in source and binary forms, with or
4 4
# without modification, are permitted provided that the following
......
55 55
_reverse_tags = dict((v.split(':')[3], k) for k, v in _firewall_tags.items())
56 56

  
57 57

  
58
def handle_vm_quotas(vm, job_id, job_opcode, job_status, job_fields):
59
    """Handle quotas for updated VirtualMachine.
60

  
61
    Update quotas for the updated VirtualMachine based on the job that run on
62
    the Ganeti backend. If a commission has been already issued for this job,
63
    then this commission is just accepted or rejected based on the job status.
64
    Otherwise, a new commission for the given change is issued, that is also in
65
    force and auto-accept mode. In this case, previous commissions are
66
    rejected, since they reflect a previous state of the VM.
67

  
68
    """
69
    if job_status not in ["success", "error", "canceled"]:
70
        return
71

  
72
    # Check successful completion of a job will trigger any quotable change in
73
    # the VM state.
74
    action = utils.get_action_from_opcode(job_opcode, job_fields)
75
    commission_info = quotas.get_commission_info(vm, action=action,
76
                                                 action_fields=job_fields)
77

  
78
    if vm.task_job_id == job_id and vm.serial is not None:
79
        # Commission for this change has already been issued. So just
80
        # accept/reject it
81
        serial = vm.serial
82
        if job_status == "success":
83
            quotas.accept_serial(serial)
84
        elif job_status in ["error", "canceled"]:
85
            log.debug("Job %s failed. Rejecting related serial %s", job_id,
86
                      serial)
87
            quotas.reject_serial(serial)
88
        vm.serial = None
89
    elif job_status == "success" and commission_info is not None:
90
        log.debug("Expected job was %s. Processing job %s. Commission for"
91
                  " this job: %s", vm.task_job_id, job_id, commission_info)
92
        # Commission for this change has not been issued, or the issued
93
        # commission was unaware of the current change. Reject all previous
94
        # commissions and create a new one in forced mode!
95
        previous_serial = vm.serial
96
        if previous_serial and not previous_serial.resolved:
97
            quotas.resolve_vm_commission(previous_serial)
98
        serial = quotas.issue_commission(user=vm.userid,
99
                                         source=quotas.DEFAULT_SOURCE,
100
                                         provisions=commission_info,
101
                                         force=True,
102
                                         auto_accept=True)
103
        # Clear VM's serial. Expected job may arrive later. However correlated
104
        # serial must not be accepted, since it reflects a previous VM state
105
        vm.serial = None
106

  
107
    return vm
108

  
109

  
58 110
@transaction.commit_on_success
59 111
def process_op_status(vm, etime, jobid, opcode, status, logmsg, nics=None,
60 112
                      beparams=None):
......
75 127
    vm.backendopcode = opcode
76 128
    vm.backendlogmsg = logmsg
77 129

  
130
    if status in ["queued", "waiting", "running"]:
131
        vm.save()
132
        return
133

  
134
    state_for_success = VirtualMachine.OPER_STATE_FROM_OPCODE.get(opcode)
78 135
    # Notifications of success change the operating state
79
    state_for_success = VirtualMachine.OPER_STATE_FROM_OPCODE.get(opcode, None)
80
    if status == 'success' and state_for_success is not None:
81
        vm.operstate = state_for_success
82

  
83
    if status == "success" and nics is not None:
84
        # Update the NICs of the VM
85
        _process_net_status(vm, etime, nics)
86

  
87
    if beparams:
88
        assert(opcode == "OP_INSTANCE_SET_PARAMS"), "'beparams' should exist"\
89
                                                    " only for SET_PARAMS"
90
        # VM Resize
91
        if status == "success":
92
            # VM has been resized. Change the flavor
136
    if status == "success":
137
        if state_for_success is not None:
138
            vm.operstate = state_for_success
139
        if nics is not None:
140
            # Update the NICs of the VM
141
            _process_net_status(vm, etime, nics)
142
        if beparams:
143
            # Change the flavor of the VM
93 144
            _process_resize(vm, beparams)
94
            vm.operstate = "STOPPED"
95
        elif status in ("canceled", "error"):
96
            vm.operstate = "STOPPED"
97
        else:
98
            vm.operstate = "RESIZE"
145
        # Update backendtime only for jobs that have been successfully
146
        # completed, since only these jobs update the state of the VM. Else a
147
        # "race condition" may occur when a successful job (e.g.
148
        # OP_INSTANCE_REMOVE) completes before an error job and messages arrive
149
        # in reversed order.
150
        vm.backendtime = etime
99 151

  
100 152
    # Special case: if OP_INSTANCE_CREATE fails --> ERROR
101 153
    if opcode == 'OP_INSTANCE_CREATE' and status in ('canceled', 'error'):
......
106 158
        # Special case: OP_INSTANCE_REMOVE fails for machines in ERROR,
107 159
        # when no instance exists at the Ganeti backend.
108 160
        # See ticket #799 for all the details.
109
        #
110 161
        if status == 'success' or (status == 'error' and
111 162
                                   vm.operstate == 'ERROR'):
112 163
            _process_net_status(vm, etime, nics=[])
113 164
            vm.deleted = True
114 165
            vm.operstate = state_for_success
115 166
            vm.backendtime = etime
116
            # Issue and accept commission to Quotaholder
117
            quotas.issue_and_accept_commission(vm, delete=True)
118

  
119
    # Update backendtime only for jobs that have been successfully completed,
120
    # since only these jobs update the state of the VM. Else a "race condition"
121
    # may occur when a successful job (e.g. OP_INSTANCE_REMOVE) completes
122
    # before an error job and messages arrive in reversed order.
123
    if status == 'success':
124
        vm.backendtime = etime
167
            status = "success"
168

  
169
    if status in ["success", "error", "canceled"]:
170
        # Job is finalized: Handle quotas/commissioning
171
        job_fields = {"nics": nics, "beparams": beparams}
172
        vm = handle_vm_quotas(vm, job_id=jobid, job_opcode=opcode,
173
                              job_status=status, job_fields=job_fields)
174
        # and clear task fields
175
        if vm.task_job_id == jobid:
176
            vm.task = None
177
            vm.task_job_id = None
125 178

  
126 179
    vm.save()
127 180

  
......
129 182
def _process_resize(vm, beparams):
130 183
    """Change flavor of a VirtualMachine based on new beparams."""
131 184
    old_flavor = vm.flavor
132
    vcpus = beparams.get("vcpus", None) or old_flavor.cpu
133
    minmem, maxmem = beparams.get("minmem"), beparams.get("maxmem")
134
    assert(minmem == maxmem), "Different minmem from maxmem"
135
    if vcpus is None and maxmem is None:
185
    vcpus = beparams.get("vcpus", old_flavor.cpu)
186
    ram = beparams.get("maxmem", old_flavor.ram)
187
    if vcpus == old_flavor.cpu and ram == old_flavor.ram:
136 188
        return
137
    ram = maxmem or old_flavor.ram
138 189
    try:
139 190
        new_flavor = Flavor.objects.get(cpu=vcpus, ram=ram,
140 191
                                        disk=old_flavor.disk,
......
678 729
        os_name = settings.GANETI_CREATEINSTANCE_KWARGS['os']
679 730
        client.ModifyInstance(vm.backend_vm_id,
680 731
                              os_name=os_name)
732
    return None
681 733

  
682 734

  
683 735
def get_instances(backend, bulk=True):

Also available in: Unified diff