Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / management / commands / reconcile-networks.py @ 2796f26a

History | View | Annotate | Download (12 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 synnefo.settings import PUBLIC_USE_POOL
44
from django.core.management.base import BaseCommand
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
        uses_pool = not network.public or PUBLIC_USE_POOL
108
        for bend in backends:
109
            bnet = get_backend_network(network, bend)
110
            gnet = ganeti_networks[bend].get(network.id)
111
            if not (bnet or gnet):
112
                # Network does not exist either in Ganeti nor in BD.
113
                continue
114
            if not bnet and gnet:
115
                # Network exists in Ganeti and not in DB.
116
                if network.action != "DESTROY" and not network.public:
117
                    bnet = reconcile_parted_network(network, bend)
118
                    if not bnet:
119
                        continue
120

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

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

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

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

    
149
            if uses_pool:
150
                # Get ganeti IP Pools
151
                available_map, reserved_map = get_network_pool(gnet)
152
                ip_available_maps.append(available_map)
153
                ip_reserved_maps.append(reserved_map)
154

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

    
159
        if conflicting_ips:
160
            detect_conflicting_ips(network)
161

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

    
165

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

    
172

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

    
182

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

    
194

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

    
202

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

    
214

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

    
225

    
226
def reconcile_ip_pools(network, available_maps, reserved_maps):
227
    available_map = reduce(lambda x, y: x & y, available_maps)
228
    reserved_map = reduce(lambda x, y: x & y, reserved_maps)
229

    
230
    pool = network.get_pool()
231
    if pool.available != available_map:
232
        write("D: Unsynced available map of network %s:\n"
233
              "\tDB: %r\n\tGB: %r\n" %
234
              (network, pool.available.to01(), available_map.to01()))
235
        if fix:
236
            pool.available = available_map
237
    if pool.reserved != reserved_map:
238
        write("D: Unsynced reserved map of network %s:\n"
239
              "\tDB: %r\n\tGB: %r\n" %
240
              (network, pool.reserved.to01(), reserved_map.to01()))
241
        if fix:
242
            pool.reserved = reserved_map
243
    pool.save()
244

    
245

    
246
def detect_conflicting_ips(network):
247
    """Detect NIC's that have the same IP in the same network."""
248
    machine_ips = network.nics.all().values_list('ipv4', 'machine')
249
    ips = map(lambda x: x[0], machine_ips)
250
    distinct_ips = set(ips)
251
    if len(distinct_ips) < len(ips):
252
        for i in distinct_ips:
253
            ips.remove(i)
254
        for i in ips:
255
            machines = [utils.id_to_instance_name(x[1])
256
                        for x in machine_ips if x[0] == i]
257
            write('D: Conflicting IP:%s Machines: %s\n' %
258
                  (i, ', '.join(machines)))
259

    
260

    
261
def reconcile_orphan_networks(db_networks, ganeti_networks):
262
    # Detect Orphan Networks in Ganeti
263
    db_network_ids = set([net.id for net in db_networks])
264
    for back_end, ganeti_networks in ganeti_networks.items():
265
        ganeti_network_ids = set(ganeti_networks.keys())
266
        orphans = ganeti_network_ids - db_network_ids
267

    
268
        if len(orphans) > 0:
269
            write('D: Orphan Networks in backend %s:\n' % back_end.clustername)
270
            write('-  ' + '\n-  '.join([str(o) for o in orphans]) + '\n')
271
            if fix:
272
                for net_id in orphans:
273
                    write('Disconnecting and deleting network %d\n' % net_id)
274
                    try:
275
                        network = Network.objects.get(id=net_id)
276
                        backend_mod.delete_network(network,
277
                                                   backend=back_end)
278
                    except Network.DoesNotExist:
279
                        write("Not entry for network %s in DB !!\n" % net_id)
280

    
281

    
282
def get_network_pool(gnet):
283
    """Return available and reserved IP maps.
284

285
    Extract the available and reserved IP map from the info return from Ganeti
286
    for a network.
287

288
    """
289
    converter = IPPool(Foo(gnet['network']))
290
    a_map = bitarray_from_map(gnet['map'])
291
    a_map.invert()
292
    reserved = gnet['external_reservations']
293
    r_map = a_map.copy()
294
    r_map.setall(True)
295
    for address in reserved.split(','):
296
        if address:
297
            index = converter.value_to_index(address.strip())
298
            a_map[index] = True
299
            r_map[index] = False
300
    return a_map, r_map
301

    
302

    
303
def bitarray_from_map(bitmap):
304
    return bitarray.bitarray(bitmap.replace("X", "1").replace(".", "0"))
305

    
306

    
307
class Foo():
308
    def __init__(self, subnet):
309
        self.available_map = ''
310
        self.reserved_map = ''
311
        self.size = 0
312
        self.network = Foo.Foo1(subnet)
313

    
314
    class Foo1():
315
        def __init__(self, subnet):
316
            self.subnet = subnet
317
            self.gateway = None