Repository: getmoto/moto

Issue:
elbv2 : Can't Register "Lambda"-Type TargetGroups
Hi All!
I'm trying to mock TargetGroups with TargetType='lambda'
The create_target_group() API fails with:

ValueError: invalid literal for int() with base 10: 'None'

I see that it's trying to parse the "port" for the target group which isn't applicable to Lambda targets ( in addition to other parameters).

Is there any plan to add this support soon? Is there _already_ support and I'm doing something wrong?

Thank you!!!

Traceback (most recent call last):
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/moto/core/models.py", line 74, in wrapper
    result = func(*args, **kwargs)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/moto/core/models.py", line 74, in wrapper
    result = func(*args, **kwargs)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/moto/core/models.py", line 74, in wrapper
    result = func(*args, **kwargs)
  File "/Users/mike/Projects/LUA-Runtime/LS-Deploy/lsclient/tests/test_unittest.py", line 64, in test_deployLambdaListener
    TargetType='lambda'
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/client.py", line 357, in _api_call
    return self._make_api_call(operation_name, kwargs)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/client.py", line 648, in _make_api_call
    operation_model, request_dict, request_context)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/client.py", line 667, in _make_request
    return self._endpoint.make_request(operation_model, request_dict)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/endpoint.py", line 102, in make_request
    return self._send_request(request_dict, operation_model)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/endpoint.py", line 135, in _send_request
    request, operation_model, context)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/endpoint.py", line 167, in _get_response
    request, operation_model)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/endpoint.py", line 218, in _do_get_response
    response_dict, operation_model.output_shape)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 242, in parse
    parsed = self._do_parse(response, shape)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 509, in _do_parse
    parsed = self._parse_shape(shape, start)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 302, in _parse_shape
    return handler(shape, node)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 377, in _handle_structure
    member_shape, member_node)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 302, in _parse_shape
    return handler(shape, node)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 360, in _handle_list
    return super(BaseXMLResponseParser, self)._handle_list(shape, node)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 310, in _handle_list
    parsed.append(self._parse_shape(member_shape, item))
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 302, in _parse_shape
    return handler(shape, node)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 377, in _handle_structure
    member_shape, member_node)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 302, in _parse_shape
    return handler(shape, node)
  File "/Users/localmikemike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 175, in _get_text_content
    return func(self, shape, text)
  File "/Users/mike/.virtualenvs/LS-Deploy/lib/python2.7/site-packages/botocore/parsers.py", line 466, in _handle_integer
    return int(text)
ValueError: invalid literal for int() with base 10: 'None'


Discussion:
I don't believe we support this today so I'm going to change this to a feature request.

Thanks for opening!
Thank you, @spulec !

Resolving patch:
diff --git a/moto/elbv2/responses.py b/moto/elbv2/responses.py
--- a/moto/elbv2/responses.py
+++ b/moto/elbv2/responses.py
@@ -839,9 +839,15 @@ def _add_tags(self, resource):
       <member>
         <TargetGroupArn>{{ target_group.arn }}</TargetGroupArn>
         <TargetGroupName>{{ target_group.name }}</TargetGroupName>
+        {% if target_group.protocol %}
         <Protocol>{{ target_group.protocol }}</Protocol>
+        {% endif %}
+        {% if target_group.port %}
         <Port>{{ target_group.port }}</Port>
+        {% endif %}
+        {% if target_group.vpc_id %}
         <VpcId>{{ target_group.vpc_id }}</VpcId>
+        {% endif %}
         <HealthCheckProtocol>{{ target_group.health_check_protocol }}</HealthCheckProtocol>
         <HealthCheckPort>{{ target_group.healthcheck_port or '' }}</HealthCheckPort>
         <HealthCheckPath>{{ target_group.healthcheck_path or '' }}</HealthCheckPath>
@@ -1077,9 +1083,15 @@ def _add_tags(self, resource):
       <member>
         <TargetGroupArn>{{ target_group.arn }}</TargetGroupArn>
         <TargetGroupName>{{ target_group.name }}</TargetGroupName>
+        {% if target_group.protocol %}
         <Protocol>{{ target_group.protocol }}</Protocol>
+        {% endif %}
+        {% if target_group.port %}
         <Port>{{ target_group.port }}</Port>
+        {% endif %}
+        {% if target_group.vpc_id %}
         <VpcId>{{ target_group.vpc_id }}</VpcId>
