99
1010import pytest
1111
12- from linode_api4 import LinodeClient , LogsStreamType , PaginatedList
12+ from linode_api4 import LinodeClient , LogsStreamType , PaginatedList , Region
1313from linode_api4 .objects import (
1414 ObjectStorageACL ,
1515 ObjectStorageBucket ,
2828 not in {"yes" , "true" },
2929 reason = f"{ _RUN_ACLP_LOGS_STREAM_TESTS } environment variable must be set to 'yes' or 'true'" ,
3030)
31+ _STREAM_FIXTURE_CLEANUP_WAIT = 2700
32+ _STREAM_FIXTURE_PROVISIONING_WAIT = 3600
3133
3234
3335@pytest .fixture (scope = "session" )
34- def create_object_storage_key (test_linode_client : LinodeClient ):
36+ def region (test_linode_client : LinodeClient ):
37+ region = get_region (test_linode_client , {"Object Storage" })
38+ yield region
39+
40+
41+ @pytest .fixture (scope = "session" )
42+ def create_object_storage_key (test_linode_client : LinodeClient , region : Region ):
3543 key = test_linode_client .object_storage .keys_create (
36- label = get_test_label (),
44+ label = get_test_label (), regions = [ region . id ]
3745 )
3846 yield key
3947 key .delete ()
@@ -43,19 +51,19 @@ def create_object_storage_key(test_linode_client: LinodeClient):
4351def test_destination (
4452 test_linode_client : LinodeClient ,
4553 create_object_storage_key : ObjectStorageKeys ,
54+ region : Region ,
4655):
4756 dest , bucket = _create_destination_with_bucket (
48- test_linode_client , create_object_storage_key
57+ test_linode_client , create_object_storage_key , region
4958 )
5059 yield dest
5160 _delete_destination_with_bucket (test_linode_client , dest , bucket )
5261
5362
5463def _create_destination_with_bucket (
55- client : LinodeClient , key : ObjectStorageKeys
64+ client : LinodeClient , key : ObjectStorageKeys , region : Region
5665):
5766 """Helper that creates an OBJ bucket and a logs destination backed by it."""
58- region = get_region (client , {"Object Storage" })
5967 bucket = client .object_storage .bucket_create (
6068 cluster_or_region = region .id ,
6169 label = get_test_label (),
@@ -79,9 +87,17 @@ def _delete_destination_with_bucket(
7987 client : LinodeClient , dest : LogsDestination , bucket : ObjectStorageBucket
8088):
8189 """Helper that deletes a logs destination and its backing OBJ bucket."""
82- send_request_when_resource_available (timeout = 1800 , func = dest .delete )
90+
91+ def no_stream_attached ():
92+ streams = client .monitor .streams ()
93+ return all (
94+ all (d .id != dest .id for d in s .destinations ) for s in streams
95+ )
96+
97+ wait_for_condition (30 , _STREAM_FIXTURE_CLEANUP_WAIT , no_stream_attached )
98+ send_request_when_resource_available (timeout = 100 , func = dest .delete )
8399 _empty_bucket (client , bucket )
84- send_request_when_resource_available (timeout = 1800 , func = bucket .delete )
100+ send_request_when_resource_available (timeout = 100 , func = bucket .delete )
85101
86102
87103def _skip_if_streams_exist (client : LinodeClient ):
@@ -304,9 +320,10 @@ def test_fails_to_create_stream_invalid_destination(invalid_destination_error):
304320def create_secondary_destination (
305321 test_linode_client : LinodeClient ,
306322 create_object_storage_key : ObjectStorageKeys ,
323+ region : Region ,
307324):
308325 dest , bucket = _create_destination_with_bucket (
309- test_linode_client , create_object_storage_key
326+ test_linode_client , create_object_storage_key , region
310327 )
311328 yield dest
312329 _delete_destination_with_bucket (test_linode_client , dest , bucket )
@@ -317,6 +334,7 @@ def create_stream(
317334 test_linode_client : LinodeClient ,
318335 test_destination : LogsDestination ,
319336 invalid_destination_error , # This ensures run order to keep negative test case deterministic
337+ create_secondary_destination : LogsDestination , # This ensures teardown order - stream must be deleted before its destinations can be deleted
320338):
321339 _skip_if_streams_exist (test_linode_client )
322340
@@ -328,7 +346,41 @@ def create_stream(
328346 assert stream .id is not None
329347 assert stream .status == LogsStreamStatus .provisioning
330348 yield stream
331- send_request_when_resource_available (timeout = 1800 , func = stream .delete )
349+ _stream_teardown (test_linode_client , stream )
350+
351+
352+ def _wait_for_stream_updatable (client : LinodeClient , stream_id : int ):
353+ """
354+ Blocks until the stream with the given id reaches active or inactive status.
355+ Updating destinations or other attributes puts the stream
356+ back into a transitional state, and attempting to delete or modify it while
357+ transitioning results in [400] errors.
358+ """
359+
360+ def is_stream_updatable ():
361+ stream = client .load (LogsStream , stream_id )
362+ return stream .status in (
363+ LogsStreamStatus .active ,
364+ LogsStreamStatus .inactive ,
365+ )
366+
367+ wait_for_condition (
368+ 30 , _STREAM_FIXTURE_PROVISIONING_WAIT , is_stream_updatable
369+ )
370+
371+
372+ def _stream_teardown (test_linode_client : LinodeClient , stream : LogsStream ):
373+ _wait_for_stream_updatable (test_linode_client , stream .id )
374+ send_request_when_resource_available (timeout = 100 , func = stream .delete )
375+
376+ # The delete request returns 200 but stream deletion is async on the backend.
377+ # Wait until the stream is fully gone before teardown continues, so that
378+ # dependent fixtures (e.g. create_secondary_destination) can proceed with teardown.
379+ def is_stream_deleted ():
380+ existing = test_linode_client .monitor .streams ()
381+ return all (s .id != stream .id for s in existing )
382+
383+ wait_for_condition (30 , _STREAM_FIXTURE_CLEANUP_WAIT , is_stream_deleted )
332384
333385
334386@pytest .fixture (scope = "session" )
@@ -347,7 +399,9 @@ def is_stream_provisioned():
347399 LogsStreamStatus .inactive ,
348400 )
349401
350- wait_for_condition (60 , 3600 , is_stream_provisioned )
402+ wait_for_condition (
403+ 60 , _STREAM_FIXTURE_PROVISIONING_WAIT , is_stream_provisioned
404+ )
351405
352406 yield test_linode_client .load (LogsStream , create_stream .id )
353407
@@ -360,15 +414,7 @@ def wait_for_updatable_status(
360414 Waits for the stream to be in an active or inactive state before a test runs.
361415 Streams can switch to `provisioning` state between updates. This makes sure the previous update is fully finished.
362416 """
363-
364- def is_stream_updatable ():
365- stream = test_linode_client .load (LogsStream , provisioned_stream .id )
366- return stream .status in (
367- LogsStreamStatus .active ,
368- LogsStreamStatus .inactive ,
369- )
370-
371- wait_for_condition (30 , 3600 , is_stream_updatable )
417+ _wait_for_stream_updatable (test_linode_client , provisioned_stream .id )
372418
373419
374420@_SKIP_STREAM_TESTS
0 commit comments