root / snf-cyclades-app / synnefo / quotas / enforce.py @ d14155e3
History | View | Annotate | Download (7.8 kB)
1 |
# Copyright 2013 GRNET S.A. All rights reserved.
|
---|---|
2 |
#
|
3 |
# Redistribution and use in source and binary forms, with or
|
4 |
# without modification, are permitted provided that the following
|
5 |
# conditions are met:
|
6 |
#
|
7 |
# 1. Redistributions of source code must retain the above
|
8 |
# copyright notice, this list of conditions and the following
|
9 |
# disclaimer.
|
10 |
#
|
11 |
# 2. Redistributions in binary form must reproduce the above
|
12 |
# copyright notice, this list of conditions and the following
|
13 |
# disclaimer in the documentation and/or other materials
|
14 |
# provided with the distribution.
|
15 |
#
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
# POSSIBILITY OF SUCH DAMAGE.
|
28 |
#
|
29 |
# The views and conclusions contained in the software and
|
30 |
# documentation are those of the authors and should not be
|
31 |
# interpreted as representing official policies, either expressed
|
32 |
# or implied, of GRNET S.A.
|
33 |
|
34 |
import time |
35 |
from synnefo.db.models import VirtualMachine, IPAddress, NetworkInterface |
36 |
from synnefo.logic import servers |
37 |
from synnefo.logic import ips as logic_ips |
38 |
from synnefo.logic import backend |
39 |
|
40 |
|
41 |
MiB = 2 ** 20 |
42 |
GiB = 2 ** 30 |
43 |
|
44 |
|
45 |
def _partition_by(f, l, convert=None): |
46 |
if convert is None: |
47 |
convert = lambda x: x
|
48 |
d = {} |
49 |
for x in l: |
50 |
group = f(x) |
51 |
group_l = d.get(group, []) |
52 |
group_l.append(convert(x)) |
53 |
d[group] = group_l |
54 |
return d
|
55 |
|
56 |
|
57 |
CHANGE = { |
58 |
"cyclades.ram": lambda vm: vm.flavor.ram * MiB, |
59 |
"cyclades.cpu": lambda vm: vm.flavor.cpu, |
60 |
"cyclades.vm": lambda vm: 1, |
61 |
"cyclades.total_ram": lambda vm: vm.flavor.ram * MiB, |
62 |
"cyclades.total_cpu": lambda vm: vm.flavor.cpu, |
63 |
"cyclades.disk": lambda vm: vm.flavor.disk * GiB, |
64 |
"cyclades.floating_ip": lambda vm: 1, |
65 |
} |
66 |
|
67 |
|
68 |
def wait_server_job(server): |
69 |
jobID = server.task_job_id |
70 |
client = server.get_client() |
71 |
status, error = backend.wait_for_job(client, jobID) |
72 |
if status != "success": |
73 |
raise ValueError(error) |
74 |
|
75 |
|
76 |
VM_SORT_LEVEL = { |
77 |
"ERROR": 4, |
78 |
"BUILD": 3, |
79 |
"STOPPED": 2, |
80 |
"STARTED": 1, |
81 |
"RESIZE": 1, |
82 |
"DESTROYED": 0, |
83 |
} |
84 |
|
85 |
|
86 |
def sort_vms(): |
87 |
def f(vm): |
88 |
level = VM_SORT_LEVEL[vm.operstate] |
89 |
return (level, vm.id)
|
90 |
return f
|
91 |
|
92 |
|
93 |
def handle_stop_active(viol_id, resource, vms, diff, actions): |
94 |
vm_actions = actions["vm"]
|
95 |
vms = [vm for vm in vms if vm.operstate in ["STARTED", "BUILD", "ERROR"]] |
96 |
vms = sorted(vms, key=sort_vms(), reverse=True) |
97 |
for vm in vms: |
98 |
if diff < 1: |
99 |
break
|
100 |
diff -= CHANGE[resource](vm) |
101 |
if vm_actions.get(vm.id) is None: |
102 |
action = "REMOVE" if vm.operstate == "ERROR" else "SHUTDOWN" |
103 |
vm_actions[vm.id] = viol_id, vm.operstate, action |
104 |
|
105 |
|
106 |
def handle_destroy(viol_id, resource, vms, diff, actions): |
107 |
vm_actions = actions["vm"]
|
108 |
vms = sorted(vms, key=sort_vms(), reverse=True) |
109 |
for vm in vms: |
110 |
if diff < 1: |
111 |
break
|
112 |
diff -= CHANGE[resource](vm) |
113 |
vm_actions[vm.id] = viol_id, vm.operstate, "REMOVE"
|
114 |
|
115 |
|
116 |
def _state_after_action(vm, action): |
117 |
if action == "REMOVE": |
118 |
return "ERROR" # highest |
119 |
if action == "SHUTDOWN": |
120 |
return "STOPPED" |
121 |
return vm.operstate # no action |
122 |
|
123 |
|
124 |
def sort_ips(vm_actions): |
125 |
def f(ip): |
126 |
if not ip.in_use(): |
127 |
level = 5
|
128 |
else:
|
129 |
machine = ip.nic.machine |
130 |
_, _, action = vm_actions.get(machine.id, (None, None, None)) |
131 |
level = VM_SORT_LEVEL[_state_after_action(machine, action)] |
132 |
return (level, ip.id)
|
133 |
return f
|
134 |
|
135 |
|
136 |
def handle_floating_ip(viol_id, resource, ips, diff, actions): |
137 |
vm_actions = actions.get("vm", {})
|
138 |
ip_actions = actions["floating_ip"]
|
139 |
ips = sorted(ips, key=sort_ips(vm_actions), reverse=True) |
140 |
for ip in ips: |
141 |
if diff < 1: |
142 |
break
|
143 |
diff -= CHANGE[resource](ip) |
144 |
state = "USED" if ip.in_use() else "FREE" |
145 |
ip_actions[ip.id] = viol_id, state, "REMOVE"
|
146 |
|
147 |
|
148 |
def get_vms(users=None): |
149 |
vms = VirtualMachine.objects.filter(deleted=False).\
|
150 |
select_related("flavor").order_by('-id') |
151 |
if users is not None: |
152 |
vms = vms.filter(userid__in=users) |
153 |
|
154 |
return _partition_by(lambda vm: vm.userid, vms) |
155 |
|
156 |
|
157 |
def get_floating_ips(users=None): |
158 |
ips = IPAddress.objects.filter(deleted=False, floating_ip=True).\ |
159 |
select_related("nic__machine")
|
160 |
if users is not None: |
161 |
ips = ips.filter(userid__in=users) |
162 |
|
163 |
return _partition_by(lambda ip: ip.userid, ips) |
164 |
|
165 |
|
166 |
def get_actual_resources(resource_type, users=None): |
167 |
ACTUAL_RESOURCES = { |
168 |
"vm": get_vms,
|
169 |
"floating_ip": get_floating_ips,
|
170 |
} |
171 |
return ACTUAL_RESOURCES[resource_type](users=users)
|
172 |
|
173 |
|
174 |
VM_ACTION = { |
175 |
"REMOVE": servers.destroy,
|
176 |
"SHUTDOWN": servers.stop,
|
177 |
} |
178 |
|
179 |
|
180 |
def apply_to_vm(action, vm_id): |
181 |
try:
|
182 |
vm = VirtualMachine.objects.select_for_update().get(id=vm_id) |
183 |
VM_ACTION[action](vm) |
184 |
return True |
185 |
except BaseException: |
186 |
return False |
187 |
|
188 |
|
189 |
def perform_vm_actions(actions, fix=False): |
190 |
log = [] |
191 |
for vm_id, (viol_id, state, vm_action) in actions.iteritems(): |
192 |
data = ("vm", vm_id, state, vm_action, viol_id)
|
193 |
if fix:
|
194 |
r = apply_to_vm(vm_action, vm_id) |
195 |
data += ("DONE" if r else "FAILED",) |
196 |
log.append(data) |
197 |
return log
|
198 |
|
199 |
|
200 |
def wait_for_ip(ip_id): |
201 |
for i in range(100): |
202 |
ip = IPAddress.objects.get(id=ip_id) |
203 |
if ip.nic_id is None: |
204 |
objs = IPAddress.objects.select_for_update() |
205 |
return objs.get(id=ip_id)
|
206 |
time.sleep(1)
|
207 |
raise ValueError( |
208 |
"Floating_ip %s: Waiting for port delete timed out." % ip_id)
|
209 |
|
210 |
|
211 |
def remove_ip(ip_id): |
212 |
try:
|
213 |
ip = IPAddress.objects.select_for_update().get(id=ip_id) |
214 |
port_id = ip.nic_id |
215 |
if port_id:
|
216 |
objs = NetworkInterface.objects.select_for_update() |
217 |
port = objs.get(id=port_id) |
218 |
servers.delete_port(port) |
219 |
if port.machine:
|
220 |
wait_server_job(port.machine) |
221 |
ip = wait_for_ip(ip_id) |
222 |
logic_ips.delete_floating_ip(ip) |
223 |
return True |
224 |
except BaseException: |
225 |
return False |
226 |
|
227 |
|
228 |
def perform_floating_ip_actions(actions, fix=False): |
229 |
log = [] |
230 |
for ip_id, (viol_id, state, ip_action) in actions.iteritems(): |
231 |
data = ("floating_ip", ip_id, state, ip_action, viol_id)
|
232 |
if ip_action == "REMOVE": |
233 |
if fix:
|
234 |
r = remove_ip(ip_id) |
235 |
data += ("DONE" if r else "FAILED",) |
236 |
log.append(data) |
237 |
return log
|
238 |
|
239 |
|
240 |
def perform_actions(actions, fix=False): |
241 |
ACTION_HANDLING = [ |
242 |
("floating_ip", perform_floating_ip_actions),
|
243 |
("vm", perform_vm_actions),
|
244 |
] |
245 |
|
246 |
logs = [] |
247 |
for resource_type, handler in ACTION_HANDLING: |
248 |
t_actions = actions.get(resource_type, {}) |
249 |
log = handler(t_actions, fix=fix) |
250 |
logs += log |
251 |
return logs
|
252 |
|
253 |
|
254 |
# It is important to check resources in this order, especially
|
255 |
# floating_ip after vm resources.
|
256 |
RESOURCE_HANDLING = [ |
257 |
("cyclades.cpu", handle_stop_active, "vm"), |
258 |
("cyclades.ram", handle_stop_active, "vm"), |
259 |
("cyclades.total_cpu", handle_destroy, "vm"), |
260 |
("cyclades.total_ram", handle_destroy, "vm"), |
261 |
("cyclades.disk", handle_destroy, "vm"), |
262 |
("cyclades.vm", handle_destroy, "vm"), |
263 |
("cyclades.floating_ip", handle_floating_ip, "floating_ip"), |
264 |
] |