Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (7.8 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 sys
38

    
39
from optparse import make_option
40

    
41
from django.conf import settings
42
from django.core.management.base import BaseCommand, CommandError
43

    
44
from synnefo.db.models import VirtualMachine
45
from synnefo.logic import reconciliation, backend
46
from synnefo.util.rapi import GanetiRapiClient
47

    
48

    
49
class Command(BaseCommand):
50
    can_import_settings = True
51

    
52
    help = 'Reconcile contents of Synnefo DB with state of Ganeti backend'
53
    output_transaction = True  # The management command runs inside
54
                               # an SQL transaction
55
    option_list = BaseCommand.option_list + (
56
        make_option('--detect-stale', action='store_true', dest='detect_stale',
57
                    default=False, help='Detect stale VM entries in DB'),
58
        make_option('--detect-orphans', action='store_true',
59
                    dest='detect_orphans',
60
                    default=False, help='Detect orphan instances in Ganeti'),
61
        make_option('--detect-unsynced', action='store_true',
62
                    dest='detect_unsynced',
63
                    default=False, help='Detect unsynced operstate between ' +
64
                                        'DB and Ganeti'),
65
        make_option('--detect-all', action='store_true',
66
                    dest='detect_all',
67
                    default=False, help='Enable all --detect-* arguments'),
68
        make_option('--fix-stale', action='store_true', dest='fix_stale',
69
                    default=False, help='Fix (remove) stale DB entries in DB'),
70
        make_option('--fix-orphans', action='store_true', dest='fix_orphans',
71
                    default=False, help='Fix (remove) orphan Ganeti VMs'),
72
        make_option('--fix-unsynced', action='store_true', dest='fix_unsynced',
73
                    default=False, help='Fix server operstate in DB, set ' +
74
                                        'from Ganeti'),
75
        make_option('--fix-all', action='store_true', dest='fix_all',
76
                    default=False, help='Enable all --fix-* arguments'))
77

    
78
    def _process_args(self, options):
79
        keys_detect = [k for k in options.keys() if k.startswith('detect_')]
80
        keys_fix = [k for k in options.keys() if k.startswith('fix_')]
81

    
82
        if options['detect_all']:
83
            for kd in keys_detect:
84
                options[kd] = True
85
        if options['fix_all']:
86
            for kf in keys_fix:
87
                options[kf] = True
88

    
89
        if not reduce(lambda x, y: x or y,
90
                      map(lambda x: options[x], keys_detect)):
91
            raise CommandError("At least one of --detect-* must be specified")
92

    
93
        for kf in keys_fix:
94
            kd = kf.replace('fix_', 'detect_', 1)
95
            if (options[kf] and not options[kd]):
96
                raise CommandError("Cannot use --%s without corresponding "
97
                                   "--%s argument" % (kf, kd))
98

    
99
    def handle(self, **options):
100
        verbosity = int(options['verbosity'])
101
        self._process_args(options)
102

    
103
        D = reconciliation.get_servers_from_db()
104
        G = reconciliation.get_instances_from_ganeti()
105

    
106
        #
107
        # Detect problems
108
        #
109
        if options['detect_stale']:
110
            stale = reconciliation.stale_servers_in_db(D, G)
111
            if len(stale) > 0:
112
                print >> sys.stderr, "Found the following stale server IDs: "
113
                print "    " + "\n    ".join(
114
                    [str(x) for x in stale])
115
            elif verbosity == 2:
116
                print >> sys.stderr, "Found no stale server IDs in DB."
117

    
118
        if options['detect_orphans']:
119
            orphans = reconciliation.orphan_instances_in_ganeti(D, G)
120
            if len(orphans) > 0:
121
                print >> sys.stderr, "Found orphan Ganeti instances with IDs: "
122
                print "    " + "\n    ".join(
123
                    [str(x) for x in orphans])
124
            elif verbosity == 2:
125
                print >> sys.stderr, "Found no orphan Ganeti instances."
126

    
127
        if options['detect_unsynced']:
128
            unsynced = reconciliation.unsynced_operstate(D, G)
129
            if len(unsynced) > 0:
130
                print >> sys.stderr, "The operstate of the following server" \
131
                                     " IDs is out-of-sync:"
132
                print "    " + "\n    ".join(
133
                    ["%d is %s in DB, %s in Ganeti" %
134
                     (x[0], x[1], ('UP' if x[2] else 'DOWN'))
135
                     for x in unsynced])
136
            elif verbosity == 2:
137
                print >> sys.stderr, "The operstate of all servers is in sync."
138

    
139
        #
140
        # Then fix them
141
        #
142
        if options['fix_stale'] and len(stale) > 0:
143
            print >> sys.stderr, \
144
                "Simulating successful Ganeti removal for %d " \
145
                "servers in the DB:" % len(stale)
146
            for vm in VirtualMachine.objects.filter(pk__in=stale):
147
                backend.process_op_status(vm=vm, jobid=-0,
148
                    opcode='OP_INSTANCE_REMOVE', status='success',
149
                    logmsg='Reconciliation: simulated Ganeti event')
150
            print >> sys.stderr, "    ...done"
151

    
152
        if options['fix_orphans'] and len(orphans) > 0:
153
            print >> sys.stderr, \
154
                "Issuing OP_INSTANCE_REMOVE for %d Ganeti instances:" % \
155
                len(orphans)
156
            for id in orphans:
157
                rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
158
                rapi.DeleteInstance('%s%s' %
159
                                    (settings.BACKEND_PREFIX_ID, str(id)))
160
            print >> sys.stderr, "    ...done"
161

    
162
        if options['fix_unsynced'] and len(unsynced) > 0:
163
            print >> sys.stderr, "Setting the state of %d out-of-sync VMs:" % \
164
                len(unsynced)
165
            for id, db_state, ganeti_up in unsynced:
166
                vm = VirtualMachine.objects.get(pk=id)
167
                opcode = "OP_INSTANCE_REBOOT" if ganeti_up \
168
                         else "OP_INSTANCE_SHUTDOWN"
169
                backend.process_op_status(vm=vm, jobid=-0,
170
                    opcode=opcode, status='success',
171
                    logmsg='Reconciliation: simulated Ganeti event')
172
            print >> sys.stderr, "    ...done"