Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (5.9 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 django.conf import settings
35
from synnefo.db.models import Backend
36
from synnefo.logic import backend as backend_mod
37
from synnefo.api import util
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, userid, 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

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

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

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

    
72
        # Get available backends
73
        available_backends = get_available_backends(flavor)
74

    
75
        # Refresh backends, if needed
76
        refresh_backends_stats(available_backends)
77

    
78
        if not available_backends:
79
            return None
80

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

    
85
        log.info("Allocated VM %r, in backend %s", vm, backend)
86

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

    
91
        return backend
92

    
93

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

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

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

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

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

    
120

    
121
def flavor_disk(flavor):
122
    """ Get flavor's 'real' disk size
123

124
    """
125
    if flavor.disk_template == 'drbd':
126
        return flavor.disk * 1024 * 2
127
    else:
128
        return flavor.disk * 1024
129

    
130

    
131
def reduce_backend_resources(backend, vm):
132
    """ Conservatively update the resources of a backend.
133

134
    Reduce the free resources of the backend by the size of the of the vm that
135
    will host. This is an underestimation of the backend capabilities.
136

137
    """
138

    
139
    new_mfree = backend.mfree - vm['ram']
140
    new_dfree = backend.dfree - vm['disk']
141
    backend.mfree = 0 if new_mfree < 0 else new_mfree
142
    backend.dfree = 0 if new_dfree < 0 else new_dfree
143
    backend.pinst_cnt += 1
144

    
145
    backend.save()
146

    
147

    
148
def refresh_backends_stats(backends):
149
    """ Refresh the statistics of the backends.
150

151
    Set db backend state to the actual state of the backend, if
152
    BACKEND_REFRESH_MIN time has passed.
153

154
    """
155

    
156
    now = datetime.datetime.now()
157
    delta = datetime.timedelta(minutes=settings.BACKEND_REFRESH_MIN)
158
    for b in backends:
159
        if now > b.updated + delta:
160
            log.debug("Updating resources of backend %r. Last Updated %r",
161
                      b, b.updated)
162
            backend_mod.update_backend_resources(b)
163

    
164

    
165
def get_backend_for_user(userid):
166
    """Find fixed Backend for user based on BACKEND_PER_USER setting."""
167

    
168
    backend = settings.BACKEND_PER_USER.get(userid)
169

    
170
    if not backend:
171
        return None
172

    
173
    try:
174
        try:
175
            backend_id = int(backend)
176
            return Backend.objects.get(id=backend_id)
177
        except ValueError:
178
            pass
179

    
180
        backend_name = str(backend)
181
        return Backend.objects.get(clustername=backend_name)
182
    except Backend.DoesNotExist:
183
        log.error("Invalid backend %s for user %s", backend, userid)