+        {% endif %}
         <HealthCheckProtocol>{{ target_group.healthcheck_protocol }}</HealthCheckProtocol>
         <HealthCheckPort>{{ target_group.healthcheck_port or '' }}</HealthCheckPort>
         <HealthCheckPath>{{ target_group.healthcheck_path or '' }}</HealthCheckPath>


Test patch:
diff --git a/tests/test_elbv2/test_elbv2.py b/tests/test_elbv2/test_elbv2.py
--- a/tests/test_elbv2/test_elbv2.py
+++ b/tests/test_elbv2/test_elbv2.py
@@ -246,191 +246,6 @@ def test_create_listeners_without_port():
     )
 
 
-@mock_elbv2
-@mock_ec2
-def test_create_target_group_and_listeners():
-    response, vpc, _, _, _, conn = create_load_balancer()
-
-    load_balancer_arn = response.get("LoadBalancers")[0].get("LoadBalancerArn")
-
-    # Can't create a target group with an invalid protocol
-    with pytest.raises(ClientError):
-        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"},
-        )
-    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]
-    target_group_arn = target_group["TargetGroupArn"]
-
-    # Add tags to the target group
-    conn.add_tags(
-        ResourceArns=[target_group_arn], Tags=[{"Key": "target", "Value": "group"}]
-    )
-    conn.describe_tags(ResourceArns=[target_group_arn])["TagDescriptions"][0][
-        "Tags"
-    ].should.equal([{"Key": "target", "Value": "group"}])
-
-    # Check it's in the describe_target_groups response
-    response = conn.describe_target_groups()
-    response.get("TargetGroups").should.have.length_of(1)
-
-    # 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")
-
-    response = conn.describe_target_groups(
-        LoadBalancerArn=load_balancer_arn, Names=["a-target"]
-    )
-    response.get("TargetGroups").should.have.length_of(1)
-
-    # And another with SSL
-    actions = {"Type": "forward", "TargetGroupArn": target_group.get("TargetGroupArn")}
-    response = conn.create_listener(
-        LoadBalancerArn=load_balancer_arn,
-        Protocol="HTTPS",
-        Port=443,
-        Certificates=[
-            {
-                "CertificateArn": "arn:aws:iam:{}:server-certificate/test-cert".format(
-                    ACCOUNT_ID
-                )
-            }
-        ],
-        DefaultActions=[actions],
-    )
-    listener = response.get("Listeners")[0]
-    listener.get("Port").should.equal(443)
-    listener.get("Protocol").should.equal("HTTPS")
-    listener.get("Certificates").should.equal(
-        [
-            {
-                "CertificateArn": "arn:aws:iam:{}:server-certificate/test-cert".format(
-                    ACCOUNT_ID
-                )
-            }
-        ]
-    )
-    listener.get("DefaultActions").should.equal(
-        [{"TargetGroupArn": target_group.get("TargetGroupArn"), "Type": "forward"}]
-    )
-
-    https_listener_arn = listener.get("ListenerArn")
-
-    response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
-    response.get("Listeners").should.have.length_of(2)
-    response = conn.describe_listeners(ListenerArns=[https_listener_arn])
-    response.get("Listeners").should.have.length_of(1)
-    listener = response.get("Listeners")[0]
-    listener.get("Port").should.equal(443)
-    listener.get("Protocol").should.equal("HTTPS")
-
-    response = conn.describe_listeners(
-        ListenerArns=[http_listener_arn, https_listener_arn]
-    )
-    response.get("Listeners").should.have.length_of(2)
-
-    conn.create_rule(
-        ListenerArn=http_listener_arn,
-        Conditions=[{"Field": "path-pattern", "Values": ["/*"]},],
-        Priority=3,
-        Actions=[actions],
-    )
-    # Try to delete the target group and it fails because there's a
-    # listener referencing it
-    with pytest.raises(ClientError) as e:
-        conn.delete_target_group(TargetGroupArn=target_group.get("TargetGroupArn"))
-    e.value.operation_name.should.equal("DeleteTargetGroup")
-    e.value.args.should.equal(
-        (
-            "An error occurred (ResourceInUse) when calling the DeleteTargetGroup operation: The target group 'arn:aws:elasticloadbalancing:us-east-1:1:targetgroup/a-target/50dc6c495c0c9188' is currently in use by a listener or a rule",
-        )
-    )  # NOQA
-
-    # Delete one listener
-    response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
-    response.get("Listeners").should.have.length_of(2)
-    conn.delete_listener(ListenerArn=http_listener_arn)
-    response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
-    response.get("Listeners").should.have.length_of(1)
-
-    # Then delete the load balancer
-    conn.delete_load_balancer(LoadBalancerArn=load_balancer_arn)
-
-    # It's gone
-    response = conn.describe_load_balancers()
-    response.get("LoadBalancers").should.have.length_of(0)
-
-    # And it deleted the remaining listener
-    with pytest.raises(ClientError) as e:
-        conn.describe_listeners(ListenerArns=[http_listener_arn, https_listener_arn])
-    e.value.response["Error"]["Code"].should.equal("ListenerNotFound")
-
-    # But not the target groups
-    response = conn.describe_target_groups()
-    response.get("TargetGroups").should.have.length_of(1)
-
-    # Which we'll now delete
-    conn.delete_target_group(TargetGroupArn=target_group.get("TargetGroupArn"))
-    response = conn.describe_target_groups()
-    response.get("TargetGroups").should.have.length_of(0)
-
-
-@mock_elbv2
-@mock_ec2
-def test_create_target_group_without_non_required_parameters():
-    response, vpc, _, _, _, conn = create_load_balancer()
-
-    # request without HealthCheckIntervalSeconds parameter
-    # which is default to 30 seconds
-    response = conn.create_target_group(
-        Name="a-target",
-        Protocol="HTTP",
-        Port=8080,
-        VpcId=vpc.id,
-        HealthCheckProtocol="HTTP",
-        HealthCheckPort="8080",
-    )
-    target_group = response.get("TargetGroups")[0]
-    target_group.should_not.be.none
-
-
 @mock_ec2
 @mock_elbv2
 def test_create_rule_forward_config_as_second_arg():
