AiC and REST login
[myslice.git] / activity / __init__.py
1 #
2 # Activity monitor
3 #
4 # Client is authenticated with an API key and a secret
5 # The API key is a 64 chars string (digits and letters) that is passed to the request
6 # The secret is a 64 chars string that is used to sign the request
7 # The generated signature is a SHA256 hes digest
8
9 import urllib, urllib2
10 import threading
11 import hmac
12 import hashlib
13 import base64
14 import time
15 import datetime
16 from myslice.configengine import ConfigEngine
17 from myslice.settings import logger
18 import random
19 import os
20 import sqlite3 as lite
21 import json
22 import syslog
23
24 config = ConfigEngine()
25 if config.activity and config.activity.apikey :
26     apikey = config.activity.apikey
27 else :
28     # apikey will be necessary
29     apikey = None
30
31 if config.activity and config.activity.secret :
32     secret = config.activity.secret
33 else :
34     # secret will be necessary
35     secret = None
36
37 if config.activity and config.activity.server :
38     server = config.activity.server
39 else :
40     # default log server
41     server = "http://athos.noc.onelab.eu/activity/push/log"
42
43 def logWrite(request, action, message, objects = None):
44     
45     if not apikey :
46         logger.info("===============>> activity: no apikey")
47         return
48     if not secret :
49         logger.info("===============>> activity: no secret")
50         return
51     
52     timestamp = time.mktime(datetime.datetime.today().timetuple())
53     ip = getClientIp(request)
54     log = {
55         "timestamp" : timestamp,
56         "client_ip" : ip,
57         "host"      : request.get_host(),
58         "referrer"  : request.META.get('HTTP_REFERER'),
59         "user"      : request.user.username,
60         "action"    : action,
61         "message"   : message,
62         "apikey"    : apikey,
63         "signature" : sign(secret, "%s%s%s%s" % (timestamp, ip, request.user, action)),
64         "slice"     : None,
65         "resource"  : None,
66         "resource_type"     : None,
67         "facility"      : None,
68         "testbed"       : None,
69     }
70    
71     if objects is not None:
72         for o in objects :
73             if (o in log) :
74                 log[o] = objects[o]
75
76     try :
77         result = urllib2.urlopen(server, urllib.urlencode(log))
78         logger.info("===============>> activity: {} <{}> {}".format(action, request.user,message))
79         content = result.read()
80
81         #checking for not sent data and sending it (50% probability)
82         if random.randint(0,100) < 50:
83             logCheck()
84
85     except urllib2.URLError as e:
86         logger.error("===============>> activity: connection to {} impossible, could not log action".format(server))
87         logger.error(e.strerror)
88
89         dbfile = ''.join([os.path.dirname(os.path.abspath(__file__)), "/errors.db"])
90         logger.error("===============>> activity: database path: " + dbfile)
91         conn = None
92         try:
93             conn = lite.connect(dbfile)
94             cur = conn.cursor()
95             cur.execute("""INSERT INTO logs(log) values('%s')""" % json.dumps(log))
96             conn.commit()
97         except lite.Error, e:
98             # this means that writing log into db also failed :(
99             # Last chance to preserve log is to send it to system syslog
100             # however there is no mechanism to pull it from this log - just manually.
101             logger.error('[activity] Error while inserting into sql db: %s' % str(e.args))
102             logger.error("[activity] data to send: '%s'" % json.dumps(log))
103         if conn:
104             conn.close()
105
106 def log(request, action, message, objects = None):
107     # Create a new thread in Daemon mode to send the log entry
108     t = threading.Thread(target=logWrite, args=(request, action, message, objects))
109     t.setDaemon(True)
110     t.start()
111
112 def getClientIp(request):
113     try :
114         x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
115         ip = x_forwarded_for.split(',')[0]
116     except:
117         ip = request.META.get('REMOTE_ADDR')
118     return ip
119
120 #
121 # sign the request with the secret key
122 def sign(secret, message):
123     return hmac.new(secret, msg=message, digestmod=hashlib.sha256).hexdigest()
124
125 #
126 # sending the logs cached in sqlite database
127
128 def logCheck():
129     """Checking local database for logs adn sending it to monitoring server"""
130     dbfile = ''.join([os.path.dirname(os.path.abspath(__file__)), "/errors.db"])
131     logger.error("[activity] database path: " + dbfile)
132     conn = None
133
134     #trying to connect local db adn pull unsent logs
135     try:
136         conn = lite.connect(dbfile)
137         cur = conn.cursor()
138         cur.execute("SELECT rowid, log from logs")
139         notsent = cur.fetchall()
140         for row in notsent:
141             #trying to send unsent data from sqlite db
142             try :
143                 urllib2.urlopen(config.activity.server, urllib.urlencode(json.loads(row[1])))
144                 #delete those who were sent
145                 cur.execute("""DELETE FROM logs where rowid = %s""" % row[0] )
146                 conn.commit()
147             except urllib2.URLError as e:
148                 # this is just to inform that DB is not working properly
149                 logger.error('[activity] Error while sending stats')
150                 logger.error(e.strerror)
151
152     except lite.Error, e:
153         #this need to be updated to store information via syslog
154         logger.error('[activity] Error while pulling from local sqlite3 db: %s' % str(e.args))
155         logger.error(e.strerror)
156     if conn:
157         conn.close()
158
159     return