add create_rule to elbv2
This commit is contained in:
parent
b4013f0e60
commit
05a2715f4b
@ -101,3 +101,44 @@ class EmptyListenersError(ELBClientError):
|
||||
super(EmptyListenersError, self).__init__(
|
||||
"ValidationError",
|
||||
"Listeners cannot be empty")
|
||||
|
||||
|
||||
class PriorityInUseError(ELBClientError):
|
||||
|
||||
def __init__(self):
|
||||
super(PriorityInUseError, self).__init__(
|
||||
"PriorityInUse",
|
||||
"The specified priority is in use.")
|
||||
|
||||
|
||||
class InvalidConditionFieldError(ELBClientError):
|
||||
|
||||
def __init__(self, invalid_name):
|
||||
super(InvalidConditionFieldError, self).__init__(
|
||||
"ValidationError",
|
||||
"Condition field '%s' must be one of '[path-pattern, host-header]" % (invalid_name))
|
||||
|
||||
|
||||
class InvalidConditionValueError(ELBClientError):
|
||||
|
||||
def __init__(self, msg):
|
||||
super(InvalidConditionValueError, self).__init__(
|
||||
"ValidationError", msg)
|
||||
|
||||
|
||||
class InvalidActionTypeError(ELBClientError):
|
||||
|
||||
def __init__(self, invalid_name, index):
|
||||
super(InvalidActionTypeError, self).__init__(
|
||||
"ValidationError",
|
||||
"1 validation error detected: Value '%s' at 'actions.%s.member.type' failed to satisfy constraint: Member must satisfy enum value set: [forward]" % (invalid_name, index)
|
||||
)
|
||||
|
||||
|
||||
class ActionTargetGroupNotFoundError(ELBClientError):
|
||||
|
||||
def __init__(self, arn):
|
||||
super(ActionTargetGroupNotFoundError, self).__init__(
|
||||
"TargetGroupNotFound",
|
||||
"Target group '%s' not found" % arn
|
||||
)
|
||||
|
@ -14,6 +14,11 @@ from .exceptions import (
|
||||
SubnetNotFoundError,
|
||||
TargetGroupNotFoundError,
|
||||
TooManyTagsError,
|
||||
PriorityInUseError,
|
||||
InvalidConditionFieldError,
|
||||
InvalidConditionValueError,
|
||||
InvalidActionTypeError,
|
||||
ActionTargetGroupNotFoundError,
|
||||
)
|
||||
|
||||
|
||||
@ -92,6 +97,34 @@ class FakeListener(BaseModel):
|
||||
self.ssl_policy = ssl_policy
|
||||
self.certificate = certificate
|
||||
self.default_actions = default_actions
|
||||
self._non_default_rules = []
|
||||
self._default_rule = FakeRule(
|
||||
listener_arn=self.arn,
|
||||
conditions=[],
|
||||
priority='default',
|
||||
actions=default_actions,
|
||||
is_default=True
|
||||
)
|
||||
|
||||
@property
|
||||
def rules(self):
|
||||
return self._non_default_rules + [self._default_rule]
|
||||
|
||||
|
||||
def register(self, rule):
|
||||
self._non_default_rules.append(rule)
|
||||
self._non_default_rules = sorted(self._non_default_rules, key=lambda x: x.priority)
|
||||
|
||||
|
||||
class FakeRule(BaseModel):
|
||||
|
||||
def __init__(self, listener_arn, conditions, priority, actions, is_default):
|
||||
self.listener_arn = listener_arn
|
||||
self.arn = listener_arn.replace(':listener/', ':listener-rule/') + "/%s" % (id(self))
|
||||
self.conditions = conditions
|
||||
self.priority = priority # int or 'default'
|
||||
self.actions = actions
|
||||
self.is_default = is_default
|
||||
|
||||
|
||||
class FakeBackend(BaseModel):
|
||||
@ -181,6 +214,53 @@ class ELBv2Backend(BaseBackend):
|
||||
self.load_balancers[arn] = new_load_balancer
|
||||
return new_load_balancer
|
||||
|
||||
def create_rule(self, listener_arn, conditions, priority, actions):
|
||||
listeners = self.describe_listeners(None, [listener_arn])
|
||||
if not listeners:
|
||||
raise ListenerNotFound()
|
||||
listener = listeners[0]
|
||||
|
||||
# validate conditions
|
||||
for condition in conditions:
|
||||
field = condition['field']
|
||||
if field not in ['path-pattern', 'host-header']:
|
||||
raise InvalidConditionFieldError(field)
|
||||
|
||||
values = condition['values']
|
||||
if len(values) == 0:
|
||||
raise InvalidConditionValueError('A condition value must be specified')
|
||||
if len(values) > 1:
|
||||
raise InvalidConditionValueError(
|
||||
"The '%s' field contains too many values; the limit is '1'" % field
|
||||
)
|
||||
|
||||
# TODO: check pattern of value for 'host-header'
|
||||
# TODO: check pattern of value for 'path-pattern'
|
||||
|
||||
# validate Priority
|
||||
for rule in listener.rules:
|
||||
if rule.priority == priority:
|
||||
raise PriorityInUseError()
|
||||
|
||||
# validate Actions
|
||||
target_group_arns = [target_group.arn for target_group in self.target_groups.values()]
|
||||
for i, action in enumerate(actions):
|
||||
index = i + 1
|
||||
action_type = action['type']
|
||||
if action_type not in ['forward']:
|
||||
raise InvalidActionTypeError(action_type, index)
|
||||
action_target_group_arn = action['target_group_arn']
|
||||
if action_target_group_arn not in target_group_arns:
|
||||
raise ActionTargetGroupNotFoundError(action_target_group_arn)
|
||||
|
||||
# TODO: check for error 'TooManyRegistrationsForTargetId'
|
||||
# TODO: check for error 'TooManyRules'
|
||||
|
||||
# create rule
|
||||
rule = FakeRule(listener.arn, conditions, priority, actions, is_default=False)
|
||||
listener.register(rule)
|
||||
return listener.rules
|
||||
|
||||
def create_target_group(self, name, **kwargs):
|
||||
for target_group in self.target_groups.values():
|
||||
if target_group.name == name:
|
||||
|
@ -28,6 +28,30 @@ class ELBV2Response(BaseResponse):
|
||||
template = self.response_template(CREATE_LOAD_BALANCER_TEMPLATE)
|
||||
return template.render(load_balancer=load_balancer)
|
||||
|
||||
def create_rule(self):
|
||||
lister_arn = self._get_param('ListenerArn')
|
||||
_conditions = self._get_list_prefix('Conditions.member')
|
||||
conditions = []
|
||||
for _condition in _conditions:
|
||||
condition = {}
|
||||
condition['field'] = _condition['field']
|
||||
values = sorted(
|
||||
[e for e in _condition.items() if e[0].startswith('values.member')],
|
||||
key=lambda x: x[0]
|
||||
)
|
||||
condition['values'] = [e[1] for e in values]
|
||||
conditions.append(condition)
|
||||
priority = self._get_int_param('Priority')
|
||||
actions = self._get_list_prefix('Actions.member')
|
||||
rules = self.elbv2_backend.create_rule(
|
||||
listener_arn=lister_arn,
|
||||
conditions=conditions,
|
||||
priority=priority,
|
||||
actions=actions
|
||||
)
|
||||
template = self.response_template(CREATE_RULE_TEMPLATE)
|
||||
return template.render(rules=rules)
|
||||
|
||||
def create_target_group(self):
|
||||
name = self._get_param('Name')
|
||||
vpc_id = self._get_param('VpcId')
|
||||
@ -321,6 +345,43 @@ CREATE_LOAD_BALANCER_TEMPLATE = """<CreateLoadBalancerResponse xmlns="http://ela
|
||||
</ResponseMetadata>
|
||||
</CreateLoadBalancerResponse>"""
|
||||
|
||||
CREATE_RULE_TEMPLATE = """<CreateRuleResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2015-12-01/">
|
||||
<CreateRuleResult>
|
||||
<Rules>
|
||||
{% for rule in rules %}
|
||||
<member>
|
||||
<IsDefault>{{ "true" if rule.is_default else "false" }}</IsDefault>
|
||||
<Conditions>
|
||||
{% for condition in rule.conditions %}
|
||||
<member>
|
||||
<Field>{{ condition["field"] }}</Field>
|
||||
<Values>
|
||||
{% for value in condition["values"] %}
|
||||
<member>{{ value }}</member>
|
||||
{% endfor %}
|
||||
</Values>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Conditions>
|
||||
<Priority>{{ rule.priority }}</Priority>
|
||||
<Actions>
|
||||
{% for action in rule.actions %}
|
||||
<member>
|
||||
<Type>{{ action["type"] }}</Type>
|
||||
<TargetGroupArn>{{ action["target_group_arn"] }}</TargetGroupArn>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Actions>
|
||||
<RuleArn>{{ rule.arn }}</RuleArn>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Rules>
|
||||
</CreateRuleResult>
|
||||
<ResponseMetadata>
|
||||
<RequestId>c5478c83-f397-11e5-bb98-57195a6eb84a</RequestId>
|
||||
</ResponseMetadata>
|
||||
</CreateRuleResponse>"""
|
||||
|
||||
CREATE_TARGET_GROUP_TEMPLATE = """<CreateTargetGroupResponse xmlns="http://elasticloadbalancing.amazonaws.com/doc/2015-12-01/">
|
||||
<CreateTargetGroupResult>
|
||||
<TargetGroups>
|
||||
|
@ -518,3 +518,207 @@ def test_target_group_attributes():
|
||||
attributes = {attr['Key']: attr['Value'] for attr in response['Attributes']}
|
||||
attributes['stickiness.type'].should.equal('lb_cookie')
|
||||
attributes['stickiness.enabled'].should.equal('true')
|
||||
|
||||
|
||||
@mock_elbv2
|
||||
@mock_ec2
|
||||
def test_create_listener_rules():
|
||||
conn = boto3.client('elbv2', region_name='us-east-1')
|
||||
ec2 = boto3.resource('ec2', region_name='us-east-1')
|
||||
|
||||
security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One')
|
||||
vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
|
||||
subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a')
|
||||
subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b')
|
||||
|
||||
response = conn.create_load_balancer(
|
||||
Name='my-lb',
|
||||
Subnets=[subnet1.id, subnet2.id],
|
||||
SecurityGroups=[security_group.id],
|
||||
Scheme='internal',
|
||||
Tags=[{'Key': 'key_name', 'Value': 'a_value'}])
|
||||
|
||||
load_balancer_arn = response.get('LoadBalancers')[0].get('LoadBalancerArn')
|
||||
|
||||
response = conn.create_target_group(
|
||||
Name='a-target',
|
||||
Protocol='HTTP',
|
||||
Port=8080,
|
||||
VpcId=vpc.id,
|
||||
HealthCheckProtocol='HTTP',
|
||||
HealthCheckPort='8080',
|
||||
HealthCheckPath='/',
|
||||
HealthCheckIntervalSeconds=5,
|
||||
HealthCheckTimeoutSeconds=5,
|
||||
HealthyThresholdCount=5,
|
||||
UnhealthyThresholdCount=2,
|
||||
Matcher={'HttpCode': '200'})
|
||||
target_group = response.get('TargetGroups')[0]
|
||||
|
||||
# Plain HTTP listener
|
||||
response = conn.create_listener(
|
||||
LoadBalancerArn=load_balancer_arn,
|
||||
Protocol='HTTP',
|
||||
Port=80,
|
||||
DefaultActions=[{'Type': 'forward', 'TargetGroupArn': target_group.get('TargetGroupArn')}])
|
||||
listener = response.get('Listeners')[0]
|
||||
listener.get('Port').should.equal(80)
|
||||
listener.get('Protocol').should.equal('HTTP')
|
||||
listener.get('DefaultActions').should.equal([{
|
||||
'TargetGroupArn': target_group.get('TargetGroupArn'),
|
||||
'Type': 'forward'}])
|
||||
http_listener_arn = listener.get('ListenerArn')
|
||||
|
||||
# create first rule
|
||||
priority = 100
|
||||
host = 'xxx.example.com'
|
||||
path_pattern = 'foobar'
|
||||
rules = conn.create_rule(
|
||||
ListenerArn=http_listener_arn,
|
||||
Priority=priority,
|
||||
Conditions=[{
|
||||
'Field': 'host-header',
|
||||
'Values': [ host ]
|
||||
},
|
||||
{
|
||||
'Field': 'path-pattern',
|
||||
'Values': [ path_pattern ]
|
||||
}],
|
||||
Actions=[{
|
||||
'TargetGroupArn': target_group.get('TargetGroupArn'),
|
||||
'Type': 'forward'
|
||||
}]
|
||||
)
|
||||
rules['Rules'][0].get('Priority').should.equal('100')
|
||||
|
||||
# check if rules is sorted by priority
|
||||
priority = 50
|
||||
host = 'yyy.example.com'
|
||||
path_pattern = 'foobar'
|
||||
rules = conn.create_rule(
|
||||
ListenerArn=http_listener_arn,
|
||||
Priority=priority,
|
||||
Conditions=[{
|
||||
'Field': 'host-header',
|
||||
'Values': [ host ]
|
||||
},
|
||||
{
|
||||
'Field': 'path-pattern',
|
||||
'Values': [ path_pattern ]
|
||||
}],
|
||||
Actions=[{
|
||||
'TargetGroupArn': target_group.get('TargetGroupArn'),
|
||||
'Type': 'forward'
|
||||
}]
|
||||
)
|
||||
priorities = [rule['Priority'] for rule in rules['Rules']]
|
||||
priorities.should.equal(['50', '100', 'default'])
|
||||
|
||||
# test for invalid action type
|
||||
safe_priority = 2
|
||||
with assert_raises(ClientError):
|
||||
r = conn.create_rule(
|
||||
ListenerArn=http_listener_arn,
|
||||
Priority=safe_priority,
|
||||
Conditions=[{
|
||||
'Field': 'host-header',
|
||||
'Values': [ host ]
|
||||
},
|
||||
{
|
||||
'Field': 'path-pattern',
|
||||
'Values': [ path_pattern ]
|
||||
}],
|
||||
Actions=[{
|
||||
'TargetGroupArn': target_group.get('TargetGroupArn'),
|
||||
'Type': 'forward2'
|
||||
}]
|
||||
)
|
||||
|
||||
# test for invalid action type
|
||||
safe_priority = 2
|
||||
invalid_target_group_arn = target_group.get('TargetGroupArn') + 'x'
|
||||
with assert_raises(ClientError):
|
||||
r = conn.create_rule(
|
||||
ListenerArn=http_listener_arn,
|
||||
Priority=safe_priority,
|
||||
Conditions=[{
|
||||
'Field': 'host-header',
|
||||
'Values': [ host ]
|
||||
},
|
||||
{
|
||||
'Field': 'path-pattern',
|
||||
'Values': [ path_pattern ]
|
||||
}],
|
||||
Actions=[{
|
||||
'TargetGroupArn': invalid_target_group_arn,
|
||||
'Type': 'forward'
|
||||
}]
|
||||
)
|
||||
|
||||
# test for PriorityInUse
|
||||
host2 = 'yyy.example.com'
|
||||
with assert_raises(ClientError):
|
||||
r = conn.create_rule(
|
||||
ListenerArn=http_listener_arn,
|
||||
Priority=priority,
|
||||
Conditions=[{
|
||||
'Field': 'host-header',
|
||||
'Values': [ host ]
|
||||
},
|
||||
{
|
||||
'Field': 'path-pattern',
|
||||
'Values': [ path_pattern ]
|
||||
}],
|
||||
Actions=[{
|
||||
'TargetGroupArn': target_group.get('TargetGroupArn'),
|
||||
'Type': 'forward'
|
||||
}]
|
||||
)
|
||||
|
||||
# test for invalid condition field_name
|
||||
safe_priority = 2
|
||||
with assert_raises(ClientError):
|
||||
r = conn.create_rule(
|
||||
ListenerArn=http_listener_arn,
|
||||
Priority=safe_priority,
|
||||
Conditions=[{
|
||||
'Field': 'xxxxxxx',
|
||||
'Values': [ host ]
|
||||
}],
|
||||
Actions=[{
|
||||
'TargetGroupArn': target_group.get('TargetGroupArn'),
|
||||
'Type': 'forward'
|
||||
}]
|
||||
)
|
||||
|
||||
# test for emptry condition value
|
||||
safe_priority = 2
|
||||
with assert_raises(ClientError):
|
||||
r = conn.create_rule(
|
||||
ListenerArn=http_listener_arn,
|
||||
Priority=safe_priority,
|
||||
Conditions=[{
|
||||
'Field': 'host-header',
|
||||
'Values': []
|
||||
}],
|
||||
Actions=[{
|
||||
'TargetGroupArn': target_group.get('TargetGroupArn'),
|
||||
'Type': 'forward'
|
||||
}]
|
||||
)
|
||||
|
||||
# test for multiple condition value
|
||||
safe_priority = 2
|
||||
with assert_raises(ClientError):
|
||||
r = conn.create_rule(
|
||||
ListenerArn=http_listener_arn,
|
||||
Priority=safe_priority,
|
||||
Conditions=[{
|
||||
'Field': 'host-header',
|
||||
'Values': [host, host]
|
||||
}],
|
||||
Actions=[{
|
||||
'TargetGroupArn': target_group.get('TargetGroupArn'),
|
||||
'Type': 'forward'
|
||||
}]
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user