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

Side by Side Diff: appengine/monorail/framework/framework_helpers.py

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 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
(Empty)
1 # Copyright 2016 The Chromium Authors. All rights reserved.
2 # Use of this source code is govered by a BSD-style
3 # license that can be found in the LICENSE file or at
4 # https://developers.google.com/open-source/licenses/bsd
5
6 """Helper functions and classes used throughout Monorail."""
7
8 import logging
9 import random
10 import string
11 import textwrap
12 import threading
13 import time
14 import traceback
15 import urllib
16 import urlparse
17
18 from google.appengine.api import app_identity
19
20 from third_party import ezt
21
22 import settings
23 from framework import actionlimit
24 from framework import framework_constants
25 from framework import template_helpers
26 from framework import timestr
27 from framework import urls
28 from services import client_config_svc
29
30
31 # For random key generation
32 RANDOM_KEY_LENGTH = 128
33 RANDOM_KEY_CHARACTERS = string.ascii_letters + string.digits
34
35 # params recognized by FormatURL, in the order they will appear in the url
36 RECOGNIZED_PARAMS = ['can', 'start', 'num', 'q', 'colspec', 'groupby', 'sort',
37 'show', 'format', 'me', 'table_title', 'projects']
38
39
40 def retry(tries, delay=1, backoff=2):
41 """A retry decorator with exponential backoff.
42
43 Functions are retried when Exceptions occur.
44
45 Args:
46 tries: int Number of times to retry, set to 0 to disable retry.
47 delay: float Initial sleep time in seconds.
48 backoff: float Must be greater than 1, further failures would sleep
49 delay*=backoff seconds.
50 """
51 if backoff <= 1:
52 raise ValueError("backoff must be greater than 1")
53 if tries < 0:
54 raise ValueError("tries must be 0 or greater")
55 if delay <= 0:
56 raise ValueError("delay must be greater than 0")
57
58 def decorator(func):
59 def wrapper(*args, **kwargs):
60 _tries, _delay = tries, delay
61 _tries += 1 # Ensure we call func at least once.
62 while _tries > 0:
63 try:
64 ret = func(*args, **kwargs)
65 return ret
66 except Exception:
67 _tries -= 1
68 if _tries == 0:
69 logging.error('Exceeded maximum number of retries for %s.',
70 func.__name__)
71 raise
72 trace_str = traceback.format_exc()
73 logging.warning('Retrying %s due to Exception: %s',
74 func.__name__, trace_str)
75 time.sleep(_delay)
76 _delay *= backoff # Wait longer the next time we fail.
77 return wrapper
78 return decorator
79
80
81 class PromiseCallback(object):
82 """Executes the work of a Promise and then dereferences everything."""
83
84 def __init__(self, promise, callback, *args, **kwargs):
85 self.promise = promise
86 self.callback = callback
87 self.args = args
88 self.kwargs = kwargs
89
90 def __call__(self):
91 try:
92 self.promise._WorkOnPromise(self.callback, *self.args, **self.kwargs)
93 finally:
94 # Make sure we no longer hold onto references to anything.
95 self.promise = self.callback = self.args = self.kwargs = None
96
97
98 class Promise(object):
99 """Class for promises to deliver a value in the future.
100
101 A thread is started to run callback(args), that thread
102 should return the value that it generates, or raise an expception.
103 p.WaitAndGetValue() will block until a value is available.
104 If an exception was raised, p.WaitAndGetValue() will re-raise the
105 same exception.
106 """
107
108 def __init__(self, callback, *args, **kwargs):
109 """Initialize the promise and immediately call the supplied function.
110
111 Args:
112 callback: Function that takes the args and returns the promise value.
113 *args: Any arguments to the target function.
114 **kwargs: Any keyword args for the target function.
115 """
116
117 self.has_value = False
118 self.value = None
119 self.event = threading.Event()
120 self.exception = None
121
122 promise_callback = PromiseCallback(self, callback, *args, **kwargs)
123
124 # Execute the callback in another thread.
125 promise_thread = threading.Thread(target=promise_callback)
126 promise_thread.start()
127
128 def _WorkOnPromise(self, callback, *args, **kwargs):
129 """Run callback to compute the promised value. Save any exceptions."""
130 try:
131 self.value = callback(*args, **kwargs)
132 except Exception as e:
133 trace_str = traceback.format_exc()
134 logging.info('Exception while working on promise: %s\n', trace_str)
135 # Add the stack trace at this point to the exception. That way, in the
136 # logs, we can see what happened further up in the call stack
137 # than WaitAndGetValue(), which re-raises exceptions.
138 e.pre_promise_trace = trace_str
139 self.exception = e
140 finally:
141 self.has_value = True
142 self.event.set()
143
144 def WaitAndGetValue(self):
145 """Block until my value is available, then return it or raise exception."""
146 self.event.wait()
147 if self.exception:
148 raise self.exception # pylint: disable=raising-bad-type
149 return self.value
150
151
152 def FormatAbsoluteURLForDomain(
153 host, project_name, servlet_name, scheme='https', **kwargs):
154 """A variant of FormatAbsoluteURL for when request objects are not available.
155
156 Args:
157 host: string with hostname and optional port, e.g. 'localhost:8080'.
158 project_name: the destination project name, if any.
159 servlet_name: site or project-local url fragement of dest page.
160 scheme: url scheme, e.g., 'http' or 'https'.
161 **kwargs: additional query string parameters may be specified as named
162 arguments to this function.
163
164 Returns:
165 A full url beginning with 'http[s]://'.
166 """
167 path_and_args = FormatURL(None, servlet_name, **kwargs)
168
169 if host:
170 domain_port = host.split(':')
171 domain_port[0] = GetPreferredDomain(domain_port[0])
172 host = ':'.join(domain_port)
173
174 absolute_domain_url = '%s://%s' % (scheme, host)
175 if project_name:
176 return '%s/p/%s%s' % (absolute_domain_url, project_name, path_and_args)
177 return absolute_domain_url + path_and_args
178
179
180 def FormatAbsoluteURL(
181 mr, servlet_name, include_project=True, project_name=None,
182 scheme=None, copy_params=True, **kwargs):
183 """Return an absolute URL to a servlet with old and new params.
184
185 Args:
186 mr: info parsed from the current request.
187 servlet_name: site or project-local url fragement of dest page.
188 include_project: if True, include the project home url as part of the
189 destination URL (as long as it is specified either in mr
190 or as the project_name param.)
191 project_name: the destination project name, to override
192 mr.project_name if include_project is True.
193 scheme: either 'http' or 'https', to override mr.request.scheme.
194 copy_params: if True, copy well-known parameters from the existing request.
195 **kwargs: additional query string parameters may be specified as named
196 arguments to this function.
197
198 Returns:
199 A full url beginning with 'http[s]://'. The destination URL will be in
200 the same domain as the current request.
201 """
202 path_and_args = FormatURL(
203 mr if copy_params else None, servlet_name, **kwargs)
204 scheme = scheme or mr.request.scheme
205
206 project_base = ''
207 if include_project:
208 project_base = '/p/%s' % (project_name or mr.project_name)
209
210 return '%s://%s%s%s' % (scheme, mr.request.host, project_base, path_and_args)
211
212
213 def FormatMovedProjectURL(mr, moved_to):
214 """Return a transformation of the given url into the given project.
215
216 Args:
217 mr: common information parsed from the HTTP request.
218 moved_to: A string from a project's moved_to field that matches
219 framework_bizobj.RE_PROJECT_NAME.
220
221 Returns:
222 The url transposed into the given destination project.
223 """
224 project_name = moved_to
225 _, _, path, parameters, query, fragment_identifier = urlparse.urlparse(
226 mr.current_page_url)
227 # Strip off leading "/p/<moved from project>"
228 path = '/' + path.split('/', 3)[3]
229 rest_of_url = urlparse.urlunparse(
230 ('', '', path, parameters, query, fragment_identifier))
231 return '/p/%s%s' % (project_name, rest_of_url)
232
233
234 def FormatURL(mr, servlet_path, **kwargs):
235 """Return a project relative URL to a servlet with old and new params."""
236 # Standard params not overridden in **kwargs come first, followed by kwargs.
237 # The exception is the 'id' param. If present then the 'id' param always comes
238 # first. See bugs.chromium.org/p/monorail/issues/detail?id=374
239 all_params = []
240 if kwargs.get('id'):
241 all_params.append(('id', kwargs['id']))
242 if mr:
243 all_params.extend(
244 (name, mr.GetParam(name)) for name in RECOGNIZED_PARAMS
245 if name not in kwargs)
246
247 all_params.extend(
248 # Ignore the 'id' param since we already added it above.
249 sorted([kwarg for kwarg in kwargs.items() if kwarg[0] != 'id']))
250 return _FormatQueryString(servlet_path, all_params)
251
252
253 def _FormatQueryString(url, params):
254 """URLencode a list of parameters and attach them to the end of a URL."""
255 param_string = '&'.join(
256 '%s=%s' % (name, urllib.quote(unicode(value).encode('utf-8')))
257 for name, value in params if value is not None)
258 if not param_string:
259 qs_start_char = ''
260 elif '?' in url:
261 qs_start_char = '&'
262 else:
263 qs_start_char = '?'
264 return '%s%s%s' % (url, qs_start_char, param_string)
265
266
267 def WordWrapSuperLongLines(s, max_cols=100):
268 """Reformat input that was not word-wrapped by the browser.
269
270 Args:
271 s: the string to be word-wrapped, it may have embedded newlines.
272 max_cols: int maximum line length.
273
274 Returns:
275 Wrapped text string.
276
277 Rather than wrap the whole thing, we only wrap super-long lines and keep
278 all the reasonable lines formated as-is.
279 """
280 lines = [textwrap.fill(line, max_cols) for line in s.splitlines()]
281 wrapped_text = '\n'.join(lines)
282
283 # The split/join logic above can lose one final blank line.
284 if s.endswith('\n') or s.endswith('\r'):
285 wrapped_text += '\n'
286
287 return wrapped_text
288
289
290 def StaticCacheHeaders():
291 """Returns HTTP headers for static content, based on the current time."""
292 year_from_now = int(time.time()) + framework_constants.SECS_PER_YEAR
293 headers = [
294 ('Cache-Control',
295 'max-age=%d, private' % framework_constants.SECS_PER_YEAR),
296 ('Last-Modified', timestr.TimeForHTMLHeader()),
297 ('Expires', timestr.TimeForHTMLHeader(when=year_from_now)),
298 ]
299 logging.info('static headers are %r', headers)
300 return headers
301
302
303 def ComputeListDeltas(old_list, new_list):
304 """Given an old and new list, return the items added and removed.
305
306 Args:
307 old_list: old list of values for comparison.
308 new_list: new list of values for comparison.
309
310 Returns:
311 Two lists: one with all the values added (in new_list but was not
312 in old_list), and one with all the values removed (not in new_list
313 but was in old_lit).
314 """
315 if old_list == new_list:
316 return [], [] # A common case: nothing was added or removed.
317
318 added = set(new_list)
319 added.difference_update(old_list)
320 removed = set(old_list)
321 removed.difference_update(new_list)
322 return list(added), list(removed)
323
324
325 def GetRoleName(effective_ids, project):
326 """Determines the name of the role a member has for a given project.
327
328 Args:
329 effective_ids: set of user IDs to get the role name for.
330 project: Project PB containing the different the different member lists.
331
332 Returns:
333 The name of the role.
334 """
335 if not effective_ids.isdisjoint(project.owner_ids):
336 return 'Owner'
337 if not effective_ids.isdisjoint(project.committer_ids):
338 return 'Committer'
339 if not effective_ids.isdisjoint(project.contributor_ids):
340 return 'Contributor'
341 return None
342
343
344 class UserSettings(object):
345 """Abstract class providing static methods for user settings forms."""
346
347 @classmethod
348 def GatherUnifiedSettingsPageData(
349 cls, logged_in_user_id, settings_user_view, settings_user):
350 """Gather EZT variables needed for the unified user settings form.
351
352 Args:
353 logged_in_user_id: The user ID of the acting user.
354 settings_user_view: The UserView of the target user.
355 settings_user: The User PB of the target user.
356
357 Returns:
358 A dictionary giving the names and values of all the variables to
359 be exported to EZT to support the unified user settings form template.
360 """
361
362 def ActionLastReset(action_limit):
363 """Return a formatted time string for the last action limit reset."""
364 if action_limit:
365 return time.asctime(time.localtime(action_limit.reset_timestamp))
366 return 'Never'
367
368 def DefaultLifetimeLimit(action_type):
369 """Return the deault lifetime limit for the give type of action."""
370 return actionlimit.ACTION_LIMITS[action_type][3]
371
372 def DefaultPeriodSoftLimit(action_type):
373 """Return the deault period soft limit for the give type of action."""
374 return actionlimit.ACTION_LIMITS[action_type][1]
375
376 def DefaultPeriodHardLimit(action_type):
377 """Return the deault period jard limit for the give type of action."""
378 return actionlimit.ACTION_LIMITS[action_type][2]
379
380 project_creation_lifetime_limit = (
381 (settings_user.project_creation_limit and
382 settings_user.project_creation_limit.lifetime_limit) or
383 DefaultLifetimeLimit(actionlimit.PROJECT_CREATION))
384 project_creation_soft_limit = (
385 (settings_user.project_creation_limit and
386 settings_user.project_creation_limit.period_soft_limit) or
387 DefaultPeriodSoftLimit(actionlimit.PROJECT_CREATION))
388 project_creation_hard_limit = (
389 (settings_user.project_creation_limit and
390 settings_user.project_creation_limit.period_hard_limit) or
391 DefaultPeriodHardLimit(actionlimit.PROJECT_CREATION))
392 issue_comment_lifetime_limit = (
393 (settings_user.issue_comment_limit and
394 settings_user.issue_comment_limit.lifetime_limit) or
395 DefaultLifetimeLimit(actionlimit.ISSUE_COMMENT))
396 issue_comment_soft_limit = (
397 (settings_user.issue_comment_limit and
398 settings_user.issue_comment_limit.period_soft_limit) or
399 DefaultPeriodSoftLimit(actionlimit.ISSUE_COMMENT))
400 issue_comment_hard_limit = (
401 (settings_user.issue_comment_limit and
402 settings_user.issue_comment_limit.period_hard_limit) or
403 DefaultPeriodHardLimit(actionlimit.ISSUE_COMMENT ))
404 issue_attachment_lifetime_limit = (
405 (settings_user.issue_attachment_limit and
406 settings_user.issue_attachment_limit.lifetime_limit) or
407 DefaultLifetimeLimit(actionlimit.ISSUE_ATTACHMENT))
408 issue_attachment_soft_limit = (
409 (settings_user.issue_attachment_limit and
410 settings_user.issue_attachment_limit.period_soft_limit) or
411 DefaultPeriodSoftLimit(actionlimit.ISSUE_ATTACHMENT))
412 issue_attachment_hard_limit = (
413 (settings_user.issue_attachment_limit and
414 settings_user.issue_attachment_limit.period_hard_limit) or
415 DefaultPeriodHardLimit(actionlimit.ISSUE_ATTACHMENT))
416 issue_bulk_edit_lifetime_limit = (
417 (settings_user.issue_bulk_edit_limit and
418 settings_user.issue_bulk_edit_limit.lifetime_limit) or
419 DefaultLifetimeLimit(actionlimit.ISSUE_BULK_EDIT))
420 issue_bulk_edit_soft_limit = (
421 (settings_user.issue_bulk_edit_limit and
422 settings_user.issue_bulk_edit_limit.period_soft_limit) or
423 DefaultPeriodSoftLimit(actionlimit.ISSUE_BULK_EDIT))
424 issue_bulk_edit_hard_limit = (
425 (settings_user.issue_bulk_edit_limit and
426 settings_user.issue_bulk_edit_limit.period_hard_limit) or
427 DefaultPeriodHardLimit(actionlimit.ISSUE_BULK_EDIT))
428 api_request_lifetime_limit = (
429 (settings_user.api_request_limit and
430 settings_user.api_request_limit.lifetime_limit) or
431 DefaultLifetimeLimit(actionlimit.API_REQUEST))
432 api_request_soft_limit = (
433 (settings_user.api_request_limit and
434 settings_user.api_request_limit.period_soft_limit) or
435 DefaultPeriodSoftLimit(actionlimit.API_REQUEST))
436 api_request_hard_limit = (
437 (settings_user.api_request_limit and
438 settings_user.api_request_limit.period_hard_limit) or
439 DefaultPeriodHardLimit(actionlimit.API_REQUEST))
440
441 return {
442 'settings_user': settings_user_view,
443 'settings_user_pb': template_helpers.PBProxy(settings_user),
444 'settings_user_is_banned': ezt.boolean(settings_user.banned),
445 'settings_user_ignore_action_limits': (
446 ezt.boolean(settings_user.ignore_action_limits)),
447 'self': ezt.boolean(logged_in_user_id == settings_user_view.user_id),
448 'project_creation_reset': (
449 ActionLastReset(settings_user.project_creation_limit)),
450 'issue_comment_reset': (
451 ActionLastReset(settings_user.issue_comment_limit)),
452 'issue_attachment_reset': (
453 ActionLastReset(settings_user.issue_attachment_limit)),
454 'issue_bulk_edit_reset': (
455 ActionLastReset(settings_user.issue_bulk_edit_limit)),
456 'api_request_reset': (
457 ActionLastReset(settings_user.api_request_limit)),
458 'project_creation_lifetime_limit': project_creation_lifetime_limit,
459 'project_creation_soft_limit': project_creation_soft_limit,
460 'project_creation_hard_limit': project_creation_hard_limit,
461 'issue_comment_lifetime_limit': issue_comment_lifetime_limit,
462 'issue_comment_soft_limit': issue_comment_soft_limit,
463 'issue_comment_hard_limit': issue_comment_hard_limit,
464 'issue_attachment_lifetime_limit': issue_attachment_lifetime_limit,
465 'issue_attachment_soft_limit': issue_attachment_soft_limit,
466 'issue_attachment_hard_limit': issue_attachment_hard_limit,
467 'issue_bulk_edit_lifetime_limit': issue_bulk_edit_lifetime_limit,
468 'issue_bulk_edit_soft_limit': issue_bulk_edit_soft_limit,
469 'issue_bulk_edit_hard_limit': issue_bulk_edit_hard_limit,
470 'api_request_lifetime_limit': api_request_lifetime_limit,
471 'api_request_soft_limit': api_request_soft_limit,
472 'api_request_hard_limit': api_request_hard_limit,
473 'profile_url_fragment': (
474 settings_user_view.profile_url[len('/u/'):]),
475 'preview_on_hover': ezt.boolean(settings_user.preview_on_hover),
476 }
477
478 @classmethod
479 def ProcessSettingsForm(
480 cls, cnxn, user_service, post_data, user_id, user, admin=False):
481 """Process the posted form data from the unified user settings form.
482
483 Args:
484 cnxn: connection to the SQL database.
485 user_service: An instance of UserService for saving changes.
486 post_data: The parsed post data from the form submission request.
487 user_id: The user id of the target user.
488 user: The user PB of the target user.
489 admin: Whether settings reserved for admins are supported.
490 """
491 obscure_email = 'obscure_email' in post_data
492
493 kwargs = {}
494 if admin:
495 kwargs.update(is_site_admin='site_admin' in post_data,
496 ignore_action_limits='ignore_action_limits' in post_data)
497 kwargs.update(is_banned='banned' in post_data,
498 banned_reason=post_data.get('banned_reason', ''))
499
500 # action limits
501 action_limit_updates = {}
502 for action_name in actionlimit.ACTION_TYPE_NAMES.iterkeys():
503 reset_input = 'reset_' + action_name
504 lifetime_input = action_name + '_lifetime_limit'
505 soft_input = action_name + '_soft_limit'
506 hard_input = action_name + '_hard_limit'
507 pb_getter = action_name + '_limit'
508 old_lifetime_limit = getattr(user, pb_getter).lifetime_limit
509 old_soft_limit = getattr(user, pb_getter).period_soft_limit
510 old_hard_limit = getattr(user, pb_getter).period_hard_limit
511
512 # Try and get the new limit from post data.
513 # If the user doesn't use an integer, act as if no change requested.
514 def _GetLimit(post_data, limit_input, old_limit):
515 try:
516 new_limit = int(post_data[limit_input])
517 except (KeyError, ValueError):
518 new_limit = old_limit
519 return new_limit
520
521 new_lifetime_limit = _GetLimit(post_data, lifetime_input,
522 old_lifetime_limit)
523 new_soft_limit = _GetLimit(post_data, soft_input,
524 old_soft_limit)
525 new_hard_limit = _GetLimit(post_data, hard_input,
526 old_hard_limit)
527
528 if ((new_lifetime_limit >= 0 and
529 new_lifetime_limit != old_lifetime_limit) or
530 (new_soft_limit >= 0 and new_soft_limit != old_soft_limit) or
531 (new_hard_limit >= 0 and new_hard_limit != old_hard_limit)):
532 action_limit_updates[action_name] = (
533 new_soft_limit, new_hard_limit, new_lifetime_limit)
534 elif reset_input in post_data:
535 action_limit_updates[action_name] = None
536 kwargs.update(action_limit_updates=action_limit_updates)
537
538 user_service.UpdateUserSettings(
539 cnxn, user_id, user, notify='notify' in post_data,
540 notify_starred='notify_starred' in post_data,
541 preview_on_hover='preview_on_hover' in post_data,
542 obscure_email=obscure_email, **kwargs)
543
544
545 def GetHostPort():
546 """Get string domain name and port number."""
547
548 app_id = app_identity.get_application_id()
549 if ':' in app_id:
550 domain, app_id = app_id.split(':')
551 else:
552 domain = ''
553
554 if domain.startswith('google'):
555 hostport = '%s.googleplex.com' % app_id
556 else:
557 hostport = '%s.appspot.com' % app_id
558
559 return GetPreferredDomain(hostport)
560
561
562 def IssueCommentURL(hostport, project, local_id, seq_num=None):
563 """Return a URL pointing directly to the specified comment."""
564 detail_url = FormatAbsoluteURLForDomain(
565 hostport, project.project_name, urls.ISSUE_DETAIL, id=local_id)
566 if seq_num:
567 detail_url += '#c%d' % seq_num
568
569 return detail_url
570
571
572 def MurmurHash3_x86_32(key, seed=0x0):
573 """Implements the x86/32-bit version of Murmur Hash 3.0.
574
575 MurmurHash3 is written by Austin Appleby, and is placed in the public
576 domain. See https://code.google.com/p/smhasher/ for details.
577
578 This pure python implementation of the x86/32 bit version of MurmurHash3 is
579 written by Fredrik Kihlander and also placed in the public domain.
580 See https://github.com/wc-duck/pymmh3 for details.
581
582 The MurmurHash3 algorithm is chosen for these reasons:
583 * It is fast, even when implemented in pure python.
584 * It is remarkably well distributed, and unlikely to cause collisions.
585 * It is stable and unchanging (any improvements will be in MurmurHash4).
586 * It is well-tested, and easily usable in other contexts (such as bulk
587 data imports).
588
589 Args:
590 key (string): the data that you want hashed
591 seed (int): An offset, treated as essentially part of the key.
592
593 Returns:
594 A 32-bit integer (can be interpreted as either signed or unsigned).
595 """
596 key = bytearray(key.encode('utf-8'))
597
598 def fmix(h):
599 h ^= h >> 16
600 h = (h * 0x85ebca6b) & 0xFFFFFFFF
601 h ^= h >> 13
602 h = (h * 0xc2b2ae35) & 0xFFFFFFFF
603 h ^= h >> 16
604 return h;
605
606 length = len(key)
607 nblocks = int(length / 4)
608
609 h1 = seed;
610
611 c1 = 0xcc9e2d51
612 c2 = 0x1b873593
613
614 # body
615 for block_start in xrange(0, nblocks * 4, 4):
616 k1 = key[ block_start + 3 ] << 24 | \
617 key[ block_start + 2 ] << 16 | \
618 key[ block_start + 1 ] << 8 | \
619 key[ block_start + 0 ]
620
621 k1 = c1 * k1 & 0xFFFFFFFF
622 k1 = (k1 << 15 | k1 >> 17) & 0xFFFFFFFF
623 k1 = (c2 * k1) & 0xFFFFFFFF;
624
625 h1 ^= k1
626 h1 = ( h1 << 13 | h1 >> 19 ) & 0xFFFFFFFF
627 h1 = ( h1 * 5 + 0xe6546b64 ) & 0xFFFFFFFF
628
629 # tail
630 tail_index = nblocks * 4
631 k1 = 0
632 tail_size = length & 3
633
634 if tail_size >= 3:
635 k1 ^= key[ tail_index + 2 ] << 16
636 if tail_size >= 2:
637 k1 ^= key[ tail_index + 1 ] << 8
638 if tail_size >= 1:
639 k1 ^= key[ tail_index + 0 ]
640
641 if tail_size != 0:
642 k1 = ( k1 * c1 ) & 0xFFFFFFFF
643 k1 = ( k1 << 15 | k1 >> 17 ) & 0xFFFFFFFF
644 k1 = ( k1 * c2 ) & 0xFFFFFFFF
645 h1 ^= k1
646
647 return fmix( h1 ^ length )
648
649
650 def MakeRandomKey(length=RANDOM_KEY_LENGTH, chars=RANDOM_KEY_CHARACTERS):
651 """Return a string with lots of random characters."""
652 chars = [random.choice(chars) for _ in range(length)]
653 return ''.join(chars)
654
655
656 def IsServiceAccount(email):
657 """Return a boolean value whether this email is a service account."""
658 if email.endswith('gserviceaccount.com'):
659 return True
660 _, client_emails = (
661 client_config_svc.GetClientConfigSvc().GetClientIDEmails())
662 return email in client_emails
663
664
665 def GetPreferredDomain(domain):
666 """Get preferred domain to display.
667
668 The preferred domain replaces app_id for default version of monorail-prod
669 and monorail-staging.
670 """
671 return settings.preferred_domains.get(domain, domain)
OLDNEW
« no previous file with comments | « appengine/monorail/framework/framework_constants.py ('k') | appengine/monorail/framework/framework_views.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698