Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ b00c0ea7

History | View | Annotate | Download (13.7 KB)

1
# -*- coding: utf-8 -*-
2
# vim:set expandtab tabstop=4 shiftwidth=4: 
3
"""Gestion de la requête, des plugins et de l'affichage du Vigiboard"""
4

    
5
from time import mktime
6
from logging import getLogger
7

    
8
from tg import url, config, tmpl_context
9
from tg.i18n import get_lang
10
from pylons.i18n import ugettext as _
11
from sqlalchemy import not_, and_, asc, desc
12
from sqlalchemy.sql.expression import or_, null as expr_null, union
13

    
14
from vigilo.models.configure import DBSession
15
from vigilo.models import Event, CorrEvent, EventHistory, \
16
                        Host, LowLevelService, StateName
17
from vigilo.models.secondary_tables import HOST_GROUP_TABLE, \
18
                                            SERVICE_GROUP_TABLE
19
from vigiboard.widgets.edit_event import EditEventForm
20
from vigiboard.widgets.search_form import SearchForm
21
from vigiboard.controllers.plugins import VigiboardRequestPlugin
22

    
23
LOGGER = getLogger(__name__)
24

    
25
class VigiboardRequest():
26
    """
27
    Classe gérant la génération de la requête finale,
28
    le préformatage des événements et celui des historiques
29
    """
30

    
31
    class_ack = {
32
        'None': '',
33
        'Acknowledged': '_Ack',
34
        'AAClosed': '_Ack',
35
    }
36

    
37
    def __init__(self, user, mask_closed_events=True):
38
        """
39
        Initialisation de l'objet qui effectue les requêtes de VigiBoard
40
        sur la base de données.
41
        Cet objet est responsable de la vérification des droits de
42
        l'utilisateur sur les données manipulées.
43
        """
44

    
45
        # TODO: Utiliser le champ "language" du modèle pour cet utilisateur ?
46
        # On récupère la langue du navigateur de l'utilisateur
47
        lang = get_lang()
48
        if not lang:
49
            lang = config['lang']
50
        else:
51
            lang = lang[0]
52

    
53
        # TODO: Il faudrait gérer les cas où tout nous intéresse dans "lang".
54
        # Si l'identifiant de langage est composé (ex: "fr_FR"),
55
        # on ne récupère que la 1ère partie.
56
        lang = lang.replace('_', '-')
57
        lang = lang.split('-')[0]
58

    
59
        self.user_groups = user.groups
60
        self.lang = lang
61
        self.generaterq = False
62
        
63
        lls_query = DBSession.query(
64
            LowLevelService.idservice.label("idsupitem"),
65
            LowLevelService.servicename.label("servicename"),
66
            Host.name.label("hostname"),
67
            SERVICE_GROUP_TABLE.c.idgroup.label("idservicegroup"),
68
            HOST_GROUP_TABLE.c.idgroup.label("idhostgroup"),
69
        ).join(
70
           (Host, Host.idhost == LowLevelService.idhost),
71
        ).outerjoin(
72
            (HOST_GROUP_TABLE, 
73
                HOST_GROUP_TABLE.c.idhost == LowLevelService.idhost),
74
            (SERVICE_GROUP_TABLE, 
75
                SERVICE_GROUP_TABLE.c.idservice == LowLevelService.idservice),
76
        ).filter(
77
            or_(
78
                HOST_GROUP_TABLE.c.idgroup.in_(self.user_groups),
79
                SERVICE_GROUP_TABLE.c.idgroup.in_(self.user_groups),
80
            ),
81
        )
82
               
83
        host_query = DBSession.query(
84
            Host.idhost.label("idsupitem"),
85
            expr_null().label("servicename"),
86
            Host.name.label("hostname"),
87
            expr_null().label("idservicegroup"),
88
            HOST_GROUP_TABLE.c.idgroup.label('idhostgroup'),
89
        ).join((HOST_GROUP_TABLE, HOST_GROUP_TABLE.c.idhost == Host.idhost)
90
        ).filter(HOST_GROUP_TABLE.c.idgroup.label('idhostgroup'
91
            ).in_(self.user_groups),
92
        )
93

    
94
        # Object Selectable renvoyant des informations sur un supitem
95
        # concerné par une alerte, avec prise en compte des droits d'accès.
96
        # On est obligés d'utiliser sqlalchemy.sql.expression.union
97
        # pour indiquer à SQLAlchemy de NE PAS regrouper les tables
98
        # dans la requête principale, sans quoi les résultats sont
99
        # incorrects.
100
        self.items = union(lls_query, host_query, correlate=False).alias()
101

    
102
        # Éléments à retourner (SELECT ...)
