Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / reconciliation.py @ 0e9a423f

History | View | Annotate | Download (9.9 kB)

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

39 9fea53cc Vangelis Koukis
Reconcile the contents of the DB with the actual state of the
40 9fea53cc Vangelis Koukis
Ganeti backend.
41 9fea53cc Vangelis Koukis

42 9fea53cc Vangelis Koukis
Let D be the set of VMs in the DB, G the set of VMs in Ganeti.
43 9fea53cc Vangelis Koukis
RULES:
44 9fea53cc Vangelis Koukis
    R1. Stale servers in DB:
45 9fea53cc Vangelis Koukis
            For any v in D but not in G:
46 9fea53cc Vangelis Koukis
            Set deleted=True.
47 9fea53cc Vangelis Koukis
    R2. Orphan instances in Ganet:
48 9fea53cc Vangelis Koukis
            For any v in G with deleted=True in D:
49 9fea53cc Vangelis Koukis
            Issue OP_INSTANCE_DESTROY.
50 9fea53cc Vangelis Koukis
    R3. Unsynced operstate:
51 9fea53cc Vangelis Koukis
            For any v whose operating state differs between G and V:
52 9fea53cc Vangelis Koukis
            Set the operating state in D based on the state in G.
53 9fea53cc Vangelis Koukis
In the code, D, G are Python dicts mapping instance ids to operating state.
54 9fea53cc Vangelis Koukis
For D, the operating state is chosen from VirtualMachine.OPER_STATES.
55 9fea53cc Vangelis Koukis
For G, the operating state is True if the machine is up, False otherwise.
56 9fea53cc Vangelis Koukis

