Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / management / commands / reconcile-servers.py @ 9e20fcee

History | View | Annotate | Download (12.9 kB)

1 7e136fd8 Giorgos Verigakis
# Copyright 2011-2012 GRNET S.A. All rights reserved.
2 8007ba7b Georgios Gousios
#
3 cb409cfd Georgios Gousios
# Redistribution and use in source and binary forms, with or without
4 cb409cfd Georgios Gousios
# modification, are permitted provided that the following conditions
5 cb409cfd Georgios Gousios
# are met:
6 8007ba7b Georgios Gousios
#
7 cb409cfd Georgios Gousios
#   1. Redistributions of source code must retain the above copyright
8 cb409cfd Georgios Gousios
#      notice, this list of conditions and the following disclaimer.
9 8007ba7b Georgios Gousios
#
10 cb409cfd Georgios Gousios
#  2. Redistributions in binary form must reproduce the above copyright
11 cb409cfd Georgios Gousios
#     notice, this list of conditions and the following disclaimer in the
12 cb409cfd Georgios Gousios
#     documentation and/or other materials provided with the distribution.
13 cb409cfd Georgios Gousios
#
14 cb409cfd Georgios Gousios
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15 cb409cfd Georgios Gousios
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 cb409cfd Georgios Gousios
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 cb409cfd Georgios Gousios
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18 cb409cfd Georgios Gousios
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 cb409cfd Georgios Gousios
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 cb409cfd Georgios Gousios
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 cb409cfd Georgios Gousios
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 cb409cfd Georgios Gousios
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 cb409cfd Georgios Gousios
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 cb409cfd Georgios Gousios
# SUCH DAMAGE.
25 cb409cfd Georgios Gousios
#
26 cb409cfd Georgios Gousios
# The views and conclusions contained in the software and documentation are
27 cb409cfd Georgios Gousios
# those of the authors and should not be interpreted as representing official
28 cb409cfd Georgios Gousios
# policies, either expressed or implied, of GRNET S.A.
29 cb409cfd Georgios Gousios
#
30 9fea53cc Vangelis Koukis
"""Reconciliation management command
31 9fea53cc Vangelis Koukis

32 9fea53cc Vangelis Koukis
Management command to reconcile the contents of the Synnefo DB with
33 3b40590c Vangelis Koukis
the state of the Ganeti backend. See docstring on top of
34 3b40590c Vangelis Koukis
logic/reconciliation.py for a description of reconciliation rules.
35 9fea53cc Vangelis Koukis

36 9fea53cc Vangelis Koukis
"""
37 9fea53cc Vangelis Koukis
import sys
38 c4e55622 Christos Stavrakakis
import datetime
39 9fea53cc Vangelis Koukis
40 76a429fb Georgios Gousios
from optparse import make_option
41 9fea53cc Vangelis Koukis
42 7e136fd8 Giorgos Verigakis
from django.core.management.base import BaseCommand, CommandError
43 8007ba7b Georgios Gousios
44 3524241a Christos Stavrakakis
from synnefo.db.models import VirtualMachine, Network, pooled_rapi_client
45 c414bc87 Christos Stavrakakis
from synnefo.logic import reconciliation, utils
46 c414bc87 Christos Stavrakakis
from synnefo.logic import backend as backend_mod
47 9e20fcee Christos Stavrakakis
from synnefo.util.mac2eui64 import mac2eui64
48 c414bc87 Christos Stavrakakis
from synnefo.management.common import get_backend
49 8007ba7b Georgios Gousios
50 8007ba7b Georgios Gousios
51 76a429fb Georgios Gousios
class Command(BaseCommand):
52 9fea53cc Vangelis Koukis
    can_import_settings = True
53 9fea53cc Vangelis Koukis
54 9fea53cc Vangelis Koukis
    help = 'Reconcile contents of Synnefo DB with state of Ganeti backend'
55 9fea53cc Vangelis Koukis
    output_transaction = True  # The management command runs inside
56 9fea53cc Vangelis Koukis
                               # an SQL transaction
