Project

General

Profile

Statistics
| Branch: | Tag: | Revision:

vigiboard / vigiboard / controllers / vigiboardrequest.py @ b2346a00

History | View | Annotate | Download (14.2 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 paste.deploy.converters import asbool
12

    
13
from sqlalchemy import not_, and_, asc, desc
14
from sqlalchemy.sql.expression import or_, null as expr_null, union
15

    
16
from vigilo.models.session import DBSession
17
from vigilo.models.tables import Event, CorrEvent, EventHistory, \
18
                        Host, LowLevelService, StateName
19
from vigilo.models.tables.secondary_tables import SUPITEM_GROUP_TABLE
20
from vigiboard.widgets.edit_event import create_edit_event_form, EditEventForm
21
from vigiboard.widgets.search_form import create_search_form, SearchForm
22
from vigiboard.controllers.plugins import VigiboardRequestPlugin
23

    
24
LOGGER = getLogger(__name__)
25

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

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

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

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

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

    
60
        self.user_groups = user.supitemgroups
61
        self.lang = lang
62
        self.generaterq = False
63

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

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

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

    
108
        # Éléments à retourner (SELECT ...)
109
        self.table = []
110

    
111
        # Tables sur lesquelles porte la récupération (JOIN)
112
        self.join = []
113

    
114
        # Tables sur lesquelles porte la récupération (OUTER JOIN)
115
        self.outerjoin = []
116

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

    
129
        # Permet de définir le sens de tri pour la priorité.
130
        if config['vigiboard_priority_order'] == 'asc':
131
            priority_order = asc(CorrEvent.priority)
132
        else:
133
            priority_order = desc(CorrEvent.priority)
134

    
135
        # Tris (ORDER BY)
136
        # Permet de répondre aux exigences suivantes :
137
        # - VIGILO_EXIG_VIGILO_BAC_0050
138
        # - VIGILO_EXIG_VIGILO_BAC_0060
139
        self.orderby = [
140
            desc(CorrEvent.status),                         # État acquittement
141
            asc(StateName.statename.in_([u'OK', u'UP'])),   # Vert / Pas vert
142
            priority_order,                                 # Priorité ITIL
143
        ]
144

    
145
        if asbool(config.get('state_first', True)):
146
            self.orderby.extend([
147
                desc(StateName.order),                      # Etat courant
148
                desc(Event.timestamp),                      # Timestamp
149
            ])
150
        else:
151
            self.orderby.extend([
152
                desc(Event.timestamp),                      # Timestamp
153
                desc(StateName.order),                      # Etat courant
154
            ])
155

    
156

    
157
        # Regroupements (GROUP BY)
158
        # PostgreSQL est pointilleux sur les colonnes qui apparaissent
159
        # dans la clause GROUP BY. Si une colonne apparaît dans ORDER BY,
160
        # elle doit systématiquement apparaître AUSSI dans GROUP BY.
161
        self.groupby = [
162
            StateName.order,
163
            Event.timestamp,
164
            CorrEvent.status,
165
            CorrEvent.priority,
166
            StateName.statename,
167
        ]
168

    
169
        self.plugin = []
170
        self.events = []
171
        self.req = DBSession
172

    
173
    def add_plugin(self, *argv):
174
        """
175
        Ajout d'un plugin, on lui prélève ses ajouts dans la requête
176
        """
177
        for i in argv:
178
            if isinstance(i, VigiboardRequestPlugin):
179
                if i.table:
180
                    self.add_table(*i.table)
181
                if i.join:
182
                    self.add_join(*i.join)
183
                if i.outerjoin:
184
                    self.add_outer_join(*i.outerjoin)
185
                if i.filter:
186
                    self.add_filter(*i.filter)
187
                if i.groupby:    
188
                    self.add_group_by(*i.groupby)
189
                if i.orderby:
190
                    self.add_order_by(*i.orderby)
191
                self.plugin.append(i)
192

    
193
    def generate_request(self):
194
        """
195
        Génération de la requête avec l'ensemble des données stockées
196
        et la place dans la variable rq de la classe
197
        """
198
        if self.generaterq:
199
            return
200

    
201
        for plug in config.get('vigiboard_plugins', []):
202
            try:
203
                mypac = __import__(
204
                    'vigiboard.controllers.plugins.' +\
205
                            plug[0], globals(), locals(), [plug[1]], -1)
206
                self.add_plugin(getattr(mypac, plug[1])())
207
            except ImportError:
208
                # On loggue l'erreur et on ignore le plugin.
209
                LOGGER.error(_('No such plugin "%s"') % plug[0])
210

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

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

    
222
        # query et join ont besoin de referrence
223
        self.req = self.req.query(*self.table)
224
        self.req = self.req.join(*self.join)
225

    
226
        # le reste, non
227
        for i in self.outerjoin:
228
            self.req = self.req.outerjoin(i)
229
        for i in self.filter:
230
            self.req = self.req.filter(i)
231
        for i in self.groupby:
232
            self.req = self.req.group_by(i)
233
        for i in self.orderby:
234
            self.req = self.req.order_by(i)
235

    
236
        self.generaterq = True
237

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

243
        @return: Nombre de ligne
244
        """
245

    
246
        self.generate_request()
247
        return self.req.count()
248

    
249
    def add_table(self, *argv):
250
        """
251
        Ajoute une ou plusieurs tables/élément d'une table à
252
        la requête.
253

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

    
266
    def add_join(self, *argv):
267
        """
268
        Ajoute une ou plusieurs jointures à
269
        la requête.
270

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

    
283
    def add_outer_join(self, *argv):
284
        """
285
        Ajoute une ou plusieurs jointures externes à
286
        la requête.
287

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

    
300
    def add_filter(self, *argv):
301
        """
302
        Ajoute un ou plusieurs filtres à la requête.
303

304
        @param argv: Liste des filtres à ajouter
305
        """
306
        
307
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
308
        # des filtres.
309
        
310
        for i in argv:
311
            for j in self.filter:
312
                if str(i) == str(j):
313
                    break
314
            self.filter.append(i)
315

    
316
    def add_group_by(self, *argv):
317
        """
318
        Ajoute un ou plusieurs groupements à la requête.
319

320
        @param argv: Liste des groupements à ajouter
321
        """
322
        
323
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
324
        # des groupements.
325
        
326
        for i in argv:
327
            for j in self.groupby:
328
                try:
329
                    if str(i) == str(j):
330
                        break
331
                # SQLAlchemy lève cette exception pour certains attributes,
332
                # par exemple les attributs définis avec synonym().
333
                except AttributeError:
334
                    pass
335
            self.groupby.append(i)
336

    
337
    def add_order_by(self, *argv):
338
        """
339
        Ajoute un ou plusieurs orders à la requête.
340

341
        @param argv: Liste des ordres à ajouter
342
        """
343
        
344
        # On vérifie qu'il n'y a pas de doublons dans la liste finale
345
        # des ordres.
346
        
347
        for i in argv:
348
            for j in self.orderby:
349
                if str(i) == str(j):
350
                    break
351
            self.orderby.append(i)
352

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

360
        @param first_row: Indice de début de la liste des événements
361
        @param last_row: Indice de fin de la liste des événements
362
        """
363
        
364
        # Si la requête n'est pas générée, on le fait
365
        self.generate_request()
366

    
367
        # Liste des éléments pour la tête du tableau
368
        self.events = []
369

    
370
        for data in self.req[first_row : last_row]:
371
            self.events.append(data)
372

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

    
381
        ids = [data[0].idevent for data in self.events]
382
        history = DBSession.query(
383
                    EventHistory,
384
                ).filter(EventHistory.idevent.in_(ids)
385
                ).order_by(desc(EventHistory.timestamp)
386
                ).order_by(desc(EventHistory.idhistory))
387
        return history
388

    
389
    def generate_tmpl_context(self):
390
        """
391
        Génère et peuple la variable tmpl_context avec les Dialogs et
392
        formulaires nécessaire au fonctionnement de Vigiboard
393
        """
394

    
395
        from vigiboard.controllers.root import get_last_modification_timestamp
396

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

    
406
        # Ajout des formulaires et préparation
407
        # des données pour ces formulaires.
408
        tmpl_context.last_modification = \
409
            mktime(get_last_modification_timestamp(ids).timetuple())
410

    
411
        tmpl_context.calendar_lang = self.lang
412
        # TRANSLATORS: Format de date et heure compatible Python/JavaScript.
413
        tmpl_context.calendar_date_format = _('%Y-%m-%d %I:%M:%S %p')
414

    
415
        tmpl_context.edit_event_form = create_edit_event_form
416
        tmpl_context.search_form = create_search_form
417