57 9fea53cc Vangelis Koukis
"""
58 9fea53cc Vangelis Koukis
59 9e98ba3c Giorgos Verigakis
import logging
60 9fea53cc Vangelis Koukis
import sys
61 0e9a423f Christos Stavrakakis
import itertools
62 9fea53cc Vangelis Koukis
63 9fea53cc Vangelis Koukis
from django.core.management import setup_environ
64 9fea53cc Vangelis Koukis
try:
65 9fea53cc Vangelis Koukis
    from synnefo import settings
66 9fea53cc Vangelis Koukis
except ImportError:
67 9fea53cc Vangelis Koukis
    raise Exception("Cannot import settings, make sure PYTHONPATH contains "
68 9fea53cc Vangelis Koukis
                    "the parent directory of the Synnefo Django project.")
69 9fea53cc Vangelis Koukis
setup_environ(settings)
70 9fea53cc Vangelis Koukis
71 4161cb41 Christos Stavrakakis
72 4161cb41 Christos Stavrakakis
from datetime import datetime, timedelta
73 4161cb41 Christos Stavrakakis
74 0e9a423f Christos Stavrakakis
from synnefo.db.models import VirtualMachine, Network, BackendNetwork
75 9e98ba3c Giorgos Verigakis
from synnefo.util.dictconfig import dictConfig
76 4161cb41 Christos Stavrakakis
from synnefo.util.rapi import GanetiApiError
77 f5b4f2a3 Christos Stavrakakis
from synnefo.logic.backend import get_ganeti_instances
78 9fea53cc Vangelis Koukis
79 9fea53cc Vangelis Koukis
80 9e98ba3c Giorgos Verigakis
log = logging.getLogger()
81 9e98ba3c Giorgos Verigakis
82 9e98ba3c Giorgos Verigakis
83 9fea53cc Vangelis Koukis
def stale_servers_in_db(D, G):
84 9fea53cc Vangelis Koukis
    idD = set(D.keys())
85 9fea53cc Vangelis Koukis
    idG = set(G.keys())
86 9fea53cc Vangelis Koukis
87 4161cb41 Christos Stavrakakis
    stale = set()
88 4161cb41 Christos Stavrakakis
    for i in idD - idG:
89 4161cb41 Christos Stavrakakis
        if D[i] == 'BUILD':
90 4161cb41 Christos Stavrakakis
            vm = VirtualMachine.objects.get(id=i)
91 4161cb41 Christos Stavrakakis
            # Check time to avoid many rapi calls
92 4161cb41 Christos Stavrakakis
            if datetime.now() > vm.backendtime + timedelta(seconds=5):
93 4161cb41 Christos Stavrakakis
                try:
94 4161cb41 Christos Stavrakakis
                    job_status = vm.client.GetJobStatus(vm.backendjobid)['status']
95 4161cb41 Christos Stavrakakis
                    if job_status in ('queued', 'waiting', 'running'):
96 4161cb41 Christos Stavrakakis
                        # Server is still building in Ganeti
97 4161cb41 Christos Stavrakakis
                        continue
98 4161cb41 Christos Stavrakakis
                    else:
99 4161cb41 Christos Stavrakakis
                        new_vm = vm.client.GetInstance('%s%d' %
100 4161cb41 Christos Stavrakakis
                                (settings.BACKEND_PREFIX_ID, i))
101 4161cb41 Christos Stavrakakis
                        # Server has just been created in Ganeti
102 4161cb41 Christos Stavrakakis
                        continue
103 4161cb41 Christos Stavrakakis
                except GanetiApiError:
104 4161cb41 Christos Stavrakakis
                    stale.add(i)
105 4161cb41 Christos Stavrakakis
        else:
106 4161cb41 Christos Stavrakakis
            stale.add(i)
107 4161cb41 Christos Stavrakakis
108 4161cb41 Christos Stavrakakis
    return stale
109 9fea53cc Vangelis Koukis
110 9fea53cc Vangelis Koukis
111 9fea53cc Vangelis Koukis
def orphan_instances_in_ganeti(D, G):
112 9fea53cc Vangelis Koukis
    idD = set(D.keys())
113 9fea53cc Vangelis Koukis
    idG = set(G.keys())
114 9fea53cc Vangelis Koukis
115 9fea53cc Vangelis Koukis
    return idG - idD
116 9fea53cc Vangelis Koukis
117 9fea53cc Vangelis Koukis
118 9fea53cc Vangelis Koukis
def unsynced_operstate(D, G):
119 9fea53cc Vangelis Koukis
    unsynced = set()
120 9fea53cc Vangelis Koukis
    idD = set(D.keys())
121 9fea53cc Vangelis Koukis
    idG = set(G.keys())
122 9fea53cc Vangelis Koukis
123 9fea53cc Vangelis Koukis
    for i in idD & idG:
124 9fea53cc Vangelis Koukis
        if (G[i] and D[i] != 'STARTED' or
125 9fea53cc Vangelis Koukis
            not G[i] and D[i] not in ('BUILD', 'ERROR', 'STOPPED')):
126 9fea53cc Vangelis Koukis
            unsynced.add((i, D[i], G[i]))
127 4161cb41 Christos Stavrakakis
        if not G[i] and D[i] == 'BUILD':
128 4161cb41 Christos Stavrakakis
            vm = VirtualMachine.objects.get(id=i)
129 4161cb41 Christos Stavrakakis
            # Check time to avoid many rapi calls
130 4161cb41 Christos Stavrakakis
            if datetime.now() > vm.backendtime + timedelta(seconds=5):
131 4161cb41 Christos Stavrakakis
                try:
132 4161cb41 Christos Stavrakakis
                    job_info = vm.client.GetJobStatus(job_id = vm.backendjobid)
133 4161cb41 Christos Stavrakakis
                    if job_info['status'] == 'success':
134 4161cb41 Christos Stavrakakis
                        unsynced.add((i, D[i], G[i]))
135 4161cb41 Christos Stavrakakis
                except GanetiApiError:
136 4161cb41 Christos Stavrakakis
                    pass
137 9fea53cc Vangelis Koukis
138 9fea53cc Vangelis Koukis
    return unsynced
139 9fea53cc Vangelis Koukis
140 9fea53cc Vangelis Koukis
141 4161cb41 Christos Stavrakakis
def instances_with_build_errors(D, G):
142 4161cb41 Christos Stavrakakis
    failed = set()
143 4161cb41 Christos Stavrakakis
    idD = set(D.keys())
144 4161cb41 Christos Stavrakakis
    idG = set(G.keys())
145 4161cb41 Christos Stavrakakis
146 4161cb41 Christos Stavrakakis
    for i in idD & idG:
147 4161cb41 Christos Stavrakakis
        if not G[i] and D[i] == 'BUILD':
148 4161cb41 Christos Stavrakakis
            vm = VirtualMachine.objects.get(id=i)
149 4161cb41 Christos Stavrakakis
            # Check time to avoid many rapi calls
150 4161cb41 Christos Stavrakakis
            if datetime.now() > vm.backendtime + timedelta(seconds=5):
151 4161cb41 Christos Stavrakakis
                try:
152 4161cb41 Christos Stavrakakis
                    job_info = vm.client.GetJobStatus(job_id = vm.backendjobid)
153 4161cb41 Christos Stavrakakis
                    if job_info['status'] == 'error':
154 4161cb41 Christos Stavrakakis
                        failed.add(i)
155 4161cb41 Christos Stavrakakis
                except GanetiApiError:
156 4161cb41 Christos Stavrakakis
                    failed.add(i)
157 4161cb41 Christos Stavrakakis
158 4161cb41 Christos Stavrakakis
    return failed
159 4161cb41 Christos Stavrakakis
160 4161cb41 Christos Stavrakakis
161 4161cb41 Christos Stavrakakis
162 9fea53cc Vangelis Koukis
def get_servers_from_db():
163 9fea53cc Vangelis Koukis
    vms = VirtualMachine.objects.filter(deleted=False)
164 9fea53cc Vangelis Koukis
    return dict(map(lambda x: (x.id, x.operstate), vms))
165 9fea53cc Vangelis Koukis
166 9fea53cc Vangelis Koukis
167 9fea53cc Vangelis Koukis
def get_instances_from_ganeti():
168 f5b4f2a3 Christos Stavrakakis
    ganeti_instances = get_ganeti_instances(bulk=True)
169 9fea53cc Vangelis Koukis
    snf_instances = {}
170 9fea53cc Vangelis Koukis
171 9fea53cc Vangelis Koukis
    prefix = settings.BACKEND_PREFIX_ID
172 9fea53cc Vangelis Koukis
    for i in ganeti_instances:
173 9fea53cc Vangelis Koukis
        if i['name'].startswith(prefix):
174 9fea53cc Vangelis Koukis
            try:
175 9fea53cc Vangelis Koukis
                id = int(i['name'].split(prefix)[1])
176 9fea53cc Vangelis Koukis
            except Exception:
177 9e98ba3c Giorgos Verigakis
                log.error("Ignoring instance with malformed name %s",
178 9fea53cc Vangelis Koukis
                              i['name'])
179 9fea53cc Vangelis Koukis
                continue
180 9fea53cc Vangelis Koukis
181 9fea53cc Vangelis Koukis
            if id in snf_instances:
182 9e98ba3c Giorgos Verigakis
                log.error("Ignoring instance with duplicate Synnefo id %s",
183 9fea53cc Vangelis Koukis
                    i['name'])
184 9fea53cc Vangelis Koukis
                continue
185 9fea53cc Vangelis Koukis
186 9fea53cc Vangelis Koukis
            snf_instances[id] = i['oper_state']
187 9fea53cc Vangelis Koukis
188 9fea53cc Vangelis Koukis
    return snf_instances
189 9fea53cc Vangelis Koukis
190 0e9a423f Christos Stavrakakis
#
191 0e9a423f Christos Stavrakakis
# Nics
192 0e9a423f Christos Stavrakakis
#
193 0e9a423f Christos Stavrakakis
def get_nics_from_ganeti():
194 0e9a423f Christos Stavrakakis
    """Get network interfaces for each ganeti instance.
