Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ 65a9ee16

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

    
34
from vigilo.models.session import DBSession
35
from vigilo.models.tables import Event, CorrEvent, EventHistory, \
36
                        Host, LowLevelService, StateName, DataPermission
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
LOGGER = getLogger(__name__)
44

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

    
51
    class_ack = {
52
        'None': '',
53
        'Acknowledged': '_Ack',
54
        'AAClosed': '_Ack',
55
    }
56

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

    
65
        self.generaterq = False
66

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

    
69
        # Sélectionne tous les IDs des services auxquels
70
        # l'utilisateur a accès.
71
        lls_query = DBSession.query(
72
            LowLevelService.idservice.label("idsupitem"),
73
            LowLevelService.servicename.label("servicename"),
74
            Host.name.label("hostname"),
75
            SUPITEM_GROUP_TABLE.c.idgroup.label("idsupitemgroup"),
76
        ).join(
77
            (Host, Host.idhost == LowLevelService.idhost),
78
        ).outerjoin(
79
            (SUPITEM_GROUP_TABLE,
80
                or_(
81
                    SUPITEM_GROUP_TABLE.c.idsupitem == \
82
                        LowLevelService.idhost,
83
                    SUPITEM_GROUP_TABLE.c.idsupitem == \
84
                        LowLevelService.idservice,
85
                )
86
            ),
87
        )
