blob: b4e6b18761d5d21da1bb23c9bf32fe7375b7f92f [file]
# Copyright 2020 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
This contains helpers for gRPC services defined in
https://github.com/grpc/grpc-proto/blob/master/grpc/channelz/v1/channelz.proto
"""
import ipaddress
import logging
from typing import Iterator, Optional
import grpc
from grpc_channelz.v1 import channelz_pb2
from grpc_channelz.v1 import channelz_pb2_grpc
import framework.rpc
logger = logging.getLogger(__name__)
# Type aliases
# Channel
Channel = channelz_pb2.Channel
ChannelConnectivityState = channelz_pb2.ChannelConnectivityState
ChannelState = ChannelConnectivityState.State # pylint: disable=no-member
_GetTopChannelsRequest = channelz_pb2.GetTopChannelsRequest
_GetTopChannelsResponse = channelz_pb2.GetTopChannelsResponse
# Subchannel
Subchannel = channelz_pb2.Subchannel
_GetSubchannelRequest = channelz_pb2.GetSubchannelRequest
_GetSubchannelResponse = channelz_pb2.GetSubchannelResponse
# Server
Server = channelz_pb2.Server
_GetServersRequest = channelz_pb2.GetServersRequest
_GetServersResponse = channelz_pb2.GetServersResponse
# Sockets
Socket = channelz_pb2.Socket
SocketRef = channelz_pb2.SocketRef
_GetSocketRequest = channelz_pb2.GetSocketRequest
_GetSocketResponse = channelz_pb2.GetSocketResponse
Address = channelz_pb2.Address
Security = channelz_pb2.Security
# Server Sockets
_GetServerSocketsRequest = channelz_pb2.GetServerSocketsRequest
_GetServerSocketsResponse = channelz_pb2.GetServerSocketsResponse
class ChannelzServiceClient(framework.rpc.grpc.GrpcClientHelper):
stub: channelz_pb2_grpc.ChannelzStub
def __init__(self, channel: grpc.Channel):
super().__init__(channel, channelz_pb2_grpc.ChannelzStub)
@staticmethod
def is_sock_tcpip_address(address: Address):
return address.WhichOneof('address') == 'tcpip_address'
@staticmethod
def is_ipv4(tcpip_address: Address.TcpIpAddress):
# According to proto, tcpip_address.ip_address is either IPv4 or IPv6.
# Correspondingly, it's either 4 bytes or 16 bytes in length.
return len(tcpip_address.ip_address) == 4
@classmethod
def sock_address_to_str(cls, address: Address):
if cls.is_sock_tcpip_address(address):
tcpip_address: Address.TcpIpAddress = address.tcpip_address
if cls.is_ipv4(tcpip_address):
ip = ipaddress.IPv4Address(tcpip_address.ip_address)
else:
ip = ipaddress.IPv6Address(tcpip_address.ip_address)
return f'{ip}:{tcpip_address.port}'
else:
raise NotImplementedError('Only tcpip_address implemented')
@classmethod
def sock_addresses_pretty(cls, socket: Socket):
return (f'local={cls.sock_address_to_str(socket.local)}, '
f'remote={cls.sock_address_to_str(socket.remote)}')
@staticmethod
def find_server_socket_matching_client(server_sockets: Iterator[Socket],
client_socket: Socket) -> Socket:
for server_socket in server_sockets:
if server_socket.remote == client_socket.local:
return server_socket
return None
def find_channels_for_target(self, target: str,
**kwargs) -> Iterator[Channel]:
return (channel for channel in self.list_channels(**kwargs)
if channel.data.target == target)
def find_server_listening_on_port(self, port: int,
**kwargs) -> Optional[Server]:
for server in self.list_servers(**kwargs):
listen_socket_ref: SocketRef
for listen_socket_ref in server.listen_socket:
listen_socket = self.get_socket(listen_socket_ref.socket_id,
**kwargs)
listen_address: Address = listen_socket.local
if (self.is_sock_tcpip_address(listen_address) and
listen_address.tcpip_address.port == port):
return server
return None
def list_channels(self, **kwargs) -> Iterator[Channel]:
"""
Iterate over all pages of all root channels.
Root channels are those which application has directly created.
This does not include subchannels nor non-top level channels.
"""
start: int = -1
response: Optional[_GetTopChannelsResponse] = None
while start < 0 or not response.end:
# From proto: To request subsequent pages, the client generates this
# value by adding 1 to the highest seen result ID.
start += 1
response = self.call_unary_with_deadline(
rpc='GetTopChannels',
req=_GetTopChannelsRequest(start_channel_id=start),
**kwargs)
for channel in response.channel:
start = max(start, channel.ref.channel_id)
yield channel
def list_servers(self, **kwargs) -> Iterator[Server]:
"""Iterate over all pages of all servers that exist in the process."""
start: int = -1
response: Optional[_GetServersResponse] = None
while start < 0 or not response.end:
# From proto: To request subsequent pages, the client generates this
# value by adding 1 to the highest seen result ID.
start += 1
response = self.call_unary_with_deadline(
rpc='GetServers',
req=_GetServersRequest(start_server_id=start),
**kwargs)
for server in response.server:
start = max(start, server.ref.server_id)
yield server
def list_server_sockets(self, server: Server, **kwargs) -> Iterator[Socket]:
"""List all server sockets that exist in server process.
Iterating over the results will resolve additional pages automatically.
"""
start: int = -1
response: Optional[_GetServerSocketsResponse] = None
while start < 0 or not response.end:
# From proto: To request subsequent pages, the client generates this
# value by adding 1 to the highest seen result ID.
start += 1
response = self.call_unary_with_deadline(
rpc='GetServerSockets',
req=_GetServerSocketsRequest(server_id=server.ref.server_id,
start_socket_id=start),
**kwargs)
socket_ref: SocketRef
for socket_ref in response.socket_ref:
start = max(start, socket_ref.socket_id)
# Yield actual socket
yield self.get_socket(socket_ref.socket_id, **kwargs)
def list_channel_sockets(self, channel: Channel,
**kwargs) -> Iterator[Socket]:
"""List all sockets of all subchannels of a given channel."""
for subchannel in self.list_channel_subchannels(channel, **kwargs):
yield from self.list_subchannels_sockets(subchannel, **kwargs)
def list_channel_subchannels(self, channel: Channel,
**kwargs) -> Iterator[Subchannel]:
"""List all subchannels of a given channel."""
for subchannel_ref in channel.subchannel_ref:
yield self.get_subchannel(subchannel_ref.subchannel_id, **kwargs)
def list_subchannels_sockets(self, subchannel: Subchannel,
**kwargs) -> Iterator[Socket]:
"""List all sockets of a given subchannel."""
for socket_ref in subchannel.socket_ref:
yield self.get_socket(socket_ref.socket_id, **kwargs)
def get_subchannel(self, subchannel_id, **kwargs) -> Subchannel:
"""Return a single Subchannel, otherwise raises RpcError."""
response: _GetSubchannelResponse = self.call_unary_with_deadline(
rpc='GetSubchannel',
req=_GetSubchannelRequest(subchannel_id=subchannel_id),
**kwargs)
return response.subchannel
def get_socket(self, socket_id, **kwargs) -> Socket:
"""Return a single Socket, otherwise raises RpcError."""
response: _GetSocketResponse = self.call_unary_with_deadline(
rpc='GetSocket',
req=_GetSocketRequest(socket_id=socket_id),
**kwargs)
return response.socket