195 0e9a423f Christos Stavrakakis

196 0e9a423f Christos Stavrakakis
    """
197 0e9a423f Christos Stavrakakis
    instances = get_ganeti_instances(bulk=True)
198 0e9a423f Christos Stavrakakis
    prefix = settings.BACKEND_PREFIX_ID
199 0e9a423f Christos Stavrakakis
200 0e9a423f Christos Stavrakakis
    snf_instances_nics = {}
201 0e9a423f Christos Stavrakakis
    for i in instances:
202 0e9a423f Christos Stavrakakis
        if i['name'].startswith(prefix):
203 0e9a423f Christos Stavrakakis
            try:
204 0e9a423f Christos Stavrakakis
                id = int(i['name'].split(prefix)[1])
205 0e9a423f Christos Stavrakakis
            except Exception:
206 0e9a423f Christos Stavrakakis
                log.error("Ignoring instance with malformed name %s",
207 0e9a423f Christos Stavrakakis
                              i['name'])
208 0e9a423f Christos Stavrakakis
                continue
209 0e9a423f Christos Stavrakakis
            if id in snf_instances_nics:
210 0e9a423f Christos Stavrakakis
                log.error("Ignoring instance with duplicate Synnefo id %s",
211 0e9a423f Christos Stavrakakis
                    i['name'])
212 0e9a423f Christos Stavrakakis
                continue
213 0e9a423f Christos Stavrakakis
214 0e9a423f Christos Stavrakakis
            ips = zip(itertools.repeat('ipv4'), i['nic.ips'])
215 0e9a423f Christos Stavrakakis
            macs = zip(itertools.repeat('mac'), i['nic.macs'])
216 0e9a423f Christos Stavrakakis
            networks = zip(itertools.repeat('network'), i['nic.networks'])
217 0e9a423f Christos Stavrakakis
            # modes = zip(itertools.repeat('mode'), i['nic.modes'])
218 0e9a423f Christos Stavrakakis
            # links = zip(itertools.repeat('link'), i['nic.links'])
219 0e9a423f Christos Stavrakakis
            # nics = zip(ips,macs,modes,networks,links)
220 0e9a423f Christos Stavrakakis
            nics = zip(ips, macs, networks)
221 0e9a423f Christos Stavrakakis
            nics = map(lambda x:dict(x), nics)
222 0e9a423f Christos Stavrakakis
            nics = dict(enumerate(nics))
223 0e9a423f Christos Stavrakakis
            snf_instances_nics[id] = nics
224 0e9a423f Christos Stavrakakis
225 0e9a423f Christos Stavrakakis
    return snf_instances_nics
226 0e9a423f Christos Stavrakakis
227 0e9a423f Christos Stavrakakis
228 0e9a423f Christos Stavrakakis
def get_nics_from_db():
229 0e9a423f Christos Stavrakakis
    """Get network interfaces for each vm in DB.
