HTTP2ServerTransport+DebugTests.swift 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. /*
  2. * Copyright 2025, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import GRPCCore
  17. import GRPCNIOTransportHTTP2
  18. import Testing
  19. @Suite("ChannelDebugCallbacks")
  20. struct ChannelDebugCallbackTests {
  21. @Test(arguments: TransportKind.allCases, TransportKind.allCases)
  22. func debugCallbacksAreCalled(serverKind: TransportKind, clientKind: TransportKind) async throws {
  23. // Validates the callbacks are called appropriately by setting up callbacks which increment
  24. // counters and then returning those stats from a gRPC service. The client's interactions with
  25. // the service drive the callbacks.
  26. let stats = DebugCallbackStats()
  27. let serverDebug = HTTP2ServerTransport.Config.ChannelDebuggingCallbacks(
  28. onBindTCPListener: { channel in
  29. stats.tcpListenersBound.add(1, ordering: .sequentiallyConsistent)
  30. return channel.eventLoop.makeSucceededVoidFuture()
  31. },
  32. onAcceptTCPConnection: { channel in
  33. stats.tcpConnectionsAccepted.add(1, ordering: .sequentiallyConsistent)
  34. return channel.eventLoop.makeSucceededVoidFuture()
  35. },
  36. onAcceptHTTP2Stream: { channel in
  37. stats.http2StreamsAccepted.add(1, ordering: .sequentiallyConsistent)
  38. return channel.eventLoop.makeSucceededVoidFuture()
  39. }
  40. )
  41. let clientDebug = HTTP2ClientTransport.Config.ChannelDebuggingCallbacks(
  42. onCreateTCPConnection: { channel in
  43. stats.tcpConnectionsCreated.add(1, ordering: .sequentiallyConsistent)
  44. return channel.eventLoop.makeSucceededVoidFuture()
  45. },
  46. onCreateHTTP2Stream: { channel in
  47. stats.http2StreamsCreated.add(1, ordering: .sequentiallyConsistent)
  48. return channel.eventLoop.makeSucceededVoidFuture()
  49. }
  50. )
  51. // For each server have the client create this many connections.
  52. let connectionsPerServer = 5
  53. // For each connection have the client create this many streams.
  54. let streamsPerConnection = 3
  55. try await withGRPCServer(
  56. transport: self.makeServerTransport(
  57. kind: serverKind,
  58. address: .ipv4(host: "127.0.0.1", port: 0),
  59. debug: serverDebug
  60. ),
  61. services: [StatsService(stats: stats)]
  62. ) { server in
  63. let address = try await server.listeningAddress!.ipv4!
  64. for connectionNumber in 1 ... connectionsPerServer {
  65. try await withGRPCClient(
  66. transport: self.makeClientTransport(
  67. kind: clientKind,
  68. target: .ipv4(host: address.host, port: address.port),
  69. debug: clientDebug
  70. )
  71. ) { client in
  72. let statsClient = StatsClient(wrapping: client)
  73. // Create a few streams per connection.
  74. for streamNumber in 1 ... streamsPerConnection {
  75. let streamCount = (connectionNumber - 1) * streamsPerConnection + streamNumber
  76. let stats = try await statsClient.getStats()
  77. #expect(stats.server.tcpListenersBound == 1)
  78. #expect(stats.server.tcpConnectionsAccepted == connectionNumber)
  79. #expect(stats.server.http2StreamsAccepted == streamCount)
  80. #expect(stats.client.tcpConnectionsCreated == connectionNumber)
  81. #expect(stats.client.http2StreamsCreated == streamCount)
  82. }
  83. }
  84. }
  85. }
  86. }
  87. private func makeServerTransport(
  88. kind: TransportKind,
  89. address: SocketAddress,
  90. debug: HTTP2ServerTransport.Config.ChannelDebuggingCallbacks
  91. ) -> NIOServerTransport {
  92. switch kind {
  93. case .posix:
  94. return NIOServerTransport(
  95. .http2NIOPosix(
  96. address: address,
  97. transportSecurity: .plaintext,
  98. config: .defaults {
  99. $0.channelDebuggingCallbacks = debug
  100. }
  101. )
  102. )
  103. #if canImport(Network)
  104. case .transportServices:
  105. return NIOServerTransport(
  106. .http2NIOTS(
  107. address: address,
  108. transportSecurity: .plaintext,
  109. config: .defaults {
  110. $0.channelDebuggingCallbacks = debug
  111. }
  112. )
  113. )
  114. #endif
  115. }
  116. }
  117. private func makeClientTransport(
  118. kind: TransportKind,
  119. target: any ResolvableTarget,
  120. debug: HTTP2ClientTransport.Config.ChannelDebuggingCallbacks
  121. ) throws -> NIOClientTransport {
  122. switch kind {
  123. case .posix:
  124. return NIOClientTransport(
  125. try .http2NIOPosix(
  126. target: target,
  127. transportSecurity: .plaintext,
  128. config: .defaults {
  129. $0.channelDebuggingCallbacks = debug
  130. }
  131. )
  132. )
  133. #if canImport(Network)
  134. case .transportServices:
  135. return NIOClientTransport(
  136. try .http2NIOTS(
  137. target: target,
  138. transportSecurity: .plaintext,
  139. config: .defaults {
  140. $0.channelDebuggingCallbacks = debug
  141. }
  142. )
  143. )
  144. #endif
  145. }
  146. }
  147. }