Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(610)

Side by Side Diff: appengine/sheriff_o_matic/ts_alerts.py

Issue 1260293009: make version of ts_mon compatible with appengine (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: set correct metric and target fields Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import alerts_history 5 import alerts_history
6 import datetime_encoder
7 import hashlib
8 import json 6 import json
9 import logging 7 import logging
8 import utils
10 import webapp2 9 import webapp2
11 import zlib 10 import zlib
12 11
13 from datetime import datetime as dt 12 from datetime import datetime as dt
14 from google.appengine.api import memcache 13 from google.appengine.api import memcache
15 from google.appengine.api import users 14 from google.appengine.api import users
16 from google.appengine.datastore import datastore_query
17 from google.appengine.ext import ndb 15 from google.appengine.ext import ndb
18 16
19 17
20 ALLOWED_APP_IDS = ('google.com:monarch-email-alerts-parser') 18 ALLOWED_APP_IDS = ('google.com:monarch-email-alerts-parser')
21 INBOUND_APP_ID = 'X-Appengine-Inbound-Appid' 19 INBOUND_APP_ID = 'X-Appengine-Inbound-Appid'
22 20
23 21
24 def is_googler():
25 user = users.get_current_user()
26 if user:
27 email = user.email()
28 return email.endswith('@google.com') and '+' not in email
29 return False
30
31
32 def convert_to_secs(duration_str):
33 duration_str = duration_str.strip()
34 if duration_str[-1] == 's':
35 return int(duration_str[:-1])
36 elif duration_str[-1] == 'm':
37 return 60 * int(duration_str[:-1])
38 elif duration_str[-1] == 'h':
39 return 3600 * int(duration_str[:-1])
40 elif duration_str[-1] == 'd':
41 return 24 * 3600 * int(duration_str[:-1])
42 elif duration_str[-1] == 'w':
43 return 7 * 24 * 3600 * int(duration_str[:-1])
44 else:
45 raise Exception('Invalid duration_str ' + duration_str[-1])
46
47
48 def secs_ago(time_string, time_now=None):
49 try:
50 time_sent = dt.strptime(time_string, '%Y-%m-%d %H:%M:%S %Z')
51 except ValueError:
52 time_sent = dt.strptime(time_string, '%Y-%m-%d %H:%M:%S')
53 time_now = time_now or int(dt.utcnow().strftime('%s'))
54 latency = int(time_now) - int(time_sent.strftime('%s'))
55 return latency
56
57
58 def hash_string(input_str):
59 return hashlib.sha1(input_str).hexdigest()
60
61
62 def generate_json_dump(alerts, human_readable=True):
63 if human_readable:
64 return json.dumps(alerts, cls=datetime_encoder.DateTimeEncoder,
65 indent=2,
66 separators=(',', ': '))
67 return json.dumps(alerts, cls=datetime_encoder.DateTimeEncoder,
68 indent=None,
69 separators=(',', ':'))
70
71
72 class TSAlertsJSON(ndb.Model): 22 class TSAlertsJSON(ndb.Model):
73 active_until = ndb.DateTimeProperty() 23 active_until = ndb.DateTimeProperty()
74 json = ndb.JsonProperty(compressed=True) 24 json = ndb.JsonProperty(compressed=True)
75 25
76 @classmethod 26 @classmethod
77 def query_active(cls): 27 def query_active(cls):
78 return cls.query().filter(TSAlertsJSON.active_until == None) 28 return cls.query().filter(TSAlertsJSON.active_until == None)
79 29
80 @classmethod 30 @classmethod
81 def query_hash(cls, key): 31 def query_hash(cls, key):
82 return cls.get_by_id(key) 32 return cls.get_by_id(key)
83 33
84 34
85 class TimeSeriesAlertsHandler(webapp2.RequestHandler): 35 class TimeSeriesAlertsHandler(webapp2.RequestHandler):
86 ALERT_TYPE = 'ts-alerts' 36 ALERT_TYPE = 'ts-alerts'
87 MEMCACHE_COMPRESSION_LEVEL = 9 37 MEMCACHE_COMPRESSION_LEVEL = 9
88 # Alerts which have continued to fire are re-sent every 5 minutes, so stale 38 # Alerts which have continued to fire are re-sent every 5 minutes, so stale
89 # alerts older than 300 seconds are replaced by incoming alerts. 39 # alerts older than 300 seconds are replaced by incoming alerts.
90 STALE_ALERT_TIMEOUT = 300 40 STALE_ALERT_TIMEOUT = 300
91 41
92 def get(self, key=None): 42 def get(self, key=None):
43 utils.increment_monarch('ts-alerts')
93 self.remove_expired_alerts() 44 self.remove_expired_alerts()
94 if not users.get_current_user(): 45 if not users.get_current_user():
95 results = {'date': dt.utcnow(), 46 results = {'date': dt.utcnow(),
96 'redirect-url': users.create_login_url(self.request.uri)} 47 'redirect-url': users.create_login_url(self.request.uri)}
97 self.write_json(results) 48 self.write_json(results)
98 return 49 return
99 50
100 if key: 51 if key:
101 logging.info('getting the key: ' + key) 52 logging.info('getting the key: ' + key)
102 try: 53 try:
103 data = memcache.get(key) or TSAlertsJSON.query_hash(key).json 54 data = memcache.get(key) or TSAlertsJSON.query_hash(key).json
104 except AttributeError: 55 except AttributeError:
105 self.response.write('This alert does not exist.') 56 self.response.write('This alert does not exist.')
106 self.response.set_status(404, 'Alert does not exist') 57 self.response.set_status(404, 'Alert does not exist')
107 return 58 return
108 if not data: 59 if not data:
109 self.response.write('This alert does not exist.') 60 self.response.write('This alert does not exist.')
110 self.response.set_status(404, 'Alert does not exist') 61 self.response.set_status(404, 'Alert does not exist')
111 elif data.get('private', True) and not is_googler(): 62 elif data.get('private', True) and not utils.is_googler():
112 logging.info('Permission denied.') 63 logging.info('Permission denied.')
113 self.abort(403) 64 self.abort(403)
114 else: 65 else:
115 self.write_json(data.get(json, data)) 66 self.write_json(data.get(json, data))
116 else: 67 else:
117 query = TSAlertsJSON.query_active().fetch() 68 query = TSAlertsJSON.query_active().fetch()
118 data = [] 69 data = []
119 for item in query: 70 for item in query:
120 if item.json.get('private', True) and not is_googler(): 71 if item.json.get('private', True) and not utils.is_googler():
121 continue 72 continue
122 data.append(item.json) 73 data.append(item.json)
123 self.write_json({'alerts': data}) 74 self.write_json({'alerts': data})
124 75
125 def post(self): 76 def post(self):
126 app_id = self.request.headers.get(INBOUND_APP_ID, None) 77 app_id = self.request.headers.get(INBOUND_APP_ID, None)
127 if app_id not in ALLOWED_APP_IDS: 78 if app_id not in ALLOWED_APP_IDS:
128 logging.info('Permission denied') 79 logging.info('Permission denied')
129 self.abort(403) 80 self.abort(403)
130 return 81 return
131 self.update_alerts() 82 self.update_alerts()
132 83
133 def put(self, key): 84 def put(self, key):
134 if not is_googler(): 85 if not utils.is_googler():
135 self.response.set_status(403, 'Permission Denied') 86 self.response.set_status(403, 'Permission Denied')
136 return 87 return
137 changed_alert = TSAlertsJSON.query_hash(key) 88 changed_alert = TSAlertsJSON.query_hash(key)
138 if not changed_alert: 89 if not changed_alert:
139 self.response.write('This alert does not exist.') 90 self.response.write('This alert does not exist.')
140 self.response.set_status(404, 'Alert does not exist') 91 self.response.set_status(404, 'Alert does not exist')
141 return 92 return
142 try: 93 try:
143 data = json.loads(self.request.body) 94 data = json.loads(self.request.body)
144 except ValueError: 95 except ValueError:
145 warning = ('Content %s was not valid JSON string.', self.request.body) 96 warning = ('Content %s was not valid JSON string.', self.request.body)
146 self.response.set_status(400, warning) 97 self.response.set_status(400, warning)
147 return 98 return
148 logging.info('Alert before: ' + str(changed_alert)) 99 logging.info('Alert before: ' + str(changed_alert))
149 logging.info('Data: ' + str(data)) 100 logging.info('Data: ' + str(data))
150 changed_alert.json.update(data) 101 changed_alert.json.update(data)
151 logging.info('Alert after: ' + str(changed_alert)) 102 logging.info('Alert after: ' + str(changed_alert))
152 changed_alert.put() 103 changed_alert.put()
153 memcache.set(key, changed_alert.json) 104 memcache.set(key, changed_alert.json)
154 self.response.write("Updated ts-alerts.") 105 self.response.write("Updated ts-alerts.")
155 106
156 def delete(self, key): 107 def delete(self, key):
157 if not is_googler(): 108 if not utils.is_googler():
158 self.response.set_status(403, 'Permission Denied') 109 self.response.set_status(403, 'Permission Denied')
159 return 110 return
160 if key == 'all': 111 if key == 'all':
161 all_keys = TSAlertsJSON.query().fetch(keys_only=True) 112 all_keys = TSAlertsJSON.query().fetch(keys_only=True)
162 ndb.delete_multi(all_keys) 113 ndb.delete_multi(all_keys)
163 for k in all_keys: 114 for k in all_keys:
164 logging.info('deleting key from memcache: ' + k.id()) 115 logging.info('deleting key from memcache: ' + k.id())
165 memcache.delete(k.id()) 116 memcache.delete(k.id())
166 self.response.set_status(200, 'Cleared all alerts') 117 self.response.set_status(200, 'Cleared all alerts')
167 return 118 return
168 changed_alert = TSAlertsJSON.query_hash(key) 119 changed_alert = TSAlertsJSON.query_hash(key)
169 if not changed_alert: 120 if not changed_alert:
170 self.response.write('This alert does not exist.') 121 self.response.write('This alert does not exist.')
171 self.response.set_status(404, 'Alert does not exist') 122 self.response.set_status(404, 'Alert does not exist')
172 return 123 return
173 memcache.delete(key) 124 memcache.delete(key)
174 changed_alert.key.delete() 125 changed_alert.key.delete()
175 126
176 def write_json(self, data): 127 def write_json(self, data):
177 self.response.headers['Access-Control-Allow-Origin'] = '*' 128 self.response.headers['Access-Control-Allow-Origin'] = '*'
178 self.response.headers['Content-Type'] = 'application/json' 129 self.response.headers['Content-Type'] = 'application/json'
179 data = generate_json_dump(data) 130 data = utils.generate_json_dump(data)
180 self.response.write(data) 131 self.response.write(data)
181 132
182 def remove_expired_alerts(self): 133 def remove_expired_alerts(self):
183 active_alerts = TSAlertsJSON.query_active().fetch() 134 active_alerts = TSAlertsJSON.query_active().fetch()
184 135
185 for alert in active_alerts: 136 for alert in active_alerts:
186 alert_age = secs_ago(alert.json['alert_sent_utc']) 137 alert_age = utils.secs_ago(alert.json['alert_sent_utc'])
187 if alert_age > self.STALE_ALERT_TIMEOUT: 138 if alert_age > self.STALE_ALERT_TIMEOUT:
188 logging.info('%s expired. alert age: %d.', alert.key.id(), alert_age) 139 logging.info('%s expired. alert age: %d.', alert.key.id(), alert_age)
189 alert.active_until = dt.utcnow() 140 alert.active_until = dt.utcnow()
190 alert.json['active_until'] = dt.strftime(alert.active_until, '%s') 141 alert.json['active_until'] = dt.strftime(alert.active_until, '%s')
191 alert.put() 142 alert.put()
192 memcache.set(alert.key.id(), alert.json) 143 memcache.set(alert.key.id(), alert.json)
193 144
194 def update_alerts(self): 145 def update_alerts(self):
195 self.remove_expired_alerts() 146 self.remove_expired_alerts()
196 try: 147 try:
197 alerts = json.loads(self.request.body) 148 alerts = json.loads(self.request.body)
198 except ValueError: 149 except ValueError:
199 warning = 'Content field was not valid JSON string.' 150 warning = 'Content field was not valid JSON string.'
200 self.response.set_status(400, warning) 151 self.response.set_status(400, warning)
201 logging.warning(warning) 152 logging.warning(warning)
202 return 153 return
203 if alerts: 154 if alerts:
204 self.store_alerts(alerts) 155 self.store_alerts(alerts)
205 156
206 def store_alerts(self, alert): 157 def store_alerts(self, alert):
207 hash_key = hash_string(alert['mash_expression'] + alert['active_since']) 158 pre_hash_string = alert['mash_expression'] + alert['active_since']
159 hash_key = utils.hash_string(pre_hash_string)
208 alert['hash_key'] = hash_key 160 alert['hash_key'] = hash_key
209 161
210 new_entry = TSAlertsJSON( 162 new_entry = TSAlertsJSON(
211 id=hash_key, 163 id=hash_key,
212 json=alert, 164 json=alert,
213 active_until=None) 165 active_until=None)
214 new_entry.put() 166 new_entry.put()
215 memcache.set(hash_key, alert) 167 memcache.set(hash_key, alert)
216 168
217 def set_memcache(self, key, data): 169 def set_memcache(self, key, data):
218 json_data = generate_json_dump(data, False) 170 json_data = utils.generate_json_dump(data, False)
219 compression_level = self.MEMCACHE_COMPRESSION_LEVEL 171 compression_level = self.MEMCACHE_COMPRESSION_LEVEL
220 compressed = zlib.compress(json_data, compression_level) 172 compressed = zlib.compress(json_data, compression_level)
221 memcache.set(key, compressed) 173 memcache.set(key, compressed)
222 174
223 175
224 class TimeSeriesAlertsHistory(alerts_history.AlertsHistory): 176 class TimeSeriesAlertsHistory(alerts_history.AlertsHistory):
225 177
226 def get(self, timestamp=None): 178 def get(self, timestamp=None):
179 utils.increment_monarch('ts-alerts-history')
227 result_json = {} 180 result_json = {}
228 if not users.get_current_user(): 181 if not users.get_current_user():
229 result_json['login-url'] = users.create_login_url(self.request.uri) 182 result_json['login-url'] = users.create_login_url(self.request.uri)
230 return result_json 183 return result_json
231 184
232 alerts = TSAlertsJSON.query_active().fetch() 185 alerts = TSAlertsJSON.query_active().fetch()
233 if timestamp: 186 if timestamp:
234 try: 187 try:
235 time = dt.fromtimestamp(int(timestamp)) 188 time = dt.fromtimestamp(int(timestamp))
236 except ValueError: 189 except ValueError:
237 self.response.set_status(400, 'Invalid timestamp.') 190 self.response.set_status(400, 'Invalid timestamp.')
238 return 191 return
239 if time > dt.utcnow(): 192 if time > dt.utcnow():
240 self.response.write('Sheriff-o-matic cannot predict the future... yet.') 193 self.response.write('Sheriff-o-matic cannot predict the future... yet.')
241 self.response.set_status(400, 'Invalid timestamp.') 194 self.response.set_status(400, 'Invalid timestamp.')
242 else: 195 else:
243 time = dt.utcnow() 196 time = dt.utcnow()
244 alerts += TSAlertsJSON.query(TSAlertsJSON.active_until > time).fetch() 197 alerts += TSAlertsJSON.query(TSAlertsJSON.active_until > time).fetch()
245 198
246 history = [] 199 history = []
247 for a in alerts: 200 for a in alerts:
248 ts, private = timestamp, a.json['private'] 201 ts, private = timestamp, a.json['private']
249 in_range = not (ts and secs_ago(a.json['active_since_utc'], ts) < 0) 202 in_range = not (ts and utils.secs_ago(a.json['active_since_utc'], ts) < 0)
250 permission = is_googler() or not private 203 permission = utils.is_googler() or not private
251 if in_range and permission: 204 if in_range and permission:
252 history.append(a.json) 205 history.append(a.json)
253 206
254 result_json.update({ 207 result_json.update({
255 'timestamp': time.strftime('%s'), 208 'timestamp': time.strftime('%s'),
256 'time_string': time.strftime('%Y-%m-%d %H:%M:%S %Z'), 209 'time_string': time.strftime('%Y-%m-%d %H:%M:%S %Z'),
257 'active_alerts': history 210 'active_alerts': history
258 }) 211 })
259 212
260 self.write_json(result_json) 213 self.write_json(result_json)
261 214
262 def write_json(self, data): 215 def write_json(self, data):
263 self.response.headers['Access-Control-Allow-Origin'] = '*' 216 self.response.headers['Access-Control-Allow-Origin'] = '*'
264 self.response.headers['Content-Type'] = 'application/json' 217 self.response.headers['Content-Type'] = 'application/json'
265 data = generate_json_dump(data) 218 data = utils.generate_json_dump(data)
266 self.response.write(data) 219 self.response.write(data)
267 220
268 221
269 app = webapp2.WSGIApplication([ 222 app = webapp2.WSGIApplication([
270 ('/ts-alerts', TimeSeriesAlertsHandler), 223 ('/ts-alerts', TimeSeriesAlertsHandler),
271 ('/ts-alerts/(.*)', TimeSeriesAlertsHandler), 224 ('/ts-alerts/(.*)', TimeSeriesAlertsHandler),
272 ('/ts-alerts-history', TimeSeriesAlertsHistory), 225 ('/ts-alerts-history', TimeSeriesAlertsHistory),
273 ('/ts-alerts-history/(.*)', TimeSeriesAlertsHistory)]) 226 ('/ts-alerts-history/(.*)', TimeSeriesAlertsHistory)])
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698