Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / management / commands / reconcile-networks.py @ 8283d6c1

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
            if not bnet:
111
                # CASE-1: Paritioned network
112
                if not network.public:
113
                    bnet = reconcile_parted_network(network, bend)
114
                    if not fix:
115
                        continue
116
                else:
117
                    continue
118

    
119
            try:
120
                gnet = ganeti_networks[bend][network.id]
121
            except KeyError:
122
                # Network does not exist in backend. If the network action is
123
                # DESTROY, then we must destroy the network in the backend.
124
                # Else we have to create it!
125
                if network.action == "DESTROY" and bnet.operstate != "DELETED":
126
                    # CASE-2: Stale DB network
127
                    reconcile_stale_network(bnet)
128
                    # Skip rest reconciliation as the backend is just being
129
                    # deleted
130
                    continue
131
                else:
132
                    # CASE-3: Missing Ganeti network
133
                    reconcile_missing_network(network, bend)
134
                    # Skip rest reconciliation as the network is just
135
                    # being created
136
                    continue
137

    
138
            try:
139
                hanging_groups = ganeti_hanging_networks[bend][network.id]
140
            except KeyError:
141
                # Network is connected to all nodegroups
142
                hanging_groups = []
143

    
144
            if hanging_groups:
145
                # CASE-3: Ganeti networks not connected to all nodegroups
146
                reconcile_hanging_groups(network, bend, hanging_groups)
147
                continue
148

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

    
154
            if uses_pool:
155
                # Get ganeti IP Pools
156
                available_map, reserved_map = get_network_pool(gnet)
157
                ip_available_maps.append(available_map)
158
                ip_reserved_maps.append(reserved_map)
159

    
160
        if uses_pool and (ip_available_maps or ip_reserved_maps):
161
            # CASE-5: Unsynced IP Pools
162
            reconcile_ip_pools(network, ip_available_maps, ip_reserved_maps)
163

    
164
        if conflicting_ips:
165
            detect_conflicting_ips()
166

    
167
    # CASE-6: Orphan networks
168
    reconcile_orphan_networks(networks, ganeti_networks)
169

    
170

    
171
def get_backend_network(network, backend):
172
    try:
173
        return BackendNetwork.objects.get(network=network, backend=backend)
174
    except BackendNetwork.DoesNotExist:
175
        return None
176

    
177

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

    
187

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

    
199

    
200
def reconcile_missing_network(network, backend):
201
    write("D: Missing Ganeti network %s in backend %s\n" %
202
          (network, backend))
203
    if fix:
204
        backend_mod.create_network(network, [backend])
205
        write("F: Issued OP_NETWORK_CONNECT\n")
206

    
207

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

    
218

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

    
229

    
230
def reconcile_ip_pools(network, available_maps, reserved_maps):
231
    available_map = reduce(lambda x, y: x & y, available_maps)
232
    reserved_map = reduce(lambda x, y: x & y, reserved_maps)
233

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

    
249

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

    
264

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

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

    
285

    
286
def get_network_pool(gnet):
287
    """Return available and reserved IP maps.
288

289
    Extract the available and reserved IP map from the info return from Ganeti
290
    for a network.
291

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

    
305

    
306
def bitarray_from_map(bitmap):
307
    return bitarray.bitarray(bitmap.replace("X", "1").replace(".", "0"))
308

    
309

    
310
class Foo():
311
    def __init__(self, subnet):
312
        self.available_map = ''
313
        self.reserved_map = ''
314
        self.size = 0
315
        self.network = Foo.Foo1(subnet)
316

    
317
    class Foo1():
318
        def __init__(self, subnet):
319
            self.subnet = subnet
320
            self.gateway = None