Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (6.3 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
                              DEFAULT_INSTANCE_NETWORKS)
37
from synnefo.db.models import Backend
38
from synnefo.logic.backend import update_resources
39
from synnefo.api.util import backend_public_networks
40

    
41
log = logging.getLogger(__name__)
42

    
43

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

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

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

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

59
        """
60

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

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

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

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

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

    
83
        if not available_backends:
84
            return None
85

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

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

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

    
96
        return backend
97

    
98

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

102
    """
103
    backends = list(Backend.objects.select_for_update().filter(drained=False,
104
                                                               offline=False))
105
    if "SNF:ANY_PUBLIC" in DEFAULT_INSTANCE_NETWORKS:
106
        backends = filter(lambda x: has_free_ip(x), backends)
107
    return backends
108

    
109

    
110
def filter_archipelagos_backends(available_backends, disk_template):
111
    if disk_template == "ext":
112
        available_backends = filter(lambda x: x.id in ARCHIPELAGO_BACKENDS,
113
                                    available_backends)
114
    else:
115
        available_backends = filter(lambda x: x.id not in ARCHIPELAGO_BACKENDS,
116
                                    available_backends)
117
    return available_backends
118

    
119

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

    
128

    
129
def flavor_disk(flavor):
130
    """ Get flavor's 'real' disk size
131

132
    """
133
    if flavor.disk_template == 'drbd':
134
        return flavor.disk * 1024 * 2
135
    else:
136
        return flavor.disk * 1024
137

    
138

    
139
def reduce_backend_resources(backend, vm):
140
    """ Conservatively update the resources of a backend.
141

142
    Reduce the free resources of the backend by the size of the of the vm that
143
    will host. This is an underestimation of the backend capabilities.
144

145
    """
146

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

    
153
    backend.save()
154

    
155

    
156
def refresh_backends_stats(backends):
157
    """ Refresh the statistics of the backends.
158

159
    Set db backend state to the actual state of the backend, if
160
    BACKEND_REFRESH_MIN time has passed.
161

162
    """
163

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

    
172

    
173
def get_backend_for_user(userid):
174
    """Find fixed Backend for user based on BACKEND_PER_USER setting."""
175

    
176
    backend = BACKEND_PER_USER.get(userid)
177

    
178
    if not backend:
179
        return None
180

    
181
    try:
182
        try:
183
            backend_id = int(backend)
184
            return Backend.objects.get(id=backend_id)
185
        except ValueError:
186
            pass
187

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