Revision cc7c0f44
b/snf-cyclades-app/synnefo/api/servers.py | ||
---|---|---|
31 | 31 |
# interpreted as representing official policies, either expressed |
32 | 32 |
# or implied, of GRNET S.A. |
33 | 33 |
|
34 |
import random |
|
35 |
|
|
36 | 34 |
from base64 import b64decode |
37 | 35 |
from logging import getLogger |
38 | 36 |
|
... | ... | |
49 | 47 |
from synnefo.logic.backend import create_instance, delete_instance |
50 | 48 |
from synnefo.logic.utils import get_rsapi_state |
51 | 49 |
from synnefo.util.rapi import GanetiApiError |
50 |
from synnefo.logic.backend_allocator import BackendAllocator |
|
51 |
|
|
52 |
from django.utils import importlib |
|
53 |
|
|
52 | 54 |
|
53 | 55 |
|
54 | 56 |
log = getLogger('synnefo.api') |
... | ... | |
196 | 198 |
# badRequest (400), |
197 | 199 |
# serverCapacityUnavailable (503), |
198 | 200 |
# overLimit (413) |
199 |
|
|
200 | 201 |
req = util.get_request_dict(request) |
201 | 202 |
log.debug('create_server %s', req) |
202 | 203 |
|
... | ... | |
253 | 254 |
if count >= vms_limit_for_user: |
254 | 255 |
raise faults.OverLimit("Server count limit exceeded for your account.") |
255 | 256 |
|
256 |
backend = random.choice(Backend.objects.all()) |
|
257 |
# We must save the VM instance now, so that it gets a |
|
258 |
# valid vm.backend_vm_id. |
|
257 |
backend_allocator = BackendAllocator() |
|
258 |
backend = backend_allocator.allocate(flavor) |
|
259 |
if backend is None: |
|
260 |
raise Exception |
|
261 |
|
|
262 |
# We must save the VM instance now, so that it gets a valid |
|
263 |
# vm.backend_vm_id. |
|
259 | 264 |
vm = VirtualMachine.objects.create( |
260 | 265 |
name=name, |
261 | 266 |
backend=backend, |
b/snf-cyclades-app/synnefo/app_settings/default/backend.py | ||
---|---|---|
33 | 33 |
'os': 'snf-image+default', |
34 | 34 |
'hvparams': {'serial_console': False}, |
35 | 35 |
'wait_for_sync': False} |
36 |
|
|
37 |
# This module implements the strategy for allocating a vm to a backend |
|
38 |
BACKEND_ALLOCATOR_MODULE = "synnefo.logic.allocators.default_allocator" |
|
39 |
# Refresh backend statistics timeout, in minutes, used in backend allocation |
|
40 |
BACKEND_REFRESH_MIN = 15 |
b/snf-cyclades-app/synnefo/logic/allocators/default_allocator.py | ||
---|---|---|
1 |
# Copyright 2011 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 |
from __future__ import division |
|
35 |
|
|
36 |
|
|
37 |
def allocate(backends, vm): |
|
38 |
if len(backends) == 1: |
|
39 |
return backends.keys()[0] |
|
40 |
|
|
41 |
# Filter those that can not host the vm |
|
42 |
capable_backends = dict((k, v) for k, v in backends.iteritems()\ |
|
43 |
if vm_fits_in_backend(v, vm)) |
|
44 |
|
|
45 |
# Since we are conservatively updating backend resources on each |
|
46 |
# allocation, a backend may actually be able to host a vm (despite |
|
47 |
# the state of the backend in db) |
|
48 |
if not capable_backends: |
|
49 |
capable_backends = backends |
|
50 |
|
|
51 |
# Compute the scores for each backend |
|
52 |
backend_scores = [(back_id, backend_score(back_resources, vm))\ |
|
53 |
for back_id, back_resources in \ |
|
54 |
capable_backends.iteritems()] |
|
55 |
|
|
56 |
# Pick out the best |
|
57 |
result = min(backend_scores, key=lambda (b_id, b_score): b_score) |
|
58 |
backend_id = result[0] |
|
59 |
|
|
60 |
return backend_id |
|
61 |
|
|
62 |
|
|
63 |
def vm_fits_in_backend(backend, vm): |
|
64 |
return backend['dfree'] > vm['disk'] and backend['mfree'] > vm['ram'] |
|
65 |
|
|
66 |
|
|
67 |
def backend_score(backend, flavor): |
|
68 |
mratio = 1 - (backend['mfree'] / backend['mtotal']) |
|
69 |
dratio = 1 - (backend['dfree'] / backend['dtotal']) |
|
70 |
# cratio = (backend.pinst_cnt+1)/backend.ctotal |
|
71 |
return 0.5 * (mratio + dratio) |
b/snf-cyclades-app/synnefo/logic/backend.py | ||
---|---|---|
443 | 443 |
def get_backends(backend=None): |
444 | 444 |
if backend: |
445 | 445 |
return [backend] |
446 |
return Backend.objects.all()
|
|
446 |
return Backend.objects.filter(offline=False)
|
|
447 | 447 |
|
448 | 448 |
|
449 | 449 |
def get_physical_resources(backend): |
b/snf-cyclades-app/synnefo/logic/backend_allocator.py | ||
---|---|---|
1 |
# Copyright 2011 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or without |
|
4 |
# modification, are permitted provided that the following conditions |
|
5 |
# are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above copyright |
|
8 |
# notice, this list of conditions and the following disclaimer. |
|
9 |
# |
|
10 |
# 2. Redistributions in binary form must reproduce the above copyright |
|
11 |
# notice, this list of conditions and the following disclaimer in the |
|
12 |
# documentation and/or other materials provided with the distribution. |
|
13 |
# |
|
14 |
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
|
15 |
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
16 |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
17 |
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
|
18 |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
19 |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
20 |
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
21 |
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
22 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
23 |
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
24 |
# SUCH DAMAGE. |
|
25 |
# |
|
26 |
# The views and conclusions contained in the software and documentation are |
|
27 |
# those of the authors and should not be interpreted as representing official |
|
28 |
# policies, either expressed or implied, of GRNET S.A. |
|
29 |
|
|
30 |
import datetime |
|
31 |
from django.utils import importlib |
|
32 |
|
|
33 |
from synnefo import settings |
|
34 |
from synnefo.db.models import Backend |
|
35 |
from synnefo.logic.backend import update_resources |
|
36 |
|
|
37 |
|
|
38 |
class BackendAllocator(): |
|
39 |
"""Wrapper class for instance allocation. |
|
40 |
|
|
41 |
""" |
|
42 |
def __init__(self): |
|
43 |
self.strategy_mod =\ |
|
44 |
importlib.import_module(settings.BACKEND_ALLOCATOR_MODULE) |
|
45 |
|
|
46 |
def allocate(self, flavor): |
|
47 |
# Get the size of the vm |
|
48 |
disk = flavor_disk(flavor) |
|
49 |
ram = flavor.ram |
|
50 |
cpu = flavor.cpu |
|
51 |
vm = {'ram': ram, 'disk': disk, 'cpu': cpu} |
|
52 |
|
|
53 |
# Refresh backends, if needed |
|
54 |
refresh_backends_stats() |
|
55 |
|
|
56 |
# Get available backends |
|
57 |
available_backends = get_available_backends() |
|
58 |
|
|
59 |
if not available_backends: |
|
60 |
return None |
|
61 |
|
|
62 |
# Find the best backend to host the vm, based on the allocation |
|
63 |
# strategy |
|
64 |
backend_id = self.strategy_mod.allocate(available_backends, vm) |
|
65 |
|
|
66 |
backend = Backend.objects.get(id=backend_id) |
|
67 |
# Reduce the free resources of the selected backend by the size of |
|
68 |
# the vm |
|
69 |
reduce_backend_resources(backend, vm) |
|
70 |
|
|
71 |
return backend |
|
72 |
|
|
73 |
|
|
74 |
def get_available_backends(): |
|
75 |
"""Get available backends from db. |
|
76 |
|
|
77 |
""" |
|
78 |
attrs = ['mfree', 'mtotal', 'dfree', 'dtotal', 'pinst_cnt', 'ctotal'] |
|
79 |
backends = {} |
|
80 |
for b in Backend.objects.filter(drained=False, offline=False): |
|
81 |
backend = {} |
|
82 |
for a in attrs: |
|
83 |
backend[a] = getattr(b, a) |
|
84 |
backends[b.id] = backend |
|
85 |
return backends |
|
86 |
|
|
87 |
|
|
88 |
def flavor_disk(flavor): |
|
89 |
""" Get flavor's 'real' disk size |
|
90 |
|
|
91 |
""" |
|
92 |
if flavor.disk_template == 'drbd': |
|
93 |
return flavor.disk * 1024 * 2 |
|
94 |
else: |
|
95 |
return flavor.disk * 1024 |
|
96 |
|
|
97 |
|
|
98 |
def reduce_backend_resources(backend, vm): |
|
99 |
""" Conservatively update the resources of a backend. |
|
100 |
|
|
101 |
Reduce the free resources of the backend by the size of the of the vm that |
|
102 |
will host. This is an underestimation of the backend capabilities. |
|
103 |
|
|
104 |
""" |
|
105 |
|
|
106 |
new_mfree = backend.mfree - vm['ram'] |
|
107 |
new_dfree = backend.dfree - vm['disk'] |
|
108 |
backend.mfree = 0 if new_mfree < 0 else new_mfree |
|
109 |
backend.dfree = 0 if new_dfree < 0 else new_dfree |
|
110 |
backend.pinst_cnt += 1 |
|
111 |
|
|
112 |
backend.save() |
|
113 |
|
|
114 |
|
|
115 |
def refresh_backends_stats(): |
|
116 |
""" Refresh the statistics of the backends. |
|
117 |
|
|
118 |
Set db backend state to the actual state of the backend, if |
|
119 |
BACKEND_REFRESH_MIN time has passed. |
|
120 |
|
|
121 |
""" |
|
122 |
|
|
123 |
now = datetime.datetime.now() |
|
124 |
delta = datetime.timedelta(minutes=settings.BACKEND_REFRESH_MIN) |
|
125 |
for b in Backend.objects.filter(drained=False, offline=False): |
|
126 |
if now > b.updated + delta: |
|
127 |
update_resources(b) |
b/snf-cyclades-app/synnefo/logic/management/commands/backend_update_stats.py | ||
---|---|---|
1 |
# Copyright 2011-2012 GRNET S.A. All rights reserved. |
|
2 |
# |
|
3 |
# Redistribution and use in source and binary forms, with or without |
|
4 |
# modification, are permitted provided that the following conditions |
|
5 |
# are met: |
|
6 |
# |
|
7 |
# 1. Redistributions of source code must retain the above copyright |
|
8 |
# notice, this list of conditions and the following disclaimer. |
|
9 |
# |
|
10 |
# 2. Redistributions in binary form must reproduce the above copyright |
|
11 |
# notice, this list of conditions and the following disclaimer in the |
|
12 |
# documentation and/or other materials provided with the distribution. |
|
13 |
# |
|
14 |
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND |
|
15 |
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
|
16 |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
|
17 |
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE |
|
18 |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
|
19 |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
|
20 |
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
|
21 |
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
|
22 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
|
23 |
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
|
24 |
# SUCH DAMAGE. |
|
25 |
# |
|
26 |
# The views and conclusions contained in the software and documentation are |
|
27 |
# those of the authors and should not be interpreted as representing official |
|
28 |
# policies, either expressed or implied, of GRNET S.A. |
|
29 |
# |
|
30 |
|
|
31 |
from optparse import make_option |
|
32 |
from django.core.management.base import BaseCommand, CommandError |
|
33 |
from synnefo import settings |
|
34 |
import datetime |
|
35 |
|
|
36 |
from synnefo.db.models import Backend |
|
37 |
from synnefo.logic.backend import update_resources |
|
38 |
|
|
39 |
|
|
40 |
class Command(BaseCommand): |
|
41 |
can_import_settings = True |
|
42 |
|
|
43 |
help = "Update backend statistics, which are used for instance allocation." |
|
44 |
output_transaction = True # The management command runs inside |
|
45 |
# an SQL transaction |
|
46 |
option_list = BaseCommand.option_list + ( |
|
47 |
make_option('--backend_id', dest='backend_id', |
|
48 |
help="Update statistics of only this backend"), |
|
49 |
make_option('--older_than', dest='older_than', metavar="MINUTES", |
|
50 |
help="Update only backends that have not been updated for\ |
|
51 |
MINUTES. Set to 0 to force update.") |
|
52 |
) |
|
53 |
|
|
54 |
def handle(self, **options): |
|
55 |
|
|
56 |
if options['backend_id']: |
|
57 |
try: |
|
58 |
backend_id = int(options['backend_id']) |
|
59 |
backends = [Backend.objects.get(id=backend_id)] |
|
60 |
except ValueError: |
|
61 |
raise CommandError("Wrong backend ID") |
|
62 |
except Backend.DoesNotExist: |
|
63 |
raise CommandError("Backend not found in DB") |
|
64 |
else: |
|
65 |
# XXX:filter drained ? |
|
66 |
backends = Backend.objects.all() |
|
67 |
|
|
68 |
now = datetime.datetime.now() |
|
69 |
if options['older_than'] is not None: |
|
70 |
minutes = int(options['older_than']) |
|
71 |
else: |
|
72 |
minutes = settings.BACKEND_REFRESH_MIN |
|
73 |
|
|
74 |
delta = datetime.timedelta(minutes=minutes) |
|
75 |
|
|
76 |
for b in backends: |
|
77 |
if now > b.updated + delta: |
|
78 |
update_resources(b) |
|
79 |
print 'Successfully updated backend with id: %d' % b.id |
|
80 |
else: |
|
81 |
print 'Backend %d does not need update' % b.id |
Also available in: Unified diff