Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 0f0e32ed

History | View | Annotate | Download (15.2 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
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
    class_ack = {
49
        'None': '',
50
        'Acknowledged': '_Ack',
51
        'AAClosed': '_Ack',
52
    }
53

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

    
62
        # Permet s'appliquer des filtres de recherche aux sous-requêtes.
63
        self.subqueries = []
64
        self.generaterq = False
65

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

    
68
        # Si l'utilisateur fait partie du groupe 'managers',
69
        # il a accès à tous les hôtes/services sans restriction.
70
        if is_manager:
71
            # Sélection de tous les services de la BDD.
72
            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
            ).distinct()
79

    
80
            # Sélection de tous les hôtes de la BDD.
81
            host_query = DBSession.query(
82
                Host.idhost.label("idsupitem"),
83
                expr_null().label("servicename"),
84
                Host.name.label("hostname"),
85
            ).host_query.distinct()
86

    
87
            # Application des filtres des plugins si nécessaire.
88
            if search is not None:
89
                # On tire ici partie du fait que les listes sont passées
90
                # par référence dans les fonctions.
91
                subqueries = [lls_query, host_query]
92
                for plugin, instance in config.get('columns_plugins', []):
93
                    instance.handle_search_fields(self, search, )
94
                lls_query = subqueries[0]
95
                host_query = subqueries[1]
96

    
97
            # Union des deux sélections précédentes
98
            self.items = union_all(
99
                lls_query,
100
                host_query,
101
                correlate=False
102
            ).alias()
103

    
104
        # Sinon, on ne récupère que les hôtes/services auquel il a accès.
105
        else:
106
            items = DBSession.query(
107
                UserSupItem.idsupitem,
108
                UserSupItem.servicename,
109
                UserSupItem.hostname,
110
            ).filter(
111
                UserSupItem.username == user.user_name
112
            ).distinct()
113

    
114
            # Application des filtres des plugins si nécessaire.
115
            if search is not None:
116
                # On tire ici partie du fait que les listes sont passées
117
                # par référence dans les fonctions.
118
                subqueries = [items]
119
                for plugin, instance in config.get('columns_plugins', []):
120
                    instance.handle_search_fields(self, search, subqueries)
121
                items = subqueries[0]
122

    
123
            self.items = items.subquery()
124

    
125
        # Éléments à retourner (SELECT ...)
126
        self.table = []
127

    
128
        # Tables sur lesquelles porte la récupération (JOIN)
129
        self.join = []
130

    
131
        # Options à ajouter la requête
132
        self.option = []
133

    
134
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
135
        self.outerjoin = []
136

    
137
        # Critères de filtrage (WHERE)
138
        self.filter = []
139
        if mask_closed_events:
140
            self.filter.append(
141
                # On masque les événements avec l'état OK
142
                # et traités (status == u'AAClosed').
143
                not_(and_(
144
                    StateName.statename.in_([u'OK', u'UP']),
145
                    CorrEvent.status == u'AAClosed'
146
                ))
147
            )
148

    
149
        # Permet de définir le sens de tri pour la priorité.
150
        if config['vigiboard_priority_order'] == 'asc':
151
            priority_order = asc(CorrEvent.priority)
152
        else:
153
            priority_order = desc(CorrEvent.priority)
154

    
155
        # Tris (ORDER BY)
156
        # Permet de répondre aux exigences suivantes :
157
        # - VIGILO_EXIG_VIGILO_BAC_0050
158
        # - VIGILO_EXIG_VIGILO_BAC_0060
159
        self.orderby = [
160
            desc(CorrEvent.status),                         # État acquittement
161
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
162
            priority_order,                                 # Priorité ITIL
163
        ]
164

    
165
        if asbool(config.get('state_first', True)):
166
            self.orderby.extend([
167
                desc(StateName.order),                      # Etat courant
168
                desc(Event.timestamp),                      # Timestamp
169
            ])
170
        else:
171
            self.orderby.extend([
172
                desc(Event.timestamp),                      # Timestamp
173
                desc(StateName.order),                      # Etat courant
174
            ])
175

    
176

    
177
        # Regroupements (GROUP BY)
178
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
179
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
180
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
181
        self.groupby = [
182
            StateName.order,
183
            Event.timestamp,
184
            CorrEvent.status,
185
            CorrEvent.priority,
186
            StateName.statename,
187
        ]
188

    
189
        self.plugin = []
190
        self.events = []
191
        self.req = DBSession
192

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

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

    
221
        for plugin in config['columns_plugins']:
