Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.1 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 datetime
38
import bitarray
39

    
40
from optparse import make_option
41

    
42
from synnefo.settings import PUBLIC_USE_POOL
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, backend, utils
49

    
50

    
51
class Command(BaseCommand):
52
    help = 'Reconcile contents of Synnefo DB with state of Ganeti backend'
53
    can_import_settings = True
54
    output_transaction = True  # The management command runs inside
55
                               # an SQL transaction
56
    option_list = BaseCommand.option_list + (
57
        make_option('--fix-all', action='store_true',
58
                    dest='fix', default=False,
59
                    help='Fix all issues.'),
60
        make_option('--conflicting-ips', action='store_true',
61
                    dest='conflicting_ips', default=False,
62
                    help='Detect conflicting ips')
63
        )
64

    
65
    def handle(self, **options):
66
        self.verbosity = int(options['verbosity'])
67
        fix = options['fix']
68
        conflicting_ips = options['conflicting_ips']
69
        reconcile_networks(self.stdout, fix, conflicting_ips)
70

    
71

    
72
def reconcile_networks(out, fix, conflicting_ips):
73
    # Get models from DB
74
    backends = Backend.objects.exclude(offline=True)
75
    networks = Network.objects.filter(deleted=False)
76

    
77
    # Get info from all ganeti backends
78
    ganeti_networks = {}
79
    ganeti_hanging_networks = {}
80
    for b in backends:
81
        g_nets = reconciliation.get_networks_from_ganeti(b)
82
        ganeti_networks[b] = g_nets
83
        g_hanging_nets = reconciliation.hanging_networks(b, g_nets)
84
        ganeti_hanging_networks[b] = g_hanging_nets
85

    
86
    # Perform reconciliation for each network
87
    for network in networks:
88
        net_id = network.id
89
        destroying = network.action == 'DESTROY'
90
        uses_pool = not network.public or PUBLIC_USE_POOL
91
        ip_available_maps = []
92
        ip_reserved_maps = []
93

    
94
        # Perform reconcilliation for each backend
95
        for b in backends:
96
            if network.public and not \
97
                BackendNetwork.objects.filter(network=network,
98
                                              backend=b).exists():
99
                    continue
100

    
101
            info = (net_id, b.clustername)
102
            back_network = None
103

    
104
            try:
105
                # Get the model describing the network to this backend
106
                back_network = BackendNetwork.objects.get(network=network,
107
                                                          backend=b)
108
            except BackendNetwork.DoesNotExist:
109
                out.write('D: No DB entry for network %d in backend %s\n' % info)
110
                if fix:
111
                    out.write('F: Created entry in DB\n')
112
                    back_network = \
113
                        BackendNetwork.objects.create(network=network,
114
                                                      backend=b)
115
                else:
116
                    continue
117

    
118
            try:
119
                # Get the info from backend
120
                ganeti_networks[b][net_id]
121
            except KeyError:
122
                # Stale network does not exist in backend
123
                if destroying:
124
                    if back_network.operstate != "DELETED":
125
                        out.write('D: Stale network %d in backend %s\n' % info)
126
                        if fix:
127
                            out.write("F: Issued OP_NETWORK_REMOVE'\n")
128
                            etime = datetime.datetime.now()
129
                            backend.process_network_status(back_network, etime,
130
                                                0, 'OP_NETWORK_REMOVE', 'success',
131
                                                'Reconciliation simulated event.')
132
                    continue
133
                else:
134
                    # Pending network
135
                    out.write('D: Pending network %d in backend %s\n' % info)
136
                    if fix:
137
                        out.write('F: Creating network in backend.\n')
138
                        backend.create_network(network, [b])
139
                        # Skip rest reconciliation as the network is just
140
                        # being created
141
                    continue
142

    
143
            try:
144
                hanging_groups = ganeti_hanging_networks[b][net_id]
145
            except KeyError:
146
                # Network is connected to all nodegroups
147
                hanging_groups = []
148

    
149
            if hanging_groups and not destroying:
150
                # Hanging network = not connected to all nodegroups of backend
151
                out.write('D: Network %d in backend %s is not connected to '
152
                          'the following groups:\n' % info)
153
                out.write('-  ' + '\n-  '.join(hanging_groups) + '\n')
154
                if fix:
155
                    for group in hanging_groups:
156
                        out.write('F: Connecting network %d to nodegroup %s\n'
157
                                  % (net_id, group))
158
                        backend.connect_network(network, b, group=group)
