Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (23.9 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 django.conf import settings
37
from django.db import transaction
38
from datetime import datetime
39

    
40
from synnefo.db.models import (Backend, VirtualMachine, Network,
41
                               BackendNetwork, BACKEND_STATUSES,
42
                               pooled_rapi_client, VirtualMachineDiagnostic)
43
from synnefo.logic import utils
44

    
45
from logging import getLogger
46
log = getLogger(__name__)
47

    
48

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

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

    
56

    
57
@transaction.commit_on_success
58
def process_op_status(vm, etime, jobid, opcode, status, logmsg):
59
    """Process a job progress notification from the backend
60

61
    Process an incoming message from the backend (currently Ganeti).
62
    Job notifications with a terminating status (sucess, error, or canceled),
63
    also update the operating state of the VM.
64

65
    """
66
    # See #1492, #1031, #1111 why this line has been removed
67
    #if (opcode not in [x[0] for x in VirtualMachine.BACKEND_OPCODES] or
68
    if status not in [x[0] for x in BACKEND_STATUSES]:
69
        raise VirtualMachine.InvalidBackendMsgError(opcode, status)
70

    
71
    vm.backendjobid = jobid
72
    vm.backendjobstatus = status
73
    vm.backendopcode = opcode
74
    vm.backendlogmsg = logmsg
75

    
76
    # Notifications of success change the operating state
77
    state_for_success = VirtualMachine.OPER_STATE_FROM_OPCODE.get(opcode, None)
78
    if status == 'success' and state_for_success is not None:
79
        vm.operstate = state_for_success
80

    
81

    
82
    # Special case: if OP_INSTANCE_CREATE fails --> ERROR
83
    if status in ('canceled', 'error') and opcode == 'OP_INSTANCE_CREATE':
84
        vm.operstate = 'ERROR'
85
        vm.backendtime = etime
86

    
87
    if opcode == 'OP_INSTANCE_REMOVE':
88
        # Set the deleted flag explicitly, cater for admin-initiated removals
89
        # Special case: OP_INSTANCE_REMOVE fails for machines in ERROR,
90
        # when no instance exists at the Ganeti backend.
91
        # See ticket #799 for all the details.
92
        #
93
        if status == 'success' or (status == 'error' and
94
                                   vm.operstate == 'ERROR'):
95
            release_instance_nics(vm)
96
            vm.nics.all().delete()
97
            vm.deleted = True
98
            vm.operstate = state_for_success
99
            vm.backendtime = etime
100

    
101
    # Update backendtime only for jobs that have been successfully completed,
102
    # since only these jobs update the state of the VM. Else a "race condition"
103
    # may occur when a successful job (e.g. OP_INSTANCE_REMOVE) completes
104
    # before an error job and messages arrive in reversed order.
105
    if status == 'success':
106
        vm.backendtime = etime
107

    
108
    vm.save()
109

    
110

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

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

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

    
121
    ganeti_nics = process_ganeti_nics(nics)
122
    if not nics_changed(vm.nics.order_by('index'), ganeti_nics):
123
        log.debug("NICs for VM %s have not changed", vm)
124

    
125
    release_instance_nics(vm)
126

    
127
    for nic in ganeti_nics:
128
        ipv4 = nic.get('ipv4', '')
129
        if ipv4:
130
            net = nic['network']
131
            net.reserve_address(ipv4)
132

    
133
        nic['dirty'] = False
134
        vm.nics.create(**nic)
135
        # Dummy save the network, because UI uses changed-since for VMs
136
        # and Networks in order to show the VM NICs
137
        net.save()
138

    
139
    vm.backendtime = etime
140
    vm.save()
141

    
142

    
143
def process_ganeti_nics(ganeti_nics):
144
    """Process NIC dict from ganeti hooks."""
145
    new_nics = []
146
    for i, new_nic in enumerate(ganeti_nics):
147
        network = new_nic.get('network', '')
148
        n = str(network)
149
        pk = utils.id_from_network_name(n)
150

    
151
        net = Network.objects.get(pk=pk)
152

    
153
        # Get the new nic info
154
        mac = new_nic.get('mac', '')
155
        ipv4 = new_nic.get('ip', '')
156
        ipv6 = new_nic.get('ipv6', '')
157

    
158
        firewall = new_nic.get('firewall', '')
159
        firewall_profile = _reverse_tags.get(firewall, '')
160
        if not firewall_profile and net.public:
161
            firewall_profile = settings.DEFAULT_FIREWALL_PROFILE
162

    
163
        nic = {
164
               'index': i,
165
               'network': net,
166
               'mac': mac,
167
               'ipv4': ipv4,
168
               'ipv6': ipv6,
169
               'firewall_profile': firewall_profile}
170

    
171
        new_nics.append(nic)
172
    return new_nics
173

    
174

    
175
def nics_changed(old_nics, new_nics):
176
    """Return True if NICs have changed in any way."""
177
    if len(old_nics) != len(new_nics):
178
        return True
179
    for old_nic, new_nic in zip(old_nics, new_nics):
180
        if not (old_nic.ipv4 == new_nic['ipv4'] and\
181
                old_nic.ipv6 == new_nic['ipv6'] and\
182
                old_nic.mac == new_nic['mac'] and\
183
                old_nic.firewall_profile == new_nic['firewall_profile'] and\
184
                old_nic.index == new_nic['index'] and\
185
                old_nic.network == new_nic['network']):
186
            return True
187
    return False
188

    
189

    
190
def release_instance_nics(vm):
191
    for nic in vm.nics.all():
192
        net = nic.network
193
        if nic.ipv4:
194
            net.release_address(nic.ipv4)
195
        nic.delete()
196
        net.save()
197

    
198

    
199
@transaction.commit_on_success
200
def process_network_status(back_network, etime, jobid, opcode, status, logmsg):
201
    if status not in [x[0] for x in BACKEND_STATUSES]:
202
        raise Network.InvalidBackendMsgError(opcode, status)
203

    
204
    back_network.backendjobid = jobid
205
    back_network.backendjobstatus = status
206
    back_network.backendopcode = opcode
207
    back_network.backendlogmsg = logmsg
208

    
209
    # Notifications of success change the operating state
210
    state_for_success = BackendNetwork.OPER_STATE_FROM_OPCODE.get(opcode, None)
211
    if status == 'success' and state_for_success is not None:
212
        back_network.operstate = state_for_success
213

    
214
    if status in ('canceled', 'error') and opcode == 'OP_NETWORK_CREATE':
215
        utils.update_state(back_network, 'ERROR')
216
        back_network.backendtime = etime
217

    
218
    if opcode == 'OP_NETWORK_REMOVE':
219
        if status == 'success' or (status == 'error' and
220
                                   back_network.operstate == 'ERROR'):
221
            back_network.operstate = state_for_success
222
            back_network.deleted = True
223
            back_network.backendtime = etime
224

    
225
    if status == 'success':
226
        back_network.backendtime = etime
227
    back_network.save()
228

    
229

    
230
@transaction.commit_on_success
231
def process_network_modify(back_network, etime, jobid, opcode, status,
232
                           add_reserved_ips, remove_reserved_ips):
233
    assert (opcode == "OP_NETWORK_SET_PARAMS")
234
    if status not in [x[0] for x in BACKEND_STATUSES]:
235
        raise Network.InvalidBackendMsgError(opcode, status)
236

    
237
    back_network.backendjobid = jobid
238
    back_network.backendjobstatus = status
239
    back_network.opcode = opcode
240

    
241
    if add_reserved_ips or remove_reserved_ips:
242
        net = back_network.network
243
        pool = net.get_pool()
244
        if add_reserved_ips:
245
            for ip in add_reserved_ips:
246
                pool.reserve(ip, external=True)
247
        if remove_reserved_ips:
248
            for ip in remove_reserved_ips:
249
                pool.put(ip, external=True)
250
        pool.save()
251

    
252
    if status == 'success':
253
        back_network.backendtime = etime
254
    back_network.save()
255

    
256

    
257
@transaction.commit_on_success
258
def process_create_progress(vm, etime, progress):
259

    
260
    percentage = int(progress)
261

    
262
    # The percentage may exceed 100%, due to the way
263
    # snf-image:copy-progress tracks bytes read by image handling processes
264
    percentage = 100 if percentage > 100 else percentage
265
    if percentage < 0:
266
        raise ValueError("Percentage cannot be negative")
267

    
268
    # FIXME: log a warning here, see #1033
269
#   if last_update > percentage:
270
#       raise ValueError("Build percentage should increase monotonically " \
271
#                        "(old = %d, new = %d)" % (last_update, percentage))
272

    
273
    # This assumes that no message of type 'ganeti-create-progress' is going to
274
    # arrive once OP_INSTANCE_CREATE has succeeded for a Ganeti instance and
275
    # the instance is STARTED.  What if the two messages are processed by two
276
    # separate dispatcher threads, and the 'ganeti-op-status' message for
277
    # successful creation gets processed before the 'ganeti-create-progress'
278
    # message? [vkoukis]
279
    #
280
    #if not vm.operstate == 'BUILD':
281
    #    raise VirtualMachine.IllegalState("VM is not in building state")
282

    
283
    vm.buildpercentage = percentage
284
    vm.backendtime = etime
285
    vm.save()
286

    
287

    
288
def create_instance_diagnostic(vm, message, source, level="DEBUG", etime=None,
289
    details=None):
290
    """
291
    Create virtual machine instance diagnostic entry.
292

293
    :param vm: VirtualMachine instance to create diagnostic for.
294
    :param message: Diagnostic message.
295
    :param source: Diagnostic source identifier (e.g. image-helper).
296
    :param level: Diagnostic level (`DEBUG`, `INFO`, `WARNING`, `ERROR`).
297
    :param etime: The time the message occured (if available).
298
    :param details: Additional details or debug information.
299
    """
300
    VirtualMachineDiagnostic.objects.create_for_vm(vm, level, source=source,
301
            source_date=etime, message=message, details=details)
302

    
303

    
304
def create_instance(vm, public_nic, flavor, image, password=None):
305
    """`image` is a dictionary which should contain the keys:
306
            'backend_id', 'format' and 'metadata'
307

308
        metadata value should be a dictionary.
309
    """
310

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

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

    
331
    kw['disk_template'] = disk_template
332
    kw['disks'] = [{"size": flavor.disk * 1024}]
333
    if provider:
334
        kw['disks'][0]['provider'] = provider
335

    
336
        if provider == 'vlmc':
337
            kw['disks'][0]['origin'] = image['backend_id']
338

    
339
    kw['nics'] = [public_nic]
340
    if settings.GANETI_USE_HOTPLUG:
341
        kw['hotplug'] = True
342
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
343
    # kw['os'] = settings.GANETI_OS_PROVIDER
344
    kw['ip_check'] = False
345
    kw['name_check'] = False
346

    
347
    # Do not specific a node explicitly, have
348
    # Ganeti use an iallocator instead
349
    #kw['pnode'] = rapi.GetNodes()[0]
350

    
351
    kw['dry_run'] = settings.TEST
352

    
353
    kw['beparams'] = {
354
       'auto_balance': True,
355
       'vcpus': flavor.cpu,
356
       'memory': flavor.ram}
357

    
358
    if provider == 'vlmc':
359
        image_id = 'null'
360
    else:
361
        image_id = image['backend_id']
362

    
363
    kw['osparams'] = {
364
        'config_url': vm.config_url,
365
        # Store image id and format to Ganeti
366
        'img_id': image_id,
367
        'img_format': image['format']}
368

    
369
    if password:
370
        # Only for admin created VMs !!
371
        kw['osparams']['img_passwd'] = password
372

    
373
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
374
    # kw['hvparams'] = dict(serial_console=False)
375

    
376
    log.debug("Creating instance %s", utils.hide_pass(kw))
377
    with pooled_rapi_client(vm) as client:
378
        return client.CreateInstance(**kw)
379

    
380

    
381
def delete_instance(vm):
382
    with pooled_rapi_client(vm) as client:
383
        return client.DeleteInstance(vm.backend_vm_id, dry_run=settings.TEST)
384

    
385

    
386
def reboot_instance(vm, reboot_type):
387
    assert reboot_type in ('soft', 'hard')
388
    with pooled_rapi_client(vm) as client:
389
        return client.RebootInstance(vm.backend_vm_id, reboot_type,
390
                                     dry_run=settings.TEST)
391

    
392

    
393
def startup_instance(vm):
394
    with pooled_rapi_client(vm) as client:
395
        return client.StartupInstance(vm.backend_vm_id, dry_run=settings.TEST)
396

    
397

    
398
def shutdown_instance(vm):
399
    with pooled_rapi_client(vm) as client:
400
        return client.ShutdownInstance(vm.backend_vm_id, dry_run=settings.TEST)
401

    
402

    
403
def get_instance_console(vm):
404
    # RAPI GetInstanceConsole() returns endpoints to the vnc_bind_address,
405
    # which is a cluster-wide setting, either 0.0.0.0 or 127.0.0.1, and pretty
406
    # useless (see #783).
407
    #
408
    # Until this is fixed on the Ganeti side, construct a console info reply
409
    # directly.
410
    #
411
    # WARNING: This assumes that VNC runs on port network_port on
412
    #          the instance's primary node, and is probably
413
    #          hypervisor-specific.
414
    #
415
    log.debug("Getting console for vm %s", vm)
416

    
417
    console = {}
418
    console['kind'] = 'vnc'
419

    
420
    with pooled_rapi_client(vm) as client:
421
        i = client.GetInstance(vm.backend_vm_id)
422

    
423
    if i['hvparams']['serial_console']:
424
        raise Exception("hv parameter serial_console cannot be true")
425
    console['host'] = i['pnode']
426
    console['port'] = i['network_port']
427

    
428
    return console
429

    
430

    
431
def get_instance_info(vm):
432
    with pooled_rapi_client(vm) as client:
433
        return client.GetInstanceInfo(vm.backend_vm_id)
434

    
435

    
436
def create_network(network, backends=None, connect=True):
437
    """Create and connect a network."""
438
    if not backends:
439
        backends = Backend.objects.exclude(offline=True)
440

    
441
    log.debug("Creating network %s in backends %s", network, backends)
442

    
443
    for backend in backends:
444
        create_jobID = _create_network(network, backend)
445
        if connect:
446
            connect_network(network, backend, create_jobID)
447

    
448

    
449
def _create_network(network, backend):
450
    """Create a network."""
451

    
452
    network_type = network.public and 'public' or 'private'
453

    
454
    tags = network.backend_tag
455
    if network.dhcp:
456
        tags.append('nfdhcpd')
457

    
458
    if network.public:
459
        conflicts_check = True
460
    else:
461
        conflicts_check = False
462

    
463
    try:
464
        bn = BackendNetwork.objects.get(network=network, backend=backend)
465
        mac_prefix = bn.mac_prefix
466
    except BackendNetwork.DoesNotExist:
467
        raise Exception("BackendNetwork for network '%s' in backend '%s'"\
468
                        " does not exist" % (network.id, backend.id))
469

    
470
    with pooled_rapi_client(backend) as client:
471
        return client.CreateNetwork(network_name=network.backend_id,
472
                                    network=network.subnet,
473
                                    network6=network.subnet6,
474
                                    gateway=network.gateway,
475
                                    gateway6=network.gateway6,
476
                                    network_type=network_type,
477
                                    mac_prefix=mac_prefix,
478
                                    conflicts_check=conflicts_check,
479
                                    tags=tags)
480

    
481

    
482
def connect_network(network, backend, depend_job=None, group=None):
483
    """Connect a network to nodegroups."""
484
    log.debug("Connecting network %s to backend %s", network, backend)
485

    
486
    mode = "routed" if "ROUTED" in network.type else "bridged"
487

    
488
    if network.public:
489
        conflicts_check = True
490
    else:
491
        conflicts_check = False
492

    
493
    depend_jobs = [depend_job] if depend_job else []
494
    with pooled_rapi_client(backend) as client:
495
        if group:
496
            client.ConnectNetwork(network.backend_id, group, mode,
497
                                  network.link, conflicts_check, depend_jobs)
498
        else:
499
            for group in client.GetGroups():
500
                client.ConnectNetwork(network.backend_id, group, mode,
501
                                      network.link, conflicts_check,
502
                                      depend_jobs)
503

    
504

    
505
def delete_network(network, backends=None, disconnect=True):
506
    if not backends:
507
        backends = Backend.objects.exclude(offline=True)
508

    
509
    log.debug("Deleting network %s from backends %s", network, backends)
510

    
511
    for backend in backends:
512
        disconnect_jobIDs = []
513
        if disconnect:
514
            disconnect_jobIDs = disconnect_network(network, backend)
515
        _delete_network(network, backend, disconnect_jobIDs)
516

    
517

    
518
def _delete_network(network, backend, depend_jobs=[]):
519
    with pooled_rapi_client(backend) as client:
520
        return client.DeleteNetwork(network.backend_id, depend_jobs)
521

    
522

    
523
def disconnect_network(network, backend, group=None):
524
    log.debug("Disconnecting network %s to backend %s", network, backend)
525

    
526
    with pooled_rapi_client(backend) as client:
527
        if group:
528
            return [client.DisconnectNetwork(network.backend_id, group)]
529
        else:
530
            jobs = []
531
            for group in client.GetGroups():
532
                job = client.DisconnectNetwork(network.backend_id, group)
533
                jobs.append(job)
534
            return jobs
535

    
536

    
537
def connect_to_network(vm, network, address=None):
538
    nic = {'ip': address, 'network': network.backend_id}
539

    
540
    log.debug("Connecting vm %s to network %s(%s)", vm, network, address)
541

    
542
    with pooled_rapi_client(vm) as client:
543
        return client.ModifyInstance(vm.backend_vm_id, nics=[('add',  nic)],
544
                                     hotplug=settings.GANETI_USE_HOTPLUG,
545
                                     dry_run=settings.TEST)
546

    
547

    
548
def disconnect_from_network(vm, nic):
549
    op = [('remove', nic.index, {})]
550

    
551
    log.debug("Removing nic of VM %s, with index %s", vm, str(nic.index))
552

    
553
    with pooled_rapi_client(vm) as client:
554
        return client.ModifyInstance(vm.backend_vm_id, nics=op,
555
                                     hotplug=settings.GANETI_USE_HOTPLUG,
556
                                     dry_run=settings.TEST)
557

    
558

    
559
def set_firewall_profile(vm, profile):
560
    try:
561
        tag = _firewall_tags[profile]
562
    except KeyError:
563
        raise ValueError("Unsopported Firewall Profile: %s" % profile)
564

    
565
    log.debug("Setting tag of VM %s to %s", vm, profile)
566

    
567
    with pooled_rapi_client(vm) as client:
568
        # Delete all firewall tags
569
        for t in _firewall_tags.values():
570
            client.DeleteInstanceTags(vm.backend_vm_id, [t],
571
                                      dry_run=settings.TEST)
572

    
573
        client.AddInstanceTags(vm.backend_vm_id, [tag], dry_run=settings.TEST)
574

    
575
        # XXX NOP ModifyInstance call to force process_net_status to run
576
        # on the dispatcher
577
        client.ModifyInstance(vm.backend_vm_id,
578
                         os_name=settings.GANETI_CREATEINSTANCE_KWARGS['os'])
579

    
580

    
581
def get_ganeti_instances(backend=None, bulk=False):
582
    instances = []
583
    for backend in get_backends(backend):
584
        with pooled_rapi_client(backend) as client:
585
            instances.append(client.GetInstances(bulk=bulk))
586

    
587
    return reduce(list.__add__, instances, [])
588

    
589

    
590
def get_ganeti_nodes(backend=None, bulk=False):
591
    nodes = []
592
    for backend in get_backends(backend):
593
        with pooled_rapi_client(backend) as client:
594
            nodes.append(client.GetNodes(bulk=bulk))
595

    
596
    return reduce(list.__add__, nodes, [])
597

    
598

    
599
def get_ganeti_jobs(backend=None, bulk=False):
600
    jobs = []
601
    for backend in get_backends(backend):
602
        with pooled_rapi_client(backend) as client:
603
            jobs.append(client.GetJobs(bulk=bulk))
604
    return reduce(list.__add__, jobs, [])
605

    
606
##
607
##
608
##
609

    
610

    
611
def get_backends(backend=None):
612
    if backend:
613
        return [backend]
614
    return Backend.objects.filter(offline=False)
615

    
616

    
617
def get_physical_resources(backend):
618
    """ Get the physical resources of a backend.
619

620
    Get the resources of a backend as reported by the backend (not the db).
621

622
    """
623
    nodes = get_ganeti_nodes(backend, bulk=True)
624
    attr = ['mfree', 'mtotal', 'dfree', 'dtotal', 'pinst_cnt', 'ctotal']
625
    res = {}
626
    for a in attr:
627
        res[a] = 0
628
    for n in nodes:
629
        # Filter out drained, offline and not vm_capable nodes since they will
630
        # not take part in the vm allocation process
631
        if n['vm_capable'] and not n['drained'] and not n['offline']\
632
           and n['cnodes']:
633
            for a in attr:
634
                res[a] += int(n[a])
635
    return res
636

    
637

    
638
def update_resources(backend, resources=None):
639
    """ Update the state of the backend resources in db.
640

641
    """
642

    
643
    if not resources:
644
        resources = get_physical_resources(backend)
645

    
646
    backend.mfree = resources['mfree']
647
    backend.mtotal = resources['mtotal']
648
    backend.dfree = resources['dfree']
649
    backend.dtotal = resources['dtotal']
650
    backend.pinst_cnt = resources['pinst_cnt']
651
    backend.ctotal = resources['ctotal']
652
    backend.updated = datetime.now()
653
    backend.save()
654

    
655

    
656
def get_memory_from_instances(backend):
657
    """ Get the memory that is used from instances.
658

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

662
    """
663
    with pooled_rapi_client(backend) as client:
664
        instances = client.GetInstances(bulk=True)
665
    mem = 0
666
    for i in instances:
667
        mem += i['oper_ram']
668
    return mem
669

    
670
##
671
## Synchronized operations for reconciliation
672
##
673

    
674

    
675
def create_network_synced(network, backend):
676
    result = _create_network_synced(network, backend)
677
    if result[0] != 'success':
678
        return result
679
    result = connect_network_synced(network, backend)
680
    return result
681

    
682

    
683
def _create_network_synced(network, backend):
684
    with pooled_rapi_client(backend) as client:
685
        job = _create_network(network, backend)
686
        result = wait_for_job(client, job)
687
    return result
688

    
689

    
690
def connect_network_synced(network, backend):
691
    if network.type in ('PUBLIC_ROUTED', 'CUSTOM_ROUTED'):
692
        mode = 'routed'
693
    else:
694
        mode = 'bridged'
695
    with pooled_rapi_client(backend) as client:
696
        for group in client.GetGroups():
697
            job = client.ConnectNetwork(network.backend_id, group, mode,
698
                                        network.link)
699
            result = wait_for_job(client, job)
700
            if result[0] != 'success':
701
                return result
702

    
703
    return result
704

    
705

    
706
def wait_for_job(client, jobid):
707
    result = client.WaitForJobChange(jobid, ['status', 'opresult'], None, None)
708
    status = result['job_info'][0]
709
    while status not in ['success', 'error', 'cancel']:
710
        result = client.WaitForJobChange(jobid, ['status', 'opresult'],
711
                                        [result], None)
712
        status = result['job_info'][0]
713

    
714
    if status == 'success':
715
        return (status, None)
716
    else:
717
        error = result['job_info'][1]
718
        return (status, error)