Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ cf3c2494

History | View | Annotate | Download (15.6 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
from logging import getLogger
25

    
26
from tg import config, tmpl_context, request, url
27
from pylons.i18n import ugettext as _
28
from paste.deploy.converters import asbool
29
from repoze.what.predicates import in_group
30

    
31
from sqlalchemy import not_, and_, asc, desc
32
from sqlalchemy.sql.expression import or_, null as expr_null, union_all
33
from sqlalchemy.orm import contains_eager
34

    
35
from vigilo.models.session import DBSession
36
from vigilo.models.tables import Event, CorrEvent, EventHistory, \
37
                        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
from vigiboard.widgets.edit_event import EditEventForm
42
from vigiboard.controllers.plugins import VigiboardRequestPlugin
43

    
44
LOGGER = getLogger(__name__)
45

    
46
class VigiboardRequest():
47
    """
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
    class_ack = {
53
        'None': '',
54
        'Acknowledged': '_Ack',
55
        'AAClosed': '_Ack',
56
    }
57

    
58
    def __init__(self, user, mask_closed_events=True):
59
        """
60
        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
        """
65

    
66
        self.generaterq = False
67

    
68
        is_manager = in_group('managers').is_met(request.environ)
69

    
70
        # Sélectionne tous les IDs des services auxquels
71
        # l'utilisateur a accès.
72
        lls_query = DBSession.query(
73
            LowLevelService.idservice.label("idsupitem"),
74
            LowLevelService.servicename.label("servicename"),
75
            Host.name.label("hostname"),
76
            SUPITEM_GROUP_TABLE.c.idgroup.label("idsupitemgroup"),
77
        ).join(
78
            (Host, Host.idhost == LowLevelService.idhost),
79
        ).outerjoin(
80
            (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
            ),
88
        )
89

    
90
        # Sélectionne tous les IDs des hôtes auxquels
91
        # l'utilisateur a accès.
92
        host_query = DBSession.query(
93
            Host.idhost.label("idsupitem"),
94
            expr_null().label("servicename"),
95
            Host.name.label("hostname"),
96
            SUPITEM_GROUP_TABLE.c.idgroup.label('idsupitemgroup'),
97
        ).join(
98
            (SUPITEM_GROUP_TABLE, SUPITEM_GROUP_TABLE.c.idsupitem == \
99
                Host.idhost),
100
        )
101

    
102
        # 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

    
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

    
124
        # Objet Selectable renvoyant des informations sur un SupItem
125
        # concerné par une alerte, avec prise en compte des droits d'accès.
126
        # On est obligés d'utiliser sqlalchemy.sql.expression.union_all
127
        # pour indiquer à SQLAlchemy de NE PAS regrouper les tables
128
        # dans la requête principale, sans quoi les résultats sont
129
        # incorrects.
130
        # 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

    
134
        # Éléments à retourner (SELECT ...)
135
        self.table = []
136

    
137
        # Tables sur lesquelles porte la récupération (JOIN)
138
        self.join = []
139

    
140
        # Options à ajouter la requête
141
        self.option = []
142

    
143
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
144
        self.outerjoin = []
145

    
146
        # Critères de filtrage (WHERE)
147
        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

    
158
        # Permet de définir le sens de tri pour la priorité.
159
        if config['vigiboard_priority_order'] == 'asc':
160
            priority_order = asc(CorrEvent.priority)
161
        else:
162
            priority_order = desc(CorrEvent.priority)
163

    
164
        # Tris (ORDER BY)
165
        # Permet de répondre aux exigences suivantes :
166
        # - VIGILO_EXIG_VIGILO_BAC_0050
167
        # - VIGILO_EXIG_VIGILO_BAC_0060
168
        self.orderby = [
169
            desc(CorrEvent.status),                         # État acquittement
170
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
171
            priority_order,                                 # Priorité ITIL
172
        ]
173

    
174
        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
        # Regroupements (GROUP BY)
187
        # 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
        self.groupby = [
191
            StateName.order,
192
            Event.timestamp,
193
            CorrEvent.status,
194
            CorrEvent.priority,
195
            StateName.statename,
196
        ]
197

    
198
        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
        for i in argv:
207
            if isinstance(i, VigiboardRequestPlugin):
208
                if i.table:
209
                    self.add_table(*i.table)
210
                if i.join:
211
                    self.add_join(*i.join)
212
                if i.outerjoin:
213
                    self.add_outer_join(*i.outerjoin)
214
                if i.filter:
215
                    self.add_filter(*i.filter)
216
                if i.groupby:
217
                    self.add_group_by(*i.groupby)
218
                if i.orderby:
219
                    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
        if self.generaterq:
228
            return
229

    
230
        for plugin in config['columns_plugins']:
231
            self.add_plugin(plugin)
232

    
233
        # Toutes les requêtes ont besoin de récupérer l'état courant
234
        # de l'événement.
235
        self.join.append((StateName, StateName.idstatename == \
236
                                        Event.current_state))
237

    
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
        self.add_group_by(*self.table)
243

    
244
        # query et join ont besoin de referrence
245
        self.req = self.req.query(*self.table)
246
        self.req = self.req.join(*self.join)
247
        self.req = self.req.options(*self.option)
248

    
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
        self.generaterq = True
260

    
261
    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
        @return: Nombre de ligne
267
        """
268

    
269
        self.generate_request()
270
        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

    
280
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
281
        # des tables.
282

    
283
        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

    
297
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
298
        # des jointures.
299

    
300
        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
    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
    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

    
337
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
338
        # des jointures externes.
339

    
340
        for i in argv:
341
            for j in self.outerjoin:
342
                if str(i) == str(j):
343
                    break
344
            self.outerjoin.append(i)
345

    
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

    
353
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
354
        # des filtres.
355

    
356
        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

    
369
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
370
        # des groupements.
371

    
372
        for i in argv:
373
            for j in self.groupby:
374
                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
            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

    
390
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
391
        # des ordres.
392

    
393
        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
        l'affichage pour un événement donné.
405

406
        @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
        """
409

    
410
        # Si la requête n'est pas générée, on le fait
411
        self.generate_request()
412

    
413
        # Liste des éléments pour la tête du tableau
414
        self.events = []
415

    
416
        for data in self.req[first_row : last_row]:
417
            self.events.append(data)
418

    
419
    def format_history(self):
420
        """
421
        Formate les historiques correspondant aux événements sélectionnés
422
        pour un affichage simple du résultat par Genshi.
423
        On génère une liste de liste, chaqu'une étant la description
424
        de l'affichage pour un historique donné.
425
        """
426

    
427
        ids = [data[0].idevent for data in self.events]
428
        history = DBSession.query(
429
                    EventHistory,
430
                ).filter(EventHistory.idevent.in_(ids)
431
                ).order_by(desc(EventHistory.timestamp)
432
                ).order_by(desc(EventHistory.idhistory))
433
        return history
434

    
435
    def generate_tmpl_context(self):
436
        """
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
        from vigiboard.controllers.root import get_last_modification_timestamp
442

    
443
        # Si les objets manipulés sont des Event, on a facilement les idevent.
444
        if not len(self.events):
445
            ids = []
446
        elif isinstance(self.events[0][0], Event):
447
            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

    
452
        # 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
        tmpl_context.edit_event_form = EditEventForm("edit_event_form",
458
            submit_text=_('Apply'), action=url('/update'))