Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 73119f8a

History | View | Annotate | Download (16.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

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

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

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

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

52
        @param user: Nom de l'utilisateur cherchant à afficher les événements.
53
        @type  user: C{str}
54
        @param mask_closed_events: Booléen indiquant si l'on souhaite masquer les
55
            événements fermés ou non.
56
        @type  mask_closed_events: C{boolean}
57
        @param search: Dictionnaire contenant les critères de recherche.
58
        @type  search: C{dict}
59
        @param sort: Colonne de tri; vide en l'absence de tri.
60
        @type  sort: C{unicode}
61
        @param order: Ordre du tri ("asc" ou "desc"); vide en l'absence de tri.
62
        @type  order: C{unicode}
63

64
        """
65

    
66
        # Permet s'appliquer des filtres de recherche aux sous-requêtes.
67
        self.subqueries = []
68
        self.generaterq = False
69

    
70
        # Éléments à retourner (SELECT ...)
71
        self.table = []
72

    
73
        # Tables sur lesquelles porte la récupération (JOIN)
74
        self.join = []
75

    
76
        # Options à ajouter la requête
77
        self.option = []
78

    
79
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
80
        self.outerjoin = []
81

    
82
        # Critères de filtrage (WHERE)
83
        self.filter = []
84

    
85
        # Regroupements (GROUP BY)
86
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
87
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
88
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
89
        self.groupby = [
90
            StateName.order,
91
            Event.timestamp,
92
            CorrEvent.ack,
93
            CorrEvent.priority,
94
            StateName.statename,
95
        ]
96

    
97
        self.req = DBSession
98
        self.plugin = []
99
        self.events = []
100

    
101
        # Si l'utilisateur est privilégié, il a accès
102
        # à tous les hôtes/services sans restriction.
103
        if config.is_manager.is_met(request.environ):
104
            # Sélection de tous les services de la BDD.
105
            lls_query = DBSession.query(
106
                LowLevelService.idservice.label("idsupitem"),
107
                LowLevelService.servicename.label("servicename"),
108
                Host.name.label("hostname"),
109
            ).join(
110
                (Host, Host.idhost == LowLevelService.idhost),
111
            ).distinct()
112

    
113
            # Sélection de tous les hôtes de la BDD.
114
            host_query = DBSession.query(
115
                Host.idhost.label("idsupitem"),
116
                expr_null().label("servicename"),
117
                Host.name.label("hostname"),
118
            ).distinct()
119

    
120
            # Application des filtres des plugins si nécessaire.
121
            if search is not None:
122
                # On tire ici partie du fait que les listes sont passées
123
                # par référence dans les fonctions.
124
                subqueries = [lls_query, host_query]
125
                for _plugin, instance in config.get('columns_plugins', []):
126
                    instance.handle_search_fields(
127
                        self, search, INNER, subqueries)
128
                lls_query = subqueries[0]
129
                host_query = subqueries[1]
130

    
131
            # Union des deux sélections précédentes
132
            self.items = union_all(
133
                lls_query,
134
                host_query,
135
                correlate=False
136
            ).alias()
137

    
138
        # Sinon, on ne récupère que les hôtes/services auquel il a accès.
139
        else:
140
            items = DBSession.query(
141
                UserSupItem.idsupitem,
142
                UserSupItem.servicename,
143
                UserSupItem.hostname,
144
            ).filter(
145
                UserSupItem.username == user.user_name
146
            ).distinct()
147

    
148
            # Application des filtres des plugins si nécessaire.
149
            if search is not None:
150
                # On tire ici partie du fait que les listes sont passées
151
                # par référence dans les fonctions.
152
                subqueries = [items]
153
                for _plugin, instance in config.get('columns_plugins', []):
154
                    instance.handle_search_fields(
155
                        self, search, INNER, subqueries)
156
                items = subqueries[0]
157

    
158
            # Permet d'avoir le même format que pour l'autre requête.
159
            self.items = items.subquery()
160

    
161
        # Tris (ORDER BY)
162
        # Permet de répondre aux exigences suivantes :
163
        # - VIGILO_EXIG_VIGILO_BAC_0050
164
        # - VIGILO_EXIG_VIGILO_BAC_0060
165
        self.orderby = []
166
        if sort:
167
            for _plugin, instance in config.get('columns_plugins', []):
168
                criterion = instance.get_sort_criterion(self, sort)
169
                if criterion is not None:
170
                    if order == 'asc':
171
                        self.orderby.append(asc(criterion))
172
                    else:
173
                        self.orderby.append(desc(criterion))
174

    
175
        # Permet de définir le sens de tri pour la priorité.
176
        if config['vigiboard_priority_order'] == 'asc':
177
            priority_order = asc(CorrEvent.priority)
178
        else:
179
            priority_order = desc(CorrEvent.priority)
180

    
181
        self.orderby.extend([
182
            asc(CorrEvent.ack),                             # État acquittement
183
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
184
            priority_order,                                 # Priorité ITIL
185
        ])
186

    
187
        if asbool(config.get('state_first', True)):
188
            self.orderby.extend([
189
                desc(StateName.order),                      # Etat courant
190
                desc(Event.timestamp),                      # Timestamp
191
            ])
192
        else:
193
            self.orderby.extend([
194
                desc(Event.timestamp),                      # Timestamp
195
                desc(StateName.order),                      # Etat courant
196
            ])
197

    
198
        if search is not None:
199
            # 2nde passe pour les filtres : self.items est désormais défini.
200
            for _plugin, instance in config.get('columns_plugins', []):
201
                instance.handle_search_fields(self, search, ITEMS, subqueries)
202

    
203
        if mask_closed_events:
204
            self.filter.append(
205
                # On masque les événements avec l'état OK
206
                # et traités (ack == CorrEvent.ACK_CLOSED).
207
                not_(and_(
208
                    StateName.statename.in_([u'OK', u'UP']),
209
                    CorrEvent.ack == CorrEvent.ACK_CLOSED
210
                ))
211
            )
212

    
213

    
214
    def add_plugin(self, *argv):
215
        """
216
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
217
        """
218
        for i in argv:
219
            if isinstance(i, VigiboardRequestPlugin):
220
                if i.table:
221
                    self.add_table(*i.table)
222
                if i.join:
223
                    self.add_join(*i.join)
224
                if i.outerjoin:
225
                    self.add_outer_join(*i.outerjoin)
226
                if i.filter:
227
                    self.add_filter(*i.filter)
228
                if i.groupby:
229
                    self.add_group_by(*i.groupby)
230
                if i.orderby:
231
                    self.add_order_by(*i.orderby)
232
                self.plugin.append(i)
233

    
234
    def generate_request(self):
235
        """
236
        Génération de la requête avec l'ensemble des données stockées
237
        et la place dans la variable rq de la classe
238
        """
239
        if self.generaterq:
240
            return
241

    
242
        for plugin in config['columns_plugins']:
243
            self.add_plugin(plugin)
244

    
245
        # Toutes les requêtes ont besoin de récupérer l'état courant
246
        # de l'événement.
247
        self.join.append((StateName, StateName.idstatename == \
248
                                        Event.current_state))
249

    
250
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
251
        # dans la clause GROUP BY. Si une colonne apparaît dans SELECT,
252
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
253
        # Ici, on ajoute automatiquement les colonnes du SELECT au GROUP BY.
254
        self.add_group_by(*self.table)
255

    
256
        # query et join ont besoin de referrence
257
        self.req = self.req.query(*self.table)
258
        self.req = self.req.join(*self.join)
259
        self.req = self.req.options(*self.option)
260

    
261
        # le reste, non
262
        for i in self.outerjoin:
263
            self.req = self.req.outerjoin(i)
264
        for i in self.filter:
265
            self.req = self.req.filter(i)
266
        for i in self.groupby:
267
            self.req = self.req.group_by(i)
268
        for i in self.orderby:
269
            self.req = self.req.order_by(i)
270
        self.generaterq = True
271

    
272
    def num_rows(self):
273
        """
274
        Retourne le nombre de lignes de la requête.
275
        Si celle-ci n'est pas encore générée, on le fait.
276

277
        @return: Nombre de ligne
278
        """
279

    
280
        self.generate_request()
281
        return self.req.count()
282

    
283
    def add_table(self, *argv):
284
        """
285
        Ajoute une ou plusieurs tables/élément d'une table à
286
        la requête.
287

288
        @param argv: Liste des tables à ajouter
289
        """
290

    
291
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
292
        # des tables.
293

    
294
        for i in argv :
295
            for j in self.table:
296
                if str(i) == str(j):
297
                    break
298
            self.table.append(i)
299

    
300
    def add_join(self, *argv):
301
        """
302
        Ajoute une ou plusieurs jointures à
303
        la requête.
304

305
        @param argv: Liste des jointures à ajouter
306
        """
307

    
308
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
309
        # des jointures.
310

    
311
        for i in argv:
312
            for j in self.join:
313
                if str(i) == str(j):
314
                    break
315
            self.join.append(i)
316

    
317
    def add_option(self, *argv):
318
        """
319
        Ajoute une ou plusieurs options à la requête.
320

321
        @param argv: Liste des options à ajouter
322
        """
323

    
324
        # On vérifie qu'il n'y a pas de doublons
325
        # dans la liste finale des options.
326

    
327
        for i in argv:
328
            for j in self.option:
329
                if str(i) == str(j):
330
                    break
331
            self.option.append(i)
332

    
333
    def add_contains_eager(self, relation):
334
        """
335
        Ajoute une option de type contains_eager à la
336
        requête pour la relation passée en paramètre.
337
        """
338
        self.add_option(contains_eager(relation))
339

    
340
    def add_outer_join(self, *argv):
341
        """
342
        Ajoute une ou plusieurs jointures externes à
343
        la requête.
344

345
        @param argv: Liste des jointures externes à ajouter
346
        """
347

    
348
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
349
        # des jointures externes.
350

    
351
        for i in argv:
352
            for j in self.outerjoin:
353
                if str(i) == str(j):
354
                    break
355
            self.outerjoin.append(i)
356

    
357
    def add_filter(self, *argv):
358
        """
359
        Ajoute un ou plusieurs filtres à la requête.
360

361
        @param argv: Liste des filtres à ajouter
362
        """
363

    
364
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
365
        # des filtres.
366

    
367
        for i in argv:
368
            for j in self.filter:
369
                if str(i) == str(j):
370
                    break
371
            self.filter.append(i)
372

    
373
    def add_group_by(self, *argv):
374
        """
375
        Ajoute un ou plusieurs groupements à la requête.
376

377
        @param argv: Liste des groupements à ajouter
378
        """
379

    
380
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
381
        # des groupements.
382

    
383
        for i in argv:
384
            for j in self.groupby:
385
                try:
386
                    if str(i) == str(j):
387
                        break
388
                # SQLAlchemy lève cette exception pour certains attributes,
389
                # par exemple les attributs définis avec synonym().
390
                except AttributeError:
391
                    pass
392
            self.groupby.append(i)
393

    
394
    def add_order_by(self, *argv):
395
        """
396
        Ajoute un ou plusieurs orders à la requête.
397

398
        @param argv: Liste des ordres à ajouter
399
        """
400

    
401
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
402
        # des ordres.
403

    
404
        for i in argv:
405
            for j in self.orderby:
406
                if str(i) == str(j):
407
                    break
408
            self.orderby.append(i)
409

    
410
    def format_events(self, first_row, last_row):
411
        """
412
        Formate la réponse de la requête et y applique les plugins
413
        pour un affichage simple du résultat par Genshi.
414
        On génère une liste de liste, chaqu'une étant la description de
415
        l'affichage pour un événement donné.
416

417
        @param first_row: Indice de début de la liste des événements
418
        @param last_row: Indice de fin de la liste des événements
419
        """
420

    
421
        # Si la requête n'est pas générée, on le fait
422
        self.generate_request()
423

    
424
        # Liste des éléments pour la tête du tableau
425
        self.events = []
426

    
427
        for data in self.req[first_row : last_row]:
428
            self.events.append(data)
429

    
430
    def format_history(self):
431
        """
432
        Formate les historiques correspondant aux événements sélectionnés
433
        pour un affichage simple du résultat par Genshi.
434
        On génère une liste de liste, chaqu'une étant la description
435
        de l'affichage pour un historique donné.
436
        """
437

    
438
        ids = [data[0].idevent for data in self.events]
439
        history = DBSession.query(
440
                    EventHistory,
441
                ).filter(EventHistory.idevent.in_(ids)
442
                ).order_by(desc(EventHistory.timestamp)
443
                ).order_by(desc(EventHistory.idhistory))
444
        return history
445

    
446
    def generate_tmpl_context(self):
447
        """
448
        Génère et peuple la variable tmpl_context avec les Dialogs et
449
        formulaires nécessaire au fonctionnement de Vigiboard
450
        """
451

    
452
        from vigiboard.controllers.root import get_last_modification_timestamp
453

    
454
        # Si les objets manipulés sont des Event, on a facilement les idevent.
455
        if not len(self.events):
456
            ids = []
457
        elif isinstance(self.events[0][0], Event):
458
            ids = [data[0].idevent for data in self.events]
459
        # Sinon, il s'agit de CorrEvent(s) dont on récupère l'idcause.
460
        else:
461
            ids = [data[0].idcause for data in self.events]
462

    
463
        # Ajout des formulaires et préparation
464
        # des données pour ces formulaires.
465
        tmpl_context.last_modification = \
466
            mktime(get_last_modification_timestamp(ids).timetuple())
467

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