Statistics
| Branch: | Tag: | Revision:

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

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

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

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

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

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

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

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

    
209

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

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

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

    
245

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

    
249

    
250

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

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