159
            elif back_network and back_network.operstate != 'ACTIVE':
160
                # Network is active
161
                out.write('D: Unsynced network %d in backend %s\n' % info)
162
                if fix:
163
                    out.write("F: Issued OP_NETWORK_CONNECT\n")
164
                    etime = datetime.datetime.now()
165
                    backend.process_network_status(back_network, etime,
166
                                        0, 'OP_NETWORK_CONNECT', 'success',
167
                                        'Reconciliation simulated event.')
168
                    network = Network.objects.get(id=network.id)
169

    
170
            if uses_pool:
171
                # Reconcile IP Pools
172
                gnet = ganeti_networks[b][net_id]
173
                converter = IPPool(Foo(gnet['network']))
174
                a_map = bitarray_from_map(gnet['map'])
175
                a_map.invert()
176
                reserved = gnet['external_reservations']
177
                r_map = a_map.copy()
178
                r_map.setall(True)
179
                for address in reserved.split(','):
180
                    index = converter.value_to_index(address)
181
                    a_map[index] = True
182
                    r_map[index] = False
183
                ip_available_maps.append(a_map)
184
                ip_reserved_maps.append(r_map)
185

    
186
        if uses_pool and (ip_available_maps or ip_reserved_maps):
187
            available_map = reduce(lambda x, y: x & y, ip_available_maps)
188
            reserved_map = reduce(lambda x, y: x & y, ip_reserved_maps)
189

    
190
            pool = network.get_pool()
191
            un_available = pool.available != available_map
192
            un_reserved = pool.reserved != reserved_map
193
            if un_available or un_reserved:
194
                out.write("Detected unsynchronized pool for network %r:\n" %
195
                          network.id)
196
                if un_available:
197
                    out.write("Available:\n\tDB: %r\n\tGB: %r\n" %
198
                             (pool.available.to01(), available_map.to01()))
199
                    if fix:
200
                        pool.available = available_map
201
                if un_reserved:
202
                    out.write("Reserved:\n\tDB: %r\n\tGB: %r\n" %
203
                             (pool.reserved.to01(), reserved_map.to01()))
204
                    if fix:
205
                        pool.reserved = reserved_map
206
                if fix:
207
                    out.write("Synchronized pools for network %r.\n" % network.id)
208
            pool.save()
209

    
210

    
211
        # Detect conflicting IPs: Detect NIC's that have the same IP
212
        # in the same network.
213
        if conflicting_ips:
214
            machine_ips = network.nics.all().values_list('ipv4', 'machine')
215
            ips = map(lambda x: x[0], machine_ips)
216
            distinct_ips = set(ips)
217
            if len(distinct_ips) < len(ips):
218
                out.write('D: Conflicting IP in network %s.\n' % net_id)
219
                conflicts = ips
220
                for i in distinct_ips:
221
                    conflicts.remove(i)
222
                for i in conflicts:
223
                    machines = [utils.id_to_instance_name(x[1]) \
224
                                for x in machine_ips if x[0] == i]
225
                    out.write('\tIP:%s Machines: %s\n' %
226
                              (i, ', '.join(machines)))
227
                if fix:
228
                    out.write('F: Can not fix it. Manually resolve the'
229
                              ' conflict.\n')
230

    
231
    # Detect Orphan Networks in Ganeti
232
    db_network_ids = set([net.id for net in networks])
233
    for back_end, ganeti_networks in ganeti_networks.items():
234
        ganeti_network_ids = set(ganeti_networks.keys())
235
        orphans = ganeti_network_ids - db_network_ids
236

    
237
        if len(orphans) > 0:
238
            out.write('D: Orphan Networks in backend %s:\n' % back_end.clustername)
239
            out.write('-  ' + '\n-  '.join([str(o) for o in orphans]) + '\n')
240
            if fix:
241
                for net_id in orphans:
242
                    out.write('Disconnecting and deleting network %d\n' % net_id)
243
                    network = Network.objects.get(id=net_id)
244
                    backend.delete_network(network, backends=[back_end])
245

    
246

    
247
def bitarray_from_map(bitmap):
248
    return bitarray.bitarray(bitmap.replace("X", "1").replace(".", "0"))
249

    
250

    
251

    
252
class Foo():
253
    def __init__(self, subnet):
254
        self.available_map = ''
255
        self.reserved_map = ''
256
        self.size = 0
257
        self.network = Foo.Foo1(subnet)
258

    
259
    class Foo1():
260
        def __init__(self, subnet):
261
            self.subnet = subnet
262
            self.gateway = None