@@ -508,68 +323,6 @@ def test_create_rule_forward_config_as_second_arg():
     )
 
 
-@mock_elbv2
-@mock_ec2
-def test_create_invalid_target_group():
-    conn = 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")
-
-    # Fail to create target group with name which length is 33
-    long_name = "A" * 33
-    with pytest.raises(ClientError):
-        conn.create_target_group(
-            Name=long_name,
-            Protocol="HTTP",
-            Port=8080,
-            VpcId=vpc.id,
-            HealthCheckProtocol="HTTP",
-            HealthCheckPort="8080",
-            HealthCheckPath="/",
-            HealthCheckIntervalSeconds=5,
-            HealthCheckTimeoutSeconds=5,
-            HealthyThresholdCount=5,
-            UnhealthyThresholdCount=2,
-            Matcher={"HttpCode": "200"},
-        )
-
-    invalid_names = ["-name", "name-", "-name-", "example.com", "test@test", "Na--me"]
-    for name in invalid_names:
-        with pytest.raises(ClientError):
-            conn.create_target_group(
-                Name=name,
-                Protocol="HTTP",
-                Port=8080,
-                VpcId=vpc.id,
-                HealthCheckProtocol="HTTP",
-                HealthCheckPort="8080",
-                HealthCheckPath="/",
-                HealthCheckIntervalSeconds=5,
-                HealthCheckTimeoutSeconds=5,
-                HealthyThresholdCount=5,
-                UnhealthyThresholdCount=2,
-                Matcher={"HttpCode": "200"},
-            )
-
-    valid_names = ["name", "Name", "000"]
-    for name in valid_names:
-        conn.create_target_group(
-            Name=name,
-            Protocol="HTTP",
-            Port=8080,
-            VpcId=vpc.id,
-            HealthCheckProtocol="HTTP",
-            HealthCheckPort="8080",
-            HealthCheckPath="/",
-            HealthCheckIntervalSeconds=5,
-            HealthCheckTimeoutSeconds=5,
-            HealthyThresholdCount=5,
-            UnhealthyThresholdCount=2,
-            Matcher={"HttpCode": "200"},
-        )
-
-
 @mock_elbv2
 @mock_ec2
 def test_describe_paginated_balancers():
@@ -852,99 +605,6 @@ def test_terminated_instance_target():
     response.get("TargetHealthDescriptions").should.have.length_of(0)
 
 