230 0e9a423f Christos Stavrakakis

231 0e9a423f Christos Stavrakakis
    """
232 0e9a423f Christos Stavrakakis
    instances = VirtualMachine.objects.filter(deleted=False)
233 0e9a423f Christos Stavrakakis
    instances_nics = {}
234 0e9a423f Christos Stavrakakis
    for instance in instances:
235 0e9a423f Christos Stavrakakis
        nics = {}
236 0e9a423f Christos Stavrakakis
        for n in instance.nics.all():
237 0e9a423f Christos Stavrakakis
            ipv4 = n.ipv4
238 0e9a423f Christos Stavrakakis
            nic = {'mac':      n.mac,
239 0e9a423f Christos Stavrakakis
                   'network':  n.network.backend_id,
240 0e9a423f Christos Stavrakakis
                   'ipv4':     ipv4 if ipv4 != '' else None
241 0e9a423f Christos Stavrakakis
                   }
242 0e9a423f Christos Stavrakakis
            nics[n.index] = nic
243 0e9a423f Christos Stavrakakis
        instances_nics[instance.id] = nics
244 0e9a423f Christos Stavrakakis
    return instances_nics
245 0e9a423f Christos Stavrakakis
246 0e9a423f Christos Stavrakakis
247 0e9a423f Christos Stavrakakis
def unsynced_nics(DBNics, GNics):
248 0e9a423f Christos Stavrakakis
    """Find unsynced network interfaces between DB and Ganeti.
249 0e9a423f Christos Stavrakakis

250 0e9a423f Christos Stavrakakis
    @ rtype: dict; {instance_id: ganeti_nics}
251 0e9a423f Christos Stavrakakis
    @ return Dictionary containing the instances ids that have unsynced network
252 0e9a423f Christos Stavrakakis
    interfaces between DB and Ganeti and the network interfaces in Ganeti.
253 0e9a423f Christos Stavrakakis

