mirror of https://github.com/grpc/grpc.git
Add the daily xDS interop GCP resource cleanup script (#28315)
* Add the daily xDS interop GCP resource cleanup script
* Add keep config feature && speed-up checking phase
* DO-NOT-SUBMIT: borrow Python test to ad-hoc run the new kokoro job
* Pin Python 3.6
* Kokoro is using pyenv to manage Python versions
* Install "dataclassses" package for 3.6.1
* Remove the latest 'test=' subprocess argument
* Revert "DO-NOT-SUBMIT: borrow Python test to ad-hoc run the new kokoro job"
This reverts commit 86d69dcb92
.
* Clean the keep config
pull/28343/head
parent
240557a55c
commit
f8a5022a26
4 changed files with 255 additions and 0 deletions
@ -0,0 +1,202 @@ |
||||
# Copyright 2021 The 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. |
||||
|
||||
import argparse |
||||
from dataclasses import dataclass |
||||
import datetime |
||||
import functools |
||||
import json |
||||
import logging |
||||
import os |
||||
import re |
||||
import subprocess |
||||
import sys |
||||
from typing import Any, List |
||||
|
||||
# Parses commandline arguments |
||||
parser = argparse.ArgumentParser() |
||||
parser.add_argument('--dry_run', |
||||
action='store_true', |
||||
help='print the deletion command without execution') |
||||
parser.add_argument('--clean_psm_sec', |
||||
action='store_true', |
||||
help='whether to enable PSM Security resource cleaning') |
||||
args = parser.parse_args() |
||||
|
||||
# Type alias |
||||
Json = Any |
||||
|
||||
# Configures this script |
||||
KEEP_PERIOD = datetime.timedelta(days=14) |
||||
GCLOUD = os.environ.get('GCLOUD', 'gcloud') |
||||
GCLOUD_CMD_TIMEOUT_S = datetime.timedelta(seconds=5).total_seconds() |
||||
ZONE = 'us-central1-a' |
||||
SECONDARY_ZONE = 'us-west1-b' |
||||
PROJECT = 'grpc-testing' |
||||
PSM_SECURITY_PREFIX = 'xds-k8s-security' |
||||
|
||||
# Global variables |
||||
KEEP_CONFIG = None |
||||
|
||||
|
||||
def load_keep_config() -> None: |
||||
global KEEP_CONFIG |
||||
json_path = os.path.realpath( |
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), |
||||
'keep_xds_interop_resources.json')) |
||||
with open(json_path, 'r') as f: |
||||
KEEP_CONFIG = json.load(f) |
||||
logging.debug('Resource keep config loaded: %s', |
||||
json.dumps(KEEP_CONFIG, indent=2)) |
||||
|
||||
|
||||
def is_marked_as_keep_gce(suffix: str) -> bool: |
||||
return suffix in KEEP_CONFIG["gce_framework"]["suffix"] |
||||
|
||||
|
||||
@functools.lru_cache() |
||||
def get_expire_timestamp() -> str: |
||||
return (datetime.datetime.now() - KEEP_PERIOD).isoformat() |
||||
|
||||
|
||||
def exec_gcloud(*cmds: List[str]) -> Json: |
||||
cmds = [GCLOUD, '--project', PROJECT, '--quiet'] + list(cmds) |
||||
if 'list' in cmds: |
||||
# Add arguments to shape the list output |
||||
cmds.extend([ |
||||
'--format', 'json', '--filter', |
||||
f'creationTimestamp <= {get_expire_timestamp()}' |
||||
]) |
||||
if args.dry_run and 'delete' in cmds: |
||||
# Skip deletion for dry-runs |
||||
logging.debug('> Skipped[Dry Run]: %s', " ".join(cmds)) |
||||
return None |
||||
# Executing the gcloud command |
||||
logging.debug('Executing: %s', " ".join(cmds)) |
||||
proc = subprocess.Popen(cmds, |
||||
stdout=subprocess.PIPE, |
||||
stderr=subprocess.PIPE) |
||||
# NOTE(lidiz) the gcloud subprocess won't return unless its output is read |
||||
stdout = proc.stdout.read() |
||||
stderr = proc.stderr.read() |
||||
try: |
||||
returncode = proc.wait(timeout=GCLOUD_CMD_TIMEOUT_S) |
||||
except subprocess.TimeoutExpired: |
||||
logging.error('> Timeout executing cmd [%s]', " ".join(cmds)) |
||||
return None |
||||
if returncode: |
||||
logging.error('> Failed to execute cmd [%s], returned %d, stderr: %s', |
||||
" ".join(cmds), returncode, stderr) |
||||
return None |
||||
if stdout: |
||||
return json.loads(stdout) |
||||
return None |
||||
|
||||
|
||||
def remove_relative_resources_run_xds_tests(suffix: str): |
||||
"""Removing GCP resources created by run_xds_tests.py.""" |
||||
logging.info('Removing run_xds_tests.py resources with suffix [%s]', suffix) |
||||
exec_gcloud('compute', 'forwarding-rules', 'delete', |
||||
f'test-forwarding-rule{suffix}', '--global') |
||||
exec_gcloud('compute', 'target-http-proxies', 'delete', |
||||
f'test-target-proxy{suffix}') |
||||
exec_gcloud('alpha', 'compute', 'target-grpc-proxies', 'delete', |
||||
f'test-target-proxy{suffix}') |
||||
exec_gcloud('compute', 'url-maps', 'delete', f'test-map{suffix}') |
||||
exec_gcloud('compute', 'backend-services', 'delete', |
||||
f'test-backend-service{suffix}', '--global') |
||||
exec_gcloud('compute', 'backend-services', 'delete', |
||||
f'test-backend-service-alternate{suffix}', '--global') |
||||
exec_gcloud('compute', 'backend-services', 'delete', |
||||
f'test-backend-service-extra{suffix}', '--global') |
||||
exec_gcloud('compute', 'backend-services', 'delete', |
||||
f'test-backend-service-more-extra{suffix}', '--global') |
||||
exec_gcloud('compute', 'firewall-rules', 'delete', f'test-fw-rule{suffix}') |
||||
exec_gcloud('compute', 'health-checks', 'delete', f'test-hc{suffix}') |
||||
exec_gcloud('compute', 'instance-groups', 'managed', 'delete', |
||||
f'test-ig{suffix}', '--zone', ZONE) |
||||
exec_gcloud('compute', 'instance-groups', 'managed', 'delete', |
||||
f'test-ig-same-zone{suffix}', '--zone', ZONE) |
||||
exec_gcloud('compute', 'instance-groups', 'managed', 'delete', |
||||
f'test-ig-secondary-zone{suffix}', '--zone', SECONDARY_ZONE) |
||||
exec_gcloud('compute', 'instance-templates', 'delete', |
||||
f'test-template{suffix}') |
||||
|
||||
|
||||
def remove_relative_resources_psm_sec(suffix: str): |
||||
"""Removing GCP resources created by PSM Sec framework.""" |
||||
logging.info('Removing PSM Security resources with suffix [%s]', suffix) |
||||
exec_gcloud('compute', 'forwarding-rules', 'delete', |
||||
f'{PSM_SECURITY_PREFIX}-forwarding-rule{suffix}', '--global') |
||||
exec_gcloud('alpha', 'compute', 'target-grpc-proxies', 'delete', |
||||
f'{PSM_SECURITY_PREFIX}-target-proxy{suffix}') |
||||
exec_gcloud('compute', 'url-maps', 'delete', |
||||
f'{PSM_SECURITY_PREFIX}-url-map{suffix}') |
||||
exec_gcloud('compute', 'backend-services', 'delete', |
||||
f'{PSM_SECURITY_PREFIX}-backend-service{suffix}', '--global') |
||||
exec_gcloud('compute', 'health-checks', 'delete', |
||||
f'{PSM_SECURITY_PREFIX}-health-check{suffix}') |
||||
exec_gcloud('compute', 'firewall-rules', 'delete', |
||||
f'{PSM_SECURITY_PREFIX}-allow-health-checks{suffix}') |
||||
exec_gcloud('alpha', 'network-security', 'server-tls-policies', 'delete', |
||||
f'{PSM_SECURITY_PREFIX}-server-tls-policy{suffix}', |
||||
'--location=global') |
||||
exec_gcloud('alpha', 'network-security', 'client-tls-policies', 'delete', |
||||
f'{PSM_SECURITY_PREFIX}-client-tls-policy{suffix}', |
||||
'--location=global') |
||||
|
||||
|
||||
def check_one_type_of_gcp_resources(list_cmd: List[str], |
||||
gce_resource_matcher: str = '', |
||||
gke_resource_matcher: str = ''): |
||||
logging.info('Checking GCP resources with %s or %s', gce_resource_matcher, |
||||
gke_resource_matcher) |
||||
for resource in exec_gcloud(*list_cmd): |
||||
if gce_resource_matcher: |
||||
result = re.search(gce_resource_matcher, resource['name']) |
||||
if result is not None: |
||||
if is_marked_as_keep_gce(result.group(1)): |
||||
logging.info( |
||||
'Skip: GCE resource suffix [%s] is marked as keep', |
||||
result.group(1)) |
||||
continue |
||||
remove_relative_resources_run_xds_tests(result.group(1)) |
||||
continue |
||||
|
||||
if gke_resource_matcher and args.clean_psm_sec: |
||||
result = re.search(gke_resource_matcher, resource['name']) |
||||
if result is not None: |
||||
remove_relative_resources_psm_sec(result.group(1)) |
||||
continue |
||||
|
||||
|
||||
def check_costly_gcp_resources() -> None: |
||||
check_one_type_of_gcp_resources( |
||||
['compute', 'health-checks', 'list'], |
||||
gce_resource_matcher=r'test-hc(.*)', |
||||
gke_resource_matcher=f'{PSM_SECURITY_PREFIX}-health-check(.*)') |
||||
check_one_type_of_gcp_resources(['compute', 'instance-templates', 'list'], |
||||
gce_resource_matcher=r'test-template(.*)') |
||||
|
||||
|
||||
def main(): |
||||
load_keep_config() |
||||
logging.info('Cleaning up xDS interop resources created before %s', |
||||
get_expire_timestamp()) |
||||
check_costly_gcp_resources() |
||||
|
||||
|
||||
if __name__ == "__main__": |
||||
logging.basicConfig(level=logging.DEBUG) |
||||
main() |
@ -0,0 +1,6 @@ |
||||
{ |
||||
"gce_framework": { |
||||
"suffix": [ |
||||
] |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
# Copyright 2021 The 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. |
||||
|
||||
# Config file for the internal CI (in protobuf text format) |
||||
|
||||
# Location of the continuous shell script in repository. |
||||
build_file: "grpc/tools/internal_ci/linux/grpc_xds_resource_cleanup.sh" |
||||
timeout_mins: 120 |
||||
action { |
||||
define_artifacts { |
||||
regex: "**/*sponge_log.*" |
||||
regex: "github/grpc/reports/**" |
||||
} |
||||
} |
@ -0,0 +1,22 @@ |
||||
#!/usr/bin/env bash |
||||
# Copyright 2021 The 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 -ex |
||||
|
||||
cd "$(dirname "$0")/../../.." |
||||
|
||||
pyenv local 3.6.1 |
||||
python3 -m pip install dataclasses |
||||
python3 tools/gcp/utils/cleanup_xds_resources.py |
Loading…
Reference in new issue