Revision adc46059

b/snf-cyclades-app/synnefo/api/servers.py
186 186

  
187 187

  
188 188
@util.api_method('POST')
189
@transaction.commit_on_success
189
# Use manual transactions. Backend and IP pool allocations need exclusive
190
# access (SELECT..FOR UPDATE). Running create_server with commit_on_success
191
# would result in backends and public networks to be locked until the job is
192
# sent to the Ganeti backend.
193
@transaction.commit_manually
190 194
def create_server(request):
191 195
    # Normal Response Code: 202
192 196
    # Error Response Codes: computeFault (400, 500),
......
255 259

  
256 260
    backend_allocator = BackendAllocator()
257 261
    backend = backend_allocator.allocate(flavor)
262

  
258 263
    if backend is None:
264
        transaction.rollback()
259 265
        raise Exception
266
    transaction.commit()
267

  
268
    if settings.PUBLIC_ROUTED_USE_POOL:
269
        (network, address) = util.allocate_public_address(backend)
270
        if address is None:
271
            transaction.rollback()
272
            raise faults.OverLimit("Can not allocate IP for new machine."
273
                                   " Public networks are full.")
274
        transaction.commit()
275
        nic = {'ip': address, 'network': network.backend_id}
276
    else:
277
        nic = {'ip': 'pool', 'network': network.backend_id}
260 278

  
261 279
    # We must save the VM instance now, so that it gets a valid
262 280
    # vm.backend_vm_id.
......
268 286
        flavor=flavor)
269 287

  
270 288
    try:
271
        jobID = create_instance(vm, flavor, image, password, personality)
289
        jobID = create_instance(vm, nic, flavor, image, password, personality)
272 290
    except GanetiApiError:
273 291
        vm.delete()
274 292
        raise
......
288 306
    server = vm_to_dict(vm, detail=True)
289 307
    server['status'] = 'BUILD'
290 308
    server['adminPass'] = password
291
    return render_server(request, server, status=202)
309

  
310
    respsone = render_server(request, server, status=202)
311
    transaction.commit()
312

  
313
    return respsone
292 314

  
293 315

  
294 316
@util.api_method('GET')
b/snf-cyclades-app/synnefo/api/util.py
60 60
from synnefo.db.models import (Flavor, VirtualMachine, VirtualMachineMetadata,
61 61
                               Network, BackendNetwork, NetworkInterface,
62 62
                               BridgePoolTable, MacPrefixPoolTable)
63
from synnefo.db.pools import EmptyPool
63 64

  
64 65
from synnefo.lib.astakos import get_user
65 66
from synnefo.plankton.backend import ImageBackend
......
217 218
    return cidr_block <= 29 and cidr_block > MAX_CIDR_BLOCK
218 219

  
219 220

  
221
def allocate_public_address(backend):
222
    """Allocate a public IP for a vm."""
223
    for network in backend_public_networks(backend):
224
        try:
225
            address = get_network_free_address(network)
226
            return (network, address)
227
        except EmptyPool:
228
            pass
229
    return (None, None)
230

  
231

  
220 232
def backend_public_networks(backend):
221 233
    """Return available public networks of the backend.
222 234

  
b/snf-cyclades-app/synnefo/db/managers.py
97 97
                                            params)
98 98

  
99 99

  
100
class ProtectedDeleteManager(Manager):
100
class ProtectedDeleteManager(ForUpdateManager):
101 101
    """ Manager for protecting Backend deletion.
102 102

  
103 103
        Call Backend delete() method in order to prevent deletion
b/snf-cyclades-app/synnefo/logic/backend.py
41 41
from synnefo.db.models import (Backend, VirtualMachine, Network,
42 42
                               BackendNetwork, BACKEND_STATUSES)
43 43
from synnefo.logic import utils
44
from synnefo.db.pools import EmptyPool
45
from synnefo.api.faults import OverLimit
46
from synnefo.api.util import backend_public_networks, get_network_free_address
47 44
from synnefo.util.rapi import GanetiRapiClient
48 45

  
49 46
log = getLogger('synnefo.logic')
......
258 255
    vm.save()
259 256

  
260 257

  
261
@transaction.commit_on_success
262
def create_instance(vm, flavor, image, password, personality):
258
def create_instance(vm, public_nic, flavor, image, password, personality):
263 259
    """`image` is a dictionary which should contain the keys:
264 260
            'backend_id', 'format' and 'metadata'
265 261

  
266 262
        metadata value should be a dictionary.
267 263
    """
268 264

  
269
    if settings.PUBLIC_ROUTED_USE_POOL:
270
        (network, address) = allocate_public_address(vm)
271
        if address is None:
272
            raise OverLimit("Can not allocate IP for new machine."
273
                            " Public networks are full.")
274
        nic = {'ip': address, 'network': network.backend_id}
275
    else:
276
        nic = {'ip': 'pool', 'network': network.backend_id}
277

  
278 265
    if settings.IGNORE_FLAVOR_DISK_SIZES:
279 266
        if image['backend_id'].find("windows") >= 0:
280 267
            sz = 14000
......
308 295
    if provider:
309 296
        kw['disks'][0]['provider'] = provider
310 297

  
311
    kw['nics'] = [nic]
298
    kw['nics'] = [public_nic]
312 299
    if settings.GANETI_USE_HOTPLUG:
313 300
        kw['hotplug'] = True
314 301
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
......
341 328
    return vm.client.CreateInstance(**kw)
342 329

  
343 330

  
344
def allocate_public_address(vm):
345
    """Allocate a public IP for a vm."""
346
    for network in backend_public_networks(vm.backend):
347
        try:
348
            address = get_network_free_address(network)
349
            return (network, address)
350
        except EmptyPool:
351
            pass
352
    return (None, None)
353

  
354

  
355 331
def delete_instance(vm):
356 332
    start_action(vm, 'DESTROY')
357 333
    vm.client.DeleteInstance(vm.backend_vm_id, dry_run=settings.TEST)
b/snf-cyclades-app/synnefo/logic/backend_allocator.py
47 47
            importlib.import_module(settings.BACKEND_ALLOCATOR_MODULE)
48 48

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

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

  
56
        """
50 57
        # Get the size of the vm
51 58
        disk = flavor_disk(flavor)
52 59
        ram = flavor.ram
......
81 88
    """Get available backends from db.
82 89

  
83 90
    """
84
    return Backend.objects.filter(drained=False, offline=False)
91
    return list(Backend.objects.select_for_update().filter(drained=False,
92
                                                           offline=False))
85 93

  
86 94

  
87 95
def flavor_disk(flavor):

Also available in: Unified diff