Skip to content

Commit cb7206b

Browse files
committed
Make S3 request failures more verbose and add token expiry test
1 parent 41e3d71 commit cb7206b

4 files changed

Lines changed: 190 additions & 85 deletions

File tree

tools/python/liveanalytics_influxdb3_migration_plugin/liveanalytics_migration_client/liveanalytics_influxdb3_migration_client.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@
1111
"""
1212

1313
import argparse
14-
import sys
14+
import json
15+
import logging
1516
import os
17+
import sys
1618
import time
19+
from datetime import datetime, timezone
20+
1721
import boto3
18-
from botocore.exceptions import ClientError, BotoCoreError
19-
from influxdb_client_3 import InfluxDBClient3, WritePrecision
20-
import json
21-
import logging
2222
import requests
23-
from datetime import datetime, timezone
23+
from botocore.exceptions import BotoCoreError, ClientError
24+
from influxdb_client_3 import InfluxDBClient3, WritePrecision
2425

2526
MIGRATION_METADATA_TABLE: str = "liveanalytics_migration_metadata"
2627
TRIGGER_NAME: str = "migration_trigger"
@@ -38,6 +39,7 @@ def __init__(
3839
timeout_seconds: int = 360, # 6 minutes.
3940
region: str = "us-west-2",
4041
max_parquet_files: int | None = None,
42+
presigned_url_expiry_seconds: int | None = None,
4143
) -> None:
4244
"""
4345
Initialize
@@ -49,6 +51,7 @@ def __init__(
4951
timeout_seconds (int): The number of seconds to wait for each migration request.
5052
region (str): The AWS Region to use.
5153
max_parquet_files (int | None): The maximum number of Parquet files to migrate.
54+
presigned_url_expiry_seconds (int | None): The expiry time for all presigned URLs.
5255
5356
Returns:
5457
None
@@ -59,6 +62,7 @@ def __init__(
5962
self.timeout_seconds: int = timeout_seconds
6063
self.region: str = region
6164
self.max_parquet_files: int | None = max_parquet_files
65+
self.presigned_url_expiry_seconds: int | None = presigned_url_expiry_seconds
6266

6367
logging.basicConfig(
6468
level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s"
@@ -582,7 +586,7 @@ def get_s3_objects_list(
582586
return list(files_to_migrate)
583587

584588
def generate_metadata(
585-
self, parquet_file_names: list[str], expiration: int = 604_800
589+
self, parquet_file_names: list[str], expiration: int | None = 604_800
586590
) -> dict[str, dict[str, str]]:
587591
"""
588592
Generates metadata, including presigned URLs for S3 objects.
@@ -675,7 +679,7 @@ def setup_influxdb_metadata(self) -> bool:
675679
)
676680
return False
677681
except requests.exceptions.RequestException as e:
678-
self.error(f"Error creating database: ", str(e))
682+
self.error("Error creating database: ", str(e))
679683
return False
680684

681685
self.delete_metadata_table(silence_errors=True)
@@ -707,12 +711,12 @@ def setup_influxdb_metadata(self) -> bool:
707711
)
708712
return False
709713
except requests.exceptions.RequestException as e:
710-
self.error(f"Error creating table: ", str(e))
714+
self.error("Error creating table: ", str(e))
711715
return False
712716
return True
713717

714718
except Exception as e:
715-
self.error(f"Failed to setup InfluxDB metadata: ", str(e))
719+
self.error("Failed to setup InfluxDB metadata: ", str(e))
716720
sys.exit(1)
717721

718722
def write_metadata_to_influxdb(self, metadata):
@@ -783,7 +787,7 @@ def write_metadata_to_influxdb(self, metadata):
783787
)
784788

785789
except Exception as e:
786-
self.error(f"Failed to write metadata to InfluxDB: ", str(e))
790+
self.error("Failed to write metadata to InfluxDB: ", str(e))
787791
sys.exit(1)
788792

789793
def bulk_invoke_http_trigger(self, metadata):
@@ -1012,7 +1016,7 @@ def create_processing_engine_trigger(self):
10121016
None
10131017
"""
10141018
try:
1015-
self.info(f"Creating processing engine HTTP trigger")
1019+
self.info("Creating processing engine HTTP trigger")
10161020

10171021
headers = {
10181022
"Authorization": f"Bearer {self.influx_token}",
@@ -1081,7 +1085,8 @@ def run(self):
10811085
parquet_file_names: list[str] = self.get_s3_objects_list()
10821086

10831087
metadata: dict[str, dict[str, str]] = self.generate_metadata(
1084-
parquet_file_names=parquet_file_names
1088+
parquet_file_names=parquet_file_names,
1089+
expiration=self.presigned_url_expiry_seconds,
10851090
)
10861091

10871092
# Setup InfluxDB metadata.
@@ -1160,7 +1165,7 @@ def delete_unloaded_data(self):
11601165
Key=marker["Key"],
11611166
VersionId=marker["VersionId"],
11621167
)
1163-
except Exception as e:
1168+
except Exception:
11641169
self.error(
11651170
f"Failed to delete deletion marker for object {marker['Key']} with version ID {marker['VersionId']}"
11661171
)
@@ -1417,6 +1422,12 @@ def main(input_args) -> int:
14171422
type=int,
14181423
help="Optional. The maximum number of Parquet files to migrate. Default behaviour is to migrate all Parquet files.",
14191424
)
1425+
parser.add_argument(
1426+
"--presigned-url-expiry-seconds",
1427+
required=False,
1428+
type=int,
1429+
help="Optional. The number of seconds that all presigned URLs will be valid for. Default is 604800 (7 days, max).",
1430+
)
14201431

14211432
args = parser.parse_args(input_args)
14221433

@@ -1426,6 +1437,7 @@ def main(input_args) -> int:
14261437
timeout_seconds: int = args.timeout_seconds
14271438
region: str = args.region
14281439
max_parquet_files: int | None = args.max_parquet_files
1440+
presigned_url_expiry_seconds: int | None = args.presigned_url_expiry_seconds
14291441

14301442
required_env_vars = [
14311443
"INFLUXDB3_HOST_URL",
@@ -1448,6 +1460,7 @@ def main(input_args) -> int:
14481460
timeout_seconds=timeout_seconds,
14491461
region=region,
14501462
max_parquet_files=max_parquet_files,
1463+
presigned_url_expiry_seconds=presigned_url_expiry_seconds,
14511464
)
14521465

14531466
try:

tools/python/liveanalytics_influxdb3_migration_plugin/liveanalytics_migration_client/tests/integration/test_liveanalytics_influxdb3_migration_client.py

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,27 @@
1-
from concurrent.futures import ThreadPoolExecutor
2-
from datetime import datetime, timezone
3-
import os
4-
from pathlib import Path
51
import json
6-
import re
7-
import time
8-
from typing import Any
9-
import unittest
2+
import logging
3+
import os
104
import random
5+
import re
116
import string
127
import sys
13-
import logging
8+
import time
9+
import unittest
10+
from concurrent.futures import ThreadPoolExecutor
11+
from datetime import datetime, timezone
12+
from pathlib import Path
13+
from typing import Any
1414

1515
import boto3
16-
from botocore.exceptions import ClientError
1716
import pandas
17+
import pytest
1818
import requests
19+
from botocore.exceptions import ClientError
1920
from influxdb_client_3 import InfluxDBClient3
20-
from testcontainers.core.wait_strategies import LogMessageWaitStrategy
2121
from testcontainers.core.container import DockerContainer, ExecResult
22-
import pytest
22+
from testcontainers.core.wait_strategies import LogMessageWaitStrategy
23+
24+
logger = logging.getLogger()
2325

2426
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../")))
2527

@@ -352,7 +354,7 @@ def delete_s3_bucket(self, bucket_name: str):
352354
Key=marker["Key"],
353355
VersionId=marker["VersionId"],
354356
)
355-
except Exception as e:
357+
except Exception:
356358
logging.error(
357359
f"Failed to delete deletion marker for object {marker['Key']} with version ID {marker['VersionId']}"
358360
)
@@ -1023,6 +1025,54 @@ def test_migration_different_database_names(self):
10231025
influxdb_v3_table_count,
10241026
)
10251027

1028+
def test_migration_expired_presigned_urls(self):
1029+
"""
1030+
Tests migrating a single record where all presigned URLs are expired.
1031+
"""
1032+
current_time: pandas.Timestamp = pandas.Timestamp.now()
1033+
1034+
start_time = current_time - pandas.Timedelta(days=30)
1035+
assert isinstance(start_time, pandas.Timestamp)
1036+
end_time = start_time + pandas.Timedelta(days=1)
1037+
assert isinstance(end_time, pandas.Timestamp)
1038+
1039+
dimensions = [
1040+
{"Name": "hostname", "Value": "hostname1", "DimensionValueType": "VARCHAR"},
1041+
{"Name": "region", "Value": "us-west-2", "DimensionValueType": "VARCHAR"},
1042+
]
1043+
1044+
record = {
1045+
"Dimensions": dimensions,
1046+
"MeasureName": "cpu_utilization",
1047+
"MeasureValue": "13.5",
1048+
"MeasureValueType": "DOUBLE",
1049+
"Time": str(start_time.value),
1050+
"TimeUnit": "NANOSECONDS",
1051+
}
1052+
self.put_records([record])
1053+
1054+
with self.assertLogs(
1055+
"liveanalytics_influxdb3_migration_client", level="INFO"
1056+
) as captured_logs:
1057+
return_code = liveanalytics_influxdb3_migration_client.main(
1058+
[
1059+
"--live-analytics-database-name",
1060+
self.la_database_name,
1061+
"--s3-bucket-name",
1062+
self.s3_bucket_name,
1063+
"--presigned-url-expiry-seconds",
1064+
"0",
1065+
]
1066+
)
1067+
1068+
self.assertEqual(return_code, 1)
1069+
1070+
expected_error = "403 Client Error: Forbidden for url: *****"
1071+
self.assertTrue(
1072+
any(expected_error in log for log in captured_logs.output),
1073+
f"Expected error not found in logs: {expected_error}",
1074+
)
1075+
10261076

10271077
if __name__ == "__main__":
10281078
unittest.main()

tools/python/liveanalytics_influxdb3_migration_plugin/liveanalytics_migration_client/tests/unit/test_liveanalytics_migration_plugin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import unittest
21
import os
32
import sys
3+
import unittest
44

55
sys.path.insert(
66
0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../../../"))

0 commit comments

Comments
 (0)