Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 011743be

History | View | Annotate | Download (15.7 KB)

1 19e88cb8 Thomas ANDREJAK
# -*- coding: utf-8 -*-
2 65383903 Francois POIROTTE
# vim:set expandtab tabstop=4 shiftwidth=4:
3 011743be Francois POIROTTE
# Copyright (C) 2007-2020 CS GROUP - France
4 9b8d9497 Francois POIROTTE
# License: GNU GPL v2 <http://www.gnu.org/licenses/gpl-2.0.html>
5 a77de887 Francois POIROTTE
6 19e88cb8 Thomas ANDREJAK
"""Gestion de la requête, des plugins et de l'affichage du Vigiboard"""
7
8 80fa03cd Francois POIROTTE
import logging
9 7365fb51 Francois POIROTTE
from time import mktime
10
11 e181e86c Francois POIROTTE
from tg import config, tmpl_context, request, url
12 02c4a1e7 Francois POIROTTE
from tg.i18n import ugettext as _
13 80fa03cd Francois POIROTTE
from paste.deploy.converters import aslist
14 b2346a00 Francois POIROTTE
15 6ab72614 Vincent QUEMENER
from sqlalchemy import not_, and_, asc, desc
16 8b2edebe Aurelien BOMPARD
from sqlalchemy.sql.expression import null as expr_null, union_all
17 cf3c2494 Vincent QUEMENER
from sqlalchemy.orm import contains_eager
18 7365fb51 Francois POIROTTE
19 e7e3d45e Francois POIROTTE
from vigilo.models.session import DBSession
20
from vigilo.models.tables import Event, CorrEvent, EventHistory, \
21 916e4b79 Francois POIROTTE
    Host, LowLevelService, StateName, UserSupItem
22 e181e86c Francois POIROTTE
from vigiboard.widgets.edit_event import EditEventForm
23 86662bc9 Francois POIROTTE
from vigiboard.controllers.plugins import VigiboardRequestPlugin, INNER, ITEMS
24 7365fb51 Francois POIROTTE
25 80fa03cd Francois POIROTTE
LOGGER = logging.getLogger(__name__)
26
27 19e88cb8 Thomas ANDREJAK
class VigiboardRequest():
28 86c3ae23 Francois POIROTTE
    """
29
    Classe gérant la génération de la requête finale,
30
    le préformatage des événements et celui des historiques
31
    """
32
33 5a845c93 Vincent QUEMENER
    def __init__(self, user, mask_closed_events=True, search=None, sort=None, order=None):
34 19e88cb8 Thomas ANDREJAK
        """
35 54644278 Francois POIROTTE
        Initialisation de l'objet qui effectue les requêtes de VigiBoard
36
        sur la base de données.
37
        Cet objet est responsable de la vérification des droits de
38
        l'utilisateur sur les données manipulées.
39 5a845c93 Vincent QUEMENER

40
        @param user: Nom de l'utilisateur cherchant à afficher les événements.
41
        @type  user: C{str}
42
        @param mask_closed_events: Booléen indiquant si l'on souhaite masquer les
43
            événements fermés ou non.
44
        @type  mask_closed_events: C{boolean}
45
        @param search: Dictionnaire contenant les critères de recherche.
46
        @type  search: C{dict}
47
        @param sort: Colonne de tri; vide en l'absence de tri.
48
        @type  sort: C{unicode}
49
        @param order: Ordre du tri ("asc" ou "desc"); vide en l'absence de tri.
50
        @type  order: C{unicode}
51

52 19e88cb8 Thomas ANDREJAK
        """
53
54 0f0e32ed Francois POIROTTE
        # Permet s'appliquer des filtres de recherche aux sous-requêtes.
55
        self.subqueries = []
56 19e88cb8 Thomas ANDREJAK
        self.generaterq = False
57 24d74687 Francois POIROTTE
58 86662bc9 Francois POIROTTE
        # É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 8ba2de75 Francois POIROTTE
            CorrEvent.ack,
81 86662bc9 Francois POIROTTE
            CorrEvent.priority,
82
            StateName.statename,
83
        ]
84
85
        self.req = DBSession
86
        self.plugin = []
87
        self.events = []
88
89 73119f8a Francois POIROTTE
        # Si l'utilisateur est privilégié, il a accès
90
        # à tous les hôtes/services sans restriction.
91
        if config.is_manager.is_met(request.environ):
92 180b869a Vincent QUEMENER
            # Sélection de tous les services de la BDD.
