From 49b90879a8b85c0d080692a70c2e7b0904ff0392 Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Sun, 1 Jul 2012 11:31:47 -0700 Subject: [PATCH] added healthcheck to elb --- .../org/jclouds/elb/domain/HealthCheck.java | 222 ++++++++++++++++++ .../org/jclouds/elb/domain/LoadBalancer.java | 63 +++-- .../jclouds/elb/xml/HealthCheckHandler.java | 79 +++++++ .../jclouds/elb/xml/LoadBalancerHandler.java | 18 +- .../features/LoadBalancerClientLiveTest.java | 1 + .../DescribeLoadBalancersResponseTest.java | 7 + .../parse/GetLoadBalancerResponseTest.java | 8 + 7 files changed, 374 insertions(+), 24 deletions(-) create mode 100644 labs/elb/src/main/java/org/jclouds/elb/domain/HealthCheck.java create mode 100644 labs/elb/src/main/java/org/jclouds/elb/xml/HealthCheckHandler.java diff --git a/labs/elb/src/main/java/org/jclouds/elb/domain/HealthCheck.java b/labs/elb/src/main/java/org/jclouds/elb/domain/HealthCheck.java new file mode 100644 index 0000000000..8c14160cf0 --- /dev/null +++ b/labs/elb/src/main/java/org/jclouds/elb/domain/HealthCheck.java @@ -0,0 +1,222 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.elb.domain; + +import com.google.common.base.Objects; +import com.google.common.base.Objects.ToStringHelper; + +/** + * Elastic Load Balancing routinely checks the health of each load-balanced Amazon EC2 instance + * based on the configurations that you specify. If Elastic Load Balancing finds an unhealthy + * instance, it stops sending traffic to the instance and reroutes traffic to healthy instances. + * + * + * @see doc + * + * @author Adrian Cole + */ +public class HealthCheck { + + public static Builder builder() { + return new ConcreteBuilder(); + } + + public Builder toBuilder() { + return new ConcreteBuilder().fromListener(this); + } + + public static abstract class Builder> { + protected abstract T self(); + + protected int healthyThreshold; + protected int interval; + protected String target; + protected int timeout; + protected int unhealthyThreshold; + + /** + * @see HealthCheck#getHealthyThreshold() + */ + public T healthyThreshold(int healthyThreshold) { + this.healthyThreshold = healthyThreshold; + return self(); + } + + /** + * @see HealthCheck#getInterval() + */ + public T interval(int interval) { + this.interval = interval; + return self(); + } + + /** + * @see HealthCheck#getTarget() + */ + public T target(String target) { + this.target = target; + return self(); + } + + /** + * @see HealthCheck#getTimeout() + */ + public T timeout(int timeout) { + this.timeout = timeout; + return self(); + } + + /** + * @see HealthCheck#getUnhealthyThreshold() + */ + public T unhealthyThreshold(int unhealthyThreshold) { + this.unhealthyThreshold = unhealthyThreshold; + return self(); + } + + public HealthCheck build() { + return new HealthCheck(healthyThreshold, interval, target, timeout, unhealthyThreshold); + } + + public T fromListener(HealthCheck in) { + return this.healthyThreshold(in.getHealthyThreshold()).interval(in.getInterval()).target(in.getTarget()) + .timeout(in.getTimeout()).unhealthyThreshold(in.getUnhealthyThreshold()); + } + } + + private static class ConcreteBuilder extends Builder { + @Override + protected ConcreteBuilder self() { + return this; + } + } + + protected final int healthyThreshold; + protected final int interval; + protected final String target; + protected final int timeout; + protected final int unhealthyThreshold; + + protected HealthCheck(int healthyThreshold, int interval, String target, int timeout, int unhealthyThreshold) { + this.healthyThreshold = healthyThreshold; + this.interval = interval; + this.target = target; + this.timeout = timeout; + this.unhealthyThreshold = unhealthyThreshold; + } + + /** + * Specifies the number of consecutive health probe successes required before moving the instance + * to the Healthy state. + */ + public int getHealthyThreshold() { + return healthyThreshold; + } + + /** + * Specifies the approximate interval, in seconds, between health checks of an individual + * instance. + */ + public int getInterval() { + return interval; + } + + /** + * Specifies the instance being checked. The timeout is either TCP, HTTP, HTTPS, or SSL. The + * range of valid ports is one (1) through 65535. + * + *

Note

+ * + * TCP is the default, specified as a TCP: port pair, for example "TCP:5000". In this case a + * healthcheck simply attempts to open a TCP connection to the instance on the specified port. + * Failure to connect within the configured timeout is considered unhealthy.
+ * SSL is also specified as SSL: port pair, for example, SSL:5000.
+ * + * For HTTP or HTTPS timeout, the situation is different. You have to include a ping path in the + * string. HTTP is specified as a HTTP:port;/;PathToPing; grouping, for example + * "HTTP:80/weather/us/wa/seattle". In this case, a HTTP GET request is issued to the instance on + * the given port and path. Any answer other than "200 OK" within the timeout period is + * considered unhealthy. + * + * The total length of the HTTP ping target needs to be 1024 16-bit Unicode characters or less. + */ + public String getTarget() { + return target; + } + + /** + * Specifies the amount of time, in seconds, during which no response means a failed health + * probe. + * + *

Note

This value must be less than the Interval value. + */ + public int getTimeout() { + return timeout; + } + + /** + * Specifies the number of consecutive health probe failures required before moving the instance + * to the Unhealthy state. + */ + public int getUnhealthyThreshold() { + return unhealthyThreshold; + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hashCode(healthyThreshold, interval, target, timeout, unhealthyThreshold); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + HealthCheck other = (HealthCheck) obj; + return Objects.equal(this.healthyThreshold, other.healthyThreshold) + && Objects.equal(this.interval, other.interval) && Objects.equal(this.target, other.target) + && Objects.equal(this.timeout, other.timeout) + && Objects.equal(this.unhealthyThreshold, other.unhealthyThreshold); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return string().toString(); + } + + protected ToStringHelper string() { + return Objects.toStringHelper(this).omitNullValues().add("healthyThreshold", healthyThreshold).add("interval", + interval).add("target", target).add("timeout", timeout).add("unhealthyThreshold", unhealthyThreshold); + } + +} diff --git a/labs/elb/src/main/java/org/jclouds/elb/domain/LoadBalancer.java b/labs/elb/src/main/java/org/jclouds/elb/domain/LoadBalancer.java index 4455dd0c36..4c8f95e891 100644 --- a/labs/elb/src/main/java/org/jclouds/elb/domain/LoadBalancer.java +++ b/labs/elb/src/main/java/org/jclouds/elb/domain/LoadBalancer.java @@ -99,13 +99,14 @@ public class LoadBalancer { public static abstract class Builder> { protected abstract T self(); - private String name; - private Date createdTime; - private String dnsName; - private ImmutableSet.Builder instanceIds = ImmutableSet. builder(); - private ImmutableSet.Builder listeners = ImmutableSet. builder(); - private Optional scheme = Optional.absent(); - private Optional VPCId = Optional.absent(); + protected String name; + protected Date createdTime; + protected String dnsName; + protected HealthCheck healthCheck; + protected ImmutableSet.Builder instanceIds = ImmutableSet. builder(); + protected ImmutableSet.Builder listeners = ImmutableSet. builder(); + protected Optional scheme = Optional.absent(); + protected Optional VPCId = Optional.absent(); /** * @see LoadBalancer#getName() @@ -131,6 +132,14 @@ public class LoadBalancer { return self(); } + /** + * @see LoadBalancer#getHealthCheck() + */ + public T healthCheck(HealthCheck healthCheck) { + this.healthCheck = healthCheck; + return self(); + } + /** * @see LoadBalancer#getInstanceIds() */ @@ -180,12 +189,14 @@ public class LoadBalancer { } public LoadBalancer build() { - return new LoadBalancer(name, createdTime, dnsName, instanceIds.build(), listeners.build(), scheme, VPCId); + return new LoadBalancer(name, createdTime, dnsName, healthCheck, instanceIds.build(), listeners.build(), + scheme, VPCId); } public T fromLoadBalancer(LoadBalancer in) { - return this.name(in.getName()).createdTime(in.getCreatedTime()).dnsName(in.getDnsName()).instanceIds( - in.getInstanceIds()).scheme(in.getScheme().orNull()).VPCId(in.getVPCId().orNull()); + return this.name(in.getName()).createdTime(in.getCreatedTime()).dnsName(in.getDnsName()).healthCheck( + in.getHealthCheck()).instanceIds(in.getInstanceIds()).scheme(in.getScheme().orNull()).VPCId( + in.getVPCId().orNull()); } } @@ -196,19 +207,22 @@ public class LoadBalancer { } } - private final String name; - private final Date createdTime; - private final String dnsName; - private final Set instanceIds; - private final Set listeners; - private final Optional scheme; - private final Optional VPCId; + protected final String name; + protected final Date createdTime; + protected final String dnsName; + protected final HealthCheck healthCheck; + protected final Set instanceIds; + protected final Set listeners; + protected final Optional scheme; + protected final Optional VPCId; - protected LoadBalancer(String name, Date createdTime, String dnsName, Iterable instanceIds, - Iterable listeners, Optional scheme, Optional VPCId) { + protected LoadBalancer(String name, Date createdTime, String dnsName, HealthCheck healthCheck, + Iterable instanceIds, Iterable listeners, Optional scheme, + Optional VPCId) { this.name = name; this.createdTime = createdTime; this.dnsName = dnsName; + this.healthCheck = healthCheck; this.instanceIds = ImmutableSet.copyOf(instanceIds); this.listeners = ImmutableSet.copyOf(listeners); this.scheme = scheme; @@ -237,6 +251,13 @@ public class LoadBalancer { return dnsName; } + /** + * Specifies information regarding the various health probes conducted on the LoadBalancer. + */ + public HealthCheck getHealthCheck() { + return healthCheck; + } + /** * Provides a list of EC2 instance IDs for the LoadBalancer. */ @@ -295,8 +316,8 @@ public class LoadBalancer { @Override public String toString() { return Objects.toStringHelper(this).omitNullValues().add("name", name).add("createdTime", createdTime).add( - "dnsName", dnsName).add("instanceIds", instanceIds).add("listeners", listeners).add("scheme", - scheme.orNull()).add("VPCId", VPCId.orNull()).toString(); + "dnsName", dnsName).add("healthCheck", healthCheck).add("instanceIds", instanceIds).add("listeners", + listeners).add("scheme", scheme.orNull()).add("VPCId", VPCId.orNull()).toString(); } } diff --git a/labs/elb/src/main/java/org/jclouds/elb/xml/HealthCheckHandler.java b/labs/elb/src/main/java/org/jclouds/elb/xml/HealthCheckHandler.java new file mode 100644 index 0000000000..7bab62e0df --- /dev/null +++ b/labs/elb/src/main/java/org/jclouds/elb/xml/HealthCheckHandler.java @@ -0,0 +1,79 @@ +/** + * Licensed to jclouds, Inc. (jclouds) under one or more + * contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. jclouds licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jclouds.elb.xml; + +import static org.jclouds.util.SaxUtils.currentOrNull; +import static org.jclouds.util.SaxUtils.equalsOrSuffix; + +import org.jclouds.elb.domain.HealthCheck; +import org.jclouds.http.functions.ParseSax; +import org.xml.sax.SAXException; + +/** + * @see xml + * + * @author Adrian Cole + */ +public class HealthCheckHandler extends ParseSax.HandlerForGeneratedRequestWithResult { + + private StringBuilder currentText = new StringBuilder(); + private HealthCheck.Builder builder = HealthCheck.builder(); + + /** + * {@inheritDoc} + */ + @Override + public HealthCheck getResult() { + try { + return builder.build(); + } finally { + builder = HealthCheck.builder(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void endElement(String uri, String name, String qName) throws SAXException { + if (equalsOrSuffix(qName, "HealthyThreshold")) { + builder.healthyThreshold(Integer.parseInt(currentOrNull(currentText))); + } else if (equalsOrSuffix(qName, "Interval")) { + builder.interval(Integer.parseInt(currentOrNull(currentText))); + } else if (equalsOrSuffix(qName, "Target")) { + builder.target(currentOrNull(currentText)); + } else if (equalsOrSuffix(qName, "Timeout")) { + builder.timeout(Integer.parseInt(currentOrNull(currentText))); + } else if (equalsOrSuffix(qName, "UnhealthyThreshold")) { + builder.unhealthyThreshold(Integer.parseInt(currentOrNull(currentText))); + } + currentText = new StringBuilder(); + } + + /** + * {@inheritDoc} + */ + @Override + public void characters(char ch[], int start, int length) { + currentText.append(ch, start, length); + } + +} diff --git a/labs/elb/src/main/java/org/jclouds/elb/xml/LoadBalancerHandler.java b/labs/elb/src/main/java/org/jclouds/elb/xml/LoadBalancerHandler.java index c703edd823..5f8ac784b4 100644 --- a/labs/elb/src/main/java/org/jclouds/elb/xml/LoadBalancerHandler.java +++ b/labs/elb/src/main/java/org/jclouds/elb/xml/LoadBalancerHandler.java @@ -37,18 +37,21 @@ import org.xml.sax.SAXException; * @author Adrian Cole */ public class LoadBalancerHandler extends ParseSax.HandlerForGeneratedRequestWithResult { - private final DateService dateService; - private final ListenerWithPoliciesHandler listenerHandler; + protected final DateService dateService; + protected final HealthCheckHandler healthCheckHandler; + protected final ListenerWithPoliciesHandler listenerHandler; @Inject - protected LoadBalancerHandler(DateService dateService, ListenerWithPoliciesHandler listenerHandler) { + protected LoadBalancerHandler(DateService dateService, HealthCheckHandler healthCheckHandler, ListenerWithPoliciesHandler listenerHandler) { this.dateService = dateService; + this.healthCheckHandler = healthCheckHandler; this.listenerHandler = listenerHandler; } private StringBuilder currentText = new StringBuilder(); private LoadBalancer.Builder builder = LoadBalancer.builder(); + private boolean inHealthCheck; private boolean inListeners; protected int memberDepth; @@ -72,6 +75,8 @@ public class LoadBalancerHandler extends ParseSax.HandlerForGeneratedRequestWith public void startElement(String url, String name, String qName, Attributes attributes) throws SAXException { if (equalsOrSuffix(qName, "member")) { memberDepth++; + } else if (equalsOrSuffix(qName, "HealthCheck")) { + inHealthCheck = true; } else if (equalsOrSuffix(qName, "ListenerDescriptions")) { inListeners = true; } @@ -90,6 +95,9 @@ public class LoadBalancerHandler extends ParseSax.HandlerForGeneratedRequestWith memberDepth--; } else if (equalsOrSuffix(qName, "ListenerDescriptions")) { inListeners = false; + } else if (equalsOrSuffix(qName, "HealthCheck")) { + builder.healthCheck(healthCheckHandler.getResult()); + inHealthCheck = false; } else if (equalsOrSuffix(qName, "LoadBalancerName")) { builder.name(currentOrNull(currentText)); } else if (equalsOrSuffix(qName, "CreatedTime")) { @@ -102,6 +110,8 @@ public class LoadBalancerHandler extends ParseSax.HandlerForGeneratedRequestWith builder.scheme(Scheme.fromValue(currentOrNull(currentText))); } else if (equalsOrSuffix(qName, "VPCId")) { builder.VPCId(currentOrNull(currentText)); + } else if (inHealthCheck) { + healthCheckHandler.endElement(uri, name, qName); } else if (inListeners) { listenerHandler.endElement(uri, name, qName); } @@ -121,6 +131,8 @@ public class LoadBalancerHandler extends ParseSax.HandlerForGeneratedRequestWith public void characters(char ch[], int start, int length) { if (inListeners) { listenerHandler.characters(ch, start, length); + } else if (inHealthCheck) { + healthCheckHandler.characters(ch, start, length); } else { currentText.append(ch, start, length); } diff --git a/labs/elb/src/test/java/org/jclouds/elb/features/LoadBalancerClientLiveTest.java b/labs/elb/src/test/java/org/jclouds/elb/features/LoadBalancerClientLiveTest.java index 6c8feae889..836cd58714 100644 --- a/labs/elb/src/test/java/org/jclouds/elb/features/LoadBalancerClientLiveTest.java +++ b/labs/elb/src/test/java/org/jclouds/elb/features/LoadBalancerClientLiveTest.java @@ -38,6 +38,7 @@ public class LoadBalancerClientLiveTest extends BaseELBClientLiveTest { checkNotNull(loadBalancer.getName(), "While Name can be null for a LoadBalancer, its Optional wrapper cannot."); checkNotNull(loadBalancer.getCreatedTime(), "CreatedTime cannot be null for a LoadBalancer."); checkNotNull(loadBalancer.getDnsName(), "DnsName cannot be null for a LoadBalancer."); + checkNotNull(loadBalancer.getHealthCheck(), "HealthCheck cannot be null for a LoadBalancer."); checkNotNull(loadBalancer.getScheme(), "While Scheme can be null for a LoadBalancer, its Optional wrapper cannot."); checkNotNull(loadBalancer.getVPCId(), "While VPCId can be null for a LoadBalancer, its Optional wrapper cannot."); diff --git a/labs/elb/src/test/java/org/jclouds/elb/parse/DescribeLoadBalancersResponseTest.java b/labs/elb/src/test/java/org/jclouds/elb/parse/DescribeLoadBalancersResponseTest.java index 0e511a08c0..2d303ec5f7 100644 --- a/labs/elb/src/test/java/org/jclouds/elb/parse/DescribeLoadBalancersResponseTest.java +++ b/labs/elb/src/test/java/org/jclouds/elb/parse/DescribeLoadBalancersResponseTest.java @@ -24,6 +24,7 @@ import java.io.InputStream; import org.jclouds.collect.PaginatedSet; import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.elb.domain.HealthCheck; import org.jclouds.elb.domain.ListenerWithPolicies; import org.jclouds.elb.domain.LoadBalancer; import org.jclouds.elb.domain.Protocol; @@ -58,6 +59,12 @@ public class DescribeLoadBalancersResponseTest extends BaseHandlerTest { .name("my-load-balancer") .createdTime(new SimpleDateFormatDateService().iso8601DateParse("2010-03-03T20:54:45.110Z")) .dnsName("my-load-balancer-1400212309.us-east-1.elb.amazonaws.com") + .healthCheck(HealthCheck.builder() + .interval(300) + .target("HTTP:80/index.html") + .healthyThreshold(3) + .timeout(30) + .unhealthyThreshold(5).build()) .instanceIds(ImmutableSet.of("i-5b33e630", "i-8f26d7e4", "i-5933e632")) .listener(ListenerWithPolicies.builder().protocol(Protocol.HTTP).port(80).instancePort(80).build()) .listener(ListenerWithPolicies.builder().protocol(Protocol.TCP).port(443).instancePort(443).build()) diff --git a/labs/elb/src/test/java/org/jclouds/elb/parse/GetLoadBalancerResponseTest.java b/labs/elb/src/test/java/org/jclouds/elb/parse/GetLoadBalancerResponseTest.java index 6556e122cd..c24e0ade62 100644 --- a/labs/elb/src/test/java/org/jclouds/elb/parse/GetLoadBalancerResponseTest.java +++ b/labs/elb/src/test/java/org/jclouds/elb/parse/GetLoadBalancerResponseTest.java @@ -23,6 +23,7 @@ import static org.testng.Assert.assertEquals; import java.io.InputStream; import org.jclouds.date.internal.SimpleDateFormatDateService; +import org.jclouds.elb.domain.HealthCheck; import org.jclouds.elb.domain.ListenerWithPolicies; import org.jclouds.elb.domain.LoadBalancer; import org.jclouds.elb.domain.Protocol; @@ -50,6 +51,7 @@ public class GetLoadBalancerResponseTest extends BaseHandlerTest { assertEquals(result, expected); assertEquals(result.getCreatedTime(), expected.getCreatedTime()); assertEquals(result.getDnsName(), expected.getDnsName()); + assertEquals(result.getHealthCheck(), expected.getHealthCheck()); assertEquals(result.getScheme(), expected.getScheme()); assertEquals(result.getVPCId(), expected.getVPCId()); } @@ -59,6 +61,12 @@ public class GetLoadBalancerResponseTest extends BaseHandlerTest { .name("my-load-balancer") .createdTime(new SimpleDateFormatDateService().iso8601DateParse("2010-03-03T20:54:45.110Z")) .dnsName("my-load-balancer-1400212309.us-east-1.elb.amazonaws.com") + .healthCheck(HealthCheck.builder() + .interval(300) + .target("HTTP:80/index.html") + .healthyThreshold(3) + .timeout(30) + .unhealthyThreshold(5).build()) .instanceIds(ImmutableSet.of("i-5b33e630", "i-8f26d7e4", "i-5933e632")) .listener(ListenerWithPolicies.builder().protocol(Protocol.HTTP).port(80).instancePort(80).build()) .listener(ListenerWithPolicies.builder().protocol(Protocol.TCP).port(443).instancePort(443).build())