Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 6f6efdcd

History | View | Annotate | Download (14.1 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
33

    
34
from vigilo.models.session import DBSession
35
from vigilo.models.tables import Event, CorrEvent, EventHistory, \
36
                        Host, LowLevelService, StateName
37
from vigilo.models.tables.secondary_tables import SUPITEM_GROUP_TABLE
38
from vigiboard.widgets.edit_event import EditEventForm
39
from vigiboard.controllers.plugins import VigiboardRequestPlugin
40

    
41
LOGGER = getLogger(__name__)
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):
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
        # Sélectionne tous les IDs des services auxquels
68
        # l'utilisateur a accès.
69
        lls_query = DBSession.query(
70
            LowLevelService.idservice.label("idsupitem"),
71
            LowLevelService.servicename.label("servicename"),
72
            Host.name.label("hostname"),
73
            SUPITEM_GROUP_TABLE.c.idgroup.label("idsupitemgroup"),
74
        ).join(
75
            (Host, Host.idhost == LowLevelService.idhost),
76
        ).outerjoin(
77
            (SUPITEM_GROUP_TABLE,
78
                or_(
79
                    SUPITEM_GROUP_TABLE.c.idsupitem == \
80
                        LowLevelService.idhost,
81
                    SUPITEM_GROUP_TABLE.c.idsupitem == \
82
                        LowLevelService.idservice,
83
                )
84
            ),
85
        )
86

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

    
99
        # Les managers ont accès à tout, les autres sont soumis
100
        # aux vérifications classiques d'accès aux données.
101
        if not is_manager:
102
            user_groups = [ug[0] for ug in user.supitemgroups() if ug[1]]
103
            lls_query = lls_query.filter(
104
                SUPITEM_GROUP_TABLE.c.idgroup.in_(user_groups)
105
            )
106
            host_query = host_query.filter(
107
                SUPITEM_GROUP_TABLE.c.idgroup.in_(user_groups)
108
            )
109

    
110

    
111
        # Objet Selectable renvoyant des informations sur un SupItem
112
        # concerné par une alerte, avec prise en compte des droits d'accès.
113
        # On est obligés d'utiliser sqlalchemy.sql.expression.union
114
        # pour indiquer à SQLAlchemy de NE PAS regrouper les tables
115
        # dans la requête principale, sans quoi les résultats sont
116
        # incorrects.
117
        self.items = union(lls_query, host_query, correlate=False).alias()
118

    
119
        # Éléments à retourner (SELECT ...)
120
        self.table = []
121

    
122
        # Tables sur lesquelles porte la récupération (JOIN)
123
        self.join = []
124

    
125
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
126
        self.outerjoin = []
127

    
128
        # Critères de filtrage (WHERE)
129
        self.filter = []
130
        if mask_closed_events:
131
            self.filter.append(
132
                # On masque les événements avec l'état OK
133
                # et traités (status == u'AAClosed').
134
                not_(and_(
135
                    StateName.statename.in_([u'OK', u'UP']),
136
                    CorrEvent.status == u'AAClosed'
137
                ))
138
            )
139

    
140
        # Permet de définir le sens de tri pour la priorité.
141
        if config['vigiboard_priority_order'] == 'asc':
142
            priority_order = asc(CorrEvent.priority)
143
        else:
144
            priority_order = desc(CorrEvent.priority)
145

    
146
        # Tris (ORDER BY)
147
        # Permet de répondre aux exigences suivantes :
148
        # - VIGILO_EXIG_VIGILO_BAC_0050
149
        # - VIGILO_EXIG_VIGILO_BAC_0060
150
        self.orderby = [
151
            desc(CorrEvent.status),                         # État acquittement
152
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
153
            priority_order,                                 # Priorité ITIL
154
        ]
155

    
156
        if asbool(config.get('state_first', True)):
157
            self.orderby.extend([
158
                desc(StateName.order),                      # Etat courant
159
                desc(Event.timestamp),                      # Timestamp
160
            ])
161
        else:
162
            self.orderby.extend([
163
                desc(Event.timestamp),                      # Timestamp
164
                desc(StateName.order),                      # Etat courant
165
            ])
166

    
167

    
168
        # Regroupements (GROUP BY)
169
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
170
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
171
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
172
        self.groupby = [
173
            StateName.order,
174
            Event.timestamp,
175
            CorrEvent.status,
176
            CorrEvent.priority,
177
            StateName.statename,
178
        ]
179

    
180
        self.plugin = []
181
        self.events = []
182
        self.req = DBSession
183

    
184
    def add_plugin(self, *argv):
185
        """
186
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
187
        """
188
        for i in argv:
189
            if isinstance(i, VigiboardRequestPlugin):
190
                if i.table:
191
                    self.add_table(*i.table)
192
                if i.join:
193
                    self.add_join(*i.join)
194
                if i.outerjoin:
195
                    self.add_outer_join(*i.outerjoin)
196
                if i.filter:
197
                    self.add_filter(*i.filter)
198
                if i.groupby:
199
                    self.add_group_by(*i.groupby)
200
                if i.orderby:
201
                    self.add_order_by(*i.orderby)
202
                self.plugin.append(i)
203

    
204
    def generate_request(self):
205
        """
206
        Génération de la requête avec l'ensemble des données stockées
207
        et la place dans la variable rq de la classe
208
        """
209
        if self.generaterq:
210
            return
211

    
212
        for plugin in config['columns_plugins']:
213
            self.add_plugin(plugin)
214

    
215
        # Toutes les requêtes ont besoin de récupérer l'état courant
216
        # de l'événement.
