Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (6.2 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.settings import (BACKEND_ALLOCATOR_MODULE, BACKEND_REFRESH_MIN,
35
                              BACKEND_PER_USER, ARCHIPELAGO_BACKENDS)
36
from synnefo.db.models import Backend
37
from synnefo.logic.backend import update_resources
38
from synnefo.api.util import backend_public_networks
39

    
40
log = logging.getLogger(__name__)
41

    
42

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

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

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

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

58
        """
59

    
60
        backend = None
61
        backend = get_backend_for_user(userid)
62
        if backend:
63
            return backend
64

    
65
        # Get the size of the vm
66
        disk = flavor_disk(flavor)
67
        ram = flavor.ram
68
        cpu = flavor.cpu
69
        vm = {'ram': ram, 'disk': disk, 'cpu': cpu}
70

    
71
        log.debug("Allocating VM: %r", vm)
72

    
73
        # Get available backends
74
        available_backends = get_available_backends()
75

    
76
        # Temporary fix for distinquishing archipelagos capable backends
77
        available_backends = filter_archipelagos_backends(available_backends,
78
                                                          flavor.disk_template)
79
        # Refresh backends, if needed
80
        refresh_backends_stats(available_backends)
81

    
82
        if not available_backends:
83
            return None
84

    
85
        # Find the best backend to host the vm, based on the allocation
86
        # strategy
87
        backend = self.strategy_mod.allocate(available_backends, vm)
88

    
89
        log.info("Allocated VM %r, in backend %s", vm, backend)
90

    
91
        # Reduce the free resources of the selected backend by the size of
92
        # the vm
93
        reduce_backend_resources(backend, vm)
94

    
95
        return backend
96

    
97

    
98
def get_available_backends():
99
    """Get available backends from db.
100

101
    """
102
    backends = list(Backend.objects.select_for_update().filter(drained=False,
103
                                                               offline=False))
104
    return filter(lambda x: has_free_ip(x), backends)
105

    
106

    
107
def filter_archipelagos_backends(available_backends, disk_template):
108
    if disk_template.startswith("ext_"):
109
        available_backends = filter(lambda x: x.id in ARCHIPELAGO_BACKENDS,
110
                                    available_backends)
111
    else:
112
        available_backends = filter(lambda x: x.id not in ARCHIPELAGO_BACKENDS,
113
                                    available_backends)
114
    return available_backends
115

    
116

    
117
def has_free_ip(backend):
118
    """Find if Backend has any free public IP."""
119
    for network in backend_public_networks(backend):
120
        if not network.get_pool().empty():
121
            return True
122
    log.warning("No available network in backend %r", backend)
123
    return False
124

    
125

    
126
def flavor_disk(flavor):
127
    """ Get flavor's 'real' disk size
128

129
    """
130
    if flavor.disk_template == 'drbd':
131
        return flavor.disk * 1024 * 2
132
    else:
133
        return flavor.disk * 1024
134

    
135

    
136
def reduce_backend_resources(backend, vm):
137
    """ Conservatively update the resources of a backend.
138

139
    Reduce the free resources of the backend by the size of the of the vm that
140
    will host. This is an underestimation of the backend capabilities.
141

142
    """
143

    
144
    new_mfree = backend.mfree - vm['ram']
145
    new_dfree = backend.dfree - vm['disk']
146
    backend.mfree = 0 if new_mfree < 0 else new_mfree
147
    backend.dfree = 0 if new_dfree < 0 else new_dfree
148
    backend.pinst_cnt += 1
149

    
150
    backend.save()
151

    
152

    
153
def refresh_backends_stats(backends):
154
    """ Refresh the statistics of the backends.
155

156
    Set db backend state to the actual state of the backend, if
157
    BACKEND_REFRESH_MIN time has passed.
158

159
    """
160

    
161
    now = datetime.datetime.now()
162
    delta = datetime.timedelta(minutes=BACKEND_REFRESH_MIN)
163
    for b in backends:
164
        if now > b.updated + delta:
165
            log.debug("Updating resources of backend %r. Last Updated %r",
166
                      b, b.updated)
167
            update_resources(b)
168

    
169

    
170
def get_backend_for_user(userid):
171
    """Find fixed Backend for user based on BACKEND_PER_USER setting."""
172

    
173
    backend = BACKEND_PER_USER.get(userid)
174

    
175
    if not backend:
176
        return None
177

    
178
    try:
179
        try:
180
            backend_id = int(backend)
181
            return Backend.objects.get(id=backend_id)
182
        except ValueError:
183
            pass
184

    
185
        backend_name = str(backend)
186
        return Backend.objects.get(clustername=backend_name)
187
    except Backend.DoesNotExist:
188
        log.error("Invalid backend %s for user %s", backend, userid)