Skip to content

Commit a2bda9c

Browse files
committed
First push
1 parent 9efae18 commit a2bda9c

File tree

13 files changed

+338
-0
lines changed

13 files changed

+338
-0
lines changed

.DS_Store

6 KB
Binary file not shown.

api/__init__.py

Whitespace-only changes.

api/handler.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import boto3
2+
import os, json
3+
from random import random
4+
5+
xray = boto3.client('xray')
6+
XRAY_RULE_ARN = os.environ['XRAY_RULE_ARN']
7+
8+
def lambda_handler(event, context):
9+
success = True
10+
rule = get_api_sampling_rule()
11+
iserror='false'
12+
try:
13+
iserror=event['queryStringParameters']['error']
14+
except:
15+
print("shh.. do not tell anyone!!")
16+
17+
if iserror == 'true':
18+
success = False
19+
20+
return {
21+
# fail the request randomly to trigger X-Ray rule adjustment
22+
"statusCode": 200 if success else 500,
23+
"body": json.dumps({
24+
"Success" : success,
25+
# return the rule definition to the response
26+
"SamplingRule": rule
27+
}, indent=2)
28+
}
29+
30+
def get_api_sampling_rule():
31+
response = xray.get_sampling_rules()
32+
rules = response.get('SamplingRuleRecords', [])
33+
for r in rules:
34+
rule = r.get('SamplingRule', {})
35+
if rule.get('RuleARN', '') == XRAY_RULE_ARN:
36+
return rule
37+
38+
return None

api/requirements.txt

Whitespace-only changes.

custom_resource/__init__.py

Whitespace-only changes.

custom_resource/handler.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
from crhelper import CfnResource
2+
import logging
3+
import boto3
4+
import re
5+
import os
6+
7+
# Initialise the helper, all inputs are optional, this example shows the defaults
8+
helper = CfnResource(json_logging=False, log_level='DEBUG', boto_level='CRITICAL', sleep_on_delete=120)
9+
10+
try:
11+
# Init code goes here
12+
client = boto3.client('xray')
13+
logger = logging.getLogger(__name__)
14+
except Exception as e:
15+
helper.init_failure(e)
16+
17+
18+
@helper.create
19+
def create(event, context):
20+
# Optionally return an ID that will be used for the resource PhysicalResourceId,
21+
# if None is returned an ID will be generated. If a poll_create function is defined
22+
# return value is placed into the poll event as event['CrHelperData']['PhysicalResourceId']
23+
24+
config = create_configuration(event)
25+
config['Version'] = 1
26+
27+
response = client.create_sampling_rule(SamplingRule=config)
28+
return setup_resource_properties_and_return_arn(response)
29+
30+
31+
@helper.update
32+
def update(event, context):
33+
# If the update resulted in a new resource being created, return an id for the new resource.
34+
# CloudFormation will send a delete event with the old id when stack update completes
35+
36+
arn = event['PhysicalResourceId']
37+
original_name = deserialize_sampling_rule_name(arn)
38+
new_name = event['ResourceProperties']['Name']
39+
40+
# handle change of the sampling rule's name by creating a new one (name is immutable)
41+
if original_name != new_name:
42+
return create(event, context)
43+
44+
config = create_configuration(event)
45+
response = client.update_sampling_rule(SamplingRuleUpdate=config)
46+
setup_resource_properties_and_return_arn(response)
47+
48+
49+
@helper.delete
50+
def delete(event, context):
51+
# Delete never returns anything. Should not fail if the underlying resources are already deleted.
52+
arn = event['PhysicalResourceId']
53+
client.delete_sampling_rule(RuleARN=arn)
54+
55+
56+
def setup_resource_properties_and_return_arn(response):
57+
arn = response['SamplingRuleRecord']['SamplingRule']['RuleARN']
58+
name = response['SamplingRuleRecord']['SamplingRule']['RuleName']
59+
60+
helper.Data["Arn"] = arn
61+
helper.Data["Name"] = name
62+
return arn
63+
64+
65+
def create_configuration(event):
66+
props = event['ResourceProperties']
67+
68+
return {
69+
# required properties
70+
'RuleName': props['Name'],
71+
'Priority': int(props['Priority']),
72+
'FixedRate': float(props['FixedRate']),
73+
'ReservoirSize': int(props['ReservoirSize']),
74+
# optional properties
75+
'ResourceARN': props.get('ResourceARN', '*'),
76+
'ServiceName': props.get('ServiceName', '*'),
77+
'ServiceType': props.get('ServiceType', '*'),
78+
'Host': props.get('Host', '*'),
79+
'HTTPMethod': props.get('HTTPMethod', '*'),
80+
'URLPath': props.get('URLPath', '*')
81+
}
82+
83+
84+
def deserialize_sampling_rule_name(physical_id):
85+
# parse sampling rule name from ARN
86+
pattern = r'^.+?:.+?:.+?:.+?:.+?:sampling-rule\/(.+?)$'
87+
88+
match = re.search(pattern, physical_id)
89+
if match:
90+
return match.group(1)
91+
92+
raise ValueError("Invalid sampling rule ARN!")
93+
94+
95+
def lambda_handler(event, context):
96+
helper(event, context)

custom_resource/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
crhelper==2.0.6

doc/architecture.png

49.1 KB
Loading

doc/pattern.png

19.1 KB
Loading

