Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / management / commands / reconcile-networks.py @ 660b9f3b

History | View | Annotate | Download (12.5 kB)

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

32
Management command to reconcile the contents of the Synnefo DB with
33
the state of the Ganeti backend. See docstring on top of
34
logic/reconciliation.py for a description of reconciliation rules.
35

36
"""
37
import sys
38
import datetime
39
import bitarray
40

    
41
from optparse import make_option
42

    
43
from django.core.management.base import BaseCommand
44
from django.db import transaction
45

    
46
from synnefo.db.models import Backend, Network, BackendNetwork
47
from synnefo.db.pools import IPPool
48
from synnefo.logic import reconciliation, utils
49
from synnefo.logic import backend as backend_mod
50

    
51
fix = False
52
write = sys.stdout.write
53

    
54

    
55
class Command(BaseCommand):
56
    help = """Reconcile contents of Synnefo DB with state of Ganeti backend
57

58
Network reconciliation can detect and fix the following cases:
59
    - Missing database entries for a network in a Ganeti backend
60
    - Stale database networks, which do no exist in the Ganeti backend
61
    - Missing Ganeti networks
62
    - Ganeti networks that are not connected to all Ganeti nodegroups
63
    - Networks that have unsynced state
64
    - Networks that have unsynced IP pools
65
    - Orphan networks in the Ganeti backend
