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