88

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

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

    
112
            lls_query = lls_query.join(
113
                (GroupHierarchy,
114
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
115
                (DataPermission,
116
                    DataPermission.idgroup == GroupHierarchy.idparent),
117
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
118
                    DataPermission.idusergroup),
119
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
120

    
121
            host_query = host_query.join(
122
                (GroupHierarchy,
123
                    GroupHierarchy.idchild == SUPITEM_GROUP_TABLE.c.idgroup),
124
                (DataPermission,
125
                    DataPermission.idgroup == GroupHierarchy.idparent),
126
                (USER_GROUP_TABLE, USER_GROUP_TABLE.c.idgroup == \
127
                    DataPermission.idusergroup),
128
            ).filter(USER_GROUP_TABLE.c.username == user.user_name)
129

    
130
        # Objet Selectable renvoyant des informations sur un SupItem
131
        # concerné par une alerte, avec prise en compte des droits d'accès.
132
        # On est obligés d'utiliser sqlalchemy.sql.expression.union_all
133
        # pour indiquer à SQLAlchemy de NE PAS regrouper les tables
134
        # dans la requête principale, sans quoi les résultats sont
135
        # incorrects.
136
        # Dans PostgreSQL, UNION ALL est beaucoup plus rapide que UNION
137
        # du fait des performances limitées du DISTINCT.
138
        self.items = union_all(lls_query, host_query, correlate=False).alias()
139

    
140
        # Éléments à retourner (SELECT ...)
141
        self.table = []
142

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

    
146
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
147
        self.outerjoin = []
148

    
149
        # Critères de filtrage (WHERE)
150
        self.filter = []
151
        if mask_closed_events:
152
            self.filter.append(
153
                # On masque les événements avec l'état OK
154
                # et traités (status == u'AAClosed').
155
                not_(and_(
156
                    StateName.statename.in_([u'OK', u'UP']),
157
                    CorrEvent.status == u'AAClosed'
158
                ))
159
            )
160

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

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

    
177
        if asbool(config.get('state_first', True)):
178
            self.orderby.extend([
179
                desc(StateName.order),                      # Etat courant
180
                desc(Event.timestamp),                      # Timestamp
181
            ])
182
        else:
183
            self.orderby.extend([
184
                desc(Event.timestamp),                      # Timestamp
185
                desc(StateName.order),                      # Etat courant
186
            ])
187

    
188

    
189
        # Regroupements (GROUP BY)
190
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
191
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
192
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
193
        self.groupby = [
194
            StateName.order,
195
            Event.timestamp,
196
            CorrEvent.status,
197
            CorrEvent.priority,
198
            StateName.statename,
199
        ]
200

    
201
        self.plugin = []
202
        self.events = []
203
        self.req = DBSession
204

    
205
    def add_plugin(self, *argv):
206
        """
207
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
208
        """
209
        for i in argv:
210
            if isinstance(i, VigiboardRequestPlugin):
211
                if i.table:
212
                    self.add_table(*i.table)
213
                if i.join:
214
                    self.add_join(*i.join)
215
                if i.outerjoin:
216
                    self.add_outer_join(*i.outerjoin)
217
                if i.filter:
218
                    self.add_filter(*i.filter)
219
                if i.groupby:
220
                    self.add_group_by(*i.groupby)
221
                if i.orderby:
222
                    self.add_order_by(*i.orderby)
223
                self.plugin.append(i)
224

    
225
    def generate_request(self):
226
        """
227
        Génération de la requête avec l'ensemble des données stockées
228
        et la place dans la variable rq de la classe
229
        """
230
        if self.generaterq:
231
            return
232

    
233
        for plugin in config['columns_plugins']:
234
            self.add_plugin(plugin)
235

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

    
241
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
242
        # dans la clause GROUP BY. Si une colonne apparaît dans SELECT,
243
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
244
        # Ici, on ajoute automatiquement les colonnes du SELECT au GROUP BY.
245
        self.add_group_by(*self.table)
246

    
247
        # query et join ont besoin de referrence
248
        self.req = self.req.query(*self.table)
249
        self.req = self.req.join(*self.join)
250

    
251
        # le reste, non
252
        for i in self.outerjoin:
253
            self.req = self.req.outerjoin(i)
254
        for i in self.filter:
255
            self.req = self.req.filter(i)
256
        for i in self.groupby:
257
            self.req = self.req.group_by(i)
258
        for i in self.orderby:
259
            self.req = self.req.order_by(i)
260

    
261
        self.generaterq = True
262

    
263
    def num_rows(self):
264
        """
265
        Retourne le nombre de lignes de la requête.
266
        Si celle-ci n'est pas encore générée, on le fait.
267

268
        @return: Nombre de ligne
269
        """
270

    
271
        self.generate_request()
272
        return self.req.count()
273

    
274
    def add_table(self, *argv):
275
        """
276
        Ajoute une ou plusieurs tables/élément d'une table à
277
        la requête.
278

279
        @param argv: Liste des tables à ajouter
280
        """
281

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

    
285
        for i in argv :
286
            for j in self.table:
287
                if str(i) == str(j):
288
                    break
289
            self.table.append(i)
290

    
291
    def add_join(self, *argv):
292
        """
293
        Ajoute une ou plusieurs jointures à
294
        la requête.
295

296
        @param argv: Liste des jointures à ajouter
297
        """
298

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

    
302
        for i in argv:
303
            for j in self.join:
304
                if str(i) == str(j):
305
                    break
306
            self.join.append(i)
307

    
308
    def add_outer_join(self, *argv):
309
        """
310
        Ajoute une ou plusieurs jointures externes à
311
        la requête.
312

313
        @param argv: Liste des jointures externes à ajouter
314
        """
315

    
316
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
317
        # des jointures externes.
318

    
319
        for i in argv:
320
            for j in self.outerjoin:
321
                if str(i) == str(j):
322
                    break
323
            self.outerjoin.append(i)
324

    
325
    def add_filter(self, *argv):
326
        """
327
        Ajoute un ou plusieurs filtres à la requête.
328

329
        @param argv: Liste des filtres à ajouter
330
        """
331

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

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

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

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

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

    
351
        for i in argv:
352
            for j in self.groupby:
353
                try:
354
                    if str(i) == str(j):
355
                        break
356
                # SQLAlchemy lève cette exception pour certains attributes,
357
                # par exemple les attributs définis avec synonym().
358
                except AttributeError:
359
                    pass
360
            self.groupby.append(i)
361

    
362
    def add_order_by(self, *argv):
363
        """
364
        Ajoute un ou plusieurs orders à la requête.
365

366
        @param argv: Liste des ordres à ajouter
367
        """
368

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

    
372
        for i in argv:
373
            for j in self.orderby:
374
                if str(i) == str(j):
375
                    break
376
            self.orderby.append(i)
377

    
378
    def format_events(self, first_row, last_row):
379
        """
380
        Formate la réponse de la requête et y applique les plugins
381
        pour un affichage simple du résultat par Genshi.
382
        On génère une liste de liste, chaqu'une étant la description de
383
        l'affichage pour un événement donné.
384

385
        @param first_row: Indice de début de la liste des événements
386
        @param last_row: Indice de fin de la liste des événements
387
        """
388

    
389
        # Si la requête n'est pas générée, on le fait
390
        self.generate_request()
391

    
392
        # Liste des éléments pour la tête du tableau
393
        self.events = []
394

    
395
        for data in self.req[first_row : last_row]:
396
            self.events.append(data)
397

    
398
    def format_history(self):
399
        """
400
        Formate les historiques correspondant aux événements sélectionnés
401
        pour un affichage simple du résultat par Genshi.
402
        On génère une liste de liste, chaqu'une étant la description
403
        de l'affichage pour un historique donné.
404
        """
405

    
406
        ids = [data[0].idevent for data in self.events]
407
        history = DBSession.query(
408
                    EventHistory,
409
                ).filter(EventHistory.idevent.in_(ids)
410
                ).order_by(desc(EventHistory.timestamp)
411
                ).order_by(desc(EventHistory.idhistory))
412
        return history
413

    
414
    def generate_tmpl_context(self):
415
        """
416
        Génère et peuple la variable tmpl_context avec les Dialogs et
417
        formulaires nécessaire au fonctionnement de Vigiboard
418
        """
419

    
420
        from vigiboard.controllers.root import get_last_modification_timestamp
421

    
422
        # Si les objets manipulés sont des Event, on a facilement les idevent.
423
        if not len(self.events):
424
            ids = []
425
        elif isinstance(self.events[0][0], Event):
426
            ids = [data[0].idevent for data in self.events]
427
        # Sinon, il s'agit de CorrEvent(s) dont on récupère l'idcause.
428
        else:
429
            ids = [data[0].idcause for data in self.events]
430

    
431
        # Ajout des formulaires et préparation
432
        # des données pour ces formulaires.
433
        tmpl_context.last_modification = \
434
            mktime(get_last_modification_timestamp(ids).timetuple())
435

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