Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.2 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_ROUTED_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.type == 'PUBLIC_ROUTED' and (not
91
                        PUBLIC_ROUTED_USE_POOL))
92
        ip_available_maps = []
93
        ip_reserved_maps = []
94

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

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

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

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

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

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

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

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

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

    
211

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

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

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

    
247

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

    
251

    
252

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

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