57 9fea53cc Vangelis Koukis
    option_list = BaseCommand.option_list + (
58 9fea53cc Vangelis Koukis
        make_option('--detect-stale', action='store_true', dest='detect_stale',
59 9fea53cc Vangelis Koukis
                    default=False, help='Detect stale VM entries in DB'),
60 9fea53cc Vangelis Koukis
        make_option('--detect-orphans', action='store_true',
61 9fea53cc Vangelis Koukis
                    dest='detect_orphans',
62 9fea53cc Vangelis Koukis
                    default=False, help='Detect orphan instances in Ganeti'),
63 9fea53cc Vangelis Koukis
        make_option('--detect-unsynced', action='store_true',
64 9fea53cc Vangelis Koukis
                    dest='detect_unsynced',
65 9fea53cc Vangelis Koukis
                    default=False, help='Detect unsynced operstate between ' +
66 9fea53cc Vangelis Koukis
                                        'DB and Ganeti'),
67 4161cb41 Christos Stavrakakis
        make_option('--detect-build-errors', action='store_true',
68 4161cb41 Christos Stavrakakis
                    dest='detect_build_errors', default=False,
69 4161cb41 Christos Stavrakakis
                    help='Detect instances with build error'),
70 0e9a423f Christos Stavrakakis
        make_option('--detect-unsynced-nics', action='store_true',
71 0e9a423f Christos Stavrakakis
                    dest='detect_unsynced_nics', default=False,
72 0e9a423f Christos Stavrakakis
                    help='Detect unsynced nics between DB and Ganeti'),
73 9fea53cc Vangelis Koukis
        make_option('--detect-all', action='store_true',
74 9fea53cc Vangelis Koukis
                    dest='detect_all',
75 9fea53cc Vangelis Koukis
                    default=False, help='Enable all --detect-* arguments'),
76 9fea53cc Vangelis Koukis
        make_option('--fix-stale', action='store_true', dest='fix_stale',
77 9fea53cc Vangelis Koukis
                    default=False, help='Fix (remove) stale DB entries in DB'),
78 9fea53cc Vangelis Koukis
        make_option('--fix-orphans', action='store_true', dest='fix_orphans',
79 9fea53cc Vangelis Koukis
                    default=False, help='Fix (remove) orphan Ganeti VMs'),
80 9fea53cc Vangelis Koukis
        make_option('--fix-unsynced', action='store_true', dest='fix_unsynced',
81 9fea53cc Vangelis Koukis
                    default=False, help='Fix server operstate in DB, set ' +
82 9fea53cc Vangelis Koukis
                                        'from Ganeti'),
83 4161cb41 Christos Stavrakakis
        make_option('--fix-build-errors', action='store_true',
84 4161cb41 Christos Stavrakakis
                    dest='fix_build_errors', default=False,
85 4161cb41 Christos Stavrakakis
                    help='Fix (remove) instances with build errors'),
86 cc92b70f Christos Stavrakakis
        make_option('--fix-unsynced-nics', action='store_true',
87 cc92b70f Christos Stavrakakis
                     dest='fix_unsynced_nics', default=False,
88 cc92b70f Christos Stavrakakis
                     help='Fix unsynced nics between DB and Ganeti'),
89 9fea53cc Vangelis Koukis
        make_option('--fix-all', action='store_true', dest='fix_all',
90 c414bc87 Christos Stavrakakis
                    default=False, help='Enable all --fix-* arguments'),
91 c414bc87 Christos Stavrakakis
        make_option('--backend-id', default=None, dest='backend-id',
92 c414bc87 Christos Stavrakakis
                    help='Reconcilie VMs only for this backend'),
93 cc92b70f Christos Stavrakakis
    )
94 9fea53cc Vangelis Koukis
95 9fea53cc Vangelis Koukis
    def _process_args(self, options):
96 9fea53cc Vangelis Koukis
        keys_detect = [k for k in options.keys() if k.startswith('detect_')]