222
            self.add_plugin(plugin)
223

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

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

    
235
        # query et join ont besoin de referrence
236
        self.req = self.req.query(*self.table)
237
        self.req = self.req.join(*self.join)
238
        self.req = self.req.options(*self.option)
239

    
240
        # le reste, non
241
        for i in self.outerjoin:
242
            self.req = self.req.outerjoin(i)
243
        for i in self.filter:
244
            self.req = self.req.filter(i)
245
        for i in self.groupby:
246
            self.req = self.req.group_by(i)
247
        for i in self.orderby:
248
            self.req = self.req.order_by(i)
249
        self.generaterq = True
250

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

256
        @return: Nombre de ligne
257
        """
258

    
259
        self.generate_request()
260
        return self.req.count()
261

    
262
    def add_table(self, *argv):
263
        """
264
        Ajoute une ou plusieurs tables/élément d'une table à
265
        la requête.
266

267
        @param argv: Liste des tables à ajouter
268
        """
269

    
270
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
271
        # des tables.
272

    
273
        for i in argv :
274
            for j in self.table:
275
                if str(i) == str(j):
276
                    break
277
            self.table.append(i)
278

    
279
    def add_join(self, *argv):
280
        """
281
        Ajoute une ou plusieurs jointures à
282
        la requête.
283

284
        @param argv: Liste des jointures à ajouter
285
        """
286

    
287
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
288
        # des jointures.
289

    
290
        for i in argv:
291
            for j in self.join:
292
                if str(i) == str(j):
293
                    break
294
            self.join.append(i)
295

    
296
    def add_option(self, *argv):
297
        """
298
        Ajoute une ou plusieurs options à la requête.
299

300
        @param argv: Liste des options à ajouter
301
        """
302

    
303
        # On vérifie qu'il n'y a pas de doublons
304
        # dans la liste finale des options.
305

    
306
        for i in argv:
307
            for j in self.option:
308
                if str(i) == str(j):
309
                    break
310
            self.option.append(i)
311

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

    
319
    def add_outer_join(self, *argv):
320
        """
321
        Ajoute une ou plusieurs jointures externes à
322
        la requête.
323

324
        @param argv: Liste des jointures externes à ajouter
325
        """
326

    
327
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
328
        # des jointures externes.
329

    
330
        for i in argv:
331
            for j in self.outerjoin:
332
                if str(i) == str(j):
333
                    break
334
            self.outerjoin.append(i)
335

    
336
    def add_filter(self, *argv):
337
        """
338
        Ajoute un ou plusieurs filtres à la requête.
339

340
        @param argv: Liste des filtres à ajouter
341
        """
342

    
343
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
344
        # des filtres.
345

    
346
        for i in argv:
347
            for j in self.filter:
348
                if str(i) == str(j):
349
                    break
350
            self.filter.append(i)
351

    
352
    def add_group_by(self, *argv):
353
        """
354
        Ajoute un ou plusieurs groupements à la requête.
355

356
        @param argv: Liste des groupements à ajouter
357
        """
358

    
359
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
360
        # des groupements.
361

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

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

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

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

    
383
        for i in argv:
384
            for j in self.orderby:
385
                if str(i) == str(j):
386
                    break
387
            self.orderby.append(i)
388

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

396
        @param first_row: Indice de début de la liste des événements
397
        @param last_row: Indice de fin de la liste des événements
398
        """
399

    
400
        # Si la requête n'est pas générée, on le fait
401
        self.generate_request()
402

    
403
        # Liste des éléments pour la tête du tableau
404
        self.events = []
405

    
406
        for data in self.req[first_row : last_row]:
407
            self.events.append(data)
408

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

    
417
        ids = [data[0].idevent for data in self.events]
418
        history = DBSession.query(
419
                    EventHistory,
420
                ).filter(EventHistory.idevent.in_(ids)
421
                ).order_by(desc(EventHistory.timestamp)
422
                ).order_by(desc(EventHistory.idhistory))
423
        return history
424

    
425
    def generate_tmpl_context(self):
426
        """
427
        Génère et peuple la variable tmpl_context avec les Dialogs et
428
        formulaires nécessaire au fonctionnement de Vigiboard
429
        """
430

    
431
        from vigiboard.controllers.root import get_last_modification_timestamp
432

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

    
442
        # Ajout des formulaires et préparation
443
        # des données pour ces formulaires.
444
        tmpl_context.last_modification = \
445
            mktime(get_last_modification_timestamp(ids).timetuple())
446

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