Statistics
| Branch: | Tag: | Revision:

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

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,
36
                              DEFAULT_INSTANCE_NETWORKS)
37
from synnefo.db.models import Backend
38
from synnefo.logic.backend import update_backend_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(flavor)
76

    
77
        # Refresh backends, if needed
78
        refresh_backends_stats(available_backends)
79

    
80
        if not available_backends:
81
            return None
82

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

    
87
        log.info("Allocated VM %r, in backend %s", vm, backend)
88

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

    
93
        return backend
94

    
95

    
96
def get_available_backends(flavor):
97
    """Get the list of available backends that can host a new VM of a flavor.
98

99
    The list contains the backends that are online and that have enabled
100
    the disk_template of the new VM.
101

102
    Also, if the new VM will be automatically connected to a public network,
103
    the backends that do not have an available public IPv4 address are
104
    excluded.
105

106
    """
107
    disk_template = flavor.disk_template
108
    # Ganeti knows only the 'ext' disk template, but the flavors disk template
109
    # includes the provider.
110
    if disk_template.startswith("ext_"):
111
        disk_template = "ext"
112

    
113
    backends = Backend.objects.select_for_update()
114
    backends = backends.filter(offline=False, drained=False,
115
                               disk_templates__contains=disk_template)
116
    backends = list(backends)
117
    if "SNF:ANY_PUBLIC" in DEFAULT_INSTANCE_NETWORKS:
118
        backends = filter(lambda x: has_free_ip(x), backends)
119
    return backends
120

    
121

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

    
130

    
131
def flavor_disk(flavor):
132
    """ Get flavor's 'real' disk size
133

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

    
140

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

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

147
    """
148

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

    
155
    backend.save()
156

    
157

    
158
def refresh_backends_stats(backends):
159
    """ Refresh the statistics of the backends.
160

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

164
    """
165

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

    
174

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

    
178
    backend = BACKEND_PER_USER.get(userid)
179

    
180
    if not backend:
181
        return None
182

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

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