Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (23.2 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.util.rapi import GanetiRapiClient
46

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

    
49

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

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

    
57

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

    
61

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

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

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

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

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

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

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

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

    
106
    vm.save()
107

    
108

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

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

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

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

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

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

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

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

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

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

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

    
161

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

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

    
178
    return networks
179

    
180

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

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

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

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

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

    
206
    back_network.save()
207

    
208

    
209
@transaction.commit_on_success
210
def process_create_progress(vm, etime, progress):
211

    
212
    percentage = int(progress)
213

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

    
220
    # FIXME: log a warning here, see #1033
221
#   if last_update > percentage:
222
#       raise ValueError("Build percentage should increase monotonically " \
223
#                        "(old = %d, new = %d)" % (last_update, percentage))
224

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

    
235
    vm.buildpercentage = percentage
236
    vm.backendtime = etime
237
    vm.save()
238

    
239

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

    
245
    # No actions to deleted and no actions beside destroy to suspended VMs
246
    if vm.deleted:
247
        raise VirtualMachine.DeletedError
248

    
249
    # No actions to machines being built. They may be destroyed, however.
250
    if vm.operstate == 'BUILD' and action != 'DESTROY':
251
        raise VirtualMachine.BuildingError
252

    
253
    vm.action = action
254
    vm.backendjobid = None
255
    vm.backendopcode = None
256
    vm.backendjobstatus = None
257
    vm.backendlogmsg = None
258

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

    
275

    
276
@transaction.commit_on_success
277
def create_instance(vm, flavor, image, password, personality):
278
    """`image` is a dictionary which should contain the keys:
279
            'backend_id', 'format' and 'metadata'
280

281
        metadata value should be a dictionary.
282
    """
283

    
284
    if settings.PUBLIC_ROUTED_USE_POOL:
285
        # Get the Network object in exclusive mode in order to
286
        # safely (isolated) reserve an IP address
287
        try:
288
            network = Network.objects.select_for_update().get(public=True)
289
        except Network.DoesNotExist:
290
            raise Exception('No public network available')
291
        pool = ippool.IPPool(network)
292
        try:
293
            address = pool.get_free_address()
294
        except ippool.IPPool.IPPoolExhausted:
295
            raise OverLimit("Can not allocate IP for new machine."
296
                            " Public network is full.")
297
        pool.save()
298
        nic = {'ip': address, 'network': settings.GANETI_PUBLIC_NETWORK}
299
    else:
300
        nic = {'ip': 'pool', 'network': settings.GANETI_PUBLIC_NETWORK}
301

    
302
    if settings.IGNORE_FLAVOR_DISK_SIZES:
303
        if image['backend_id'].find("windows") >= 0:
304
            sz = 14000
305
        else:
306
            sz = 4000
307
    else:
308
        sz = flavor.disk * 1024
309

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

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

    
330
    kw['disk_template'] = disk_template
331
    kw['disks'] = [{"size": sz}]
332
    if provider:
333
        kw['disks'][0]['provider'] = provider
334

    
335

    
336
    kw['nics'] = [nic]
337
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
338
    # kw['os'] = settings.GANETI_OS_PROVIDER
339
    kw['ip_check'] = False
340
    kw['name_check'] = False
341
    # Do not specific a node explicitly, have
342
    # Ganeti use an iallocator instead
343
    #
344
    # kw['pnode']=rapi.GetNodes()[0]
345
    kw['dry_run'] = settings.TEST
346

    
347
    kw['beparams'] = {
348
        'auto_balance': True,
349
        'vcpus': flavor.cpu,
350
        'memory': flavor.ram}
351

    
352
    kw['osparams'] = {
353
        'img_id': image['backend_id'],
354
        'img_passwd': password,
355
        'img_format': image['format']}
356
    if personality:
357
        kw['osparams']['img_personality'] = json.dumps(personality)
358

    
359
    kw['osparams']['img_properties'] = json.dumps(image['metadata'])
360

    
361
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
362
    # kw['hvparams'] = dict(serial_console=False)
363

    
364
    return vm.client.CreateInstance(**kw)
365

    
366

    
367
def delete_instance(vm):
368
    start_action(vm, 'DESTROY')
369
    vm.client.DeleteInstance(vm.backend_vm_id, dry_run=settings.TEST)
370

    
371

    
372
def reboot_instance(vm, reboot_type):
373
    assert reboot_type in ('soft', 'hard')
374
    vm.client.RebootInstance(vm.backend_vm_id, reboot_type,
375
                             dry_run=settings.TEST)
376
    log.info('Rebooting instance %s', vm.backend_vm_id)
377

    
378

    
379
def startup_instance(vm):
380
    start_action(vm, 'START')
381
    vm.client.StartupInstance(vm.backend_vm_id, dry_run=settings.TEST)
382

    
383

    
384
def shutdown_instance(vm):
385
    start_action(vm, 'STOP')
386
    vm.client.ShutdownInstance(vm.backend_vm_id, dry_run=settings.TEST)
387

    
388

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

    
409
    return console
410
    # return rapi.GetInstanceConsole(vm.backend_vm_id)
411

    
412

    
413
def request_status_update(vm):
414
    return vm.client.GetInstanceInfo(vm.backend_vm_id)
415

    
416

    
417
def update_status(vm, status):
418
    utils.update_state(vm, status)
419

    
420

    
421
def create_network(network, backends=None):
422
    """ Add and connect a network to backends.
423

424
    @param network: Network object
425
    @param backends: List of Backend objects. None defaults to all.
426

427
    """
428
    backend_jobs = _create_network(network, backends)
429
    connect_network(network, backend_jobs)
430
    return network
431

    
432

    
433
def _create_network(network, backends=None):
434
    """Add a network to backends.
435
    @param network: Network object
436
    @param backends: List of Backend objects. None defaults to all.
437

438
    """
439

    
440
    network_type = network.public and 'public' or 'private'
441
    if not backends:
442
        backends = Backend.objects.exclude(offline=True)
443

    
444
    tags = network.backend_tag
445
    if network.dhcp:
446
        tags.append('nfdhcpd')
447
    tags = ','.join(tags)
448

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

    
466
    return backend_jobs
467

    
468

    
469
def connect_network(network, backend_jobs=None):
470
    """Connect a network to all nodegroups.
471

472
    @param network: Network object
473
    @param backend_jobs: List of tuples of the form (Backend, jobs) which are
474
                         the backends to connect the network and the jobs on
475
                         which the connect job depends.
476

477
    """
478

    
479
    if network.type in ('PUBLIC_ROUTED', 'CUSTOM_ROUTED'):
480
        mode = 'routed'
481
    else:
482
        mode = 'bridged'
483

    
484
    if not backend_jobs:
485
        backend_jobs = [(backend, []) for backend in
486
                        Backend.objects.exclude(offline=True)]
487

    
488
    for backend, job in backend_jobs:
489
        client = backend.client
490
        for group in client.GetGroups():
491
            client.ConnectNetwork(network.backend_id, group, mode,
492
                                  network.link, [job])
493

    
494

    
495
def connect_network_group(backend, network, group):
496
    """Connect a network to a specific nodegroup of a backend.
497

498
    """
499
    if network.type in ('PUBLIC_ROUTED', 'CUSTOM_ROUTED'):
500
        mode = 'routed'
501
    else:
502
        mode = 'bridged'
503

    
504
    return backend.client.ConnectNetwork(network.backend_id, group, mode,
505
                                         network.link)
506

    
507

    
508
def delete_network(network, backends=None):
509
    """ Disconnect and a remove a network from backends.
510

511
    @param network: Network object
512
    @param backends: List of Backend objects. None defaults to all.
513

514
    """
515
    backend_jobs = disconnect_network(network, backends)
516
    _delete_network(network, backend_jobs)
517

    
518

    
519
def disconnect_network(network, backends=None):
520
    """Disconnect a network from all nodegroups.
521

522
    @param network: Network object
523
    @param backends: List of Backend objects. None defaults to all.
524

525
    """
526

    
527
    if not backends:
528
        backends = Backend.objects.exclude(offline=True)
529

    
530
    backend_jobs = []
531
    for backend in backends:
532
        client = backend.client
533
        jobs = []
534
        for group in client.GetGroups():
535
            job = client.DisconnectNetwork(network.backend_id, group)
536
            jobs.append(job)
537
        backend_jobs.append((backend, jobs))
538

    
539
    return backend_jobs
540

    
541

    
542
def disconnect_from_network(vm, nic):
543
    """Disconnect a virtual machine from a network by removing it's nic.
544

545
    @param vm: VirtualMachine object
546
    @param network: Network object
547

548
    """
549

    
550
    op = [('remove', nic.index, {})]
551
    return vm.client.ModifyInstance(vm.backend_vm_id, nics=op,
552
                                    hotplug=settings.GANETI_USE_HOTPLUG,
553
                                    dry_run=settings.TEST)
554

    
555

    
556
def _delete_network(network, backend_jobs=None):
557
    if not backend_jobs:
558
        backend_jobs = [(backend, []) for backend in
559
                Backend.objects.exclude(offline=True)]
560
    for backend, jobs in backend_jobs:
561
        backend.client.DeleteNetwork(network.backend_id, jobs)
562

    
563

    
564
def connect_to_network(vm, network, address):
565
    """Connect a virtual machine to a network.
566

567
    @param vm: VirtualMachine object
568
    @param network: Network object
569

570
    """
571

    
572
    # ip = network.dhcp and 'pool' or None
573

    
574
    nic = {'ip': address, 'network': network.backend_id}
575
    vm.client.ModifyInstance(vm.backend_vm_id, nics=[('add',  nic)],
576
                             hotplug=settings.GANETI_USE_HOTPLUG,
577
                             dry_run=settings.TEST)
578

    
579

    
580
def set_firewall_profile(vm, profile):
581
    try:
582
        tag = _firewall_tags[profile]
583
    except KeyError:
584
        raise ValueError("Unsopported Firewall Profile: %s" % profile)
585

    
586
    client = vm.client
587
    # Delete all firewall tags
588
    for t in _firewall_tags.values():
589
        client.DeleteInstanceTags(vm.backend_vm_id, [t], dry_run=settings.TEST)
590

    
591
    client.AddInstanceTags(vm.backend_vm_id, [tag], dry_run=settings.TEST)
592

    
593
    # XXX NOP ModifyInstance call to force process_net_status to run
594
    # on the dispatcher
595
    vm.client.ModifyInstance(vm.backend_vm_id,
596
                        os_name=settings.GANETI_CREATEINSTANCE_KWARGS['os'])
597

    
598

    
599
def get_ganeti_instances(backend=None, bulk=False):
600
    Instances = [c.client.GetInstances(bulk=bulk)\
601
                 for c in get_backends(backend)]
602
    return reduce(list.__add__, Instances, [])
603

    
604

    
605
def get_ganeti_nodes(backend=None, bulk=False):
606
    Nodes = [c.client.GetNodes(bulk=bulk) for c in get_backends(backend)]
607
    return reduce(list.__add__, Nodes, [])
608

    
609

    
610
def get_ganeti_jobs(backend=None, bulk=False):
611
    Jobs = [c.client.GetJobs(bulk=bulk) for c in get_backends(backend)]
612
    return reduce(list.__add__, Jobs, [])
613

    
614
##
615
##
616
##
617

    
618

    
619
def get_backends(backend=None):
620
    if backend:
621
        return [backend]
622
    return Backend.objects.filter(offline=False)
623

    
624

    
625
def get_physical_resources(backend):
626
    """ Get the physical resources of a backend.
627

628
    Get the resources of a backend as reported by the backend (not the db).
629

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

    
645

    
646
def update_resources(backend, resources=None):
647
    """ Update the state of the backend resources in db.
648

649
    """
650

    
651
    if not resources:
652
        resources = get_physical_resources(backend)
653

    
654
    backend.mfree = resources['mfree']
655
    backend.mtotal = resources['mtotal']
656
    backend.dfree = resources['dfree']
657
    backend.dtotal = resources['dtotal']
658
    backend.pinst_cnt = resources['pinst_cnt']
659
    backend.ctotal = resources['ctotal']
660
    backend.updated = datetime.now()
661
    backend.save()
662

    
663

    
664
def get_memory_from_instances(backend):
665
    """ Get the memory that is used from instances.
666

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

670
    """
671
    instances = backend.client.GetInstances(bulk=True)
672
    mem = 0
673
    for i in instances:
674
        mem += i['oper_ram']
675
    return mem
676

    
677
##
678
## Synchronized operations for reconciliation
679
##
680

    
681

    
682
def create_network_synced(network, backend):
683
    result = _create_network_synced(network, backend)
684
    if result[0] != 'success':
685
        return result
686
    result = connect_network_synced(network, backend)
687
    return result
688

    
689

    
690
def _create_network_synced(network, backend):
691
    client = backend.client
692

    
693
    backend_jobs = _create_network(network, [backend])
694
    (_, job) = backend_jobs[0]
695
    return wait_for_job(client, job)
696

    
697

    
698
def connect_network_synced(network, backend):
699
    if network.type in ('PUBLIC_ROUTED', 'CUSTOM_ROUTED'):
700
        mode = 'routed'
701
    else:
702
        mode = 'bridged'
703
    client = backend.client
704

    
705
    for group in client.GetGroups():
706
        job = client.ConnectNetwork(network.backend_id, group, mode,
707
                                    network.link)
708
        result = wait_for_job(client, job)
709
        if result[0] != 'success':
710
            return result
711

    
712
    return result
713

    
714

    
715
def wait_for_job(client, jobid):
716
    result = client.WaitForJobChange(jobid, ['status', 'opresult'], None, None)
717
    status = result['job_info'][0]
718
    while status not in ['success', 'error', 'cancel']:
719
        result = client.WaitForJobChange(jobid, ['status', 'opresult'],
720
                                        [result], None)
721
        status = result['job_info'][0]
722

    
723
    if status == 'success':
724
        return (status, None)
725
    else:
726
        error = result['job_info'][1]
727
        return (status, error)