Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.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 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.logic import reconciliation, backend, utils
48

    
49

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

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

    
70

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

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

    
85
    # Perform reconciliation for each network
86
    for network in networks:
87
        net_id = network.id
88
        destroying = network.action == 'DESTROY'
89
        uses_pool = not (network.type == 'PUBLIC_ROUTED' and (not
90
                        PUBLIC_ROUTED_USE_POOL))
91
        ip_address_maps = []
92

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

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

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

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

    
139
            try:
140
                hanging_groups = ganeti_hanging_networks[b][net_id]
141
            except KeyError:
142
                # Network is connected to all nodegroups
143
                hanging_groups = []
144

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

    
165
            if uses_pool:
166
                # Reconcile IP Pools
167
                ip_map = ganeti_networks[b][net_id]['map']
168
                ip_address_maps.append(bitarray_from_o1(ip_map))
169

    
170
        if ip_address_maps and uses_pool:
171
            network_bitarray = reduce(lambda x, y: x | y, ip_address_maps)
172
            if not network.pool.reservations == network_bitarray:
173
                out.write('D: Unsynced pool of network %d\n' % net_id)
174
                out.write('\t DB:\t%s\n' % network.pool.reservations.to01())
175
                out.write('\t Ganeti:%s\n' % network_bitarray.to01())
176
                if fix:
177
                    update_network_reservations(network, network_bitarray)
178
                    out.write('F: Synchronized network pools\n')
179

    
180
        # Detect conflicting IPs: Detect NIC's that have the same IP
181
        # in the same network.
182
        if conflicting_ips:
183
            machine_ips = network.nics.all().values_list('ipv4', 'machine')
184
            ips = map(lambda x: x[0], machine_ips)
185
            distinct_ips = set(ips)
186
            if len(distinct_ips) < len(ips):
187
                out.write('D: Conflicting IP in network %s.\n' % net_id)
188
                conflicts = ips
189
                for i in distinct_ips:
190
                    conflicts.remove(i)
191
                for i in conflicts:
192
                    machines = [utils.id_to_instance_name(x[1]) \
193
                                for x in machine_ips if x[0] == i]
194
                    out.write('\tIP:%s Machines: %s\n' %
195
                              (i, ', '.join(machines)))
196
                if fix:
197
                    out.write('F: Can not fix it. Manually resolve the'
198
                              ' conflict.\n')
199

    
200
    # Detect Orphan Networks in Ganeti
201
    db_network_ids = set([net.id for net in networks])
202
    for back_end, ganeti_networks in ganeti_networks.items():
203
        ganeti_network_ids = set(ganeti_networks.keys())
204
        orphans = ganeti_network_ids - db_network_ids
205

    
206
        if len(orphans) > 0:
207
            out.write('D: Orphan Networks in backend %s:\n' % back_end.clustername)
208
            out.write('-  ' + '\n-  '.join([str(o) for o in orphans]) + '\n')
209
            client = back_end.client
210
            if fix:
211
                #XXX:Move this to backend
212
                for id in orphans:
213
                    out.write('Disconnecting and deleting network %d\n' % id)
214
                    network = utils.id_to_network_name(id)
215
                    for group in client.GetGroups():
216
                        client.DisconnectNetwork(network, group)
217
                        client.DeleteNetwork(network)
218

    
219

    
220
def bitarray_from_o1(bitmap):
221
    return bitarray.bitarray(bitmap.replace("X", "1").replace(".", "0"))
222

    
223

    
224
@transaction.commit_on_success
225
def update_network_reservations(network, reservations):
226
    network.pool.reservations = reservations
227
    network.pool.save()