Revision 22ee6892

b/snf-cyclades-app/synnefo/logic/backend.py
38 38
from django.db import transaction
39 39
from datetime import datetime
40 40

  
41
from synnefo.db.models import (Backend, VirtualMachine, Network, NetworkLink,
42
                               BACKEND_STATUSES)
41
from synnefo.db.models import (Backend, VirtualMachine, Network,
42
                               BackendNetwork, BACKEND_STATUSES)
43 43
from synnefo.logic import utils
44 44
from synnefo.util.rapi import GanetiRapiClient
45 45

  
46

  
47

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

  
50 48

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

  
60

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

  
119 118
    vm.nics.all().delete()
120 119
    for i, nic in enumerate(nics):
121
        if i == 0:
120
        network = nic.get('network', '')
121
        n = str(network)
122
        if n == settings.GANETI_PUBLIC_NETWORK:
122 123
            net = Network.objects.get(public=True)
123 124
        else:
124
            try:
125
                link = NetworkLink.objects.get(name=nic['link'])
126
            except NetworkLink.DoesNotExist:
127
                # Cannot find an instance of NetworkLink for
128
                # the link attribute specified in the notification
129
                raise NetworkLink.DoesNotExist("Cannot find a NetworkLink "
130
                    "object for link='%s'" % nic['link'])
131
            net = link.network
132
            if net is None:
133
                raise Network.DoesNotExist("NetworkLink for link='%s' not "
134
                    "associated with an existing Network instance." %
135
                    nic['link'])
125
            pk = utils.id_from_network_name(n)
126
            net = Network.objects.get(id=pk)
136 127

  
137 128
        firewall = nic.get('firewall', '')
138 129
        firewall_profile = _reverse_tags.get(firewall, '')
......
147 138
            ipv6=nic.get('ipv6', ''),
148 139
            firewall_profile=firewall_profile)
149 140

  
150
        # network nics modified, update network object
151
        net.save()
152

  
153 141
    vm.backendtime = etime
154 142
    vm.save()
143
    net.save()
144

  
145

  
146
@transaction.commit_on_success
147
def process_network_status(back_network, etime, jobid, opcode, status, logmsg):
148
    if status not in [x[0] for x in BACKEND_STATUSES]:
149
        return
150
        #raise Network.InvalidBackendMsgError(opcode, status)
151

  
152
    back_network.backendjobid = jobid
153
    back_network.backendjobstatus = status
154
    back_network.backendopcode = opcode
155
    back_network.backendlogmsg = logmsg
156

  
157
    # Notifications of success change the operating state
158
    state_for_success = BackendNetwork.OPER_STATE_FROM_OPCODE.get(opcode, None)
159
    if status == 'success' and state_for_success is not None:
160
        back_network.operstate = state_for_success
161
        if opcode == 'OP_NETWORK_REMOVE':
162
            back_network.deleted = True
163

  
164
    if status in ('canceled', 'error'):
165
        utils.update_state(back_network, 'ERROR')
166

  
167
    if (status == 'error' and opcode == 'OP_NETWORK_REMOVE' and
168
        back_network.operstate == 'ERROR'):
169
        back_network.deleted = True
170
        back_network.operstate = 'DELETED'
171

  
172
    back_network.save()
155 173

  
156 174

  
157 175
@transaction.commit_on_success
......
336 354
    utils.update_state(vm, status)
337 355

  
338 356

  
339
def create_network_link():
340
    try:
341
        last = NetworkLink.objects.order_by('-index')[0]
342
        index = last.index + 1
343
    except IndexError:
344
        index = 1
357
def create_network(network, backends=None):
358
    """ Add and connect a network to backends.
345 359

  
346
    if index <= settings.GANETI_MAX_LINK_NUMBER:
347
        name = '%s%d' % (settings.GANETI_LINK_PREFIX, index)
348
        return NetworkLink.objects.create(index=index, name=name,
349
                                            available=True)
350
    return None     # All link slots are filled
360
    @param network: Network object
361
    @param backends: List of Backend objects. None defaults to all.
351 362

  
363
    """
364
    backend_jobs = _create_network(network, backends)
365
    connect_network(network, backend_jobs)
366
    return network
352 367

  
353
@transaction.commit_on_success
354
def create_network(name, user_id):
355
    try:
356
        link = NetworkLink.objects.filter(available=True)[0]
357
    except IndexError:
358
        link = create_network_link()
359
        if not link:
360
            raise NetworkLink.NotAvailable
361

  
362
    network = Network.objects.create(
363
        name=name,
364
        userid=user_id,
365
        state='ACTIVE',
366
        link=link)
367

  
368
    link.network = network
369
    link.available = False
370
    link.save()
371 368

  
372
    return network
369
def _create_network(network, backends=None):
370
    """Add a network to backends.
371
    @param network: Network object
372
    @param backends: List of Backend objects. None defaults to all.
373 373

  
374
    """
374 375

  
375
@transaction.commit_on_success
376
def delete_network(net):
377
    link = net.link
378
    if link.name != settings.GANETI_NULL_LINK:
379
        link.available = True
380
        link.network = None
381
        link.save()
382

  
383
    for vm in net.machines.all():
384
        disconnect_from_network(vm, net)
385
        vm.save()
386
    net.state = 'DELETED'
387
    net.save()
376
    network_type = network.public and 'public' or 'private'
377

  
378
    if not backends:
379
        backends = Backend.objects.exclude(offline=True)
380

  
381
    tags = network.backend_tag
382
    if network.dhcp:
383
        tags.append('nfdhcpd')
384
    tags = ','.join(tags)
385

  
386
    backend_jobs = []
387
    for backend in backends:
388
        job = backend.client.CreateNetwork(
389
                network_name=network.backend_id,
390
                network=network.subnet,
391
                gateway=network.gateway,
392
                network_type=network_type,
393
                mac_prefix=network.mac_prefix,
394
                tags=tags)
395
        backend_jobs.append((backend, job))
396

  
397
    return backend_jobs
398

  
399

  
400
def connect_network(network, backend_jobs=None):
401
    """Connect a network to all nodegroups.
402

  
403
    @param network: Network object
404
    @param backend_jobs: List of tuples of the form (Backend, jobs) which are
405
                         the backends to connect the network and the jobs on
406
                         which the connect job depends.
407

  
408
    """
388 409

  
410
    mode = network.public and 'routed' or 'bridged'
389 411

  
390
def connect_to_network(vm, net):
391
    nic = {'mode': 'bridged', 'link': net.link.name}
392
    vm.client.ModifyInstance(vm.backend_vm_id, nics=[('add', -1, nic)],
393
                        hotplug=True, dry_run=settings.TEST)
412
    if not backend_jobs:
413
        backend_jobs = [(backend, []) for backend in
414
                        Backend.objects.exclude(offline=True)]
394 415

  
416
    for backend, job in backend_jobs:
417
        client = backend.client
418
        for group in client.GetGroups():
419
            client.ConnectNetwork(network.backend_id, group, mode,
420
                                  network.link, [job])
421

  
422

  
423
def connect_network_group(backend, network, group):
424
    """Connect a network to a specific nodegroup of a backend.
425

  
426
    """
427
    mode = network.public and 'routed' or 'bridged'
428

  
429
    return backend.client.ConnectNetwork(network.backend_id, group, mode,
430
                                         network.link)
431

  
432

  
433
def delete_network(network, backends=None):
434
    """ Disconnect and a remove a network from backends.
435

  
436
    @param network: Network object
437
    @param backends: List of Backend objects. None defaults to all.
438

  
439
    """
440
    backend_jobs = disconnect_network(network, backends)
441
    _delete_network(network, backend_jobs)
442

  
443

  
444
def disconnect_network(network, backends=None):
445
    """Disconnect a network from virtualmachines and nodegroups.
446

  
447
    @param network: Network object
448
    @param backends: List of Backend objects. None defaults to all.
449

  
450
    """
451

  
452
    if not backends:
453
        backends = Backend.objects.exclude(offline=True)
454

  
455
    backend_jobs = []
456
    for backend in backends:
457
        client = backend.client
458
        jobs = []
459
        for vm in network.machines.filter(backend=backend):
460
            job = disconnect_from_network(vm, network)
461
            jobs.append(job)
462

  
463
        jobs2 = []
464
        for group in client.GetGroups():
465
            job = client.DisconnectNetwork(network.backend_id, group, jobs)
466
            jobs2.append(job)
467
        backend_jobs.append((backend, jobs2))
