def test_set_recorded_to_timeline(self): request = get_current_browser_request() timeline = get_request_timeline(request) self.client.set('foo', 'bar') action = timeline.actions[-1] self.assertEqual('memcache-set', action.category) self.assertEqual('foo', action.detail)
def raw_sendmail(from_addr, to_addrs, raw_message, message_detail): """Send a raw RFC8222 email message. All headers and encoding should already be done, as the message is spooled out verbatim to the delivery agent. You should not need to call this method directly, although it may be necessary to pass on signed or encrypted messages. Returns the message-id. :param message_detail: String of detail about the message to be recorded to help with debugging, eg the message subject. """ assert not isinstance(to_addrs, basestring), 'to_addrs must be a sequence' assert isinstance(raw_message, str), 'Not a plain string' assert raw_message.decode('ascii'), 'Not ASCII - badly encoded message' mailer = getUtility(IMailDelivery, 'Mail') request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start("sendmail", message_detail) try: return mailer.send(from_addr, to_addrs, raw_message) finally: action.finish()
def _getPubKey(self, fingerprint): """See IGPGHandler for further information.""" request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start('retrieving GPG key', 'Fingerprint: %s' % fingerprint) try: return self._grabPage('get', fingerprint) # We record an OOPS for most errors: If the keyserver does not # respond, callsites should show users an error message like # "sorry, the keyserver is not responding, try again in a few # minutes." The details of the error do not matter for users # (and for the code in callsites), but we should be able to see # if this problem occurs too often. except urllib2.HTTPError as exc: # Old versions of SKS return a 500 error when queried for a # non-existent key. Production was upgraded in 2013/01, but # let's leave this here for a while. # # We can extract the fact that the key is unknown by looking # into the response's content. if exc.code in (404, 500) and exc.fp is not None: content = exc.fp.read() no_key_message = 'No results found: No keys found' if content.find(no_key_message) >= 0: raise GPGKeyDoesNotExistOnServer(fingerprint) errorlog.globalErrorUtility.raising(sys.exc_info(), request) raise GPGKeyTemporarilyNotFoundError(fingerprint) except (TimeoutError, urllib2.URLError) as exc: errorlog.globalErrorUtility.raising(sys.exc_info(), request) raise GPGKeyTemporarilyNotFoundError(fingerprint) finally: action.finish()
def search(self, terms, start=0): """See `ISearchService`. The config.google.client_id is used as Google client-id in the search request. Search returns 20 or fewer results for each query. For terms that match more than 20 results, the start param can be used over multiple queries to get successive sets of results. :return: `ISearchResults` (PageMatches). :raise: `GoogleWrongGSPVersion` if the xml cannot be parsed. """ search_url = self.create_search_url(terms, start=start) from lp.services.timeout import urlfetch request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start("google-search-api", search_url) try: gsp_xml = urlfetch(search_url) except (TimeoutError, urllib2.HTTPError, urllib2.URLError) as error: # Google search service errors are not code errors. Let the # call site choose to handle the unavailable service. raise GoogleResponseError("The response errored: %s" % str(error)) finally: action.finish() page_matches = self._parse_google_search_protocol(gsp_xml) return page_matches
def test_product_licenses_modified_licenses_other_open_source(self): product, event = self.make_product_event([License.OTHER_OPEN_SOURCE]) product_licenses_modified(product, event) notifications = pop_notifications() self.assertEqual(1, len(notifications)) request = get_current_browser_request() self.assertEqual(0, len(request.response.notifications))
def test_product_licenses_modified_licenses_other_dont_know(self): product, event = self.make_product_event([License.DONT_KNOW]) product_licenses_modified(product, event) notifications = pop_notifications() self.assertEqual(1, len(notifications)) request = get_current_browser_request() self.assertEqual(0, len(request.response.notifications))
def test_product_licenses_modified_licenses_other_proprietary(self): product, event = self.make_product_event([License.OTHER_PROPRIETARY]) product_licenses_modified(product, event) notifications = pop_notifications() self.assertEqual(1, len(notifications)) request = get_current_browser_request() self.assertEqual(1, len(request.response.notifications))
def test_product_licenses_modified_licenses_common_license(self): product, event = self.make_product_event([License.MIT]) product_licenses_modified(product, event) notifications = pop_notifications() self.assertEqual(0, len(notifications)) request = get_current_browser_request() self.assertEqual(0, len(request.response.notifications))
def _getTimeline(): # XXX cjwatson 2016-06-29: This can be simplified once jobs have # timeline support. request = get_current_browser_request() if request is None: return None return get_request_timeline(request)
def test_correct_value_is_cached(self): login(ANONYMOUS) menu = TestMenu(object()) link = menu._get_link('test_link') request = get_current_browser_request() cache = request.annotations.get(MENU_ANNOTATION_KEY) self.assertEquals([link], cache.values())
def search(self, terms, start=0): """See `ISearchService`. The config.google.client_id is used as Google client-id in the search request. Search returns 20 or fewer results for each query. For terms that match more than 20 results, the start param can be used over multiple queries to get successive sets of results. :return: `ISearchResults` (PageMatches). :raise: `GoogleWrongGSPVersion` if the xml cannot be parsed. """ search_url = self.create_search_url(terms, start=start) from lp.services.timeout import urlfetch request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start("google-search-api", search_url) try: gsp_xml = urlfetch(search_url) except (TimeoutError, urllib2.HTTPError, urllib2.URLError) as error: # Google search service errors are not code errors. Let the # call site choose to handle the unavailable service. raise GoogleResponseError( "The response errored: %s" % str(error)) finally: action.finish() page_matches = self._parse_google_search_protocol(gsp_xml) return page_matches
def search(self, terms, start=0): """See `ISearchService`. The `subscription_key` and `custom_config_id` are used in the search request. Search returns 20 or fewer results for each query. For terms that match more than 20 results, the start param can be used over multiple queries to get successive sets of results. :return: `ISearchResults` (PageMatches). :raise: `SiteSearchResponseError` if the json response is incomplete or cannot be parsed. """ search_url = self.create_search_url(terms, start=start) search_headers = self.create_search_headers() request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start("bing-search-api", search_url) try: response = urlfetch( search_url, headers=search_headers, use_proxy=True) except (TimeoutError, requests.RequestException) as error: raise SiteSearchResponseError( "The response errored: %s" % str(error)) finally: action.finish() page_matches = self._parse_search_response(response.content, start) return page_matches
def test_stderr(self): with override_environ(LP_DEBUG_SQL='1'): tracer = da.LaunchpadStatementTracer() with person_logged_in(self.person): with stderr() as file: tracer.connection_raw_execute( self.connection, None, 'SELECT * FROM bar WHERE bing = 42', ()) timeline = get_request_timeline(get_current_browser_request()) action = timeline.actions[-1] self.assertEqual('SELECT * FROM bar WHERE bing = 42', action.detail) self.assertEqual('SQL-stub-database', action.category) self.assertIs(None, action.duration) # Now we change the detail to verify that the action is the # source of the final log. action.detail = 'SELECT * FROM surprise' tracer.connection_raw_execute_success( self.connection, None, 'SELECT * FROM bar WHERE bing = 42', ()) self.assertIsNot(None, action.duration) self.assertEndsWith( file.getvalue(), '@SQL-stub-database SELECT * FROM surprise\n' + "-" * 70 + "\n")
def _getPubKey(self, fingerprint): """See IGPGHandler for further information.""" request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start( 'retrieving GPG key', 'Fingerprint: %s' % fingerprint) try: return self._grabPage('get', fingerprint) # We record an OOPS for most errors: If the keyserver does not # respond, callsites should show users an error message like # "sorry, the keyserver is not responding, try again in a few # minutes." The details of the error do not matter for users # (and for the code in callsites), but we should be able to see # if this problem occurs too often. except urllib2.HTTPError as exc: # Old versions of SKS return a 500 error when queried for a # non-existent key. Production was upgraded in 2013/01, but # let's leave this here for a while. # # We can extract the fact that the key is unknown by looking # into the response's content. if exc.code in (404, 500) and exc.fp is not None: content = exc.fp.read() no_key_message = 'No results found: No keys found' if content.find(no_key_message) >= 0: raise GPGKeyDoesNotExistOnServer(fingerprint) errorlog.globalErrorUtility.raising(sys.exc_info(), request) raise GPGKeyTemporarilyNotFoundError(fingerprint) except (TimeoutError, urllib2.URLError) as exc: errorlog.globalErrorUtility.raising(sys.exc_info(), request) raise GPGKeyTemporarilyNotFoundError(fingerprint) finally: action.finish()
def set_request_started( starttime=None, request_statements=None, txn=None, enable_timeout=True): """Set the start time for the request being served by the current thread. :param start_time: The start time of the request. If given, it is used as the start time for the request, as returned by time(). If it is not given, the current time is used. :param request_statements; The sequence used to store the logged SQL statements. :type request_statements: mutable sequence. :param txn: The current transaction manager. If given, txn.commit() and txn.abort() calls are logged too. :param enable_timeout: If True, a timeout error is raised if the request runs for a longer time than the configured timeout. """ if getattr(_local, 'request_start_time', None) is not None: warnings.warn('set_request_started() called before previous request ' 'finished', stacklevel=1) if starttime is None: starttime = time() _local.request_start_time = starttime request = get_current_browser_request() if request_statements is not None: # Specify a specific sequence object for the timeline. set_request_timeline(request, Timeline(request_statements)) else: # Ensure a timeline is created, so that time offset for actions is # reasonable. set_request_timeline(request, Timeline()) _local.current_statement_timeout = None _local.enable_timeout = enable_timeout _local.commit_logger = CommitLogger(transaction) transaction.manager.registerSynch(_local.commit_logger)
def test_correct_value_is_cached(self): login(ANONYMOUS) menu = TestMenu(object()) link = menu._get_link("test_link") request = get_current_browser_request() cache = request.annotations.get(MENU_ANNOTATION_KEY) self.assertEquals([link], cache.values())
def _request(self, method, path, **kwargs): """Make a request to the Git hosting API.""" # Fetch the current timeout before starting the timeline action, # since making a database query inside this action will result in an # OverlappingActionError. get_default_timeout_function()() timeline = get_request_timeline(get_current_browser_request()) action = timeline.start("git-hosting-%s" % method, "%s %s" % (path, json.dumps(kwargs))) try: response = urlfetch(urljoin(self.endpoint, path), method=method, **kwargs) except TimeoutError: # Re-raise this directly so that it can be handled specially by # callers. raise except requests.RequestException: raise except Exception: _, val, tb = sys.exc_info() reraise(RequestExceptionWrapper, RequestExceptionWrapper(*val.args), tb) finally: action.finish() if response.content: return response.json() else: return None
def gpgme_timeline(name, detail): request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start("gpgme-%s" % name, detail, allow_nested=True) try: yield finally: action.finish()
def test_existing_oops_stops_user_requested(self): # If there is already an existing oops id in the request, then the # user requested oops is ignored. request = get_current_browser_request() request.oopsid = "EXISTING" request.annotations[LAZR_OOPS_USER_REQUESTED_KEY] = True maybe_record_user_requested_oops() self.assertEqual("EXISTING", request.oopsid)
def test_multiple_calls(self): # Asking to record the OOPS twice just returns the same ID. request = get_current_browser_request() request.annotations[LAZR_OOPS_USER_REQUESTED_KEY] = True maybe_record_user_requested_oops() orig_oops_id = request.oopsid maybe_record_user_requested_oops() self.assertEqual(orig_oops_id, request.oopsid)
def toDataForJSON(self, media_type): """See `IJSONPublishable`.""" if media_type != "application/json": raise ValueError("Unhandled media type %s" % media_type) request = get_current_browser_request() field = InlineObject(schema=IGitNascentRuleGrant).bind(self) marshaller = getMultiAdapter((field, request), IFieldMarshaller) return marshaller.unmarshall(None, self)
def test_participation_restored(self): # test_traverse restores the interaction (and hence # participation) that was present before it was called. request = LaunchpadTestRequest() login(ANONYMOUS, request) product = self.factory.makeProduct() test_traverse('https://launchpad.dev/' + product.name) self.assertIs(request, get_current_browser_request())
def test_JsonModelNamespace_traverse_LPFormView(self): # Test traversal for JSON model namespace, # ++model++ for a non-LaunchpadView context. request = get_current_browser_request() context = object() view = LaunchpadFormView(context, request) namespace = JsonModelNamespaceView(view, request) result = namespace.traverse(view, None) self.assertEqual(result, namespace)
def test_annotation_key(self): # The request for an oops is stored in the request annotations. # If a user request oops is recorded, the oops id is stored in # the request. request = get_current_browser_request() request.annotations[LAZR_OOPS_USER_REQUESTED_KEY] = True self.assertIs(None, request.oopsid) maybe_record_user_requested_oops() self.assertIsNot(None, request.oopsid)
def translate_if_i18n(obj_or_msgid): """Translate an internationalized object, returning the result. Returns any other type of object untouched. """ if isinstance(obj_or_msgid, Message): return translate(obj_or_msgid, context=get_current_browser_request()) else: # Just text (or something unknown). return obj_or_msgid
def test_cache_key_is_unique(self): # The cache key must include the link name, the context of the link # and the class where the link is defined. login(ANONYMOUS) context = object() menu = TestMenu(context) menu._get_link("test_link") cache = get_current_browser_request().annotations.get(MENU_ANNOTATION_KEY) self.assertEquals(len(cache.keys()), 1) self.assertContentEqual(cache.keys()[0], (menu.__class__, context, "test_link"))
def assertRequest(self, url_suffix, json_data=None, method=None, **kwargs): [request] = self.requests self.assertThat(request, MatchesStructure.byEquality( url=urlappend(self.endpoint, url_suffix), method=method, **kwargs)) if json_data is not None: self.assertEqual(json_data, json.loads(request.body)) timeline = get_request_timeline(get_current_browser_request()) action = timeline.actions[-1] self.assertEqual("git-hosting-%s" % method.lower(), action.category) self.assertEqual( "/" + url_suffix.split("?", 1)[0], action.detail.split(" ", 1)[0])
def test_display_escapee_user_data(self): # A notification is added if there is a message to show. product, user = self.make_product_user([License.OTHER_PROPRIETARY]) product.display_name = '<b>Look</b>' notification = LicenseNotification(product) result = notification.display() self.assertIs(True, result) request = get_current_browser_request() self.assertEqual(1, len(request.response.notifications)) self.assertIn('<b>Look</b>', request.response.notifications[0].message)
def read(self, chunksize=None): request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start("librarian-read", self.url) try: if chunksize is None: return self.file.read() else: return self.file.read(chunksize) finally: action.finish()
def test_OopsNamespace_traverse(self): # The traverse method of the OopsNamespace sets the request # annotation, and returns the context that it was created with. request = get_current_browser_request() self.assertIs( None, request.annotations.get(LAZR_OOPS_USER_REQUESTED_KEY)) context = object() namespace = OopsNamespace(context, request) result = namespace.traverse("name", None) self.assertIs(context, result) self.assertTrue(request.annotations.get(LAZR_OOPS_USER_REQUESTED_KEY))
def test_OopsNamespace_traverse(self): # The traverse method of the OopsNamespace sets the request # annotation, and returns the context that it was created with. request = get_current_browser_request() self.assertIs(None, request.annotations.get(LAZR_OOPS_USER_REQUESTED_KEY)) context = object() namespace = OopsNamespace(context, request) result = namespace.traverse("name", None) self.assertIs(context, result) self.assertTrue(request.annotations.get(LAZR_OOPS_USER_REQUESTED_KEY))
def test_cache_key_is_unique(self): # The cache key must include the link name, the context of the link # and the class where the link is defined. login(ANONYMOUS) context = object() menu = TestMenu(context) menu._get_link('test_link') cache = get_current_browser_request().annotations.get( MENU_ANNOTATION_KEY) self.assertEquals(len(cache.keys()), 1) self.assertContentEqual(cache.keys()[0], (menu.__class__, context, 'test_link'))
def test_display_escapee_user_data(self): # A notification is added if there is a message to show. product, user = self.make_product_user([License.OTHER_PROPRIETARY]) product.displayname = '<b>Look</b>' notification = LicenseNotification(product) result = notification.display() self.assertIs(True, result) request = get_current_browser_request() self.assertEqual(1, len(request.response.notifications)) self.assertIn( '<b>Look</b>', request.response.notifications[0].message)
def display(self): """Show a message in a browser page about the product's licence.""" request = get_current_browser_request() message = self.getCommercialUseMessage() if request is None or message == '': return False safe_message = structured( '%s<br />Learn more about ' '<a href="https://help.launchpad.net/CommercialHosting">' 'commercial subscriptions</a>', message) request.response.addNotification(safe_message) return True
def test_runJobHandleErrors_oops_timeline(self): """The oops timeline only covers the job itself.""" timeline = get_request_timeline(get_current_browser_request()) timeline.start('test', 'sentinel').finish() job = RaisingJobTimelineMessage('boom') flush_database_updates() runner = JobRunner([job]) runner.runJobHandleError(job) self.assertEqual(1, len(self.oopses)) actions = [action[2:4] for action in self.oopses[0]['timeline']] self.assertIn(('job', 'boom'), actions) self.assertNotIn(('test', 'sentinel'), actions)
def maybe_record_user_requested_oops(): """If an OOPS has been requested, report one. It will be stored in request.oopsid. """ request = get_current_browser_request() # If there's a request and no existing OOPS, but an OOPS has been # requested, record one. if (request is not None and request.oopsid is None and request.annotations.get(LAZR_OOPS_USER_REQUESTED_KEY, False)): globalErrorUtility.raising((UserRequestOops, UserRequestOops(), None), request)
def assertRequest(self, url_suffix, **kwargs): [request] = self.requests self.assertThat( request, MatchesStructure.byEquality(url=urlappend(self.endpoint, url_suffix), method="GET", **kwargs)) timeline = get_request_timeline(get_current_browser_request()) action = timeline.actions[-1] self.assertEqual("branch-hosting-get", action.category) self.assertEqual("/" + url_suffix.split("?", 1)[0], action.detail.split(" ", 1)[0])
def maybe_record_user_requested_oops(): """If an OOPS has been requested, report one. It will be stored in request.oopsid. """ request = get_current_browser_request() # If there's a request and no existing OOPS, but an OOPS has been # requested, record one. if (request is not None and request.oopsid is None and request.annotations.get(LAZR_OOPS_USER_REQUESTED_KEY, False)): globalErrorUtility.raising( (UserRequestOops, UserRequestOops(), None), request)
def test_display_has_message(self): # A notification is added if there is a message to show. product, user = self.make_product_user([License.OTHER_PROPRIETARY]) notification = LicenseNotification(product) result = notification.display() message = notification.getCommercialUseMessage() self.assertIs(True, result) request = get_current_browser_request() self.assertEqual(1, len(request.response.notifications)) self.assertIn( html_escape(message), request.response.notifications[0].message) self.assertIn( '<a href="https://help.launchpad.net/CommercialHosting">', request.response.notifications[0].message)
def connection_raw_execute(self, connection, raw_cursor, statement, params): statement_to_log = statement if params: statement_to_log = raw_cursor.mogrify( statement, tuple(connection.to_database(params))) # Record traceback to log, if requested. print_traceback = self._debug_sql_extra log_sql = getattr(_local, 'sql_logging', None) log_traceback = False if log_sql is not None: log_sql.append(dict(stack=None, sql=None, exception=None)) conditional = getattr(_local, 'sql_logging_tracebacks_if', None) if callable(conditional): try: log_traceback = conditional( self._normalize_whitespace( statement_to_log.strip()).upper()) except (MemoryError, SystemExit, KeyboardInterrupt): raise except: exc_type, exc_value, tb = sys.exc_info() log_sql[-1]['exception'] = (exc_type, exc_value) log_sql[-1]['stack'] = extract_tb(tb) else: log_traceback = bool(conditional) if print_traceback or log_traceback: stack = extract_stack() if log_traceback: log_sql[-1]['stack'] = stack if print_traceback: print_list(stack) sys.stderr.write("." * 70 + "\n") # store the last executed statement as an attribute on the current # thread threading.currentThread().lp_last_sql_statement = statement request_starttime = getattr(_local, 'request_start_time', None) if request_starttime is None: if print_traceback or self._debug_sql or log_sql is not None: # Stash some information for logging at the end of the # SQL execution. connection._lp_statement_info = ( int(time() * 1000), 'SQL-%s' % connection._database.name, statement_to_log) return action = get_request_timeline(get_current_browser_request()).start( 'SQL-%s' % connection._database.name, statement_to_log) connection._lp_statement_action = action
def summarize_requests(): """Produce human-readable summary of requests issued so far.""" secs = get_request_duration() request = get_current_browser_request() timeline = get_request_timeline(request) from lp.services.webapp.errorlog import ( maybe_record_user_requested_oops) maybe_record_user_requested_oops() if request.oopsid is None: oops_str = "" else: oops_str = " %s" % request.oopsid log = "%s queries/external actions issued in %.2f seconds%s" % ( len(timeline.actions), secs, oops_str) return log
def _get_link(self, name): request = get_current_browser_request() if request is not None: # We must not use a weak ref here because if we do so and # templates do stuff like "context/menu:bugs/foo", then there # would be no reference to the Link object, which would allow it # to be garbage collected during the course of the request. cache = request.annotations.setdefault(MENU_ANNOTATION_KEY, {}) key = (self.__class__, self.context, name) link = cache.get(key) if link is None: link = self._buildLink(name) cache[key] = link return link return self._buildLink(name)
def getFileByAlias( self, aliasID, timeout=LIBRARIAN_SERVER_DEFAULT_TIMEOUT): """See `IFileDownloadClient`.""" url = self._getURLForDownload(aliasID) if url is None: # File has been deleted return None try_until = time.time() + timeout request = get_current_browser_request() timeline = get_request_timeline(request) action = timeline.start("librarian-connection", url) try: return self._connect_read(url, try_until, aliasID) finally: action.finish()
def iterlinks(self, request_url=None): """See `INavigationMenu`. Menus may be associated with content objects and their views. The state of a menu's links depends upon the request_url (or the URL of the request) and whether the current view's menu is the link's menu. """ request = get_current_browser_request() view = get_current_view(request) if request_url is None: request_url = URI(request.getURL()) for link in super(NavigationMenu, self).iterlinks( request_url=request_url, view=view): yield link
def get_raw_form_value_from_current_request(field_name): # XXX: StevenK 2013-02-06 bug=1116954: We should not need to refetch # the file content from the request, since the passed in one has been # wrongly encoded. # Circular imports. from lp.services.webapp.servers import WebServiceClientRequest request = get_current_browser_request() assert isinstance(request, WebServiceClientRequest) # Zope wrongly encodes any form element that doesn't look like a file, # so re-fetch the file content if it has been encoded. if request and request.form.has_key(field_name) and isinstance( request.form[field_name], unicode): request._environ['wsgi.input'].seek(0) fs = FieldStorage(fp=request._body_instream, environ=request._environ) return fs[field_name].value