103
        self.table = []
104

    
105
        # Tables sur lesquelles porte la récupération (JOIN)
106
        self.join = []
107

    
108
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
109
        self.outerjoin = []
110

    
111
        # Critères de filtrage (WHERE)
112
        self.filter = []
113
        if mask_closed_events:
114
            self.filter.append(
115
                # On masque les événements avec l'état OK
116
                # et traités (status == u'AAClosed').
117
                not_(and_(
118
                    StateName.statename.in_([u'OK', u'UP']),
119
                    CorrEvent.status == u'AAClosed'
120
                ))
121
            )
122

    
123
        # Permet de définir le sens de tri pour la priorité.
124
        if config['vigiboard_priority_order'] == 'asc':
125
            priority_order = asc(CorrEvent.priority)
126
        else:
127
            priority_order = desc(CorrEvent.priority)
128

    
129
        # Tris (ORDER BY)
130
        self.orderby = [
131
            desc(CorrEvent.status),                         # État acquittement
132
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
133
            priority_order,                                 # Priorité ITIL
134
            desc(StateName.order),                          # Etat courant
135
            desc(Event.timestamp),                          # Timestamp
136
        ]
137

    
138
        # Regroupements (GROUP BY)
139
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
140
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
141
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
142
        self.groupby = [
143
            StateName.order,
144
            Event.timestamp,
145
            CorrEvent.status,
146
            CorrEvent.priority,
147
            StateName.statename,
148
        ]
149

    
150
        self.plugin = []
151
        self.events = []
152
        self.req = DBSession
153

    
154
    def add_plugin(self, *argv):
155
        """
156
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
157
        """
158
        for i in argv:
159
            if isinstance(i, VigiboardRequestPlugin):
160
                if i.table:
161
                    self.add_table(*i.table)
162
                if i.join:
163
                    self.add_join(*i.join)
164
                if i.outerjoin:
165
                    self.add_outer_join(*i.outerjoin)
166
                if i.filter:
167
                    self.add_filter(*i.filter)
168
                if i.groupby:    
169
                    self.add_group_by(*i.groupby)
170
                if i.orderby:
171
                    self.add_order_by(*i.orderby)
172
                self.plugin.append(i)
173

    
174
    def generate_request(self):
175
        """
176
        Génération de la requête avec l'ensemble des données stockées
177
        et la place dans la variable rq de la classe
178
        """
179
        if self.generaterq:
180
            return
181

    
182
        for plug in config.get('vigiboard_plugins', []):
183
            try:
184
                mypac = __import__(
185
                    'vigiboard.controllers.plugins.' +\
186
                            plug[0], globals(), locals(), [plug[1]], -1)
187
                self.add_plugin(getattr(mypac, plug[1])())
188
            except ImportError:
189
                # On loggue l'erreur et on ignore le plugin.
190
                LOGGER.error(_('No such plugin "%s"') % plug[0])
191

    
192
        # Toutes les requêtes ont besoin de récupérer l'état courant
193
        # de l'événement.
194
        self.join.append((StateName, StateName.idstatename == \
195
                                        Event.current_state))
196

    
197
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
198
        # dans la clause GROUP BY. Si une colonne apparaît dans SELECT,
199
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
200
        # Ici, on ajoute automatiquement les colonnes du SELECT au GROUP BY.
201
        self.add_group_by(*self.table)
202

    
203
        # query et join ont besoin de referrence
204
        self.req = self.req.query(*self.table)
205
        self.req = self.req.join(*self.join)
206

    
207
        # le reste, non
208
        for i in self.outerjoin:
209
            self.req = self.req.outerjoin(i)
210
        for i in self.filter:
211
            self.req = self.req.filter(i)
212
        for i in self.groupby:
213
            self.req = self.req.group_by(i)
214
        for i in self.orderby:
215
            self.req = self.req.order_by(i)
216

    
217
        self.generaterq = True
218

    
219
    def num_rows(self):
220
        """
221
        Retourne le nombre de lignes de la requête.
222
        Si celle-ci n'est pas encore générée, on le fait.
223

224
        @return: Nombre de ligne
225
        """
226

    
227
        self.generate_request()
228
        return self.req.count()
229

    
230
    def add_table(self, *argv):
231
        """
232
        Ajoute une ou plusieurs tables/élément d'une table à
233
        la requête.
234

235
        @param argv: Liste des tables à ajouter
236
        """
237
        
238
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
239
        # des tables.
240
        
241
        for i in argv :
242
            for j in self.table:
243
                if str(i) == str(j):
244
                    break
245
            self.table.append(i)
246

    
247
    def add_join(self, *argv):
