Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.9 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
from django.db import transaction
46

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

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

    
55

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

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

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

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

    
89

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

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

    
104
    # Perform reconciliation for each network
105
    for network in networks:
106
        ip_available_maps = []
107
        ip_reserved_maps = []
108
        uses_pool = not network.public or PUBLIC_USE_POOL
109
        for bend in backends:
110
            bnet = get_backend_network(network, bend)
111
            if not bnet:
112
                # CASE-1: Paritioned network
113
                if not network.public:
114
                    bnet = reconcile_parted_network(network, bend)
115
                    if not fix:
116
                        continue
117
                else:
118
                    continue
119

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

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

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

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

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

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

    
165
        if conflicting_ips:
166
            detect_conflicting_ips()
167

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

    
171

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

    
178

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

    
188

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

    
200

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

    
208

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

    
219

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

    
230

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

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

    
250

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

    
265

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

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

    
282

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

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

289
    """
290
    converter = IPPool(Foo(gnet['network']))
291
    a_map = bitarray_from_map(gnet['map'])
292
    a_map.invert()
293
    reserved = gnet['external_reservations']
294
    r_map = a_map.copy()
295
    r_map.setall(True)
296
    for address in reserved.split(','):
297
        index = converter.value_to_index(address)
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