run_channelz.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. # Copyright 2020 gRPC authors.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. """Channelz debugging tool for xDS test client/server.
  15. This is intended as a debugging / local development helper and not executed
  16. as a part of interop test suites.
  17. Typical usage examples:
  18. # Show channel and server socket pair
  19. python -m bin.run_channelz --flagfile=config/local-dev.cfg
  20. # Evaluate setup for different security configurations
  21. python -m bin.run_channelz --flagfile=config/local-dev.cfg --security=tls
  22. python -m bin.run_channelz --flagfile=config/local-dev.cfg --security=mtls_error
  23. # More information and usage options
  24. python -m bin.run_channelz --helpfull
  25. """
  26. import hashlib
  27. import logging
  28. from absl import app
  29. from absl import flags
  30. from framework import xds_flags
  31. from framework import xds_k8s_flags
  32. from framework.infrastructure import k8s
  33. from framework.rpc import grpc_channelz
  34. from framework.test_app import client_app
  35. from framework.test_app import server_app
  36. logger = logging.getLogger(__name__)
  37. # Flags
  38. _SERVER_RPC_HOST = flags.DEFINE_string('server_rpc_host',
  39. default='127.0.0.1',
  40. help='Server RPC host')
  41. _CLIENT_RPC_HOST = flags.DEFINE_string('client_rpc_host',
  42. default='127.0.0.1',
  43. help='Client RPC host')
  44. _SECURITY = flags.DEFINE_enum('security',
  45. default=None,
  46. enum_values=[
  47. 'mtls', 'tls', 'plaintext', 'mtls_error',
  48. 'server_authz_error'
  49. ],
  50. help='Show info for a security setup')
  51. flags.adopt_module_key_flags(xds_flags)
  52. flags.adopt_module_key_flags(xds_k8s_flags)
  53. # Running outside of a test suite, so require explicit resource_suffix.
  54. flags.mark_flag_as_required("resource_suffix")
  55. # Type aliases
  56. _Channel = grpc_channelz.Channel
  57. _Socket = grpc_channelz.Socket
  58. _ChannelState = grpc_channelz.ChannelState
  59. _XdsTestServer = server_app.XdsTestServer
  60. _XdsTestClient = client_app.XdsTestClient
  61. def debug_cert(cert):
  62. if not cert:
  63. return '<missing>'
  64. sha1 = hashlib.sha1(cert)
  65. return f'sha1={sha1.hexdigest()}, len={len(cert)}'
  66. def debug_sock_tls(tls):
  67. return (f'local: {debug_cert(tls.local_certificate)}\n'
  68. f'remote: {debug_cert(tls.remote_certificate)}')
  69. def get_deployment_pod_ips(k8s_ns, deployment_name):
  70. deployment = k8s_ns.get_deployment(deployment_name)
  71. pods = k8s_ns.list_deployment_pods(deployment)
  72. return [pod.status.pod_ip for pod in pods]
  73. def debug_security_setup_negative(test_client):
  74. """Debug negative cases: mTLS Error, Server AuthZ error
  75. 1) mTLS Error: Server expects client mTLS cert,
  76. but client configured only for TLS.
  77. 2) AuthZ error: Client does not authorize server because of mismatched
  78. SAN name.
  79. """
  80. # Client side.
  81. client_correct_setup = True
  82. channel: _Channel = test_client.wait_for_server_channel_state(
  83. state=_ChannelState.TRANSIENT_FAILURE)
  84. try:
  85. subchannel, *subchannels = list(
  86. test_client.channelz.list_channel_subchannels(channel))
  87. except ValueError:
  88. print("Client setup fail: subchannel not found. "
  89. "Common causes: test client didn't connect to TD; "
  90. "test client exhausted retries, and closed all subchannels.")
  91. return
  92. # Client must have exactly one subchannel.
  93. logger.debug('Found subchannel, %s', subchannel)
  94. if subchannels:
  95. client_correct_setup = False
  96. print(f'Unexpected subchannels {subchannels}')
  97. subchannel_state: _ChannelState = subchannel.data.state.state
  98. if subchannel_state is not _ChannelState.TRANSIENT_FAILURE:
  99. client_correct_setup = False
  100. print('Subchannel expected to be in '
  101. 'TRANSIENT_FAILURE, same as its channel')
  102. # Client subchannel must have no sockets.
  103. sockets = list(test_client.channelz.list_subchannels_sockets(subchannel))
  104. if sockets:
  105. client_correct_setup = False
  106. print(f'Unexpected subchannel sockets {sockets}')
  107. # Results.
  108. if client_correct_setup:
  109. print('Client setup pass: the channel '
  110. 'to the server has exactly one subchannel '
  111. 'in TRANSIENT_FAILURE, and no sockets')
  112. def debug_security_setup_positive(test_client, test_server):
  113. """Debug positive cases: mTLS, TLS, Plaintext."""
  114. test_client.wait_for_active_server_channel()
  115. client_sock: _Socket = test_client.get_active_server_channel_socket()
  116. server_sock: _Socket = test_server.get_server_socket_matching_client(
  117. client_sock)
  118. server_tls = server_sock.security.tls
  119. client_tls = client_sock.security.tls
  120. print(f'\nServer certs:\n{debug_sock_tls(server_tls)}')
  121. print(f'\nClient certs:\n{debug_sock_tls(client_tls)}')
  122. print()
  123. if server_tls.local_certificate:
  124. eq = server_tls.local_certificate == client_tls.remote_certificate
  125. print(f'(TLS) Server local matches client remote: {eq}')
  126. else:
  127. print('(TLS) Not detected')
  128. if server_tls.remote_certificate:
  129. eq = server_tls.remote_certificate == client_tls.local_certificate
  130. print(f'(mTLS) Server remote matches client local: {eq}')
  131. else:
  132. print('(mTLS) Not detected')
  133. def debug_basic_setup(test_client, test_server):
  134. """Show channel and server socket pair"""
  135. test_client.wait_for_active_server_channel()
  136. client_sock: _Socket = test_client.get_active_server_channel_socket()
  137. server_sock: _Socket = test_server.get_server_socket_matching_client(
  138. client_sock)
  139. print(f'Client socket:\n{client_sock}\n')
  140. print(f'Matching server:\n{server_sock}\n')
  141. def main(argv):
  142. if len(argv) > 1:
  143. raise app.UsageError('Too many command-line arguments.')
  144. k8s_api_manager = k8s.KubernetesApiManager(xds_k8s_flags.KUBE_CONTEXT.value)
  145. # Resource names.
  146. resource_prefix: str = xds_flags.RESOURCE_PREFIX.value
  147. resource_suffix: str = xds_flags.RESOURCE_SUFFIX.value
  148. # Server
  149. server_name = xds_flags.SERVER_NAME.value
  150. server_namespace = resource_prefix
  151. server_k8s_ns = k8s.KubernetesNamespace(k8s_api_manager, server_namespace)
  152. server_pod_ip = get_deployment_pod_ips(server_k8s_ns, server_name)[0]
  153. test_server: _XdsTestServer = _XdsTestServer(
  154. ip=server_pod_ip,
  155. rpc_port=xds_flags.SERVER_PORT.value,
  156. xds_host=xds_flags.SERVER_XDS_HOST.value,
  157. xds_port=xds_flags.SERVER_XDS_PORT.value,
  158. rpc_host=_SERVER_RPC_HOST.value)
  159. # Client
  160. client_name = xds_flags.CLIENT_NAME.value
  161. client_namespace = resource_prefix
  162. client_k8s_ns = k8s.KubernetesNamespace(k8s_api_manager, client_namespace)
  163. client_pod_ip = get_deployment_pod_ips(client_k8s_ns, client_name)[0]
  164. test_client: _XdsTestClient = _XdsTestClient(
  165. ip=client_pod_ip,
  166. server_target=test_server.xds_uri,
  167. rpc_port=xds_flags.CLIENT_PORT.value,
  168. rpc_host=_CLIENT_RPC_HOST.value)
  169. if _SECURITY.value in ('mtls', 'tls', 'plaintext'):
  170. debug_security_setup_positive(test_client, test_server)
  171. elif _SECURITY.value == ('mtls_error', 'server_authz_error'):
  172. debug_security_setup_negative(test_client)
  173. else:
  174. debug_basic_setup(test_client, test_server)
  175. test_client.close()
  176. test_server.close()
  177. if __name__ == '__main__':
  178. app.run(main)