93 0f0e32ed Francois POIROTTE
            lls_query = DBSession.query(
94 180b869a Vincent QUEMENER
                LowLevelService.idservice.label("idsupitem"),
95
                LowLevelService.servicename.label("servicename"),
96
                Host.name.label("hostname"),
97 b2668166 Francois POIROTTE
                Host.address.label("address"),
98 180b869a Vincent QUEMENER
            ).join(
99
                (Host, Host.idhost == LowLevelService.idhost),
100 0f0e32ed Francois POIROTTE
            ).distinct()
101 180b869a Vincent QUEMENER
102
            # Sélection de tous les hôtes de la BDD.
103 0f0e32ed Francois POIROTTE
            host_query = DBSession.query(
104 180b869a Vincent QUEMENER
                Host.idhost.label("idsupitem"),
105
                expr_null().label("servicename"),
106
                Host.name.label("hostname"),
107 b2668166 Francois POIROTTE
                Host.address.label("address"),
108 032d0f30 Vincent QUEMENER
            ).distinct()
109 0f0e32ed Francois POIROTTE
110
            # Application des filtres des plugins si nécessaire.
111
            if search is not None:
112
                # On tire ici partie du fait que les listes sont passées
113
                # par référence dans les fonctions.
114
                subqueries = [lls_query, host_query]
115 8b2edebe Aurelien BOMPARD
                for _plugin, instance in config.get('columns_plugins', []):
116 86662bc9 Francois POIROTTE
                    instance.handle_search_fields(
117
                        self, search, INNER, subqueries)
118 0f0e32ed Francois POIROTTE
                lls_query = subqueries[0]
119
                host_query = subqueries[1]
120 180b869a Vincent QUEMENER
121
            # Union des deux sélections précédentes
122
            self.items = union_all(
123 0f0e32ed Francois POIROTTE
                lls_query,
124
                host_query,
125 180b869a Vincent QUEMENER
                correlate=False
126
            ).alias()
127
128
        # Sinon, on ne récupère que les hôtes/services auquel il a accès.
129
        else:
130 916e4b79 Francois POIROTTE
            items = DBSession.query(
131 180b869a Vincent QUEMENER
                UserSupItem.idsupitem,
132
                UserSupItem.servicename,
133
                UserSupItem.hostname,
134 b2668166 Francois POIROTTE
                UserSupItem.address,
135 180b869a Vincent QUEMENER
            ).filter(
136
                UserSupItem.username == user.user_name
137 0f0e32ed Francois POIROTTE
            ).distinct()
138 180b869a Vincent QUEMENER
139 0f0e32ed Francois POIROTTE
            # Application des filtres des plugins si nécessaire.
140
            if search is not None:
141
                # On tire ici partie du fait que les listes sont passées
142
                # par référence dans les fonctions.
143
                subqueries = [items]
144 8b2edebe Aurelien BOMPARD
                for _plugin, instance in config.get('columns_plugins', []):
145 86662bc9 Francois POIROTTE
                    instance.handle_search_fields(
146
                        self, search, INNER, subqueries)
147 0f0e32ed Francois POIROTTE
                items = subqueries[0]
148 916e4b79 Francois POIROTTE
149 86662bc9 Francois POIROTTE
            # Permet d'avoir le même format que pour l'autre requête.
150 0f0e32ed Francois POIROTTE
            self.items = items.subquery()
151 8484b8bd Francois POIROTTE
152 5a845c93 Vincent QUEMENER
        # Tris (ORDER BY)
153
        # Permet de répondre aux exigences suivantes :
154
        # - VIGILO_EXIG_VIGILO_BAC_0050
155
        # - VIGILO_EXIG_VIGILO_BAC_0060
156
        self.orderby = []
157 80fa03cd Francois POIROTTE
        plugins = config.get('columns_plugins', [])
158 5a845c93 Vincent QUEMENER
        if sort:
159 80fa03cd Francois POIROTTE
            for _plugin, instance in plugins:
160 5a845c93 Vincent QUEMENER
                criterion = instance.get_sort_criterion(self, sort)
161
                if criterion is not None:
162
                    if order == 'asc':
163
                        self.orderby.append(asc(criterion))
164
                    else:
165
                        self.orderby.append(desc(criterion))
166
167 80fa03cd Francois POIROTTE
        default_sort = aslist(config.get('default_sort', ''))
168
        for sort_column in default_sort:
169
            criterion = None