254 0e9a423f Christos Stavrakakis
    """
255 0e9a423f Christos Stavrakakis
    idD = set(DBNics.keys())
256 0e9a423f Christos Stavrakakis
    idG = set(GNics.keys())
257 0e9a423f Christos Stavrakakis
258 0e9a423f Christos Stavrakakis
    unsynced = {}
259 0e9a423f Christos Stavrakakis
    for i in idD & idG:
260 0e9a423f Christos Stavrakakis
        nicsD = DBNics[i]
261 0e9a423f Christos Stavrakakis
        nicsG = GNics[i]
262 0e9a423f Christos Stavrakakis
        if len(nicsD) != len(nicsG):
263 0e9a423f Christos Stavrakakis
            unsynced[i] = (nicsD, nicsG)
264 0e9a423f Christos Stavrakakis
            continue
265 0e9a423f Christos Stavrakakis
        for index in nicsG.keys():
266 0e9a423f Christos Stavrakakis
            nicD = nicsD[index]
267 0e9a423f Christos Stavrakakis
            nicG = nicsG[index]
268 0e9a423f Christos Stavrakakis
            if nicD['ipv4'] != nicG['ipv4'] or \
269 0e9a423f Christos Stavrakakis
               nicD['mac'] != nicG['mac'] or \
270 0e9a423f Christos Stavrakakis
               nicD['network'] != nicG['network']:
271 0e9a423f Christos Stavrakakis
                   unsynced[i] = (nicsD, nicsG)
272 0e9a423f Christos Stavrakakis
                   break
273 0e9a423f Christos Stavrakakis
274 0e9a423f Christos Stavrakakis
    return unsynced
275 0e9a423f Christos Stavrakakis
276 0e9a423f Christos Stavrakakis
#
277 0e9a423f Christos Stavrakakis
# Networks
278 0e9a423f Christos Stavrakakis
#
279 0e9a423f Christos Stavrakakis
def get_networks_from_ganeti(backend):
280 0e9a423f Christos Stavrakakis
    prefix = settings.BACKEND_PREFIX_ID
281 0e9a423f Christos Stavrakakis
282 0e9a423f Christos Stavrakakis
    networks = {}
283 0e9a423f Christos Stavrakakis
    for net in backend.client.GetNetworks(bulk=True):
284 0e9a423f Christos Stavrakakis
        if net['name'].startswith(prefix):
285 0e9a423f Christos Stavrakakis
            # TODO: Get it from fun. Catch errors
286 0e9a423f Christos Stavrakakis
            id = int(net['name'].split(prefix)[1])
287 0e9a423f Christos Stavrakakis
            networks[id] = net
288 0e9a423f Christos Stavrakakis
289 0e9a423f Christos Stavrakakis
    return networks
290 0e9a423f Christos Stavrakakis
291 0e9a423f Christos Stavrakakis
292 0e9a423f Christos Stavrakakis
def hanging_networks(backend, GNets):
293 0e9a423f Christos Stavrakakis
    """Get networks that are not connected to all Nodegroups.
294 0e9a423f Christos Stavrakakis

295 0e9a423f Christos Stavrakakis
    """
296 0e9a423f Christos Stavrakakis
    def get_network_groups(group_list):
297 0e9a423f Christos Stavrakakis
        groups = set()
298 0e9a423f Christos Stavrakakis
        for g in group_list:
299 0e9a423f Christos Stavrakakis
            g_name = g.split('(')[0]
300 0e9a423f Christos Stavrakakis
            groups.add(g_name)
301 0e9a423f Christos Stavrakakis
        return groups
302 0e9a423f Christos Stavrakakis
303 0e9a423f Christos Stavrakakis
    groups = set(backend.client.GetGroups())
304 0e9a423f Christos Stavrakakis
305 0e9a423f Christos Stavrakakis
    hanging = {}
306 0e9a423f Christos Stavrakakis
    for id, info in GNets.items():
307 0e9a423f Christos Stavrakakis
        group_list = get_network_groups(info['group_list'])
308 0e9a423f Christos Stavrakakis
        if group_list != groups:
309 0e9a423f Christos Stavrakakis
            hanging[id] = groups - group_list
310 0e9a423f Christos Stavrakakis
    return hanging
311 0e9a423f Christos Stavrakakis
312 9fea53cc Vangelis Koukis
313 a65ee5fc Vangelis Koukis
# Only for testing this module individually
314 9fea53cc Vangelis Koukis
def main():
315 9fea53cc Vangelis Koukis
    print get_instances_from_ganeti()
316 9fea53cc Vangelis Koukis
317 9fea53cc Vangelis Koukis
318 9fea53cc Vangelis Koukis
if __name__ == "__main__":
319 9fea53cc Vangelis Koukis
    sys.exit(main())