From 096a89266c05e123f98eef10ecf2638aa058e884 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Sat, 21 May 2022 22:55:36 +0000 Subject: [PATCH] ELB:describe_instance_health() - implement OutOfService status (#5154) --- IMPLEMENTATION_COVERAGE.md | 4 +-- docs/docs/services/elb.rst | 2 +- moto/ec2/models/instances.py | 3 ++ moto/elb/models.py | 21 +++++++++++ moto/elb/responses.py | 17 ++------- tests/test_elb/test_elb.py | 69 ++++++++++++++++++++++++++++-------- 6 files changed, 84 insertions(+), 32 deletions(-) diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 837d70efa..e08af8d2f 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -2292,7 +2292,7 @@ ## elb
-62% implemented +65% implemented - [ ] add_tags - [X] apply_security_groups_to_load_balancer @@ -2308,7 +2308,7 @@ - [X] delete_load_balancer_policy - [ ] deregister_instances_from_load_balancer - [ ] describe_account_limits -- [ ] describe_instance_health +- [X] describe_instance_health - [ ] describe_load_balancer_attributes - [X] describe_load_balancer_policies - [ ] describe_load_balancer_policy_types diff --git a/docs/docs/services/elb.rst b/docs/docs/services/elb.rst index 171c6075f..e4cc475eb 100644 --- a/docs/docs/services/elb.rst +++ b/docs/docs/services/elb.rst @@ -39,7 +39,7 @@ elb - [X] delete_load_balancer_policy - [ ] deregister_instances_from_load_balancer - [ ] describe_account_limits -- [ ] describe_instance_health +- [X] describe_instance_health - [ ] describe_load_balancer_attributes - [X] describe_load_balancer_policies - [ ] describe_load_balancer_policy_types diff --git a/moto/ec2/models/instances.py b/moto/ec2/models/instances.py index dcc578d88..90a98942e 100644 --- a/moto/ec2/models/instances.py +++ b/moto/ec2/models/instances.py @@ -353,6 +353,9 @@ class Instance(TaggedEC2Resource, BotoInstance, CloudFormationModel): "Client.UserInitiatedShutdown", ) + def is_running(self): + return self._state.name == "running" + def delete(self, region): # pylint: disable=unused-argument self.terminate() diff --git a/moto/elb/models.py b/moto/elb/models.py index c29d86ffb..2ccde9bff 100644 --- a/moto/elb/models.py +++ b/moto/elb/models.py @@ -6,6 +6,7 @@ from collections import OrderedDict from moto.core import BaseBackend, BaseModel, CloudFormationModel from moto.core.utils import BackendDict from moto.ec2.models import ec2_backends +from moto.ec2.exceptions import InvalidInstanceIdError from uuid import uuid4 from .exceptions import ( BadHealthCheckDefinition, @@ -380,6 +381,26 @@ class ELBBackend(BaseBackend): raise PolicyNotFoundError() return policies + def describe_instance_health(self, lb_name, instances): + provided_ids = [i["InstanceId"] for i in instances] + registered_ids = self.get_load_balancer(lb_name).instance_ids + ec2_backend = ec2_backends[self.region_name] + if len(provided_ids) == 0: + provided_ids = registered_ids + instances = [] + for instance_id in provided_ids: + if instance_id not in registered_ids: + instances.append({"InstanceId": instance_id, "State": "Unknown"}) + else: + try: + instance = ec2_backend.get_instance(instance_id) + state = "InService" if instance.is_running() else "OutOfService" + instances.append({"InstanceId": instance_id, "State": state}) + except InvalidInstanceIdError: + pass + + return instances + def delete_load_balancer_listeners(self, name, ports): balancer = self.load_balancers.get(name, None) listeners = [] diff --git a/moto/elb/responses.py b/moto/elb/responses.py index b8c610538..3c2389e2c 100644 --- a/moto/elb/responses.py +++ b/moto/elb/responses.py @@ -288,21 +288,10 @@ class ELBResponse(BaseResponse): return template.render(policies=policies) def describe_instance_health(self): - load_balancer_name = self._get_param("LoadBalancerName") - provided_instance_ids = [ - list(param.values())[0] - for param in self._get_list_prefix("Instances.member") - ] - registered_instances_id = self.elb_backend.get_load_balancer( - load_balancer_name - ).instance_ids - if len(provided_instance_ids) == 0: - provided_instance_ids = registered_instances_id + lb_name = self._get_param("LoadBalancerName") + instances = self._get_params().get("Instances", []) + instances = self.elb_backend.describe_instance_health(lb_name, instances) template = self.response_template(DESCRIBE_INSTANCE_HEALTH_TEMPLATE) - instances = [] - for instance_id in provided_instance_ids: - state = "InService" if instance_id in registered_instances_id else "Unknown" - instances.append({"InstanceId": instance_id, "State": state}) return template.render(instances=instances) def add_tags(self): diff --git a/tests/test_elb/test_elb.py b/tests/test_elb/test_elb.py index 6e5120aa3..06a259d80 100644 --- a/tests/test_elb/test_elb.py +++ b/tests/test_elb/test_elb.py @@ -868,30 +868,69 @@ def test_connection_settings_attribute(): def test_describe_instance_health(): elb = boto3.client("elb", region_name="us-east-1") ec2 = boto3.client("ec2", region_name="us-east-1") - instances = ec2.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=2, MaxCount=2)[ - "Instances" - ] + # Create three instances + resp = ec2.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=2, MaxCount=2) + instance_ids = [i["InstanceId"] for i in resp["Instances"]] + + # Register two instances with an LB lb_name = "my_load_balancer" elb.create_load_balancer( Listeners=[{"InstancePort": 80, "LoadBalancerPort": 8080, "Protocol": "HTTP"}], LoadBalancerName=lb_name, ) elb.register_instances_with_load_balancer( - LoadBalancerName=lb_name, Instances=[{"InstanceId": instances[0]["InstanceId"]}] + LoadBalancerName=lb_name, + Instances=[{"InstanceId": instance_ids[0]}, {"InstanceId": instance_ids[1]}], ) + + # Describe the Health of all instances + instances_health = elb.describe_instance_health(LoadBalancerName=lb_name)[ + "InstanceStates" + ] + instances_health.should.have.length_of(2) + + +@mock_ec2 +@mock_elb +def test_describe_instance_health__with_instance_ids(): + elb = boto3.client("elb", region_name="us-east-1") + ec2 = boto3.client("ec2", region_name="us-east-1") + # Create three instances + resp = ec2.run_instances(ImageId=EXAMPLE_AMI_ID, MinCount=3, MaxCount=3) + instance_ids = [i["InstanceId"] for i in resp["Instances"]] + + # Register two instances with an LB + lb_name = "my_load_balancer" + elb.create_load_balancer( + Listeners=[{"InstancePort": 80, "LoadBalancerPort": 8080, "Protocol": "HTTP"}], + LoadBalancerName=lb_name, + ) + elb.register_instances_with_load_balancer( + LoadBalancerName=lb_name, + Instances=[{"InstanceId": instance_ids[0]}, {"InstanceId": instance_ids[2]}], + ) + + # Stop one instance + ec2.stop_instances(InstanceIds=[instance_ids[2]]) + + # Describe the Health of instances instances_health = elb.describe_instance_health( LoadBalancerName=lb_name, - Instances=[{"InstanceId": instance["InstanceId"]} for instance in instances], - ) - instances_health["InstanceStates"].should.have.length_of(2) - instances_health["InstanceStates"][0]["InstanceId"].should.equal( - instances[0]["InstanceId"] - ) - instances_health["InstanceStates"][0]["State"].should.equal("InService") - instances_health["InstanceStates"][1]["InstanceId"].should.equal( - instances[1]["InstanceId"] - ) - instances_health["InstanceStates"][1]["State"].should.equal("Unknown") + Instances=[{"InstanceId": iid} for iid in instance_ids], + )["InstanceStates"] + instances_health.should.have.length_of(3) + + # The first instance is healthy + instances_health[0]["InstanceId"].should.equal(instance_ids[0]) + instances_health[0]["State"].should.equal("InService") + + # The second instance was never known to ELB + instances_health[1]["InstanceId"].should.equal(instance_ids[1]) + instances_health[1]["State"].should.equal("Unknown") + + # The third instance was stopped + instances_health[2]["InstanceId"].should.equal(instance_ids[2]) + instances_health[2]["State"].should.equal("OutOfService") @mock_elb