Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ cf3c2494

History | View | Annotate | Download (15.6 KB)

1 19e88cb8 Thomas ANDREJAK
# -*- coding: utf-8 -*-
2 65383903 Francois POIROTTE
# vim:set expandtab tabstop=4 shiftwidth=4:
3 a77de887 Francois POIROTTE
################################################################################
4
#
5 3b537383 Francois POIROTTE
# Copyright (C) 2007-2011 CS-SI
6 a77de887 Francois POIROTTE
#
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 19e88cb8 Thomas ANDREJAK
"""Gestion de la requête, des plugins et de l'affichage du Vigiboard"""
22
23 7365fb51 Francois POIROTTE
from time import mktime
24
from logging import getLogger
25
26 e181e86c Francois POIROTTE
from tg import config, tmpl_context, request, url
27 7365fb51 Francois POIROTTE
from pylons.i18n import ugettext as _
28 b2346a00 Francois POIROTTE
from paste.deploy.converters import asbool
29 f2e30877 Francois POIROTTE
from repoze.what.predicates import in_group
30 b2346a00 Francois POIROTTE
31 6ab72614 Vincent QUEMENER
from sqlalchemy import not_, and_, asc, desc
32 65a9ee16 Francois POIROTTE
from sqlalchemy.sql.expression import or_, null as expr_null, union_all
33 cf3c2494 Vincent QUEMENER
from sqlalchemy.orm import contains_eager
34 7365fb51 Francois POIROTTE
35 e7e3d45e Francois POIROTTE
from vigilo.models.session import DBSession
36
from vigilo.models.tables import Event, CorrEvent, EventHistory, \
37 eec46cb0 Vincent QUEMENER
                        Host, LowLevelService, StateName, DataPermission
38
from vigilo.models.tables.grouphierarchy import GroupHierarchy
39
from vigilo.models.tables.secondary_tables import SUPITEM_GROUP_TABLE, \
40
        USER_GROUP_TABLE
41 e181e86c Francois POIROTTE
from vigiboard.widgets.edit_event import EditEventForm
42 94f31908 Francois POIROTTE
from vigiboard.controllers.plugins import VigiboardRequestPlugin
43 7365fb51 Francois POIROTTE
44
LOGGER = getLogger(__name__)
45 19e88cb8 Thomas ANDREJAK
46
class VigiboardRequest():
47 86c3ae23 Francois POIROTTE
    """
48
    Classe gérant la génération de la requête finale,
49
    le préformatage des événements et celui des historiques
50
    """
51
52 ee3ae8c8 Francois POIROTTE
    class_ack = {
53
        'None': '',
54 df66bc2c Francois POIROTTE
        'Acknowledged': '_Ack',
55
        'AAClosed': '_Ack',
56 ee3ae8c8 Francois POIROTTE
    }
57
58 54644278 Francois POIROTTE
    def __init__(self, user, mask_closed_events=True):
59 19e88cb8 Thomas ANDREJAK
        """
60 54644278 Francois POIROTTE
        Initialisation de l'objet qui effectue les requêtes de VigiBoard
61
        sur la base de données.
62
        Cet objet est responsable de la vérification des droits de
63
        l'utilisateur sur les données manipulées.
64 19e88cb8 Thomas ANDREJAK
        """
65
66
        self.generaterq = False
67 24d74687 Francois POIROTTE
68 f2e30877 Francois POIROTTE
        is_manager = in_group('managers').is_met(request.environ)
69
70 24d74687 Francois POIROTTE
        # Sélectionne tous les IDs des services auxquels
71
        # l'utilisateur a accès.
72 24334b4b Vincent QUEMENER
        lls_query = DBSession.query(
73 53fda08d Francois POIROTTE
            LowLevelService.idservice.label("idsupitem"),
74
            LowLevelService.servicename.label("servicename"),
75 24334b4b Vincent QUEMENER
            Host.name.label("hostname"),
76 24d74687 Francois POIROTTE
            SUPITEM_GROUP_TABLE.c.idgroup.label("idsupitemgroup"),
77 24334b4b Vincent QUEMENER
        ).join(
78 24d74687 Francois POIROTTE
            (Host, Host.idhost == LowLevelService.idhost),
79 24334b4b Vincent QUEMENER
        ).outerjoin(
80 24d74687 Francois POIROTTE
            (SUPITEM_GROUP_TABLE,
81
                or_(
82
                    SUPITEM_GROUP_TABLE.c.idsupitem == \
83
                        LowLevelService.idhost,
84
                    SUPITEM_GROUP_TABLE.c.idsupitem == \
85
                        LowLevelService.idservice,
86
                )
87 24334b4b Vincent QUEMENER
            ),
88
        )
89 24d74687 Francois POIROTTE
90
        # Sélectionne tous les IDs des hôtes auxquels
91
        # l'utilisateur a accès.
92 24334b4b Vincent QUEMENER
        host_query = DBSession.query(
93
            Host.idhost.label("idsupitem"),
94
            expr_null().label("servicename"),
95
            Host.name.label("hostname"),
96 24d74687 Francois POIROTTE
            SUPITEM_GROUP_TABLE.c.idgroup.label('idsupitemgroup'),
97
        ).join(
98
            (SUPITEM_GROUP_TABLE, SUPITEM_GROUP_TABLE.c.idsupitem == \
99
                Host.idhost),
100 24334b4b Vincent QUEMENER
        )
101
102 f2e30877 Francois POIROTTE
        # Les managers ont accès à tout, les autres sont soumis
103
        # aux vérifications classiques d'accès aux données.
104
        if not is_manager:
105 eec46cb0 Vincent QUEMENER
106
            lls_query = lls_query.join(
107
                (GroupHierarchy,
108
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
109
                (DataPermission,
110
                    DataPermission.idgroup == GroupHierarchy.idparent),
111
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
112
                    DataPermission.idusergroup),
113
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
114
115
            host_query = host_query.join(
116
                (GroupHierarchy,
117
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
118
                (DataPermission,
119
                    DataPermission.idgroup == GroupHierarchy.idparent),
120
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
121
                    DataPermission.idusergroup),
122
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
123 f2e30877 Francois POIROTTE
124 24d74687 Francois POIROTTE
        # Objet Selectable renvoyant des informations sur un SupItem
125 911069bc Francois POIROTTE
        # concerné par une alerte, avec prise en compte des droits d'accès.
126 65a9ee16 Francois POIROTTE
        # On est obligés d'utiliser sqlalchemy.sql.expression.union_all
127 24334b4b Vincent QUEMENER
        # pour indiquer à SQLAlchemy de NE PAS regrouper les tables
128
        # dans la requête principale, sans quoi les résultats sont
129
        # incorrects.
130 65a9ee16 Francois POIROTTE
        # Dans PostgreSQL, UNION ALL est beaucoup plus rapide que UNION
131
        # du fait des performances limitées du DISTINCT.
132
        self.items = union_all(lls_query, host_query, correlate=False).alias()
133 8484b8bd Francois POIROTTE
134 911069bc Francois POIROTTE
        # Éléments à retourner (SELECT ...)
135
        self.table = []
136 8484b8bd Francois POIROTTE
137 911069bc Francois POIROTTE
        # Tables sur lesquelles porte la récupération (JOIN)
138
        self.join = []
139
140 cf3c2494 Vincent QUEMENER
        # Options à ajouter la requête
141
        self.option = []
142
143 911069bc Francois POIROTTE
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
144 24334b4b Vincent QUEMENER
        self.outerjoin = []
145 8484b8bd Francois POIROTTE
146 911069bc Francois POIROTTE
        # Critères de filtrage (WHERE)
147 54644278 Francois POIROTTE
        self.filter = []
148
        if mask_closed_events:
149
            self.filter.append(
150
                # On masque les événements avec l'état OK
151
                # et traités (status == u'AAClosed').
152
                not_(and_(
153
                    StateName.statename.in_([u'OK', u'UP']),
154
                    CorrEvent.status == u'AAClosed'
155
                ))
156
            )
157 8484b8bd Francois POIROTTE
158 434b574d Francois POIROTTE
        # Permet de définir le sens de tri pour la priorité.
159
        if config['vigiboard_priority_order'] == 'asc':
160 780ca169 Francois POIROTTE
            priority_order = asc(CorrEvent.priority)
161 434b574d Francois POIROTTE
        else:
162 780ca169 Francois POIROTTE
            priority_order = desc(CorrEvent.priority)
163 434b574d Francois POIROTTE
164 911069bc Francois POIROTTE
        # Tris (ORDER BY)
165 e48c54c4 Francois POIROTTE
        # Permet de répondre aux exigences suivantes :
166
        # - VIGILO_EXIG_VIGILO_BAC_0050
167
        # - VIGILO_EXIG_VIGILO_BAC_0060
168 8484b8bd Francois POIROTTE
        self.orderby = [
169 b00c0ea7 Francois POIROTTE
            desc(CorrEvent.status),                         # État acquittement
170
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
171
            priority_order,                                 # Priorité ITIL
172 24334b4b Vincent QUEMENER
        ]
173 8484b8bd Francois POIROTTE
174 b2346a00 Francois POIROTTE
        if asbool(config.get('state_first', True)):
175
            self.orderby.extend([
176
                desc(StateName.order),                      # Etat courant
177
                desc(Event.timestamp),                      # Timestamp
178
            ])
179
        else:
180
            self.orderby.extend([
181
                desc(Event.timestamp),                      # Timestamp
182
                desc(StateName.order),                      # Etat courant
183
            ])
184
185
186 911069bc Francois POIROTTE
        # Regroupements (GROUP BY)
187 b00c0ea7 Francois POIROTTE
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
188
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
189
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
190 8484b8bd Francois POIROTTE
        self.groupby = [
191 911069bc Francois POIROTTE
            StateName.order,
192
            Event.timestamp,
193
            CorrEvent.status,
194
            CorrEvent.priority,
195 b00c0ea7 Francois POIROTTE
            StateName.statename,
196 911069bc Francois POIROTTE
        ]
197 8484b8bd Francois POIROTTE
198 19e88cb8 Thomas ANDREJAK
        self.plugin = []
199
        self.events = []
200
        self.req = DBSession
201
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
259 911069bc Francois POIROTTE
        self.generaterq = True
260
261 19e88cb8 Thomas ANDREJAK
    def num_rows(self):
262
        """
263
        Retourne le nombre de lignes de la requête.
264
        Si celle-ci n'est pas encore générée, on le fait.
265

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

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

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

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

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

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

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

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

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