Dead Letter Queue SNS DLQ
RE CS:
There is a difference in triggering a Lambda from SNS (topic) and SQS (queue). When using SQS and an error occurs, the original message is simply put back on the queue. After the visibility timeout is exceeded, the queue will dump the message on a DLQ (depending on configuration). No error context is provided and this could take several minutes, depending on the visibility timeout. When a Lambda processes a message from SNS and a DLQ is configured on the lambda, the message is immediately put on the DLQ and the message on the DLQ has the error context. This seems like the preferable approach, although not as scalable as a SQS system for extreme message volume. But, I don’t think the systems we have worked on so far would exceed the SNS system capacity.
So there is a difference in triggering a lambda from SQS and SNS. I think SNS has the advantage in terms of responsiveness and error handling. Here is a sample error message dropped on the DLQ when triggered by SNS:
{
"version": "1.0",
"timestamp": "2023-06-16T17:57:05.098Z",
"requestContext": {
"requestId": "16d47381-f576-447f-bd38-1a7f864bda89",
"functionArn": "arn:aws:lambda:us-west-2:118234403147:function:cslater-dev-echofish-CruiseSplitterLambdaSt-Lambda-E40cQxCJ7Kz5:$LATEST",
"condition": "RetriesExhausted",
"approximateInvokeCount": 1
},
"requestPayload": {
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:us-west-2:118234403147:cslater-dev-echofish-cruise-splitter-done:427c7ca3-be8f-4a02-8da3-3486e94699d6",
"Sns": {
"Type": "Notification",
"MessageId": "3a7f4179-c464-5a83-8376-1c7f60ceb0ed",
"TopicArn": "arn:aws:sns:us-west-2:118234403147:cslater-dev-echofish-cruise-splitter-done",
"Subject": null,
"Message": "{\"cat\":\"dog\"}",
"Timestamp": "2023-06-16T17:56:58.867Z",
"SignatureVersion": "1",
"Signature": "ayDuhCrpveJ7iY0IDIJ5yz98eGeAVimFnEfyiefR8jntmLnyWOc7RDu38hMDPjvmjTfydsuN99t98XT4e8jG9gk4ybCZ1W0i6/3xOes8UVvqu8RhjBLPMKprVEnzVJnMIA0HSzmACVoPvqcY82dSEUeyHmYC7oeKabvBOPu+G6qwoE5EggW8IP1FB+DYps1KNh2Ovxy6I/jCZ+bSJcjTMSJpuloGmjR2qeSnKJxxPvTa3HtrpbYQSXNcqD8ZusDFlE9KHqTNX/kHugbPPgML0kYIXBOD/Tzy9CbSTNF53H2i9jFNb1YSsHNerjxONSsb0pwA0lRQiQJIEc3DBP3pUg==",
"SigningCertUrl": "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem",
"UnsubscribeUrl": "https://sns.us-west-2.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-west-2:118234403147:cslater-dev-echofish-cruise-splitter-done:427c7ca3-be8f-4a02-8da3-3486e94699d6",
"MessageAttributes": {
}
}
}
]
},
"responseContext": {
"statusCode": 200,
"executedVersion": "$LATEST",
"functionError": "Unhandled"
},
"responsePayload": {
"errorMessage": "The bucket is in this region: us-east-1. Please use this region to retry the request (Service: Amazon S3; Status Code: 301; Error Code: PermanentRedirect; Request ID: VDNTZR33KMDWXK0V; S3 Extended Request ID: fFQrSirLJ2OyDX8g1e8/3zPekrPGY5RFJFiBswf8Jm9NHRoD5E97fM2wB0ciDkH+t0qyWjGK+jI=; Proxy: null)",
"errorType": "com.amazonaws.services.s3.model.AmazonS3Exception",
"stackTrace": [
"com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleErrorResponse(AmazonHttpClient.java:1879)",
"com.amazonaws.http.AmazonHttpClient$RequestExecutor.handleServiceErrorResponse(AmazonHttpClient.java:1418)",
"com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeOneRequest(AmazonHttpClient.java:1387)",
"com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeHelper(AmazonHttpClient.java:1157)",
"com.amazonaws.http.AmazonHttpClient$RequestExecutor.doExecute(AmazonHttpClient.java:814)",
"com.amazonaws.http.AmazonHttpClient$RequestExecutor.executeWithTimer(AmazonHttpClient.java:781)",
"com.amazonaws.http.AmazonHttpClient$RequestExecutor.execute(AmazonHttpClient.java:755)",
"com.amazonaws.http.AmazonHttpClient$RequestExecutor.access$500(AmazonHttpClient.java:715)",
"com.amazonaws.http.AmazonHttpClient$RequestExecutionBuilderImpl.execute(AmazonHttpClient.java:697)",
"com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:561)",
"com.amazonaws.http.AmazonHttpClient.execute(AmazonHttpClient.java:541)",
"com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5470)",
"com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5417)",
"com.amazonaws.services.s3.AmazonS3Client.invoke(AmazonS3Client.java:5411)",
"com.amazonaws.services.s3.AmazonS3Client.listObjects(AmazonS3Client.java:929)",
"com.amazonaws.services.s3.AmazonS3Client.listObjects(AmazonS3Client.java:903)",
"edu.colorado.cires.cmg.echofish.data.s3.S3OperationsImpl.listObjects(S3OperationsImpl.java:70)",
"edu.colorado.cires.cmg.echofish.aws.lambda.cruisesplit.CruiseSplitterLambdaHandler.getRawFiles(CruiseSplitterLambdaHandler.java:71)",
"edu.colorado.cires.cmg.echofish.aws.lambda.cruisesplit.CruiseSplitterLambdaHandler.handleRequest(CruiseSplitterLambdaHandler.java:46)",
"edu.colorado.cires.cmg.echofish.aws.lambda.cruisesplit.CruiseSplitterLambda.handleRequest(CruiseSplitterLambda.java:47)",
"java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)",
"java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)",
"java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)",
"java.base/java.lang.reflect.Method.invoke(Unknown Source)"
]
}
}
And that error was immediately put on the DLQ. When using SQS, all that you get on the DLQ is the original message and that message doesn’t show up until after the visibility timeout.
J: I’m not sure I understand how one would configure a DLQ on an SNS topic or on a Lambda itself. I’ve only used DLQs on the SQS queues.
C: There is no DLQ on a SNS topic. However you can add one on a Lambda that is fed by a SNS topic
C: Here is the CF template for that:
LambdaEvent: Type: AWS::Lambda::EventInvokeConfig Properties: FunctionName: !Ref Lambda Qualifier: $LATEST MaximumRetryAttempts: 0 DestinationConfig: OnFailure: Destination: !Ref DeadLetterTopicArn
C: The destination can be a variety of endpoints. This uses a topic, but it could be a queue
C: It is available in the console too, under the destination config
C: This does not work if the Lambda is fed by SQS though
C: It needs to be asynchronous. SQS invocations are synchronous.
Testing with one sns topic into lambda and two sns topics out (one success, one DLQ):
aws --profile "echofish" sns publish --topic-arn "arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete" --message '{"ship":"Henry_B._Bigelow","cruise":"HB0707","sensor":"EK60","file":"AAA.raw"}'
Testing error with out-of-time, message:
ends up in DLQ
{"version":"1.0","timestamp":"2023-06-26T20:07:13.432Z","requestContext":{"requestId":"670e04ff-eb30-4b22-8da8-1f3eb16618dc","functionArn":"arn:aws:lambda:us-east-1:118234403147:function:rudy-delete-2:$LATEST","condition":"RetriesExhausted","approximateInvokeCount":1},"requestPayload":{"Records":[{"EventSource":"aws:sns","EventVersion":"1.0","EventSubscriptionArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","Sns":{"Type":"Notification","MessageId":"246b01d0-52c5-596a-b0f7-5cc3a0ebfef0","TopicArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete","Subject":null,"Message":"{\"ship\":\"Henry_B._Bigelow\",\"cruise\":\"HB0707\",\"sensor\":\"EK60\",\"file\":\"GGG.raw\"}","Timestamp":"2023-06-26T20:07:10.527Z","SignatureVersion":"1","Signature":"oYGSB16rMiPzgqQP0vI/1bkiKKqQaEtQ3NqjUX7bu3lURZx0yPVJubo4etZw0rPe/y1PjqFef7YilJLVW16Ocfw2kJXRLHNiMDpOSBIGUQUWjSAbBXsOOHuoqhSZ3DbIhpPK6nfwTe9rgI8JpQLs7w7M5/XEpxFwrI8bIwAjgwNelRH3CkoQ8G06LEmpKkVd4C2RKu/0vxgNVpLmPB5ORRySczR/UXYXphkQZXjjJPkCUoLHOB5eAA4QpLIC/6u8ZHGhvy1UbI8s4ik16KbHzr73typO6JyPdSwXxl6Dm4UG+3bDIGmCqWPSO+1EpIB58IxScTsASpm6fmJN8C9JmQ==","SigningCertUrl":"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem","UnsubscribeUrl":"https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","MessageAttributes":{}}}]},"responseContext":{"statusCode":200,"executedVersion":"$LATEST","functionError":"Unhandled"},"responsePayload":{"errorMessage":"2023-06-26T20:07:13.315Z 670e04ff-eb30-4b22-8da8-1f3eb16618dc Task timed out after 2.00 seconds"}}
"responsePayload": {
"errorMessage": "2023-06-26T20:07:13.315Z 670e04ff-eb30-4b22-8da8-1f3eb16618dc Task timed out after 2.00 seconds"
}
Testing error with out-of-memory, message:
ends up in DLQ
{"version":"1.0","timestamp":"2023-06-26T20:12:05.835Z","requestContext":{"requestId":"5ecb663d-0033-4157-9187-b0802ceb3689","functionArn":"arn:aws:lambda:us-east-1:118234403147:function:rudy-delete-2:$LATEST","condition":"RetriesExhausted","approximateInvokeCount":1},"requestPayload":{"Records":[{"EventSource":"aws:sns","EventVersion":"1.0","EventSubscriptionArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","Sns":{"Type":"Notification","MessageId":"a8fb6d73-8e4f-5941-92d4-30607c9441d9","TopicArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete","Subject":null,"Message":"{\"ship\":\"Henry_B._Bigelow\",\"cruise\":\"HB0707\",\"sensor\":\"EK60\",\"file\":\"III.raw\"}","Timestamp":"2023-06-26T20:10:03.965Z","SignatureVersion":"1","Signature":"aCs3ae1WcpqQl6podwUGnvRMGy2VXXJpthXA+jvTqX5iZZIsxqy+R6Wx5Kun4eOC9Pky268hP5hINO8x2DBy4W50WZVzUssL8et9WLn21USG/HJQpl/zpu2NcdinsXebVj7Rxx/vwPKzi+mLaPTw94cc714Gw+h91vCPpr+y76lz+yIngJ2S1FffEX/bJ8mNKeqIkE2jC+TrgIifcQxB+D0QIc7WPrG6ln4uqhTN8NY+PTbl9sxNkhLlh9LeNIvmckus67AcYg/zW6Ty8ksNbU3SRWhxsuOut2MIw77+4mKPj5qlcKhScyh99pzunGr9WukszFzIUHJTQo0TM9FuDw==","SigningCertUrl":"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem","UnsubscribeUrl":"https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","MessageAttributes":{}}}]},"responseContext":{"statusCode":200,"executedVersion":"$LATEST","functionError":"Unhandled"},"responsePayload":{"errorType":"Runtime.ExitError","errorMessage":"RequestId: 5ecb663d-0033-4157-9187-b0802ceb3689 Error: Runtime exited with error: signal: killed"}}
"errorMessage": "RequestId: 5ecb663d-0033-4157-9187-b0802ceb3689 Error: Runtime exited with error: signal: killed"
"responsePayload": {
"errorType": "Runtime.ExitError",
"errorMessage": "RequestId: 5ecb663d-0033-4157-9187-b0802ceb3689 Error: Runtime exited with error: signal: killed"
}
Testint nominal:
ends up in
{"version":"1.0","timestamp":"2023-06-26T20:40:12.972Z","requestContext":{"requestId":"695c7c40-a562-4b81-8cbd-443a17a9ffc8","functionArn":"arn:aws:lambda:us-east-1:118234403147:function:rudy-delete-2:$LATEST","condition":"Success","approximateInvokeCount":1},"requestPayload":{"Records":[{"EventSource":"aws:sns","EventVersion":"1.0","EventSubscriptionArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","Sns":{"Type":"Notification","MessageId":"6f548af6-fde2-5390-8b4b-d7e5040e84e5","TopicArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete","Subject":null,"Message":"{\"ship\":\"Henry_B._Bigelow\",\"cruise\":\"HB0707\",\"sensor\":\"EK60\",\"file\":\"NNN.raw\"}","Timestamp":"2023-06-26T20:40:12.315Z","SignatureVersion":"1","Signature":"hhmLia4pe5HekhLonD4IEE77OSJiGYQVkLicGWw2e0LBPCs7AbN6xq5lAm19DZ8yrdSZGZZOd0xDrvrwv8nu3A2Sp3R6OXeULk2Vh3aWyZjjCaCxt5gSvwfRD4MgZK1sqFVV+xQXmhuO0jnxdgg/GTV8vxQSqHkPcW0z7t8JYTvcxo8DHsf0/zYV5szZ7uNJDznU1DGINSE8omZRit7pvUXQ8NF7uOBkTDxu82l3uZQY4TqSAvWz8UCrZ6zzpVXXKj89womlXgPD4T9AAxpA2FZJ6Yiyz2kYjoQTCJZdgwq7DerztzYrCADGPTn3yy+sDgrbpQVIBe2Mj68VTG5k5Q==","SigningCertUrl":"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem","UnsubscribeUrl":"https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","MessageAttributes":{}}}]},"responseContext":{"statusCode":200,"executedVersion":"$LATEST"},"responsePayload":{"status": "success123"}}
"responsePayload": {
"status": "success123"
}
Test exception:
{"version":"1.0","timestamp":"2023-06-26T20:23:31.300Z","requestContext":{"requestId":"adf9603b-3e8c-48b9-a077-6210b65ca7b1","functionArn":"arn:aws:lambda:us-east-1:118234403147:function:rudy-delete-2:$LATEST","condition":"Success","approximateInvokeCount":1},"requestPayload":{"Records":[{"EventSource":"aws:sns","EventVersion":"1.0","EventSubscriptionArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","Sns":{"Type":"Notification","MessageId":"3f4cd070-0c7a-5938-9168-8d63fa78fe70","TopicArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete","Subject":null,"Message":"{\"ship\":\"Henry_B._Bigelow\",\"cruise\":\"HB0707\",\"sensor\":\"EK60\",\"file\":\"LLL.raw\"}","Timestamp":"2023-06-26T20:23:30.604Z","SignatureVersion":"1","Signature":"PZMQ2gG6sEjZ3AcxagOSS1FxKbPvlULGR0Yl5KnFQzPk+5uL86sxhiNXIlId2qTyEYg0v9E74VvT83bAfbP7+gPGviywzsDFePxYTFEc1g4uPGvt0FKAFVcxOKdW5v7JWcFcnME6aPQk14dR6yuPnSkm+y5wATvkF+U6+VXt7JMxwS3tESNl5ksCq9qQhHcf0LaVapIfLNNekKUMCLlcVhRy4pIe+qUHtKbrbWL7FeboaaRXcaBqn9pJvx6dn+YUQTLmX+WfZz9PTK4EJLuivdowPj3A3GmB7cZGSuV9igwih8RkrneHdgu9eKqJwAhMWjcwniiYvXSX+AiGBhYMAw==","SigningCertUrl":"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem","UnsubscribeUrl":"https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","MessageAttributes":{}}}]},"responseContext":{"statusCode":200,"executedVersion":"$LATEST"},"responsePayload":{"status": "success123"}}
"responsePayload": {
"status": "success123"
}
Test exception with raise:
{"version":"1.0","timestamp":"2023-06-26T20:24:46.195Z","requestContext":{"requestId":"028ad37e-9ce5-4c00-8fd3-ba325556d0b3","functionArn":"arn:aws:lambda:us-east-1:118234403147:function:rudy-delete-2:$LATEST","condition":"RetriesExhausted","approximateInvokeCount":1},"requestPayload":{"Records":[{"EventSource":"aws:sns","EventVersion":"1.0","EventSubscriptionArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","Sns":{"Type":"Notification","MessageId":"b2ea80ff-0f9a-57b2-b47e-ddecf9d7e370","TopicArn":"arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete","Subject":null,"Message":"{\"ship\":\"Henry_B._Bigelow\",\"cruise\":\"HB0707\",\"sensor\":\"EK60\",\"file\":\"MMM.raw\"}","Timestamp":"2023-06-26T20:24:45.442Z","SignatureVersion":"1","Signature":"MaTC3e0jyqKQHqOAdDwLaL+Iua/TLzc8xt0aL/Vs2/NAvJkWX7jXk4pa7W/eWt1EXq2JsTFX7cxMub45XwDKDLjzn+Sg0RBNO5MM4ubmJC4mEfLFyX4FYiKRsfUiHfBKjhPADI8UXgVe2Gm9bnGd8if56F8H7QB8tzAO9hEu8A4tAOUMD/fseI8R5lmS14oQbnaLjTb9xr7e7uvtsBv9O5aVZc2X3ByOKC/C9Np0vQW70Fd0WEQJL6mK/Oj1eYaLqxcrKxuuQD4kGjt6XZWlcBmZqzWhM478n3q7Tlxd6mW/pfmoXEzVSIFc3JEO0suQRQjFSxDs80wMoIsWId6jUQ==","SigningCertUrl":"https://sns.us-east-1.amazonaws.com/SimpleNotificationService-01d088a6f77103d0fe307c0069e40ed6.pem","UnsubscribeUrl":"https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:118234403147:rudy-standard-test-input-delete:cad59843-86c9-4f7e-90c5-0fd4e900a2bb","MessageAttributes":{}}}]},"responseContext":{"statusCode":200,"executedVersion":"$LATEST","functionError":"Unhandled"},"responsePayload":{"errorMessage": "Assertion failed here.", "errorType": "AssertionError", "requestId": "028ad37e-9ce5-4c00-8fd3-ba325556d0b3", "stackTrace": [" File \"/var/task/lambda_function.py\", line 67, in lambda_handler\n test_throw_exception()\n", " File \"/var/task/lambda_function.py\", line 29, in test_throw_exception\n assert (1 > 2), \"Assertion failed here.\"\n"]}}
"responsePayload": {
"errorMessage": "Assertion failed here.",
"errorType": "AssertionError",
"requestId": "028ad37e-9ce5-4c00-8fd3-ba325556d0b3",
"stackTrace": [" File \"/var/task/lambda_function.py\", line 67, in lambda_handler\n test_throw_exception()\n", " File \"/var/task/lambda_function.py\", line 29, in test_throw_exception\n assert (1 > 2), \"Assertion failed here.\"\n"]
}
With lambda code:
import json
import logging
import os
import time
import random
import boto3
from datetime import datetime
logging.basicConfig(level=logging.DEBUG)
logger=logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
def test_out_of_time():
random_number = random.randint(10, 20)
logger.info(f"Testing out of time, random_number is: {random_number}")
time.sleep(random_number)
def test_out_of_memory():
s = []
logger.info(f"Testing out of memory")
for i in range(1000000):
for j in range(1000000):
for k in range(1000000):
s.append("More")
def test_throw_exception():
try:
assert (1 > 2), "Assertion failed here."
except Exception as e:
logger.info('Exception error in function')
logger.error(e)
raise
def lambda_handler(event: dict, context: dict) -> dict:
logger.info(event)
#body = json.loads(event['Records'][0]['body'])
#message = event # json.loads(body["Message"])
message = json.loads(event['Records'][0]['Sns']['Message'])
#
logger.info(message['ship'])
logger.info(message['cruise'])
logger.info(message['sensor'])
logger.info(message['file'])
#
#test_out_of_time()
#test_out_of_memory()
#
#try:
# assert (1 > 2), "Assertion failed here2."
#except Exception as e:
# logger.info('Exception error2')
# logger.error(e)
# logger.info('Raise2')
# raise
"""
try:
test_throw_exception()
except Exception as e:
logger.error(e)
logger.info('Exception was caught')
return '{ "statusCode": 400, "body": "Echopype failed" }'
else:
logger.info('Exception was not caught')
"""
#test_throw_exception()
#
logger.info("Made it past the exception though.")
now = datetime.now()
current_time = now.strftime("%H:%M:%S %p")
message.update(current_time=current_time)
#
#sqs = boto3.client('sqs')
#url = "https://sqs.us-east-1.amazonaws.com/118234403147/rudy-standard-happy-path-test-delete"
#sqs.send_message(QueueUrl=url, MessageBody=str(message))
#
#sns_client = boto3.client('sns')
#response = sns_client.publish(TopicArn='arn:aws:sns:us-east-1:118234403147:rudy-standard-test-delete', Message=str(message))
#logger.info(response)
#
return {"status": "success123"}
Last updated
Was this helpful?