Newer
Older
#!/usr/bin/env python
#coding=utf8
'''
This module contains the hypervisor destination election stuff.
'''
from __future__ import absolute_import
from copy import copy
from ccserver.exceptions import (UnknownElectionAlgo, UnknownElectionType,
ElectionError)
def tags(*args):
'''
Decorator used to declare tags used by a filter.
'''
def decorator(func):
func.__tags__ = set(args)
return func
return decorator
class Elector(object):
# Filtering function for destination hypervisors:
FILTERS = {
'cold': (('is_hv', 'filter r=hv'),
('not_source_hv', 'filter source hv'),
('is_connected', 'filter connected hv'),
('vm_htype_eq_hv', 'filter bad hv types'),
('has_rights', 'filter rights'),
('has_alloc', 'filter allocatable hv'),
('duplicate_name', 'filter vm duplicate names'),
('enough_disk', 'filter hv with not enough disk'),),
'hot': (('is_hv', 'filter r=hv'),
('not_source_hv', 'filter source hv'),
('is_connected', 'filter connected hv'),
('vm_htype_eq_hv', 'filter bad hv types'),
('has_rights', 'filter rights'),
('has_alloc', 'filter allocatable hv'),
('duplicate_name', 'filter vm duplicate names'),
#('enough_ram', 'filter hv with not enough ram'),
('enough_disk', 'filter hv with not enough disk'),),
ALGO_BY_TYPES = {'cold': ('fair', ),
'hot': ('fair', )}
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
def __init__(self, server, query_vm, query_dest, login):
# The server instance for this election:
self._server = server
# The TQL query to select vm:
self._query_vm = query_vm
# The TQL query to select destination hypervisor:
self._query_dest = query_dest
# The login of the initiator of the election:
self._login = login
def election(self, mtype, algo):
'''
Generate a new migration plan for this election. You must specify the
migration type and the distribution algoritm.
:param mtype: the migration type
:param algo: the distribution algoritm
'''
# Check the choosen election method:
if mtype not in self.ALGO_BY_TYPES:
raise UnknownElectionType('%r is unknown migration type' % mtype)
else:
if algo not in self.ALGO_BY_TYPES[mtype]:
raise UnknownElectionAlgo('%r is unknown migration algo' % algo)
else:
func = '_algo_%s' % algo
if not hasattr(self, func):
raise UnknownElectionAlgo('%r not found' % func)
else:
distribute = getattr(self, func)
# Get the destination hypervisor candidates:
candidates = self._get_candidates(self.FILTERS[mtype])
# Distributes VMs to each candidate:
migration_plan = distribute(mtype, candidates)
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# Return the migration plan:
return migration_plan
def _get_candidates(self, filters):
# Get all the tags needed for hypervisors and construct the final
# filter list:
filterfuncs = []
hv_tags = set()
for name, desc in filters:
filterfunc = getattr(self, '_filter_%s' % name)
filterfuncs.append((filterfunc, desc))
hv_tags |= getattr(filterfunc, '__tags__', set())
# Get the selected vms and hvs:
vms = self._server.list(self._query_vm, show=('*',))
hvs = self._server.list(self._query_dest, show=hv_tags)
candidates = []
# Filters the candidates:
for vm in vms:
if vm['r'] != 'vm':
continue
tql = 'id=%s' % vm['id']
if not self._server.check(self._login, 'coldmigrate', tql):
continue
vm_dest = copy(hvs)
for func, desc in filterfuncs:
vm_dest = func(vm, vm_dest)
candidates.append((vm, vm_dest))
return candidates
#####
##### Distribution algorithm methods:
#####
def _algo_fair(self, mtype, candidates):
migration_plan = []
# Sort vm by number of destination hv:
candidates = sorted(candidates, key=lambda x: len(x[1]))
hv_alloc = {}
for vm, hvs in candidates:
if not hvs:
raise ElectionError('No destination found for %r vm' % vm['id'])
else:
# Try to take an hypervisor that is not already in the plan:
for hv in hvs:
if hv['id'] not in hv_alloc:
break
else:
# If all candidates for this VM are already in the
# migration plan, we take the less allocated:
hv = min(hvs, key=lambda x: hv_alloc[x['id']])
migration_plan.append({'sid': vm['id'], 'did': hv['id'],
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
hv_alloc[hv['id']] = hv_alloc.get(hv['id'], 0) + 1
return migration_plan
#####
##### Filtering methods:
#####
@tags('r')
def _filter_is_hv(self, vm, hvs):
returned = []
for hv in hvs:
if hv.get('r') == 'hv':
returned.append(hv)
return returned
@tags('con')
def _filter_is_connected(self, vm, hvs):
returned = []
for hv in hvs:
if hv.get('con'):
returned.append(hv)
return returned
@tags('htype')
def _filter_vm_htype_eq_hv(self, vm, hvs):
returned = []
vm_htype = vm.get('htype')
for hv in hvs:
if hv.get('htype') == vm_htype:
returned.append(hv)
return returned
@tags('id')
def _filter_not_source_hv(self, vm, hvs):
returned = []
hv_id, _, _ = vm['id'].partition('.')
for hv in hvs:
if hv['id'] != hv_id:
returned.append(hv)
return returned
def _filter_has_rights(self, vm, hvs):
returned = []
for hv in hvs:
tql = 'id=%s' % hv['id']
if self._server.check(self._login, 'coldmigrate_dest', tql):
returned.append(hv)
return returned
@tags('alloc')
def _filter_has_alloc(self, vm, hvs):
returned = []
for hv in hvs:
if hv.get('alloc', False):
returned.append(hv)
return returned
def _filter_duplicate_name(self, vm, hvs):
returned = []
hv_id, _, vm_name = vm['id'].partition('.')
for hv in hvs:
vms = self._server.list('id:%s.*$h' % hv['id'])
duplicate = False
for vm in vms:
if vm.get('h') == vm_name:
duplicate = True
break
if not duplicate:
returned.append(hv)
return returned
@tags('memfree')
def _filter_enough_ram(self, vm, hvs):
returned = []
for hv in hvs:
if int(hv.get('memfree', 0)) >= int(vm.get('mem', 0)):
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
returned.append(hv)
return returned
@tags('cpu')
def _filter_enough_core(self, vm, hvs):
returned = []
for hv in hvs:
# Calculate the total number of vcpu used by VMs:
vms = self.manager.server.list('id:%s.*$cpu' % hv['id'])
count = 0
for vm in vms:
count += int(vm.get('cpu', 0))
if int(hv.get('cpu', 0)) >= count + int(self.vm.get('cpu', 0)):
returned.append(hv)
return returned
@tags('*')
def _filter_enough_disk(self, vm, hvs):
returned = []
return hvs
# Calculate the size needed for each pools:
pools = {}
for disk in vm['disk'].split():
size = vm['disk%s_size' % disk]
pool = vm['disk%s_pool' % disk]
vol = vm['disk%s_vol' % disk]
dpool = pools.get(pool, {'size': 0, 'vols': set()})
dpool['size'] += int(size)
dpool['vols'].add(vol)
pools[pool] = dpool
# Check for each HV if it match:
for hv in hvs:
good = True
for pool, prop in pools.iteritems():
free = int(hv.get('sto%s_free' % pool, 0))
vols = set(hv.get('sto%s_vol' % pool, '').split())
if free < prop['size'] or prop['vols'] & vols:
good = False
if good:
returned.append(hv)
return returned
def _filter_not_locked(self, vm, hvs):
if len(hvs) == 1:
return hvs
returned = []
for hv in hvs:
lock = self.manager.server.get_connection(hv['id']).lock
if not lock.locked():
returned.append(hv)
if len(returned) == 0:
return hvs
return returned
def _filter_biggest_id(self, hvs):
return sorted(hvs, key=lambda x: x['id'])[-1]