Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (7.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 sys
38
import datetime
39

    
40
from optparse import make_option
41

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

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

    
49

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

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

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

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

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

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

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

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

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

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

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

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

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

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