468

  
469
    return backend_jobs
470

  
471

  
472
def disconnect_from_network(vm, network):
473
    """Disconnect a virtual machine from a network by removing it's nic.
474

  
475
    @param vm: VirtualMachine object
476
    @param network: Network object
477

  
478
    """
395 479

  
396
def disconnect_from_network(vm, net):
397 480
    nics = vm.nics.filter(network__public=False).order_by('index')
398
    ops = [('remove', nic.index, {}) for nic in nics if nic.network == net]
481
    ops = [('remove', nic.index, {}) for nic in nics if nic.network == network]
399 482
    if not ops:  # Vm not connected to network
400 483
        return
401
    vm.client.ModifyInstance(vm.backend_vm_id, nics=ops[::-1],
402
                        hotplug=True, dry_run=settings.TEST)
484
    job = vm.client.ModifyInstance(vm.backend_vm_id, nics=ops[::-1],
485
                                    hotplug=True, dry_run=settings.TEST)
486

  
487
    return job
488

  
489

  
490
def _delete_network(network, backend_jobs=None):
491
    if not backend_jobs:
492
        backend_jobs = [(backend, []) for backend in
493
                Backend.objects.exclude(offline=True)]
494
    for backend, jobs in backend_jobs:
495
        backend.client.DeleteNetwork(network.backend_id, jobs)
496

  
497

  
498
def connect_to_network(vm, network):
499
    """Connect a virtual machine to a network.
500

  
501
    @param vm: VirtualMachine object
502
    @param network: Network object
503

  
504
    """
505

  
506
    ip = network.dhcp and 'pool' or None
507

  
508
    nic = {'ip': ip, 'network': network.backend_id}
509
    vm.client.ModifyInstance(vm.backend_vm_id, nics=[('add',  nic)],
510
                             hotplug=True, dry_run=settings.TEST)
403 511

  
404 512

  
405 513
def set_firewall_profile(vm, profile):
b/snf-cyclades-app/synnefo/logic/callbacks.py
33 33
import logging
34 34
import json
35 35
from functools import wraps
36
from datetime import datetime
37 36

  
38
from synnefo.db.models import VirtualMachine
37
from synnefo.db.models import Backend, VirtualMachine, Network, BackendNetwork
39 38
from synnefo.logic import utils, backend
40 39

  
41 40
from synnefo.lib.utils import merge_time
......
57 56
            msg = json.loads(message['body'])
58 57
            func(msg)
59 58
            client.basic_ack(message)
60
        except ValueError:
61
            log.error("Incoming message not in JSON format: %s", message)
59
        except ValueError as e:
60
            log.error("Incoming message not in JSON format %s: %s", e, message)
62 61
            client.basic_ack(message)
63
        except KeyError:
64
            log.error("Malformed incoming JSON, missing attributes: %s",
65
                      message)
62
        except KeyError as e:
63
            log.error("Malformed incoming JSON, missing attribute %s: %s",
64
                      e, message)
66 65
            client.basic_ack(message)
67 66
        except Exception as e:
68 67
            log.exception("Unexpected error: %s, msg: %s", e, msg)
69 68

  
70 69
    return wrapper
71 70

  
71

  
72 72
def instance_from_msg(func):
73 73
    """ Decorator for getting the VirtualMachine object of the msg.
74 74

  
......
87 87
                      msg['instance'], vm_id)
88 88
    return wrapper
89 89

  
90

  
90 91
def network_from_msg(func):
91
    """ Decorator for getting the Network object of the msg.
92
    """ Decorator for getting the BackendNetwork object of the msg.
92 93

  
93 94
    """
94 95
    @handle_message_delivery
......
97 98
        try:
98 99
            network_id = utils.id_from_network_name(msg["network"])
99 100
            network = Network.objects.get(id=network_id)
100
            func(network, msg)
101
            backend = Backend.objects.get(clustername=msg['cluster'])
102
            backend_network = BackendNetwork.objects.get(network=network,
103
                                                         backend=backend)
104
            func(backend_network, msg)
101 105
        except Network.InvalidBackendIdError:
102 106
            log.debug("Ignoring msg for unknown network %s.", msg['network'])
103 107
        except Network.DoesNotExist:
104
            log.error("Network %s with id %d not found in DB.",
105
                      msg['network'], vm_id)