-@mock_ec2
-@mock_elbv2
-def test_target_group_attributes():
-    response, vpc, _, _, _, conn = create_load_balancer()
-
-    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]
-
-    # Check it's in the describe_target_groups response
-    response = conn.describe_target_groups()
-    response.get("TargetGroups").should.have.length_of(1)
-    target_group_arn = target_group["TargetGroupArn"]
-
-    # check if Names filter works
-    response = conn.describe_target_groups(Names=[])
-    response = conn.describe_target_groups(Names=["a-target"])
-    response.get("TargetGroups").should.have.length_of(1)
-    target_group_arn = target_group["TargetGroupArn"]
-
-    # The attributes should start with the two defaults
-    response = conn.describe_target_group_attributes(TargetGroupArn=target_group_arn)
-    response["Attributes"].should.have.length_of(2)
-    attributes = {attr["Key"]: attr["Value"] for attr in response["Attributes"]}
-    attributes["deregistration_delay.timeout_seconds"].should.equal("300")
-    attributes["stickiness.enabled"].should.equal("false")
-
-    # Add cookie stickiness
-    response = conn.modify_target_group_attributes(
-        TargetGroupArn=target_group_arn,
-        Attributes=[
-            {"Key": "stickiness.enabled", "Value": "true"},
-            {"Key": "stickiness.type", "Value": "lb_cookie"},
-        ],
-    )
-
-    # The response should have only the keys updated
-    response["Attributes"].should.have.length_of(2)
-    attributes = {attr["Key"]: attr["Value"] for attr in response["Attributes"]}
-    attributes["stickiness.type"].should.equal("lb_cookie")
-    attributes["stickiness.enabled"].should.equal("true")
-
-    # These new values should be in the full attribute list
-    response = conn.describe_target_group_attributes(TargetGroupArn=target_group_arn)
-    response["Attributes"].should.have.length_of(3)
-    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_target_group_invalid_protocol():
-    elbv2 = 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")
-
-    # Can't create a target group with an invalid protocol
-    with pytest.raises(ClientError) as ex:
-        elbv2.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"},
-        )
-    err = ex.value.response["Error"]
-    err["Code"].should.equal("ValidationError")
-    err["Message"].should.contain(
-        "Value /HTTP at 'healthCheckProtocol' failed to satisfy constraint"
-    )
-
-
 @mock_elbv2
 @mock_ec2
 def test_create_rule_priority_in_use():
@@ -1319,58 +979,6 @@ def test_handle_listener_rules():
         )
 
 
-@mock_elbv2
-@mock_ec2
-def test_describe_invalid_target_group():
-    response, vpc, _, _, _, conn = create_load_balancer()
-
-    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"},
-    )
-
-    # Check error raises correctly
-    with pytest.raises(ClientError):
-        conn.describe_target_groups(Names=["invalid"])
-
-
-@mock_elbv2
-@mock_ec2
-def test_describe_target_groups_no_arguments():
-    response, vpc, _, _, _, conn = create_load_balancer()
-
-    response.get("LoadBalancers")[0].get("LoadBalancerArn")
-
-    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"},
-    )
-
-    assert len(conn.describe_target_groups()["TargetGroups"]) == 1
-
-
 @mock_elbv2
 def test_describe_account_limits():
     client = boto3.client("elbv2", region_name="eu-central-1")
@@ -1538,53 +1146,6 @@ def test_modify_load_balancer_attributes_idle_timeout():
     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
