Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (9.4 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 django.core.management.base import BaseCommand
43
from django.db import transaction
44

    
45
from synnefo.db.models import Backend, Network, BackendNetwork
46
from synnefo.logic import reconciliation, backend, utils
47

    
48

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

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

    
68

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

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

    
83
    # Perform reconciliation for each network
84
    for network in networks:
85
        net_id = network.id
86
        destroying = network.action == 'DESTROY'
87
        ip_address_maps = []
88

    
89
        # Perform reconcilliation for each backend
90
        for b in backends:
91
            info = (net_id, b.clustername)
92
            back_network = None
93

    
94
            try:
95
                # Get the model describing the network to this backend
96
                back_network = BackendNetwork.objects.get(network=network,
97
                                                          backend=b)
98
            except BackendNetwork.DoesNotExist:
99
                out.write('D: No DB entry for network %d in backend %s\n' % info)
100
                if fix:
101
                    out.write('F: Created entry in DB\n')
102
                    back_network = \
103
                        BackendNetwork.objects.create(network=network,
104
                                                      backend=b)
105

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

    
130
            try:
131
                hanging_groups = ganeti_hanging_networks[b][net_id]
132
            except KeyError:
133
                # Network is connected to all nodegroups
134
                hanging_groups = []
135

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

    
156
            # Reconcile IP Pools
157
            ip_map = ganeti_networks[b][net_id]['map']
158
            ip_address_maps.append(bitarray_from_o1(ip_map))
159

    
160
        network_bitarray = reduce(lambda x, y: x | y, ip_address_maps)
161
        if not network.pool.reservations == network_bitarray:
162
            out.write('D: Unsynced pool of network %d\n' % net_id)
163
            out.write('\t DB:\t%s\n' % network.pool.reservations.to01())
164
            out.write('\t Ganeti:%s\n' % network_bitarray.to01())
165
            if fix:
166
                update_network_reservations(network, network_bitarray)
167
                out.write('F: Synchronized network pools\n')
168

    
169
        # Detect conflicting IPs: Detect NIC's that have the same IP
170
        # in the same network.
171
        if conflicting_ips:
172
            machine_ips = network.nics.all().values_list('ipv4', 'machine')
173
            ips = map(lambda x: x[0], machine_ips)
174
            distinct_ips = set(ips)
175
            if len(distinct_ips) < len(ips):
176
                out.write('D: Conflicting IP in network %s.\n' % net_id)
177
                conflicts = ips
178
                for i in distinct_ips:
179
                    conflicts.remove(i)
180
                for i in conflicts:
181
                    machines = [utils.id_to_instance_name(x[1]) \
182
                                for x in machine_ips if x[0] == i]
183
                    out.write('\tIP:%s Machines: %s\n' %
184
                              (i, ', '.join(machines)))
185
                if fix:
186
                    out.write('F: Can not fix it. Manually resolve the'
187
                              ' conflict.\n')
188

    
189
    # Detect Orphan Networks in Ganeti
190
    db_network_ids = set([net.id for net in networks])
191
    for back_end, ganeti_networks in ganeti_networks.items():
192
        ganeti_network_ids = set(ganeti_networks.keys())
193
        orphans = ganeti_network_ids - db_network_ids
194

    
195
        if len(orphans) > 0:
196
            out.write('D: Orphan Networks in backend %s:\n' % back_end.clustername)
197
            out.write('-  ' + '\n-  '.join([str(o) for o in orphans]) + '\n')
198
            client = back_end.client
199
            if fix:
200
                #XXX:Move this to backend
201
                for id in orphans:
202
                    out.write('Disconnecting and deleting network %d\n' % id)
203
                    network = utils.id_to_network_name(id)
204
                    for group in client.GetGroups():
205
                        client.DisconnectNetwork(network, group)
206
                        client.DeleteNetwork(network)
207

    
208

    
209
def bitarray_from_o1(bitmap):
210
    return bitarray.bitarray(bitmap.replace("X", "1").replace(".", "0"))
211

    
212

    
213
@transaction.commit_on_success
214
def update_network_reservations(network, reservations):
215
    network.pool.reservations = reservations
216
    network.pool.save()