|
21 | 21 | import typing
|
22 | 22 | from typing import Dict, Optional
|
23 | 23 |
|
| 24 | +import google.api_core.exceptions |
24 | 25 | import google.cloud.bigquery as bigquery
|
25 | 26 | import google.cloud.bigquery_connection_v1 as bigquery_connection_v1
|
26 | 27 | import google.cloud.exceptions
|
|
34 | 35 | import test_utils.prefixer
|
35 | 36 |
|
36 | 37 | import bigframes
|
37 |
| -from tests.system.utils import convert_pandas_dtypes |
| 38 | +import tests.system.utils |
| 39 | + |
| 40 | +# Use this to control the number of cloud functions being deleted in a single |
| 41 | +# test session. This should help soften the spike of the number of mutations per |
| 42 | +# minute tracked against a quota limit (default 60, increased to 120 for |
| 43 | +# bigframes-dev project) by the Cloud Functions API |
| 44 | +# We are running pytest with "-n 20". Let's say each session lasts about a |
| 45 | +# minute, so we are setting a limit of 120/20 = 6 deletions per session. |
| 46 | +MAX_NUM_FUNCTIONS_TO_DELETE_PER_SESSION = 6 |
38 | 47 |
|
39 | 48 | CURRENT_DIR = pathlib.Path(__file__).parent
|
40 | 49 | DATA_DIR = CURRENT_DIR.parent / "data"
|
@@ -348,7 +357,7 @@ def nested_pandas_df() -> pd.DataFrame:
|
348 | 357 | DATA_DIR / "nested.jsonl",
|
349 | 358 | lines=True,
|
350 | 359 | )
|
351 |
| - convert_pandas_dtypes(df, bytes_col=True) |
| 360 | + tests.system.utils.convert_pandas_dtypes(df, bytes_col=True) |
352 | 361 |
|
353 | 362 | df = df.set_index("rowindex")
|
354 | 363 | return df
|
@@ -400,7 +409,7 @@ def scalars_pandas_df_default_index() -> pd.DataFrame:
|
400 | 409 | DATA_DIR / "scalars.jsonl",
|
401 | 410 | lines=True,
|
402 | 411 | )
|
403 |
| - convert_pandas_dtypes(df, bytes_col=True) |
| 412 | + tests.system.utils.convert_pandas_dtypes(df, bytes_col=True) |
404 | 413 |
|
405 | 414 | df = df.set_index("rowindex", drop=False)
|
406 | 415 | df.index.name = None
|
@@ -1040,3 +1049,55 @@ def floats_bf(session, floats_pd):
|
1040 | 1049 | @pytest.fixture()
|
1041 | 1050 | def floats_product_bf(session, floats_product_pd):
|
1042 | 1051 | return session.read_pandas(floats_product_pd)
|
| 1052 | + |
| 1053 | + |
| 1054 | +@pytest.fixture(scope="session", autouse=True) |
| 1055 | +def cleanup_cloud_functions(session, cloudfunctions_client, dataset_id_permanent): |
| 1056 | + """Clean up stale cloud functions.""" |
| 1057 | + permanent_endpoints = tests.system.utils.get_remote_function_endpoints( |
| 1058 | + session.bqclient, dataset_id_permanent |
| 1059 | + ) |
| 1060 | + delete_count = 0 |
| 1061 | + for cloud_function in tests.system.utils.get_cloud_functions( |
| 1062 | + cloudfunctions_client, |
| 1063 | + session.bqclient.project, |
| 1064 | + session.bqclient.location, |
| 1065 | + name_prefix="bigframes-", |
| 1066 | + ): |
| 1067 | + # Ignore bigframes cloud functions referred by the remote functions in |
| 1068 | + # the permanent dataset |
| 1069 | + if cloud_function.service_config.uri in permanent_endpoints: |
| 1070 | + continue |
| 1071 | + |
| 1072 | + # Ignore the functions less than one day old |
| 1073 | + age = datetime.now() - datetime.fromtimestamp( |
| 1074 | + cloud_function.update_time.timestamp() |
| 1075 | + ) |
| 1076 | + if age.days <= 0: |
| 1077 | + continue |
| 1078 | + |
| 1079 | + # Go ahead and delete |
| 1080 | + try: |
| 1081 | + tests.system.utils.delete_cloud_function( |
| 1082 | + cloudfunctions_client, cloud_function.name |
| 1083 | + ) |
| 1084 | + delete_count += 1 |
| 1085 | + if delete_count >= MAX_NUM_FUNCTIONS_TO_DELETE_PER_SESSION: |
| 1086 | + break |
| 1087 | + except google.api_core.exceptions.NotFound: |
| 1088 | + # This can happen when multiple pytest sessions are running in |
| 1089 | + # parallel. Two or more sessions may discover the same cloud |
| 1090 | + # function, but only one of them would be able to delete it |
| 1091 | + # successfully, while the other instance will run into this |
| 1092 | + # exception. Ignore this exception. |
| 1093 | + pass |
| 1094 | + except google.api_core.exceptions.ResourceExhausted: |
| 1095 | + # This can happen if we are hitting GCP limits, e.g. |
| 1096 | + # google.api_core.exceptions.ResourceExhausted: 429 Quota exceeded |
| 1097 | + # for quota metric 'Per project mutation requests' and limit |
| 1098 | + # 'Per project mutation requests per minute per region' of service |
| 1099 | + # 'cloudfunctions.googleapis.com' for consumer |
| 1100 | + # 'project_number:1084210331973'. |
| 1101 | + # [reason: "RATE_LIMIT_EXCEEDED" domain: "googleapis.com" ... |
| 1102 | + # Let's stop further clean up and leave it to later. |
| 1103 | + break |
0 commit comments