diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md
index 04b52bce..2725a057 100644
--- a/IMPLEMENTATION_COVERAGE.md
+++ b/IMPLEMENTATION_COVERAGE.md
@@ -2456,8 +2456,8 @@
- [ ] update_distribution
- [ ] update_streaming_distribution
-## elbv2 - 54% implemented
-- [ ] add_tags
+## elbv2 - 100% implemented
+- [X] add_tags
- [X] create_listener
- [X] create_load_balancer
- [X] create_rule
@@ -2467,27 +2467,27 @@
- [X] delete_rule
- [X] delete_target_group
- [X] deregister_targets
-- [ ] describe_account_limits
+- [X] describe_account_limits
- [X] describe_listeners
-- [ ] describe_load_balancer_attributes
+- [X] describe_load_balancer_attributes
- [X] describe_load_balancers
- [X] describe_rules
-- [ ] describe_ssl_policies
-- [ ] describe_tags
-- [ ] describe_target_group_attributes
+- [X] describe_ssl_policies
+- [X] describe_tags
+- [X] describe_target_group_attributes
- [X] describe_target_groups
- [X] describe_target_health
-- [ ] modify_listener
-- [ ] modify_load_balancer_attributes
+- [X] modify_listener
+- [X] modify_load_balancer_attributes
- [X] modify_rule
-- [ ] modify_target_group
-- [ ] modify_target_group_attributes
+- [X] modify_target_group
+- [X] modify_target_group_attributes
- [X] register_targets
-- [ ] remove_tags
-- [ ] set_ip_address_type
+- [X] remove_tags
+- [X] set_ip_address_type
- [X] set_rule_priorities
-- [ ] set_security_groups
-- [ ] set_subnets
+- [X] set_security_groups
+- [X] set_subnets
## sdb - 0% implemented
- [ ] batch_delete_attributes
diff --git a/README.md b/README.md
index a58bf219..402b443b 100644
--- a/README.md
+++ b/README.md
@@ -88,7 +88,7 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L
|------------------------------------------------------------------------------|
| ELB | @mock_elb | core endpoints done |
|------------------------------------------------------------------------------|
-| ELBv2 | @mock_elbv2 | core endpoints done |
+| ELBv2 | @mock_elbv2 | all endpoints done |
|------------------------------------------------------------------------------|
| EMR | @mock_emr | core endpoints done |
|------------------------------------------------------------------------------|
diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py
index 8aa9ee9f..53eedc16 100644
--- a/moto/elbv2/models.py
+++ b/moto/elbv2/models.py
@@ -3,8 +3,10 @@ from __future__ import unicode_literals
import datetime
import re
from moto.compat import OrderedDict
+from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel
from moto.ec2.models import ec2_backends
+from moto.acm.models import acm_backends
from .exceptions import (
DuplicateLoadBalancerName,
DuplicateListenerError,
@@ -40,6 +42,8 @@ class FakeHealthStatus(BaseModel):
class FakeTargetGroup(BaseModel):
+ HTTP_CODE_REGEX = re.compile(r'(?:(?:\d+-\d+|\d+),?)+')
+
def __init__(self,
name,
arn,
@@ -52,7 +56,8 @@ class FakeTargetGroup(BaseModel):
healthcheck_interval_seconds,
healthcheck_timeout_seconds,
healthy_threshold_count,
- unhealthy_threshold_count):
+ unhealthy_threshold_count,
+ http_codes):
self.name = name
self.arn = arn
self.vpc_id = vpc_id
@@ -67,6 +72,7 @@ class FakeTargetGroup(BaseModel):
self.unhealthy_threshold_count = unhealthy_threshold_count
self.load_balancer_arns = []
self.tags = {}
+ self.http_status_codes = http_codes
self.attributes = {
'deregistration_delay.timeout_seconds': 300,
@@ -109,6 +115,7 @@ class FakeListener(BaseModel):
self.port = port
self.ssl_policy = ssl_policy
self.certificate = certificate
+ self.certificates = [certificate] if certificate is not None else []
self.default_actions = default_actions
self._non_default_rules = []
self._default_rule = FakeRule(
@@ -153,6 +160,8 @@ class FakeBackend(BaseModel):
class FakeLoadBalancer(BaseModel):
+ VALID_ATTRS = {'access_logs.s3.enabled', 'access_logs.s3.bucket', 'access_logs.s3.prefix',
+ 'deletion_protection.enabled', 'idle_timeout.timeout_seconds'}
def __init__(self, name, security_groups, subnets, vpc_id, arn, dns_name, scheme='internet-facing'):
self.name = name
@@ -166,6 +175,15 @@ class FakeLoadBalancer(BaseModel):
self.arn = arn
self.dns_name = dns_name
+ self.stack = 'ipv4'
+ self.attrs = {
+ 'access_logs.s3.enabled': 'false',
+ 'access_logs.s3.bucket': None,
+ 'access_logs.s3.prefix': None,
+ 'deletion_protection.enabled': 'false',
+ 'idle_timeout.timeout_seconds': '60'
+ }
+
@property
def physical_resource_id(self):
return self.name
@@ -194,6 +212,26 @@ class ELBv2Backend(BaseBackend):
self.target_groups = OrderedDict()
self.load_balancers = OrderedDict()
+ @property
+ def ec2_backend(self):
+ """
+ EC2 backend
+
+ :return: EC2 Backend
+ :rtype: moto.ec2.models.EC2Backend
+ """
+ return ec2_backends[self.region_name]
+
+ @property
+ def acm_backend(self):
+ """
+ ACM backend
+
+ :return: ACM Backend
+ :rtype: moto.acm.models.AWSCertificateManagerBackend
+ """
+ return acm_backends[self.region_name]
+
def reset(self):
region_name = self.region_name
self.__dict__ = {}
@@ -201,12 +239,11 @@ class ELBv2Backend(BaseBackend):
def create_load_balancer(self, name, security_groups, subnet_ids, scheme='internet-facing'):
vpc_id = None
- ec2_backend = ec2_backends[self.region_name]
subnets = []
if not subnet_ids:
raise SubnetNotFoundError()
for subnet_id in subnet_ids:
- subnet = ec2_backend.get_subnet(subnet_id)
+ subnet = self.ec2_backend.get_subnet(subnet_id)
if subnet is None:
raise SubnetNotFoundError()
subnets.append(subnet)
@@ -300,6 +337,9 @@ class ELBv2Backend(BaseBackend):
if target_group.name == name:
raise DuplicateTargetGroupName()
+ if FakeTargetGroup.HTTP_CODE_REGEX.match(kwargs['http_codes']) is None:
+ raise RESTError('InvalidParameterValue', 'HttpCode must be like 200 | 200-399 | 200,201 ...')
+
arn = "arn:aws:elasticloadbalancing:%s:1:targetgroup/%s/50dc6c495c0c9188" % (self.region_name, name)
target_group = FakeTargetGroup(name, arn, **kwargs)
self.target_groups[target_group.arn] = target_group
@@ -547,6 +587,166 @@ class ELBv2Backend(BaseBackend):
modified_rules.append(given_rule)
return modified_rules
+ def set_ip_address_type(self, arn, ip_type):
+ if ip_type not in ('internal', 'dualstack'):
+ raise RESTError('InvalidParameterValue', 'IpAddressType must be either internal | dualstack')
+
+ balancer = self.load_balancers.get(arn)
+ if balancer is None:
+ raise LoadBalancerNotFoundError()
+
+ if ip_type == 'dualstack' and balancer.scheme == 'internal':
+ raise RESTError('InvalidConfigurationRequest', 'Internal load balancers cannot be dualstack')
+
+ balancer.stack = ip_type
+
+ def set_security_groups(self, arn, sec_groups):
+ balancer = self.load_balancers.get(arn)
+ if balancer is None:
+ raise LoadBalancerNotFoundError()
+
+ # Check all security groups exist
+ for sec_group_id in sec_groups:
+ if self.ec2_backend.get_security_group_from_id(sec_group_id) is None:
+ raise RESTError('InvalidSecurityGroup', 'Security group {0} does not exist'.format(sec_group_id))
+
+ balancer.security_groups = sec_groups
+
+ def set_subnets(self, arn, subnets):
+ balancer = self.load_balancers.get(arn)
+ if balancer is None:
+ raise LoadBalancerNotFoundError()
+
+ subnet_objects = []
+ sub_zone_list = {}
+ for subnet in subnets:
+ try:
+ subnet = self.ec2_backend.get_subnet(subnet)
+
+ if subnet.availability_zone in sub_zone_list:
+ raise RESTError('InvalidConfigurationRequest', 'More than 1 subnet cannot be specified for 1 availability zone')
+
+ sub_zone_list[subnet.availability_zone] = subnet.id
+ subnet_objects.append(subnet)
+ except Exception:
+ raise SubnetNotFoundError()
+
+ if len(sub_zone_list) < 2:
+ raise RESTError('InvalidConfigurationRequest', 'More than 1 availability zone must be specified')
+
+ balancer.subnets = subnet_objects
+
+ return sub_zone_list.items()
+
+ def modify_load_balancer_attributes(self, arn, attrs):
+ balancer = self.load_balancers.get(arn)
+ if balancer is None:
+ raise LoadBalancerNotFoundError()
+
+ for key in attrs:
+ if key not in FakeLoadBalancer.VALID_ATTRS:
+ raise RESTError('InvalidConfigurationRequest', 'Key {0} not valid'.format(key))
+
+ balancer.attrs.update(attrs)
+ return balancer.attrs
+
+ def describe_load_balancer_attributes(self, arn):
+ balancer = self.load_balancers.get(arn)
+ if balancer is None:
+ raise LoadBalancerNotFoundError()
+
+ return balancer.attrs
+
+ def modify_target_group(self, arn, health_check_proto=None, health_check_port=None, health_check_path=None, health_check_interval=None,
+ health_check_timeout=None, healthy_threshold_count=None, unhealthy_threshold_count=None, http_codes=None):
+ target_group = self.target_groups.get(arn)
+ if target_group is None:
+ raise TargetGroupNotFoundError()
+
+ if http_codes is not None and FakeTargetGroup.HTTP_CODE_REGEX.match(http_codes) is None:
+ raise RESTError('InvalidParameterValue', 'HttpCode must be like 200 | 200-399 | 200,201 ...')
+
+ if http_codes is not None:
+ target_group.http_status_codes = http_codes
+ if health_check_interval is not None:
+ target_group.healthcheck_interval_seconds = health_check_interval
+ if health_check_path is not None:
+ target_group.healthcheck_path = health_check_path
+ if health_check_port is not None:
+ target_group.healthcheck_port = health_check_port
+ if health_check_proto is not None:
+ target_group.healthcheck_protocol = health_check_proto
+ if health_check_timeout is not None:
+ target_group.healthcheck_timeout_seconds = health_check_timeout
+ if healthy_threshold_count is not None:
+ target_group.healthy_threshold_count = healthy_threshold_count
+ if unhealthy_threshold_count is not None:
+ target_group.unhealthy_threshold_count = unhealthy_threshold_count
+
+ return target_group
+
+ def modify_listener(self, arn, port=None, protocol=None, ssl_policy=None, certificates=None, default_actions=None):
+ for load_balancer in self.load_balancers.values():
+ if arn in load_balancer.listeners:
+ break
+ else:
+ raise ListenerNotFoundError()
+
+ listener = load_balancer.listeners[arn]
+
+ if port is not None:
+ for listener_arn, current_listener in load_balancer.listeners.items():
+ if listener_arn == arn:
+ continue
+ if listener.port == port:
+ raise DuplicateListenerError()
+
+ listener.port = port
+
+ if protocol is not None:
+ if protocol not in ('HTTP', 'HTTPS', 'TCP'):
+ raise RESTError('UnsupportedProtocol', 'Protocol {0} is not supported'.format(protocol))
+
+ # HTTPS checks
+ if protocol == 'HTTPS':
+ # HTTPS
+
+ # Might already be HTTPS so may not provide certs
+ if certificates is None and listener.protocol != 'HTTPS':
+ raise RESTError('InvalidConfigurationRequest', 'Certificates must be provided for HTTPS')
+
+ # Check certificates exist
+ if certificates is not None:
+ default_cert = None
+ all_certs = set() # for SNI
+ for cert in certificates:
+ if cert['is_default'] == 'true':
+ default_cert = cert['certificate_arn']
+ try:
+ self.acm_backend.get_certificate(cert['certificate_arn'])
+ except Exception:
+ raise RESTError('CertificateNotFound', 'Certificate {0} not found'.format(cert['certificate_arn']))
+
+ all_certs.add(cert['certificate_arn'])
+
+ if default_cert is None:
+ raise RESTError('InvalidConfigurationRequest', 'No default certificate')
+
+ listener.certificate = default_cert
+ listener.certificates = list(all_certs)
+
+ listener.protocol = protocol
+
+ if ssl_policy is not None:
+ # Its already validated in responses.py
+ listener.ssl_policy = ssl_policy
+
+ if default_actions is not None:
+ # Is currently not validated
+ listener.default_actions = default_actions
+
+ return listener
+
def _any_listener_using(self, target_group_arn):
for load_balancer in self.load_balancers.values():
for listener in load_balancer.listeners.values():
diff --git a/moto/elbv2/responses.py b/moto/elbv2/responses.py
index 3e853518..45c35d42 100644
--- a/moto/elbv2/responses.py
+++ b/moto/elbv2/responses.py
@@ -1,4 +1,6 @@
from __future__ import unicode_literals
+from moto.core.exceptions import RESTError
+from moto.core.utils import amzn_request_id
from moto.core.responses import BaseResponse
from .models import elbv2_backends
from .exceptions import DuplicateTagKeysError
@@ -6,12 +8,131 @@ from .exceptions import LoadBalancerNotFoundError
from .exceptions import TargetGroupNotFoundError
-class ELBV2Response(BaseResponse):
+SSL_POLICIES = [
+ {
+ 'name': 'ELBSecurityPolicy-2016-08',
+ 'ssl_protocols': ['TLSv1', 'TLSv1.1', 'TLSv1.2'],
+ 'ciphers': [
+ {'name': 'ECDHE-ECDSA-AES128-GCM-SHA256', 'priority': 1},
+ {'name': 'ECDHE-RSA-AES128-GCM-SHA256', 'priority': 2},
+ {'name': 'ECDHE-ECDSA-AES128-SHA256', 'priority': 3},
+ {'name': 'ECDHE-RSA-AES128-SHA256', 'priority': 4},
+ {'name': 'ECDHE-ECDSA-AES128-SHA', 'priority': 5},
+ {'name': 'ECDHE-RSA-AES128-SHA', 'priority': 6},
+ {'name': 'ECDHE-ECDSA-AES256-GCM-SHA384', 'priority': 7},
+ {'name': 'ECDHE-RSA-AES256-GCM-SHA384', 'priority': 8},
+ {'name': 'ECDHE-ECDSA-AES256-SHA384', 'priority': 9},
+ {'name': 'ECDHE-RSA-AES256-SHA384', 'priority': 10},
+ {'name': 'ECDHE-RSA-AES256-SHA', 'priority': 11},
+ {'name': 'ECDHE-ECDSA-AES256-SHA', 'priority': 12},
+ {'name': 'AES128-GCM-SHA256', 'priority': 13},
+ {'name': 'AES128-SHA256', 'priority': 14},
+ {'name': 'AES128-SHA', 'priority': 15},
+ {'name': 'AES256-GCM-SHA384', 'priority': 16},
+ {'name': 'AES256-SHA256', 'priority': 17},
+ {'name': 'AES256-SHA', 'priority': 18}
+ ],
+ },
+ {
+ 'name': 'ELBSecurityPolicy-TLS-1-2-2017-01',
+ 'ssl_protocols': ['TLSv1.2'],
+ 'ciphers': [
+ {'name': 'ECDHE-ECDSA-AES128-GCM-SHA256', 'priority': 1},
+ {'name': 'ECDHE-RSA-AES128-GCM-SHA256', 'priority': 2},
+ {'name': 'ECDHE-ECDSA-AES128-SHA256', 'priority': 3},
+ {'name': 'ECDHE-RSA-AES128-SHA256', 'priority': 4},
+ {'name': 'ECDHE-ECDSA-AES256-GCM-SHA384', 'priority': 5},
+ {'name': 'ECDHE-RSA-AES256-GCM-SHA384', 'priority': 6},
+ {'name': 'ECDHE-ECDSA-AES256-SHA384', 'priority': 7},
+ {'name': 'ECDHE-RSA-AES256-SHA384', 'priority': 8},
+ {'name': 'AES128-GCM-SHA256', 'priority': 9},
+ {'name': 'AES128-SHA256', 'priority': 10},
+ {'name': 'AES256-GCM-SHA384', 'priority': 11},
+ {'name': 'AES256-SHA256', 'priority': 12}
+ ]
+ },
+ {
+ 'name': 'ELBSecurityPolicy-TLS-1-1-2017-01',
+ 'ssl_protocols': ['TLSv1.1', 'TLSv1.2'],
+ 'ciphers': [
+ {'name': 'ECDHE-ECDSA-AES128-GCM-SHA256', 'priority': 1},
+ {'name': 'ECDHE-RSA-AES128-GCM-SHA256', 'priority': 2},
+ {'name': 'ECDHE-ECDSA-AES128-SHA256', 'priority': 3},
+ {'name': 'ECDHE-RSA-AES128-SHA256', 'priority': 4},
+ {'name': 'ECDHE-ECDSA-AES128-SHA', 'priority': 5},
+ {'name': 'ECDHE-RSA-AES128-SHA', 'priority': 6},
+ {'name': 'ECDHE-ECDSA-AES256-GCM-SHA384', 'priority': 7},
+ {'name': 'ECDHE-RSA-AES256-GCM-SHA384', 'priority': 8},
+ {'name': 'ECDHE-ECDSA-AES256-SHA384', 'priority': 9},
+ {'name': 'ECDHE-RSA-AES256-SHA384', 'priority': 10},
+ {'name': 'ECDHE-RSA-AES256-SHA', 'priority': 11},
+ {'name': 'ECDHE-ECDSA-AES256-SHA', 'priority': 12},
+ {'name': 'AES128-GCM-SHA256', 'priority': 13},
+ {'name': 'AES128-SHA256', 'priority': 14},
+ {'name': 'AES128-SHA', 'priority': 15},
+ {'name': 'AES256-GCM-SHA384', 'priority': 16},
+ {'name': 'AES256-SHA256', 'priority': 17},
+ {'name': 'AES256-SHA', 'priority': 18}
+ ]
+ },
+ {
+ 'name': 'ELBSecurityPolicy-2015-05',
+ 'ssl_protocols': ['TLSv1', 'TLSv1.1', 'TLSv1.2'],
+ 'ciphers': [
+ {'name': 'ECDHE-ECDSA-AES128-GCM-SHA256', 'priority': 1},
+ {'name': 'ECDHE-RSA-AES128-GCM-SHA256', 'priority': 2},
+ {'name': 'ECDHE-ECDSA-AES128-SHA256', 'priority': 3},
+ {'name': 'ECDHE-RSA-AES128-SHA256', 'priority': 4},
+ {'name': 'ECDHE-ECDSA-AES128-SHA', 'priority': 5},
+ {'name': 'ECDHE-RSA-AES128-SHA', 'priority': 6},
+ {'name': 'ECDHE-ECDSA-AES256-GCM-SHA384', 'priority': 7},
+ {'name': 'ECDHE-RSA-AES256-GCM-SHA384', 'priority': 8},
+ {'name': 'ECDHE-ECDSA-AES256-SHA384', 'priority': 9},
+ {'name': 'ECDHE-RSA-AES256-SHA384', 'priority': 10},
+ {'name': 'ECDHE-RSA-AES256-SHA', 'priority': 11},
+ {'name': 'ECDHE-ECDSA-AES256-SHA', 'priority': 12},
+ {'name': 'AES128-GCM-SHA256', 'priority': 13},
+ {'name': 'AES128-SHA256', 'priority': 14},
+ {'name': 'AES128-SHA', 'priority': 15},
+ {'name': 'AES256-GCM-SHA384', 'priority': 16},
+ {'name': 'AES256-SHA256', 'priority': 17},
+ {'name': 'AES256-SHA', 'priority': 18}
+ ]
+ },
+ {
+ 'name': 'ELBSecurityPolicy-TLS-1-0-2015-04',
+ 'ssl_protocols': ['TLSv1', 'TLSv1.1', 'TLSv1.2'],
+ 'ciphers': [
+ {'name': 'ECDHE-ECDSA-AES128-GCM-SHA256', 'priority': 1},
+ {'name': 'ECDHE-RSA-AES128-GCM-SHA256', 'priority': 2},
+ {'name': 'ECDHE-ECDSA-AES128-SHA256', 'priority': 3},
+ {'name': 'ECDHE-RSA-AES128-SHA256', 'priority': 4},
+ {'name': 'ECDHE-ECDSA-AES128-SHA', 'priority': 5},
+ {'name': 'ECDHE-RSA-AES128-SHA', 'priority': 6},
+ {'name': 'ECDHE-ECDSA-AES256-GCM-SHA384', 'priority': 7},
+ {'name': 'ECDHE-RSA-AES256-GCM-SHA384', 'priority': 8},
+ {'name': 'ECDHE-ECDSA-AES256-SHA384', 'priority': 9},
+ {'name': 'ECDHE-RSA-AES256-SHA384', 'priority': 10},
+ {'name': 'ECDHE-RSA-AES256-SHA', 'priority': 11},
+ {'name': 'ECDHE-ECDSA-AES256-SHA', 'priority': 12},
+ {'name': 'AES128-GCM-SHA256', 'priority': 13},
+ {'name': 'AES128-SHA256', 'priority': 14},
+ {'name': 'AES128-SHA', 'priority': 15},
+ {'name': 'AES256-GCM-SHA384', 'priority': 16},
+ {'name': 'AES256-SHA256', 'priority': 17},
+ {'name': 'AES256-SHA', 'priority': 18},
+ {'name': 'DES-CBC3-SHA', 'priority': 19}
+ ]
+ }
+]
+
+class ELBV2Response(BaseResponse):
@property
def elbv2_backend(self):
return elbv2_backends[self.region]
+ @amzn_request_id
def create_load_balancer(self):
load_balancer_name = self._get_param('Name')
subnet_ids = self._get_multi_param("Subnets.member")
@@ -28,6 +149,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(CREATE_LOAD_BALANCER_TEMPLATE)
return template.render(load_balancer=load_balancer)
+ @amzn_request_id
def create_rule(self):
lister_arn = self._get_param('ListenerArn')
_conditions = self._get_list_prefix('Conditions.member')
@@ -52,6 +174,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(CREATE_RULE_TEMPLATE)
return template.render(rules=rules)
+ @amzn_request_id
def create_target_group(self):
name = self._get_param('Name')
vpc_id = self._get_param('VpcId')
@@ -64,6 +187,7 @@ class ELBV2Response(BaseResponse):
healthcheck_timeout_seconds = self._get_param('HealthCheckTimeoutSeconds', '5')
healthy_threshold_count = self._get_param('HealthyThresholdCount', '5')
unhealthy_threshold_count = self._get_param('UnhealthyThresholdCount', '2')
+ http_codes = self._get_param('Matcher.HttpCode', '200')
target_group = self.elbv2_backend.create_target_group(
name,
@@ -77,11 +201,13 @@ class ELBV2Response(BaseResponse):
healthcheck_timeout_seconds=healthcheck_timeout_seconds,
healthy_threshold_count=healthy_threshold_count,
unhealthy_threshold_count=unhealthy_threshold_count,
+ http_codes=http_codes
)
template = self.response_template(CREATE_TARGET_GROUP_TEMPLATE)
return template.render(target_group=target_group)
+ @amzn_request_id
def create_listener(self):
load_balancer_arn = self._get_param('LoadBalancerArn')
protocol = self._get_param('Protocol')
@@ -105,6 +231,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(CREATE_LISTENER_TEMPLATE)
return template.render(listener=listener)
+ @amzn_request_id
def describe_load_balancers(self):
arns = self._get_multi_param("LoadBalancerArns.member")
names = self._get_multi_param("Names.member")
@@ -124,6 +251,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(DESCRIBE_LOAD_BALANCERS_TEMPLATE)
return template.render(load_balancers=load_balancers_resp, marker=next_marker)
+ @amzn_request_id
def describe_rules(self):
listener_arn = self._get_param('ListenerArn')
rule_arns = self._get_multi_param('RuleArns.member') if any(k for k in list(self.querystring.keys()) if k.startswith('RuleArns.member')) else None
@@ -144,6 +272,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(DESCRIBE_RULES_TEMPLATE)
return template.render(rules=rules_resp, marker=next_marker)
+ @amzn_request_id
def describe_target_groups(self):
load_balancer_arn = self._get_param('LoadBalancerArn')
target_group_arns = self._get_multi_param('TargetGroupArns.member')
@@ -153,6 +282,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(DESCRIBE_TARGET_GROUPS_TEMPLATE)
return template.render(target_groups=target_groups)
+ @amzn_request_id
def describe_target_group_attributes(self):
target_group_arn = self._get_param('TargetGroupArn')
target_group = self.elbv2_backend.target_groups.get(target_group_arn)
@@ -161,6 +291,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(DESCRIBE_TARGET_GROUP_ATTRIBUTES_TEMPLATE)
return template.render(attributes=target_group.attributes)
+ @amzn_request_id
def describe_listeners(self):
load_balancer_arn = self._get_param('LoadBalancerArn')
listener_arns = self._get_multi_param('ListenerArns.member')
@@ -171,30 +302,35 @@ class ELBV2Response(BaseResponse):
template = self.response_template(DESCRIBE_LISTENERS_TEMPLATE)
return template.render(listeners=listeners)
+ @amzn_request_id
def delete_load_balancer(self):
arn = self._get_param('LoadBalancerArn')
self.elbv2_backend.delete_load_balancer(arn)
template = self.response_template(DELETE_LOAD_BALANCER_TEMPLATE)
return template.render()
+ @amzn_request_id
def delete_rule(self):
arn = self._get_param('RuleArn')
self.elbv2_backend.delete_rule(arn)
template = self.response_template(DELETE_RULE_TEMPLATE)
return template.render()
+ @amzn_request_id
def delete_target_group(self):
arn = self._get_param('TargetGroupArn')
self.elbv2_backend.delete_target_group(arn)
template = self.response_template(DELETE_TARGET_GROUP_TEMPLATE)
return template.render()
+ @amzn_request_id
def delete_listener(self):
arn = self._get_param('ListenerArn')
self.elbv2_backend.delete_listener(arn)
template = self.response_template(DELETE_LISTENER_TEMPLATE)
return template.render()
+ @amzn_request_id
def modify_rule(self):
rule_arn = self._get_param('RuleArn')
_conditions = self._get_list_prefix('Conditions.member')
@@ -217,6 +353,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(MODIFY_RULE_TEMPLATE)
return template.render(rules=rules)
+ @amzn_request_id
def modify_target_group_attributes(self):
target_group_arn = self._get_param('TargetGroupArn')
target_group = self.elbv2_backend.target_groups.get(target_group_arn)
@@ -230,6 +367,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(MODIFY_TARGET_GROUP_ATTRIBUTES_TEMPLATE)
return template.render(attributes=attributes)
+ @amzn_request_id
def register_targets(self):
target_group_arn = self._get_param('TargetGroupArn')
targets = self._get_list_prefix('Targets.member')
@@ -238,6 +376,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(REGISTER_TARGETS_TEMPLATE)
return template.render()
+ @amzn_request_id
def deregister_targets(self):
target_group_arn = self._get_param('TargetGroupArn')
targets = self._get_list_prefix('Targets.member')
@@ -246,6 +385,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(DEREGISTER_TARGETS_TEMPLATE)
return template.render()
+ @amzn_request_id
def describe_target_health(self):
target_group_arn = self._get_param('TargetGroupArn')
targets = self._get_list_prefix('Targets.member')
@@ -254,6 +394,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(DESCRIBE_TARGET_HEALTH_TEMPLATE)
return template.render(target_health_descriptions=target_health_descriptions)
+ @amzn_request_id
def set_rule_priorities(self):
rule_priorities = self._get_list_prefix('RulePriorities.member')
for rule_priority in rule_priorities:
@@ -262,6 +403,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(SET_RULE_PRIORITIES_TEMPLATE)
return template.render(rules=rules)
+ @amzn_request_id
def add_tags(self):
resource_arns = self._get_multi_param('ResourceArns.member')
@@ -281,6 +423,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(ADD_TAGS_TEMPLATE)
return template.render()
+ @amzn_request_id
def remove_tags(self):
resource_arns = self._get_multi_param('ResourceArns.member')
tag_keys = self._get_multi_param('TagKeys.member')
@@ -301,6 +444,7 @@ class ELBV2Response(BaseResponse):
template = self.response_template(REMOVE_TAGS_TEMPLATE)
return template.render()
+ @amzn_request_id
def describe_tags(self):
resource_arns = self._get_multi_param('ResourceArns.member')
resources = []
@@ -320,6 +464,125 @@ class ELBV2Response(BaseResponse):
template = self.response_template(DESCRIBE_TAGS_TEMPLATE)
return template.render(resources=resources)
+ @amzn_request_id
+ def describe_account_limits(self):
+ # Supports paging but not worth implementing yet
+ # marker = self._get_param('Marker')
+ # page_size = self._get_param('PageSize')
+
+ limits = {
+ 'application-load-balancers': 20,
+ 'target-groups': 3000,
+ 'targets-per-application-load-balancer': 30,
+ 'listeners-per-application-load-balancer': 50,
+ 'rules-per-application-load-balancer': 100,
+ 'network-load-balancers': 20,
+ 'targets-per-network-load-balancer': 200,
+ 'listeners-per-network-load-balancer': 50
+ }
+
+ template = self.response_template(DESCRIBE_LIMITS_TEMPLATE)
+ return template.render(limits=limits)
+
+ @amzn_request_id
+ def describe_ssl_policies(self):
+ names = self._get_multi_param('Names.member.')
+ # Supports paging but not worth implementing yet
+ # marker = self._get_param('Marker')
+ # page_size = self._get_param('PageSize')
+
+ policies = SSL_POLICIES
+ if names:
+ policies = filter(lambda policy: policy['name'] in names, policies)
+
+ template = self.response_template(DESCRIBE_SSL_POLICIES_TEMPLATE)
+ return template.render(policies=policies)
+
+ @amzn_request_id
+ def set_ip_address_type(self):
+ arn = self._get_param('LoadBalancerArn')
+ ip_type = self._get_param('IpAddressType')
+
+ self.elbv2_backend.set_ip_address_type(arn, ip_type)
+
+ template = self.response_template(SET_IP_ADDRESS_TYPE_TEMPLATE)
+ return template.render(ip_type=ip_type)
+
+ @amzn_request_id
+ def set_security_groups(self):
+ arn = self._get_param('LoadBalancerArn')
+ sec_groups = self._get_multi_param('SecurityGroups.member.')
+
+ self.elbv2_backend.set_security_groups(arn, sec_groups)
+
+ template = self.response_template(SET_SECURITY_GROUPS_TEMPLATE)
+ return template.render(sec_groups=sec_groups)
+
+ @amzn_request_id
+ def set_subnets(self):
+ arn = self._get_param('LoadBalancerArn')
+ subnets = self._get_multi_param('Subnets.member.')
+
+ subnet_zone_list = self.elbv2_backend.set_subnets(arn, subnets)
+
+ template = self.response_template(SET_SUBNETS_TEMPLATE)
+ return template.render(subnets=subnet_zone_list)
+
+ @amzn_request_id
+ def modify_load_balancer_attributes(self):
+ arn = self._get_param('LoadBalancerArn')
+ attrs = self._get_map_prefix('Attributes.member', key_end='Key', value_end='Value')
+
+ all_attrs = self.elbv2_backend.modify_load_balancer_attributes(arn, attrs)
+
+ template = self.response_template(MODIFY_LOADBALANCER_ATTRS_TEMPLATE)
+ return template.render(attrs=all_attrs)
+
+ @amzn_request_id
+ def describe_load_balancer_attributes(self):
+ arn = self._get_param('LoadBalancerArn')
+ attrs = self.elbv2_backend.describe_load_balancer_attributes(arn)
+
+ template = self.response_template(DESCRIBE_LOADBALANCER_ATTRS_TEMPLATE)
+ return template.render(attrs=attrs)
+
+ @amzn_request_id
+ def modify_target_group(self):
+ arn = self._get_param('TargetGroupArn')
+
+ health_check_proto = self._get_param('HealthCheckProtocol') # 'HTTP' | 'HTTPS' | 'TCP',
+ health_check_port = self._get_param('HealthCheckPort')
+ health_check_path = self._get_param('HealthCheckPath')
+ health_check_interval = self._get_param('HealthCheckIntervalSeconds')
+ health_check_timeout = self._get_param('HealthCheckTimeoutSeconds')
+ healthy_threshold_count = self._get_param('HealthyThresholdCount')
+ unhealthy_threshold_count = self._get_param('UnhealthyThresholdCount')
+ http_codes = self._get_param('Matcher.HttpCode')
+
+ target_group = self.elbv2_backend.modify_target_group(arn, health_check_proto, health_check_port, health_check_path, health_check_interval,
+ health_check_timeout, healthy_threshold_count, unhealthy_threshold_count, http_codes)
+
+ template = self.response_template(MODIFY_TARGET_GROUP_TEMPLATE)
+ return template.render(target_group=target_group)
+
+ @amzn_request_id
+ def modify_listener(self):
+ arn = self._get_param('ListenerArn')
+ port = self._get_param('Port')
+ protocol = self._get_param('Protocol')
+ ssl_policy = self._get_param('SslPolicy')
+ certificates = self._get_list_prefix('Certificates.member')
+ default_actions = self._get_list_prefix('DefaultActions.member')
+
+ # Should really move SSL Policies to models
+ if ssl_policy is not None and ssl_policy not in [item['name'] for item in SSL_POLICIES]:
+ raise RESTError('SSLPolicyNotFound', 'Policy {0} not found'.format(ssl_policy))
+
+ listener = self.elbv2_backend.modify_listener(arn, port, protocol, ssl_policy, certificates, default_actions)
+
+ template = self.response_template(MODIFY_LISTENER_TEMPLATE)
+ return template.render(listener=listener)
+
def _add_tags(self, resource):
tag_values = []
tag_keys = []
@@ -348,14 +611,14 @@ class ELBV2Response(BaseResponse):
ADD_TAGS_TEMPLATE = """
- 360e81f7-1100-11e4-b6ed-0f30EXAMPLE
+ {{ request_id }}
"""
REMOVE_TAGS_TEMPLATE = """
- 360e81f7-1100-11e4-b6ed-0f30EXAMPLE
+ {{ request_id }}
"""
@@ -378,11 +641,10 @@ DESCRIBE_TAGS_TEMPLATE = """
@@ -415,7 +677,7 @@ CREATE_LOAD_BALANCER_TEMPLATE = """
- 1549581b-12b7-11e3-895e-1334aEXAMPLE
+ {{ request_id }}
"""
DELETE_RULE_TEMPLATE = """
- 1549581b-12b7-11e3-895e-1334aEXAMPLE
+ {{ request_id }}
"""
DELETE_TARGET_GROUP_TEMPLATE = """
- 1549581b-12b7-11e3-895e-1334aEXAMPLE
+ {{ request_id }}
"""
DELETE_LISTENER_TEMPLATE = """
- 1549581b-12b7-11e3-895e-1334aEXAMPLE
+ {{ request_id }}
"""
@@ -580,7 +844,7 @@ DESCRIBE_LOAD_BALANCERS_TEMPLATE = """
@@ -671,11 +934,10 @@ DESCRIBE_TARGET_GROUP_ATTRIBUTES_TEMPLATE = """
- 70092c0e-f3a9-11e5-ae48-cff02092876b
+ {{ request_id }}
"""
-
DESCRIBE_LISTENERS_TEMPLATE = """
@@ -706,7 +968,7 @@ DESCRIBE_LISTENERS_TEMPLATE = """
- 70092c0e-f3a9-11e5-ae48-cff02092876b
+ {{ request_id }}
"""
@@ -782,7 +1044,7 @@ REGISTER_TARGETS_TEMPLATE = """
- 83c88b9d-12b7-11e3-8b82-87b12EXAMPLE
+ {{ request_id }}
"""
-
DELETE_LOAD_BALANCER_LISTENERS = """
- 83c88b9d-12b7-11e3-8b82-87b12EXAMPLE
+ {{ request_id }}
"""
@@ -837,7 +1098,7 @@ DESCRIBE_ATTRIBUTES_TEMPLATE = """
- 83c88b9d-12b7-11e3-8b82-87b12EXAMPLE
+ {{ request_id }}
"""
@@ -871,7 +1132,7 @@ MODIFY_ATTRIBUTES_TEMPLATE = """
- 83c88b9d-12b7-11e3-8b82-87b12EXAMPLE
+ {{ request_id }}
"""
@@ -887,7 +1148,7 @@ CREATE_LOAD_BALANCER_POLICY_TEMPLATE = """
- 07b1ecbc-1100-11e3-acaf-dd7edEXAMPLE
+ {{ request_id }}
"""
@@ -895,7 +1156,7 @@ SET_LOAD_BALANCER_POLICIES_OF_LISTENER_TEMPLATE = """
- 0eb9b381-dde0-11e2-8d78-6ddbaEXAMPLE
+ {{ request_id }}
"""
@@ -918,7 +1179,7 @@ DESCRIBE_TARGET_HEALTH_TEMPLATE = """
+
+
+ {% for key, value in limits.items() %}
+
+ {{ key }}
+ {{ value }}
+
+ {% endfor %}
+
+
+
+ {{ request_id }}
+
+"""
+
+DESCRIBE_SSL_POLICIES_TEMPLATE = """
+
+
+ {% for policy in policies %}
+
+ {{ policy['name'] }}
+
+ {% for cipher in policy['ciphers'] %}
+
+ {{ cipher['name'] }}
+ {{ cipher['priority'] }}
+
+ {% endfor %}
+
+
+ {% for proto in policy['ssl_protocols'] %}
+ {{ proto }}
+ {% endfor %}
+
+
+ {% endfor %}
+
+
+
+ {{ request_id }}
+
+"""
+
+SET_IP_ADDRESS_TYPE_TEMPLATE = """
+
+ {{ ip_type }}
+
+
+ {{ request_id }}
+
+"""
+
+SET_SECURITY_GROUPS_TEMPLATE = """
+
+
+ {% for group in sec_groups %}
+ {{ group }}
+ {% endfor %}
+
+
+
+ {{ request_id }}
+
+"""
+
+SET_SUBNETS_TEMPLATE = """
+
+
+ {% for zone_id, subnet_id in subnets %}
+
+ {{ subnet_id }}
+ {{ zone_id }}
+
+ {% endfor %}
+
+
+
+ {{ request_id }}
+
+"""
+
+MODIFY_LOADBALANCER_ATTRS_TEMPLATE = """
+
+
+ {% for key, value in attrs.items() %}
+
+ {% if value == None %}{% else %}{{ value }}{% endif %}
+ {{ key }}
+
+ {% endfor %}
+
+
+
+ {{ request_id }}
+
+"""
+
+DESCRIBE_LOADBALANCER_ATTRS_TEMPLATE = """
+
+
+ {% for key, value in attrs.items() %}
+
+ {% if value == None %}{% else %}{{ value }}{% endif %}
+ {{ key }}
+
+ {% endfor %}
+
+
+
+ {{ request_id }}
+
+"""
+
+MODIFY_TARGET_GROUP_TEMPLATE = """
+
+
+
+ {{ target_group.arn }}
+ {{ target_group.name }}
+ {{ target_group.protocol }}
+ {{ target_group.port }}
+ {{ target_group.vpc_id }}
+ {{ target_group.healthcheck_protocol }}
+ {{ target_group.healthcheck_port }}
+ {{ target_group.healthcheck_path }}
+ {{ target_group.healthcheck_interval_seconds }}
+ {{ target_group.healthcheck_timeout_seconds }}
+ {{ target_group.healthy_threshold_count }}
+ {{ target_group.unhealthy_threshold_count }}
+
+ {{ target_group.http_status_codes }}
+
+
+ {% for load_balancer_arn in target_group.load_balancer_arns %}
+ {{ load_balancer_arn }}
+ {% endfor %}
+
+
+
+
+
+ {{ request_id }}
+
+"""
+
+MODIFY_LISTENER_TEMPLATE = """
+
+
+
+ {{ listener.load_balancer_arn }}
+ {{ listener.protocol }}
+ {% if listener.certificates %}
+
+ {% for cert in listener.certificates %}
+
+ {{ cert }}
+
+ {% endfor %}
+
+ {% endif %}
+ {{ listener.port }}
+ {{ listener.ssl_policy }}
+ {{ listener.arn }}
+
+ {% for action in listener.default_actions %}
+
+ {{ action.type }}
+ {{ action.target_group_arn }}
+
+ {% endfor %}
+
+
+
+
+
+ {{ request_id }}
+
+"""
diff --git a/tests/test_elbv2/test_elbv2.py b/tests/test_elbv2/test_elbv2.py
index 98634c67..85771523 100644
--- a/tests/test_elbv2/test_elbv2.py
+++ b/tests/test_elbv2/test_elbv2.py
@@ -5,7 +5,8 @@ from botocore.exceptions import ClientError
from nose.tools import assert_raises
import sure # noqa
-from moto import mock_elbv2, mock_ec2
+from moto import mock_elbv2, mock_ec2, mock_acm
+from moto.elbv2 import elbv2_backends
@mock_elbv2
@@ -1030,3 +1031,372 @@ def test_describe_invalid_target_group():
# Check error raises correctly
with assert_raises(ClientError):
conn.describe_target_groups(Names=['invalid'])
+
+
+@mock_elbv2
+def test_describe_account_limits():
+ client = boto3.client('elbv2', region_name='eu-central-1')
+
+ resp = client.describe_account_limits()
+ resp['Limits'][0].should.contain('Name')
+ resp['Limits'][0].should.contain('Max')
+
+
+@mock_elbv2
+def test_describe_ssl_policies():
+ client = boto3.client('elbv2', region_name='eu-central-1')
+
+ resp = client.describe_ssl_policies()
+ len(resp['SslPolicies']).should.equal(5)
+
+ resp = client.describe_ssl_policies(Names=['ELBSecurityPolicy-TLS-1-2-2017-01', 'ELBSecurityPolicy-2016-08'])
+ len(resp['SslPolicies']).should.equal(2)
+
+
+@mock_elbv2
+@mock_ec2
+def test_set_ip_address_type():
+ client = 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 = client.create_load_balancer(
+ Name='my-lb',
+ Subnets=[subnet1.id, subnet2.id],
+ SecurityGroups=[security_group.id],
+ Scheme='internal',
+ Tags=[{'Key': 'key_name', 'Value': 'a_value'}])
+ arn = response['LoadBalancers'][0]['LoadBalancerArn']
+
+ # Internal LBs cant be dualstack yet
+ with assert_raises(ClientError):
+ client.set_ip_address_type(
+ LoadBalancerArn=arn,
+ IpAddressType='dualstack'
+ )
+
+ # Create internet facing one
+ response = client.create_load_balancer(
+ Name='my-lb2',
+ Subnets=[subnet1.id, subnet2.id],
+ SecurityGroups=[security_group.id],
+ Scheme='internet-facing',
+ Tags=[{'Key': 'key_name', 'Value': 'a_value'}])
+ arn = response['LoadBalancers'][0]['LoadBalancerArn']
+
+ client.set_ip_address_type(
+ LoadBalancerArn=arn,
+ IpAddressType='dualstack'
+ )
+
+
+@mock_elbv2
+@mock_ec2
+def test_set_security_groups():
+ client = 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')
+ security_group2 = ec2.create_security_group(
+ GroupName='b-security-group', Description='Second 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 = client.create_load_balancer(
+ Name='my-lb',
+ Subnets=[subnet1.id, subnet2.id],
+ SecurityGroups=[security_group.id],
+ Scheme='internal',
+ Tags=[{'Key': 'key_name', 'Value': 'a_value'}])
+ arn = response['LoadBalancers'][0]['LoadBalancerArn']
+
+ client.set_security_groups(
+ LoadBalancerArn=arn,
+ SecurityGroups=[security_group.id, security_group2.id]
+ )
+
+ resp = client.describe_load_balancers(LoadBalancerArns=[arn])
+ len(resp['LoadBalancers'][0]['SecurityGroups']).should.equal(2)
+
+ with assert_raises(ClientError):
+ client.set_security_groups(
+ LoadBalancerArn=arn,
+ SecurityGroups=['non_existant']
+ )
+
+
+@mock_elbv2
+@mock_ec2
+def test_set_subnets():
+ client = 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')
+ subnet3 = ec2.create_subnet(
+ VpcId=vpc.id,
+ CidrBlock='172.28.7.192/26',
+ AvailabilityZone='us-east-1c')
+
+ response = client.create_load_balancer(
+ Name='my-lb',
+ Subnets=[subnet1.id, subnet2.id],
+ SecurityGroups=[security_group.id],
+ Scheme='internal',
+ Tags=[{'Key': 'key_name', 'Value': 'a_value'}])
+ arn = response['LoadBalancers'][0]['LoadBalancerArn']
+
+ client.set_subnets(
+ LoadBalancerArn=arn,
+ Subnets=[subnet1.id, subnet2.id, subnet3.id]
+ )
+
+ resp = client.describe_load_balancers(LoadBalancerArns=[arn])
+ len(resp['LoadBalancers'][0]['AvailabilityZones']).should.equal(3)
+
+ # Only 1 AZ
+ with assert_raises(ClientError):
+ client.set_subnets(
+ LoadBalancerArn=arn,
+ Subnets=[subnet1.id]
+ )
+
+ # Multiple subnets in same AZ
+ with assert_raises(ClientError):
+ client.set_subnets(
+ LoadBalancerArn=arn,
+ Subnets=[subnet1.id, subnet2.id, subnet2.id]
+ )
+
+
+@mock_elbv2
+@mock_ec2
+def test_set_subnets():
+ client = 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 = client.create_load_balancer(
+ Name='my-lb',
+ Subnets=[subnet1.id, subnet2.id],
+ SecurityGroups=[security_group.id],
+ Scheme='internal',
+ Tags=[{'Key': 'key_name', 'Value': 'a_value'}])
+ arn = response['LoadBalancers'][0]['LoadBalancerArn']
+
+ client.modify_load_balancer_attributes(
+ LoadBalancerArn=arn,
+ Attributes=[{'Key': 'idle_timeout.timeout_seconds', 'Value': '600'}]
+ )
+
+ # Check its 600 not 60
+ response = client.describe_load_balancer_attributes(
+ LoadBalancerArn=arn
+ )
+ idle_timeout = list(filter(lambda item: item['Key'] == 'idle_timeout.timeout_seconds', response['Attributes']))[0]
+ idle_timeout['Value'].should.equal('600')
+
+
+@mock_elbv2
+@mock_ec2
+def test_modify_target_group():
+ client = boto3.client('elbv2', region_name='us-east-1')
+ ec2 = boto3.resource('ec2', region_name='us-east-1')
+
+ vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default')
+
+ response = client.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'})
+ arn = response.get('TargetGroups')[0]['TargetGroupArn']
+
+ client.modify_target_group(
+ TargetGroupArn=arn,
+ HealthCheckProtocol='HTTPS',
+ HealthCheckPort='8081',
+ HealthCheckPath='/status',
+ HealthCheckIntervalSeconds=10,
+ HealthCheckTimeoutSeconds=10,
+ HealthyThresholdCount=10,
+ UnhealthyThresholdCount=4,
+ Matcher={'HttpCode': '200-399'}
+ )
+
+ response = client.describe_target_groups(
+ TargetGroupArns=[arn]
+ )
+ response['TargetGroups'][0]['Matcher']['HttpCode'].should.equal('200-399')
+ response['TargetGroups'][0]['HealthCheckIntervalSeconds'].should.equal(10)
+ response['TargetGroups'][0]['HealthCheckPath'].should.equal('/status')
+ response['TargetGroups'][0]['HealthCheckPort'].should.equal('8081')
+ response['TargetGroups'][0]['HealthCheckProtocol'].should.equal('HTTPS')
+ response['TargetGroups'][0]['HealthCheckTimeoutSeconds'].should.equal(10)
+ response['TargetGroups'][0]['HealthyThresholdCount'].should.equal(10)
+ response['TargetGroups'][0]['UnhealthyThresholdCount'].should.equal(4)
+
+
+@mock_elbv2
+@mock_ec2
+@mock_acm
+def test_modify_listener_http_to_https():
+ client = boto3.client('elbv2', region_name='eu-central-1')
+ acm = boto3.client('acm', region_name='eu-central-1')
+ ec2 = boto3.resource('ec2', region_name='eu-central-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='eu-central-1a')
+ subnet2 = ec2.create_subnet(
+ VpcId=vpc.id,
+ CidrBlock='172.28.7.192/26',
+ AvailabilityZone='eu-central-1b')
+
+ response = client.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 = client.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]
+ target_group_arn = target_group['TargetGroupArn']
+
+ # Plain HTTP listener
+ response = client.create_listener(
+ LoadBalancerArn=load_balancer_arn,
+ Protocol='HTTP',
+ Port=80,
+ DefaultActions=[{'Type': 'forward', 'TargetGroupArn': target_group_arn}]
+ )
+ listener_arn = response['Listeners'][0]['ListenerArn']
+
+ response = acm.request_certificate(
+ DomainName='google.com',
+ SubjectAlternativeNames=['google.com', 'www.google.com', 'mail.google.com'],
+ )
+ google_arn = response['CertificateArn']
+ response = acm.request_certificate(
+ DomainName='yahoo.com',
+ SubjectAlternativeNames=['yahoo.com', 'www.yahoo.com', 'mail.yahoo.com'],
+ )
+ yahoo_arn = response['CertificateArn']
+
+ response = client.modify_listener(
+ ListenerArn=listener_arn,
+ Port=443,
+ Protocol='HTTPS',
+ SslPolicy='ELBSecurityPolicy-TLS-1-2-2017-01',
+ Certificates=[
+ {'CertificateArn': google_arn, 'IsDefault': False},
+ {'CertificateArn': yahoo_arn, 'IsDefault': True}
+ ],
+ DefaultActions=[
+ {'Type': 'forward', 'TargetGroupArn': target_group_arn}
+ ]
+ )
+ response['Listeners'][0]['Port'].should.equal(443)
+ response['Listeners'][0]['Protocol'].should.equal('HTTPS')
+ response['Listeners'][0]['SslPolicy'].should.equal('ELBSecurityPolicy-TLS-1-2-2017-01')
+ len(response['Listeners'][0]['Certificates']).should.equal(2)
+
+ # Check default cert
+ listener = elbv2_backends['eu-central-1'].load_balancers[load_balancer_arn].listeners[listener_arn]
+ listener.certificate.should.equal(yahoo_arn)
+
+ # No default cert
+ with assert_raises(ClientError):
+ client.modify_listener(
+ ListenerArn=listener_arn,
+ Port=443,
+ Protocol='HTTPS',
+ SslPolicy='ELBSecurityPolicy-TLS-1-2-2017-01',
+ Certificates=[
+ {'CertificateArn': google_arn, 'IsDefault': False}
+ ],
+ DefaultActions=[
+ {'Type': 'forward', 'TargetGroupArn': target_group_arn}
+ ]
+ )
+
+ # Bad cert
+ with assert_raises(ClientError):
+ client.modify_listener(
+ ListenerArn=listener_arn,
+ Port=443,
+ Protocol='HTTPS',
+ SslPolicy='ELBSecurityPolicy-TLS-1-2-2017-01',
+ Certificates=[
+ {'CertificateArn': 'lalala', 'IsDefault': True}
+ ],
+ DefaultActions=[
+ {'Type': 'forward', 'TargetGroupArn': target_group_arn}
+ ]
+ )