Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / backend_allocator.py @ f96f60fd

History | View | Annotate | Download (4.7 kB)

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 logging
31
import datetime
32
from django.utils import importlib
33

    
34
from synnefo import settings
35
from synnefo.db.models import Backend
36
from synnefo.logic.backend import update_resources
37
from synnefo.api.util import backend_public_networks
38

    
39
log = logging.getLogger(__name__)
40

    
41

    
42
class BackendAllocator():
43
    """Wrapper class for instance allocation.
44

45
    """
46
    def __init__(self):
47
        self.strategy_mod =\
48
            importlib.import_module(settings.BACKEND_ALLOCATOR_MODULE)
49

    
50
    def allocate(self, flavor):
51
        """Allocate a vm of the specified flavor to a backend.
52

53
        Warning!!: An explicit commit is required after calling this function,
54
        in order to release the locks acquired by the get_available_backends
55
        function.
56

57
        """
58
        # Get the size of the vm
59
        disk = flavor_disk(flavor)
60
        ram = flavor.ram
61
        cpu = flavor.cpu
62
        vm = {'ram': ram, 'disk': disk, 'cpu': cpu}
63

    
64
        log.debug("Allocating VM: %r", vm)
65

    
66
        # Refresh backends, if needed
67
        refresh_backends_stats()
68

    
69
        # Get available backends
70
        available_backends = get_available_backends()
71

    
72
        if not available_backends:
73
            return None
74

    
75
        # Find the best backend to host the vm, based on the allocation
76
        # strategy
77
        backend = self.strategy_mod.allocate(available_backends, vm)
78

    
79
        log.info("Allocated VM %r, in backend %s", vm, backend)
80

    
81
        # Reduce the free resources of the selected backend by the size of
82
        # the vm
83
        reduce_backend_resources(backend, vm)
84

    
85
        return backend
86

    
87

    
88
def get_available_backends():
89
    """Get available backends from db.
90

91
    """
92
    backends = list(Backend.objects.select_for_update().filter(drained=False,
93
                                                               offline=False))
94
    return filter(lambda x: has_free_ip(x), backends)
95

    
96

    
97
def has_free_ip(backend):
98
    """Find if Backend has any free public IP."""
99
    for network in backend_public_networks(backend):
100
        if not network.get_pool().empty():
101
            return True
102
    return False
103

    
104

    
105
def flavor_disk(flavor):
106
    """ Get flavor's 'real' disk size
107

108
    """
109
    if flavor.disk_template == 'drbd':
110
        return flavor.disk * 1024 * 2
111
    else:
112
        return flavor.disk * 1024
113

    
114

    
115
def reduce_backend_resources(backend, vm):
116
    """ Conservatively update the resources of a backend.
117

118
    Reduce the free resources of the backend by the size of the of the vm that
119
    will host. This is an underestimation of the backend capabilities.
120

121
    """
122

    
123
    new_mfree = backend.mfree - vm['ram']
124
    new_dfree = backend.dfree - vm['disk']
125
    backend.mfree = 0 if new_mfree < 0 else new_mfree
126
    backend.dfree = 0 if new_dfree < 0 else new_dfree
127
    backend.pinst_cnt += 1
128

    
129
    backend.save()
130

    
131

    
132
def refresh_backends_stats():
133
    """ Refresh the statistics of the backends.
134

135
    Set db backend state to the actual state of the backend, if
136
    BACKEND_REFRESH_MIN time has passed.
137

138
    """
139

    
140
    now = datetime.datetime.now()
141
    delta = datetime.timedelta(minutes=settings.BACKEND_REFRESH_MIN)
142
    for b in Backend.objects.filter(drained=False, offline=False):
143
        if now > b.updated + delta:
144
            log.debug("Updating resources of backend %r", b)
145
            update_resources(b)