Repository: envoyproxy/envoy
File: test/integration/multiplexed_integration_test.cc (C++)

Code before:
  test_server_->waitForCounterGe("http.config_test.downstream_flow_control_paused_reading_total",
                                 1);
  test_server_->waitForGaugeGe("cluster.cluster_0.upstream_cx_tx_bytes_buffered", 1000);

  // Close upstream, check cleanup.
  fake_upstreams_[0].reset();

  ASSERT_TRUE(response_decoder->waitForAnyTermination());
  test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0);
  test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0);
  test_server_->waitForGaugeGe("cluster.cluster_0.upstream_cx_tx_bytes_buffered", 0);
}

TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, ResetPropogation) {
  // There are four streams created in total, client stream, Envoy server stream,
  // Envoy client stream and upstream server stream.
  // When we close a stream actively with a specific reset reason, we expect the peer
  // to receive the related error code in onStreamClose().
  // But note, the onStreamClose() for the active closing side will also be called.
  // But the error code for active closing side will always be 0 if the Oghttp2 is used.

  initialize();

  {
    // At the downstream side, because a complete local reply will be send before resetting
    // the stream, so the stream close code observed at downstream side will be NO_ERROR (0).
    // So, we expect 1 log entry with "closed: 2" for Oghttp2 and 2 log entries with "closed: 2"
    // for other implementations.
    size_t log_num = 0;
    if (GetParam().http2_implementation == Http2Impl::Oghttp2) {
      log_num = 1;
    } else {
      log_num = 2;
    }

    // The ProtocolError will be translated to OGHTTP2_PROTOCOL_ERROR (1).
    EXPECT_LOG_CONTAINS_N_TIMES("debug", "closed: 1", log_num, {
      codec_client_ = makeHttpConnection(lookupPort("http"));
      auto encoder_decoder = codec_client_->startRequest(default_request_headers_);
      auto response = std::move(encoder_decoder.second);
      waitForNextUpstreamConnection({0}, std::chrono::milliseconds(500), fake_upstream_connection_);
      ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_));

      // This will result in the router to send a local reply. And because the downstream request
      // is not complete yet, it will finally result in resetting of the downstream stream.
      upstream_request_->encodeResetStream(Http::StreamResetReason::ProtocolError);
      ASSERT_TRUE(response->waitForReset());
      EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());

      cleanupUpstreamAndDownstream();
    });

Review comment (bug):
Could you also explicitly set `reset_ignore_upstream_reason` as true here? When the default is changed (in-house patch or override), this test will fail.

Code after:
  test_server_->waitForCounterGe("http.config_test.downstream_flow_control_paused_reading_total",
                                 1);
  test_server_->waitForGaugeGe("cluster.cluster_0.upstream_cx_tx_bytes_buffered", 1000);

  // Close upstream, check cleanup.
  fake_upstreams_[0].reset();

  ASSERT_TRUE(response_decoder->waitForAnyTermination());
  test_server_->waitForGaugeEq("cluster.cluster_0.upstream_rq_active", 0);
  test_server_->waitForGaugeEq("http.config_test.downstream_rq_active", 0);
  test_server_->waitForGaugeGe("cluster.cluster_0.upstream_cx_tx_bytes_buffered", 0);
}

TEST_P(MultiplexedIntegrationTestWithSimulatedTimeHttp2Only, ResetPropogation) {
  // There are four streams created in total, client stream, Envoy server stream,
  // Envoy client stream and upstream server stream.
  // When we close a stream actively with a specific reset reason, we expect the peer
  // to receive the related error code in onStreamClose().
  // But note, the onStreamClose() for the active closing side will also be called.
  // But the error code for active closing side will always be 0 if the Oghttp2 is used.
  config_helper_.addRuntimeOverride("envoy.reloadable_features.reset_ignore_upstream_reason",
                                    "true");

  initialize();

  {
    // At the downstream side, because a complete local reply will be send before resetting
    // the stream, so the stream close code observed at downstream side will be NO_ERROR (0).
    // So, we expect 1 log entry with "closed: 2" for Oghttp2 and 2 log entries with "closed: 2"
    // for other implementations.
    size_t log_num = 0;
    if (GetParam().http2_implementation == Http2Impl::Oghttp2) {
      log_num = 1;
    } else {
      log_num = 2;
    }

    // The ProtocolError will be translated to OGHTTP2_PROTOCOL_ERROR (1).
    EXPECT_LOG_CONTAINS_N_TIMES("debug", "closed: 1", log_num, {
      codec_client_ = makeHttpConnection(lookupPort("http"));
      auto encoder_decoder = codec_client_->startRequest(default_request_headers_);
      auto response = std::move(encoder_decoder.second);
      waitForNextUpstreamConnection({0}, std::chrono::milliseconds(500), fake_upstream_connection_);
      ASSERT_TRUE(fake_upstream_connection_->waitForNewStream(*dispatcher_, upstream_request_));

      // This will result in the router to send a local reply. And because the downstream request
      // is not complete yet, it will finally result in resetting of the downstream stream.
      upstream_request_->encodeResetStream(Http::StreamResetReason::ProtocolError);
      ASSERT_TRUE(response->waitForReset());
      EXPECT_EQ(Http::StreamResetReason::RemoteReset, response->resetReason());