template.yaml

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
AWSTemplateFormatVersion: 2010-09-09
2+
Transform: AWS::Serverless-2016-10-31
3+
4+
Description: X-Ray sampling rule dynamic adjustment
5+
6+
Globals:
7+
Function:
8+
Tracing: Active
9+
Api:
10+
TracingEnabled: true
11+
12+
Resources:
13+
# Custom X-Ray sampling rule
14+
SimpleXRayRule:
15+
Type: Custom::XRaySamplingRule
16+
Properties:
17+
ServiceToken: !Sub '${XRayRuleCustomResourceLambda.Arn}'
18+
Name: MyCustomRule
19+
Priority: 20
20+
FixedRate: 0.05
21+
ReservoirSize: 1
22+
23+
# Custom CFN resource lambda
24+
XRayRuleCustomResourceLambda:
25+
Type: AWS::Serverless::Function
26+
Properties:
27+
CodeUri: custom_resource/
28+
Handler: handler.lambda_handler
29+
Runtime: python3.8
30+
Role: !Sub '${XRayRuleCustomResourceExecutionRole.Arn}'
31+
Timeout: 90
32+
XRayRuleCustomResourceExecutionRole:
33+
Type: AWS::IAM::Role
34+
Properties:
35+
AssumeRolePolicyDocument:
36+
Version: 2012-10-17
37+
Statement:
38+
- Effect: Allow
39+
Principal:
40+
Service: lambda.amazonaws.com
41+
Action: sts:AssumeRole
42+
ManagedPolicyArns:
43+
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
44+
- arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess
45+
Path: /
46+
Policies:
47+
- PolicyName: LambdaRequiredPermissions
48+
PolicyDocument:
49+
Version: 2012-10-17
50+
Statement:
51+
- Effect: Allow
52+
Action:
53+
- 'xray:CreateSamplingRule'
54+
- 'xray:UpdateSamplingRule'
55+
- 'xray:DeleteSamplingRule'
56+
Resource:
57+
- !Sub >-
58+
arn:aws:xray:${AWS::Region}:${AWS::AccountId}:sampling-rule/*
59+
60+
# Web API Lambda
61+
WebApiFunction:
62+
Type: AWS::Serverless::Function
63+
Properties:
64+
CodeUri: api/
65+
Handler: handler.lambda_handler
66+
Runtime: python3.8
67+
Role: !Sub '${WebApiExecutionRole.Arn}'
68+
Environment:
69+
Variables:
70+
XRAY_RULE_ARN: !Sub '${SimpleXRayRule.Arn}'
71+
Events:
72+
HelloWorld:
73+
Type: Api
74+
Properties:
75+
Path: /
76+
Method: get
77+
WebApiExecutionRole:
78+
Type: AWS::IAM::Role
79+
Properties:
80+
AssumeRolePolicyDocument:
81+
Version: 2012-10-17
82+
Statement:
83+
- Effect: Allow
84+
Principal:
85+
Service: lambda.amazonaws.com
86+
Action: 'sts:AssumeRole'
87+
ManagedPolicyArns:
88+
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
89+
- arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess
90+
Path: /
91+
Policies:
92+
- PolicyName: LambdaRequiredPermissions
93+
PolicyDocument:
94+
Version: 2012-10-17
95+
Statement:
96+
- Effect: Allow
97+
Action:
98+
- 'xray:GetSamplingRules'
99+
Resource:
100+
- '*'
101+
102+
# X-Ray sampling rule adjuster lambda
103+
XRayRuleAdjusterLambda:
104+
Type: AWS::Serverless::Function
105+
Properties:
106+
CodeUri: xray_rule_adjuster/
107+
Handler: handler.lambda_handler
108+
Runtime: python3.8
109+
Role: !Sub '${XRayRuleAdjusterExecutionRole.Arn}'
110+
Environment:
111+
Variables:
112+
XRAY_RULE_ARN: !Sub '${SimpleXRayRule.Arn}'
113+
Timeout: 90
114+
Events:
115+
Trigges:
116+
Type: SNS
117+
Properties:
118+
Topic:
119+
Ref: ApiGatewayNotificationTopic
120+
XRayRuleAdjusterExecutionRole:
121+
Type: AWS::IAM::Role
122+
Properties:
123+
AssumeRolePolicyDocument:
124+
Version: 2012-10-17
125+
Statement:
126+
- Effect: Allow
127+
Principal:
128+
Service: lambda.amazonaws.com
129+
Action: 'sts:AssumeRole'
130+
ManagedPolicyArns:
131+
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
132+
- arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess
133+
Path: /
134+
Policies:
135+
- PolicyName: LambdaRequiredPermissions
136+
PolicyDocument:
137+
Version: 2012-10-17
138+
Statement:
139+
- Effect: Allow
140+
Action:
141+
- xray:UpdateSamplingRule
142+
Resource:
143+
- !Sub '${SimpleXRayRule.Arn}'
144+
145+
# CloudWatch alarm
146+
ApiGatewayAlarm:
147+
Type: AWS::CloudWatch::Alarm
148+
Properties:
149+
AlarmDescription: Number of 5xx errors alarm
150+
AlarmActions:
151+
- Ref: ApiGatewayNotificationTopic
152+
OKActions:
153+
- Ref: ApiGatewayNotificationTopic
154+
MetricName: 5XXError
155+
Namespace: AWS/ApiGateway
156+
Statistic: Sum
157+
Period: 60
158+
EvaluationPeriods: 1
159+
Threshold: 5
160+
ComparisonOperator: GreaterThanThreshold
161+
TreatMissingData: 'notBreaching'
162+
Dimensions:
163+
- Name: ApiName
164+
Value:
165+
Ref: 'AWS::StackName'
166+
ApiGatewayNotificationTopic:
167+
Type: AWS::SNS::Topic
168+
169+
Outputs:
170+
WebApi:
171+
Description: "API Gateway endpoint URL for Prod stage for WebApi function"
172+
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"

0 commit comments

Comments
 (0)