170
            sort_field, _dummy, sort_order = sort_column.partition(':')
171
            for _plugin, instance in plugins:
172
                criterion = instance.get_sort_criterion(self, sort_field)
173
                if criterion is not None:
174
                    if sort_order == 'desc':
175
                        self.orderby.append(desc(criterion))
176
                    elif sort_order in ('asc', None):
177
                        self.orderby.append(asc(criterion))
178
                    else:
179
                        self.orderby.append(asc(criterion))
180
                        LOGGER.warn('Invalid sort order: "%s", sorting in '
181
                                    'ascending order instead', sort_order)
182
                    break
183
            if criterion is None:
184
                LOGGER.info('No such plugin: "%s"', sort_field)
185 5a845c93 Vincent QUEMENER
186 86662bc9 Francois POIROTTE
        if search is not None:
187
            # 2nde passe pour les filtres : self.items est désormais défini.
188 80fa03cd Francois POIROTTE
            for _plugin, instance in plugins:
189 86662bc9 Francois POIROTTE
                instance.handle_search_fields(self, search, ITEMS, subqueries)
190 911069bc Francois POIROTTE
191 54644278 Francois POIROTTE
        if mask_closed_events:
192
            self.filter.append(
193
                # On masque les événements avec l'état OK
194 8ba2de75 Francois POIROTTE
                # et traités (ack == CorrEvent.ACK_CLOSED).
195 54644278 Francois POIROTTE
                not_(and_(
196
                    StateName.statename.in_([u'OK', u'UP']),
197 8ba2de75 Francois POIROTTE
                    CorrEvent.ack == CorrEvent.ACK_CLOSED
198 54644278 Francois POIROTTE
                ))
199
            )
200 8484b8bd Francois POIROTTE
201 19e88cb8 Thomas ANDREJAK
202
    def add_plugin(self, *argv):
203
        """
204
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
205
        """
206 df66bc2c Francois POIROTTE
        for i in argv:
207 19e88cb8 Thomas ANDREJAK
            if isinstance(i, VigiboardRequestPlugin):
208 df66bc2c Francois POIROTTE
                if i.table:
209 19e88cb8 Thomas ANDREJAK
                    self.add_table(*i.table)
210 df66bc2c Francois POIROTTE
                if i.join:
211 19e88cb8 Thomas ANDREJAK
                    self.add_join(*i.join)
212 df66bc2c Francois POIROTTE
                if i.outerjoin:
213 19e88cb8 Thomas ANDREJAK
                    self.add_outer_join(*i.outerjoin)
214 df66bc2c Francois POIROTTE
                if i.filter:
215 19e88cb8 Thomas ANDREJAK
                    self.add_filter(*i.filter)
216 65383903 Francois POIROTTE
                if i.groupby:
217 19e88cb8 Thomas ANDREJAK
                    self.add_group_by(*i.groupby)
218 df66bc2c Francois POIROTTE
                if i.orderby:
219 19e88cb8 Thomas ANDREJAK
                    self.add_order_by(*i.orderby)
220
                self.plugin.append(i)
221
222
    def generate_request(self):
223
        """
224
        Génération de la requête avec l'ensemble des données stockées
225
        et la place dans la variable rq de la classe
226
        """
227 911069bc Francois POIROTTE
        if self.generaterq:
228
            return
229
230 65383903 Francois POIROTTE
        for plugin in config['columns_plugins']:
231
            self.add_plugin(plugin)
232 bc94248f Francois POIROTTE
233 b00c0ea7 Francois POIROTTE
        # Toutes les requêtes ont besoin de récupérer l'état courant
234
        # de l'événement.
235 5d20c2c5 Francois POIROTTE
        self.join.append((StateName, StateName.idstatename == \
236
                                        Event.current_state))
237 b00c0ea7 Francois POIROTTE
238
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
239
        # dans la clause GROUP BY. Si une colonne apparaît dans SELECT,
240
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
241
        # Ici, on ajoute automatiquement les colonnes du SELECT au GROUP BY.
242 911069bc Francois POIROTTE
        self.add_group_by(*self.table)
243
244 19e88cb8 Thomas ANDREJAK
        # query et join ont besoin de referrence
245
        self.req = self.req.query(*self.table)
246
        self.req = self.req.join(*self.join)
247 cf3c2494 Vincent QUEMENER
        self.req = self.req.options(*self.option)
248 19e88cb8 Thomas ANDREJAK
249
        # le reste, non