217
        self.join.append((StateName, StateName.idstatename == \
218
                                        Event.current_state))
219

    
220
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
221
        # dans la clause GROUP BY. Si une colonne apparaît dans SELECT,
222
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
223
        # Ici, on ajoute automatiquement les colonnes du SELECT au GROUP BY.
224
        self.add_group_by(*self.table)
225

    
226
        # query et join ont besoin de referrence
227
        self.req = self.req.query(*self.table)
228
        self.req = self.req.join(*self.join)
229

    
230
        # le reste, non
231
        for i in self.outerjoin:
232
            self.req = self.req.outerjoin(i)
233
        for i in self.filter:
234
            self.req = self.req.filter(i)
235
        for i in self.groupby:
236
            self.req = self.req.group_by(i)
237
        for i in self.orderby:
238
            self.req = self.req.order_by(i)
239

    
240
        self.generaterq = True
241

    
242
    def num_rows(self):
243
        """
244
        Retourne le nombre de lignes de la requête.
245
        Si celle-ci n'est pas encore générée, on le fait.
246

247
        @return: Nombre de lignes
248
        """
249

    
250
        self.generate_request()
251
        return self.req.count()
252

    
253
    def add_table(self, *argv):
254
        """
255
        Ajoute une ou plusieurs tables/élément d'une table à
256
        la requête.
257

258
        @param argv: Liste des tables à ajouter
259
        """
260

    
261
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
262
        # des tables.
263

    
264
        for i in argv :
265
            for j in self.table:
266
                if str(i) == str(j):
267
                    break
268
            self.table.append(i)
269

    
270
    def add_join(self, *argv):
271
        """
272
        Ajoute une ou plusieurs jointures à
273
        la requête.
274

275
        @param argv: Liste des jointures à ajouter
276
        """
277

    
278
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
279
        # des jointures.
280

    
281
        for i in argv:
282
            for j in self.join:
283
                if str(i) == str(j):
284
                    break
285
            self.join.append(i)
286

    
287
    def add_outer_join(self, *argv):
288
        """
289
        Ajoute une ou plusieurs jointures externes à
290
        la requête.
291

292
        @param argv: Liste des jointures externes à ajouter
293
        """
294

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

    
298
        for i in argv:
299
            for j in self.outerjoin:
300
                if str(i) == str(j):
301
                    break
302
            self.outerjoin.append(i)
303

    
304
    def add_filter(self, *argv):
305
        """
306
        Ajoute un ou plusieurs filtres à la requête.
307

308
        @param argv: Liste des filtres à ajouter
309
        """
310

    
311
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
312
        # des filtres.
313

    
314
        for i in argv:
315
            for j in self.filter:
316
                if str(i) == str(j):
317
                    break
318
            self.filter.append(i)
319

    
320
    def add_group_by(self, *argv):
321
        """
322
        Ajoute un ou plusieurs groupements à la requête.
323

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

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

    
330
        for i in argv:
331
            for j in self.groupby:
332
                try:
333
                    if str(i) == str(j):
334
                        break
335
                # SQLAlchemy lève cette exception pour certains attributes,
336
                # par exemple les attributs définis avec synonym().
337
                except AttributeError:
338
                    pass
339
            self.groupby.append(i)
340

    
341
    def add_order_by(self, *argv):
342
        """
343
        Ajoute un ou plusieurs orders à la requête.
344

345
        @param argv: Liste des ordres à ajouter
346
        """
347

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

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

    
357
    def format_events(self, first_row, last_row):
358
        """
359
        Formate la réponse de la requête et y applique les plugins
360
        pour un affichage simple du résultat par Genshi.
361
        On génère une liste de liste, chaqu'une étant la description de
362
        l'affichage pour un événement donné.
363

364
        @param first_row: Indice de début de la liste des événements
365
        @param last_row: Indice de fin de la liste des événements
366
        """
367

    
368
        # Si la requête n'est pas générée, on le fait
369
        self.generate_request()
370

    
371
        # Liste des éléments pour la tête du tableau
372
        self.events = []
373

    
374
        for data in self.req[first_row : last_row]:
375
            self.events.append(data)
376

    
377
    def format_history(self):
378
        """
379
        Formate les historiques correspondant aux événements sélectionnés
380
        pour un affichage simple du résultat par Genshi.
381
        On génère une liste de liste, chaqu'une étant la description
382
        de l'affichage pour un historique donné.
383
        """
384

    
385
        ids = [data[0].idevent for data in self.events]
386
        history = DBSession.query(
387
                    EventHistory,
388
                ).filter(EventHistory.idevent.in_(ids)
389
                ).order_by(desc(EventHistory.timestamp)
390
                ).order_by(desc(EventHistory.idhistory))
391
        return history
392

    
393
    def generate_tmpl_context(self):
394
        """
395
        Génère et peuple la variable tmpl_context avec les Dialogs et
396
        formulaires nécessaire au fonctionnement de Vigiboard
397
        """
398

    
399
        from vigiboard.controllers.root import get_last_modification_timestamp
400

    
401
        # Si les objets manipulés sont des Event, on a facilement les idevent.
402
        if not len(self.events):
403
            ids = []
404
        elif isinstance(self.events[0][0], Event):
405
            ids = [data[0].idevent for data in self.events]
406
        # Sinon, il s'agit de CorrEvent(s) dont on récupère l'idcause.
407
        else:
408
            ids = [data[0].idcause for data in self.events]
409

    
410
        # Ajout des formulaires et préparation
411
        # des données pour ces formulaires.
412
        tmpl_context.last_modification = \
413
            mktime(get_last_modification_timestamp(ids).timetuple())
414

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