Statistics
| Branch: | Tag: | Revision:

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

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

    
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
                    reconcile_parted_network(network, bend)
118

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

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

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

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

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

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

    
157
        if conflicting_ips:
158
            detect_conflicting_ips()
159

    
160
    # CASE-6: Orphan networks
161
    reconcile_orphan_networks(networks, ganeti_networks)
162

    
163

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

    
170

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

    
180

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

    
192

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

    
200

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

    
212

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

    
223

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

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

    
243

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

    
258

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

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

    
279

    
280
def get_network_pool(gnet):
281
    """Return available and reserved IP maps.
282

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

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

    
299

    
300
def bitarray_from_map(bitmap):
301
    return bitarray.bitarray(bitmap.replace("X", "1").replace(".", "0"))
302

    
303

    
304
class Foo():
305
    def __init__(self, subnet):
306
        self.available_map = ''
307
        self.reserved_map = ''
308
        self.size = 0
309
        self.network = Foo.Foo1(subnet)
310

    
311
    class Foo1():
312
        def __init__(self, subnet):
313
            self.subnet = subnet
314
            self.gateway = None