
org.apache.stratos.aws.extension.AWSLoadBalancer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of apache-stratos-aws-extension Show documentation
Show all versions of apache-stratos-aws-extension Show documentation
Apache Stratos AWS Extension for Load Balancing
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF 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.apache.stratos.aws.extension;
import com.amazonaws.services.elasticloadbalancing.model.CreateAppCookieStickinessPolicyResult;
import com.amazonaws.services.elasticloadbalancing.model.Instance;
import com.amazonaws.services.elasticloadbalancing.model.Listener;
import com.amazonaws.services.elasticloadbalancing.model.LoadBalancerDescription;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.stratos.aws.extension.exception.PersistenceException;
import org.apache.stratos.aws.extension.persistence.FileBasedPersistenceManager;
import org.apache.stratos.aws.extension.persistence.PersistenceManager;
import org.apache.stratos.aws.extension.persistence.dto.LBInfoDTO;
import org.apache.stratos.load.balancer.common.domain.Cluster;
import org.apache.stratos.load.balancer.common.domain.Member;
import org.apache.stratos.load.balancer.common.domain.Service;
import org.apache.stratos.load.balancer.common.domain.Topology;
import org.apache.stratos.load.balancer.extension.api.LoadBalancer;
import org.apache.stratos.load.balancer.extension.api.exception.LoadBalancerExtensionException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class AWSLoadBalancer implements LoadBalancer {
private static final Log log = LogFactory.getLog(AWSLoadBalancer.class);
// A map to store load balancer information
// against the cluster id
private static ConcurrentHashMap clusterIdToLoadBalancerMap = new ConcurrentHashMap();
// Object used to invoke methods related to AWS API
private AWSHelper awsHelper;
// PersistenceManager: used to persist LB Information by tuples of
PersistenceManager persistenceManager;
public AWSLoadBalancer() throws LoadBalancerExtensionException {
awsHelper = new AWSHelper();
persistenceManager = new FileBasedPersistenceManager();
initialize();
}
/*
* configure method iterates over topology and configures the AWS load
* balancers needed. Configuration may involve creating a new load balancer
* for a cluster, updating existing load balancers or deleting unwanted load
* balancers.
*/
public boolean configure(Topology topology)
throws LoadBalancerExtensionException {
log.info("AWS load balancer extension is being reconfigured.");
HashSet activeClusters = new HashSet();
for (Service service : topology.getServices()) {
for (Cluster cluster : service.getClusters()) {
// Check if a load balancer is created for this cluster
if (clusterIdToLoadBalancerMap.containsKey(cluster.getClusterId())) {
// A load balancer is already present for this cluster
// Get the load balancer and update it.
if (log.isDebugEnabled()) {
log.debug("Load balancer for cluster " + cluster.getClusterId() + " is already present.");
}
LoadBalancerInfo loadBalancerInfo = clusterIdToLoadBalancerMap.get(cluster.getClusterId());
String loadBalancerName = loadBalancerInfo.getName();
String region = loadBalancerInfo.getRegion();
// Get all the instances attached - Attach newly added instances to load balancer
// attachedInstances list is useful in finding out what are the new instances which
// should be attached to this load balancer.
List attachedInstances = awsHelper.getAttachedInstances(loadBalancerName, region);
// clusterMembers stores all the members of a cluster.
Collection clusterMembers = cluster.getMembers();
if (clusterMembers.size() > 0) {
activeClusters.add(cluster.getClusterId());
List instancesToAddToLoadBalancer = new ArrayList();
List availabilityZones = new ArrayList();
for (Member member : clusterMembers) {
// if instance id of member is not in
// attachedInstances
// add this to instancesToAddToLoadBalancer
Instance instance = new Instance(awsHelper.getAWSInstanceName(member.getInstanceId()));
if (attachedInstances == null || !attachedInstances.contains(instance)) {
instancesToAddToLoadBalancer.add(instance);
if (log.isDebugEnabled()) {
log.debug("Instance " + awsHelper.getAWSInstanceName(member.getInstanceId()) +
" needs to be registered to load balancer " + loadBalancerName);
}
// LB Common Member has a property 'EC2_AVAILABILITY_ZONE' points to the ec2 availability zone
// for this member. Use the property value to update the LB about the relevant zone
String availabilityZone = getEC2AvaialbilityZoneOfMember(member);
if (availabilityZone != null) {
availabilityZones.add(availabilityZone);
}
}
}
if (instancesToAddToLoadBalancer.size() > 0) {
awsHelper.registerInstancesToLoadBalancer(
loadBalancerName,
instancesToAddToLoadBalancer, region);
}
// update LB with the zones
if (!availabilityZones.isEmpty() && !AWSExtensionContext.getInstance().isOperatingInVPC()) {
awsHelper.addAvailabilityZonesForLoadBalancer(loadBalancerName, availabilityZones, region);
}
}
} else {
// Create a new load balancer for this cluster
Collection clusterMembers = cluster.getMembers();
if (clusterMembers.size() > 0) {
Member aMember = clusterMembers.iterator().next();
// a unique load balancer name with user-defined prefix and a sequence number.
String loadBalancerName = awsHelper.generateLoadBalancerName(cluster.getServiceName());
String region = awsHelper.getAWSRegion(aMember.getInstanceId());
// list of AWS listeners obtained using port mappings of one of the members of the cluster.
List listenersForThisCluster = awsHelper.getRequiredListeners(aMember);
// Get the initial zone identifier list (a, b, c) to consider in creating
// the LB as defined in aws.properties file
Set initialZones = awsHelper.getInitialZones();
Set initialAvailabilityZones = new HashSet<>();
if (initialZones.isEmpty()) {
// initial availability zones not defined
// use the default (a)
initialAvailabilityZones.add(awsHelper.getAvailabilityZoneFromRegion
(region));
} else {
// prepend the region and construct the availability zone list with
// full names ( + )
for (String zone : initialZones) {
initialAvailabilityZones.add(region + zone);
}
}
// Returns DNS name of load balancer which was created.
// This is used in the domain mapping of this cluster.
String loadBalancerDNSName = awsHelper.createLoadBalancer(loadBalancerName, listenersForThisCluster,
region, initialAvailabilityZones, AWSExtensionContext.getInstance().isOperatingInVPC());
// enable connection draining (default) and cross zone load balancing (if specified in aws-extension.sh)
awsHelper.modifyLBAttributes(loadBalancerName, region, AWSExtensionContext.getInstance().
isCrossZoneLoadBalancingEnabled(), true);
// Add the inbound rule the security group of the load balancer
// For each listener, add a new rule with load balancer port as allowed protocol in the security group.
// if security group id is defined, directly use that
for (Listener listener : listenersForThisCluster) {
int port = listener.getLoadBalancerPort();
if (awsHelper.getLbSecurityGroupIdDefinedInConfiguration() != null && !awsHelper.
getLbSecurityGroupIdDefinedInConfiguration().isEmpty()) {
for (String protocol : awsHelper.getAllowedProtocolsForLBSecurityGroup()) {
awsHelper.addInboundRuleToSecurityGroup(awsHelper.getLbSecurityGroupIdDefinedInConfiguration(),
region, protocol, port);
}
} else if (awsHelper.getLbSecurityGroupName() != null && !awsHelper
.getLbSecurityGroupName().isEmpty()) {
for (String protocol : awsHelper.getAllowedProtocolsForLBSecurityGroup()) {
awsHelper.addInboundRuleToSecurityGroup(awsHelper.getSecurityGroupId(awsHelper
.getLbSecurityGroupName(), region), region, protocol, port);
}
}
}
log.info("Load balancer '" + loadBalancerDNSName + "' created for cluster '" + cluster.getClusterId());
// Register instances in the cluster to load balancer
List instances = new ArrayList();
List availabilityZones = new ArrayList();
for (Member member : clusterMembers) {
String instanceId = member.getInstanceId();
if (log.isDebugEnabled()) {
log.debug("Instance " + awsHelper.getAWSInstanceName(instanceId) + " needs to be registered to load balancer "
+ loadBalancerName);
}
Instance instance = new Instance();
instance.setInstanceId(awsHelper.getAWSInstanceName(instanceId));
instances.add(instance);
// LB Common Member has a property 'EC2_AVAILABILITY_ZONE' which points to the ec2 availability
// zone for this member. Use the property value to update the LB about the relevant zone
String availabilityZone = getEC2AvaialbilityZoneOfMember(member);
if (availabilityZone != null) {
availabilityZones.add(availabilityZone);
}
}
awsHelper.registerInstancesToLoadBalancer(loadBalancerName, instances, region);
// update LB with the zones
if (!availabilityZones.isEmpty() && !AWSExtensionContext.getInstance().isOperatingInVPC()) {
awsHelper.addAvailabilityZonesForLoadBalancer(loadBalancerName, availabilityZones, region);
}
// add stickiness policy
if (awsHelper.getAppStickySessionCookie() != null && !awsHelper.getAppStickySessionCookie().isEmpty()) {
CreateAppCookieStickinessPolicyResult result = awsHelper.createStickySessionPolicy(loadBalancerName,
awsHelper.getAppStickySessionCookie(), Constants.STICKINESS_POLICY, region);
if (result != null) {
// Take a single port mapping from a member, and apply the policy for
// the LB Listener port (Proxy port of the port mapping)
awsHelper.applyPolicyToLBListenerPorts(aMember.getPorts(), loadBalancerName,
Constants.STICKINESS_POLICY, region);
}
}
// persist LB info
try {
persistenceManager.persist(new LBInfoDTO(loadBalancerName, cluster.getClusterId(), region));
} catch (PersistenceException e) {
log.error("Unable to persist LB Information for " + loadBalancerName + ", cluster id " +
cluster.getClusterId());
}
LoadBalancerInfo loadBalancerInfo = new LoadBalancerInfo(
loadBalancerName, region);
clusterIdToLoadBalancerMap.put(cluster.getClusterId(),
loadBalancerInfo);
activeClusters.add(cluster.getClusterId());
}
pause(3000);
}
}
}
// if 'terminate.lb.on.cluster.removal' = true in aws-extension.sh
if (AWSExtensionContext.getInstance().terminateLBOnClusterRemoval()) {
// Find out clusters which were present earlier but are not now.
List clustersToRemoveFromMap = new ArrayList();
// TODO: improve using an iterator and removing the unwanted cluster id in this loop
for (String clusterId : clusterIdToLoadBalancerMap.keySet()) {
if (!activeClusters.contains(clusterId)) {
clustersToRemoveFromMap.add(clusterId);
if (log.isDebugEnabled()) {
log.debug("Load balancer for cluster " + clusterId
+ " needs to be removed.");
}
}
}
// Delete load balancers associated with these clusters.
for (String clusterId : clustersToRemoveFromMap) {
// Remove load balancer for this cluster.
final String loadBalancerName = clusterIdToLoadBalancerMap.get(clusterId).getName();
final String region = clusterIdToLoadBalancerMap.get(clusterId).getRegion();
awsHelper.deleteLoadBalancer(loadBalancerName, region);
//remove and persist
try {
persistenceManager.remove(new LBInfoDTO(loadBalancerName, clusterId, region));
} catch (PersistenceException e) {
log.error("Unable to persist LB Information for " + loadBalancerName + ", cluster id " +
clusterId);
}
clusterIdToLoadBalancerMap.remove(clusterId);
}
}
activeClusters.clear();
log.info("AWS load balancer extension was reconfigured as per the topology.");
return true;
}
private String getEC2AvaialbilityZoneOfMember(Member member) {
if (member.getProperties() != null) {
return member.getProperties().getProperty(Constants.EC2_AVAILABILITY_ZONE_PROPERTY);
}
return null;
}
/*
* start method is called after extension if configured first time. Does
* nothing but logs the message.
*/
public void start() throws LoadBalancerExtensionException {
log.info("AWS load balancer extension started.");
}
private void initialize() {
// load persisted LB information
Set lbInfo = null;
try {
lbInfo = persistenceManager.retrieve();
} catch (PersistenceException e) {
log.error("Unable to retrieve persisted LB Information", e);
}
if (lbInfo != null) {
for (LBInfoDTO lbInfoDTO : lbInfo) {
LoadBalancerDescription lbDesc = awsHelper.getLoadBalancerDescription(lbInfoDTO.getName(),
lbInfoDTO.getRegion());
if (lbDesc != null) {
clusterIdToLoadBalancerMap.put(lbInfoDTO.getClusterId(), new LoadBalancerInfo(lbInfoDTO.getName(),
lbInfoDTO.getRegion()));
} else {
// make debug
if (log.isInfoEnabled()) {
log.info("Unable to locate LB " + lbInfoDTO.getName());
}
// remove the persisted entry
try {
persistenceManager.remove(new LBInfoDTO(lbInfoDTO.getName(), lbInfoDTO.getClusterId(), lbInfoDTO.getRegion()));
} catch (PersistenceException e) {
log.error("Unable to remove persisted LB Information", e);
}
}
}
}
}
/*
* reload method is called every time after extension if configured. Does
* nothing but logs the message.
*/
public void reload() throws LoadBalancerExtensionException {
// Check what is appropriate to do here.
log.info("AWS load balancer extension reloaded.");
}
/*
* stop method deletes load balancers for all clusters in the topology.
*/
public void stop() throws LoadBalancerExtensionException {
// Remove all load balancers if 'terminate.lbs.on.extension.stop' = true in aws-extension.sh
if (AWSExtensionContext.getInstance().terminateLBsOnExtensionStop()) {
for (Map.Entry lbInfoEntry : clusterIdToLoadBalancerMap
.entrySet()) {
// Remove load balancer
awsHelper.deleteLoadBalancer(lbInfoEntry.getValue().getName(),
lbInfoEntry.getValue().getRegion());
// remove the persisted entry
try {
persistenceManager.remove(new LBInfoDTO(lbInfoEntry.getValue().getName(), lbInfoEntry.getKey(),
lbInfoEntry.getValue().getRegion()));
} catch (PersistenceException e) {
log.error("Unable to remove persisted LB Information", e);
}
}
} else {
if (log.isInfoEnabled()) {
log.info("Not terminating LBs since terminate.lbs.on.extension.stop=false");
}
}
}
private static void pause(long duration) {
// sleep to stop AWS Rate Exceeding: Caused by: com.amazonaws.AmazonServiceException: Rate exceeded
// (Service: AmazonElasticLoadBalancing; Status Code: 400; Error Code: Throttling; Request ID: xxx-xxx)
try {
Thread.sleep(duration);
} catch (InterruptedException ignored) {
}
}
public static ConcurrentHashMap getClusterIdToLoadBalancerMap() {
return clusterIdToLoadBalancerMap;
}
}
/**
* Used to store load balancer name and the region in which it is created. This
* helps in finding region while calling API methods to modify/delete a load
* balancer.
*/
class LoadBalancerInfo {
private String name;
private String region;
public LoadBalancerInfo(String name, String region) {
this.name = name;
this.region = region;
}
public String getName() {
return name;
}
public String getRegion() {
return region;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy