Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 180b869a

History | View | Annotate | Download (15.8 KB)

1
# -*- coding: utf-8 -*-
2
# vim:set expandtab tabstop=4 shiftwidth=4:
3
################################################################################
4
#
5
# Copyright (C) 2007-2011 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 or_, 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, DataPermission, UserSupItem
37
from vigilo.models.tables.grouphierarchy import GroupHierarchy
38
from vigilo.models.tables.secondary_tables import SUPITEM_GROUP_TABLE, \
39
        USER_GROUP_TABLE
40
from vigiboard.widgets.edit_event import EditEventForm
41
from vigiboard.controllers.plugins import VigiboardRequestPlugin
42

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

    
49
    class_ack = {
50
        'None': '',
51
        'Acknowledged': '_Ack',
52
        'AAClosed': '_Ack',
53
    }
54

    
55
    def __init__(self, user, mask_closed_events=True, supitemgroup=None):
56
        """
57
        Initialisation de l'objet qui effectue les requêtes de VigiBoard
58
        sur la base de données.
59
        Cet objet est responsable de la vérification des droits de
60
        l'utilisateur sur les données manipulées.
61
        """
62

    
63
        self.generaterq = False
64

    
65
        is_manager = in_group('managers').is_met(request.environ)
66

    
67
        # Si l'utilisateur fait partie du groupe 'managers',
68
        # il a accès à tous les hôtes/services sans restriction.
69
        if is_manager:
70

    
71
            # Sélection de tous les services de la BDD.
72
            self.lls_query = DBSession.query(
73
                LowLevelService.idservice.label("idsupitem"),
74
                LowLevelService.servicename.label("servicename"),
75
                Host.name.label("hostname"),
76
            ).join(
77
                (Host, Host.idhost == LowLevelService.idhost),
78
            )
79
            
80
            # Ajout d'un filtre sur le groupe de supitems
81
            if supitemgroup:
82
                self.lls_query = self.lls_query.join(
83
                    (SUPITEM_GROUP_TABLE,
84
                        or_(
85
                            SUPITEM_GROUP_TABLE.c.idsupitem == \
86
                                LowLevelService.idhost,
87
                            SUPITEM_GROUP_TABLE.c.idsupitem == \
88
                                LowLevelService.idservice,
89
                        )
90
                    ),
91
                    (GroupHierarchy, GroupHierarchy.idchild == 
92
                        SUPITEM_GROUP_TABLE.c.idgroup)
93
                ).filter(
94
                    GroupHierarchy.idparent == supitemgroup
95
                )
96

    
97
            self.lls_query = self.lls_query.distinct()
98

    
99
            # Sélection de tous les hôtes de la BDD.
100
            self.host_query = DBSession.query(
101
                Host.idhost.label("idsupitem"),
102
                expr_null().label("servicename"),
103
                Host.name.label("hostname"),
104
            )
105
            
106
            # Ajout d'un filtre sur le groupe de supitems
107
            if supitemgroup:
108
                self.host_query = self.host_query.join(
109
                    (SUPITEM_GROUP_TABLE,
110
                        SUPITEM_GROUP_TABLE.c.idsupitem == \
111
                            Host.idhost,
112
                    ),
113
                    (GroupHierarchy, GroupHierarchy.idchild == 
114
                        SUPITEM_GROUP_TABLE.c.idgroup)
115
                ).filter(
116
                    GroupHierarchy.idparent == supitemgroup
117
                )
118

    
119
            self.host_query = self.host_query.distinct()
120

    
121
            # Union des deux sélections précédentes
122
            self.items = union_all(
123
                self.lls_query,
124
                self.host_query,
125
                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
            self.items = DBSession.query(
131
                UserSupItem.idsupitem,
132
                UserSupItem.servicename,
133
                UserSupItem.hostname,
134
            ).filter(
135
                UserSupItem.username == user.user_name
136
            )
137

    
138
            # Ajout d'un filtre sur le groupe de supitems
139
            if supitemgroup:
140
                self.items = self.items.filter(
141
                    UserSupItem.idsupitemgroup == supitemgroup
142
                )
143
            
144
            self.items = self.items.distinct().subquery()
145

    
146
        # Éléments à retourner (SELECT ...)
147
        self.table = []
148

    
149
        # Tables sur lesquelles porte la récupération (JOIN)
150
        self.join = []
151

    
152
        # Options à ajouter la requête
153
        self.option = []
154

    
155
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
156
        self.outerjoin = []
157

    
158
        # Critères de filtrage (WHERE)
159
        self.filter = []
160
        if mask_closed_events:
161
            self.filter.append(
162
                # On masque les événements avec l'état OK
163
                # et traités (status == u'AAClosed').
164
                not_(and_(
165
                    StateName.statename.in_([u'OK', u'UP']),
166
                    CorrEvent.status == u'AAClosed'
167
                ))
168
            )
169

    
170
        # Permet de définir le sens de tri pour la priorité.
171
        if config['vigiboard_priority_order'] == 'asc':
172
            priority_order = asc(CorrEvent.priority)
173
        else:
174
            priority_order = desc(CorrEvent.priority)
175

    
176
        # Tris (ORDER BY)
177
        # Permet de répondre aux exigences suivantes :
178
        # - VIGILO_EXIG_VIGILO_BAC_0050
179
        # - VIGILO_EXIG_VIGILO_BAC_0060
180
        self.orderby = [
181
            desc(CorrEvent.status),                         # État acquittement
182
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
183
            priority_order,                                 # Priorité ITIL
184
        ]
185

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

    
197

    
198
        # Regroupements (GROUP BY)
199
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
200
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
201
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
202
        self.groupby = [
203
            StateName.order,
204
            Event.timestamp,
205
            CorrEvent.status,
206
            CorrEvent.priority,
207
            StateName.statename,
208
        ]
209

    
210
        self.plugin = []
211
        self.events = []
212
        self.req = DBSession
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

    
271
        self.generaterq = True
272

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
453
        from vigiboard.controllers.root import get_last_modification_timestamp
454

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

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

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