Skip to content

chore: [FOSS] Sync Public: Add retry to flaky APIGW tests and remove redundant CF calls #2433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import logging
from unittest.case import skipIf

import pytest
from tenacity import stop_after_attempt, retry_if_exception_type, after_log, wait_exponential, retry, wait_random

from integration.helpers.base_test import BaseTest
Expand All @@ -14,6 +15,7 @@
current_region_does_not_support([GATEWAY_RESPONSES]), "GatewayResponses is not supported in this testing region"
)
class TestApiWithGatewayResponses(BaseTest):
@pytest.mark.flaky(reruns=5)
def test_gateway_responses(self):
self.create_and_verify_stack("combination/api_with_gateway_responses")

Expand Down Expand Up @@ -45,7 +47,7 @@ def test_gateway_responses(self):
reraise=True,
)
def _verify_request_response_and_cors(self, url, expected_response):
response = self.verify_get_request_response(url, expected_response)
response = self.verify_get_request_response(url, expected_response, "AWS::ApiGateway::RestApi")
access_control_allow_origin = response.headers.get("Access-Control-Allow-Origin", "")
self.assertEqual(access_control_allow_origin, "*", "Access-Control-Allow-Origin must be '*'")

Expand Down
13 changes: 10 additions & 3 deletions integration/combination/test_function_with_http_api.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
import logging
from unittest.case import skipIf

import pytest

from integration.helpers.base_test import BaseTest
from integration.helpers.resource import current_region_does_not_support
from integration.config.service_names import HTTP_API

LOG = logging.getLogger(__name__)


@skipIf(current_region_does_not_support([HTTP_API]), "HttpApi is not supported in this testing region")
class TestFunctionWithHttpApi(BaseTest):
@pytest.mark.flaky(reruns=5)
def test_function_with_http_api(self):
self.create_and_verify_stack("combination/function_with_http_api")

stack_outputs = self.get_stack_outputs()
base_url = stack_outputs["ApiUrl"]
self.verify_get_request_response(base_url + "some/path", 200)
self.verify_get_request_response(base_url + "something", 404)
self.verify_get_request_response(base_url + "another/endpoint", 404)
resource_type = "AWS::ApiGatewayV2::Api"
self.verify_get_request_response(base_url + "some/path", 200, resource_type)
self.verify_get_request_response(base_url + "something", 404, resource_type)
self.verify_get_request_response(base_url + "another/endpoint", 404, resource_type)
75 changes: 2 additions & 73 deletions integration/helpers/deployer/deployer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@
# - Moved DeployColor to colors.py
# - Removed unnecessary functions from artifact_exporter
import sys
import math
from collections import OrderedDict
import logging
import time
from datetime import datetime

from integration.helpers.resource import generate_suffix

import botocore

from integration.helpers.deployer.utils.colors import DeployColor
from integration.helpers.deployer.exceptions import exceptions as deploy_exceptions
from integration.helpers.deployer.utils.retry import retry, retry_with_exponential_backoff_and_jitter
from integration.helpers.deployer.utils.retry import retry_with_exponential_backoff_and_jitter
from integration.helpers.deployer.utils.table_print import (
pprint_column_names,
pprint_columns,
Expand Down Expand Up @@ -307,75 +307,6 @@ def get_last_event_time(self, stack_name):
except KeyError:
return time.time()

@pprint_column_names(
format_string=DESCRIBE_STACK_EVENTS_FORMAT_STRING,
format_kwargs=DESCRIBE_STACK_EVENTS_DEFAULT_ARGS,
table_header=DESCRIBE_STACK_EVENTS_TABLE_HEADER_NAME,
)
def describe_stack_events(self, stack_name, time_stamp_marker, **kwargs):
"""
Calls CloudFormation to get current stack events
:param stack_name: Name or ID of the stack
:param time_stamp_marker: last event time on the stack to start streaming events from.
:return:
"""

stack_change_in_progress = True
events = set()
retry_attempts = 0

while stack_change_in_progress and retry_attempts <= self.max_attempts:
try:

# Only sleep if there have been no retry_attempts
time.sleep(self.client_sleep if retry_attempts == 0 else 0)
describe_stacks_resp = self._client.describe_stacks(StackName=stack_name)
paginator = self._client.get_paginator("describe_stack_events")
response_iterator = paginator.paginate(StackName=stack_name)
stack_status = describe_stacks_resp["Stacks"][0]["StackStatus"]
latest_time_stamp_marker = time_stamp_marker
for event_items in response_iterator:
for event in event_items["StackEvents"]:
if event["EventId"] not in events and utc_to_timestamp(event["Timestamp"]) > time_stamp_marker:
events.add(event["EventId"])
latest_time_stamp_marker = max(
latest_time_stamp_marker, utc_to_timestamp(event["Timestamp"])
)
row_color = self.deploy_color.get_stack_events_status_color(status=event["ResourceStatus"])
pprint_columns(
columns=[
event["ResourceStatus"],
event["ResourceType"],
event["LogicalResourceId"],
event.get("ResourceStatusReason", "-"),
],
width=kwargs["width"],
margin=kwargs["margin"],
format_string=DESCRIBE_STACK_EVENTS_FORMAT_STRING,
format_args=kwargs["format_args"],
columns_dict=DESCRIBE_STACK_EVENTS_DEFAULT_ARGS.copy(),
color=row_color,
)
# Skip already shown old event entries
elif utc_to_timestamp(event["Timestamp"]) <= time_stamp_marker:
time_stamp_marker = latest_time_stamp_marker
break
else: # go to next loop if not break from inside loop
time_stamp_marker = latest_time_stamp_marker # update marker if all events are new
continue
break # reached here only if break from inner loop!

if self._check_stack_complete(stack_status):
stack_change_in_progress = False
break
except botocore.exceptions.ClientError as ex:
retry_attempts = retry_attempts + 1
if retry_attempts > self.max_attempts:
LOG.error("Describing stack events for %s failed: %s", stack_name, str(ex))
return
# Sleep in exponential backoff mode
time.sleep(math.pow(self.backoff, retry_attempts))

def _check_stack_complete(self, status):
return "COMPLETE" in status and "CLEANUP" not in status

Expand All @@ -386,8 +317,6 @@ def wait_for_execute(self, stack_name, changeset_type):
)
sys.stdout.flush()

self.describe_stack_events(stack_name, self.get_last_event_time(stack_name))

# Pick the right waiter
if changeset_type == "CREATE":
waiter = self._client.get_waiter("stack_create_complete")
Expand Down
7 changes: 6 additions & 1 deletion integration/single/test_basic_function.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import logging
from unittest.case import skipIf

from integration.config.service_names import KMS, XRAY, ARM, CODE_DEPLOY, HTTP_API
import pytest

from integration.config.service_names import KMS, XRAY, ARM, CODE_DEPLOY, HTTP_API
from integration.helpers.resource import current_region_does_not_support
from parameterized import parameterized
from integration.helpers.base_test import BaseTest

LOG = logging.getLogger(__name__)


class TestBasicFunction(BaseTest):
"""
Expand Down Expand Up @@ -36,6 +40,7 @@ def test_basic_function(self, file_name):
"single/function_alias_with_http_api_events",
]
)
@pytest.mark.flaky(reruns=5)
@skipIf(current_region_does_not_support([HTTP_API]), "HTTP API is not supported in this testing region")
def test_function_with_http_api_events(self, file_name):
self.create_and_verify_stack(file_name)
Expand Down