66
"""
67

    
68
    can_import_settings = True
69
    output_transaction = True  # The management command runs inside
70
                               # an SQL transaction
71
    option_list = BaseCommand.option_list + (
72
        make_option('--fix-all', action='store_true',
73
                    dest='fix', default=False,
74
                    help='Fix all issues.'),
75
        make_option('--conflicting-ips', action='store_true',
76
                    dest='conflicting_ips', default=False,
77
                    help='Detect conflicting ips')
78
    )
79

    
80
    def handle(self, **options):
81
        global fix, write
82
        fix = options['fix']
83
        write = self.stdout.write
84
        self.verbosity = int(options['verbosity'])
85
        conflicting_ips = options['conflicting_ips']
86
        reconcile_networks(conflicting_ips)
87

    
88

    
89
def reconcile_networks(conflicting_ips=False):
90
    # Get models from DB
91
    backends = Backend.objects.exclude(offline=True)
92
    networks = Network.objects.filter(deleted=False)
93

    
94
    # Get info from all ganeti backends
95
    ganeti_networks = {}
96
    ganeti_hanging_networks = {}
97
    for b in backends:
98
        g_nets = reconciliation.get_networks_from_ganeti(b)
99
        ganeti_networks[b] = g_nets
100
        g_hanging_nets = reconciliation.hanging_networks(b, g_nets)
101
        ganeti_hanging_networks[b] = g_hanging_nets
102

    
103
    # Perform reconciliation for each network
104
    for network in networks:
105
        ip_available_maps = []
106
        ip_reserved_maps = []
107
        for bend in backends:
108
            bnet = get_backend_network(network, bend)
109
            gnet = ganeti_networks[bend].get(network.id)
110
            if not bnet:
111
                if network.floating_ip_pool:
112
                    # Network is a floating IP pool and does not exist in
113
                    # backend. We need to create it
114
                    bnet = reconcile_parted_network(network, bend)
115
                elif not gnet:
116
                    # Network does not exist either in Ganeti nor in BD.
117
                    continue
118
                else:
119
                    # Network exists in Ganeti and not in DB.
120
                    if network.action != "DESTROY" and not network.public:
121
                        bnet = reconcile_parted_network(network, bend)
122

    
123
            if not gnet:
124
                # Network does not exist in Ganeti. If the network action is
125
                # DESTROY, we have to mark as deleted in DB, else we have to
126
                # create it in Ganeti.
127
                if network.action == "DESTROY":
128
                    if bnet.operstate != "DELETED":
129
                        reconcile_stale_network(bnet)
130
                else:
131
                    reconcile_missing_network(network, bend)
132
                # Skip rest reconciliation!
133
                continue
134

    
135
            try:
136
                hanging_groups = ganeti_hanging_networks[bend][network.id]
137
            except KeyError:
138
                # Network is connected to all nodegroups
139
                hanging_groups = []
140

    
141
            if hanging_groups:
142
                # CASE-3: Ganeti networks not connected to all nodegroups
143
                reconcile_hanging_groups(network, bend, hanging_groups)
144
                continue
145

    
146
            if bnet.operstate != 'ACTIVE':
147
                # CASE-4: Unsynced network state. At this point the network
148
                # exists and is connected to all nodes so is must be active!
149
                reconcile_unsynced_network(network, bend, bnet)
150

    
151
            # Get ganeti IP Pools
152
            available_map, reserved_map = get_network_pool(gnet)
153
            ip_available_maps.append(available_map)
154
            ip_reserved_maps.append(reserved_map)
155

    
156
        if ip_available_maps or ip_reserved_maps:
157
            # CASE-5: Unsynced IP Pools
158
            reconcile_ip_pools(network, ip_available_maps, ip_reserved_maps)
159

    
160
        if conflicting_ips:
161
            detect_conflicting_ips()
162

    
163
    # CASE-6: Orphan networks
164
    reconcile_orphan_networks(networks, ganeti_networks)
165

    
166

    
167
def get_backend_network(network, backend):
168
    try:
169
        return BackendNetwork.objects.get(network=network, backend=backend)
170
    except BackendNetwork.DoesNotExist:
171
        return None
172

    
173

    
174
def reconcile_parted_network(network, backend):
175
    write("D: Missing DB entry for network %s in backend %s\n" %
176
          (network, backend))
177
    if fix:
178
        network.create_backend_network(backend)
179
        write("F: Created DB entry\n")
180
        bnet = get_backend_network(network, backend)
181
        return bnet
182

    
183

    
184
def reconcile_stale_network(backend_network):
185
    write("D: Stale DB entry for network %s in backend %s\n" %
186
          (backend_network.network, backend_network.backend))
187
    if fix:
188
        etime = datetime.datetime.now()
189
        backend_mod.process_network_status(backend_network, etime, 0,
190
                                           "OP_NETWORK_REMOVE",
191
                                           "success",
192
                                           "Reconciliation simulated event")
193
        write("F: Reconciled event: OP_NETWORK_REMOVE\n")
194

    
195

    
196
def reconcile_missing_network(network, backend):
197
    write("D: Missing Ganeti network %s in backend %s\n" %
198
          (network, backend))
199
    if fix:
200
        backend_mod.create_network(network, backend)
201
        write("F: Issued OP_NETWORK_CONNECT\n")
202

    
203

    
204
def reconcile_hanging_groups(network, backend, hanging_groups):
205
    write('D: Network %s in backend %s is not connected to '
206
          'the following groups:\n' % (network, backend))
207
    write('-  ' + '\n-  '.join(hanging_groups) + '\n')
208
    if fix:
209
        for group in hanging_groups:
210
            write('F: Connecting network %s to nodegroup %s\n'
211
                  % (network, group))
212
            backend_mod.connect_network(network, backend, depends=[],
213
                                        group=group)
214

    
215

    
216
def reconcile_unsynced_network(network, backend, backend_network):
217
    write("D: Unsynced network %s in backend %s\n" % (network, backend))
218
    if fix:
219
        write("F: Issuing OP_NETWORK_CONNECT\n")
220
        etime = datetime.datetime.now()
221
        backend_mod.process_network_status(backend_network, etime, 0,
222
                                           "OP_NETWORK_CONNECT",
223
                                           "success",
224
                                           "Reconciliation simulated eventd")
225

    
226

    
227
@transaction.commit_on_success
228
def reconcile_ip_pools(network, available_maps, reserved_maps):
229
    available_map = reduce(lambda x, y: x & y, available_maps)
230
    reserved_map = reduce(lambda x, y: x & y, reserved_maps)
231

    
232
    pool = network.get_pool()
233
    # Temporary release unused floating IPs
234
    temp_pool = network.get_pool()
235
    used_ips = network.nics.values_list("ipv4", flat=True)
236
    unused_static_ips = network.floating_ips.exclude(ipv4__in=used_ips)
237
    map(lambda ip: temp_pool.put(ip.ipv4), unused_static_ips)
238
    if temp_pool.available != available_map:
239
        write("D: Unsynced available map of network %s:\n"
240
              "\tDB: %r\n\tGB: %r\n" %
241
              (network, temp_pool.available.to01(), available_map.to01()))
242
        if fix:
243
            pool.available = available_map
244
            # Release unsued floating IPs, as they are not included in the
245
            # available map
246
            map(lambda ip: pool.reserve(ip.ipv4), unused_static_ips)
247
            pool.save()
248
    if pool.reserved != reserved_map:
249
        write("D: Unsynced reserved map of network %s:\n"
250
              "\tDB: %r\n\tGB: %r\n" %
251
              (network, pool.reserved.to01(), reserved_map.to01()))
252
        if fix:
253
            pool.reserved = reserved_map
254
            pool.save()
255

    
256

    
257
def detect_conflicting_ips(network):
258
    """Detect NIC's that have the same IP in the same network."""
