Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (13 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 c59f7e0f Christos Stavrakakis
                    dest='fix_unsynced_nics', default=False,
88 c59f7e0f 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 c59f7e0f Christos Stavrakakis
                msg = "The os for the following server IDs was not build"\
164 c59f7e0f Christos Stavrakakis
                      " successfully:"
165 c59f7e0f Christos Stavrakakis
                print >> sys.stderr, msg
166 4161cb41 Christos Stavrakakis
                print "    " + "\n    ".join(
167 4161cb41 Christos Stavrakakis
                    ["%d" % x for x in build_errors])
168 4161cb41 Christos Stavrakakis
            elif verbosity == 2:
169 4161cb41 Christos Stavrakakis
                print >> sys.stderr, "Found no instances with build errors."
170 4161cb41 Christos Stavrakakis
171 0e9a423f Christos Stavrakakis
        if options['detect_unsynced_nics']:
172 0e9a423f Christos Stavrakakis
            def pretty_print_nics(nics):
173 0e9a423f Christos Stavrakakis
                if not nics:
174 0e9a423f Christos Stavrakakis
                    print ''.ljust(18) + 'None'
175 0e9a423f Christos Stavrakakis
                for index, info in nics.items():
176 c59f7e0f Christos Stavrakakis
                    print ''.ljust(18) + 'nic/' + str(index) +\
177 c59f7e0f Christos Stavrakakis
                          ': MAC: %s, IP: %s, Network: %s' % \
178 c59f7e0f Christos Stavrakakis
                          (info['mac'], info['ipv4'], info['network'])
179 0e9a423f Christos Stavrakakis
180 0e9a423f Christos Stavrakakis
            unsynced_nics = reconciliation.unsynced_nics(DBNics, GNics)
181 0e9a423f Christos Stavrakakis
            if len(unsynced_nics) > 0:
182 c59f7e0f Christos Stavrakakis
                msg = "The NICs of the servers with the following IDs are"\
183 c59f7e0f Christos Stavrakakis
                      " unsynced:"
184 c59f7e0f Christos Stavrakakis
                print >> sys.stderr, msg
185 0e9a423f Christos Stavrakakis
                for id, nics in unsynced_nics.items():
186 0e9a423f Christos Stavrakakis
                    print ''.ljust(2) + '%6d:' % id
187 0e9a423f Christos Stavrakakis
                    print ''.ljust(8) + '%8s:' % 'DB'
188 0e9a423f Christos Stavrakakis
                    pretty_print_nics(nics[0])
189 0e9a423f Christos Stavrakakis
                    print ''.ljust(8) + '%8s:' % 'Ganeti'
190 0e9a423f Christos Stavrakakis
                    pretty_print_nics(nics[1])
191 0e9a423f Christos Stavrakakis
            elif verbosity == 2:
192 0e9a423f Christos Stavrakakis
                print >> sys.stderr, "All instance nics are synced."
193 0e9a423f Christos Stavrakakis
194 9fea53cc Vangelis Koukis
        #
195 9fea53cc Vangelis Koukis
        # Then fix them
196 9fea53cc Vangelis Koukis
        #
197 9fea53cc Vangelis Koukis
        if options['fix_stale'] and len(stale) > 0:
198 9fea53cc Vangelis Koukis
            print >> sys.stderr, \
199 9fea53cc Vangelis Koukis
                "Simulating successful Ganeti removal for %d " \
200 9fea53cc Vangelis Koukis
                "servers in the DB:" % len(stale)
201 9fea53cc Vangelis Koukis
            for vm in VirtualMachine.objects.filter(pk__in=stale):
202 c4e55622 Christos Stavrakakis
                event_time = datetime.datetime.now()
203 cc92b70f Christos Stavrakakis
                backend_mod.process_op_status(
204 cc92b70f Christos Stavrakakis
                    vm=vm,
205 cc92b70f Christos Stavrakakis
                    etime=event_time,
206 cc92b70f Christos Stavrakakis
                    jobid=-0,
207 9fea53cc Vangelis Koukis
                    opcode='OP_INSTANCE_REMOVE', status='success',
208 9fea53cc Vangelis Koukis
                    logmsg='Reconciliation: simulated Ganeti event')
209 9fea53cc Vangelis Koukis
            print >> sys.stderr, "    ...done"
210 9fea53cc Vangelis Koukis
211 9fea53cc Vangelis Koukis
        if options['fix_orphans'] and len(orphans) > 0:
212 9fea53cc Vangelis Koukis
            print >> sys.stderr, \
213 9fea53cc Vangelis Koukis
                "Issuing OP_INSTANCE_REMOVE for %d Ganeti instances:" % \
214 9fea53cc Vangelis Koukis
                len(orphans)
215 9fea53cc Vangelis Koukis
            for id in orphans:
216 e64e7ade Christos Stavrakakis
                try:
217 e64e7ade Christos Stavrakakis
                    vm = VirtualMachine.objects.get(pk=id)
218 3524241a Christos Stavrakakis
                    with pooled_rapi_client(vm) as client:
219 3524241a Christos Stavrakakis
                        client.DeleteInstance(utils.id_to_instance_name(id))
220 e64e7ade Christos Stavrakakis
                except VirtualMachine.DoesNotExist:
221 5706f527 Christos Stavrakakis
                    print >> sys.stderr, "No entry for VM %d in DB !!" % id
222 9fea53cc Vangelis Koukis
            print >> sys.stderr, "    ...done"
223 9fea53cc Vangelis Koukis
224 9fea53cc Vangelis Koukis
        if options['fix_unsynced'] and len(unsynced) > 0:
225 9fea53cc Vangelis Koukis
            print >> sys.stderr, "Setting the state of %d out-of-sync VMs:" % \
226 9fea53cc Vangelis Koukis
                len(unsynced)
227 9fea53cc Vangelis Koukis
            for id, db_state, ganeti_up in unsynced:
228 9fea53cc Vangelis Koukis
                vm = VirtualMachine.objects.get(pk=id)
229 9fea53cc Vangelis Koukis
                opcode = "OP_INSTANCE_REBOOT" if ganeti_up \
230 9fea53cc Vangelis Koukis
                         else "OP_INSTANCE_SHUTDOWN"
231 c4e55622 Christos Stavrakakis
                event_time = datetime.datetime.now()
232 cc92b70f Christos Stavrakakis
                backend_mod.process_op_status(
233 cc92b70f Christos Stavrakakis
                    vm=vm, etime=event_time, jobid=-0,
234 9fea53cc Vangelis Koukis
                    opcode=opcode, status='success',
235 9fea53cc Vangelis Koukis
                    logmsg='Reconciliation: simulated Ganeti event')
236 9fea53cc Vangelis Koukis
            print >> sys.stderr, "    ...done"
237 4161cb41 Christos Stavrakakis
238 4161cb41 Christos Stavrakakis
        if options['fix_build_errors'] and len(build_errors) > 0:
239 c59f7e0f Christos Stavrakakis
            print >> sys.stderr, "Setting the state of %d build-errors VMs:" %\
240 c59f7e0f Christos Stavrakakis
                                 len(build_errors)
241 4161cb41 Christos Stavrakakis
            for id in build_errors:
242 4161cb41 Christos Stavrakakis
                vm = VirtualMachine.objects.get(pk=id)
243 4161cb41 Christos Stavrakakis
                event_time = datetime.datetime.now()
244 c59f7e0f Christos Stavrakakis
                backend_mod.process_op_status(
245 c59f7e0f Christos Stavrakakis
                    vm=vm, etime=event_time, jobid=-0,
246 4161cb41 Christos Stavrakakis
                    opcode="OP_INSTANCE_CREATE", status='error',
247 4161cb41 Christos Stavrakakis
                    logmsg='Reconciliation: simulated Ganeti event')
248 4161cb41 Christos Stavrakakis
            print >> sys.stderr, "    ...done"
249 4161cb41 Christos Stavrakakis
250 0e9a423f Christos Stavrakakis
        if options['fix_unsynced_nics'] and len(unsynced_nics) > 0:
251 0e9a423f Christos Stavrakakis
            print >> sys.stderr, "Setting the nics of %d out-of-sync VMs:" % \
252 c59f7e0f Christos Stavrakakis
                                 len(unsynced_nics)
253 0e9a423f Christos Stavrakakis
            for id, nics in unsynced_nics.items():
254 0e9a423f Christos Stavrakakis
                vm = VirtualMachine.objects.get(pk=id)
255 0e9a423f Christos Stavrakakis
                nics = nics[1]  # Ganeti nics
256 0e9a423f Christos Stavrakakis
                if nics == {}:  # No nics
257 0e9a423f Christos Stavrakakis
                    vm.nics.all.delete()
258 0e9a423f Christos Stavrakakis
                    continue
259 0e9a423f Christos Stavrakakis
                for index, nic in nics.items():
260 cc3f266e Christos Stavrakakis
                    net_id = utils.id_from_network_name(nic['network'])
261 cc3f266e Christos Stavrakakis
                    subnet6 = Network.objects.get(id=net_id).subnet6
262 0e9a423f Christos Stavrakakis
                    # Produce ipv6
263 cc3f266e Christos Stavrakakis
                    ipv6 = subnet6 and mac2eui64(nic['mac'], subnet6) or None
264 0e9a423f Christos Stavrakakis
                    nic['ipv6'] = ipv6
265 0e9a423f Christos Stavrakakis
                    # Rename ipv4 to ip
266 0e9a423f Christos Stavrakakis
                    nic['ip'] = nic['ipv4']
267 0e9a423f Christos Stavrakakis
                # Dict to sorted list
268 0e9a423f Christos Stavrakakis
                final_nics = []
269 0e9a423f Christos Stavrakakis
                nics_keys = nics.keys()
270 0e9a423f Christos Stavrakakis
                nics_keys.sort()
271 0e9a423f Christos Stavrakakis
                for i in nics_keys:
272 0e9a423f Christos Stavrakakis
                    if nics[i]['network']:
273 0e9a423f Christos Stavrakakis
                        final_nics.append(nics[i])
274 0e9a423f Christos Stavrakakis
                    else:
275 0e9a423f Christos Stavrakakis
                        print 'Network of nic %d of vm %s is None. ' \
276 0e9a423f Christos Stavrakakis
                              'Can not reconcile' % (i, vm.backend_vm_id)
277 0e9a423f Christos Stavrakakis
                event_time = datetime.datetime.now()
278 cc92b70f Christos Stavrakakis
                backend_mod.process_net_status(vm=vm, etime=event_time,
279 cc92b70f Christos Stavrakakis
                                               nics=final_nics)
280 0e9a423f Christos Stavrakakis
            print >> sys.stderr, "    ...done"