blob: 5bcb92845849a30b2dd7b1c5ec298a748c4578b2 [file] [log] [blame]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -04001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Upload all debug symbols required for crash reporting purposes.
6
7This script need only be used to upload release builds symbols or to debug
8crashes on non-release builds (in which case try to only upload the symbols
Mike Frysinger02e1e072013-11-10 22:11:34 -05009for those executables involved).
10"""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040011
Mike Frysingera4fa1e82014-01-15 01:45:56 -050012from __future__ import print_function
13
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040014import ctypes
Mike Frysinger8ec8c502014-02-10 00:19:13 -050015import datetime
Mike Frysinger02e92402013-11-22 16:22:02 -050016import functools
Mike Frysinger0c0efa22014-02-09 23:32:23 -050017import hashlib
Mike Frysingera4fa1e82014-01-15 01:45:56 -050018import httplib
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040019import multiprocessing
20import os
Mike Frysinger094a2172013-08-14 12:54:35 -040021import poster
Mike Frysinger66e51e92014-05-03 16:52:00 -040022try:
23 import Queue
24except ImportError:
25 # Python-3 renamed to "queue". We still use Queue to avoid collisions
26 # with naming variables as "queue". Maybe we'll transition at some point.
27 # pylint: disable=F0401
28 import queue as Queue
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040029import random
Mike Frysinger66e51e92014-05-03 16:52:00 -040030import signal
Mike Frysingerfd355652014-01-23 02:57:48 -050031import socket
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040032import textwrap
33import tempfile
34import time
Mike Frysinger094a2172013-08-14 12:54:35 -040035import urllib2
Mike Frysingerd41938e2014-02-10 06:37:55 -050036import urlparse
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040037
Mike Frysinger0c0efa22014-02-09 23:32:23 -050038from chromite.buildbot import constants
Mike Frysingerd41938e2014-02-10 06:37:55 -050039from chromite.lib import cache
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040040from chromite.lib import commandline
41from chromite.lib import cros_build_lib
Mike Frysingerd41938e2014-02-10 06:37:55 -050042from chromite.lib import gs
43from chromite.lib import osutils
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040044from chromite.lib import parallel
David Jamesc93e6a4d2014-01-13 11:37:36 -080045from chromite.lib import retry_util
Mike Frysinger66e51e92014-05-03 16:52:00 -040046from chromite.lib import signals
Mike Frysinger0c0efa22014-02-09 23:32:23 -050047from chromite.lib import timeout_util
Mike Frysinger69cb41d2013-08-11 20:08:19 -040048from chromite.scripts import cros_generate_breakpad_symbols
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040049
Mike Frysinger0c0efa22014-02-09 23:32:23 -050050# Needs to be after chromite imports.
51# TODO(build): When doing the initial buildbot bootstrap, we won't have any
52# other repos available. So ignore isolateserver imports. But buildbot will
53# re-exec itself once it has done a full repo sync and then the module will
54# be available -- it isn't needed that early. http://crbug.com/341152
55try:
56 import isolateserver
57except ImportError:
58 isolateserver = None
59
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -040060
61# URLs used for uploading symbols.
62OFFICIAL_UPLOAD_URL = 'http://clients2.google.com/cr/symbol'
63STAGING_UPLOAD_URL = 'http://clients2.google.com/cr/staging_symbol'
64
65
66# The crash server rejects files that are this big.
67CRASH_SERVER_FILE_LIMIT = 350 * 1024 * 1024
68# Give ourselves a little breathing room from what the server expects.
69DEFAULT_FILE_LIMIT = CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024)
70
71
Mike Frysinger0c0efa22014-02-09 23:32:23 -050072# The batch limit when talking to the dedup server. We avoid sending one at a
73# time as the round trip overhead will dominate. Conversely, we avoid sending
74# all at once so we can start uploading symbols asap -- the symbol server is a
75# bit slow and will take longer than anything else.
76# TODO: A better algorithm would be adaptive. If we have more than one symbol
77# in the upload queue waiting, we could send more symbols to the dedupe server
78# at a time.
79DEDUPE_LIMIT = 100
80
81# How long to wait for the server to respond with the results. Note that the
82# larger the limit above, the larger this will need to be. So we give it ~1
83# second per item max.
84DEDUPE_TIMEOUT = DEDUPE_LIMIT
85
Mike Frysinger4dd462e2014-04-30 16:21:51 -040086# How long to wait for the notification to finish (in minutes). If it takes
87# longer than this, we'll stop notifiying, but that's not a big deal as we
88# will be able to recover in later runs.
89DEDUPE_NOTIFY_TIMEOUT = 20
90
Mike Frysinger0c0efa22014-02-09 23:32:23 -050091# The unique namespace in the dedupe server that only we use. Helps avoid
92# collisions with all the hashed values and unrelated content.
93OFFICIAL_DEDUPE_NAMESPACE = 'chromium-os-upload-symbols'
94STAGING_DEDUPE_NAMESPACE = '%s-staging' % OFFICIAL_DEDUPE_NAMESPACE
95
96
Mike Frysingercd78a082013-06-26 17:13:04 -040097# How long to wait (in seconds) for a single upload to complete. This has
98# to allow for symbols that are up to CRASH_SERVER_FILE_LIMIT in size.
99UPLOAD_TIMEOUT = 30 * 60
100
101
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400102# Sleep for 200ms in between uploads to avoid DoS'ing symbol server.
103DEFAULT_SLEEP_DELAY = 0.2
104
105
106# Number of seconds to wait before retrying an upload. The delay will double
107# for each subsequent retry of the same symbol file.
108INITIAL_RETRY_DELAY = 1
109
110# Allow up to 7 attempts to upload a symbol file (total delay may be
111# 1+2+4+8+16+32=63 seconds).
112MAX_RETRIES = 6
113
Mike Frysingereb753bf2013-11-22 16:05:35 -0500114# Number of total errors, before uploads are no longer attempted.
115# This is used to avoid lots of errors causing unreasonable delays.
116# See the related, but independent, error values below.
117MAX_TOTAL_ERRORS_FOR_RETRY = 30
118
119# A watermark of transient errors which we allow recovery from. If we hit
120# errors infrequently, overall we're probably doing fine. For example, if
121# we have one failure every 100 passes, then we probably don't want to fail
122# right away. But if we hit a string of failures in a row, we want to abort.
123#
124# The watermark starts at 0 (and can never go below that). When this error
125# level is exceeded, we stop uploading. When a failure happens, we add the
126# fail adjustment, and when an upload succeeds, we add the pass adjustment.
127# We want to penalize failures more so that we ramp up when there is a string
128# of them, but then slowly back off as things start working.
129#
130# A quick example:
131# 0.0: Starting point.
132# 0.0: Upload works, so add -0.5, and then clamp to 0.
133# 1.0: Upload fails, so add 1.0.
134# 2.0: Upload fails, so add 1.0.
135# 1.5: Upload works, so add -0.5.
136# 1.0: Upload works, so add -0.5.
137ERROR_WATERMARK = 3.0
138ERROR_ADJUST_FAIL = 1.0
139ERROR_ADJUST_PASS = -0.5
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400140
141
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500142def SymUpload(upload_url, sym_item):
Mike Frysinger094a2172013-08-14 12:54:35 -0400143 """Upload a symbol file to a HTTP server
144
145 The upload is a multipart/form-data POST with the following parameters:
146 code_file: the basename of the module, e.g. "app"
147 code_identifier: the module file's identifier
148 debug_file: the basename of the debugging file, e.g. "app"
149 debug_identifier: the debug file's identifier, usually consisting of
150 the guid and age embedded in the pdb, e.g.
151 "11111111BBBB3333DDDD555555555555F"
152 version: the file version of the module, e.g. "1.2.3.4"
153 product: HTTP-friendly product name
154 os: the operating system that the module was built for
155 cpu: the CPU that the module was built for
156 symbol_file: the contents of the breakpad-format symbol file
157
158 Args:
Mike Frysinger094a2172013-08-14 12:54:35 -0400159 upload_url: The crash URL to POST the |sym_file| to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500160 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Mike Frysinger094a2172013-08-14 12:54:35 -0400161 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500162 sym_header = sym_item.sym_header
163 sym_file = sym_item.sym_file
Mike Frysinger094a2172013-08-14 12:54:35 -0400164
165 fields = (
166 ('code_file', sym_header.name),
167 ('debug_file', sym_header.name),
168 ('debug_identifier', sym_header.id.replace('-', '')),
Mike Frysingerb8a966b2014-03-19 17:36:18 -0400169 # The product/version fields are used by the server only for statistic
170 # purposes. They do not impact symbolization, so they're safe to set
171 # to any value all the time.
172 # In this case, we use it to help see the load our build system is
173 # placing on the server.
174 # Not sure what to set for the version. Maybe the git sha1 of this file.
175 # Note: the server restricts this to 30 chars.
Mike Frysinger094a2172013-08-14 12:54:35 -0400176 #('version', None),
Mike Frysingerb8a966b2014-03-19 17:36:18 -0400177 ('product', 'ChromeOS'),
Mike Frysinger094a2172013-08-14 12:54:35 -0400178 ('os', sym_header.os),
179 ('cpu', sym_header.cpu),
180 poster.encode.MultipartParam.from_file('symbol_file', sym_file),
181 )
182
183 data, headers = poster.encode.multipart_encode(fields)
184 request = urllib2.Request(upload_url, data, headers)
185 request.add_header('User-agent', 'chromite.upload_symbols')
186 urllib2.urlopen(request, timeout=UPLOAD_TIMEOUT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400187
188
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500189def TestingSymUpload(upload_url, sym_item):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400190 """A stub version of SymUpload for --testing usage"""
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500191 cmd = ['sym_upload', sym_item.sym_file, upload_url]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400192 # Randomly fail 80% of the time (the retry logic makes this 80%/3 per file).
193 returncode = random.randint(1, 100) <= 80
194 cros_build_lib.Debug('would run (and return %i): %s', returncode,
Matt Tennant7feda352013-12-20 14:03:40 -0800195 cros_build_lib.CmdToStr(cmd))
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400196 if returncode:
197 output = 'Failed to send the symbol file.'
198 else:
199 output = 'Successfully sent the symbol file.'
200 result = cros_build_lib.CommandResult(cmd=cmd, error=None, output=output,
201 returncode=returncode)
202 if returncode:
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500203 exceptions = (
Mike Frysingerfd355652014-01-23 02:57:48 -0500204 socket.error('[socket.error] forced test fail'),
Mike Frysingera4fa1e82014-01-15 01:45:56 -0500205 httplib.BadStatusLine('[BadStatusLine] forced test fail'),
206 urllib2.HTTPError(upload_url, 400, '[HTTPError] forced test fail',
207 {}, None),
208 urllib2.URLError('[URLError] forced test fail'),
209 )
210 raise random.choice(exceptions)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400211 else:
212 return result
213
214
Mike Frysingereb753bf2013-11-22 16:05:35 -0500215def ErrorLimitHit(num_errors, watermark_errors):
216 """See if our error limit has been hit
217
218 Args:
219 num_errors: A multiprocessing.Value of the raw number of failures.
220 watermark_errors: A multiprocessing.Value of the current rate of failures.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500221
Mike Frysingereb753bf2013-11-22 16:05:35 -0500222 Returns:
223 True if our error limits have been exceeded.
224 """
225 return ((num_errors is not None and
226 num_errors.value > MAX_TOTAL_ERRORS_FOR_RETRY) or
227 (watermark_errors is not None and
228 watermark_errors.value > ERROR_WATERMARK))
229
230
231def _UpdateCounter(counter, adj):
232 """Update |counter| by |adj|
233
234 Handle atomic updates of |counter|. Also make sure it does not
235 fall below 0.
236
237 Args:
238 counter: A multiprocessing.Value to update
239 adj: The value to add to |counter|
240 """
241 def _Update():
242 clamp = 0 if type(adj) is int else 0.0
243 counter.value = max(clamp, counter.value + adj)
244
245 if hasattr(counter, 'get_lock'):
246 with counter.get_lock():
247 _Update()
248 elif counter is not None:
249 _Update()
250
251
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500252def UploadSymbol(upload_url, sym_item, file_limit=DEFAULT_FILE_LIMIT,
Mike Frysinger02e92402013-11-22 16:22:02 -0500253 sleep=0, num_errors=None, watermark_errors=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500254 failed_queue=None, passed_queue=None):
255 """Upload |sym_item| to |upload_url|
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400256
257 Args:
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400258 upload_url: The crash server to upload things to
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500259 sym_item: A SymbolItem containing the path to the breakpad symbol to upload
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400260 file_limit: The max file size of a symbol file before we try to strip it
261 sleep: Number of seconds to sleep before running
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400262 num_errors: An object to update with the error count (needs a .value member)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500263 watermark_errors: An object to track current error behavior (needs a .value)
Mike Frysinger02e92402013-11-22 16:22:02 -0500264 failed_queue: When a symbol fails, add it to this queue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500265 passed_queue: When a symbol passes, add it to this queue
Mike Frysinger1a736a82013-12-12 01:50:59 -0500266
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400267 Returns:
268 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400269 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500270 sym_file = sym_item.sym_file
271 upload_item = sym_item
272
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400273 if num_errors is None:
274 num_errors = ctypes.c_int()
Mike Frysingereb753bf2013-11-22 16:05:35 -0500275 if ErrorLimitHit(num_errors, watermark_errors):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400276 # Abandon ship! It's on fire! NOoooooooooooOOOoooooo.
Mike Frysinger7f9be142014-01-15 02:16:42 -0500277 if failed_queue:
278 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400279 return 0
280
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400281 if sleep:
282 # Keeps us from DoS-ing the symbol server.
283 time.sleep(sleep)
284
285 cros_build_lib.Debug('uploading %s' % sym_file)
286
287 # Ideally there'd be a tempfile.SpooledNamedTemporaryFile that we could use.
288 with tempfile.NamedTemporaryFile(prefix='upload_symbols',
289 bufsize=0) as temp_sym_file:
290 if file_limit:
291 # If the symbols size is too big, strip out the call frame info. The CFI
292 # is unnecessary for 32bit x86 targets where the frame pointer is used (as
293 # all of ours have) and it accounts for over half the size of the symbols
294 # uploaded.
295 file_size = os.path.getsize(sym_file)
296 if file_size > file_limit:
297 cros_build_lib.Warning('stripping CFI from %s due to size %s > %s',
298 sym_file, file_size, file_limit)
299 temp_sym_file.writelines([x for x in open(sym_file, 'rb').readlines()
300 if not x.startswith('STACK CFI')])
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500301
302 upload_item = FakeItem(sym_file=temp_sym_file.name,
303 sym_header=sym_item.sym_header)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400304
305 # Hopefully the crash server will let it through. But it probably won't.
306 # Not sure what the best answer is in this case.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500307 file_size = os.path.getsize(upload_item.sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400308 if file_size > CRASH_SERVER_FILE_LIMIT:
309 cros_build_lib.PrintBuildbotStepWarnings()
Mike Frysinger02e92402013-11-22 16:22:02 -0500310 cros_build_lib.Warning('upload file %s is awfully large, risking '
311 'rejection by the symbol server (%s > %s)',
312 sym_file, file_size, CRASH_SERVER_FILE_LIMIT)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400313
314 # Upload the symbol file.
Mike Frysingereb753bf2013-11-22 16:05:35 -0500315 success = False
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400316 try:
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400317 cros_build_lib.TimedCommand(
David Jamesc93e6a4d2014-01-13 11:37:36 -0800318 retry_util.RetryException,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400319 (urllib2.HTTPError, urllib2.URLError), MAX_RETRIES, SymUpload,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500320 upload_url, upload_item, sleep=INITIAL_RETRY_DELAY,
Mike Frysinger9adcfd22013-10-24 12:01:40 -0400321 timed_log_msg='upload of %10i bytes took %%s: %s' %
322 (file_size, os.path.basename(sym_file)))
Mike Frysingereb753bf2013-11-22 16:05:35 -0500323 success = True
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500324
325 if passed_queue:
326 passed_queue.put(sym_item)
Mike Frysinger094a2172013-08-14 12:54:35 -0400327 except urllib2.HTTPError as e:
328 cros_build_lib.Warning('could not upload: %s: HTTP %s: %s',
329 os.path.basename(sym_file), e.code, e.reason)
Mike Frysingerfd355652014-01-23 02:57:48 -0500330 except (urllib2.URLError, httplib.HTTPException, socket.error) as e:
Mike Frysingerc4ab5782013-10-02 18:14:22 -0400331 cros_build_lib.Warning('could not upload: %s: %s',
332 os.path.basename(sym_file), e)
Mike Frysingereb753bf2013-11-22 16:05:35 -0500333 finally:
334 if success:
335 _UpdateCounter(watermark_errors, ERROR_ADJUST_PASS)
336 else:
337 _UpdateCounter(num_errors, 1)
338 _UpdateCounter(watermark_errors, ERROR_ADJUST_FAIL)
Mike Frysinger02e92402013-11-22 16:22:02 -0500339 if failed_queue:
340 failed_queue.put(sym_file)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400341
342 return num_errors.value
343
344
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500345# A dummy class that allows for stubbing in tests and SymUpload.
346FakeItem = cros_build_lib.Collection(
347 'FakeItem', sym_file=None, sym_header=None, content=lambda x: '')
348
349
350# TODO(build): Delete this if check. http://crbug.com/341152
351if isolateserver:
352 class SymbolItem(isolateserver.BufferItem):
353 """Turn a sym_file into an isolateserver.Item"""
354
355 ALGO = hashlib.sha1
356
357 def __init__(self, sym_file):
358 sym_header = cros_generate_breakpad_symbols.ReadSymsHeader(sym_file)
359 super(SymbolItem, self).__init__(str(sym_header), self.ALGO)
360 self.sym_header = sym_header
361 self.sym_file = sym_file
362
363
364def SymbolDeduplicatorNotify(dedupe_namespace, dedupe_queue):
365 """Send a symbol file to the swarming service
366
367 Notify the swarming service of a successful upload. If the notification fails
368 for any reason, we ignore it. We don't care as it just means we'll upload it
369 again later on, and the symbol server will handle that graciously.
370
371 This func runs in a different process from the main one, so we cannot share
372 the storage object. Instead, we create our own. This func stays alive for
373 the life of the process, so we only create one here overall.
374
375 Args:
376 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
377 dedupe_queue: The queue to read SymbolItems from
378 """
379 if dedupe_queue is None:
380 return
381
382 item = None
383 try:
Mike Frysinger650e6722014-04-28 18:29:15 -0400384 with timeout_util.Timeout(DEDUPE_TIMEOUT):
385 storage = isolateserver.get_storage_api(constants.ISOLATESERVER,
386 dedupe_namespace)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500387 for item in iter(dedupe_queue.get, None):
388 with timeout_util.Timeout(DEDUPE_TIMEOUT):
Mike Frysingerefef3672014-04-20 10:06:45 -0400389 cros_build_lib.Debug('sending %s to dedupe server', item.sym_file)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500390 storage.push(item, item.content(0))
Mike Frysinger66e51e92014-05-03 16:52:00 -0400391 cros_build_lib.Debug('sent %s', item.sym_file)
Mike Frysingerae298452014-03-24 22:45:23 -0400392 cros_build_lib.Info('dedupe notification finished; exiting')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500393 except Exception:
394 sym_file = item.sym_file if (item and item.sym_file) else ''
395 cros_build_lib.Warning('posting %s to dedupe server failed',
396 os.path.basename(sym_file), exc_info=True)
397
Mike Frysinger58312e92014-03-18 04:18:36 -0400398 # Keep draining the queue though so it doesn't fill up.
399 while dedupe_queue.get() is not None:
400 continue
401
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500402
403def SymbolDeduplicator(storage, sym_paths):
404 """Filter out symbol files that we've already uploaded
405
406 Using the swarming service, ask it to tell us which symbol files we've already
407 uploaded in previous runs and/or by other bots. If the query fails for any
408 reason, we'll just upload all symbols. This is fine as the symbol server will
409 do the right thing and this phase is purely an optimization.
410
411 This code runs in the main thread which is why we can re-use the existing
412 storage object. Saves us from having to recreate one all the time.
413
414 Args:
415 storage: An isolateserver.StorageApi object
416 sym_paths: List of symbol files to check against the dedupe server
417
418 Returns:
419 List of symbol files that have not been uploaded before
420 """
421 if not sym_paths:
422 return sym_paths
423
424 items = [SymbolItem(x) for x in sym_paths]
425 if storage:
426 try:
427 with timeout_util.Timeout(DEDUPE_TIMEOUT):
428 items = storage.contains(items)
429 except Exception:
430 cros_build_lib.Warning('talking to dedupe server failed', exc_info=True)
431
432 return items
433
434
Mike Frysingerd41938e2014-02-10 06:37:55 -0500435def IsTarball(path):
436 """Guess if this is a tarball based on the filename."""
437 parts = path.split('.')
438 if len(parts) <= 1:
439 return False
440
441 if parts[-1] == 'tar':
442 return True
443
444 if parts[-2] == 'tar':
445 return parts[-1] in ('bz2', 'gz', 'xz')
446
447 return parts[-1] in ('tbz2', 'tbz', 'tgz', 'txz')
448
449
450def SymbolFinder(tempdir, paths):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500451 """Locate symbol files in |paths|
452
453 Args:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500454 tempdir: Path to use for temporary files (caller will clean up).
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500455 paths: A list of input paths to walk. Files are returned w/out any checks.
Mike Frysingerd41938e2014-02-10 06:37:55 -0500456 Dirs are searched for files that end in ".sym". Urls are fetched and then
457 processed. Tarballs are unpacked and walked.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500458
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500459 Returns:
460 Yield every viable sym file.
461 """
462 for p in paths:
Don Garrett25f309a2014-03-19 14:02:12 -0700463 # Pylint is confused about members of ParseResult.
Don Garrettf8bf7842014-03-20 17:03:42 -0700464
Mike Frysingerd41938e2014-02-10 06:37:55 -0500465 o = urlparse.urlparse(p)
Don Garrettf8bf7842014-03-20 17:03:42 -0700466 if o.scheme: # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500467 # Support globs of filenames.
468 ctx = gs.GSContext()
469 for p in ctx.LS(p):
470 cros_build_lib.Info('processing files inside %s', p)
471 o = urlparse.urlparse(p)
472 cache_dir = commandline.GetCacheDir()
473 common_path = os.path.join(cache_dir, constants.COMMON_CACHE)
474 tar_cache = cache.TarballCache(common_path)
Don Garrettf8bf7842014-03-20 17:03:42 -0700475 key = ('%s%s' % (o.netloc, o.path)).split('/') # pylint: disable=E1101
Mike Frysingerd41938e2014-02-10 06:37:55 -0500476 # The common cache will not be LRU, removing the need to hold a read
477 # lock on the cached gsutil.
478 ref = tar_cache.Lookup(key)
479 try:
480 ref.SetDefault(p)
481 except cros_build_lib.RunCommandError as e:
482 cros_build_lib.Warning('ignoring %s\n%s', p, e)
483 continue
484 for p in SymbolFinder(tempdir, [ref.path]):
485 yield p
486
487 elif os.path.isdir(p):
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500488 for root, _, files in os.walk(p):
489 for f in files:
490 if f.endswith('.sym'):
491 yield os.path.join(root, f)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500492
493 elif IsTarball(p):
494 cros_build_lib.Info('processing files inside %s', p)
495 tardir = tempfile.mkdtemp(dir=tempdir)
496 cache.Untar(os.path.realpath(p), tardir)
497 for p in SymbolFinder(tardir, [tardir]):
498 yield p
499
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500500 else:
501 yield p
502
503
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500504def WriteQueueToFile(listing, queue, relpath=None):
505 """Write all the items in |queue| to the |listing|.
506
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500507 Note: The queue must have a sentinel None appended to the end.
508
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500509 Args:
510 listing: Where to write out the list of files.
511 queue: The queue of paths to drain.
512 relpath: If set, write out paths relative to this one.
513 """
514 if not listing:
Mike Frysingera0ddac62014-03-14 10:30:25 -0400515 # Still drain the queue so we make sure the producer has finished
516 # before we return. Otherwise, the queue might get destroyed too
517 # quickly which will trigger a traceback in the producer.
518 while queue.get() is not None:
519 continue
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500520 return
521
522 with cros_build_lib.Open(listing, 'wb+') as f:
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500523 while True:
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500524 path = queue.get()
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500525 if path is None:
526 return
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500527 if relpath:
528 path = os.path.relpath(path, relpath)
529 f.write('%s\n' % path)
530
531
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400532def UploadSymbols(board=None, official=False, breakpad_dir=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400533 file_limit=DEFAULT_FILE_LIMIT, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500534 upload_limit=None, sym_paths=None, failed_list=None,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500535 root=None, retry=True, dedupe_namespace=None):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400536 """Upload all the generated symbols for |board| to the crash server
537
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400538 You can use in a few ways:
539 * pass |board| to locate all of its symbols
540 * pass |breakpad_dir| to upload all the symbols in there
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500541 * pass |sym_paths| to upload specific symbols (or dirs of symbols)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400542
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400543 Args:
544 board: The board whose symbols we wish to upload
545 official: Use the official symbol server rather than the staging one
546 breakpad_dir: The full path to the breakpad directory where symbols live
547 file_limit: The max file size of a symbol file before we try to strip it
548 sleep: How long to sleep in between uploads
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500549 upload_limit: If set, only upload this many symbols (meant for testing)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500550 sym_paths: Specific symbol files (or dirs of sym files) to upload,
551 otherwise search |breakpad_dir|
Mike Frysinger7f9be142014-01-15 02:16:42 -0500552 failed_list: Write the names of all sym files we did not upload; can be a
553 filename or file-like object.
Mike Frysinger118d2502013-08-19 03:36:56 -0400554 root: The tree to prefix to |breakpad_dir| (if |breakpad_dir| is not set)
Mike Frysinger02e92402013-11-22 16:22:02 -0500555 retry: Whether we should retry failures.
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500556 dedupe_namespace: The isolateserver namespace to dedupe uploaded symbols.
Mike Frysinger1a736a82013-12-12 01:50:59 -0500557
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400558 Returns:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400559 The number of errors that were encountered.
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400560 """
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500561 # TODO(build): Delete this assert.
562 assert isolateserver, 'Missing isolateserver import http://crbug.com/341152'
563
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400564 if official:
565 upload_url = OFFICIAL_UPLOAD_URL
566 else:
567 cros_build_lib.Warning('unofficial builds upload to the staging server')
568 upload_url = STAGING_UPLOAD_URL
569
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500570 if sym_paths:
571 cros_build_lib.Info('uploading specified symbols to %s', upload_url)
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400572 else:
573 if breakpad_dir is None:
Mike Frysinger118d2502013-08-19 03:36:56 -0400574 breakpad_dir = os.path.join(
575 root,
576 cros_generate_breakpad_symbols.FindBreakpadDir(board).lstrip('/'))
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400577 cros_build_lib.Info('uploading all symbols to %s from %s', upload_url,
578 breakpad_dir)
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500579 sym_paths = [breakpad_dir]
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400580
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500581 # We use storage_query to ask the server about existing symbols. The
582 # storage_notify_proc process is used to post updates to the server. We
583 # cannot safely share the storage object between threads/processes, but
584 # we also want to minimize creating new ones as each object has to init
585 # new state (like server connections).
Mike Frysinger650e6722014-04-28 18:29:15 -0400586 storage_query = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500587 if dedupe_namespace:
588 dedupe_limit = DEDUPE_LIMIT
589 dedupe_queue = multiprocessing.Queue()
Mike Frysinger650e6722014-04-28 18:29:15 -0400590 try:
591 with timeout_util.Timeout(DEDUPE_TIMEOUT):
592 storage_query = isolateserver.get_storage_api(constants.ISOLATESERVER,
593 dedupe_namespace)
594 except Exception:
595 cros_build_lib.Warning('initializing dedupe server connection failed',
596 exc_info=True)
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500597 else:
598 dedupe_limit = 1
Mike Frysinger650e6722014-04-28 18:29:15 -0400599 dedupe_queue = None
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500600 # Can't use parallel.BackgroundTaskRunner because that'll create multiple
601 # processes and we want only one the whole time (see comment above).
602 storage_notify_proc = multiprocessing.Process(
603 target=SymbolDeduplicatorNotify, args=(dedupe_namespace, dedupe_queue))
604
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400605 bg_errors = multiprocessing.Value('i')
Mike Frysingereb753bf2013-11-22 16:05:35 -0500606 watermark_errors = multiprocessing.Value('f')
Mike Frysinger02e92402013-11-22 16:22:02 -0500607 failed_queue = multiprocessing.Queue()
608 uploader = functools.partial(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500609 UploadSymbol, upload_url, file_limit=file_limit, sleep=sleep,
610 num_errors=bg_errors, watermark_errors=watermark_errors,
611 failed_queue=failed_queue, passed_queue=dedupe_queue)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400612
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500613 start_time = datetime.datetime.now()
614 Counters = cros_build_lib.Collection(
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500615 'Counters', upload_limit=upload_limit, uploaded_count=0, deduped_count=0)
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500616 counters = Counters()
617
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500618 def _Upload(queue, counters, files):
619 if not files:
620 return
621
622 missing_count = 0
623 for item in SymbolDeduplicator(storage_query, files):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500624 missing_count += 1
Mike Frysingerd42e5f02014-03-14 11:19:37 -0400625
626 if counters.upload_limit == 0:
627 continue
628
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500629 queue.put((item,))
630 counters.uploaded_count += 1
631 if counters.upload_limit is not None:
632 counters.upload_limit -= 1
633
634 counters.deduped_count += (len(files) - missing_count)
635
Mike Frysinger13870082014-03-14 10:41:20 -0400636 try:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500637 storage_notify_proc.start()
Mike Frysinger02e92402013-11-22 16:22:02 -0500638
Mike Frysinger13870082014-03-14 10:41:20 -0400639 with osutils.TempDir(prefix='upload_symbols.') as tempdir:
640 # For the first run, we collect the symbols that failed. If the
641 # overall failure rate was low, we'll retry them on the second run.
642 for retry in (retry, False):
643 # We need to limit ourselves to one upload at a time to avoid the server
644 # kicking in DoS protection. See these bugs for more details:
645 # http://crbug.com/209442
646 # http://crbug.com/212496
647 with parallel.BackgroundTaskRunner(uploader, processes=1) as queue:
Mike Frysingerd41938e2014-02-10 06:37:55 -0500648 dedupe_list = []
Mike Frysinger13870082014-03-14 10:41:20 -0400649 for sym_file in SymbolFinder(tempdir, sym_paths):
650 dedupe_list.append(sym_file)
651 dedupe_len = len(dedupe_list)
652 if dedupe_len < dedupe_limit:
653 if (counters.upload_limit is None or
654 dedupe_len < counters.upload_limit):
655 continue
Mike Frysinger02e92402013-11-22 16:22:02 -0500656
Mike Frysinger1010a892014-03-14 11:24:17 -0400657 # We check the counter before _Upload so that we don't keep talking
658 # to the dedupe server. Otherwise, we end up sending one symbol at
659 # a time to it and that slows things down a lot.
660 if counters.upload_limit == 0:
661 break
662
Mike Frysinger13870082014-03-14 10:41:20 -0400663 _Upload(queue, counters, dedupe_list)
664 dedupe_list = []
665 _Upload(queue, counters, dedupe_list)
Mike Frysingerd41938e2014-02-10 06:37:55 -0500666
Mike Frysinger13870082014-03-14 10:41:20 -0400667 # See if we need to retry, and if we haven't failed too many times yet.
668 if not retry or ErrorLimitHit(bg_errors, watermark_errors):
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500669 break
Mike Frysinger5e6dd712014-03-07 22:21:17 -0500670
Mike Frysinger13870082014-03-14 10:41:20 -0400671 sym_paths = []
672 failed_queue.put(None)
673 while True:
674 sym_path = failed_queue.get()
675 if sym_path is None:
676 break
677 sym_paths.append(sym_path)
Mike Frysinger02e92402013-11-22 16:22:02 -0500678
Mike Frysinger13870082014-03-14 10:41:20 -0400679 if sym_paths:
680 cros_build_lib.Warning('retrying %i symbols', len(sym_paths))
681 if counters.upload_limit is not None:
682 counters.upload_limit += len(sym_paths)
683 # Decrement the error count in case we recover in the second pass.
684 assert bg_errors.value >= len(sym_paths), \
685 'more failed files than errors?'
686 bg_errors.value -= len(sym_paths)
687 else:
688 # No failed symbols, so just return now.
689 break
Mike Frysinger7f9be142014-01-15 02:16:42 -0500690
Mike Frysinger13870082014-03-14 10:41:20 -0400691 # If the user has requested it, save all the symbol files that we failed to
692 # upload to a listing file. This should help with recovery efforts later.
693 failed_queue.put(None)
694 WriteQueueToFile(failed_list, failed_queue, breakpad_dir)
695
696 finally:
Mike Frysingerae298452014-03-24 22:45:23 -0400697 cros_build_lib.Info('finished uploading; joining background process')
Mike Frysinger13870082014-03-14 10:41:20 -0400698 if dedupe_queue:
699 dedupe_queue.put(None)
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400700
701 # The notification might be slow going, so give it some time to finish.
702 # We have to poll here as the process monitor is watching for output and
703 # will kill us if we go silent for too long.
704 wait_minutes = DEDUPE_NOTIFY_TIMEOUT
705 while storage_notify_proc.is_alive() and wait_minutes > 0:
706 cros_build_lib.Info('waiting up to %i minutes for ~%i notifications',
707 wait_minutes, dedupe_queue.qsize())
708 storage_notify_proc.join(60)
709 wait_minutes -= 1
710
711 # The process is taking too long, so kill it and complain.
712 if storage_notify_proc.is_alive():
Mike Frysinger4dd462e2014-04-30 16:21:51 -0400713 cros_build_lib.Warning('notification process took too long')
714 cros_build_lib.PrintBuildbotStepWarnings()
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500715
Mike Frysinger66e51e92014-05-03 16:52:00 -0400716 # Kill it gracefully first (traceback) before tacking it down harder.
717 pid = storage_notify_proc.pid
718 for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGKILL):
719 cros_build_lib.Warning('sending %s to %i', signals.StrSignal(sig), pid)
720 os.kill(pid, sig)
721 time.sleep(5)
722 if storage_notify_proc.is_alive():
723 break
724
725 # Drain the queue so we don't hang when we finish.
726 try:
727 while dedupe_queue.get_nowait():
728 pass
729 except Queue.Empty:
730 pass
731
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500732 cros_build_lib.Info('uploaded %i symbols (%i were deduped) which took: %s',
733 counters.uploaded_count, counters.deduped_count,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500734 datetime.datetime.now() - start_time)
735
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500736 return bg_errors.value
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400737
738
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400739def main(argv):
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500740 # TODO(build): Delete this assert.
741 assert isolateserver, 'Missing isolateserver import http://crbug.com/341152'
742
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400743 parser = commandline.ArgumentParser(description=__doc__)
744
Mike Frysingerd41938e2014-02-10 06:37:55 -0500745 parser.add_argument('sym_paths', type='path_or_uri', nargs='*', default=None,
746 help='symbol file or directory or URL or tarball')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400747 parser.add_argument('--board', default=None,
748 help='board to build packages for')
749 parser.add_argument('--breakpad_root', type='path', default=None,
750 help='root directory for breakpad symbols')
751 parser.add_argument('--official_build', action='store_true', default=False,
752 help='point to official symbol server')
753 parser.add_argument('--regenerate', action='store_true', default=False,
754 help='regenerate all symbols')
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500755 parser.add_argument('--upload-limit', type=int, default=None,
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400756 help='only upload # number of symbols')
757 parser.add_argument('--strip_cfi', type=int,
758 default=CRASH_SERVER_FILE_LIMIT - (10 * 1024 * 1024),
759 help='strip CFI data for files above this size')
Mike Frysinger7f9be142014-01-15 02:16:42 -0500760 parser.add_argument('--failed-list', type='path',
761 help='where to save a list of failed symbols')
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500762 parser.add_argument('--dedupe', action='store_true', default=False,
763 help='use the swarming service to avoid re-uploading')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400764 parser.add_argument('--testing', action='store_true', default=False,
765 help='run in testing mode')
766 parser.add_argument('--yes', action='store_true', default=False,
767 help='answer yes to all prompts')
768
769 opts = parser.parse_args(argv)
Mike Frysinger90e49ca2014-01-14 14:42:07 -0500770 opts.Freeze()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400771
Mike Frysinger9b2ff5c2013-11-22 10:01:12 -0500772 if opts.sym_paths:
Mike Frysinger9dcf9ae2013-08-10 15:17:09 -0400773 if opts.regenerate:
774 cros_build_lib.Die('--regenerate may not be used with specific files')
775 else:
776 if opts.board is None:
777 cros_build_lib.Die('--board is required')
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400778
779 if opts.breakpad_root and opts.regenerate:
780 cros_build_lib.Die('--regenerate may not be used with --breakpad_root')
781
782 if opts.testing:
783 # TODO(build): Kill off --testing mode once unittests are up-to-snuff.
784 cros_build_lib.Info('running in testing mode')
785 # pylint: disable=W0601,W0603
786 global INITIAL_RETRY_DELAY, SymUpload, DEFAULT_SLEEP_DELAY
787 INITIAL_RETRY_DELAY = DEFAULT_SLEEP_DELAY = 0
788 SymUpload = TestingSymUpload
789
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500790 dedupe_namespace = None
791 if opts.dedupe:
792 if opts.official_build and not opts.testing:
793 dedupe_namespace = OFFICIAL_DEDUPE_NAMESPACE
794 else:
795 dedupe_namespace = STAGING_DEDUPE_NAMESPACE
796
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400797 if not opts.yes:
Mike Frysingerc5de9602014-02-09 02:42:36 -0500798 prolog = '\n'.join(textwrap.wrap(textwrap.dedent("""
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400799 Uploading symbols for an entire Chromium OS build is really only
800 necessary for release builds and in a few cases for developers
801 to debug problems. It will take considerable time to run. For
802 developer debugging purposes, consider instead passing specific
803 files to upload.
Mike Frysingerc5de9602014-02-09 02:42:36 -0500804 """), 80)).strip()
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400805 if not cros_build_lib.BooleanPrompt(
806 prompt='Are you sure you want to upload all build symbols',
Mike Frysingerc5de9602014-02-09 02:42:36 -0500807 default=False, prolog=prolog):
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400808 cros_build_lib.Die('better safe than sorry')
809
810 ret = 0
811 if opts.regenerate:
Mike Frysinger69cb41d2013-08-11 20:08:19 -0400812 ret += cros_generate_breakpad_symbols.GenerateBreakpadSymbols(
813 opts.board, breakpad_dir=opts.breakpad_root)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400814
815 ret += UploadSymbols(opts.board, official=opts.official_build,
816 breakpad_dir=opts.breakpad_root,
817 file_limit=opts.strip_cfi, sleep=DEFAULT_SLEEP_DELAY,
Mike Frysinger8ec8c502014-02-10 00:19:13 -0500818 upload_limit=opts.upload_limit, sym_paths=opts.sym_paths,
Mike Frysinger0c0efa22014-02-09 23:32:23 -0500819 failed_list=opts.failed_list,
820 dedupe_namespace=dedupe_namespace)
Mike Frysingerd5fcb3a2013-05-30 21:10:50 -0400821 if ret:
822 cros_build_lib.Error('encountered %i problem(s)', ret)
823 # Since exit(status) gets masked, clamp it to 1 so we don't inadvertently
824 # return 0 in case we are a multiple of the mask.
825 ret = 1
826
827 return ret
Mike Frysinger094a2172013-08-14 12:54:35 -0400828
829
830# We need this to run once per process. Do it at module import time as that
831# will let us avoid doing it inline at function call time (see SymUpload) as
832# that func might be called by the multiprocessing module which means we'll
833# do the opener logic multiple times overall. Plus, if you're importing this
834# module, it's a pretty good chance that you're going to need this.
835poster.streaminghttp.register_openers()