mirror of https://github.com/grpc/grpc.git
[PSM Interop] Various improvements to the helper scripts (#32745)
- Fix broken `bin/run_channelz.py` helper - Create `bin/run_ping_pong.py` helper that runs the baseline (aka "ping_pong") test against preconfigured infra - Setup automatic port forwarding when running `bin/run_channelz.py` and `bin/run_ping_pong.py` - Create `bin/cleanup_cluster.sh` helper to wipe xds out resources based namespaces present on the cluster Note: this involves a small change to the non-helper code, but it's just moving a the part that makes XdsTestServer/XdsTestClient instance for a given pod.pull/32950/head
parent
83cdbfff8c
commit
c0ee9ff4d5
10 changed files with 565 additions and 140 deletions
@ -0,0 +1,88 @@ |
|||||||
|
#!/usr/bin/env bash |
||||||
|
# Copyright 2023 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. |
||||||
|
|
||||||
|
set -eo pipefail |
||||||
|
|
||||||
|
SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" |
||||||
|
readonly SCRIPT_DIR |
||||||
|
readonly XDS_K8S_DRIVER_DIR="${SCRIPT_DIR}/.." |
||||||
|
|
||||||
|
cd "${XDS_K8S_DRIVER_DIR}" |
||||||
|
|
||||||
|
NO_SECURE="yes" |
||||||
|
DATE_TO=$(date -Iseconds) |
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do |
||||||
|
case $1 in |
||||||
|
--secure) NO_SECURE=""; shift ;; |
||||||
|
--date_to=*) DATE_TO="${1#*=}T00:00:00Z"; shift ;; |
||||||
|
*) echo "Unknown argument $1"; exit 1 ;; |
||||||
|
esac |
||||||
|
done |
||||||
|
|
||||||
|
jq_selector=$(cat <<- 'EOM' |
||||||
|
.items[].metadata | |
||||||
|
select( |
||||||
|
(.name | test("-(client|server)-")) and |
||||||
|
(.creationTimestamp < $date_to) |
||||||
|
) | .name |
||||||
|
EOM |
||||||
|
) |
||||||
|
|
||||||
|
mapfile -t namespaces < <(\ |
||||||
|
kubectl get namespaces --sort-by='{.metadata.creationTimestamp}'\ |
||||||
|
--selector='owner=xds-k8s-interop-test'\ |
||||||
|
-o json\ |
||||||
|
| jq --arg date_to "${DATE_TO}" -r "${jq_selector}" |
||||||
|
) |
||||||
|
|
||||||
|
if [[ -z "${namespaces[*]}" ]]; then |
||||||
|
echo "All clean." |
||||||
|
exit 0 |
||||||
|
fi |
||||||
|
|
||||||
|
echo "Found namespaces:" |
||||||
|
namespaces_joined=$(IFS=,; printf '%s' "${namespaces[*]}") |
||||||
|
kubectl get namespaces --sort-by='{.metadata.creationTimestamp}' \ |
||||||
|
--selector="name in (${namespaces_joined})" |
||||||
|
# Suffixes |
||||||
|
mapfile -t suffixes < <(\ |
||||||
|
printf '%s\n' "${namespaces[@]}" | sed -E 's/psm-interop-(server|client)-//' |
||||||
|
) |
||||||
|
echo |
||||||
|
echo "Found suffixes: ${suffixes[*]}" |
||||||
|
|
||||||
|
echo "Run plan:" |
||||||
|
for suffix in "${suffixes[@]}"; do |
||||||
|
echo ./bin/cleanup.sh ${NO_SECURE:+"--nosecure"} "--resource_suffix=${suffix}" |
||||||
|
done |
||||||
|
|
||||||
|
read -r -n 1 -p "Continue? (y/N) " answer |
||||||
|
if [[ "$answer" != "${answer#[Yy]}" ]] ;then |
||||||
|
echo |
||||||
|
echo "Starting the cleanup." |
||||||
|
else |
||||||
|
echo |
||||||
|
echo "Exit" |
||||||
|
exit 0 |
||||||
|
fi |
||||||
|
|
||||||
|
for suffix in "${suffixes[@]}"; do |
||||||
|
echo "-------------------- Cleaning suffix ${suffix} --------------------" |
||||||
|
set -x |
||||||
|
./bin/cleanup.sh ${NO_SECURE:+"--nosecure"} "--resource_suffix=${suffix}" |
||||||
|
set +x |
||||||
|
echo "-------------------- Finished cleaning ${suffix} --------------------" |
||||||
|
done |
@ -0,0 +1,13 @@ |
|||||||
|
# Copyright 2023 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. |
@ -0,0 +1,157 @@ |
|||||||
|
# Copyright 2023 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. |
||||||
|
"""Common functionality for bin/ python helpers.""" |
||||||
|
import atexit |
||||||
|
import signal |
||||||
|
import sys |
||||||
|
|
||||||
|
from absl import logging |
||||||
|
|
||||||
|
from framework import xds_flags |
||||||
|
from framework import xds_k8s_flags |
||||||
|
from framework.infrastructure import gcp |
||||||
|
from framework.infrastructure import k8s |
||||||
|
from framework.test_app import client_app |
||||||
|
from framework.test_app import server_app |
||||||
|
from framework.test_app.runners.k8s import k8s_xds_client_runner |
||||||
|
from framework.test_app.runners.k8s import k8s_xds_server_runner |
||||||
|
|
||||||
|
logger = logging.get_absl_logger() |
||||||
|
|
||||||
|
# Type aliases |
||||||
|
KubernetesClientRunner = k8s_xds_client_runner.KubernetesClientRunner |
||||||
|
KubernetesServerRunner = k8s_xds_server_runner.KubernetesServerRunner |
||||||
|
_XdsTestServer = server_app.XdsTestServer |
||||||
|
_XdsTestClient = client_app.XdsTestClient |
||||||
|
|
||||||
|
|
||||||
|
def make_client_namespace( |
||||||
|
k8s_api_manager: k8s.KubernetesApiManager) -> k8s.KubernetesNamespace: |
||||||
|
namespace_name: str = KubernetesClientRunner.make_namespace_name( |
||||||
|
xds_flags.RESOURCE_PREFIX.value, xds_flags.RESOURCE_SUFFIX.value) |
||||||
|
return k8s.KubernetesNamespace(k8s_api_manager, namespace_name) |
||||||
|
|
||||||
|
|
||||||
|
def make_client_runner(namespace: k8s.KubernetesNamespace, |
||||||
|
gcp_api_manager: gcp.api.GcpApiManager, |
||||||
|
port_forwarding: bool = False, |
||||||
|
reuse_namespace: bool = True, |
||||||
|
secure: bool = False) -> KubernetesClientRunner: |
||||||
|
# KubernetesClientRunner arguments. |
||||||
|
runner_kwargs = dict( |
||||||
|
deployment_name=xds_flags.CLIENT_NAME.value, |
||||||
|
image_name=xds_k8s_flags.CLIENT_IMAGE.value, |
||||||
|
td_bootstrap_image=xds_k8s_flags.TD_BOOTSTRAP_IMAGE.value, |
||||||
|
gcp_project=xds_flags.PROJECT.value, |
||||||
|
gcp_api_manager=gcp_api_manager, |
||||||
|
gcp_service_account=xds_k8s_flags.GCP_SERVICE_ACCOUNT.value, |
||||||
|
xds_server_uri=xds_flags.XDS_SERVER_URI.value, |
||||||
|
network=xds_flags.NETWORK.value, |
||||||
|
stats_port=xds_flags.CLIENT_PORT.value, |
||||||
|
reuse_namespace=reuse_namespace, |
||||||
|
debug_use_port_forwarding=port_forwarding) |
||||||
|
|
||||||
|
if secure: |
||||||
|
runner_kwargs.update( |
||||||
|
deployment_template='client-secure.deployment.yaml') |
||||||
|
return KubernetesClientRunner(namespace, **runner_kwargs) |
||||||
|
|
||||||
|
|
||||||
|
def make_server_namespace( |
||||||
|
k8s_api_manager: k8s.KubernetesApiManager) -> k8s.KubernetesNamespace: |
||||||
|
namespace_name: str = KubernetesServerRunner.make_namespace_name( |
||||||
|
xds_flags.RESOURCE_PREFIX.value, xds_flags.RESOURCE_SUFFIX.value) |
||||||
|
return k8s.KubernetesNamespace(k8s_api_manager, namespace_name) |
||||||
|
|
||||||
|
|
||||||
|
def make_server_runner(namespace: k8s.KubernetesNamespace, |
||||||
|
gcp_api_manager: gcp.api.GcpApiManager, |
||||||
|
port_forwarding: bool = False, |
||||||
|
reuse_namespace: bool = True, |
||||||
|
reuse_service: bool = False, |
||||||
|
secure: bool = False) -> KubernetesServerRunner: |
||||||
|
# KubernetesServerRunner arguments. |
||||||
|
runner_kwargs = dict( |
||||||
|
deployment_name=xds_flags.SERVER_NAME.value, |
||||||
|
image_name=xds_k8s_flags.SERVER_IMAGE.value, |
||||||
|
td_bootstrap_image=xds_k8s_flags.TD_BOOTSTRAP_IMAGE.value, |
||||||
|
gcp_project=xds_flags.PROJECT.value, |
||||||
|
gcp_api_manager=gcp_api_manager, |
||||||
|
gcp_service_account=xds_k8s_flags.GCP_SERVICE_ACCOUNT.value, |
||||||
|
network=xds_flags.NETWORK.value, |
||||||
|
reuse_namespace=reuse_namespace, |
||||||
|
reuse_service=reuse_service, |
||||||
|
debug_use_port_forwarding=port_forwarding) |
||||||
|
|
||||||
|
if secure: |
||||||
|
runner_kwargs.update( |
||||||
|
xds_server_uri=xds_flags.XDS_SERVER_URI.value, |
||||||
|
deployment_template='server-secure.deployment.yaml') |
||||||
|
|
||||||
|
return KubernetesServerRunner(namespace, **runner_kwargs) |
||||||
|
|
||||||
|
|
||||||
|
def _ensure_atexit(signum, frame): |
||||||
|
"""Needed to handle signals or atexit handler won't be called.""" |
||||||
|
del frame |
||||||
|
|
||||||
|
# Pylint is wrong about "Module 'signal' has no 'Signals' member": |
||||||
|
# https://docs.python.org/3/library/signal.html#signal.Signals |
||||||
|
sig = signal.Signals(signum) # pylint: disable=no-member |
||||||
|
logger.warning('Caught %r, initiating graceful shutdown...\n', sig) |
||||||
|
sys.exit(1) |
||||||
|
|
||||||
|
|
||||||
|
def _graceful_exit(server_runner: KubernetesServerRunner, |
||||||
|
client_runner: KubernetesClientRunner): |
||||||
|
"""Stop port forwarding processes.""" |
||||||
|
client_runner.stop_pod_dependencies() |
||||||
|
server_runner.stop_pod_dependencies() |
||||||
|
|
||||||
|
|
||||||
|
def register_graceful_exit(server_runner: KubernetesServerRunner, |
||||||
|
client_runner: KubernetesClientRunner): |
||||||
|
atexit.register(_graceful_exit, server_runner, client_runner) |
||||||
|
for signum in (signal.SIGTERM, signal.SIGHUP, signal.SIGINT): |
||||||
|
signal.signal(signum, _ensure_atexit) |
||||||
|
|
||||||
|
|
||||||
|
def get_client_pod(client_runner: KubernetesClientRunner, |
||||||
|
deployment_name: str) -> k8s.V1Pod: |
||||||
|
client_deployment: k8s.V1Deployment |
||||||
|
client_deployment = client_runner.k8s_namespace.get_deployment( |
||||||
|
deployment_name) |
||||||
|
client_pod_name: str = client_runner._wait_deployment_pod_count( |
||||||
|
client_deployment)[0] |
||||||
|
return client_runner._wait_pod_started(client_pod_name) |
||||||
|
|
||||||
|
|
||||||
|
def get_server_pod(server_runner: KubernetesServerRunner, |
||||||
|
deployment_name: str) -> k8s.V1Pod: |
||||||
|
server_deployment: k8s.V1Deployment |
||||||
|
server_deployment = server_runner.k8s_namespace.get_deployment( |
||||||
|
deployment_name) |
||||||
|
server_pod_name: str = server_runner._wait_deployment_pod_count( |
||||||
|
server_deployment)[0] |
||||||
|
return server_runner._wait_pod_started(server_pod_name) |
||||||
|
|
||||||
|
|
||||||
|
def get_test_server_for_pod(server_runner: KubernetesServerRunner, |
||||||
|
server_pod: k8s.V1Pod, **kwargs) -> _XdsTestServer: |
||||||
|
return server_runner._xds_test_server_for_pod(server_pod, **kwargs) |
||||||
|
|
||||||
|
|
||||||
|
def get_test_client_for_pod(client_runner: KubernetesClientRunner, |
||||||
|
client_pod: k8s.V1Pod, **kwargs) -> _XdsTestClient: |
||||||
|
return client_runner._xds_test_client_for_pod(client_pod, **kwargs) |
@ -0,0 +1,141 @@ |
|||||||
|
# Copyright 2023 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. |
||||||
|
from absl import app |
||||||
|
from absl import flags |
||||||
|
from absl import logging |
||||||
|
|
||||||
|
from bin.lib import common |
||||||
|
from framework import xds_flags |
||||||
|
from framework import xds_k8s_flags |
||||||
|
from framework.infrastructure import gcp |
||||||
|
from framework.infrastructure import k8s |
||||||
|
from framework.rpc import grpc_channelz |
||||||
|
from framework.rpc import grpc_testing |
||||||
|
from framework.test_app import client_app |
||||||
|
from framework.test_app import server_app |
||||||
|
|
||||||
|
# Flags |
||||||
|
_SECURE = flags.DEFINE_bool( |
||||||
|
"secure", |
||||||
|
default=False, |
||||||
|
help="Set to True if the the client/server were started " |
||||||
|
"with the PSM security enabled.") |
||||||
|
_NUM_RPCS = flags.DEFINE_integer("num_rpcs", |
||||||
|
default=100, |
||||||
|
lower_bound=1, |
||||||
|
upper_bound=10_000, |
||||||
|
help="The number of RPCs to check.") |
||||||
|
flags.adopt_module_key_flags(xds_flags) |
||||||
|
flags.adopt_module_key_flags(xds_k8s_flags) |
||||||
|
# Running outside of a test suite, so require explicit resource_suffix. |
||||||
|
flags.mark_flag_as_required(xds_flags.RESOURCE_SUFFIX.name) |
||||||
|
flags.register_validator(xds_flags.SERVER_XDS_PORT.name, |
||||||
|
lambda val: val > 0, |
||||||
|
message="Run outside of a test suite, must provide" |
||||||
|
" the exact port value (must be greater than 0).") |
||||||
|
|
||||||
|
logger = logging.get_absl_logger() |
||||||
|
|
||||||
|
# Type aliases |
||||||
|
_Channel = grpc_channelz.Channel |
||||||
|
_Socket = grpc_channelz.Socket |
||||||
|
_ChannelState = grpc_channelz.ChannelState |
||||||
|
_XdsTestServer = server_app.XdsTestServer |
||||||
|
_XdsTestClient = client_app.XdsTestClient |
||||||
|
LoadBalancerStatsResponse = grpc_testing.LoadBalancerStatsResponse |
||||||
|
|
||||||
|
|
||||||
|
def get_client_rpc_stats(test_client: _XdsTestClient, |
||||||
|
num_rpcs: int) -> LoadBalancerStatsResponse: |
||||||
|
lb_stats = test_client.get_load_balancer_stats(num_rpcs=num_rpcs) |
||||||
|
logger.info('Received LoadBalancerStatsResponse from test client %s:\n%s', |
||||||
|
test_client.hostname, lb_stats) |
||||||
|
return lb_stats |
||||||
|
|
||||||
|
|
||||||
|
def run_ping_pong(test_client: _XdsTestClient, num_rpcs: int): |
||||||
|
test_client.wait_for_active_server_channel() |
||||||
|
lb_stats = get_client_rpc_stats(test_client, num_rpcs) |
||||||
|
for backend, rpcs_count in lb_stats.rpcs_by_peer.items(): |
||||||
|
if int(rpcs_count) < 1: |
||||||
|
raise AssertionError( |
||||||
|
f'Backend {backend} did not receive a single RPC') |
||||||
|
|
||||||
|
failed = int(lb_stats.num_failures) |
||||||
|
if int(lb_stats.num_failures) > 0: |
||||||
|
raise AssertionError( |
||||||
|
f'Expected all RPCs to succeed: {failed} of {num_rpcs} failed') |
||||||
|
|
||||||
|
|
||||||
|
def main(argv): |
||||||
|
if len(argv) > 1: |
||||||
|
raise app.UsageError('Too many command-line arguments.') |
||||||
|
|
||||||
|
# Must be called before KubernetesApiManager or GcpApiManager init. |
||||||
|
xds_flags.set_socket_default_timeout_from_flag() |
||||||
|
|
||||||
|
# Flags. |
||||||
|
should_port_forward: bool = xds_k8s_flags.DEBUG_USE_PORT_FORWARDING.value |
||||||
|
is_secure: bool = _SECURE.value |
||||||
|
|
||||||
|
# Setup. |
||||||
|
gcp_api_manager = gcp.api.GcpApiManager() |
||||||
|
k8s_api_manager = k8s.KubernetesApiManager(xds_k8s_flags.KUBE_CONTEXT.value) |
||||||
|
|
||||||
|
# Server. |
||||||
|
server_namespace = common.make_server_namespace(k8s_api_manager) |
||||||
|
server_runner = common.make_server_runner( |
||||||
|
server_namespace, |
||||||
|
gcp_api_manager, |
||||||
|
port_forwarding=should_port_forward, |
||||||
|
secure=is_secure) |
||||||
|
# Find server pod. |
||||||
|
server_pod: k8s.V1Pod = common.get_server_pod(server_runner, |
||||||
|
xds_flags.SERVER_NAME.value) |
||||||
|
|
||||||
|
# Client |
||||||
|
client_namespace = common.make_client_namespace(k8s_api_manager) |
||||||
|
client_runner = common.make_client_runner( |
||||||
|
client_namespace, |
||||||
|
gcp_api_manager, |
||||||
|
port_forwarding=should_port_forward, |
||||||
|
secure=is_secure) |
||||||
|
# Find client pod. |
||||||
|
client_pod: k8s.V1Pod = common.get_client_pod(client_runner, |
||||||
|
xds_flags.CLIENT_NAME.value) |
||||||
|
|
||||||
|
# Ensure port forwarding stopped. |
||||||
|
common.register_graceful_exit(server_runner, client_runner) |
||||||
|
|
||||||
|
# Create server app for the server pod. |
||||||
|
test_server: _XdsTestServer = common.get_test_server_for_pod( |
||||||
|
server_runner, |
||||||
|
server_pod, |
||||||
|
test_port=xds_flags.SERVER_PORT.value, |
||||||
|
secure_mode=is_secure) |
||||||
|
test_server.set_xds_address(xds_flags.SERVER_XDS_HOST.value, |
||||||
|
xds_flags.SERVER_XDS_PORT.value) |
||||||
|
|
||||||
|
# Create client app for the client pod. |
||||||
|
test_client: _XdsTestClient = common.get_test_client_for_pod( |
||||||
|
client_runner, client_pod, server_target=test_server.xds_uri) |
||||||
|
|
||||||
|
with test_client, test_server: |
||||||
|
run_ping_pong(test_client, _NUM_RPCS.value) |
||||||
|
|
||||||
|
logger.info('SUCCESS!') |
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__': |
||||||
|
app.run(main) |
Loading…
Reference in new issue