248
        """
249
        Ajoute une ou plusieurs jointures à
250
        la requête.
251

252
        @param argv: Liste des jointures à ajouter
253
        """
254
        
255
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
256
        # des jointures.
257
        
258
        for i in argv:
259
            for j in self.join:
260
                if str(i) == str(j):
261
                    break
262
            self.join.append(i)
263

    
264
    def add_outer_join(self, *argv):
265
        """
266
        Ajoute une ou plusieurs jointures externes à
267
        la requête.
268

269
        @param argv: Liste des jointures externes à ajouter
270
        """
271
        
272
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
273
        # des jointures externes.
274
        
275
        for i in argv:
276
            for j in self.outerjoin:
277
                if str(i) == str(j):
278
                    break
279
            self.outerjoin.append(i)    
280

    
281
    def add_filter(self, *argv):
282
        """
283
        Ajoute un ou plusieurs filtres à la requête.
284

285
        @param argv: Liste des filtres à ajouter
286
        """
287
        
288
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
289
        # des filtres.
290
        
291
        for i in argv:
292
            for j in self.filter:
293
                if str(i) == str(j):
294
                    break
295
            self.filter.append(i)
296

    
297
    def add_group_by(self, *argv):
298
        """
299
        Ajoute un ou plusieurs groupements à la requête.
300

301
        @param argv: Liste des groupements à ajouter
302
        """
303
        
304
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
305
        # des groupements.
306
        
307
        for i in argv:
308
            for j in self.groupby:
309
                try:
310
                    if str(i) == str(j):
311
                        break
312
                # SQLAlchemy lève cette exception pour certains attributes,
313
                # par exemple les attributs définis avec synonym().
314
                except AttributeError:
315
                    pass
316
            self.groupby.append(i)
317

    
318
    def add_order_by(self, *argv):
319
        """
320
        Ajoute un ou plusieurs orders à la requête.
321

322
        @param argv: Liste des ordres à ajouter
323
        """
324
        
325
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
326
        # des ordres.
327
        
328
        for i in argv:
329
            for j in self.orderby:
330
                if str(i) == str(j):
331
                    break
332
            self.orderby.append(i)
333

    
334
    def format_events(self, first_row, last_row):
335
        """
336
        Formate la réponse de la requête et y applique les plugins
337
        pour un affichage simple du résultat par Genshi.
338
        On génère une liste de liste, chaqu'une étant la description de
339
        l'affichage pour un événement donné.
340

341
        @param first_row: Indice de début de la liste des événements
342
        @param last_row: Indice de fin de la liste des événements
343
        """
344
        
345
        # Si la requête n'est pas générée, on le fait
346
        self.generate_request()
347

    
348
        # Liste des éléments pour la tête du tableau
349
        self.events = []
350

    
351
        for data in self.req[first_row : last_row]:
352
            self.events.append(data)
353

    
354
    def format_history(self):
355
        """
356
        Formate les historiques correspondant aux événements sélectionnés
357
        pour un affichage simple du résultat par Genshi.
358
        On génère une liste de liste, chaqu'une étant la description
359
        de l'affichage pour un historique donné.
360
        """
361

    
362
        ids = [data[0].idevent for data in self.events]
363
        history = DBSession.query(
364
                    EventHistory,
365
                ).filter(EventHistory.idevent.in_(ids)
366
                ).order_by(desc(EventHistory.timestamp)
367
                ).order_by(desc(EventHistory.idhistory))
368
        return history
369

    
370
    def generate_tmpl_context(self):
371
        """
372
        Génère et peuple la variable tmpl_context avec les Dialogs et
373
        formulaires nécessaire au fonctionnement de Vigiboard
374
        """
375

    
376
        from vigiboard.controllers.root import get_last_modification_timestamp
377

    
378
        # Si les objets manipulés sont des Event, on a facilement les idevent.
379
        if not len(self.events):
380
            ids = []
381
        elif isinstance(self.events[0][0], Event):
382
            ids = [data[0].idevent for data in self.events]
383
        # Sinon, il s'agit de CorrEvent(s) dont on récupère l'idcause.
384
        else:
385
            ids = [data[0].idcause for data in self.events]
386
        
387
        # Dialogue d'édition
388
        tmpl_context.edit_event_form = EditEventForm('edit_event_form',
389
            last_modification=mktime(get_last_modification_timestamp(
390
                ids).timetuple()),
391
            action=url('/update'), 
392
        )
393

    
394
        # Dialogue de recherche
395
        tmpl_context.search_form = SearchForm('search_form', lang=self.lang,
396
                                        # TRANSLATORS: Format de date et heure.
397
                                        date_format=_('%Y-%m-%d %I:%M:%S %p'))
398