97 9fea53cc Vangelis Koukis
        keys_fix = [k for k in options.keys() if k.startswith('fix_')]
98 9fea53cc Vangelis Koukis
99 cc3c59b3 Christos Stavrakakis
        if not reduce(lambda x, y: x or y,
100 cc3c59b3 Christos Stavrakakis
                      map(lambda x: options[x], keys_detect)):
101 cc3c59b3 Christos Stavrakakis
            options['detect_all'] = True
102 cc3c59b3 Christos Stavrakakis
103 9fea53cc Vangelis Koukis
        if options['detect_all']:
104 9fea53cc Vangelis Koukis
            for kd in keys_detect:
105 9fea53cc Vangelis Koukis
                options[kd] = True
106 9fea53cc Vangelis Koukis
        if options['fix_all']:
107 9fea53cc Vangelis Koukis
            for kf in keys_fix:
108 9fea53cc Vangelis Koukis
                options[kf] = True
109 9fea53cc Vangelis Koukis
110 9fea53cc Vangelis Koukis
        for kf in keys_fix:
111 9fea53cc Vangelis Koukis
            kd = kf.replace('fix_', 'detect_', 1)
112 9fea53cc Vangelis Koukis
            if (options[kf] and not options[kd]):
113 7e136fd8 Giorgos Verigakis
                raise CommandError("Cannot use --%s without corresponding "
114 7e136fd8 Giorgos Verigakis
                                   "--%s argument" % (kf, kd))
115 9fea53cc Vangelis Koukis
116 9fea53cc Vangelis Koukis
    def handle(self, **options):
117 9fea53cc Vangelis Koukis
        verbosity = int(options['verbosity'])
118 9fea53cc Vangelis Koukis
        self._process_args(options)
119 c414bc87 Christos Stavrakakis
        backend_id = options['backend-id']
120 c414bc87 Christos Stavrakakis
        backend = get_backend(backend_id) if backend_id else None
121 9fea53cc Vangelis Koukis
122 c414bc87 Christos Stavrakakis
        D = reconciliation.get_servers_from_db(backend)
123 c414bc87 Christos Stavrakakis
        G, GNics = reconciliation.get_instances_from_ganeti(backend)
124 9fea53cc Vangelis Koukis
125 c414bc87 Christos Stavrakakis
        DBNics = reconciliation.get_nics_from_db(backend)
126 c6ad2f2d Christos Stavrakakis
127 9fea53cc Vangelis Koukis
        #
128 9fea53cc Vangelis Koukis
        # Detect problems
129 9fea53cc Vangelis Koukis
        #
130 9fea53cc Vangelis Koukis
        if options['detect_stale']:
131 9fea53cc Vangelis Koukis
            stale = reconciliation.stale_servers_in_db(D, G)
132 9fea53cc Vangelis Koukis
            if len(stale) > 0:
133 9fea53cc Vangelis Koukis
                print >> sys.stderr, "Found the following stale server IDs: "
134 9fea53cc Vangelis Koukis
                print "    " + "\n    ".join(
135 9fea53cc Vangelis Koukis
                    [str(x) for x in stale])
136 9fea53cc Vangelis Koukis
            elif verbosity == 2:
137 9fea53cc Vangelis Koukis
                print >> sys.stderr, "Found no stale server IDs in DB."
138 9fea53cc Vangelis Koukis
139 9fea53cc Vangelis Koukis
        if options['detect_orphans']:
140 9fea53cc Vangelis Koukis
            orphans = reconciliation.orphan_instances_in_ganeti(D, G)
141 9fea53cc Vangelis Koukis
            if len(orphans) > 0:
142 9fea53cc Vangelis Koukis
                print >> sys.stderr, "Found orphan Ganeti instances with IDs: "
143 9fea53cc Vangelis Koukis
                print "    " + "\n    ".join(
144 9fea53cc Vangelis Koukis
                    [str(x) for x in orphans])
145 9fea53cc Vangelis Koukis
            elif verbosity == 2:
