Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / backend.py @ 7fede91e

History | View | Annotate | Download (23.4 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
import json
35

    
36
from logging import getLogger
37
from django.conf import settings
38
from django.db import transaction
39
from datetime import datetime
40

    
41
from synnefo.db.models import (Backend, VirtualMachine, Network,
42
                               BackendNetwork, BACKEND_STATUSES)
43
from synnefo.logic import utils, ippool
44
from synnefo.api.faults import OverLimit
45
from synnefo.api.util import backend_public_networks, get_network_free_address
46
from synnefo.util.rapi import GanetiRapiClient
47

    
48
log = getLogger('synnefo.logic')
49

    
50

    
51
_firewall_tags = {
52
    'ENABLED': settings.GANETI_FIREWALL_ENABLED_TAG,
53
    'DISABLED': settings.GANETI_FIREWALL_DISABLED_TAG,
54
    'PROTECTED': settings.GANETI_FIREWALL_PROTECTED_TAG}
55

    
56
_reverse_tags = dict((v.split(':')[3], k) for k, v in _firewall_tags.items())
57

    
58

    
59
def create_client(hostname, port=5080, username=None, password=None):
60
    return GanetiRapiClient(hostname, port, username, password)
61

    
62

    
63
@transaction.commit_on_success
64
def process_op_status(vm, etime, jobid, opcode, status, logmsg):
65
    """Process a job progress notification from the backend
66

67
    Process an incoming message from the backend (currently Ganeti).
68
    Job notifications with a terminating status (sucess, error, or canceled),
69
    also update the operating state of the VM.
70

71
    """
72
    # See #1492, #1031, #1111 why this line has been removed
73
    #if (opcode not in [x[0] for x in VirtualMachine.BACKEND_OPCODES] or
74
    if status not in [x[0] for x in BACKEND_STATUSES]:
75
        raise VirtualMachine.InvalidBackendMsgError(opcode, status)
76

    
77
    vm.backendjobid = jobid
78
    vm.backendjobstatus = status
79
    vm.backendopcode = opcode
80
    vm.backendlogmsg = logmsg
81

    
82
    # Notifications of success change the operating state
83
    state_for_success = VirtualMachine.OPER_STATE_FROM_OPCODE.get(opcode, None)
84
    if status == 'success' and state_for_success is not None:
85
        utils.update_state(vm, state_for_success)
86
        # Set the deleted flag explicitly, cater for admin-initiated removals
87
        if opcode == 'OP_INSTANCE_REMOVE':
88
            release_instance_nics(vm)
89
            vm.deleted = True
90
            vm.nics.all().delete()
91

    
92
    # Special case: if OP_INSTANCE_CREATE fails --> ERROR
93
    if status in ('canceled', 'error') and opcode == 'OP_INSTANCE_CREATE':
94
        utils.update_state(vm, 'ERROR')
95

    
96
    # Special case: OP_INSTANCE_REMOVE fails for machines in ERROR,
97
    # when no instance exists at the Ganeti backend.
98
    # See ticket #799 for all the details.
99
    #
100
    if (status == 'error' and opcode == 'OP_INSTANCE_REMOVE'):
101
        vm.deleted = True
102
        vm.nics.all().delete()
103

    
104
    vm.backendtime = etime
105
    # Any other notification of failure leaves the operating state unchanged
106

    
107
    vm.save()
108

    
109

    
110
@transaction.commit_on_success
111
def process_net_status(vm, etime, nics):
112
    """Process a net status notification from the backend
113

114
    Process an incoming message from the Ganeti backend,
115
    detailing the NIC configuration of a VM instance.
116

117
    Update the state of the VM in the DB accordingly.
118
    """
119

    
120
    # Release the ips of the old nics. Get back the networks as multiple
121
    # changes in the same network, must happen in the same Network object,
122
    # because transaction will be commited only on exit of the function.
123
    networks = release_instance_nics(vm)
124

    
125
    new_nics = enumerate(nics)
126
    for i, new_nic in new_nics:
127
        network = new_nic.get('network', '')
128
        n = str(network)
129
        pk = utils.id_from_network_name(n)
130

    
131
        # Get the cached Network or get it from DB
132
        if pk in networks:
133
            net = networks[pk]
134
        else:
135
            net = Network.objects.select_for_update().get(pk=pk)
136

    
137
        # Get the new nic info
138
        mac = new_nic.get('mac', '')
139
        ipv4 = new_nic.get('ip', '')
140
        ipv6 = new_nic.get('ipv6', '')
141

    
142
        firewall = new_nic.get('firewall', '')
143
        firewall_profile = _reverse_tags.get(firewall, '')
144
        if not firewall_profile and net.public:
145
            firewall_profile = settings.DEFAULT_FIREWALL_PROFILE
146

    
147
        if ipv4:
148
            net.reserve_address(ipv4)
149

    
150
        vm.nics.create(
151
            network=net,
152
            index=i,
153
            mac=mac,
154
            ipv4=ipv4,
155
            ipv6=ipv6,
156
            firewall_profile=firewall_profile,
157
            dirty=False)
158

    
159
    vm.backendtime = etime
160
    vm.save()
161

    
162

    
163
def release_instance_nics(vm):
164
    networks = {}
165

    
166
    for nic in vm.nics.all():
167
        pk = nic.network.pk
168
        # Get the cached Network or get it from DB
169
        if pk in networks:
170
            net = networks[pk]
171
        else:
172
            # Get the network object in exclusive mode in order
173
            # to guarantee consistency of the address pool
174
            net = Network.objects.select_for_update().get(pk=pk)
175
        if nic.ipv4:
176
            net.release_address(nic.ipv4)
177
        nic.delete()
178

    
179
    return networks
180

    
181

    
182
@transaction.commit_on_success
183
def process_network_status(back_network, etime, jobid, opcode, status, logmsg):
184
    if status not in [x[0] for x in BACKEND_STATUSES]:
185
        return
186
        #raise Network.InvalidBackendMsgError(opcode, status)
187

    
188
    back_network.backendjobid = jobid
189
    back_network.backendjobstatus = status
190
    back_network.backendopcode = opcode
191
    back_network.backendlogmsg = logmsg
192

    
193
    # Notifications of success change the operating state
194
    state_for_success = BackendNetwork.OPER_STATE_FROM_OPCODE.get(opcode, None)
195
    if status == 'success' and state_for_success is not None:
196
        back_network.operstate = state_for_success
197
        if opcode == 'OP_NETWORK_REMOVE':
198
            back_network.deleted = True
199

    
200
    if status in ('canceled', 'error') and opcode == 'OP_NETWORK_CREATE':
201
        utils.update_state(back_network, 'ERROR')
202

    
203
    if (status == 'error' and opcode == 'OP_NETWORK_REMOVE'):
204
        back_network.deleted = True
205
        back_network.operstate = 'DELETED'
206

    
207
    back_network.save()
208

    
209

    
210
@transaction.commit_on_success
211
def process_create_progress(vm, etime, rprogress, wprogress):
212

    
213
    # XXX: This only uses the read progress for now.
214
    #      Explore whether it would make sense to use the value of wprogress
215
    #      somewhere.
216
    percentage = int(rprogress)
217

    
218
    # The percentage may exceed 100%, due to the way
219
    # snf-progress-monitor tracks bytes read by image handling processes
220
    percentage = 100 if percentage > 100 else percentage
221
    if percentage < 0:
222
        raise ValueError("Percentage cannot be negative")
223

    
224
    # FIXME: log a warning here, see #1033
225
#   if last_update > percentage:
226
#       raise ValueError("Build percentage should increase monotonically " \
227
#                        "(old = %d, new = %d)" % (last_update, percentage))
228

    
229
    # This assumes that no message of type 'ganeti-create-progress' is going to
230
    # arrive once OP_INSTANCE_CREATE has succeeded for a Ganeti instance and
231
    # the instance is STARTED.  What if the two messages are processed by two
232
    # separate dispatcher threads, and the 'ganeti-op-status' message for
233
    # successful creation gets processed before the 'ganeti-create-progress'
234
    # message? [vkoukis]
235
    #
236
    #if not vm.operstate == 'BUILD':
237
    #    raise VirtualMachine.IllegalState("VM is not in building state")
238

    
239
    vm.buildpercentage = percentage
240
    vm.backendtime = etime
241
    vm.save()
242

    
243

    
244
def start_action(vm, action):
245
    """Update the state of a VM when a new action is initiated."""
246
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
247
        raise VirtualMachine.InvalidActionError(action)
248

    
249
    # No actions to deleted and no actions beside destroy to suspended VMs
250
    if vm.deleted:
251
        raise VirtualMachine.DeletedError
252

    
253
    # No actions to machines being built. They may be destroyed, however.
254
    if vm.operstate == 'BUILD' and action != 'DESTROY':
255
        raise VirtualMachine.BuildingError
256

    
257
    vm.action = action
258
    vm.backendjobid = None
259
    vm.backendopcode = None
260
    vm.backendjobstatus = None
261
    vm.backendlogmsg = None
262

    
263
    # Update the relevant flags if the VM is being suspended or destroyed.
264
    # Do not set the deleted flag here, see ticket #721.
265
    #
266
    # The deleted flag is set asynchronously, when an OP_INSTANCE_REMOVE
267
    # completes successfully. Hence, a server may be visible for some time
268
    # after a DELETE /servers/id returns HTTP 204.
269
    #
270
    if action == "DESTROY":
271
        # vm.deleted = True
272
        pass
273
    elif action == "SUSPEND":
274
        vm.suspended = True
275
    elif action == "START":
276
        vm.suspended = False
277
    vm.save()
278

    
279

    
280
@transaction.commit_on_success
281
def create_instance(vm, flavor, image, password, personality):
282
    """`image` is a dictionary which should contain the keys:
283
            'backend_id', 'format' and 'metadata'
284

285
        metadata value should be a dictionary.
286
    """
287

    
288
    if settings.PUBLIC_ROUTED_USE_POOL:
289
        (network, address) = allocate_public_address(vm)
290
        if address is None:
291
            raise OverLimit("Can not allocate IP for new machine."
292
                            " Public networks are full.")
293
        nic = {'ip': address, 'network': network.backend_id}
294
    else:
295
        nic = {'ip': 'pool', 'network': network.backend_id}
296

    
297
    if settings.IGNORE_FLAVOR_DISK_SIZES:
298
        if image['backend_id'].find("windows") >= 0:
299
            sz = 14000
300
        else:
301
            sz = 4000
302
    else:
303
        sz = flavor.disk * 1024
304

    
305
    # Handle arguments to CreateInstance() as a dictionary,
306
    # initialize it based on a deployment-specific value.
307
    # This enables the administrator to override deployment-specific
308
    # arguments, such as the disk template to use, name of os provider
309
    # and hypervisor-specific parameters at will (see Synnefo #785, #835).
310
    #
311
    kw = settings.GANETI_CREATEINSTANCE_KWARGS
312
    kw['mode'] = 'create'
313
    kw['name'] = vm.backend_vm_id
314
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
315

    
316
    # Identify if provider parameter should be set in disk options.
317
    # Current implementation support providers only fo ext template.
318
    # To select specific provider for an ext template, template name
319
    # should be formated as `ext_<provider_name>`.
320
    provider = None
321
    disk_template = flavor.disk_template
322
    if flavor.disk_template.startswith("ext"):
323
        disk_template, provider = flavor.disk_template.split("_", 1)
324

    
325
    kw['disk_template'] = disk_template
326
    kw['disks'] = [{"size": sz}]
327
    if provider:
328
        kw['disks'][0]['provider'] = provider
329

    
330
    kw['nics'] = [nic]
331
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
332
    # kw['os'] = settings.GANETI_OS_PROVIDER
333
    kw['ip_check'] = False
334
    kw['name_check'] = False
335
    # Do not specific a node explicitly, have
336
    # Ganeti use an iallocator instead
337
    #
338
    # kw['pnode']=rapi.GetNodes()[0]
339
    kw['dry_run'] = settings.TEST
340

    
341
    kw['beparams'] = {
342
        'auto_balance': True,
343
        'vcpus': flavor.cpu,
344
        'memory': flavor.ram}
345

    
346
    kw['osparams'] = {
347
        'img_id': image['backend_id'],
348
        'img_passwd': password,
349
        'img_format': image['format']}
350
    if personality:
351
        kw['osparams']['img_personality'] = json.dumps(personality)
352

    
353
    kw['osparams']['img_properties'] = json.dumps(image['metadata'])
354

    
355
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
356
    # kw['hvparams'] = dict(serial_console=False)
357

    
358
    return vm.client.CreateInstance(**kw)
359

    
360

    
361
def allocate_public_address(vm):
362
    """Allocate a public IP for a vm."""
363
    for network in backend_public_networks(vm.backend):
364
        try:
365
            address = get_network_free_address(network)
366
            return (network, address)
367
        except ippool.IPPool.IPPoolExhausted:
368
            pass
369
    return (None, None)
370

    
371

    
372
def delete_instance(vm):
373
    start_action(vm, 'DESTROY')
374
    vm.client.DeleteInstance(vm.backend_vm_id, dry_run=settings.TEST)
375

    
376

    
377
def reboot_instance(vm, reboot_type):
378
    assert reboot_type in ('soft', 'hard')
379
    vm.client.RebootInstance(vm.backend_vm_id, reboot_type, dry_run=settings.TEST)
380
    log.info('Rebooting instance %s', vm.backend_vm_id)
381

    
382

    
383
def startup_instance(vm):
384
    start_action(vm, 'START')
385
    vm.client.StartupInstance(vm.backend_vm_id, dry_run=settings.TEST)
386

    
387

    
388
def shutdown_instance(vm):
389
    start_action(vm, 'STOP')
390
    vm.client.ShutdownInstance(vm.backend_vm_id, dry_run=settings.TEST)
391

    
392

    
393
def get_instance_console(vm):
394
    # RAPI GetInstanceConsole() returns endpoints to the vnc_bind_address,
395
    # which is a cluster-wide setting, either 0.0.0.0 or 127.0.0.1, and pretty
396
    # useless (see #783).
397
    #
398
    # Until this is fixed on the Ganeti side, construct a console info reply
399
    # directly.
400
    #
401
    # WARNING: This assumes that VNC runs on port network_port on
402
    #          the instance's primary node, and is probably
403
    #          hypervisor-specific.
404
    #
405
    console = {}
406
    console['kind'] = 'vnc'
407
    i = vm.client.GetInstance(vm.backend_vm_id)
408
    if i['hvparams']['serial_console']:
409
        raise Exception("hv parameter serial_console cannot be true")
410
    console['host'] = i['pnode']
411
    console['port'] = i['network_port']
412

    
413
    return console
414
    # return rapi.GetInstanceConsole(vm.backend_vm_id)
415

    
416

    
417
def request_status_update(vm):
418
    return vm.client.GetInstanceInfo(vm.backend_vm_id)
419

    
420

    
421
def update_status(vm, status):
422
    utils.update_state(vm, status)
423

    
424

    
425
def create_network(network, backends=None):
426
    """ Add and connect a network to backends.
427

428
    @param network: Network object
429
    @param backends: List of Backend objects. None defaults to all.
430

431
    """
432
    backend_jobs = _create_network(network, backends)
433
    connect_network(network, backend_jobs)
434
    return network
435

    
436

    
437
def _create_network(network, backends=None):
438
    """Add a network to backends.
439
    @param network: Network object
440
    @param backends: List of Backend objects. None defaults to all.
441

442
    """
443

    
444
    network_type = network.public and 'public' or 'private'
445
    if not backends:
446
        backends = Backend.objects.exclude(offline=True)
447

    
448
    tags = network.backend_tag
449
    if network.dhcp:
450
        tags.append('nfdhcpd')
451
    tags = ','.join(tags)
452

    
453
    backend_jobs = []
454
    for backend in backends:
455
        try:
456
            backend_network = BackendNetwork.objects.get(network=network,
457
                                                         backend=backend)
458
        except BackendNetwork.DoesNotExist:
459
            raise Exception("BackendNetwork for network '%s' in backend '%s'"\
460
                            " does not exist" % (network.id, backend.id))
461
        job = backend.client.CreateNetwork(
462
                network_name=network.backend_id,
463
                network=network.subnet,
464
                gateway=network.gateway,
465
                network_type=network_type,
466
                mac_prefix=backend_network.mac_prefix,
467
                tags=tags)
468
        backend_jobs.append((backend, job))
469

    
470
    return backend_jobs
471

    
472

    
473
def connect_network(network, backend_jobs=None):
474
    """Connect a network to all nodegroups.
475

476
    @param network: Network object
477
    @param backend_jobs: List of tuples of the form (Backend, jobs) which are
478
                         the backends to connect the network and the jobs on
479
                         which the connect job depends.
480

481
    """
482

    
483
    if network.type in ('PUBLIC_ROUTED', 'CUSTOM_ROUTED'):
484
        mode = 'routed'
485
    else:
486
        mode = 'bridged'
487

    
488
    if not backend_jobs:
489
        backend_jobs = [(backend, []) for backend in
490
                        Backend.objects.exclude(offline=True)]
491

    
492
    for backend, job in backend_jobs:
493
        client = backend.client
494
        for group in client.GetGroups():
495
            client.ConnectNetwork(network.backend_id, group, mode,
496
                                  network.link, [job])
497

    
498

    
499
def connect_network_group(backend, network, group):
500
    """Connect a network to a specific nodegroup of a backend.
501

502
    """
503
    if network.type in ('PUBLIC_ROUTED', 'CUSTOM_ROUTED'):
504
        mode = 'routed'
505
    else:
506
        mode = 'bridged'
507

    
508
    return backend.client.ConnectNetwork(network.backend_id, group, mode,
509
                                         network.link)
510

    
511

    
512
def delete_network(network, backends=None):
513
    """ Disconnect and a remove a network from backends.
514

515
    @param network: Network object
516
    @param backends: List of Backend objects. None defaults to all.
517

518
    """
519
    backend_jobs = disconnect_network(network, backends)
520
    _delete_network(network, backend_jobs)
521

    
522

    
523
def disconnect_network(network, backends=None):
524
    """Disconnect a network from all nodegroups.
525

526
    @param network: Network object
527
    @param backends: List of Backend objects. None defaults to all.
528

529
    """
530

    
531
    if not backends:
532
        backends = Backend.objects.exclude(offline=True)
533

    
534
    backend_jobs = []
535
    for backend in backends:
536
        client = backend.client
537
        jobs = []
538
        for group in client.GetGroups():
539
            job = client.DisconnectNetwork(network.backend_id, group)
540
            jobs.append(job)
541
        backend_jobs.append((backend, jobs))
542

    
543
    return backend_jobs
544

    
545

    
546
def disconnect_from_network(vm, nic):
547
    """Disconnect a virtual machine from a network by removing it's nic.
548

549
    @param vm: VirtualMachine object
550
    @param network: Network object
551

552
    """
553

    
554
    op = [('remove', nic.index, {})]
555
    return vm.client.ModifyInstance(vm.backend_vm_id, nics=op,
556
                                    hotplug=settings.GANETI_USE_HOTPLUG,
557
                                    dry_run=settings.TEST)
558

    
559

    
560
def _delete_network(network, backend_jobs=None):
561
    if not backend_jobs:
562
        backend_jobs = [(backend, []) for backend in
563
                Backend.objects.exclude(offline=True)]
564
    for backend, jobs in backend_jobs:
565
        backend.client.DeleteNetwork(network.backend_id, jobs)
566

    
567

    
568
def connect_to_network(vm, network, address):
569
    """Connect a virtual machine to a network.
570

571
    @param vm: VirtualMachine object
572
    @param network: Network object
573

574
    """
575

    
576
    # ip = network.dhcp and 'pool' or None
577

    
578
    nic = {'ip': address, 'network': network.backend_id}
579
    vm.client.ModifyInstance(vm.backend_vm_id, nics=[('add',  nic)],
580
                             hotplug=settings.GANETI_USE_HOTPLUG,
581
                             dry_run=settings.TEST)
582

    
583

    
584
def set_firewall_profile(vm, profile):
585
    try:
586
        tag = _firewall_tags[profile]
587
    except KeyError:
588
        raise ValueError("Unsopported Firewall Profile: %s" % profile)
589

    
590
    client = vm.client
591
    # Delete all firewall tags
592
    for t in _firewall_tags.values():
593
        client.DeleteInstanceTags(vm.backend_vm_id, [t], dry_run=settings.TEST)
594

    
595
    client.AddInstanceTags(vm.backend_vm_id, [tag], dry_run=settings.TEST)
596

    
597
    # XXX NOP ModifyInstance call to force process_net_status to run
598
    # on the dispatcher
599
    vm.client.ModifyInstance(vm.backend_vm_id,
600
                        os_name=settings.GANETI_CREATEINSTANCE_KWARGS['os'])
601

    
602

    
603
def get_ganeti_instances(backend=None, bulk=False):
604
    Instances = [c.client.GetInstances(bulk=bulk)\
605
                 for c in get_backends(backend)]
606
    return reduce(list.__add__, Instances, [])
607

    
608

    
609
def get_ganeti_nodes(backend=None, bulk=False):
610
    Nodes = [c.client.GetNodes(bulk=bulk) for c in get_backends(backend)]
611
    return reduce(list.__add__, Nodes, [])
612

    
613

    
614
def get_ganeti_jobs(backend=None, bulk=False):
615
    Jobs = [c.client.GetJobs(bulk=bulk) for c in get_backends(backend)]
616
    return reduce(list.__add__, Jobs, [])
617

    
618
##
619
##
620
##
621

    
622

    
623
def get_backends(backend=None):
624
    if backend:
625
        return [backend]
626
    return Backend.objects.filter(offline=False)
627

    
628

    
629
def get_physical_resources(backend):
630
    """ Get the physical resources of a backend.
631

632
    Get the resources of a backend as reported by the backend (not the db).
633

634
    """
635
    nodes = get_ganeti_nodes(backend, bulk=True)
636
    attr = ['mfree', 'mtotal', 'dfree', 'dtotal', 'pinst_cnt', 'ctotal']
637
    res = {}
638
    for a in attr:
639
        res[a] = 0
640
    for n in nodes:
641
        # Filter out drained, offline and not vm_capable nodes since they will
642
        # not take part in the vm allocation process
643
        if n['vm_capable'] and not n['drained'] and not n['offline']\
644
           and n['cnodes']:
645
            for a in attr:
646
                res[a] += int(n[a])
647
    return res
648

    
649

    
650
def update_resources(backend, resources=None):
651
    """ Update the state of the backend resources in db.
652

653
    """
654

    
655
    if not resources:
656
        resources = get_physical_resources(backend)
657

    
658
    backend.mfree = resources['mfree']
659
    backend.mtotal = resources['mtotal']
660
    backend.dfree = resources['dfree']
661
    backend.dtotal = resources['dtotal']
662
    backend.pinst_cnt = resources['pinst_cnt']
663
    backend.ctotal = resources['ctotal']
664
    backend.updated = datetime.now()
665
    backend.save()
666

    
667

    
668
def get_memory_from_instances(backend):
669
    """ Get the memory that is used from instances.
670

671
    Get the used memory of a backend. Note: This is different for
672
    the real memory used, due to kvm's memory de-duplication.
673

674
    """
675
    instances = backend.client.GetInstances(bulk=True)
676
    mem = 0
677
    for i in instances:
678
        mem += i['oper_ram']
679
    return mem
680

    
681
##
682
## Synchronized operations for reconciliation
683
##
684

    
685

    
686
def create_network_synced(network, backend):
687
    result = _create_network_synced(network, backend)
688
    if result[0] != 'success':
689
        return result
690
    result = connect_network_synced(network, backend)
691
    return result
692

    
693

    
694
def _create_network_synced(network, backend):
695
    client = backend.client
696

    
697
    backend_jobs = _create_network(network, [backend])
698
    (_, job) = backend_jobs[0]
699
    return wait_for_job(client, job)
700

    
701

    
702
def connect_network_synced(network, backend):
703
    if network.type in ('PUBLIC_ROUTED', 'CUSTOM_ROUTED'):
704
        mode = 'routed'
705
    else:
706
        mode = 'bridged'
707
    client = backend.client
708

    
709
    for group in client.GetGroups():
710
        job = client.ConnectNetwork(network.backend_id, group, mode,
711
                                    network.link)
712
        result = wait_for_job(client, job)
713
        if result[0] != 'success':
714
            return result
715

    
716
    return result
717

    
718

    
719
def wait_for_job(client, jobid):
720
    result = client.WaitForJobChange(jobid, ['status', 'opresult'], None, None)
721
    status = result['job_info'][0]
722
    while status not in ['success', 'error', 'cancel']:
723
        result = client.WaitForJobChange(jobid, ['status', 'opresult'],
724
                                        [result], None)
725
        status = result['job_info'][0]
726

    
727
    if status == 'success':
728
        return (status, None)
729
    else:
730
        error = result['job_info'][1]
731
        return (status, error)