250
        for i in self.outerjoin:
251
            self.req = self.req.outerjoin(i)
252
        for i in self.filter:
253
            self.req = self.req.filter(i)
254
        for i in self.groupby:
255
            self.req = self.req.group_by(i)
256
        for i in self.orderby:
257
            self.req = self.req.order_by(i)
258 911069bc Francois POIROTTE
        self.generaterq = True
259
260 19e88cb8 Thomas ANDREJAK
    def num_rows(self):
261
        """
262
        Retourne le nombre de lignes de la requête.
263
        Si celle-ci n'est pas encore générée, on le fait.
264

265 0bd9c069 Francois POIROTTE
        @return: Nombre de ligne
266 19e88cb8 Thomas ANDREJAK
        """
267
268 911069bc Francois POIROTTE
        self.generate_request()
269 19e88cb8 Thomas ANDREJAK
        return self.req.count()
270
271
    def add_table(self, *argv):
272
        """
273
        Ajoute une ou plusieurs tables/élément d'une table à
274
        la requête.
275

276
        @param argv: Liste des tables à ajouter
277
        """
278 65383903 Francois POIROTTE
279 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
280
        # des tables.
281 65383903 Francois POIROTTE
282 19e88cb8 Thomas ANDREJAK
        for i in argv :
283
            for j in self.table:
284
                if str(i) == str(j):
285
                    break
286
            self.table.append(i)
287
288
    def add_join(self, *argv):
289
        """
290
        Ajoute une ou plusieurs jointures à
291
        la requête.
292

293
        @param argv: Liste des jointures à ajouter
294
        """
295 65383903 Francois POIROTTE
296 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
297
        # des jointures.
298 65383903 Francois POIROTTE
299 19e88cb8 Thomas ANDREJAK
        for i in argv:
300
            for j in self.join:
301
                if str(i) == str(j):
302
                    break
303
            self.join.append(i)
304
305 cf3c2494 Vincent QUEMENER
    def add_option(self, *argv):
306
        """
307
        Ajoute une ou plusieurs options à la requête.
308

309
        @param argv: Liste des options à ajouter
310
        """
311
312
        # On vérifie qu'il n'y a pas de doublons
313
        # dans la liste finale des options.
314
315
        for i in argv:
316
            for j in self.option:
317
                if str(i) == str(j):
318
                    break
319
            self.option.append(i)
320
321
    def add_contains_eager(self, relation):
322
        """
323
        Ajoute une option de type contains_eager à la
324
        requête pour la relation passée en paramètre.
325
        """
326
        self.add_option(contains_eager(relation))
327
328 19e88cb8 Thomas ANDREJAK
    def add_outer_join(self, *argv):
329
        """
330
        Ajoute une ou plusieurs jointures externes à
331
        la requête.
332

333
        @param argv: Liste des jointures externes à ajouter
334
        """
335 65383903 Francois POIROTTE
336 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
337
        # des jointures externes.
338 65383903 Francois POIROTTE
339 19e88cb8 Thomas ANDREJAK
        for i in argv:
340
            for j in self.outerjoin:
341
                if str(i) == str(j):
342
                    break
343 65383903 Francois POIROTTE
            self.outerjoin.append(i)
344 19e88cb8 Thomas ANDREJAK
345
    def add_filter(self, *argv):
346
        """
347
        Ajoute un ou plusieurs filtres à la requête.
348

349
        @param argv: Liste des filtres à ajouter
350
        """
351 65383903 Francois POIROTTE
352 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
353
        # des filtres.
354 65383903 Francois POIROTTE
355 19e88cb8 Thomas ANDREJAK
        for i in argv:
356
            for j in self.filter:
357
                if str(i) == str(j):
358
                    break
359
            self.filter.append(i)
360
361
    def add_group_by(self, *argv):
362
        """
363
        Ajoute un ou plusieurs groupements à la requête.
364

365
        @param argv: Liste des groupements à ajouter
366
        """
367 65383903 Francois POIROTTE
368 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
369
        # des groupements.
370 65383903 Francois POIROTTE
371 19e88cb8 Thomas ANDREJAK
        for i in argv:
372
            for j in self.groupby:
373 df66bc2c Francois POIROTTE
                try:
374
                    if str(i) == str(j):
375
                        break
376
                # SQLAlchemy lève cette exception pour certains attributes,
377
                # par exemple les attributs définis avec synonym().
378
                except AttributeError:
379
                    pass
380 19e88cb8 Thomas ANDREJAK
            self.groupby.append(i)
381
382
    def add_order_by(self, *argv):
383
        """
384
        Ajoute un ou plusieurs orders à la requête.
385

386
        @param argv: Liste des ordres à ajouter
387
        """
388 65383903 Francois POIROTTE
389 8484b8bd Francois POIROTTE
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
390
        # des ordres.
391 65383903 Francois POIROTTE
392 19e88cb8 Thomas ANDREJAK
        for i in argv:
393
            for j in self.orderby:
394
                if str(i) == str(j):
395
                    break
396
            self.orderby.append(i)
397
398
    def format_events(self, first_row, last_row):
399
        """
400
        Formate la réponse de la requête et y applique les plugins
401
        pour un affichage simple du résultat par Genshi.
402
        On génère une liste de liste, chaqu'une étant la description de
403 a2a22ade Francois POIROTTE
        l'affichage pour un événement donné.
404 19e88cb8 Thomas ANDREJAK

405 a2a22ade Francois POIROTTE
        @param first_row: Indice de début de la liste des événements
406
        @param last_row: Indice de fin de la liste des événements
407 19e88cb8 Thomas ANDREJAK
        """
408 65383903 Francois POIROTTE
409 19e88cb8 Thomas ANDREJAK
        # Si la requête n'est pas générée, on le fait
410 911069bc Francois POIROTTE
        self.generate_request()
411 19e88cb8 Thomas ANDREJAK
412
        # Liste des éléments pour la tête du tableau
413 15b98053 Francois POIROTTE
        self.events = []
414 19e88cb8 Thomas ANDREJAK
415 15b98053 Francois POIROTTE
        for data in self.req[first_row : last_row]:
416 94f31908 Francois POIROTTE
            self.events.append(data)
417 19e88cb8 Thomas ANDREJAK
418 8484b8bd Francois POIROTTE
    def format_history(self):
419 19e88cb8 Thomas ANDREJAK
        """
420 a2a22ade Francois POIROTTE
        Formate les historiques correspondant aux événements sélectionnés
421 19e88cb8 Thomas ANDREJAK
        pour un affichage simple du résultat par Genshi.
422 8484b8bd Francois POIROTTE
        On génère une liste de liste, chaqu'une étant la description
423
        de l'affichage pour un historique donné.
424 19e88cb8 Thomas ANDREJAK
        """
425
426 539f69fc Francois POIROTTE
        ids = [data[0].idevent for data in self.events]
427 ee3ae8c8 Francois POIROTTE
        history = DBSession.query(
428
                    EventHistory,
429 15b98053 Francois POIROTTE
                ).filter(EventHistory.idevent.in_(ids)
430 19e88cb8 Thomas ANDREJAK
                ).order_by(desc(EventHistory.timestamp)
431
                ).order_by(desc(EventHistory.idhistory))
432 539f69fc Francois POIROTTE
        return history
433 19e88cb8 Thomas ANDREJAK
434 24d4f8b0 Vincent QUEMENER
    def generate_tmpl_context(self):
435 19e88cb8 Thomas ANDREJAK
        """
436 b2668166 Francois POIROTTE
        Génère et peuple la variable tmpl_context avec les dialogues et
437 19e88cb8 Thomas ANDREJAK
        formulaires nécessaire au fonctionnement de Vigiboard
438
        """
439
440 97f6d842 Vincent QUEMENER
        from vigiboard.controllers.root import get_last_modification_timestamp
441 bcf87133 Francois POIROTTE
442
        # Si les objets manipulés sont des Event, on a facilement les idevent.
443 94f31908 Francois POIROTTE
        if not len(self.events):
444
            ids = []
445
        elif isinstance(self.events[0][0], Event):
446 bcf87133 Francois POIROTTE
            ids = [data[0].idevent for data in self.events]
447
        # Sinon, il s'agit de CorrEvent(s) dont on récupère l'idcause.
448
        else:
449
            ids = [data[0].idcause for data in self.events]
450 e9ccb711 Francois POIROTTE
451 57387640 Francois POIROTTE
        # Ajout des formulaires et préparation
452
        # des données pour ces formulaires.
453
        tmpl_context.last_modification = \
454
            mktime(get_last_modification_timestamp(ids).timetuple())
455
456 e181e86c Francois POIROTTE
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
457 a5f99051 Francois POIROTTE
            submit_text=_('Apply'), action=url('/update'))