146 9fea53cc Vangelis Koukis
                print >> sys.stderr, "Found no orphan Ganeti instances."
147 9fea53cc Vangelis Koukis
148 9fea53cc Vangelis Koukis
        if options['detect_unsynced']:
149 9fea53cc Vangelis Koukis
            unsynced = reconciliation.unsynced_operstate(D, G)
150 9fea53cc Vangelis Koukis
            if len(unsynced) > 0:
151 9fea53cc Vangelis Koukis
                print >> sys.stderr, "The operstate of the following server" \
152 9fea53cc Vangelis Koukis
                                     " IDs is out-of-sync:"
153 9fea53cc Vangelis Koukis
                print "    " + "\n    ".join(
154 9fea53cc Vangelis Koukis
                    ["%d is %s in DB, %s in Ganeti" %
155 9fea53cc Vangelis Koukis
                     (x[0], x[1], ('UP' if x[2] else 'DOWN'))
156 9fea53cc Vangelis Koukis
                     for x in unsynced])
157 9fea53cc Vangelis Koukis
            elif verbosity == 2:
158 9fea53cc Vangelis Koukis
                print >> sys.stderr, "The operstate of all servers is in sync."
159 9fea53cc Vangelis Koukis
160 4161cb41 Christos Stavrakakis
        if options['detect_build_errors']:
161 4161cb41 Christos Stavrakakis
            build_errors = reconciliation.instances_with_build_errors(D, G)
162 4161cb41 Christos Stavrakakis
            if len(build_errors) > 0:
163 4161cb41 Christos Stavrakakis
                print >> sys.stderr, "The os for the following server IDs was "\
164 4161cb41 Christos Stavrakakis
                                     "not build successfully:"
165 4161cb41 Christos Stavrakakis
                print "    " + "\n    ".join(
166 4161cb41 Christos Stavrakakis
                    ["%d" % x for x in build_errors])
167 4161cb41 Christos Stavrakakis
            elif verbosity == 2:
168 4161cb41 Christos Stavrakakis
                print >> sys.stderr, "Found no instances with build errors."
169 4161cb41 Christos Stavrakakis
170 0e9a423f Christos Stavrakakis
        if options['detect_unsynced_nics']:
171 0e9a423f Christos Stavrakakis
            def pretty_print_nics(nics):
172 0e9a423f Christos Stavrakakis
                if not nics:
173 0e9a423f Christos Stavrakakis
                    print ''.ljust(18) + 'None'
174 0e9a423f Christos Stavrakakis
                for index, info in nics.items():
175 0e9a423f Christos Stavrakakis
                    print ''.ljust(18) + 'nic/' + str(index) + ': MAC: %s, IP: %s, Network: %s' % \
176 0e9a423f Christos Stavrakakis
                      (info['mac'], info['ipv4'], info['network'])
177 0e9a423f Christos Stavrakakis
178 0e9a423f Christos Stavrakakis
            unsynced_nics = reconciliation.unsynced_nics(DBNics, GNics)
179 0e9a423f Christos Stavrakakis
            if len(unsynced_nics) > 0:
180 e6f6627c Christos Stavrakakis
                print >> sys.stderr, "The NICs of servers with the following IDs "\
181 0e9a423f Christos Stavrakakis
                                     "are unsynced:"
182 0e9a423f Christos Stavrakakis
                for id, nics in unsynced_nics.items():
183 0e9a423f Christos Stavrakakis
                    print ''.ljust(2) + '%6d:' % id
184 0e9a423f Christos Stavrakakis
                    print ''.ljust(8) + '%8s:' % 'DB'
185 0e9a423f Christos Stavrakakis
                    pretty_print_nics(nics[0])
186 0e9a423f Christos Stavrakakis
                    print ''.ljust(8) + '%8s:' % 'Ganeti'
187 0e9a423f Christos Stavrakakis
                    pretty_print_nics(nics[1])
188 0e9a423f Christos Stavrakakis
            elif verbosity == 2:
