Statistics
| Branch: | Tag: | Revision:

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

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.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, rprogress, wprogress):
211

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

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

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

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

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

    
242

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

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

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

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

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

    
278

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

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

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

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

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

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

    
333
    kw['disk_template'] = disk_template
334
    kw['disks'] = [{"size": sz}]
335
    if provider:
336
        kw['disks'][0]['provider'] = provider
337

    
338

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

    
350
    kw['beparams'] = {
351
        'auto_balance': True,
352
        'vcpus': flavor.cpu,
353
        'memory': flavor.ram}
354

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

    
362
    kw['osparams']['img_properties'] = json.dumps(image['metadata'])
363

    
364
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
365
    # kw['hvparams'] = dict(serial_console=False)
366

    
367
    return vm.client.CreateInstance(**kw)
368

    
369

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

    
374

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

    
380

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

    
385

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

    
390

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

    
411
    return console
412
    # return rapi.GetInstanceConsole(vm.backend_vm_id)
413

    
414

    
415
def request_status_update(vm):
416
    return vm.client.GetInstanceInfo(vm.backend_vm_id)
417

    
418

    
419
def update_status(vm, status):
420
    utils.update_state(vm, status)
421

    
422

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

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

429
    """
430
    backend_jobs = _create_network(network, backends)
431
    connect_network(network, backend_jobs)
432
    return network
433

    
434

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

440
    """
441

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

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

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

    
468
    return backend_jobs
469

    
470

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

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

479
    """
480

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

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

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

    
496

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

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

    
506
    return backend.client.ConnectNetwork(network.backend_id, group, mode,
507
                                         network.link)
508

    
509

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

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

516
    """
517
    backend_jobs = disconnect_network(network, backends)
518
    _delete_network(network, backend_jobs)
519

    
520

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

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

527
    """
528

    
529
    if not backends:
530
        backends = Backend.objects.exclude(offline=True)
531

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

    
541
    return backend_jobs
542

    
543

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

547
    @param vm: VirtualMachine object
548
    @param network: Network object
549

550
    """
551

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

    
557

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

    
565

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

569
    @param vm: VirtualMachine object
570
    @param network: Network object
571

572
    """
573

    
574
    # ip = network.dhcp and 'pool' or None
575

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

    
581

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

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

    
593
    client.AddInstanceTags(vm.backend_vm_id, [tag], dry_run=settings.TEST)
594

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

    
600

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

    
606

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

    
611

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

    
616
##
617
##
618
##
619

    
620

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

    
626

    
627
def get_physical_resources(backend):
628
    """ Get the physical resources of a backend.
629

630
    Get the resources of a backend as reported by the backend (not the db).
631

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

    
647

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

651
    """
652

    
653
    if not resources:
654
        resources = get_physical_resources(backend)
655

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

    
665

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

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

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

    
679
##
680
## Synchronized operations for reconciliation
681
##
682

    
683

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

    
691

    
692
def _create_network_synced(network, backend):
693
    client = backend.client
694

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

    
699

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

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

    
714
    return result
715

    
716

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

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