diff --git a/tests/test_elbv2/test_elbv2_target_groups.py b/tests/test_elbv2/test_elbv2_target_groups.py
new file mode 100644
--- /dev/null
+++ b/tests/test_elbv2/test_elbv2_target_groups.py
@@ -0,0 +1,523 @@
+import boto3
+from botocore.exceptions import ClientError
+import pytest
+import sure  # noqa # pylint: disable=unused-import
+
+from moto import mock_elbv2, mock_ec2
+from moto.core import ACCOUNT_ID
+
+from .test_elbv2 import create_load_balancer
+
+
+@mock_ec2
+@mock_elbv2
+def test_create_target_group_with_invalid_healthcheck_protocol():
+    _, vpc, _, _, _, conn = create_load_balancer()
+    # Can't create a target group with an invalid protocol
+    with pytest.raises(ClientError) as exc:
+        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"},
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationError")
+    err["Message"].should.equal(
+        "Value /HTTP at 'healthCheckProtocol' failed to satisfy constraint: Member must satisfy enum value set: ['HTTPS', 'HTTP', 'TCP']"
+    )
+
+
+@mock_elbv2
+@mock_ec2
+def test_create_target_group_and_listeners():
+    response, vpc, _, _, _, conn = create_load_balancer()
+
+    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]
+    target_group_arn = target_group["TargetGroupArn"]
+
+    # Add tags to the target group
+    conn.add_tags(
+        ResourceArns=[target_group_arn], Tags=[{"Key": "target", "Value": "group"}]
+    )
+    conn.describe_tags(ResourceArns=[target_group_arn])["TagDescriptions"][0][
+        "Tags"
+    ].should.equal([{"Key": "target", "Value": "group"}])
+
+    # Check it's in the describe_target_groups response
+    response = conn.describe_target_groups()
+    response.get("TargetGroups").should.have.length_of(1)
+
+    # 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")
+
+    response = conn.describe_target_groups(
+        LoadBalancerArn=load_balancer_arn, Names=["a-target"]
+    )
+    response.get("TargetGroups").should.have.length_of(1)
+
+    # And another with SSL
+    actions = {"Type": "forward", "TargetGroupArn": target_group.get("TargetGroupArn")}
+    response = conn.create_listener(
+        LoadBalancerArn=load_balancer_arn,
+        Protocol="HTTPS",
+        Port=443,
+        Certificates=[
+            {
+                "CertificateArn": "arn:aws:iam:{}:server-certificate/test-cert".format(
+                    ACCOUNT_ID
+                )
+            }
+        ],
+        DefaultActions=[actions],
+    )
+    listener = response.get("Listeners")[0]
+    listener.get("Port").should.equal(443)
+    listener.get("Protocol").should.equal("HTTPS")
+    listener.get("Certificates").should.equal(
+        [
+            {
+                "CertificateArn": "arn:aws:iam:{}:server-certificate/test-cert".format(
+                    ACCOUNT_ID
+                )
+            }
+        ]
+    )
+    listener.get("DefaultActions").should.equal(
+        [{"TargetGroupArn": target_group.get("TargetGroupArn"), "Type": "forward"}]
+    )
+
+    https_listener_arn = listener.get("ListenerArn")
+
+    response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
+    response.get("Listeners").should.have.length_of(2)
+    response = conn.describe_listeners(ListenerArns=[https_listener_arn])
+    response.get("Listeners").should.have.length_of(1)
+    listener = response.get("Listeners")[0]
+    listener.get("Port").should.equal(443)
+    listener.get("Protocol").should.equal("HTTPS")
+
+    response = conn.describe_listeners(
+        ListenerArns=[http_listener_arn, https_listener_arn]
+    )
+    response.get("Listeners").should.have.length_of(2)
+
+    conn.create_rule(
+        ListenerArn=http_listener_arn,
+        Conditions=[{"Field": "path-pattern", "Values": ["/*"]},],
+        Priority=3,
+        Actions=[actions],
+    )
+    # Try to delete the target group and it fails because there's a
+    # listener referencing it
+    with pytest.raises(ClientError) as e:
+        conn.delete_target_group(TargetGroupArn=target_group.get("TargetGroupArn"))
+    e.value.operation_name.should.equal("DeleteTargetGroup")
+    e.value.args.should.equal(
+        (
+            "An error occurred (ResourceInUse) when calling the DeleteTargetGroup operation: The target group 'arn:aws:elasticloadbalancing:us-east-1:1:targetgroup/a-target/50dc6c495c0c9188' is currently in use by a listener or a rule",
+        )
+    )  # NOQA
+
+    # Delete one listener
+    response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
+    response.get("Listeners").should.have.length_of(2)
+    conn.delete_listener(ListenerArn=http_listener_arn)
+    response = conn.describe_listeners(LoadBalancerArn=load_balancer_arn)
+    response.get("Listeners").should.have.length_of(1)
+
+    # Then delete the load balancer
+    conn.delete_load_balancer(LoadBalancerArn=load_balancer_arn)
+
+    # It's gone
+    response = conn.describe_load_balancers()
+    response.get("LoadBalancers").should.have.length_of(0)
+
+    # And it deleted the remaining listener
+    with pytest.raises(ClientError) as e:
+        conn.describe_listeners(ListenerArns=[http_listener_arn, https_listener_arn])
+    e.value.response["Error"]["Code"].should.equal("ListenerNotFound")
+
+    # But not the target groups
+    response = conn.describe_target_groups()
+    response.get("TargetGroups").should.have.length_of(1)
+
+    # Which we'll now delete
+    conn.delete_target_group(TargetGroupArn=target_group.get("TargetGroupArn"))
+    response = conn.describe_target_groups()
+    response.get("TargetGroups").should.have.length_of(0)
+
+
+@mock_elbv2
+@mock_ec2
+def test_create_target_group_without_non_required_parameters():
+    response, vpc, _, _, _, conn = create_load_balancer()
+
+    # request without HealthCheckIntervalSeconds parameter
+    # which is default to 30 seconds
+    response = conn.create_target_group(
+        Name="a-target",
+        Protocol="HTTP",
+        Port=8080,
+        VpcId=vpc.id,
+        HealthCheckProtocol="HTTP",
+        HealthCheckPort="8080",
+    )
+    response.get("TargetGroups", []).should.have.length_of(1)
+
+
+@mock_elbv2
+@mock_ec2
+def test_create_invalid_target_group_long_name():
+    conn = 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")
+
+    # Fail to create target group with name which length is 33
+    long_name = "A" * 33
+    with pytest.raises(ClientError) as exc:
+        conn.create_target_group(
+            Name=long_name,
+            Protocol="HTTP",
+            Port=8080,
+            VpcId=vpc.id,
+            HealthCheckProtocol="HTTP",
+            HealthCheckPort="8080",
+            HealthCheckPath="/",
+            HealthCheckIntervalSeconds=5,
+            HealthCheckTimeoutSeconds=5,
+            HealthyThresholdCount=5,
+            UnhealthyThresholdCount=2,
+            Matcher={"HttpCode": "200"},
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationError")
+    err["Message"].should.equal(
+        "Target group name 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' cannot be longer than '32' characters"
+    )
+
+
+@mock_elbv2
+@mock_ec2
+@pytest.mark.parametrize("name", ["-name", "name-", "-name-", "Na--me"])
+def test_create_invalid_target_group_invalid_characters(name):
+    conn = 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")
+
+    with pytest.raises(ClientError) as exc:
+        conn.create_target_group(
+            Name=name,
+            Protocol="HTTP",
+            Port=8080,
+            VpcId=vpc.id,
+            HealthCheckProtocol="HTTP",
+            HealthCheckPort="8080",
+            HealthCheckPath="/",
+            HealthCheckIntervalSeconds=5,
+            HealthCheckTimeoutSeconds=5,
+            HealthyThresholdCount=5,
+            UnhealthyThresholdCount=2,
+            Matcher={"HttpCode": "200"},
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationError")
+    err["Message"].should.equal(
+        f"1 validation error detected: Value '{name}' at 'targetGroup.targetGroupArn.targetGroupName' failed to satisfy constraint: Member must satisfy regular expression pattern: (?!.*--)(?!^-)(?!.*-$)^[A-Za-z0-9-]+$"
+    )
+
+
+@mock_elbv2
+@mock_ec2
+@pytest.mark.parametrize("name", ["example.com", "test@test"])
+def test_create_invalid_target_group_alphanumeric_characters(name):
+    conn = 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")
+
+    with pytest.raises(ClientError) as exc:
+        conn.create_target_group(
+            Name=name,
+            Protocol="HTTP",
+            Port=8080,
+            VpcId=vpc.id,
+            HealthCheckProtocol="HTTP",
+            HealthCheckPort="8080",
+            HealthCheckPath="/",
+            HealthCheckIntervalSeconds=5,
+            HealthCheckTimeoutSeconds=5,
+            HealthyThresholdCount=5,
+            UnhealthyThresholdCount=2,
+            Matcher={"HttpCode": "200"},
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationError")
+    err["Message"].should.equal(
+        f"Target group name '{name}' can only contain characters that are alphanumeric characters or hyphens(-)"
+    )
+
+
+@mock_elbv2
+@mock_ec2
+@pytest.mark.parametrize("name", ["name", "Name", "000"])
+def test_create_valid_target_group_valid_names(name):
+    conn = 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")
+
+    conn.create_target_group(
+        Name=name,
+        Protocol="HTTP",
+        Port=8080,
+        VpcId=vpc.id,
+        HealthCheckProtocol="HTTP",
+        HealthCheckPort="8080",
+        HealthCheckPath="/",
+        HealthCheckIntervalSeconds=5,
+        HealthCheckTimeoutSeconds=5,
+        HealthyThresholdCount=5,
+        UnhealthyThresholdCount=2,
+        Matcher={"HttpCode": "200"},
+    )
+
+
+@mock_ec2
+@mock_elbv2
+def test_target_group_attributes():
+    response, vpc, _, _, _, conn = create_load_balancer()
+
+    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]
+
+    # Check it's in the describe_target_groups response
+    response = conn.describe_target_groups()
+    response.get("TargetGroups").should.have.length_of(1)
+    target_group_arn = target_group["TargetGroupArn"]
+
+    # check if Names filter works
+    response = conn.describe_target_groups(Names=[])
+    response = conn.describe_target_groups(Names=["a-target"])
+    response.get("TargetGroups").should.have.length_of(1)
+    target_group_arn = target_group["TargetGroupArn"]
+
+    # The attributes should start with the two defaults
+    response = conn.describe_target_group_attributes(TargetGroupArn=target_group_arn)
+    response["Attributes"].should.have.length_of(2)
+    attributes = {attr["Key"]: attr["Value"] for attr in response["Attributes"]}
+    attributes["deregistration_delay.timeout_seconds"].should.equal("300")
+    attributes["stickiness.enabled"].should.equal("false")
+
+    # Add cookie stickiness
+    response = conn.modify_target_group_attributes(
+        TargetGroupArn=target_group_arn,
+        Attributes=[
+            {"Key": "stickiness.enabled", "Value": "true"},
+            {"Key": "stickiness.type", "Value": "lb_cookie"},
+        ],
+    )
+
+    # The response should have only the keys updated
+    response["Attributes"].should.have.length_of(2)
+    attributes = {attr["Key"]: attr["Value"] for attr in response["Attributes"]}
+    attributes["stickiness.type"].should.equal("lb_cookie")
+    attributes["stickiness.enabled"].should.equal("true")
+
+    # These new values should be in the full attribute list
+    response = conn.describe_target_group_attributes(TargetGroupArn=target_group_arn)
+    response["Attributes"].should.have.length_of(3)
+    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_target_group_invalid_protocol():
+    elbv2 = 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")
+
+    # Can't create a target group with an invalid protocol
+    with pytest.raises(ClientError) as ex:
+        elbv2.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"},
+        )
+    err = ex.value.response["Error"]
+    err["Code"].should.equal("ValidationError")
+    err["Message"].should.contain(
+        "Value /HTTP at 'healthCheckProtocol' failed to satisfy constraint"
+    )
+
+
+@mock_elbv2
+def test_describe_invalid_target_group():
+    conn = boto3.client("elbv2", region_name="us-east-1")
+
+    # Check error raises correctly
+    with pytest.raises(ClientError) as exc:
+        conn.describe_target_groups(Names=["invalid"])
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("TargetGroupNotFound")
+    err["Message"].should.equal("The specified target group does not exist.")
+
+
+@mock_elbv2
+@mock_ec2
+def test_describe_target_groups_no_arguments():
+    response, vpc, _, _, _, conn = create_load_balancer()
+
+    response.get("LoadBalancers")[0].get("LoadBalancerArn")
+
+    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"},
+    )
+
+    conn.describe_target_groups()["TargetGroups"].should.have.length_of(1)
+
+
+@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
+@pytest.mark.parametrize("target_type", ["instance", "ip", "lambda", "alb", "other"])
+def test_create_target_group_with_target_type(target_type):
+    response, _, _, _, _, conn = create_load_balancer()
+
+    response = conn.create_target_group(Name="a-target", TargetType=target_type)
+
+    group = response["TargetGroups"][0]
+    group.should.have.key("TargetGroupArn")
+    group.should.have.key("TargetGroupName").equal("a-target")
+    group.should.have.key("TargetType").equal(target_type)
+    group.shouldnt.have.key("Protocol")
+    group.shouldnt.have.key("VpcId")
+
+    group = conn.describe_target_groups()["TargetGroups"][0]
+    group.should.have.key("TargetGroupArn")
+    group.should.have.key("TargetGroupName").equal("a-target")
+    group.should.have.key("TargetType").equal(target_type)
+    group.shouldnt.have.key("Protocol")
+    group.shouldnt.have.key("VpcId")