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