259
    machine_ips = network.nics.all().values_list('ipv4', 'machine')
260
    ips = map(lambda x: x[0], machine_ips)
261
    distinct_ips = set(ips)
262
    if len(distinct_ips) < len(ips):
263
        for i in distinct_ips:
264
            ips.remove(i)
265
        for i in ips:
266
            machines = [utils.id_to_instance_name(x[1])
267
                        for x in machine_ips if x[0] == i]
268
            write('D: Conflicting IP:%s Machines: %s\n' %
269
                  (i, ', '.join(machines)))
270

    
271

    
272
def reconcile_orphan_networks(db_networks, ganeti_networks):
273
    # Detect Orphan Networks in Ganeti
274
    db_network_ids = set([net.id for net in db_networks])
275
    for back_end, ganeti_networks in ganeti_networks.items():
276
        ganeti_network_ids = set(ganeti_networks.keys())
277
        orphans = ganeti_network_ids - db_network_ids
278

    
279
        if len(orphans) > 0:
280
            write('D: Orphan Networks in backend %s:\n' % back_end.clustername)
281
            write('-  ' + '\n-  '.join([str(o) for o in orphans]) + '\n')
282
            if fix:
283
                for net_id in orphans:
284
                    write('Disconnecting and deleting network %d\n' % net_id)
285
                    try:
286
                        network = Network.objects.get(id=net_id)
287
                        backend_mod.delete_network(network,
288
                                                   backend=back_end)
289
                    except Network.DoesNotExist:
290
                        write("Not entry for network %s in DB !!\n" % net_id)
291

    
292

    
293
def get_network_pool(gnet):
294
    """Return available and reserved IP maps.
295

296
    Extract the available and reserved IP map from the info return from Ganeti
297
    for a network.
298

299
    """
300
    converter = IPPool(Foo(gnet['network']))
301
    a_map = bitarray_from_map(gnet['map'])
302
    a_map.invert()
303
    reserved = gnet['external_reservations']
304
    r_map = a_map.copy()
305
    r_map.setall(True)
306
    for address in reserved.split(','):
307
        index = converter.value_to_index(address)
308
        a_map[index] = True
309
        r_map[index] = False
310
    return a_map, r_map
311

    
312

    
313
def bitarray_from_map(bitmap):
314
    return bitarray.bitarray(bitmap.replace("X", "1").replace(".", "0"))
315

    
316

    
317
class Foo():
318
    def __init__(self, subnet):
319
        self.available_map = ''
320
        self.reserved_map = ''
321
        self.size = 0
322
        self.network = Foo.Foo1(subnet)
323

    
324
    class Foo1():
325
        def __init__(self, subnet):
326
            self.subnet = subnet
327
            self.gateway = None