Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 25892058

History | View | Annotate | Download (15.3 KB)

1
# -*- coding: utf-8 -*-
2
# vim:set expandtab tabstop=4 shiftwidth=4:
3
################################################################################
4
#
5
# Copyright (C) 2007-2013 CS-SI
6
#
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License version 2 as
9
# published by the Free Software Foundation.
10
#
11
# This program is distributed in the hope that it will be useful,
12
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
# GNU General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
################################################################################
20

    
21
"""Gestion de la requête, des plugins et de l'affichage du Vigiboard"""
22

    
23
from time import mktime
24

    
25
from tg import config, tmpl_context, request, url
26
from pylons.i18n import ugettext as _
27
from paste.deploy.converters import asbool
28
from repoze.what.predicates import in_group
29

    
30
from sqlalchemy import not_, and_, asc, desc
31
from sqlalchemy.sql.expression import null as expr_null, union_all
32
from sqlalchemy.orm import contains_eager
33

    
34
from vigilo.models.session import DBSession
35
from vigilo.models.tables import Event, CorrEvent, EventHistory, \
36
    Host, LowLevelService, StateName, UserSupItem
37
from vigiboard.widgets.edit_event import EditEventForm
38
from vigiboard.controllers.plugins import VigiboardRequestPlugin, INNER, ITEMS
39

    
40
class VigiboardRequest():
41
    """
42
    Classe gérant la génération de la requête finale,
43
    le préformatage des événements et celui des historiques
44
    """
45

    
46
    def __init__(self, user, mask_closed_events=True, search=None):
47
        """
48
        Initialisation de l'objet qui effectue les requêtes de VigiBoard
49
        sur la base de données.
50
        Cet objet est responsable de la vérification des droits de
51
        l'utilisateur sur les données manipulées.
52
        """
53

    
54
        # Permet s'appliquer des filtres de recherche aux sous-requêtes.
55
        self.subqueries = []
56
        self.generaterq = False
57

    
58
        # Éléments à retourner (SELECT ...)
59
        self.table = []
60

    
61
        # Tables sur lesquelles porte la récupération (JOIN)
62
        self.join = []
63

    
64
        # Options à ajouter la requête
65
        self.option = []
66

    
67
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
68
        self.outerjoin = []
69

    
70
        # Critères de filtrage (WHERE)
71
        self.filter = []
72

    
73
        # Regroupements (GROUP BY)
74
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
75
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
76
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
77
        self.groupby = [
78
            StateName.order,
79
            Event.timestamp,
80
            CorrEvent.ack,
81
            CorrEvent.priority,
82
            StateName.statename,
83
        ]
84

    
85
        # Permet de définir le sens de tri pour la priorité.
86
        if config['vigiboard_priority_order'] == 'asc':
87
            priority_order = asc(CorrEvent.priority)
88
        else:
89
            priority_order = desc(CorrEvent.priority)
90

    
91
        # Tris (ORDER BY)
92
        # Permet de répondre aux exigences suivantes :
93
        # - VIGILO_EXIG_VIGILO_BAC_0050
94
        # - VIGILO_EXIG_VIGILO_BAC_0060
95
        self.orderby = [
96
            asc(CorrEvent.ack),                             # État acquittement
97
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
98
            priority_order,                                 # Priorité ITIL
99
        ]
100

    
101
        if asbool(config.get('state_first', True)):
102
            self.orderby.extend([
103
                desc(StateName.order),                      # Etat courant
104
                desc(Event.timestamp),                      # Timestamp
105
            ])
106
        else:
107
            self.orderby.extend([
108
                desc(Event.timestamp),                      # Timestamp
109
                desc(StateName.order),                      # Etat courant
110
            ])
111

    
112
        self.req = DBSession
113
        self.plugin = []
114
        self.events = []
115

    
116

    
117
        is_manager = in_group('managers').is_met(request.environ)
118

    
119
        # Si l'utilisateur fait partie du groupe 'managers',
120
        # il a accès à tous les hôtes/services sans restriction.
121
        if is_manager:
122
            # Sélection de tous les services de la BDD.
123
            lls_query = DBSession.query(
124
                LowLevelService.idservice.label("idsupitem"),
125
                LowLevelService.servicename.label("servicename"),
126
                Host.name.label("hostname"),
127
            ).join(
128
                (Host, Host.idhost == LowLevelService.idhost),
129
            ).distinct()
130

    
131
            # Sélection de tous les hôtes de la BDD.
132
            host_query = DBSession.query(
133
                Host.idhost.label("idsupitem"),
134
                expr_null().label("servicename"),
135
                Host.name.label("hostname"),
136
            ).distinct()
137

    
138
            # Application des filtres des plugins si nécessaire.
139
            if search is not None:
140
                # On tire ici partie du fait que les listes sont passées
141
                # par référence dans les fonctions.
142
                subqueries = [lls_query, host_query]