108
            log.error("Network %s not found in DB.", msg['network'])
109
        except Backend.DoesNotExist:
110
            log.error("Backend %s not found in DB.", msg['cluster'])
111
        except BackendNetwork.DoesNotExist:
112
            log.error("Network %s on backend %s not found in DB.",
113
                      msg['network'], msg['cluster'])
106 114
    return wrapper
107 115

  
116

  
108 117
def if_update_required(func):
109 118
    """
110 119
    Decorator for checking if an incoming message needs to update the db.
......
113 122
    - The message has been redelivered and the action has already been
114 123
      completed. In this case the event_time will be equal with the one
115 124
      in the database.
116
    - The message describes a previous state in the ganeti, from the one that is
117
      described in the db. In this case the event_time will be smaller from the
118
      one in the database.
125
    - The message describes a previous state in the ganeti, from the one that
126
      is described in the db. In this case the event_time will be smaller from
127
      the one in the database.
119 128

  
120 129
    """
121 130
    @wraps(func)
......
123 132
        event_time = merge_time(msg['event_time'])
124 133
        db_time = target.backendtime
125 134

  
126
        if event_time <= db_time:
135
        if db_time and event_time <= db_time:
127 136
            format_ = "%d/%m/%y %H:%M:%S:%f"
128 137
            log.debug("Ignoring message %s.\nevent_timestamp: %s db_timestamp: %s",
129 138
                      msg,
......
179 188
        log.error("Message is of unknown type %s.", msg['type'])
180 189
        return
181 190

  
191
    backend.process_network_status(network, event_time,
192
                                   msg['jobId'], msg['operation'],
193
                                   msg['status'], msg['logmsg'])
182 194

  
183
    log.debug("Done processing ganeti-network-status msg for vm %s.",
184
              msg['instance'])
195
    log.debug("Done processing ganeti-network-status msg for network %s.",
196
              msg['network'])
185 197

  
186 198

  
187 199
@instance_from_msg
......
205 217
        log.debug("Msg: %s", message['body'])
206 218
        client.basic_ack(message)
207 219
    except Exception as e:
208
        log.exception("Could not receive message")
220
        log.exception("Could not receive message %s" % e)
b/snf-cyclades-app/synnefo/logic/utils.py
29 29

  
30 30
# Utility functions
31 31

  
32
from synnefo.db.models import VirtualMachine
32
from synnefo.db.models import VirtualMachine, Network
33 33

  
34 34
from django.conf import settings
35 35

  
......
48 48

  
49 49
    return int(ns)
50 50

  
51
def id_from_network_name(name):
52
    """Returns Network's Django id, given a ganeti machine name.
53

  
54
    Strips the ganeti prefix atm. Needs a better name!
55

  
56
    """
57
    if not str(name).startswith(settings.BACKEND_PREFIX_ID):
58
        raise Network.InvalidBackendIdError(str(name))
59
    ns = str(name).replace(settings.BACKEND_PREFIX_ID, "", 1)
60
    if not ns.isdigit():
61
        raise Network.InvalidBackendIdError(str(name))
62

  
63
    return int(ns)
64

  
65

  
51 66
def get_rsapi_state(vm):
52 67
    """Returns the API state for a virtual machine
53 68

  
b/snf-cyclades-gtools/synnefo/ganeti/hook.py
76 76
    """
77 77
    nics = {}
78 78

  
79
    key_to_attr = { 'IP': 'ip', 'MAC': 'mac', 'BRIDGE': 'link' }
79
    key_to_attr = { 'IP': 'ip', 'MAC': 'mac', 'BRIDGE': 'link', 'NETWORK' : 'network' }
80 80

  
81 81
    for env in environ.keys():
82 82
        if env.startswith("GANETI_INSTANCE_NIC"):
83 83
            s = env.replace("GANETI_INSTANCE_NIC", "").split('_', 1)
84
            if len(s) == 2 and s[0].isdigit() and s[1] in ('MAC', 'IP', 'BRIDGE'):
84
            if len(s) == 2 and s[0].isdigit() and s[1] in ('MAC', 'IP', 'BRIDGE', 'NETWORK'):
85 85
                index = int(s[0])
86 86
                key = key_to_attr[s[1]]
87 87

  

Also available in: Unified diff