189 0e9a423f Christos Stavrakakis
                print >> sys.stderr, "All instance nics are synced."
190 0e9a423f Christos Stavrakakis
191 9fea53cc Vangelis Koukis
        #
192 9fea53cc Vangelis Koukis
        # Then fix them
193 9fea53cc Vangelis Koukis
        #
194 9fea53cc Vangelis Koukis
        if options['fix_stale'] and len(stale) > 0:
195 9fea53cc Vangelis Koukis
            print >> sys.stderr, \
196 9fea53cc Vangelis Koukis
                "Simulating successful Ganeti removal for %d " \
197 9fea53cc Vangelis Koukis
                "servers in the DB:" % len(stale)
198 9fea53cc Vangelis Koukis
            for vm in VirtualMachine.objects.filter(pk__in=stale):
199 c4e55622 Christos Stavrakakis
                event_time = datetime.datetime.now()
200 cc92b70f Christos Stavrakakis
                backend_mod.process_op_status(
201 cc92b70f Christos Stavrakakis
                    vm=vm,
202 cc92b70f Christos Stavrakakis
                    etime=event_time,
203 cc92b70f Christos Stavrakakis
                    jobid=-0,
204 9fea53cc Vangelis Koukis
                    opcode='OP_INSTANCE_REMOVE', status='success',
205 9fea53cc Vangelis Koukis
                    logmsg='Reconciliation: simulated Ganeti event')
206 9fea53cc Vangelis Koukis
            print >> sys.stderr, "    ...done"
207 9fea53cc Vangelis Koukis
208 9fea53cc Vangelis Koukis
        if options['fix_orphans'] and len(orphans) > 0:
209 9fea53cc Vangelis Koukis
            print >> sys.stderr, \
210 9fea53cc Vangelis Koukis
                "Issuing OP_INSTANCE_REMOVE for %d Ganeti instances:" % \
211 9fea53cc Vangelis Koukis
                len(orphans)
212 9fea53cc Vangelis Koukis
            for id in orphans:
213 e64e7ade Christos Stavrakakis
                try:
214 e64e7ade Christos Stavrakakis
                    vm = VirtualMachine.objects.get(pk=id)
215 3524241a Christos Stavrakakis
                    with pooled_rapi_client(vm) as client:
216 3524241a Christos Stavrakakis
                        client.DeleteInstance(utils.id_to_instance_name(id))
217 e64e7ade Christos Stavrakakis
                except VirtualMachine.DoesNotExist:
218 5706f527 Christos Stavrakakis
                    print >> sys.stderr, "No entry for VM %d in DB !!" % id
219 9fea53cc Vangelis Koukis
            print >> sys.stderr, "    ...done"
220 9fea53cc Vangelis Koukis
221 9fea53cc Vangelis Koukis
        if options['fix_unsynced'] and len(unsynced) > 0:
222 9fea53cc Vangelis Koukis
            print >> sys.stderr, "Setting the state of %d out-of-sync VMs:" % \
223 9fea53cc Vangelis Koukis
                len(unsynced)
224 9fea53cc Vangelis Koukis
            for id, db_state, ganeti_up in unsynced:
225 9fea53cc Vangelis Koukis
                vm = VirtualMachine.objects.get(pk=id)
226 9fea53cc Vangelis Koukis
                opcode = "OP_INSTANCE_REBOOT" if ganeti_up \
227 9fea53cc Vangelis Koukis
                         else "OP_INSTANCE_SHUTDOWN"
228 c4e55622 Christos Stavrakakis
                event_time = datetime.datetime.now()
229 cc92b70f Christos Stavrakakis
                backend_mod.process_op_status(
230 cc92b70f Christos Stavrakakis
                    vm=vm, etime=event_time, jobid=-0,
231 9fea53cc Vangelis Koukis
                    opcode=opcode, status='success',
232 9fea53cc Vangelis Koukis
                    logmsg='Reconciliation: simulated Ganeti event')
233 9fea53cc Vangelis Koukis
            print >> sys.stderr, "    ...done"