143
                for _plugin, instance in config.get('columns_plugins', []):
144
                    instance.handle_search_fields(
145
                        self, search, INNER, subqueries)
146
                lls_query = subqueries[0]
147
                host_query = subqueries[1]
148

    
149
            # Union des deux sélections précédentes
150
            self.items = union_all(
151
                lls_query,
152
                host_query,
153
                correlate=False
154
            ).alias()
155

    
156
        # Sinon, on ne récupère que les hôtes/services auquel il a accès.
157
        else:
158
            items = DBSession.query(
159
                UserSupItem.idsupitem,
160
                UserSupItem.servicename,
161
                UserSupItem.hostname,
162
            ).filter(
163
                UserSupItem.username == user.user_name
164
            ).distinct()
165

    
166
            # Application des filtres des plugins si nécessaire.
167
            if search is not None:
168
                # On tire ici partie du fait que les listes sont passées
169
                # par référence dans les fonctions.
170
                subqueries = [items]
171
                for _plugin, instance in config.get('columns_plugins', []):
172
                    instance.handle_search_fields(
173
                        self, search, INNER, subqueries)
174
                items = subqueries[0]
175

    
176
            # Permet d'avoir le même format que pour l'autre requête.
177
            self.items = items.subquery()
178

    
179
        if search is not None:
180
            # 2nde passe pour les filtres : self.items est désormais défini.
181
            for _plugin, instance in config.get('columns_plugins', []):
182
                instance.handle_search_fields(self, search, ITEMS, subqueries)
183

    
184
        if mask_closed_events:
185
            self.filter.append(
186
                # On masque les événements avec l'état OK
187
                # et traités (ack == CorrEvent.ACK_CLOSED).
188
                not_(and_(
189
                    StateName.statename.in_([u'OK', u'UP']),
190
                    CorrEvent.ack == CorrEvent.ACK_CLOSED
191
                ))
192
            )
193

    
194

    
195
    def add_plugin(self, *argv):
196
        """
197
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
198
        """
199
        for i in argv:
200
            if isinstance(i, VigiboardRequestPlugin):
201
                if i.table:
202
                    self.add_table(*i.table)
203
                if i.join:
204
                    self.add_join(*i.join)
205
                if i.outerjoin:
206
                    self.add_outer_join(*i.outerjoin)
207
                if i.filter:
208
                    self.add_filter(*i.filter)
209
                if i.groupby:
210
                    self.add_group_by(*i.groupby)
211
                if i.orderby:
212
                    self.add_order_by(*i.orderby)
213
                self.plugin.append(i)
214

    
215
    def generate_request(self):
216
        """
217
        Génération de la requête avec l'ensemble des données stockées
218
        et la place dans la variable rq de la classe
219
        """
220
        if self.generaterq:
221
            return
222

    
223
        for plugin in config['columns_plugins']:
224
            self.add_plugin(plugin)
225

    
226
        # Toutes les requêtes ont besoin de récupérer l'état courant
227
        # de l'événement.
228
        self.join.append((StateName, StateName.idstatename == \
229
                                        Event.current_state))
230

    
231
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
232
        # dans la clause GROUP BY. Si une colonne apparaît dans SELECT,
233
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
234
        # Ici, on ajoute automatiquement les colonnes du SELECT au GROUP BY.
235
        self.add_group_by(*self.table)
236

    
237
        # query et join ont besoin de referrence
238
        self.req = self.req.query(*self.table)
239
        self.req = self.req.join(*self.join)
240
        self.req = self.req.options(*self.option)
241

    
242
        # le reste, non
243
        for i in self.outerjoin:
244
            self.req = self.req.outerjoin(i)
245
        for i in self.filter:
246
            self.req = self.req.filter(i)
247
        for i in self.groupby:
248
            self.req = self.req.group_by(i)
249
        for i in self.orderby:
250
            self.req = self.req.order_by(i)
251
        self.generaterq = True
252

    
253
    def num_rows(self):
254
        """
255
        Retourne le nombre de lignes de la requête.
256
        Si celle-ci n'est pas encore générée, on le fait.
257

258
        @return: Nombre de ligne
259
        """
260

    
261
        self.generate_request()
262
        return self.req.count()
263

    
264
    def add_table(self, *argv):
265
        """
266
        Ajoute une ou plusieurs tables/élément d'une table à
267
        la requête.
268

269
        @param argv: Liste des tables à ajouter
270
        """
271

    
272
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
273
        # des tables.
274

    
275
        for i in argv :
276
            for j in self.table:
277
                if str(i) == str(j):
278
                    break
279
            self.table.append(i)
280

    
281
    def add_join(self, *argv):
282
        """
283
        Ajoute une ou plusieurs jointures à
284
        la requête.
285

286
        @param argv: Liste des jointures à ajouter
287
        """
288

    
289
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
290
        # des jointures.
291

    
292
        for i in argv:
293
            for j in self.join:
294
                if str(i) == str(j):
295
                    break
296
            self.join.append(i)
297

    
298
    def add_option(self, *argv):
299
        """
300
        Ajoute une ou plusieurs options à la requête.
301

302
        @param argv: Liste des options à ajouter
303
        """
304

    
305
        # On vérifie qu'il n'y a pas de doublons
306
        # dans la liste finale des options.
307

    
308
        for i in argv:
309
            for j in self.option:
310
                if str(i) == str(j):
311
                    break
312
            self.option.append(i)
313

    
314
    def add_contains_eager(self, relation):
315
        """
316
        Ajoute une option de type contains_eager à la
317
        requête pour la relation passée en paramètre.
318
        """
319
        self.add_option(contains_eager(relation))
320

    
321
    def add_outer_join(self, *argv):
322
        """
323
        Ajoute une ou plusieurs jointures externes à
324
        la requête.
325

326
        @param argv: Liste des jointures externes à ajouter
327
        """
328

    
329
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
330
        # des jointures externes.
331

    
332
        for i in argv:
333
            for j in self.outerjoin:
334
                if str(i) == str(j):
335
                    break
336
            self.outerjoin.append(i)
337

    
338
    def add_filter(self, *argv):
339
        """
340
        Ajoute un ou plusieurs filtres à la requête.
341

342
        @param argv: Liste des filtres à ajouter
343
        """
344

    
345
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
346
        # des filtres.
347

    
348
        for i in argv:
349
            for j in self.filter:
350
                if str(i) == str(j):
351
                    break
352
            self.filter.append(i)
353

    
354
    def add_group_by(self, *argv):
355
        """
356
        Ajoute un ou plusieurs groupements à la requête.
357

358
        @param argv: Liste des groupements à ajouter
359
        """
360

    
361
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
362
        # des groupements.
363

    
364
        for i in argv:
365
            for j in self.groupby:
366
                try:
367
                    if str(i) == str(j):
368
                        break
369
                # SQLAlchemy lève cette exception pour certains attributes,
370
                # par exemple les attributs définis avec synonym().
371
                except AttributeError:
372
                    pass
373
            self.groupby.append(i)
374

    
375
    def add_order_by(self, *argv):
376
        """
377
        Ajoute un ou plusieurs orders à la requête.
378

379
        @param argv: Liste des ordres à ajouter
380
        """
381

    
382
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
383
        # des ordres.
384

    
385
        for i in argv:
386
            for j in self.orderby:
387
                if str(i) == str(j):
388
                    break
389
            self.orderby.append(i)
390

    
391
    def format_events(self, first_row, last_row):
392
        """
393
        Formate la réponse de la requête et y applique les plugins
394
        pour un affichage simple du résultat par Genshi.
395
        On génère une liste de liste, chaqu'une étant la description de
396
        l'affichage pour un événement donné.
397

398
        @param first_row: Indice de début de la liste des événements
399
        @param last_row: Indice de fin de la liste des événements
400
        """
401

    
402
        # Si la requête n'est pas générée, on le fait
403
        self.generate_request()
404

    
405
        # Liste des éléments pour la tête du tableau
406
        self.events = []
407

    
408
        for data in self.req[first_row : last_row]:
409
            self.events.append(data)
410

    
411
    def format_history(self):
412
        """
413
        Formate les historiques correspondant aux événements sélectionnés
414
        pour un affichage simple du résultat par Genshi.
415
        On génère une liste de liste, chaqu'une étant la description
416
        de l'affichage pour un historique donné.
417
        """
418

    
419
        ids = [data[0].idevent for data in self.events]
420
        history = DBSession.query(
421
                    EventHistory,
422
                ).filter(EventHistory.idevent.in_(ids)
423
                ).order_by(desc(EventHistory.timestamp)
424
                ).order_by(desc(EventHistory.idhistory))
425
        return history
426

    
427
    def generate_tmpl_context(self):
428
        """
429
        Génère et peuple la variable tmpl_context avec les Dialogs et
430
        formulaires nécessaire au fonctionnement de Vigiboard
431
        """
432

    
433
        from vigiboard.controllers.root import get_last_modification_timestamp
434

    
435
        # Si les objets manipulés sont des Event, on a facilement les idevent.
436
        if not len(self.events):
437
            ids = []
438
        elif isinstance(self.events[0][0], Event):
439
            ids = [data[0].idevent for data in self.events]
440
        # Sinon, il s'agit de CorrEvent(s) dont on récupère l'idcause.
441
        else:
442
            ids = [data[0].idcause for data in self.events]
443

    
444
        # Ajout des formulaires et préparation
445
        # des données pour ces formulaires.
446
        tmpl_context.last_modification = \
447
            mktime(get_last_modification_timestamp(ids).timetuple())
448

    
449
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
450
            submit_text=_('Apply'), action=url('/update'))