Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 8ba2de75

History | View | Annotate | Download (15.5 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, UserSupItem
37
from vigilo.models.tables.grouphierarchy import GroupHierarchy
38
from vigilo.models.tables.secondary_tables import SUPITEM_GROUP_TABLE
39
from vigiboard.widgets.edit_event import EditEventForm
40
from vigiboard.controllers.plugins import VigiboardRequestPlugin, INNER, ITEMS
41

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

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

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

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

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

    
66
        # Options à ajouter la requête
67
        self.option = []
68

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

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

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

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

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

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

    
114
        self.req = DBSession
115
        self.plugin = []
116
        self.events = []
117

    
118

    
119
        is_manager = in_group('managers').is_met(request.environ)
120

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

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

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

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

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

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

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

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

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

    
196

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

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

    
225
        for plugin in config['columns_plugins']:
226
            self.add_plugin(plugin)
227

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

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

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

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

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

260
        @return: Nombre de ligne
261
        """
262

    
263
        self.generate_request()
264
        return self.req.count()
265

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

271
        @param argv: Liste des tables à ajouter
272
        """
273

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

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

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

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

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

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

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

304
        @param argv: Liste des options à ajouter
305
        """
306

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

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

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

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

328
        @param argv: Liste des jointures externes à ajouter
329
        """
330

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

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

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

344
        @param argv: Liste des filtres à ajouter
345
        """
346

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

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

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

360
        @param argv: Liste des groupements à ajouter
361
        """
362

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

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

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

381
        @param argv: Liste des ordres à ajouter
382
        """
383

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

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

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

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

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

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

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

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

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

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

    
435
        from vigiboard.controllers.root import get_last_modification_timestamp
436

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

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

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