234 4161cb41 Christos Stavrakakis
235 4161cb41 Christos Stavrakakis
        if options['fix_build_errors'] and len(build_errors) > 0:
236 4161cb41 Christos Stavrakakis
            print >> sys.stderr, "Setting the state of %d build-errors VMs:" % \
237 4161cb41 Christos Stavrakakis
                len(build_errors)
238 4161cb41 Christos Stavrakakis
            for id in build_errors:
239 4161cb41 Christos Stavrakakis
                vm = VirtualMachine.objects.get(pk=id)
240 4161cb41 Christos Stavrakakis
                event_time = datetime.datetime.now()
241 c414bc87 Christos Stavrakakis
                backend_mod.process_op_status(vm=vm, etime=event_time, jobid=-0,
242 4161cb41 Christos Stavrakakis
                    opcode="OP_INSTANCE_CREATE", status='error',
243 4161cb41 Christos Stavrakakis
                    logmsg='Reconciliation: simulated Ganeti event')
244 4161cb41 Christos Stavrakakis
            print >> sys.stderr, "    ...done"
245 4161cb41 Christos Stavrakakis
246 0e9a423f Christos Stavrakakis
        if options['fix_unsynced_nics'] and len(unsynced_nics) > 0:
247 0e9a423f Christos Stavrakakis
            print >> sys.stderr, "Setting the nics of %d out-of-sync VMs:" % \
248 0e9a423f Christos Stavrakakis
                                  len(unsynced_nics)
249 0e9a423f Christos Stavrakakis
            for id, nics in unsynced_nics.items():
250 0e9a423f Christos Stavrakakis
                vm = VirtualMachine.objects.get(pk=id)
251 0e9a423f Christos Stavrakakis
                nics = nics[1]  # Ganeti nics
252 0e9a423f Christos Stavrakakis
                if nics == {}:  # No nics
253 0e9a423f Christos Stavrakakis
                    vm.nics.all.delete()
254 0e9a423f Christos Stavrakakis
                    continue
255 0e9a423f Christos Stavrakakis
                for index, nic in nics.items():
256 cc3f266e Christos Stavrakakis
                    net_id = utils.id_from_network_name(nic['network'])
257 cc3f266e Christos Stavrakakis
                    subnet6 = Network.objects.get(id=net_id).subnet6
258 0e9a423f Christos Stavrakakis
                    # Produce ipv6
259 cc3f266e Christos Stavrakakis
                    ipv6 = subnet6 and mac2eui64(nic['mac'], subnet6) or None
260 0e9a423f Christos Stavrakakis
                    nic['ipv6'] = ipv6
261 0e9a423f Christos Stavrakakis
                    # Rename ipv4 to ip
262 0e9a423f Christos Stavrakakis
                    nic['ip'] = nic['ipv4']
263 0e9a423f Christos Stavrakakis
                # Dict to sorted list
264 0e9a423f Christos Stavrakakis
                final_nics = []
265 0e9a423f Christos Stavrakakis
                nics_keys = nics.keys()
266 0e9a423f Christos Stavrakakis
                nics_keys.sort()
267 0e9a423f Christos Stavrakakis
                for i in nics_keys:
268 0e9a423f Christos Stavrakakis
                    if nics[i]['network']:
269 0e9a423f Christos Stavrakakis
                        final_nics.append(nics[i])
270 0e9a423f Christos Stavrakakis
                    else:
271 0e9a423f Christos Stavrakakis
                        print 'Network of nic %d of vm %s is None. ' \
272 0e9a423f Christos Stavrakakis
                              'Can not reconcile' % (i, vm.backend_vm_id)
273 0e9a423f Christos Stavrakakis
                event_time = datetime.datetime.now()
274 cc92b70f Christos Stavrakakis
                backend_mod.process_net_status(vm=vm, etime=event_time,
275 cc92b70f Christos Stavrakakis
                                               nics=final_nics)
276 0e9a423f Christos Stavrakakis
            print >> sys.stderr, "    ...done"